Skip to content

Commit e1936b6

Browse files
authoredMar 23, 2022
feat: implement transparent language server client (#25)
Now that `vscode-sql-notebook` has a UI for connecting to the database, we can use that information to automatically configure a language server to provide completion and formatting. Until further testing shows that this is stable under more conditions, LSP initialization is gated behind a default `false` configuration option.
1 parent 86a7e5f commit e1936b6

14 files changed

+1506
-164
lines changed
 

‎.github/workflows/build.yml

+9-13
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,17 @@ on:
1111
jobs:
1212
package:
1313
runs-on: ubuntu-20.04
14+
defaults:
15+
run:
16+
shell: nix develop -c bash {0}
1417
steps:
15-
- uses: actions/checkout@v2
16-
- uses: actions/setup-node@v1
18+
- uses: actions/checkout@v3
1719
with:
18-
node-version: '16.x'
19-
- uses: actions/cache@v2
20-
with:
21-
path: '**/node_modules'
22-
key: ${{ runner.os }}-modules-${{ hashFiles('**/package-lock.lock') }}
23-
- name: Install dependencies
24-
run: npm ci
25-
- name: Install vsce
26-
run: npm install -g vsce@1.100.0
27-
- name: Package extension
28-
run: vsce package --out sqlnotebook-${{ github.sha }}.vsix
20+
submodules: true
21+
- uses: cachix/install-nix-action@v16
22+
- run: npm ci
23+
- run: ./compile_sqls.fish
24+
- run: npx vsce package --out sqlnotebook-${{ github.sha }}.vsix
2925
- name: Upload vsix as artifact
3026
uses: actions/upload-artifact@v1
3127
with:

‎.github/workflows/publish.yml

+13-10
Original file line numberDiff line numberDiff line change
@@ -18,26 +18,29 @@ permissions:
1818
jobs:
1919
publish:
2020
runs-on: ubuntu-latest
21+
defaults:
22+
run:
23+
shell: nix develop -c bash {0}
2124
steps:
22-
- uses: actions/checkout@v2
25+
- uses: actions/checkout@v3
26+
with:
27+
submodules: true
2328
- name: Parse version
2429
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
25-
- name: Install dependencies
26-
run: npm ci
27-
- name: Install vsce
28-
run: npm install -g vsce@1.100.0
29-
- name: Package extension
30-
run: vsce package --out sqlnotebook-${{ env.RELEASE_VERSION }}.vsix
30+
- uses: cachix/install-nix-action@v16
31+
- run: npm ci
32+
- run: ./compile_sqls.fish
33+
- run: npx vsce package --out sqlnotebook-${{ env.RELEASE_VERSION }}.vsix
3134
- name: Upload vsix as artifact
3235
uses: actions/upload-artifact@v1
3336
with:
3437
name: sqlnotebook-${{ env.RELEASE_VERSION }}.vsix
3538
path: sqlnotebook-${{ env.RELEASE_VERSION }}.vsix
3639
- name: Publish Extension to Microsoft Marketplace
37-
run: vsce publish --packagePath ./sqlnotebook-${{ env.RELEASE_VERSION }}.vsix
40+
run: npx vsce publish --packagePath ./sqlnotebook-${{ env.RELEASE_VERSION }}.vsix
41+
shell: nix develop -c bash {0}
3842
env:
3943
VSCE_PAT: ${{ secrets.VSCE_CREDENTIALS }}
40-
- name: Install OpenVSX CLI
41-
run: npm install -g ovsx
4244
- name: Publish to OpenVSX
4345
run: npx ovsx publish ./sqlnotebook-${{ env.RELEASE_VERSION }}.vsix -p ${{ secrets.OPEN_VSX_CREDENTIALS }}
46+
shell: nix develop -c bash {0}

‎.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
out
22
node_modules
33
*.vsix
4+
sqls_bin
5+
sqls

‎.gitmodules

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "sqls"]
2+
path = sqls
3+
url = https://github.com/cmoog/sqls

‎.vscodeignore

+1
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ vsc-extension-quickstart.md
99
**/*.map
1010
**/*.ts
1111
**/*.tsbuildinfo
12+
sqls

‎CHANGELOG.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Change Log
22

3-
## [Unreleased]
3+
## [Unreleased 0.5.0]
44

5-
- Initial release
5+
- Package `sqls` language server into `vscode-sql-notebook`.
6+
- When running on a compatible arch/os, notebooks will now
7+
benefit from enhacned typed autocomplete and hover information
8+
when connected to a valid database connection.

‎compile_sqls.fish

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/usr/bin/env fish
2+
3+
# This script compiles the ./sqls language server
4+
# into binaries for the most common ARCH/OS combinations.
5+
# Then, we use `upx` to compress the binary to save on
6+
# overall bundle size.
7+
#
8+
# Eventually, these binaries are bundled into the final
9+
# .vsix asset and shipped with the extension. At runtime,
10+
# the extension will check if an available binary exists for
11+
# the given detected ARCH/OS combo. If yes, it will run it as
12+
# a language server to provide enhanced completion support.
13+
14+
15+
set --local arch { arm64, amd64, 386 }
16+
set --local os { linux, darwin, windows }
17+
18+
cd sqls
19+
20+
for a in $arch
21+
for o in $os
22+
if [ "$o" = "darwin" -a "$a" = "386" ]
23+
continue
24+
end
25+
echo "building sqls_"$a"_"$o
26+
set --local binpath "../sqls_bin/sqls_"$a"_"$o
27+
CGO_ENABLED=0 GOOS=$o GOARCH=$a go build -o $binpath -ldflags="-s -w"; or exit 1
28+
29+
# disable this for now, it was causing issues on arm64/darwin
30+
# upx $binpath
31+
end
32+
end

‎flake.nix

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
in
1313
{
1414
devShell = pkgs.mkShell {
15-
packages = with pkgs; [ nodejs-16_x ];
15+
packages = with pkgs; [ nodejs-16_x go_1_17 fish upx ];
1616
};
1717
}
1818
);

‎package-lock.json

+1,292-136
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+14-1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,16 @@
9090
}
9191
]
9292
},
93+
"configuration": {
94+
"title": "SQL Notebook",
95+
"properties": {
96+
"SQLNotebook.useLanguageServer": {
97+
"type": "boolean",
98+
"default": false,
99+
"description": "(Unstable) Use embeded language server for intelligent completion and hover information."
100+
}
101+
}
102+
},
93103
"views": {
94104
"sqlnotebook": [
95105
{
@@ -143,10 +153,12 @@
143153
"fork-ts-checker-webpack-plugin": "^5.0.14",
144154
"glob": "^7.1.7",
145155
"mocha": "^9.2.0",
156+
"ovsx": "^0.3.0",
146157
"prettier": "^2.6.0",
147158
"style-loader": "^1.2.1",
148159
"ts-loader": "^9.2.6",
149160
"typescript": "^4.3.2",
161+
"vsce": "1.100.0",
150162
"vscode-notebook-error-overlay": "^1.0.1",
151163
"vscode-test": "^1.5.2",
152164
"webpack": "^5.65.0",
@@ -158,7 +170,8 @@
158170
"mysql2": "^2.3.0",
159171
"pg": "^8.7.1",
160172
"react": "^17.0.2",
161-
"react-dom": "^17.0.2"
173+
"react-dom": "^17.0.2",
174+
"vscode-languageclient": "^7.0.0"
162175
},
163176
"prettier": {
164177
"semi": true,

‎sqls

Submodule sqls added at 8f60007

‎src/commands.ts

+42-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import {
55
SQLNotebookConnections,
66
} from './connections';
77
import { getPool, PoolConfig } from './driver';
8-
import { storageKey, globalConnPool } from './extension';
8+
import { storageKey, globalConnPool, globalLspClient } from './extension';
9+
import { getCompiledLSPBinaryPath, sqlsDriverFromDriver } from './lsp';
910

1011
export const deleteConnectionConfiguration =
1112
(
@@ -72,6 +73,10 @@ export const connectToDatabase =
7273
const conn = await globalConnPool.pool.getConnection();
7374
await conn.query('SELECT 1'); // essentially a ping to see if the connection works
7475
connectionsSidepanel.setActive(match.name);
76+
if (shouldUseLanguageServer()) {
77+
startLanguageServer(match, password);
78+
}
79+
7580
vscode.window.showInformationMessage(
7681
`Successfully connected to "${match.name}"`
7782
);
@@ -80,7 +85,43 @@ export const connectToDatabase =
8085
// @ts-ignore
8186
`Failed to connect to "${match.name}": ${err.message}`
8287
);
88+
globalLspClient.stop();
8389
globalConnPool.pool = null;
8490
connectionsSidepanel.setActive(null);
8591
}
8692
};
93+
94+
function startLanguageServer(conn: ConnData, password?: string) {
95+
try {
96+
const driver = sqlsDriverFromDriver(conn.driver);
97+
const binPath = getCompiledLSPBinaryPath();
98+
if (!binPath)
99+
throw Error('Platform not supported, language server disabled.');
100+
if (driver) {
101+
globalLspClient.start({
102+
binPath,
103+
host: conn.host,
104+
port: conn.port,
105+
password: password,
106+
driver,
107+
database: conn.database,
108+
user: conn.user,
109+
});
110+
} else {
111+
vscode.window.showWarningMessage(
112+
`Driver ${conn.driver} not supported by language server. Completion support disabled.`
113+
);
114+
}
115+
} catch (e) {
116+
vscode.window.showWarningMessage(
117+
`Language server failed to initialize: ${e}`
118+
);
119+
}
120+
}
121+
122+
function shouldUseLanguageServer(): boolean {
123+
return (
124+
vscode.workspace.getConfiguration('SQLNotebook').get('useLanguageServer') ||
125+
false
126+
);
127+
}

‎src/extension.ts

+3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { SQLNotebookConnections } from './connections';
44
import { connectToDatabase, deleteConnectionConfiguration } from './commands';
55
import { Pool, ExecutionResult, TabularResult, Row } from './driver';
66
import { activateFormProvider } from './form';
7+
import { SqlLspClient } from './lsp';
78

89
const notebookType = 'sql-notebook';
910
export const storageKey = 'sqlnotebook-connections';
@@ -12,6 +13,8 @@ export const globalConnPool: { pool: Pool | null } = {
1213
pool: null,
1314
};
1415

16+
export const globalLspClient = new SqlLspClient();
17+
1518
export function activate(context: vscode.ExtensionContext) {
1619
context.subscriptions.push(
1720
vscode.workspace.registerNotebookSerializer(

‎src/lsp.ts

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import {
2+
LanguageClient,
3+
LanguageClientOptions,
4+
ServerOptions,
5+
} from 'vscode-languageclient/node';
6+
import * as vscode from 'vscode';
7+
import { DriverKey } from './driver';
8+
import path = require('path');
9+
10+
export interface LspConfig {
11+
binPath: string;
12+
host: string;
13+
port: number;
14+
user: string;
15+
password?: string;
16+
database?: string;
17+
driver: SqlsDriver;
18+
}
19+
20+
export type SqlsDriver = 'mysql' | 'postgresql' | 'mysql8' | 'sqlight3'; // TODO: complete
21+
22+
export const sqlsDriverFromDriver = (
23+
driverKey: DriverKey
24+
): SqlsDriver | null => {
25+
switch (driverKey) {
26+
case 'mysql':
27+
return 'mysql';
28+
case 'postgres':
29+
return 'postgresql';
30+
}
31+
return null;
32+
};
33+
34+
export function getCompiledLSPBinaryPath(): string | null {
35+
const { arch, platform } = process;
36+
const goarch = { arm64: 'arm64', x64: 'amd64' }[arch];
37+
const goos = { linux: 'linux', darwin: 'darwin', win32: 'windows' }[
38+
platform.toString()
39+
];
40+
if (!goarch && !goos) return null;
41+
return path.join(
42+
__filename,
43+
'..',
44+
'..',
45+
'..',
46+
'sqls_bin',
47+
`sqls_${goarch}_${goos}`
48+
);
49+
}
50+
51+
export class SqlLspClient {
52+
private client: LanguageClient | null;
53+
constructor() {
54+
this.client = null;
55+
}
56+
start(config: LspConfig) {
57+
let serverOptions: ServerOptions = {
58+
command: config.binPath,
59+
args: [],
60+
};
61+
62+
let clientOptions: LanguageClientOptions = {
63+
documentSelector: [{ language: 'sql' }],
64+
initializationOptions: {
65+
disableCodeAction: true,
66+
connectionConfig: {
67+
driver: config.driver,
68+
user: config.user,
69+
passwd: config.password,
70+
71+
host: config.host,
72+
port: config.port,
73+
74+
dbName: config.database,
75+
76+
proto: 'tcp',
77+
},
78+
},
79+
outputChannel: vscode.window.createOutputChannel('sqls'),
80+
};
81+
82+
this.client = new LanguageClient('sqls', serverOptions, clientOptions);
83+
this.client.start();
84+
}
85+
async stop() {
86+
await this.client?.stop();
87+
}
88+
}

0 commit comments

Comments
 (0)
Please sign in to comment.