@marimo-team/frontend 0.23.16-dev35 → 0.23.16-dev36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{CellStatus-CmojVYzm.js → CellStatus-Dua-Q5qp.js} +1 -1
- package/dist/assets/{JsonOutput-BwwRpByA.js → JsonOutput-BjTsPSAI.js} +12 -12
- package/dist/assets/{MarimoErrorOutput-BWte8vjk.js → MarimoErrorOutput-BSyqD6Hg.js} +8 -8
- package/dist/assets/{RenderHTML-DRltV71H.js → RenderHTML-DG0Fm01X.js} +1 -1
- package/dist/assets/{RunButton-DIegd2mN.js → RunButton-DFs1N-c9.js} +1 -1
- package/dist/assets/{add-cell-with-ai-BBFD-M9V.js → add-cell-with-ai-C6Kxfu_D.js} +17 -17
- package/dist/assets/{add-connection-dialog-CECJ8507.js → add-connection-dialog-DAqIrB2O.js} +1 -1
- package/dist/assets/{agent-panel-YaS0WXP1.js → agent-panel-GtkUm7qc.js} +5 -5
- package/dist/assets/ai-model-dropdown-UEHQ_Tx0.js +5 -0
- package/dist/assets/{app-config-button-BW-w01aF.js → app-config-button-BmBlROBo.js} +1 -1
- package/dist/assets/{cell-editor-EV-1IccQ.js → cell-editor-DKzBZjFa.js} +10 -10
- package/dist/assets/{cell-link-C5gpAn3R.js → cell-link-CRHwRCw2.js} +1 -1
- package/dist/assets/cells-C-9Qa_6_.js +235 -0
- package/dist/assets/{chat-display-8BbC5B-D.js → chat-display-CqB9H1kF.js} +1 -1
- package/dist/assets/{chat-panel-CMOZp5xW.js → chat-panel-C3wpuHtk.js} +2 -2
- package/dist/assets/{chat-ui-DWhJ2Ozy.js → chat-ui-DF2nZlBy.js} +1 -1
- package/dist/assets/{column-preview-DeXESYpM.js → column-preview-Bp4QHPE2.js} +1 -1
- package/dist/assets/{command-palette-Dx6AkiPd.js → command-palette-DaJdKaQz.js} +1 -1
- package/dist/assets/{common-XVELED_O.js → common-DeheUlBO.js} +1 -1
- package/dist/assets/{components-BLHpmhXx.js → components-Dk-7Tn_8.js} +1 -1
- package/dist/assets/{datasource-CTqa8bf-.js → datasource-D-oZZRFN.js} +2 -2
- package/dist/assets/{dependency-graph-panel-C4Nzcddp.js → dependency-graph-panel-JK4qjNID.js} +3 -3
- package/dist/assets/{documentation-panel-Bym4G7IS.js → documentation-panel-C_TxLeX8.js} +1 -1
- package/dist/assets/{download-HKgqJX4l.js → download-QhwPYWYu.js} +2 -2
- package/dist/assets/{edit-page-Jtp6Qqjl.js → edit-page-C8yy6X_7.js} +6 -6
- package/dist/assets/{error-entries-CxyvaxEM.js → error-entries-B8hPG61B.js} +2 -2
- package/dist/assets/{error-panel-CfSy7uUr.js → error-panel-BOBOm9wN.js} +1 -1
- package/dist/assets/{file-explorer-panel-Dd5Xn2TK.js → file-explorer-panel-CQOFpuIG.js} +4 -4
- package/dist/assets/{file-icons-CSjJ3JC6.js → file-icons-DoBeqg86.js} +1 -1
- package/dist/assets/{file-name-input-CrKtrXMl.js → file-name-input-B8d-QPzY.js} +1 -1
- package/dist/assets/{floating-outline-ByQ8vvif.js → floating-outline-ylNOBTvA.js} +1 -1
- package/dist/assets/{focus-CVONl-Dy.js → focus-CJzSyxLL.js} +1 -1
- package/dist/assets/{form-Ca3yd_Od.js → form-C2_p_FeE.js} +2 -2
- package/dist/assets/{home-page-FpAawj9s.js → home-page-D_6eKuGc.js} +2 -2
- package/dist/assets/{html-to-image-C-4ysbGe.js → html-to-image-DfyxinVN.js} +2 -2
- package/dist/assets/{index-CiJmYQBT.js → index-D-wPCBTa.js} +7 -7
- package/dist/assets/{index-xs-zSSrX.css → index-D6Dp_PFj.css} +1 -1
- package/dist/assets/{layout-Dtql_o_T.js → layout-BWV4OadK.js} +5 -5
- package/dist/assets/logs-panel-DpMLUEOB.js +1 -0
- package/dist/assets/{markdown-renderer-dABDqZDw.js → markdown-renderer-DtzxgcfB.js} +2 -2
- package/dist/assets/{name-cell-input-Y4knmEIU.js → name-cell-input-SbknxDvi.js} +1 -1
- package/dist/assets/{outline-panel-CQ_KWLip.js → outline-panel-kV7ARoXE.js} +1 -1
- package/dist/assets/{packages-panel-Ijt7d51P.js → packages-panel-B3biBPQ1.js} +1 -1
- package/dist/assets/{panels-DfU1Oikj.js → panels-BsrSW0bC.js} +2 -2
- package/dist/assets/process-output-DCAOrKEU.js +1 -0
- package/dist/assets/{radio-group-22eWJ9fv.js → radio-group-BWAhJtjV.js} +1 -1
- package/dist/assets/readonly-code-display-D_rgEfAm.js +1 -0
- package/dist/assets/{readonly-python-code-n_TrLmsR.js → readonly-python-code-B5ABqdqK.js} +1 -1
- package/dist/assets/{reveal-component-CCLTV1q6.js → reveal-component-CrcdxGl_.js} +2 -2
- package/dist/assets/{run-page-DbezK7Qs.js → run-page-DFHpKr66.js} +1 -1
- package/dist/assets/{scratchpad-panel-C2wAEv5n.js → scratchpad-panel-CfqnZK3C.js} +1 -1
- package/dist/assets/{session-panel-BZGNcZmp.js → session-panel-DWS61Z1x.js} +1 -1
- package/dist/assets/{skeleton-BzlKcq21.js → skeleton-PJnQ2_IA.js} +1 -1
- package/dist/assets/{snippets-panel-DNaNtkR0.js → snippets-panel-D1eovB0O.js} +1 -1
- package/dist/assets/{state-jCdh8Dcx.js → state-BjouLTuw.js} +1 -1
- package/dist/assets/{state-DFPAfLw7.js → state-Bu-aMpIc.js} +3 -3
- package/dist/assets/{textarea-DuEtSOCl.js → textarea-DhPaQ0wx.js} +1 -1
- package/dist/assets/{tracing-ttUd3J7U.js → tracing-B2ux60rQ.js} +1 -1
- package/dist/assets/{tracing-panel-rVAa3En0.js → tracing-panel-CF8H5wRv.js} +2 -2
- package/dist/assets/{tree-actions-UCr1tMHw.js → tree-actions-CPSDN4eE.js} +1 -1
- package/dist/assets/useCellActionButton-k2OXllYI.js +1 -0
- package/dist/assets/{useDeleteCell-PrgejW8A.js → useDeleteCell-C3nmnuxK.js} +1 -1
- package/dist/assets/{useDependencyPanelTab-5jc-_-hy.js → useDependencyPanelTab-D14Lna3z.js} +1 -1
- package/dist/assets/{useNotebookActions-4vw_NiRs.js → useNotebookActions-CHM0Ardg.js} +3 -3
- package/dist/assets/useRunCells-C4qD32Ix.js +1 -0
- package/dist/assets/useSplitCell-BEebdldD.js +1 -0
- package/dist/index.html +23 -23
- package/package.json +2 -3
- package/src/components/editor/chrome/panels/__tests__/logs-panel.test.tsx +25 -0
- package/src/components/editor/chrome/panels/logs-panel.tsx +12 -2
- package/src/core/cells/__tests__/cells.test.ts +12 -2
- package/src/core/cells/__tests__/logs.test.ts +5 -5
- package/src/core/cells/cells.ts +13 -0
- package/src/core/cells/document-changes.ts +1 -0
- package/src/core/cells/logs.ts +14 -4
- package/src/core/codemirror/copilot/__tests__/language-server.test.ts +396 -100
- package/src/core/codemirror/copilot/__tests__/transport.test.ts +128 -540
- package/src/core/codemirror/copilot/language-server.ts +182 -70
- package/src/core/codemirror/copilot/transport.ts +15 -207
- package/src/core/codemirror/copilot/types.ts +9 -2
- package/src/core/codemirror/language/languages/python.ts +23 -11
- package/src/core/codemirror/lsp/__tests__/federated-lsp.test.ts +161 -0
- package/src/core/codemirror/lsp/__tests__/log-messages.test.ts +53 -0
- package/src/core/codemirror/lsp/__tests__/normalize-markdown-math.test.ts +25 -0
- package/src/core/codemirror/lsp/__tests__/notebook-lsp.test.ts +598 -156
- package/src/core/codemirror/lsp/__tests__/transports.test.ts +112 -0
- package/src/core/codemirror/lsp/federated-lsp.ts +76 -23
- package/src/core/codemirror/lsp/log-messages.ts +37 -0
- package/src/core/codemirror/lsp/normalize-markdown-math.ts +8 -4
- package/src/core/codemirror/lsp/notebook-lsp.ts +220 -56
- package/src/core/codemirror/lsp/transports.ts +8 -1
- package/src/core/codemirror/lsp/types.ts +68 -8
- package/src/core/codemirror/lsp/utils.ts +14 -0
- package/src/core/lsp/__tests__/transport.test.ts +509 -337
- package/src/core/lsp/transport.ts +304 -132
- package/src/css/app/codemirror.css +16 -2
- package/src/stories/log-viewer.stories.tsx +12 -6
- package/dist/assets/ai-model-dropdown-BpVBUZnz.js +0 -5
- package/dist/assets/cells-6yd8nOAu.js +0 -231
- package/dist/assets/logs-panel-5u1wUBn1.js +0 -1
- package/dist/assets/process-output-Dd11YI04.js +0 -1
- package/dist/assets/readonly-code-display-BjK-TEri.js +0 -1
- package/dist/assets/useCellActionButton-CJknR0XY.js +0 -1
- package/dist/assets/useRunCells-DHsd7HGL.js +0 -1
- package/dist/assets/useSplitCell-MZpER0Bh.js +0 -1
|
@@ -1,28 +1,81 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import type {
|
|
3
|
+
JSONRPCMessage,
|
|
4
|
+
JSONRPCNotification,
|
|
5
|
+
JSONRPCRequest,
|
|
6
|
+
Transport,
|
|
7
|
+
} from "@marimo-team/codemirror-languageserver";
|
|
5
8
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
9
|
+
import { store } from "@/core/state/jotai";
|
|
6
10
|
import { CopilotLanguageServerClient } from "../language-server";
|
|
11
|
+
import { copilotStatusState } from "../state";
|
|
12
|
+
|
|
13
|
+
const copilotState = vi.hoisted(() => ({ isEnabled: true }));
|
|
14
|
+
|
|
15
|
+
vi.mock("../state", async (importOriginal) => {
|
|
16
|
+
const actual = await importOriginal<typeof import("../state")>();
|
|
17
|
+
return {
|
|
18
|
+
...actual,
|
|
19
|
+
isCopilotEnabled: () => copilotState.isEnabled,
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
class MockTransport implements Transport {
|
|
24
|
+
private readonly handlers = new Set<(message: JSONRPCMessage) => void>();
|
|
25
|
+
readonly sent: JSONRPCMessage[] = [];
|
|
26
|
+
|
|
27
|
+
connect = vi.fn().mockResolvedValue(undefined);
|
|
28
|
+
close = vi.fn();
|
|
29
|
+
send = vi.fn((message: JSONRPCMessage) => {
|
|
30
|
+
this.sent.push(message);
|
|
31
|
+
if (isRequest(message) && message.method === "initialize") {
|
|
32
|
+
queueMicrotask(() => {
|
|
33
|
+
this.emit({
|
|
34
|
+
jsonrpc: "2.0",
|
|
35
|
+
id: message.id,
|
|
36
|
+
result: { capabilities: {} },
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
onMessage(handler: (message: JSONRPCMessage) => void): () => void {
|
|
43
|
+
this.handlers.add(handler);
|
|
44
|
+
return () => {
|
|
45
|
+
this.handlers.delete(handler);
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
emit(message: JSONRPCMessage) {
|
|
50
|
+
for (const handler of this.handlers) {
|
|
51
|
+
handler(message);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
7
55
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
56
|
+
function isRequest(message: JSONRPCMessage): message is JSONRPCRequest {
|
|
57
|
+
return "method" in message && "id" in message;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function notifications(
|
|
61
|
+
transport: MockTransport,
|
|
62
|
+
method: string,
|
|
63
|
+
): JSONRPCNotification[] {
|
|
64
|
+
return transport.sent.filter(
|
|
65
|
+
(message): message is JSONRPCNotification =>
|
|
66
|
+
"method" in message && !("id" in message) && message.method === method,
|
|
67
|
+
);
|
|
16
68
|
}
|
|
17
69
|
|
|
18
70
|
describe("CopilotLanguageServerClient", () => {
|
|
19
71
|
let mockTransport: MockTransport;
|
|
20
72
|
|
|
21
73
|
beforeEach(() => {
|
|
74
|
+
copilotState.isEnabled = true;
|
|
22
75
|
mockTransport = new MockTransport();
|
|
23
76
|
});
|
|
24
77
|
|
|
25
|
-
it("
|
|
78
|
+
it("initializes without copilot settings", () => {
|
|
26
79
|
const client = new CopilotLanguageServerClient({
|
|
27
80
|
rootUri: "file:///test",
|
|
28
81
|
workspaceFolders: null,
|
|
@@ -32,178 +85,421 @@ describe("CopilotLanguageServerClient", () => {
|
|
|
32
85
|
expect(client).toBeDefined();
|
|
33
86
|
});
|
|
34
87
|
|
|
35
|
-
it("
|
|
88
|
+
it("initializes with copilot settings", () => {
|
|
36
89
|
const client = new CopilotLanguageServerClient({
|
|
37
90
|
rootUri: "file:///test",
|
|
38
91
|
workspaceFolders: null,
|
|
39
92
|
transport: mockTransport,
|
|
40
|
-
copilotSettings: {
|
|
93
|
+
copilotSettings: {
|
|
94
|
+
http: {
|
|
95
|
+
proxy: "http://proxy.example.com:8888",
|
|
96
|
+
proxyStrictSSL: true,
|
|
97
|
+
},
|
|
98
|
+
telemetry: {
|
|
99
|
+
telemetryLevel: "off",
|
|
100
|
+
},
|
|
101
|
+
},
|
|
41
102
|
});
|
|
42
103
|
|
|
43
104
|
expect(client).toBeDefined();
|
|
44
105
|
});
|
|
45
106
|
|
|
46
|
-
it("
|
|
107
|
+
it("sends configuration after initialization", async () => {
|
|
47
108
|
const copilotSettings = {
|
|
48
109
|
http: {
|
|
49
110
|
proxy: "http://proxy.example.com:8888",
|
|
50
111
|
proxyStrictSSL: true,
|
|
51
112
|
},
|
|
52
|
-
telemetry: {
|
|
53
|
-
telemetryLevel: "off",
|
|
54
|
-
},
|
|
55
113
|
};
|
|
56
|
-
|
|
57
114
|
const client = new CopilotLanguageServerClient({
|
|
58
115
|
rootUri: "file:///test",
|
|
59
116
|
workspaceFolders: null,
|
|
60
117
|
transport: mockTransport,
|
|
61
118
|
copilotSettings,
|
|
62
119
|
});
|
|
63
|
-
|
|
64
|
-
|
|
120
|
+
await client.initializePromise;
|
|
121
|
+
await vi.waitFor(() => {
|
|
122
|
+
expect(
|
|
123
|
+
notifications(mockTransport, "workspace/didChangeConfiguration"),
|
|
124
|
+
).toEqual([
|
|
125
|
+
{
|
|
126
|
+
jsonrpc: "2.0",
|
|
127
|
+
method: "workspace/didChangeConfiguration",
|
|
128
|
+
params: { settings: copilotSettings },
|
|
129
|
+
},
|
|
130
|
+
]);
|
|
131
|
+
});
|
|
65
132
|
});
|
|
66
133
|
|
|
67
|
-
it(
|
|
134
|
+
it.each([
|
|
135
|
+
["empty", {}],
|
|
136
|
+
["omitted", undefined],
|
|
137
|
+
])(
|
|
138
|
+
"does not send configuration when settings are %s",
|
|
139
|
+
async (_, copilotSettings) => {
|
|
140
|
+
const client = new CopilotLanguageServerClient({
|
|
141
|
+
rootUri: "file:///test",
|
|
142
|
+
workspaceFolders: null,
|
|
143
|
+
transport: mockTransport,
|
|
144
|
+
copilotSettings,
|
|
145
|
+
});
|
|
146
|
+
await client.initializePromise;
|
|
147
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
148
|
+
|
|
149
|
+
expect(
|
|
150
|
+
notifications(mockTransport, "workspace/didChangeConfiguration"),
|
|
151
|
+
).toEqual([]);
|
|
152
|
+
},
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
it("accepts enterprise, proxy, and telemetry settings together", async () => {
|
|
68
156
|
const copilotSettings = {
|
|
69
157
|
http: {
|
|
70
158
|
proxy: "http://proxy.example.com:8888",
|
|
71
159
|
proxyStrictSSL: true,
|
|
160
|
+
proxyKerberosServicePrincipal: "HTTP/proxy.example.com",
|
|
161
|
+
},
|
|
162
|
+
telemetry: {
|
|
163
|
+
telemetryLevel: "all",
|
|
164
|
+
},
|
|
165
|
+
"github-enterprise": {
|
|
166
|
+
uri: "https://github.enterprise.com",
|
|
72
167
|
},
|
|
73
168
|
};
|
|
74
|
-
|
|
75
169
|
const client = new CopilotLanguageServerClient({
|
|
76
170
|
rootUri: "file:///test",
|
|
77
171
|
workspaceFolders: null,
|
|
78
172
|
transport: mockTransport,
|
|
79
173
|
copilotSettings,
|
|
80
174
|
});
|
|
81
|
-
|
|
82
|
-
// Spy on the notify method
|
|
83
|
-
const notifySpy = vi.spyOn(client as any, "notify");
|
|
84
|
-
|
|
85
|
-
// Wait for initialization
|
|
86
175
|
await client.initializePromise;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
176
|
+
await vi.waitFor(() => {
|
|
177
|
+
expect(
|
|
178
|
+
notifications(mockTransport, "workspace/didChangeConfiguration"),
|
|
179
|
+
).toEqual([
|
|
180
|
+
{
|
|
181
|
+
jsonrpc: "2.0",
|
|
182
|
+
method: "workspace/didChangeConfiguration",
|
|
183
|
+
params: { settings: copilotSettings },
|
|
184
|
+
},
|
|
185
|
+
]);
|
|
94
186
|
});
|
|
95
187
|
});
|
|
96
188
|
|
|
97
|
-
it("
|
|
189
|
+
it("reopens the latest Copilot document after re-initialization", async () => {
|
|
98
190
|
const client = new CopilotLanguageServerClient({
|
|
99
191
|
rootUri: "file:///test",
|
|
100
192
|
workspaceFolders: null,
|
|
101
193
|
transport: mockTransport,
|
|
102
|
-
copilotSettings: {},
|
|
103
194
|
});
|
|
195
|
+
await client.initializePromise;
|
|
196
|
+
await client.textDocumentDidOpen({
|
|
197
|
+
textDocument: {
|
|
198
|
+
uri: "file:///test.py",
|
|
199
|
+
languageId: "python",
|
|
200
|
+
version: 0,
|
|
201
|
+
text: "initial",
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
await client.textDocumentDidChange({
|
|
205
|
+
textDocument: { uri: "file:///test.py", version: 1 },
|
|
206
|
+
contentChanges: [{ text: "updated" }],
|
|
207
|
+
});
|
|
208
|
+
mockTransport.sent.length = 0;
|
|
209
|
+
|
|
210
|
+
await client.reInitialize();
|
|
211
|
+
|
|
212
|
+
expect(notifications(mockTransport, "textDocument/didOpen")).toEqual([
|
|
213
|
+
{
|
|
214
|
+
jsonrpc: "2.0",
|
|
215
|
+
method: "textDocument/didOpen",
|
|
216
|
+
params: {
|
|
217
|
+
textDocument: {
|
|
218
|
+
uri: "file:///test.py",
|
|
219
|
+
languageId: "python",
|
|
220
|
+
version: 1,
|
|
221
|
+
text: "updated",
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
]);
|
|
226
|
+
});
|
|
104
227
|
|
|
105
|
-
|
|
106
|
-
const
|
|
228
|
+
it("reopens the document with the languageId it was opened with", async () => {
|
|
229
|
+
const client = new CopilotLanguageServerClient({
|
|
230
|
+
rootUri: "file:///test",
|
|
231
|
+
workspaceFolders: null,
|
|
232
|
+
transport: mockTransport,
|
|
233
|
+
});
|
|
234
|
+
await client.initializePromise;
|
|
235
|
+
// The Copilot plugin opens as "copilot", not "python"
|
|
236
|
+
await client.textDocumentDidOpen({
|
|
237
|
+
textDocument: {
|
|
238
|
+
uri: "file:///test.py",
|
|
239
|
+
languageId: "copilot",
|
|
240
|
+
version: 0,
|
|
241
|
+
text: "initial",
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
await client.textDocumentDidChange({
|
|
245
|
+
textDocument: { uri: "file:///test.py", version: 1 },
|
|
246
|
+
contentChanges: [{ text: "updated" }],
|
|
247
|
+
});
|
|
248
|
+
mockTransport.sent.length = 0;
|
|
249
|
+
|
|
250
|
+
await client.reInitialize();
|
|
251
|
+
|
|
252
|
+
expect(notifications(mockTransport, "textDocument/didOpen")).toEqual([
|
|
253
|
+
{
|
|
254
|
+
jsonrpc: "2.0",
|
|
255
|
+
method: "textDocument/didOpen",
|
|
256
|
+
params: {
|
|
257
|
+
textDocument: {
|
|
258
|
+
uri: "file:///test.py",
|
|
259
|
+
languageId: "copilot",
|
|
260
|
+
version: 1,
|
|
261
|
+
text: "updated",
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
]);
|
|
266
|
+
});
|
|
107
267
|
|
|
108
|
-
|
|
268
|
+
it("ignores an empty didChange without corrupting document state", async () => {
|
|
269
|
+
const client = new CopilotLanguageServerClient({
|
|
270
|
+
rootUri: "file:///test",
|
|
271
|
+
workspaceFolders: null,
|
|
272
|
+
transport: mockTransport,
|
|
273
|
+
});
|
|
109
274
|
await client.initializePromise;
|
|
275
|
+
mockTransport.sent.length = 0;
|
|
110
276
|
|
|
111
|
-
|
|
112
|
-
|
|
277
|
+
await expect(
|
|
278
|
+
client.textDocumentDidChange({
|
|
279
|
+
textDocument: { uri: "file:///test.py", version: 1 },
|
|
280
|
+
contentChanges: [],
|
|
281
|
+
}),
|
|
282
|
+
).resolves.toBeUndefined();
|
|
113
283
|
|
|
114
|
-
|
|
115
|
-
expect(
|
|
116
|
-
"workspace/didChangeConfiguration",
|
|
117
|
-
expect.anything(),
|
|
118
|
-
);
|
|
284
|
+
expect(notifications(mockTransport, "textDocument/didOpen")).toEqual([]);
|
|
285
|
+
expect(notifications(mockTransport, "textDocument/didChange")).toEqual([]);
|
|
119
286
|
});
|
|
120
287
|
|
|
121
|
-
it("
|
|
288
|
+
it("balances document references when Copilot is disabled before close", async () => {
|
|
122
289
|
const client = new CopilotLanguageServerClient({
|
|
123
290
|
rootUri: "file:///test",
|
|
124
291
|
workspaceFolders: null,
|
|
125
292
|
transport: mockTransport,
|
|
126
293
|
});
|
|
127
|
-
|
|
128
|
-
// Spy on the notify method
|
|
129
|
-
const notifySpy = vi.spyOn(client as any, "notify");
|
|
130
|
-
|
|
131
|
-
// Wait for initialization
|
|
132
294
|
await client.initializePromise;
|
|
295
|
+
await client.textDocumentDidOpen({
|
|
296
|
+
textDocument: {
|
|
297
|
+
uri: "file:///test.py",
|
|
298
|
+
languageId: "python",
|
|
299
|
+
version: 7,
|
|
300
|
+
text: "value = 1",
|
|
301
|
+
},
|
|
302
|
+
});
|
|
133
303
|
|
|
134
|
-
|
|
135
|
-
await
|
|
304
|
+
copilotState.isEnabled = false;
|
|
305
|
+
await client.textDocumentDidClose({
|
|
306
|
+
textDocument: { uri: "file:///test.py" },
|
|
307
|
+
});
|
|
308
|
+
copilotState.isEnabled = true;
|
|
309
|
+
mockTransport.sent.length = 0;
|
|
310
|
+
await client.reInitialize();
|
|
136
311
|
|
|
137
|
-
|
|
138
|
-
expect(notifySpy).not.toHaveBeenCalledWith(
|
|
139
|
-
"workspace/didChangeConfiguration",
|
|
140
|
-
expect.anything(),
|
|
141
|
-
);
|
|
312
|
+
expect(notifications(mockTransport, "textDocument/didOpen")).toEqual([]);
|
|
142
313
|
});
|
|
143
314
|
|
|
144
|
-
it("
|
|
145
|
-
const copilotSettings = {
|
|
146
|
-
"github-enterprise": {
|
|
147
|
-
uri: "https://github.enterprise.com",
|
|
148
|
-
},
|
|
149
|
-
};
|
|
150
|
-
|
|
315
|
+
it("does not close the shared document until the last mounted editor closes it (relies on base client's own refcounting)", async () => {
|
|
151
316
|
const client = new CopilotLanguageServerClient({
|
|
152
317
|
rootUri: "file:///test",
|
|
153
318
|
workspaceFolders: null,
|
|
154
319
|
transport: mockTransport,
|
|
155
|
-
copilotSettings,
|
|
156
320
|
});
|
|
157
|
-
|
|
158
|
-
// Spy on the notify method
|
|
159
|
-
const notifySpy = vi.spyOn(client as any, "notify");
|
|
160
|
-
|
|
161
|
-
// Wait for initialization
|
|
162
321
|
await client.initializePromise;
|
|
163
322
|
|
|
164
|
-
//
|
|
165
|
-
|
|
323
|
+
// Two editors (e.g. two visible cells) share this client and both open
|
|
324
|
+
// the same document.
|
|
325
|
+
await client.textDocumentDidOpen({
|
|
326
|
+
textDocument: {
|
|
327
|
+
uri: "file:///test.py",
|
|
328
|
+
languageId: "python",
|
|
329
|
+
version: 1,
|
|
330
|
+
text: "value = 1",
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
await client.textDocumentDidOpen({
|
|
334
|
+
textDocument: {
|
|
335
|
+
uri: "file:///test.py",
|
|
336
|
+
languageId: "python",
|
|
337
|
+
version: 1,
|
|
338
|
+
text: "value = 1",
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
mockTransport.sent.length = 0;
|
|
166
342
|
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
|
|
343
|
+
// Closing the first editor must not close the shared document while the
|
|
344
|
+
// second editor is still mounted.
|
|
345
|
+
await client.textDocumentDidClose({
|
|
346
|
+
textDocument: { uri: "file:///test.py" },
|
|
170
347
|
});
|
|
171
|
-
|
|
348
|
+
expect(notifications(mockTransport, "textDocument/didClose")).toEqual([]);
|
|
172
349
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
350
|
+
// Closing the last editor closes the document for real.
|
|
351
|
+
await client.textDocumentDidClose({
|
|
352
|
+
textDocument: { uri: "file:///test.py" },
|
|
353
|
+
});
|
|
354
|
+
expect(notifications(mockTransport, "textDocument/didClose")).toEqual([
|
|
355
|
+
{
|
|
356
|
+
jsonrpc: "2.0",
|
|
357
|
+
method: "textDocument/didClose",
|
|
358
|
+
params: { textDocument: { uri: "file:///test.py" } },
|
|
179
359
|
},
|
|
180
|
-
|
|
181
|
-
|
|
360
|
+
]);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it("keeps document versions monotonic after opening a versioned document", async () => {
|
|
364
|
+
const client = new CopilotLanguageServerClient({
|
|
365
|
+
rootUri: "file:///test",
|
|
366
|
+
workspaceFolders: null,
|
|
367
|
+
transport: mockTransport,
|
|
368
|
+
});
|
|
369
|
+
await client.initializePromise;
|
|
370
|
+
await client.textDocumentDidOpen({
|
|
371
|
+
textDocument: {
|
|
372
|
+
uri: "file:///test.py",
|
|
373
|
+
languageId: "python",
|
|
374
|
+
version: 7,
|
|
375
|
+
text: "value = 1",
|
|
182
376
|
},
|
|
183
|
-
|
|
184
|
-
|
|
377
|
+
});
|
|
378
|
+
mockTransport.sent.length = 0;
|
|
379
|
+
|
|
380
|
+
await client.textDocumentDidChange({
|
|
381
|
+
textDocument: { uri: "file:///test.py", version: 8 },
|
|
382
|
+
contentChanges: [{ text: "value = 2" }],
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
expect(notifications(mockTransport, "textDocument/didChange")).toEqual([
|
|
386
|
+
{
|
|
387
|
+
jsonrpc: "2.0",
|
|
388
|
+
method: "textDocument/didChange",
|
|
389
|
+
params: {
|
|
390
|
+
textDocument: { uri: "file:///test.py", version: 8 },
|
|
391
|
+
contentChanges: [{ text: "value = 2" }],
|
|
392
|
+
},
|
|
185
393
|
},
|
|
186
|
-
|
|
394
|
+
]);
|
|
395
|
+
});
|
|
187
396
|
|
|
397
|
+
it("ignores malformed custom notifications", async () => {
|
|
188
398
|
const client = new CopilotLanguageServerClient({
|
|
189
399
|
rootUri: "file:///test",
|
|
190
400
|
workspaceFolders: null,
|
|
191
401
|
transport: mockTransport,
|
|
192
|
-
copilotSettings,
|
|
193
402
|
});
|
|
403
|
+
await client.initializePromise;
|
|
194
404
|
|
|
195
|
-
|
|
196
|
-
|
|
405
|
+
expect(() => {
|
|
406
|
+
mockTransport.emit({
|
|
407
|
+
jsonrpc: "2.0",
|
|
408
|
+
method: "statusNotification",
|
|
409
|
+
params: { busy: "not-a-boolean" },
|
|
410
|
+
});
|
|
411
|
+
mockTransport.emit({
|
|
412
|
+
jsonrpc: "2.0",
|
|
413
|
+
method: "window/logMessage",
|
|
414
|
+
params: null,
|
|
415
|
+
});
|
|
416
|
+
}).not.toThrow();
|
|
417
|
+
});
|
|
197
418
|
|
|
198
|
-
|
|
199
|
-
|
|
419
|
+
describe("status notifications", () => {
|
|
420
|
+
beforeEach(() => {
|
|
421
|
+
store.set(copilotStatusState, {
|
|
422
|
+
busy: false,
|
|
423
|
+
kind: null,
|
|
424
|
+
message: null,
|
|
425
|
+
});
|
|
426
|
+
});
|
|
200
427
|
|
|
201
|
-
|
|
202
|
-
|
|
428
|
+
it.each([
|
|
429
|
+
[
|
|
430
|
+
"omitted message and unknown kind",
|
|
431
|
+
{ kind: "Inactive", busy: false },
|
|
432
|
+
{ busy: false, kind: "Inactive", message: null, status: undefined },
|
|
433
|
+
],
|
|
434
|
+
[
|
|
435
|
+
"omitted busy",
|
|
436
|
+
{ kind: "Error", message: "Not signed in" },
|
|
437
|
+
{
|
|
438
|
+
busy: false,
|
|
439
|
+
kind: "Error",
|
|
440
|
+
message: "Not signed in",
|
|
441
|
+
status: undefined,
|
|
442
|
+
},
|
|
443
|
+
],
|
|
444
|
+
[
|
|
445
|
+
"a fully populated payload",
|
|
446
|
+
{
|
|
447
|
+
busy: true,
|
|
448
|
+
kind: "Normal",
|
|
449
|
+
message: "Working",
|
|
450
|
+
status: "InProgress",
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
busy: true,
|
|
454
|
+
kind: "Normal",
|
|
455
|
+
message: "Working",
|
|
456
|
+
status: "InProgress",
|
|
457
|
+
},
|
|
458
|
+
],
|
|
459
|
+
[
|
|
460
|
+
"unusable field types",
|
|
461
|
+
{ busy: 1, kind: 2, message: 3, status: 4 },
|
|
462
|
+
{ busy: false, kind: null, message: null, status: undefined },
|
|
463
|
+
],
|
|
464
|
+
])(
|
|
465
|
+
"normalizes rather than discards a status update with %s",
|
|
466
|
+
async (_label, params, expected) => {
|
|
467
|
+
const client = new CopilotLanguageServerClient({
|
|
468
|
+
rootUri: "file:///test",
|
|
469
|
+
workspaceFolders: null,
|
|
470
|
+
transport: mockTransport,
|
|
471
|
+
});
|
|
472
|
+
await client.initializePromise;
|
|
473
|
+
|
|
474
|
+
mockTransport.emit({
|
|
475
|
+
jsonrpc: "2.0",
|
|
476
|
+
method: "didChangeStatus",
|
|
477
|
+
params,
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
expect(store.get(copilotStatusState)).toEqual(expected);
|
|
481
|
+
},
|
|
482
|
+
);
|
|
203
483
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
484
|
+
it("ignores a status update with non-object params", async () => {
|
|
485
|
+
const client = new CopilotLanguageServerClient({
|
|
486
|
+
rootUri: "file:///test",
|
|
487
|
+
workspaceFolders: null,
|
|
488
|
+
transport: mockTransport,
|
|
489
|
+
});
|
|
490
|
+
await client.initializePromise;
|
|
491
|
+
|
|
492
|
+
mockTransport.emit({
|
|
493
|
+
jsonrpc: "2.0",
|
|
494
|
+
method: "statusNotification",
|
|
495
|
+
params: "not-an-object",
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
expect(store.get(copilotStatusState)).toEqual({
|
|
499
|
+
busy: false,
|
|
500
|
+
kind: null,
|
|
501
|
+
message: null,
|
|
502
|
+
});
|
|
207
503
|
});
|
|
208
504
|
});
|
|
209
505
|
});
|