codemirror-languageserver 1.13.0 → 1.15.0

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.
@@ -0,0 +1,17 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>CodeMirror Language Server Demo</title>
5
+ <link
6
+ type="text/css"
7
+ rel="stylesheet"
8
+ href="https://cdn.jsdelivr.net/gh/FurqanSoftware/sleek@v1.52.4/dist/sleek.css"
9
+ />
10
+ </head>
11
+ <body class="p-1">
12
+ <div class="form__field br-1-4">
13
+ <div id="editor"></div>
14
+ </div>
15
+ <script type="module" src="./dist/demo.js"></script>
16
+ </body>
17
+ </html>
@@ -0,0 +1,4 @@
1
+ import { Command, KeyBinding } from '@codemirror/view';
2
+ export declare const jumpToDefinition: Command;
3
+ export declare const jumpToDefinitionPos: (pos: number) => Command;
4
+ export declare const jumpToDefinitionKeymap: readonly KeyBinding[];
package/dist/index.d.ts CHANGED
@@ -1,98 +1,2 @@
1
- import { EditorView, Tooltip } from "@codemirror/view";
2
- import { CompletionTriggerKind } from "vscode-languageserver-protocol";
3
- import type { CompletionContext, CompletionResult } from "@codemirror/autocomplete";
4
- import type { PluginValue, ViewUpdate } from "@codemirror/view";
5
- import { Transport } from "@open-rpc/client-js/build/transports/Transport";
6
- import type { PublishDiagnosticsParams } from "vscode-languageserver-protocol";
7
- import type * as LSP from "vscode-languageserver-protocol";
8
- interface LSPEventMap {
9
- "textDocument/publishDiagnostics": LSP.PublishDiagnosticsParams;
10
- }
11
- type Notification = {
12
- [key in keyof LSPEventMap]: {
13
- jsonrpc: "2.0";
14
- id?: null | undefined;
15
- method: key;
16
- params: LSPEventMap[key];
17
- };
18
- }[keyof LSPEventMap];
19
- export declare class LanguageServerClient {
20
- ready: boolean;
21
- capabilities: LSP.ServerCapabilities<any>;
22
- initializePromise: Promise<void>;
23
- private rootUri;
24
- private workspaceFolders;
25
- private autoClose?;
26
- private transport;
27
- private requestManager;
28
- private client;
29
- private plugins;
30
- constructor(options: LanguageServerClientOptions);
31
- initialize(): Promise<void>;
32
- close(): void;
33
- textDocumentDidOpen(params: LSP.DidOpenTextDocumentParams): Promise<LSP.DidOpenTextDocumentParams>;
34
- textDocumentDidChange(params: LSP.DidChangeTextDocumentParams): Promise<LSP.DidChangeTextDocumentParams>;
35
- textDocumentHover(params: LSP.HoverParams): Promise<LSP.Hover>;
36
- textDocumentCompletion(params: LSP.CompletionParams): Promise<LSP.CompletionList | LSP.CompletionItem[]>;
37
- attachPlugin(plugin: LanguageServerPlugin): void;
38
- detachPlugin(plugin: LanguageServerPlugin): void;
39
- private request;
40
- private notify;
41
- private processNotification;
42
- }
43
- declare class LanguageServerPlugin implements PluginValue {
44
- private view;
45
- private allowHTMLContent;
46
- client: LanguageServerClient;
47
- private documentUri;
48
- private languageId;
49
- private documentVersion;
50
- private changesTimeout;
51
- constructor(view: EditorView, allowHTMLContent: boolean);
52
- update({ docChanged }: ViewUpdate): void;
53
- destroy(): void;
54
- initialize({ documentText }: {
55
- documentText: string;
56
- }): Promise<void>;
57
- sendChange({ documentText }: {
58
- documentText: string;
59
- }): Promise<void>;
60
- requestDiagnostics(view: EditorView): void;
61
- requestHoverTooltip(view: EditorView, { line, character }: {
62
- line: number;
63
- character: number;
64
- }): Promise<Tooltip | null>;
65
- requestCompletion(context: CompletionContext, { line, character }: {
66
- line: number;
67
- character: number;
68
- }, { triggerKind, triggerCharacter, }: {
69
- triggerKind: CompletionTriggerKind;
70
- triggerCharacter: string | undefined;
71
- }): Promise<CompletionResult | null>;
72
- processNotification(notification: Notification): void;
73
- processDiagnostics(params: PublishDiagnosticsParams): void;
74
- }
75
- interface LanguageServerBaseOptions {
76
- rootUri: string | null;
77
- workspaceFolders: LSP.WorkspaceFolder[] | null;
78
- documentUri: string;
79
- languageId: string;
80
- }
81
- interface LanguageServerClientOptions extends LanguageServerBaseOptions {
82
- transport: Transport;
83
- autoClose?: boolean;
84
- }
85
- interface LanguageServerOptions extends LanguageServerClientOptions {
86
- client?: LanguageServerClient;
87
- allowHTMLContent?: boolean;
88
- }
89
- interface LanguageServerWebsocketOptions extends LanguageServerBaseOptions {
90
- serverUri: `ws://${string}` | `wss://${string}`;
91
- }
92
- export declare function languageServer(options: LanguageServerWebsocketOptions): ({
93
- extension: import("@codemirror/state").Extension;
94
- } | readonly import("@codemirror/state").Extension[])[];
95
- export declare function languageServerWithTransport(options: LanguageServerOptions): ({
96
- extension: import("@codemirror/state").Extension;
97
- } | readonly import("@codemirror/state").Extension[])[];
98
- export {};
1
+ export { LanguageServerClient, languageServerPlugin, languageServer, languageServerWithTransport, } from './plugin';
2
+ export { jumpToDefinition, jumpToDefinitionPos, jumpToDefinitionKeymap, } from './definition';
package/dist/index.js CHANGED
@@ -1,20 +1,41 @@
1
- import { autocompletion, insertCompletionText } from '@codemirror/autocomplete';
1
+ import { insertCompletionText, autocompletion } from '@codemirror/autocomplete';
2
2
  import { setDiagnostics } from '@codemirror/lint';
3
3
  import { Facet } from '@codemirror/state';
4
4
  import { ViewPlugin, hoverTooltip } from '@codemirror/view';
5
5
  import { RequestManager, Client, WebSocketTransport } from '@open-rpc/client-js';
6
- import { CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity } from 'vscode-languageserver-protocol';
7
- import { marked } from 'marked/lib/marked.esm.js';
6
+ import { CompletionItemKind, DiagnosticSeverity, CompletionTriggerKind } from 'vscode-languageserver-protocol';
7
+ import { marked } from 'marked';
8
+
9
+ function posToOffset(doc, pos) {
10
+ if (pos.line >= doc.lines) {
11
+ return;
12
+ }
13
+ const offset = doc.line(pos.line + 1).from + pos.character;
14
+ if (offset > doc.length) {
15
+ return;
16
+ }
17
+ return offset;
18
+ }
19
+ function offsetToPos(doc, offset) {
20
+ const line = doc.lineAt(offset);
21
+ return {
22
+ character: offset - line.from,
23
+ line: line.number - 1,
24
+ };
25
+ }
8
26
 
9
27
  const timeout = 10000;
10
28
  const changesDelay = 500;
11
29
  const CompletionItemKindMap = Object.fromEntries(Object.entries(CompletionItemKind).map(([key, value]) => [value, key]));
12
- const useLast = (values) => values.reduce((_, v) => v, "");
13
- const client = Facet.define({ combine: useLast });
30
+ const useLast = (values) => values.reduce((_, v) => v, '');
31
+ const client = Facet.define({
32
+ combine: useLast,
33
+ });
14
34
  const documentUri = Facet.define({ combine: useLast });
15
35
  const languageId = Facet.define({ combine: useLast });
16
36
  class LanguageServerClient {
17
37
  constructor(options) {
38
+ this.options = options;
18
39
  this.rootUri = options.rootUri;
19
40
  this.workspaceFolders = options.workspaceFolders;
20
41
  this.autoClose = options.autoClose;
@@ -29,11 +50,11 @@ class LanguageServerClient {
29
50
  if (webSocketTransport && webSocketTransport.connection) {
30
51
  // XXX(hjr265): Need a better way to do this. Relevant issue:
31
52
  // https://github.com/FurqanSoftware/codemirror-languageserver/issues/9
32
- webSocketTransport.connection.addEventListener("message", (message) => {
53
+ webSocketTransport.connection.addEventListener('message', (message) => {
33
54
  const data = JSON.parse(message.data);
34
55
  if (data.method && data.id) {
35
56
  webSocketTransport.connection.send(JSON.stringify({
36
- jsonrpc: "2.0",
57
+ jsonrpc: '2.0',
37
58
  id: data.id,
38
59
  result: null,
39
60
  }));
@@ -42,13 +63,14 @@ class LanguageServerClient {
42
63
  }
43
64
  this.initializePromise = this.initialize();
44
65
  }
45
- async initialize() {
46
- const { capabilities } = await this.request("initialize", {
66
+ getInitializeParams() {
67
+ var _a;
68
+ return {
47
69
  capabilities: {
48
70
  textDocument: {
49
71
  hover: {
50
72
  dynamicRegistration: true,
51
- contentFormat: ["plaintext", "markdown"],
73
+ contentFormat: ['plaintext', 'markdown'],
52
74
  },
53
75
  moniker: {},
54
76
  synchronization: {
@@ -62,16 +84,16 @@ class LanguageServerClient {
62
84
  completionItem: {
63
85
  snippetSupport: false,
64
86
  commitCharactersSupport: true,
65
- documentationFormat: ["plaintext", "markdown"],
87
+ documentationFormat: ['plaintext', 'markdown'],
66
88
  deprecatedSupport: false,
67
89
  preselectSupport: false,
68
90
  },
69
- contextSupport: false,
91
+ contextSupport: true,
70
92
  },
71
93
  signatureHelp: {
72
94
  dynamicRegistration: true,
73
95
  signatureInformation: {
74
- documentationFormat: ["plaintext", "markdown"],
96
+ documentationFormat: ['plaintext', 'markdown'],
75
97
  },
76
98
  },
77
99
  declaration: {
@@ -97,29 +119,35 @@ class LanguageServerClient {
97
119
  },
98
120
  },
99
121
  },
100
- initializationOptions: null,
122
+ initializationOptions: (_a = this.options.initializationOptions) !== null && _a !== void 0 ? _a : null,
101
123
  processId: null,
102
124
  rootUri: this.rootUri,
103
125
  workspaceFolders: this.workspaceFolders,
104
- }, timeout * 3);
126
+ };
127
+ }
128
+ async initialize() {
129
+ const { capabilities } = await this.request('initialize', this.getInitializeParams(), timeout * 3);
105
130
  this.capabilities = capabilities;
106
- this.notify("initialized", {});
131
+ this.notify('initialized', {});
107
132
  this.ready = true;
108
133
  }
109
134
  close() {
110
135
  this.client.close();
111
136
  }
112
137
  textDocumentDidOpen(params) {
113
- return this.notify("textDocument/didOpen", params);
138
+ return this.notify('textDocument/didOpen', params);
114
139
  }
115
140
  textDocumentDidChange(params) {
116
- return this.notify("textDocument/didChange", params);
141
+ return this.notify('textDocument/didChange', params);
117
142
  }
118
143
  async textDocumentHover(params) {
119
- return await this.request("textDocument/hover", params, timeout);
144
+ return await this.request('textDocument/hover', params, timeout);
120
145
  }
121
146
  async textDocumentCompletion(params) {
122
- return await this.request("textDocument/completion", params, timeout);
147
+ return await this.request('textDocument/completion', params, timeout);
148
+ }
149
+ async textDocumentDefinition(params) {
150
+ return await this.request('textDocument/definition', params, timeout);
123
151
  }
124
152
  attachPlugin(plugin) {
125
153
  this.plugins.push(plugin);
@@ -231,13 +259,13 @@ class LanguageServerPlugin {
231
259
  if (pos === null) {
232
260
  return null;
233
261
  }
234
- const dom = document.createElement("div");
235
- dom.classList.add("documentation");
262
+ const dom = document.createElement('div');
263
+ dom.classList.add('documentation');
236
264
  if (this.allowHTMLContent) {
237
- dom.innerHTML = formatContents(contents);
265
+ dom.innerHTML = await formatContents(contents);
238
266
  }
239
267
  else {
240
- dom.textContent = formatContents(contents);
268
+ dom.textContent = await formatContents(contents);
241
269
  }
242
270
  return {
243
271
  pos,
@@ -247,7 +275,8 @@ class LanguageServerPlugin {
247
275
  };
248
276
  }
249
277
  async requestCompletion(context, { line, character }, { triggerKind, triggerCharacter, }) {
250
- if (!this.client.ready || !this.client.capabilities.completionProvider) {
278
+ if (!this.client.ready ||
279
+ !this.client.capabilities.completionProvider) {
251
280
  return null;
252
281
  }
253
282
  this.sendChange({
@@ -264,7 +293,7 @@ class LanguageServerPlugin {
264
293
  if (!result) {
265
294
  return null;
266
295
  }
267
- let items = "items" in result ? result.items : result;
296
+ let items = 'items' in result ? result.items : result;
268
297
  const [span, match] = prefixMatch(items);
269
298
  const token = context.matchBefore(match);
270
299
  let { pos } = context;
@@ -309,10 +338,12 @@ class LanguageServerPlugin {
309
338
  }
310
339
  additionalTextEdits
311
340
  .sort(({ range: { end: a } }, { range: { end: b } }) => {
312
- if (posToOffset(view.state.doc, a) < posToOffset(view.state.doc, b)) {
341
+ if (posToOffset(view.state.doc, a) <
342
+ posToOffset(view.state.doc, b)) {
313
343
  return 1;
314
344
  }
315
- else if (posToOffset(view.state.doc, a) > posToOffset(view.state.doc, b)) {
345
+ else if (posToOffset(view.state.doc, a) >
346
+ posToOffset(view.state.doc, b)) {
316
347
  return -1;
317
348
  }
318
349
  return 0;
@@ -330,7 +361,18 @@ class LanguageServerPlugin {
330
361
  type: kind && CompletionItemKindMap[kind].toLowerCase(),
331
362
  };
332
363
  if (documentation) {
333
- completion.info = formatContents(documentation);
364
+ completion.info = async () => {
365
+ const dom = document.createElement('div');
366
+ dom.classList.add('documentation');
367
+ if (this.allowHTMLContent) {
368
+ dom.innerHTML = await formatContents(documentation);
369
+ }
370
+ else {
371
+ dom.textContent =
372
+ await formatContents(documentation);
373
+ }
374
+ return dom;
375
+ };
334
376
  }
335
377
  return completion;
336
378
  });
@@ -340,10 +382,39 @@ class LanguageServerPlugin {
340
382
  filter: false,
341
383
  };
342
384
  }
385
+ async requestDefinition(view, { line, character }) {
386
+ if (!this.client.ready ||
387
+ !this.client.capabilities.definitionProvider) {
388
+ return null;
389
+ }
390
+ const result = await this.client.textDocumentDefinition({
391
+ textDocument: { uri: this.documentUri },
392
+ position: { line, character },
393
+ });
394
+ if (!result)
395
+ return;
396
+ const location = Array.isArray(result) ? result[0] : result;
397
+ const uri = location.uri ||
398
+ location.targetUri;
399
+ const range = location.range ||
400
+ location.targetRange;
401
+ if (uri === this.documentUri) {
402
+ view.dispatch(view.state.update({
403
+ selection: {
404
+ anchor: posToOffset(view.state.doc, range.start),
405
+ head: posToOffset(view.state.doc, range.end),
406
+ },
407
+ }));
408
+ }
409
+ return {
410
+ uri,
411
+ range,
412
+ };
413
+ }
343
414
  processNotification(notification) {
344
415
  try {
345
416
  switch (notification.method) {
346
- case "textDocument/publishDiagnostics":
417
+ case 'textDocument/publishDiagnostics':
347
418
  this.processDiagnostics(notification.params);
348
419
  }
349
420
  }
@@ -360,14 +431,17 @@ class LanguageServerPlugin {
360
431
  from: posToOffset(this.view.state.doc, range.start),
361
432
  to: posToOffset(this.view.state.doc, range.end),
362
433
  severity: {
363
- [DiagnosticSeverity.Error]: "error",
364
- [DiagnosticSeverity.Warning]: "warning",
365
- [DiagnosticSeverity.Information]: "info",
366
- [DiagnosticSeverity.Hint]: "info",
434
+ [DiagnosticSeverity.Error]: 'error',
435
+ [DiagnosticSeverity.Warning]: 'warning',
436
+ [DiagnosticSeverity.Information]: 'info',
437
+ [DiagnosticSeverity.Hint]: 'info',
367
438
  }[severity],
368
439
  message,
369
440
  }))
370
- .filter(({ from, to }) => from !== null && to !== null && from !== undefined && to !== undefined)
441
+ .filter(({ from, to }) => from !== null &&
442
+ to !== null &&
443
+ from !== undefined &&
444
+ to !== undefined)
371
445
  .sort((a, b) => {
372
446
  switch (true) {
373
447
  case a.from < b.from:
@@ -380,33 +454,38 @@ class LanguageServerPlugin {
380
454
  this.view.dispatch(setDiagnostics(this.view.state, diagnostics));
381
455
  }
382
456
  }
457
+ const languageServerPlugin = ViewPlugin.fromClass(LanguageServerPlugin);
383
458
  function languageServer(options) {
384
459
  const serverUri = options.serverUri;
385
- delete options.serverUri;
460
+ const { serverUri: _, ...optionsWithoutServerUri } = options;
386
461
  return languageServerWithTransport({
387
- ...options,
462
+ ...optionsWithoutServerUri,
388
463
  transport: new WebSocketTransport(serverUri),
389
464
  });
390
465
  }
391
466
  function languageServerWithTransport(options) {
392
- let plugin = null;
393
467
  return [
394
- client.of(options.client || new LanguageServerClient({ ...options, autoClose: true })),
468
+ client.of(options.client ||
469
+ new LanguageServerClient({
470
+ ...options,
471
+ autoClose: true,
472
+ })),
395
473
  documentUri.of(options.documentUri),
396
474
  languageId.of(options.languageId),
397
- ViewPlugin.define((view) => (plugin = new LanguageServerPlugin(view, options.allowHTMLContent))),
475
+ languageServerPlugin.of(options.allowHTMLContent),
398
476
  hoverTooltip((view, pos) => {
399
477
  var _a;
400
- return (_a = plugin === null || plugin === void 0 ? void 0 : plugin.requestHoverTooltip(view, offsetToPos(view.state.doc, pos))) !== null && _a !== void 0 ? _a : null;
478
+ const plugin = view.plugin(languageServerPlugin);
479
+ return ((_a = plugin === null || plugin === void 0 ? void 0 : plugin.requestHoverTooltip(view, offsetToPos(view.state.doc, pos))) !== null && _a !== void 0 ? _a : null);
401
480
  }),
402
481
  autocompletion({
403
482
  override: [
404
483
  async (context) => {
405
484
  var _a, _b, _c;
406
- if (plugin == null) {
485
+ const { state, pos, explicit, view } = context;
486
+ const plugin = view.plugin(languageServerPlugin);
487
+ if (plugin == null)
407
488
  return null;
408
- }
409
- const { state, pos, explicit } = context;
410
489
  const line = state.doc.lineAt(pos);
411
490
  let trigKind = CompletionTriggerKind.Invoked;
412
491
  let trigChar;
@@ -428,61 +507,45 @@ function languageServerWithTransport(options) {
428
507
  }),
429
508
  ];
430
509
  }
431
- function posToOffset(doc, pos) {
432
- if (pos.line >= doc.lines) {
433
- return;
434
- }
435
- const offset = doc.line(pos.line + 1).from + pos.character;
436
- if (offset > doc.length) {
437
- return;
438
- }
439
- return offset;
440
- }
441
- function offsetToPos(doc, offset) {
442
- const line = doc.lineAt(offset);
443
- return {
444
- character: offset - line.from,
445
- line: line.number - 1,
446
- };
447
- }
448
- function formatContents(contents) {
510
+ async function formatContents(contents) {
449
511
  if (isLSPMarkupContent(contents)) {
450
512
  let value = contents.value;
451
- if (contents.kind === "markdown") {
452
- value = marked.parse(value);
513
+ if (contents.kind === 'markdown') {
514
+ value = await marked.parse(value);
453
515
  }
454
516
  return value;
455
517
  }
456
518
  else if (Array.isArray(contents)) {
457
- return contents.map((c) => formatContents(c) + "\n\n").join("");
519
+ return contents.map((c) => formatContents(c) + '\n\n').join('');
458
520
  }
459
- else if (typeof contents === "string") {
521
+ else if (typeof contents === 'string') {
460
522
  return contents;
461
523
  }
462
524
  }
463
525
  function toSet(chars) {
464
- let preamble = "";
465
- let flat = Array.from(chars).join("");
526
+ let preamble = '';
527
+ let flat = Array.from(chars).join('');
466
528
  const words = /\w/.test(flat);
467
529
  if (words) {
468
- preamble += "\\w";
469
- flat = flat.replace(/\w/g, "");
530
+ preamble += '\\w';
531
+ flat = flat.replace(/\w/g, '');
470
532
  }
471
- return `[${preamble}${flat.replace(/[^\w\s]/g, "\\$&")}]`;
533
+ return `[${preamble}${flat.replace(/[^\w\s]/g, '\\$&')}]`;
472
534
  }
473
535
  function prefixMatch(items) {
474
536
  var _a;
475
537
  const first = new Set();
476
538
  const rest = new Set();
477
539
  for (const item of items) {
478
- const [initial, ...restStr] = ((_a = item.textEdit) === null || _a === void 0 ? void 0 : _a.newText) || item.label;
540
+ const text = ((_a = item.textEdit) === null || _a === void 0 ? void 0 : _a.newText) || item.label;
541
+ const initial = text[0];
479
542
  first.add(initial);
480
- for (const char of restStr) {
481
- rest.add(char);
543
+ for (let i = 1; i < text.length; i++) {
544
+ rest.add(text[i]);
482
545
  }
483
546
  }
484
- const source = toSet(first) + toSet(rest) + "*$";
485
- return [new RegExp("^" + source), new RegExp(source)];
547
+ const source = toSet(first) + toSet(rest) + '*$';
548
+ return [new RegExp('^' + source), new RegExp(source)];
486
549
  }
487
550
  function isLSPTextEdit(textEdit) {
488
551
  return (textEdit === null || textEdit === void 0 ? void 0 : textEdit.range) !== undefined;
@@ -491,4 +554,17 @@ function isLSPMarkupContent(contents) {
491
554
  return contents.kind !== undefined;
492
555
  }
493
556
 
494
- export { LanguageServerClient, languageServer, languageServerWithTransport };
557
+ function jumpTo(view, pos) {
558
+ const plugin = view.plugin(languageServerPlugin);
559
+ if (!plugin)
560
+ return false;
561
+ plugin.requestDefinition(view, offsetToPos(view.state.doc, pos));
562
+ return true;
563
+ }
564
+ const jumpToDefinition = (view) => jumpTo(view, view.state.selection.main.head);
565
+ const jumpToDefinitionPos = (pos) => (view) => jumpTo(view, pos);
566
+ const jumpToDefinitionKeymap = [
567
+ { key: 'F12', run: jumpToDefinition, preventDefault: true },
568
+ ];
569
+
570
+ export { LanguageServerClient, jumpToDefinition, jumpToDefinitionKeymap, jumpToDefinitionPos, languageServer, languageServerPlugin, languageServerWithTransport };