Skip to content

Commit c8b0b7a

Browse files
committed
feat: add plan diff lsp endpoint and vscode command
1 parent a9ef531 commit c8b0b7a

6 files changed

Lines changed: 180 additions & 0 deletions

File tree

sqlmesh/lsp/custom.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,30 @@ class FormatProjectResponse(CustomMethodResponseBaseClass):
143143
"""
144144

145145
pass
146+
147+
148+
LIST_ENVIRONMENTS_FEATURE = "sqlmesh/list_environments"
149+
150+
151+
class ListEnvironmentsRequest(CustomMethodRequestBaseClass):
152+
pass
153+
154+
155+
class ListEnvironmentsResponse(CustomMethodResponseBaseClass):
156+
environments: t.List[str]
157+
158+
159+
PLAN_DIFF_FEATURE = "sqlmesh/plan_diff"
160+
161+
162+
class PlanDiffRequest(CustomMethodRequestBaseClass):
163+
environment: str
164+
165+
166+
class PlanDiffEntry(PydanticModel):
167+
name: str
168+
diff: str
169+
170+
171+
class PlanDiffResponse(CustomMethodResponseBaseClass):
172+
diffs: t.List[PlanDiffEntry]

sqlmesh/lsp/main.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
RENDER_MODEL_FEATURE,
3232
SUPPORTED_METHODS_FEATURE,
3333
FORMAT_PROJECT_FEATURE,
34+
LIST_ENVIRONMENTS_FEATURE,
35+
PLAN_DIFF_FEATURE,
3436
AllModelsRequest,
3537
AllModelsResponse,
3638
AllModelsForRenderRequest,
@@ -43,6 +45,11 @@
4345
FormatProjectRequest,
4446
FormatProjectResponse,
4547
CustomMethod,
48+
ListEnvironmentsRequest,
49+
ListEnvironmentsResponse,
50+
PlanDiffRequest,
51+
PlanDiffResponse,
52+
PlanDiffEntry,
4653
)
4754
from sqlmesh.lsp.hints import get_hints
4855
from sqlmesh.lsp.reference import (
@@ -89,6 +96,8 @@ def __init__(
8996
API_FEATURE: self._custom_api,
9097
SUPPORTED_METHODS_FEATURE: self._custom_supported_methods,
9198
FORMAT_PROJECT_FEATURE: self._custom_format_project,
99+
LIST_ENVIRONMENTS_FEATURE: self._custom_list_environments,
100+
PLAN_DIFF_FEATURE: self._custom_plan_diff,
92101
}
93102

94103
# Register LSP features (e.g., formatting, hover, etc.)
@@ -211,6 +220,37 @@ def _custom_api(
211220

212221
raise NotImplementedError(f"API request not implemented: {request.url}")
213222

223+
def _custom_list_environments(
224+
self, ls: LanguageServer, params: ListEnvironmentsRequest
225+
) -> ListEnvironmentsResponse:
226+
if self.lsp_context is None:
227+
current_path = Path.cwd()
228+
self._ensure_context_in_folder(current_path)
229+
if self.lsp_context is None:
230+
raise RuntimeError("No context found")
231+
232+
envs = self.lsp_context.context._new_state_sync().get_environments_summary()
233+
return ListEnvironmentsResponse(environments=[e.name for e in envs])
234+
235+
def _custom_plan_diff(self, ls: LanguageServer, params: PlanDiffRequest) -> PlanDiffResponse:
236+
if self.lsp_context is None:
237+
current_path = Path.cwd()
238+
self._ensure_context_in_folder(current_path)
239+
if self.lsp_context is None:
240+
raise RuntimeError("No context found")
241+
242+
plan = self.lsp_context.context.plan_builder(
243+
params.environment,
244+
skip_tests=True,
245+
skip_backfill=True,
246+
).build()
247+
248+
diffs = [
249+
PlanDiffEntry(name=name, diff=plan.context_diff.text_diff(name))
250+
for name in plan.context_diff.modified_snapshots
251+
]
252+
return PlanDiffResponse(diffs=diffs)
253+
214254
def _custom_supported_methods(
215255
self, ls: LanguageServer, params: SupportedMethodsRequest
216256
) -> SupportedMethodsResponse:

vscode/extension/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@
9393
"title": "Render Model",
9494
"description": "Render the model in the current editor",
9595
"icon": "$(open-preview)"
96+
},
97+
{
98+
"command": "sqlmesh.plan",
99+
"title": "Show Plan Diff",
100+
"description": "Create a plan and show the diff"
96101
}
97102
],
98103
"menus": {
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import * as vscode from 'vscode'
2+
import { LSPClient } from '../lsp/lsp'
3+
import { isErr } from '@bus/result'
4+
5+
export function planDiff(lspClient?: LSPClient) {
6+
return async () => {
7+
if (!lspClient) {
8+
vscode.window.showErrorMessage('LSP client not available')
9+
return
10+
}
11+
12+
const envResult = await lspClient.call_custom_method(
13+
'sqlmesh/list_environments',
14+
{},
15+
)
16+
17+
if (isErr(envResult)) {
18+
vscode.window.showErrorMessage(
19+
`Failed to list environments: ${envResult.error.message}`,
20+
)
21+
return
22+
}
23+
24+
const env = await vscode.window.showQuickPick(envResult.value.environments, {
25+
placeHolder: 'Select environment to plan',
26+
})
27+
28+
if (!env) {
29+
return
30+
}
31+
32+
const diffResult = await lspClient.call_custom_method('sqlmesh/plan_diff', {
33+
environment: env,
34+
})
35+
36+
if (isErr(diffResult)) {
37+
vscode.window.showErrorMessage(
38+
`Failed to get plan diff: ${diffResult.error.message}`,
39+
)
40+
return
41+
}
42+
43+
if (!diffResult.value.diffs.length) {
44+
vscode.window.showInformationMessage('No changes detected')
45+
return
46+
}
47+
48+
let selected = diffResult.value.diffs[0]
49+
if (diffResult.value.diffs.length > 1) {
50+
const pick = await vscode.window.showQuickPick(
51+
diffResult.value.diffs.map(d => ({ label: d.name })),
52+
{ placeHolder: 'Select a model diff to view' },
53+
)
54+
if (!pick) {
55+
return
56+
}
57+
selected = diffResult.value.diffs.find(d => d.name === pick.label)!
58+
}
59+
60+
const doc = await vscode.workspace.openTextDocument({
61+
content: selected.diff,
62+
language: 'diff',
63+
})
64+
await vscode.window.showTextDocument(doc, { preview: true })
65+
}
66+
}

vscode/extension/src/extension.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { signOut } from './commands/signout'
1313
import { signIn } from './commands/signin'
1414
import { signInSpecifyFlow } from './commands/signinSpecifyFlow'
1515
import { renderModel, reRenderModelForSourceFile } from './commands/renderModel'
16+
import { planDiff } from './commands/planDiff'
1617
import { isErr } from '@bus/result'
1718
import { handleError } from './utilities/errors'
1819
import { selector, completionProvider } from './completion/completion'
@@ -83,6 +84,13 @@ export async function activate(context: vscode.ExtensionContext) {
8384
),
8485
)
8586

87+
context.subscriptions.push(
88+
vscode.commands.registerCommand(
89+
'sqlmesh.plan',
90+
planDiff(lspClient),
91+
),
92+
)
93+
8694
// Register the webview
8795
const lineagePanel = new LineagePanel(context.extensionUri, lspClient)
8896
context.subscriptions.push(

vscode/extension/src/lsp/custom.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ export type CustomLSPMethods =
3333
| AllModelsForRenderMethod
3434
| SupportedMethodsMethod
3535
| FormatProjectMethod
36+
| ListEnvironmentsMethod
37+
| PlanDiffMethod
3638

3739
interface AllModelsRequest {
3840
textDocument: {
@@ -106,3 +108,35 @@ interface FormatProjectRequest {}
106108

107109
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
108110
interface FormatProjectResponse {}
111+
112+
export interface ListEnvironmentsMethod {
113+
method: 'sqlmesh/list_environments'
114+
request: ListEnvironmentsRequest
115+
response: ListEnvironmentsResponse
116+
}
117+
118+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
119+
interface ListEnvironmentsRequest {}
120+
121+
interface ListEnvironmentsResponse {
122+
environments: string[]
123+
}
124+
125+
export interface PlanDiffMethod {
126+
method: 'sqlmesh/plan_diff'
127+
request: PlanDiffRequest
128+
response: PlanDiffResponse
129+
}
130+
131+
interface PlanDiffRequest {
132+
environment: string
133+
}
134+
135+
interface PlanDiffEntry {
136+
name: string
137+
diff: string
138+
}
139+
140+
interface PlanDiffResponse {
141+
diffs: PlanDiffEntry[]
142+
}

0 commit comments

Comments
 (0)