Skip to content

Commit 9238534

Browse files
authored
fix(vscode): improve registering of lsp in extension (#5056)
1 parent a645a79 commit 9238534

File tree

3 files changed

+204
-223
lines changed

3 files changed

+204
-223
lines changed

vscode/extension/src/commands/renderModel.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@ import { RenderedModelProvider } from '../providers/renderedModelProvider'
66

77
export async function reRenderModelForSourceFile(
88
sourceUri: string,
9-
lspClient: LSPClient,
9+
lspClient: LSPClient | undefined,
1010
renderedModelProvider: RenderedModelProvider,
1111
): Promise<void> {
1212
const renderedUri = renderedModelProvider.getRenderedUriForSource(sourceUri)
1313
if (!renderedUri) {
1414
return // No rendered model exists for this source file
1515
}
16+
if (!lspClient) {
17+
return
18+
}
1619

1720
// Call the render model API
1821
const result = await lspClient.call_custom_method('sqlmesh/render_model', {

vscode/extension/src/extension.ts

Lines changed: 100 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,51 @@
1-
import { format } from './commands/format'
1+
/**********************************************************************
2+
* Extension entry point *
3+
*********************************************************************/
4+
25
import * as vscode from 'vscode'
3-
import {
4-
createOutputChannel,
5-
onDidChangeConfiguration,
6-
registerCommand,
7-
} from './utilities/common/vscodeapi'
8-
import { registerLogger, traceInfo, traceVerbose } from './utilities/common/log'
9-
import { onDidChangePythonInterpreter } from './utilities/common/python'
10-
import { LSPClient } from './lsp/lsp'
11-
import { AuthenticationProviderTobikoCloud } from './auth/auth'
6+
7+
import { format } from './commands/format'
128
import { signOut } from './commands/signout'
139
import { signIn } from './commands/signin'
1410
import { signInSpecifyFlow } from './commands/signinSpecifyFlow'
1511
import { renderModel, reRenderModelForSourceFile } from './commands/renderModel'
1612
import { stop } from './commands/stop'
1713
import { printEnvironment } from './commands/printEnvironment'
18-
import { isErr } from '@bus/result'
14+
15+
import {
16+
createOutputChannel,
17+
onDidChangeConfiguration,
18+
registerCommand,
19+
} from './utilities/common/vscodeapi'
20+
import {
21+
registerLogger,
22+
traceInfo,
23+
traceVerbose,
24+
traceError,
25+
} from './utilities/common/log'
26+
import { onDidChangePythonInterpreter } from './utilities/common/python'
27+
import { sleep } from './utilities/sleep'
1928
import { handleError } from './utilities/errors'
29+
2030
import { selector, completionProvider } from './completion/completion'
2131
import { LineagePanel } from './webviews/lineagePanel'
2232
import { RenderedModelProvider } from './providers/renderedModelProvider'
23-
import { sleep } from './utilities/sleep'
33+
2434
import {
2535
controller as testController,
2636
setupTestController,
2737
} from './tests/tests'
2838

39+
import { isErr } from '@bus/result'
40+
import { AuthenticationProviderTobikoCloud } from './auth/auth'
41+
import { LSPClient } from './lsp/lsp'
42+
43+
/** Singleton LSP client for the extension. */
2944
let lspClient: LSPClient | undefined
3045

31-
// This method is called when your extension is activated
32-
// Your extension is activated the very first time the command is executed
46+
/** Handle to the (single) test controller disposable so we can replace it on restart. */
47+
let testControllerDisposable: vscode.Disposable | undefined
48+
3349
export async function activate(context: vscode.ExtensionContext) {
3450
const extensionOutputChannel = createOutputChannel('sqlmesh')
3551
context.subscriptions.push(
@@ -38,7 +54,6 @@ export async function activate(context: vscode.ExtensionContext) {
3854
)
3955
traceInfo('Activating SQLMesh extension')
4056

41-
traceInfo('Registering authentication provider')
4257
const authProvider = new AuthenticationProviderTobikoCloud()
4358
context.subscriptions.push(
4459
vscode.authentication.registerAuthenticationProvider(
@@ -48,33 +63,70 @@ export async function activate(context: vscode.ExtensionContext) {
4863
{ supportsMultipleAccounts: false },
4964
),
5065
)
51-
traceInfo('Authentication provider registered')
5266

67+
const restartLsp = async (invokedByUser = false): Promise<void> => {
68+
if (!lspClient) {
69+
lspClient = new LSPClient()
70+
}
71+
72+
traceVerbose('Restarting SQLMesh LSP client')
73+
const result = await lspClient.restart(invokedByUser)
74+
if (isErr(result)) {
75+
await handleError(
76+
authProvider,
77+
restartLsp,
78+
result.error,
79+
'LSP restart failed',
80+
)
81+
return
82+
}
83+
84+
// push once to avoid duplicate disposables on multiple restarts
85+
if (!context.subscriptions.includes(lspClient)) {
86+
context.subscriptions.push(lspClient)
87+
}
88+
89+
/* Replace the test controller each time we restart the client */
90+
if (testControllerDisposable) {
91+
testControllerDisposable.dispose()
92+
}
93+
testControllerDisposable = setupTestController(lspClient)
94+
context.subscriptions.push(testControllerDisposable)
95+
}
96+
97+
// commands needing the restart helper
5398
context.subscriptions.push(
5499
vscode.commands.registerCommand(
55100
'sqlmesh.signin',
56-
signIn(authProvider, async () => {
57-
traceInfo('Restarting LSP after sign-in')
58-
await restart()
59-
}),
101+
signIn(authProvider, () => restartLsp()),
60102
),
61-
)
62-
context.subscriptions.push(
63103
vscode.commands.registerCommand(
64104
'sqlmesh.signinSpecifyFlow',
65-
signInSpecifyFlow(authProvider, async () => {
66-
traceInfo('Restarting LSP after sign-in')
67-
await restart()
68-
}),
105+
signInSpecifyFlow(authProvider, () => restartLsp()),
69106
),
70-
)
71-
context.subscriptions.push(
72107
vscode.commands.registerCommand('sqlmesh.signout', signOut(authProvider)),
73108
)
74109

110+
// Instantiate the LSP client (once)
75111
lspClient = new LSPClient()
112+
const startResult = await lspClient.start()
113+
if (isErr(startResult)) {
114+
await handleError(
115+
authProvider,
116+
restartLsp,
117+
startResult.error,
118+
'Failed to start LSP',
119+
)
120+
return // abort activation – nothing else to do
121+
}
122+
123+
context.subscriptions.push(lspClient)
76124

77-
// Create and register the rendered model provider
125+
// Initialize the test controller
126+
testControllerDisposable = setupTestController(lspClient)
127+
context.subscriptions.push(testControllerDisposable, testController)
128+
129+
// Register the rendered model provider
78130
const renderedModelProvider = new RenderedModelProvider()
79131
context.subscriptions.push(
80132
vscode.workspace.registerTextDocumentContentProvider(
@@ -91,7 +143,6 @@ export async function activate(context: vscode.ExtensionContext) {
91143
),
92144
)
93145

94-
// Register the webview
95146
const lineagePanel = new LineagePanel(context.extensionUri, lspClient)
96147
context.subscriptions.push(
97148
vscode.window.registerWebviewViewProvider(
@@ -100,11 +151,10 @@ export async function activate(context: vscode.ExtensionContext) {
100151
),
101152
)
102153

103-
// Add file save listener for auto-rerendering models
154+
// Re‑render model automatically when its source file is saved
104155
context.subscriptions.push(
105156
vscode.workspace.onDidSaveTextDocument(async document => {
106157
if (
107-
lspClient &&
108158
renderedModelProvider.hasRenderedModelForSource(
109159
document.uri.toString(true),
110160
)
@@ -119,73 +169,23 @@ export async function activate(context: vscode.ExtensionContext) {
119169
}),
120170
)
121171

122-
const restart = async (invokedByUser = false) => {
123-
if (lspClient) {
124-
traceVerbose('Restarting LSP client')
125-
const restartResult = await lspClient.restart(invokedByUser)
126-
if (isErr(restartResult)) {
127-
return handleError(
128-
authProvider,
129-
restart,
130-
restartResult.error,
131-
'LSP restart failed',
132-
)
133-
}
134-
context.subscriptions.push(lspClient)
135-
context.subscriptions.push(setupTestController(lspClient))
136-
} else {
137-
lspClient = new LSPClient()
138-
const result = await lspClient.start(invokedByUser)
139-
if (isErr(result)) {
140-
return handleError(
141-
authProvider,
142-
restart,
143-
result.error,
144-
'Failed to start LSP',
145-
)
146-
} else {
147-
context.subscriptions.push(lspClient)
148-
context.subscriptions.push(setupTestController(lspClient))
149-
}
150-
}
151-
}
152-
172+
// miscellaneous commands
153173
context.subscriptions.push(
154174
vscode.commands.registerCommand(
155175
'sqlmesh.format',
156-
format(authProvider, lspClient, restart),
176+
format(authProvider, lspClient, restartLsp),
157177
),
178+
registerCommand('sqlmesh.restart', () => restartLsp(true)),
179+
registerCommand('sqlmesh.stop', stop(lspClient)),
180+
registerCommand('sqlmesh.printEnvironment', printEnvironment()),
158181
)
159182

160183
context.subscriptions.push(
161-
onDidChangePythonInterpreter(async () => {
162-
await restart()
163-
}),
164-
onDidChangeConfiguration(async () => {
165-
await restart()
166-
}),
167-
registerCommand(`sqlmesh.restart`, async () => {
168-
await restart(true)
169-
}),
170-
registerCommand(`sqlmesh.stop`, stop(lspClient)),
171-
registerCommand(`sqlmesh.printEnvironment`, printEnvironment()),
184+
onDidChangePythonInterpreter(() => restartLsp()),
185+
onDidChangeConfiguration(() => restartLsp()),
172186
)
173187

174-
const result = await lspClient.start()
175-
if (isErr(result)) {
176-
return handleError(
177-
authProvider,
178-
restart,
179-
result.error,
180-
'Failed to start LSP',
181-
)
182-
} else {
183-
context.subscriptions.push(lspClient)
184-
context.subscriptions.push(setupTestController(lspClient))
185-
context.subscriptions.push(testController)
186-
}
187-
188-
if (lspClient && !lspClient.hasCompletionCapability()) {
188+
if (!lspClient.hasCompletionCapability()) {
189189
context.subscriptions.push(
190190
vscode.languages.registerCompletionItemProvider(
191191
selector,
@@ -194,12 +194,19 @@ export async function activate(context: vscode.ExtensionContext) {
194194
)
195195
}
196196

197-
traceInfo('Extension activated')
197+
traceInfo('SQLMesh extension activated')
198198
}
199199

200200
// This method is called when your extension is deactivated
201201
export async function deactivate() {
202-
if (lspClient) {
203-
await lspClient.dispose()
202+
try {
203+
if (testControllerDisposable) {
204+
testControllerDisposable.dispose()
205+
}
206+
if (lspClient) {
207+
await lspClient.dispose()
208+
}
209+
} catch (e) {
210+
traceError(`Error during deactivate: ${e}`)
204211
}
205212
}

0 commit comments

Comments
 (0)