@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.
Files changed (105) hide show
  1. package/dist/assets/{CellStatus-CmojVYzm.js → CellStatus-Dua-Q5qp.js} +1 -1
  2. package/dist/assets/{JsonOutput-BwwRpByA.js → JsonOutput-BjTsPSAI.js} +12 -12
  3. package/dist/assets/{MarimoErrorOutput-BWte8vjk.js → MarimoErrorOutput-BSyqD6Hg.js} +8 -8
  4. package/dist/assets/{RenderHTML-DRltV71H.js → RenderHTML-DG0Fm01X.js} +1 -1
  5. package/dist/assets/{RunButton-DIegd2mN.js → RunButton-DFs1N-c9.js} +1 -1
  6. package/dist/assets/{add-cell-with-ai-BBFD-M9V.js → add-cell-with-ai-C6Kxfu_D.js} +17 -17
  7. package/dist/assets/{add-connection-dialog-CECJ8507.js → add-connection-dialog-DAqIrB2O.js} +1 -1
  8. package/dist/assets/{agent-panel-YaS0WXP1.js → agent-panel-GtkUm7qc.js} +5 -5
  9. package/dist/assets/ai-model-dropdown-UEHQ_Tx0.js +5 -0
  10. package/dist/assets/{app-config-button-BW-w01aF.js → app-config-button-BmBlROBo.js} +1 -1
  11. package/dist/assets/{cell-editor-EV-1IccQ.js → cell-editor-DKzBZjFa.js} +10 -10
  12. package/dist/assets/{cell-link-C5gpAn3R.js → cell-link-CRHwRCw2.js} +1 -1
  13. package/dist/assets/cells-C-9Qa_6_.js +235 -0
  14. package/dist/assets/{chat-display-8BbC5B-D.js → chat-display-CqB9H1kF.js} +1 -1
  15. package/dist/assets/{chat-panel-CMOZp5xW.js → chat-panel-C3wpuHtk.js} +2 -2
  16. package/dist/assets/{chat-ui-DWhJ2Ozy.js → chat-ui-DF2nZlBy.js} +1 -1
  17. package/dist/assets/{column-preview-DeXESYpM.js → column-preview-Bp4QHPE2.js} +1 -1
  18. package/dist/assets/{command-palette-Dx6AkiPd.js → command-palette-DaJdKaQz.js} +1 -1
  19. package/dist/assets/{common-XVELED_O.js → common-DeheUlBO.js} +1 -1
  20. package/dist/assets/{components-BLHpmhXx.js → components-Dk-7Tn_8.js} +1 -1
  21. package/dist/assets/{datasource-CTqa8bf-.js → datasource-D-oZZRFN.js} +2 -2
  22. package/dist/assets/{dependency-graph-panel-C4Nzcddp.js → dependency-graph-panel-JK4qjNID.js} +3 -3
  23. package/dist/assets/{documentation-panel-Bym4G7IS.js → documentation-panel-C_TxLeX8.js} +1 -1
  24. package/dist/assets/{download-HKgqJX4l.js → download-QhwPYWYu.js} +2 -2
  25. package/dist/assets/{edit-page-Jtp6Qqjl.js → edit-page-C8yy6X_7.js} +6 -6
  26. package/dist/assets/{error-entries-CxyvaxEM.js → error-entries-B8hPG61B.js} +2 -2
  27. package/dist/assets/{error-panel-CfSy7uUr.js → error-panel-BOBOm9wN.js} +1 -1
  28. package/dist/assets/{file-explorer-panel-Dd5Xn2TK.js → file-explorer-panel-CQOFpuIG.js} +4 -4
  29. package/dist/assets/{file-icons-CSjJ3JC6.js → file-icons-DoBeqg86.js} +1 -1
  30. package/dist/assets/{file-name-input-CrKtrXMl.js → file-name-input-B8d-QPzY.js} +1 -1
  31. package/dist/assets/{floating-outline-ByQ8vvif.js → floating-outline-ylNOBTvA.js} +1 -1
  32. package/dist/assets/{focus-CVONl-Dy.js → focus-CJzSyxLL.js} +1 -1
  33. package/dist/assets/{form-Ca3yd_Od.js → form-C2_p_FeE.js} +2 -2
  34. package/dist/assets/{home-page-FpAawj9s.js → home-page-D_6eKuGc.js} +2 -2
  35. package/dist/assets/{html-to-image-C-4ysbGe.js → html-to-image-DfyxinVN.js} +2 -2
  36. package/dist/assets/{index-CiJmYQBT.js → index-D-wPCBTa.js} +7 -7
  37. package/dist/assets/{index-xs-zSSrX.css → index-D6Dp_PFj.css} +1 -1
  38. package/dist/assets/{layout-Dtql_o_T.js → layout-BWV4OadK.js} +5 -5
  39. package/dist/assets/logs-panel-DpMLUEOB.js +1 -0
  40. package/dist/assets/{markdown-renderer-dABDqZDw.js → markdown-renderer-DtzxgcfB.js} +2 -2
  41. package/dist/assets/{name-cell-input-Y4knmEIU.js → name-cell-input-SbknxDvi.js} +1 -1
  42. package/dist/assets/{outline-panel-CQ_KWLip.js → outline-panel-kV7ARoXE.js} +1 -1
  43. package/dist/assets/{packages-panel-Ijt7d51P.js → packages-panel-B3biBPQ1.js} +1 -1
  44. package/dist/assets/{panels-DfU1Oikj.js → panels-BsrSW0bC.js} +2 -2
  45. package/dist/assets/process-output-DCAOrKEU.js +1 -0
  46. package/dist/assets/{radio-group-22eWJ9fv.js → radio-group-BWAhJtjV.js} +1 -1
  47. package/dist/assets/readonly-code-display-D_rgEfAm.js +1 -0
  48. package/dist/assets/{readonly-python-code-n_TrLmsR.js → readonly-python-code-B5ABqdqK.js} +1 -1
  49. package/dist/assets/{reveal-component-CCLTV1q6.js → reveal-component-CrcdxGl_.js} +2 -2
  50. package/dist/assets/{run-page-DbezK7Qs.js → run-page-DFHpKr66.js} +1 -1
  51. package/dist/assets/{scratchpad-panel-C2wAEv5n.js → scratchpad-panel-CfqnZK3C.js} +1 -1
  52. package/dist/assets/{session-panel-BZGNcZmp.js → session-panel-DWS61Z1x.js} +1 -1
  53. package/dist/assets/{skeleton-BzlKcq21.js → skeleton-PJnQ2_IA.js} +1 -1
  54. package/dist/assets/{snippets-panel-DNaNtkR0.js → snippets-panel-D1eovB0O.js} +1 -1
  55. package/dist/assets/{state-jCdh8Dcx.js → state-BjouLTuw.js} +1 -1
  56. package/dist/assets/{state-DFPAfLw7.js → state-Bu-aMpIc.js} +3 -3
  57. package/dist/assets/{textarea-DuEtSOCl.js → textarea-DhPaQ0wx.js} +1 -1
  58. package/dist/assets/{tracing-ttUd3J7U.js → tracing-B2ux60rQ.js} +1 -1
  59. package/dist/assets/{tracing-panel-rVAa3En0.js → tracing-panel-CF8H5wRv.js} +2 -2
  60. package/dist/assets/{tree-actions-UCr1tMHw.js → tree-actions-CPSDN4eE.js} +1 -1
  61. package/dist/assets/useCellActionButton-k2OXllYI.js +1 -0
  62. package/dist/assets/{useDeleteCell-PrgejW8A.js → useDeleteCell-C3nmnuxK.js} +1 -1
  63. package/dist/assets/{useDependencyPanelTab-5jc-_-hy.js → useDependencyPanelTab-D14Lna3z.js} +1 -1
  64. package/dist/assets/{useNotebookActions-4vw_NiRs.js → useNotebookActions-CHM0Ardg.js} +3 -3
  65. package/dist/assets/useRunCells-C4qD32Ix.js +1 -0
  66. package/dist/assets/useSplitCell-BEebdldD.js +1 -0
  67. package/dist/index.html +23 -23
  68. package/package.json +2 -3
  69. package/src/components/editor/chrome/panels/__tests__/logs-panel.test.tsx +25 -0
  70. package/src/components/editor/chrome/panels/logs-panel.tsx +12 -2
  71. package/src/core/cells/__tests__/cells.test.ts +12 -2
  72. package/src/core/cells/__tests__/logs.test.ts +5 -5
  73. package/src/core/cells/cells.ts +13 -0
  74. package/src/core/cells/document-changes.ts +1 -0
  75. package/src/core/cells/logs.ts +14 -4
  76. package/src/core/codemirror/copilot/__tests__/language-server.test.ts +396 -100
  77. package/src/core/codemirror/copilot/__tests__/transport.test.ts +128 -540
  78. package/src/core/codemirror/copilot/language-server.ts +182 -70
  79. package/src/core/codemirror/copilot/transport.ts +15 -207
  80. package/src/core/codemirror/copilot/types.ts +9 -2
  81. package/src/core/codemirror/language/languages/python.ts +23 -11
  82. package/src/core/codemirror/lsp/__tests__/federated-lsp.test.ts +161 -0
  83. package/src/core/codemirror/lsp/__tests__/log-messages.test.ts +53 -0
  84. package/src/core/codemirror/lsp/__tests__/normalize-markdown-math.test.ts +25 -0
  85. package/src/core/codemirror/lsp/__tests__/notebook-lsp.test.ts +598 -156
  86. package/src/core/codemirror/lsp/__tests__/transports.test.ts +112 -0
  87. package/src/core/codemirror/lsp/federated-lsp.ts +76 -23
  88. package/src/core/codemirror/lsp/log-messages.ts +37 -0
  89. package/src/core/codemirror/lsp/normalize-markdown-math.ts +8 -4
  90. package/src/core/codemirror/lsp/notebook-lsp.ts +220 -56
  91. package/src/core/codemirror/lsp/transports.ts +8 -1
  92. package/src/core/codemirror/lsp/types.ts +68 -8
  93. package/src/core/codemirror/lsp/utils.ts +14 -0
  94. package/src/core/lsp/__tests__/transport.test.ts +509 -337
  95. package/src/core/lsp/transport.ts +304 -132
  96. package/src/css/app/codemirror.css +16 -2
  97. package/src/stories/log-viewer.stories.tsx +12 -6
  98. package/dist/assets/ai-model-dropdown-BpVBUZnz.js +0 -5
  99. package/dist/assets/cells-6yd8nOAu.js +0 -231
  100. package/dist/assets/logs-panel-5u1wUBn1.js +0 -1
  101. package/dist/assets/process-output-Dd11YI04.js +0 -1
  102. package/dist/assets/readonly-code-display-BjK-TEri.js +0 -1
  103. package/dist/assets/useCellActionButton-CJknR0XY.js +0 -1
  104. package/dist/assets/useRunCells-DHsd7HGL.js +0 -1
  105. package/dist/assets/useSplitCell-MZpER0Bh.js +0 -1
@@ -0,0 +1,112 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4
+ import { initialNotebookState, notebookAtom } from "@/core/cells/cells";
5
+ import { store } from "@/core/state/jotai";
6
+ import { createTransport } from "../transports";
7
+
8
+ vi.mock("../../../network/connection", () => ({
9
+ waitForConnectionOpen: vi.fn(() => Promise.resolve()),
10
+ }));
11
+
12
+ vi.mock("../../../runtime/config", () => ({
13
+ getRuntimeManager: () => ({
14
+ getLSPURL: (server: string) => new URL(`ws://localhost:2718/lsp/${server}`),
15
+ }),
16
+ }));
17
+
18
+ /** Minimal stand-in for the browser WebSocket used by `WebSocketTransport`. */
19
+ class FakeWebSocket {
20
+ static readonly instances: FakeWebSocket[] = [];
21
+ static readonly OPEN = 1;
22
+
23
+ readyState = 0;
24
+ readonly url: string;
25
+ private readonly listeners = new Map<string, Set<(event: unknown) => void>>();
26
+
27
+ constructor(url: string) {
28
+ this.url = url;
29
+ FakeWebSocket.instances.push(this);
30
+ }
31
+
32
+ addEventListener(type: string, listener: (event: unknown) => void) {
33
+ const listeners = this.listeners.get(type) ?? new Set();
34
+ listeners.add(listener);
35
+ this.listeners.set(type, listeners);
36
+ }
37
+
38
+ send() {
39
+ // no-op
40
+ }
41
+
42
+ close() {
43
+ this.readyState = 3;
44
+ }
45
+
46
+ open() {
47
+ this.readyState = FakeWebSocket.OPEN;
48
+ this.emit("open", {});
49
+ }
50
+
51
+ receive(message: unknown) {
52
+ this.emit("message", { data: JSON.stringify(message) });
53
+ }
54
+
55
+ private emit(type: string, event: unknown) {
56
+ for (const listener of this.listeners.get(type) ?? []) {
57
+ listener(event);
58
+ }
59
+ }
60
+ }
61
+
62
+ describe("createTransport", () => {
63
+ beforeEach(() => {
64
+ FakeWebSocket.instances.length = 0;
65
+ vi.stubGlobal("WebSocket", FakeWebSocket);
66
+ vi.spyOn(console, "log").mockImplementation(() => undefined);
67
+ store.set(notebookAtom, initialNotebookState());
68
+ });
69
+
70
+ afterEach(() => {
71
+ vi.unstubAllGlobals();
72
+ });
73
+
74
+ it("forwards window/logMessage notifications to the logs panel", async () => {
75
+ const transport = createTransport("pylsp");
76
+ const connected = transport.connect();
77
+ await vi.waitFor(() => expect(FakeWebSocket.instances).toHaveLength(1));
78
+ const socket = FakeWebSocket.instances[0];
79
+ socket.open();
80
+ await connected;
81
+
82
+ socket.receive({
83
+ jsonrpc: "2.0",
84
+ method: "window/logMessage",
85
+ params: { type: 1, message: "pylsp failed to start" },
86
+ });
87
+ socket.receive({
88
+ jsonrpc: "2.0",
89
+ method: "window/logMessage",
90
+ params: { type: 3, message: "indexing workspace" },
91
+ });
92
+ // Unrelated notifications are left alone.
93
+ socket.receive({
94
+ jsonrpc: "2.0",
95
+ method: "textDocument/publishDiagnostics",
96
+ params: { uri: "file:///a", diagnostics: [] },
97
+ });
98
+
99
+ expect(store.get(notebookAtom).cellLogs).toMatchObject([
100
+ {
101
+ level: "stderr",
102
+ message: "pylsp failed to start",
103
+ source: { type: "lsp", name: "pylsp" },
104
+ },
105
+ {
106
+ level: "stdout",
107
+ message: "indexing workspace",
108
+ source: { type: "lsp", name: "pylsp" },
109
+ },
110
+ ]);
111
+ });
112
+ });
@@ -2,7 +2,7 @@
2
2
 
3
3
  import type * as LSP from "vscode-languageserver-protocol";
4
4
  import { Objects } from "@/utils/objects";
5
- import type { ILanguageServerClient } from "./types";
5
+ import type { ILanguageServerClient, RoutableLspMethod } from "./types";
6
6
  import { getLspDocumentUri } from "./utils";
7
7
 
8
8
  function removeFalseyValues<T extends object>(obj: T): T {
@@ -54,7 +54,7 @@ export class FederatedLanguageServerClient implements ILanguageServerClient {
54
54
  }
55
55
  return undefined;
56
56
  })
57
- .filter((c) => c != null);
57
+ .filter((c): c is LSP.ClientCapabilities => c != null);
58
58
 
59
59
  return mergeDictsIgnoreFalsey<LSP.ClientCapabilities>(capabilities);
60
60
  }
@@ -72,7 +72,7 @@ export class FederatedLanguageServerClient implements ILanguageServerClient {
72
72
  get capabilities(): LSP.ServerCapabilities | null {
73
73
  const capabilities = this.clients
74
74
  .map((client) => client.capabilities)
75
- .filter((c) => c !== null);
75
+ .filter((c): c is LSP.ServerCapabilities => c !== null);
76
76
  return mergeDictsIgnoreFalsey<LSP.ServerCapabilities>(capabilities);
77
77
  }
78
78
 
@@ -92,16 +92,20 @@ export class FederatedLanguageServerClient implements ILanguageServerClient {
92
92
  });
93
93
  }
94
94
 
95
- private firstWithCapability(
96
- capability: keyof LSP.ServerCapabilities,
95
+ private firstWithMethod(
96
+ method: RoutableLspMethod,
97
97
  ): ILanguageServerClient | undefined {
98
- return this.clients.find((client) => client.capabilities?.[capability]);
98
+ return this.clients.find((client) => client.hasCapability(method));
99
99
  }
100
100
 
101
- private clientsWithCapability(
102
- capability: keyof LSP.ServerCapabilities,
101
+ private clientsWithMethod(
102
+ method: RoutableLspMethod,
103
103
  ): ILanguageServerClient[] {
104
- return this.clients.filter((client) => client.capabilities?.[capability]);
104
+ return this.clients.filter((client) => client.hasCapability(method));
105
+ }
106
+
107
+ hasCapability(method: string): boolean {
108
+ return this.clients.some((client) => client.hasCapability(method));
105
109
  }
106
110
 
107
111
  async initialize(): Promise<void> {
@@ -114,18 +118,16 @@ export class FederatedLanguageServerClient implements ILanguageServerClient {
114
118
 
115
119
  async textDocumentDidChange(
116
120
  params: LSP.DidChangeTextDocumentParams,
117
- ): Promise<LSP.DidChangeTextDocumentParams> {
121
+ ): Promise<void> {
118
122
  await Promise.all(
119
123
  this.clients.map((client) => client.textDocumentDidChange(params)),
120
124
  );
121
-
122
- return params;
123
125
  }
124
126
 
125
127
  async completionItemResolve(
126
128
  item: LSP.CompletionItem,
127
129
  ): Promise<LSP.CompletionItem> {
128
- const client = this.firstWithCapability("completionProvider");
130
+ const client = this.firstWithMethod("textDocument/completion");
129
131
  if (client) {
130
132
  return client.completionItemResolve(item);
131
133
  }
@@ -135,17 +137,22 @@ export class FederatedLanguageServerClient implements ILanguageServerClient {
135
137
  async textDocumentCodeAction(
136
138
  params: LSP.CodeActionParams,
137
139
  ): Promise<(LSP.Command | LSP.CodeAction)[] | null> {
138
- const client = this.firstWithCapability("codeActionProvider");
140
+ const client = this.firstWithMethod("textDocument/codeAction");
139
141
  if (client) {
140
142
  return client.textDocumentCodeAction(params);
141
143
  }
142
144
  return null;
143
145
  }
144
146
 
147
+ async codeActionResolve(action: LSP.CodeAction): Promise<LSP.CodeAction> {
148
+ const client = this.firstWithMethod("textDocument/codeAction");
149
+ return client ? client.codeActionResolve(action) : action;
150
+ }
151
+
145
152
  async textDocumentRename(
146
153
  params: LSP.RenameParams,
147
154
  ): Promise<LSP.WorkspaceEdit | null> {
148
- const client = this.firstWithCapability("renameProvider");
155
+ const client = this.firstWithMethod("textDocument/rename");
149
156
  if (client) {
150
157
  return client.textDocumentRename(params);
151
158
  }
@@ -155,7 +162,7 @@ export class FederatedLanguageServerClient implements ILanguageServerClient {
155
162
  async textDocumentPrepareRename(
156
163
  params: LSP.PrepareRenameParams,
157
164
  ): Promise<LSP.PrepareRenameResult | null> {
158
- const client = this.firstWithCapability("renameProvider");
165
+ const client = this.firstWithMethod("textDocument/prepareRename");
159
166
  if (client) {
160
167
  return client.textDocumentPrepareRename(params);
161
168
  }
@@ -165,7 +172,7 @@ export class FederatedLanguageServerClient implements ILanguageServerClient {
165
172
  async textDocumentSignatureHelp(
166
173
  params: LSP.SignatureHelpParams,
167
174
  ): Promise<LSP.SignatureHelp | null> {
168
- const client = this.firstWithCapability("signatureHelpProvider");
175
+ const client = this.firstWithMethod("textDocument/signatureHelp");
169
176
  if (client) {
170
177
  return client.textDocumentSignatureHelp(params);
171
178
  }
@@ -176,7 +183,7 @@ export class FederatedLanguageServerClient implements ILanguageServerClient {
176
183
  async textDocumentCompletion(
177
184
  params: LSP.CompletionParams,
178
185
  ): Promise<LSP.CompletionList | LSP.CompletionItem[] | null> {
179
- const clients = this.clientsWithCapability("completionProvider");
186
+ const clients = this.clientsWithMethod("textDocument/completion");
180
187
  const results = await Promise.allSettled(
181
188
  clients.map((client) => client.textDocumentCompletion(params)),
182
189
  );
@@ -187,24 +194,70 @@ export class FederatedLanguageServerClient implements ILanguageServerClient {
187
194
  async textDocumentDefinition(
188
195
  params: LSP.DefinitionParams,
189
196
  ): Promise<LSP.Definition | LSP.LocationLink[] | null> {
190
- const client = this.firstWithCapability("definitionProvider");
197
+ const client = this.firstWithMethod("textDocument/definition");
191
198
  if (client) {
192
199
  return client.textDocumentDefinition(params);
193
200
  }
194
201
  return null;
195
202
  }
196
203
 
197
- async textDocumentDidOpen(params: LSP.DidOpenTextDocumentParams) {
198
- await Promise.all(
204
+ async textDocumentDidOpen(
205
+ params: LSP.DidOpenTextDocumentParams,
206
+ ): Promise<boolean> {
207
+ const results = await Promise.all(
199
208
  this.clients.map((client) => client.textDocumentDidOpen(params)),
200
209
  );
201
210
 
202
- return params;
211
+ return results.some((result) => result !== false);
212
+ }
213
+
214
+ async textDocumentDidClose(
215
+ params: LSP.DidCloseTextDocumentParams,
216
+ ): Promise<void> {
217
+ await Promise.all(
218
+ this.clients.map((client) => client.textDocumentDidClose(params)),
219
+ );
220
+ }
221
+
222
+ async textDocumentWillSave(
223
+ params: LSP.WillSaveTextDocumentParams,
224
+ ): Promise<void> {
225
+ await Promise.all(
226
+ this.clients.map((client) => client.textDocumentWillSave(params)),
227
+ );
228
+ }
229
+
230
+ async textDocumentWillSaveWaitUntil(
231
+ params: LSP.WillSaveTextDocumentParams,
232
+ ): Promise<LSP.TextEdit[] | null> {
233
+ // This blocks the save, so query concurrently rather than paying the sum of
234
+ // the servers' latencies. The earliest client that returned edits still
235
+ // wins, so the result doesn't depend on who answers first.
236
+ const results = await Promise.allSettled(
237
+ this.clients.map((client) =>
238
+ client.textDocumentWillSaveWaitUntil(params),
239
+ ),
240
+ );
241
+
242
+ for (const result of results) {
243
+ if (result.status === "fulfilled" && result.value?.length) {
244
+ return result.value;
245
+ }
246
+ }
247
+ return null;
248
+ }
249
+
250
+ async textDocumentDidSave(
251
+ params: LSP.DidSaveTextDocumentParams,
252
+ ): Promise<void> {
253
+ await Promise.all(
254
+ this.clients.map((client) => client.textDocumentDidSave(params)),
255
+ );
203
256
  }
204
257
 
205
258
  async textDocumentHover(params: LSP.HoverParams): Promise<LSP.Hover> {
206
259
  for (const client of this.clients) {
207
- if (!client.capabilities?.hoverProvider) {
260
+ if (!client.hasCapability("textDocument/hover")) {
208
261
  continue;
209
262
  }
210
263
  const result = await client.textDocumentHover(params);
@@ -0,0 +1,37 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import type { JSONRPCMessage } from "@marimo-team/codemirror-languageserver";
4
+ import { addLogs } from "@/core/cells/cells";
5
+ import { isRecord } from "@/utils/records";
6
+
7
+ // LSP MessageType: 1 = Error, 2 = Warning, 3 = Info, 4 = Log, 5 = Debug
8
+ const ERROR_MESSAGE_TYPES = new Set<unknown>([1, 2]);
9
+
10
+ /**
11
+ * Forward a server's `window/logMessage` notifications to the Logs panel.
12
+ * Servers report failures they can't surface as diagnostics this way, such as
13
+ * a missing interpreter or a plugin that failed to load.
14
+ */
15
+ export function handleLogMessage(
16
+ serverName: string,
17
+ message: JSONRPCMessage,
18
+ ): void {
19
+ if (!("method" in message) || message.method !== "window/logMessage") {
20
+ return;
21
+ }
22
+ const params: unknown = message.params;
23
+ if (!isRecord(params) || typeof params.message !== "string") {
24
+ return;
25
+ }
26
+
27
+ addLogs({
28
+ logs: [
29
+ {
30
+ timestamp: Date.now() / 1000,
31
+ level: ERROR_MESSAGE_TYPES.has(params.type) ? "stderr" : "stdout",
32
+ message: params.message,
33
+ source: { type: "lsp", name: serverName },
34
+ },
35
+ ],
36
+ });
37
+ }
@@ -6,8 +6,10 @@ import type * as LSP from "vscode-languageserver-protocol";
6
6
  const INLINE_MATH_ROLE_REGEX = /(?<!`):math:`([^\n`]+)`/g;
7
7
  // Matches role variant with HTML code tag: :math:<code>...</code>
8
8
  const INLINE_MATH_ROLE_CODE_REGEX = /:math:\s*<code>(.+?)<\/code>/gs;
9
- // Matches display bracket delimiters: \[...\]
10
- const DISPLAY_BRACKET_REGEX = /\\\[(.+?)\\]/gs;
9
+ // Matches display bracket delimiters: \[...\]. Must start a line or follow
10
+ // whitespace, so escaped brackets in type annotations (`Optional\[Callable\]`)
11
+ // are not read as math.
12
+ const DISPLAY_BRACKET_REGEX = /(?<!\S)\\\[(.+?)\\]/gs;
11
13
  // Matches inline paren delimiters: \(...\)
12
14
  const INLINE_PAREN_REGEX = /\\\((.+?)\\\)/gs;
13
15
  // Matches display dollar delimiters: $$...$$
@@ -36,6 +38,8 @@ export function normalizeMarkdownMath(markdown: string): string {
36
38
  return markdown;
37
39
  }
38
40
 
41
+ // Segments are split on line boundaries, so the newline between two of them
42
+ // belongs to neither and has to be restored on join.
39
43
  return splitByFencedCodeBlocks(markdown)
40
44
  .map((segment) => {
41
45
  if (segment.isFenced) {
@@ -55,7 +59,7 @@ export function normalizeMarkdownMath(markdown: string): string {
55
59
  })
56
60
  .join("");
57
61
  })
58
- .join("");
62
+ .join("\n");
59
63
  }
60
64
 
61
65
  /**
@@ -132,7 +136,7 @@ function containsMathSyntax(markdown: string): boolean {
132
136
  markdown.includes(".. math::") ||
133
137
  markdown.includes(":math:`") ||
134
138
  // Detects display bracket delimiters.
135
- /\\\[[\S\s]+?\\]/.test(markdown) ||
139
+ /(?<!\S)\\\[[\S\s]+?\\]/.test(markdown) ||
136
140
  // Detects inline paren delimiters.
137
141
  /\\\([\S\s]+?\\\)/.test(markdown) ||
138
142
  // Detects display dollar delimiters.