Skip to content

Commit 07b6cbc

Browse files
authored
fix: disable snippets for LSP completions (#5949)
Upgrade `@marimo-team/codemirror-languageserver` to remove snippet completions
1 parent 7437950 commit 07b6cbc

File tree

12 files changed

+95
-85
lines changed

12 files changed

+95
-85
lines changed

.github/workflows/playwright.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,7 @@ jobs:
7070

7171
- name: 🐍 Install marimo
7272
run: |
73-
uv pip install .
74-
echo "MARIMO_VERSION=$(marimo --version)" >> $GITHUB_ENV
73+
echo "MARIMO_VERSION=$(uv run marimo --version)" >> $GITHUB_ENV
7574
7675
- name: 🎭 Get installed Playwright version
7776
id: playwright-version

frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
"@lezer/markdown": "^1.4.3",
5151
"@lezer/python": "^1.1.18",
5252
"@marimo-team/codemirror-ai": "^0.3.2",
53-
"@marimo-team/codemirror-languageserver": "1.15.24",
53+
"@marimo-team/codemirror-languageserver": "^1.16.0",
5454
"@marimo-team/codemirror-mcp": "^0.1.5",
5555
"@marimo-team/codemirror-sql": "^0.2.3",
5656
"@marimo-team/llm-info": "workspace:*",

frontend/playwright.config.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export function startServer(app: ApplicationNames): void {
104104
}
105105
const port = options.port ?? EDIT_PORT;
106106
const pathToApp = path.join(pydir, app);
107-
const marimoCmd = `marimo -q ${options.command} ${pathToApp} -p ${port} --headless`;
107+
const marimoCmd = `uv run marimo -q ${options.command} ${pathToApp} -p ${port} --headless`;
108108
exec(marimoCmd);
109109
}
110110

@@ -196,7 +196,7 @@ const config: PlaywrightTestConfig = {
196196
const baseUrl = command === "run" ? options.baseUrl : undefined;
197197

198198
const pathToApp = path.join(pydir, app);
199-
let marimoCmd = `marimo -q ${command} ${pathToApp} -p ${port} --headless --no-token`;
199+
let marimoCmd = `uv run marimo -q ${command} ${pathToApp} -p ${port} --headless --no-token`;
200200
if (baseUrl) {
201201
marimoCmd += ` --base-url=${baseUrl}`;
202202
}
@@ -213,7 +213,7 @@ const config: PlaywrightTestConfig = {
213213
};
214214
}),
215215
{
216-
command: `marimo -q edit -p ${EDIT_PORT} --headless --no-token`,
216+
command: `uv run marimo -q edit -p ${EDIT_PORT} --headless --no-token`,
217217
url: getUrl(EDIT_PORT),
218218
reuseExistingServer: true,
219219
timeout: 30 * 1000,

frontend/src/components/editor/navigation/__tests__/clipboard.test.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -178,11 +178,6 @@ describe("useCellClipboard", () => {
178178
"Failed to copy cells to clipboard",
179179
expect.any(Error),
180180
);
181-
expect(toast).toHaveBeenCalledWith({
182-
title: "Copy failed",
183-
description: "Failed to copy cells to clipboard.",
184-
variant: "danger",
185-
});
186181
});
187182

188183
it("should filter out non-existent cells", async () => {

frontend/src/components/editor/navigation/clipboard.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { z } from "zod";
55
import { toast } from "@/components/ui/use-toast";
66
import { getNotebook, useCellActions } from "@/core/cells/cells";
77
import type { CellId } from "@/core/cells/ids";
8+
import { copyToClipboard } from "@/utils/copy";
89
import { Logger } from "@/utils/Logger";
910

1011
// According to MDN, custom mimetypes should start with "web "
@@ -70,7 +71,7 @@ export function useCellClipboard() {
7071
// Fallback to simple text copy
7172
try {
7273
const plainText = cells.map((cell) => cell.code).join("\n\n");
73-
await navigator.clipboard.writeText(plainText);
74+
await copyToClipboard(plainText);
7475
toastSuccess(cells.length);
7576
} catch {
7677
toastError();

frontend/src/core/codemirror/language/languages/python.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,6 @@ const pylspClient = once((lspConfig: LSPConfig) => {
128128
return new NotebookLanguageServerClient(
129129
new LanguageServerClient({
130130
...lspClientOpts,
131-
autoClose: false,
132131
}),
133132
settings,
134133
);
@@ -146,7 +145,6 @@ const tyLspClient = once((_: LSPConfig) => {
146145
return new NotebookLanguageServerClient(
147146
new LanguageServerClient({
148147
...lspClientOpts,
149-
autoClose: false,
150148
getWorkspaceConfiguration: (_) => [{ disableLanguageServices: true }],
151149
}),
152150
{},
@@ -165,7 +163,6 @@ const pyrightClient = once((_: LSPConfig) => {
165163
return new NotebookLanguageServerClient(
166164
new LanguageServerClient({
167165
...lspClientOpts,
168-
autoClose: false,
169166
}),
170167
{},
171168
);
@@ -242,6 +239,7 @@ export class PythonLanguageAdapter implements LanguageAdapter<{}> {
242239
client: client as unknown as LanguageServerClient,
243240
languageId: "python",
244241
allowHTMLContent: true,
242+
useSnippetOnCompletion: false,
245243
hoverConfig: hoverOptions,
246244
completionConfig: autocompleteOptions,
247245
// Default to false

frontend/src/core/codemirror/lsp/__tests__/notebook-lsp.test.ts

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -195,35 +195,36 @@ describe("createNotebookLens", () => {
195195
});
196196

197197
describe("NotebookLanguageServerClient", () => {
198-
let plugins: any[] = [];
199198
let mockClient: Mocked<ILanguageServerClient>;
200199
let notebookClient: NotebookLanguageServerClient;
201200

202201
beforeEach(() => {
203-
plugins = [];
204202
mockClient = {
205-
plugins,
206203
ready: true,
207204
capabilities: {},
208205
initializePromise: Promise.resolve(),
206+
clientCapabilities: {},
207+
completionItemResolve: vi.fn(),
209208
initialize: vi.fn(),
210209
close: vi.fn(),
211-
detachPlugin: vi.fn().mockImplementation((plugin) => {
212-
plugins = plugins.filter((p) => p !== plugin);
213-
}),
214-
attachPlugin: vi.fn().mockImplementation((plugin) => {
215-
plugins.push(plugin);
216-
}),
210+
onNotification: vi.fn(),
217211
textDocumentDidOpen: vi.fn(),
218212
textDocumentDidChange: vi.fn(),
219213
textDocumentHover: vi.fn(),
220214
textDocumentCompletion: vi.fn(),
215+
textDocumentDefinition: vi.fn(),
216+
textDocumentPrepareRename: vi.fn(),
217+
textDocumentCodeAction: vi.fn(),
218+
textDocumentSignatureHelp: vi.fn(),
221219
textDocumentRename: vi.fn(),
222-
processNotification: vi.fn(),
223-
notify: vi.fn(),
224-
request: vi.fn(),
225-
} as unknown as Mocked<ILanguageServerClient>;
226-
notebookClient = new NotebookLanguageServerClient(mockClient, {});
220+
};
221+
(mockClient as any).processNotification = vi.fn();
222+
(mockClient as any).notify = vi.fn();
223+
notebookClient = new NotebookLanguageServerClient(mockClient, {}, () => ({
224+
[Cells.cell1]: new EditorView({ doc: "# this is a comment" }),
225+
[Cells.cell2]: new EditorView({ doc: "import math\nimport numpy" }),
226+
[Cells.cell3]: new EditorView({ doc: "print(math.sqrt(4))" }),
227+
}));
227228

228229
// Mock the atom instead of the instance method
229230
vi.spyOn(store, "get").mockImplementation((atom) => {
@@ -404,6 +405,12 @@ describe("NotebookLanguageServerClient", () => {
404405
});
405406
expect(mockView3.state.doc.toString()).toBe("print(math.sqrt(4))");
406407

408+
(notebookClient as any).getNotebookEditors = () => ({
409+
[Cells.cell1]: mockView1,
410+
[Cells.cell2]: mockView2,
411+
[Cells.cell3]: mockView3,
412+
});
413+
407414
// Setup rename params
408415
const renameParams: LSP.RenameParams = {
409416
textDocument: { uri: CellDocumentUri.of(Cells.cell2) },

frontend/src/core/codemirror/lsp/federated-lsp.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
/* Copyright 2024 Marimo. All rights reserved. */
22

3-
import type { LanguageServerPlugin } from "@marimo-team/codemirror-languageserver";
43
import type * as LSP from "vscode-languageserver-protocol";
54
import { Objects } from "@/utils/objects";
65
import type { ILanguageServerClient } from "./types";
@@ -24,6 +23,26 @@ export class FederatedLanguageServerClient implements ILanguageServerClient {
2423
this.documentUri = getLSPDocument();
2524
}
2625

26+
onNotification(
27+
listener: (n: {
28+
jsonrpc: "2.0";
29+
id?: null | undefined;
30+
method: "textDocument/publishDiagnostics";
31+
params: LSP.PublishDiagnosticsParams;
32+
}) => void,
33+
): () => boolean {
34+
const callbacks: Array<() => boolean> = [];
35+
for (const client of this.clients) {
36+
callbacks.push(client.onNotification(listener));
37+
}
38+
return () => {
39+
for (const cb of callbacks) {
40+
cb();
41+
}
42+
return true;
43+
};
44+
}
45+
2746
get clientCapabilities(): LSP.ClientCapabilities | undefined {
2847
const capabilities = this.clients
2948
.map((client) => {
@@ -153,14 +172,6 @@ export class FederatedLanguageServerClient implements ILanguageServerClient {
153172
return null;
154173
}
155174

156-
attachPlugin(plugin: LanguageServerPlugin): void {
157-
this.clients.forEach((client) => client.attachPlugin(plugin));
158-
}
159-
160-
detachPlugin(plugin: LanguageServerPlugin): void {
161-
this.clients.forEach((client) => client.detachPlugin(plugin));
162-
}
163-
164175
// Merge completions from all clients
165176
async textDocumentCompletion(
166177
params: LSP.CompletionParams,

frontend/src/core/codemirror/lsp/notebook-lsp.ts

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
/* Copyright 2024 Marimo. All rights reserved. */
22

3+
import type { EditorView } from "@codemirror/view";
34
import type * as LSP from "vscode-languageserver-protocol";
5+
import { getNotebook } from "@/core/cells/cells";
46
import type { CellId } from "@/core/cells/ids";
57
import { store } from "@/core/state/jotai";
68
import { invariant } from "@/utils/invariant";
79
import { Logger } from "@/utils/Logger";
810
import { LRUCache } from "@/utils/lru";
11+
import { Objects } from "@/utils/objects";
912
import { topologicalCodesAtom } from "../copilot/getCodes";
1013
import { createNotebookLens, type NotebookLens } from "./lens";
1114
import {
1215
CellDocumentUri,
1316
type ILanguageServerClient,
1417
isClientWithNotify,
15-
isClientWithPlugins,
1618
} from "./types";
1719
import { getLSPDocument } from "./utils";
1820

@@ -84,18 +86,32 @@ class Snapshotter {
8486
}
8587
}
8688

89+
const defaultGetNotebookEditors = () => {
90+
const evs = getNotebook().cellHandles;
91+
return Objects.mapValues(evs, (r) => r.current?.editorViewOrNull);
92+
};
93+
8794
export class NotebookLanguageServerClient implements ILanguageServerClient {
8895
public readonly documentUri: LSP.DocumentUri;
8996
private readonly client: ILanguageServerClient;
9097
private readonly snapshotter: Snapshotter;
98+
private readonly getNotebookEditors: () => Record<
99+
CellId,
100+
EditorView | null | undefined
101+
>;
91102

92103
private static readonly SEEN_CELL_DOCUMENT_URIS = new Set<CellDocumentUri>();
93104

94105
constructor(
95106
client: ILanguageServerClient,
96107
initialSettings: Record<string, unknown>,
108+
getNotebookEditors: () => Record<
109+
CellId,
110+
EditorView | null | undefined
111+
> = defaultGetNotebookEditors,
97112
) {
98113
this.documentUri = getLSPDocument();
114+
this.getNotebookEditors = getNotebookEditors;
99115

100116
this.client = client;
101117
this.patchProcessNotification();
@@ -114,6 +130,17 @@ export class NotebookLanguageServerClient implements ILanguageServerClient {
114130
this.snapshotter = new Snapshotter(this.getNotebookCode.bind(this));
115131
}
116132

133+
onNotification(
134+
listener: (n: {
135+
jsonrpc: "2.0";
136+
id?: null | undefined;
137+
method: "textDocument/publishDiagnostics";
138+
params: LSP.PublishDiagnosticsParams;
139+
}) => void,
140+
): () => boolean {
141+
return this.client.onNotification(listener);
142+
}
143+
117144
get ready(): boolean {
118145
return this.client.ready;
119146
}
@@ -150,16 +177,6 @@ export class NotebookLanguageServerClient implements ILanguageServerClient {
150177
this.client.close();
151178
}
152179

153-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
154-
detachPlugin(plugin: any): void {
155-
this.client.detachPlugin(plugin);
156-
}
157-
158-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
159-
attachPlugin(plugin: any): void {
160-
this.client.attachPlugin(plugin);
161-
}
162-
163180
private getNotebookCode() {
164181
return store.get(topologicalCodesAtom);
165182
}
@@ -371,37 +388,26 @@ export class NotebookLanguageServerClient implements ILanguageServerClient {
371388
const newEdits = lens.getEditsForNewText(edit.newText);
372389
const editsToNewCode = new Map(newEdits.map((e) => [e.cellId, e.text]));
373390

374-
invariant(
375-
isClientWithPlugins(this.client),
376-
"Expected client with plugins.",
377-
);
378-
379391
// Update the code in the plugins manually
380-
for (const plugin of this.client.plugins) {
381-
const documentUri: string = plugin.documentUri;
382-
if (!CellDocumentUri.is(documentUri)) {
383-
Logger.warn("Invalid cell document URI", documentUri);
384-
continue;
385-
}
386-
387-
const cellId = CellDocumentUri.parse(documentUri);
392+
const editors = this.getNotebookEditors();
393+
for (const [cellId, ev] of Objects.entries(editors)) {
388394
const newCode = editsToNewCode.get(cellId);
389395
if (newCode == null) {
390396
Logger.warn("No new code for cell", cellId);
391397
continue;
392398
}
393399

394-
if (!plugin.view) {
395-
Logger.warn("No view for plugin", plugin);
400+
if (!ev) {
401+
Logger.warn("No view for plugin", cellId);
396402
continue;
397403
}
398404

399405
// Only update if it has changed
400-
if (plugin.view.state.doc.toString() !== newCode) {
401-
plugin.view.dispatch({
406+
if (ev.state.doc.toString() !== newCode) {
407+
ev.dispatch({
402408
changes: {
403409
from: 0,
404-
to: plugin.view.state.doc.length,
410+
to: ev.state.doc.length,
405411
insert: newCode,
406412
},
407413
});

frontend/src/core/codemirror/lsp/types.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
/* Copyright 2024 Marimo. All rights reserved. */
22

3-
import type { EditorView } from "@codemirror/view";
43
import type { LanguageServerClient } from "@marimo-team/codemirror-languageserver";
54
import type { DocumentUri } from "vscode-languageserver-protocol";
65
import type { CellId } from "@/core/cells/ids";
@@ -41,15 +40,3 @@ export function isClientWithNotify(
4140
} {
4241
return "notify" in client;
4342
}
44-
45-
/**
46-
* Plugins are a @private on `LanguageServerClient`,
47-
* hiding public use with TypeScript.
48-
*/
49-
export function isClientWithPlugins(
50-
client: ILanguageServerClient,
51-
): client is ILanguageServerClient & {
52-
plugins: { documentUri: string; view?: EditorView }[];
53-
} {
54-
return "plugins" in client;
55-
}

0 commit comments

Comments
 (0)