@adecore/editor-react 0.21.0 → 0.21.2

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 (109) hide show
  1. package/dist/AnchoredPopup.js +2 -3
  2. package/dist/HoverSections.js +5 -11
  3. package/dist/PeekPanel.js +6 -13
  4. package/dist/RenameCard.js +2 -2
  5. package/dist/SymbolPicker.js +2 -3
  6. package/dist/code-actions-model.js +9 -8
  7. package/dist/code-actions.d.ts +1 -1
  8. package/dist/code-actions.js +21 -20
  9. package/dist/code-authors.js +1 -1
  10. package/dist/code-vision.d.ts +1 -0
  11. package/dist/code-vision.js +17 -14
  12. package/dist/completion-call.js +2 -6
  13. package/dist/completion-model.d.ts +3 -2
  14. package/dist/completion-model.js +8 -9
  15. package/dist/completion.js +8 -10
  16. package/dist/context-menu.js +4 -5
  17. package/dist/definition-link.js +2 -1
  18. package/dist/diagnostics-model.d.ts +2 -0
  19. package/dist/diagnostics-model.js +8 -2
  20. package/dist/diagnostics.d.ts +1 -0
  21. package/dist/diagnostics.js +13 -12
  22. package/dist/editor-language.d.ts +2 -2
  23. package/dist/editor-language.js +10 -10
  24. package/dist/error-message.d.ts +1 -0
  25. package/dist/error-message.js +3 -0
  26. package/dist/folding.js +5 -24
  27. package/dist/history.d.ts +1 -0
  28. package/dist/history.js +9 -12
  29. package/dist/hover-content.d.ts +1 -0
  30. package/dist/hover-content.js +5 -4
  31. package/dist/hover.d.ts +2 -2
  32. package/dist/hover.js +10 -9
  33. package/dist/inlay-hints.js +4 -4
  34. package/dist/language-ids.js +1 -1
  35. package/dist/line-text.d.ts +3 -0
  36. package/dist/line-text.js +7 -0
  37. package/dist/navigation.js +9 -9
  38. package/dist/paths.d.ts +1 -0
  39. package/dist/paths.js +4 -0
  40. package/dist/peek.js +11 -4
  41. package/dist/project-language.js +15 -13
  42. package/dist/project-problems.d.ts +1 -2
  43. package/dist/project-problems.js +2 -4
  44. package/dist/refresher.js +11 -6
  45. package/dist/rename.d.ts +2 -2
  46. package/dist/rename.js +14 -12
  47. package/dist/review-group.d.ts +1 -0
  48. package/dist/review-group.js +10 -14
  49. package/dist/row-host.d.ts +13 -20
  50. package/dist/row-host.js +33 -60
  51. package/dist/semantic-model.js +4 -6
  52. package/dist/semantic-tokens.js +2 -4
  53. package/dist/signature.js +1 -1
  54. package/dist/snippet-session.js +3 -4
  55. package/dist/symbol-links.js +2 -2
  56. package/dist/symbol-picker-model.js +15 -9
  57. package/dist/symbol-picker.js +2 -1
  58. package/dist/symbols.d.ts +7 -1
  59. package/dist/symbols.js +22 -28
  60. package/dist/testing.js +3 -3
  61. package/dist/word-diff.js +2 -4
  62. package/dist/workspace-edit.js +7 -8
  63. package/package.json +5 -5
  64. package/src/AnchoredPopup.tsx +2 -3
  65. package/src/HoverSections.tsx +5 -11
  66. package/src/PeekPanel.tsx +6 -14
  67. package/src/RenameCard.tsx +2 -2
  68. package/src/SymbolPicker.tsx +3 -4
  69. package/src/code-actions-model.ts +10 -8
  70. package/src/code-actions.ts +36 -38
  71. package/src/code-authors.ts +2 -2
  72. package/src/code-vision.ts +19 -15
  73. package/src/completion-call.ts +2 -6
  74. package/src/completion-model.ts +10 -12
  75. package/src/completion.ts +8 -13
  76. package/src/context-menu.ts +4 -5
  77. package/src/definition-link.ts +2 -1
  78. package/src/diagnostics-model.ts +10 -2
  79. package/src/diagnostics.ts +15 -13
  80. package/src/editor-language.ts +11 -11
  81. package/src/error-message.ts +3 -0
  82. package/src/folding.ts +6 -26
  83. package/src/history.ts +11 -12
  84. package/src/hover-content.ts +6 -4
  85. package/src/hover.ts +10 -9
  86. package/src/inlay-hints.ts +4 -4
  87. package/src/language-ids.ts +1 -1
  88. package/src/line-text.ts +10 -0
  89. package/src/navigation.ts +11 -10
  90. package/src/paths.ts +5 -0
  91. package/src/peek.ts +11 -10
  92. package/src/project-language.ts +17 -16
  93. package/src/project-problems.ts +3 -6
  94. package/src/refresher.ts +12 -6
  95. package/src/rename.ts +16 -18
  96. package/src/review-group.ts +11 -15
  97. package/src/row-host.ts +54 -76
  98. package/src/semantic-model.ts +4 -6
  99. package/src/semantic-tokens.ts +2 -4
  100. package/src/signature.ts +1 -1
  101. package/src/snippet-session.ts +3 -4
  102. package/src/symbol-links.ts +2 -2
  103. package/src/symbol-picker-model.ts +17 -9
  104. package/src/symbol-picker.ts +2 -1
  105. package/src/symbols.ts +29 -28
  106. package/src/testing.ts +4 -2
  107. package/src/view-state.ts +2 -0
  108. package/src/word-diff.ts +2 -4
  109. package/src/workspace-edit.ts +7 -8
@@ -1,4 +1,5 @@
1
1
  const SEVERITIES = { 1: 'error', 2: 'warning', 3: 'info', 4: 'hint' };
2
+ const HINT = 4;
2
3
  const UNNECESSARY = 1;
3
4
  const DEPRECATED = 2;
4
5
  /* The scroll track's tip is a line or two, so a long message is cut where it stops being a tip. */
@@ -16,6 +17,11 @@ export function markerOf(diagnostic) {
16
17
  message: diagnostic.message.length > TICK_MESSAGE_LENGTH ? `${diagnostic.message.slice(0, TICK_MESSAGE_LENGTH)}…` : diagnostic.message
17
18
  };
18
19
  }
20
+ /* A diagnostic that names no severity counts as an error. */
21
+ export function problemCountsOf(diagnostics) {
22
+ const count = (severity) => diagnostics.filter((diagnostic) => (diagnostic.severity ?? 1) === severity).length;
23
+ return { error: count(1), warning: count(2), info: count(3) };
24
+ }
19
25
  export function comparePositions(left, right) {
20
26
  return left.line === right.line ? left.character - right.character : left.line - right.line;
21
27
  }
@@ -57,13 +63,13 @@ export function rangeHolds(range, position) {
57
63
  export function problemsAt(problems, position) {
58
64
  const rank = (problem) => problem.diagnostic.severity ?? 1;
59
65
  return problems
60
- .filter((problem) => (problem.diagnostic.severity ?? 1) < 4 && rangeHolds(problem.diagnostic.range, position))
66
+ .filter((problem) => (problem.diagnostic.severity ?? 1) < HINT && rangeHolds(problem.diagnostic.range, position))
61
67
  .sort((left, right) => rank(left) - rank(right));
62
68
  }
63
69
  /* The start of the first problem after the position, or the last one before it when `direction` is back; it wraps round the document. */
64
70
  export function neighborProblem(problems, from, direction) {
65
71
  const candidates = problems
66
- .filter((problem) => (problem.diagnostic.severity ?? 1) < 4)
72
+ .filter((problem) => (problem.diagnostic.severity ?? 1) < HINT)
67
73
  .sort((left, right) => comparePositions(left.diagnostic.range.start, right.diagnostic.range.start));
68
74
  if (candidates.length === 0) {
69
75
  return null;
@@ -18,6 +18,7 @@ export declare class DiagnosticsFeature {
18
18
  step(direction: 1 | -1): boolean;
19
19
  goToFirst(): boolean;
20
20
  onChange(listener: () => void): () => void;
21
+ private reveal;
21
22
  private shift;
22
23
  private rebuild;
23
24
  }
@@ -1,5 +1,5 @@
1
1
  import { isShortcut } from './shortcut-keys.js';
2
- import { comparePositions, markerOf, neighborProblem, problemsAt, shiftRange } from './diagnostics-model.js';
2
+ import { comparePositions, markerOf, neighborProblem, problemCountsOf, problemsAt, shiftRange } from './diagnostics-model.js';
3
3
  /*
4
4
  * The problems the servers report for one file. A report from a server replaces the one before it
5
5
  * from the same server, the editor draws them, and each one follows its text through edits made
@@ -41,34 +41,29 @@ export class DiagnosticsFeature {
41
41
  return this.list;
42
42
  }
43
43
  counts() {
44
- const count = (severity) => this.list.filter((problem) => (problem.diagnostic.severity ?? 1) === severity).length;
45
- return { error: count(1), warning: count(2), info: count(3) };
44
+ return problemCountsOf(this.list.map((problem) => problem.diagnostic));
46
45
  }
47
46
  at(position) {
48
47
  return problemsAt(this.list, position);
49
48
  }
50
49
  /* Puts the caret on the next problem, or the one before; false when there is none. */
51
50
  step(direction) {
52
- const { editor } = this.language;
53
- const target = neighborProblem(this.list, editor.getCaret(), direction);
51
+ const caret = this.language.editor.getCaret();
52
+ const target = neighborProblem(this.list, caret, direction);
54
53
  if (target === null) {
55
54
  return false;
56
55
  }
57
- editor.setCaret(target.diagnostic.range.start, comparePositions(target.diagnostic.range.start, editor.getCaret()) > 0 ? 'centerDown' : 'centerUp');
58
- editor.focus();
59
- this.language.hover.showProblemsAt(editor.getCaret());
56
+ const { start } = target.diagnostic.range;
57
+ this.reveal(start, comparePositions(start, caret) > 0 ? 'centerDown' : 'centerUp');
60
58
  return true;
61
59
  }
62
60
  /* Puts the caret on the first problem of the file. */
63
61
  goToFirst() {
64
- const { editor } = this.language;
65
62
  const target = neighborProblem(this.list, { line: -1, character: 0 }, 1);
66
63
  if (target === null) {
67
64
  return false;
68
65
  }
69
- editor.setCaret(target.diagnostic.range.start, 'center');
70
- editor.focus();
71
- this.language.hover.showProblemsAt(editor.getCaret());
66
+ this.reveal(target.diagnostic.range.start, 'center');
72
67
  return true;
73
68
  }
74
69
  onChange(listener) {
@@ -77,6 +72,12 @@ export class DiagnosticsFeature {
77
72
  this.listeners.delete(listener);
78
73
  };
79
74
  }
75
+ reveal(position, reveal) {
76
+ const { editor } = this.language;
77
+ editor.setCaret(position, reveal);
78
+ editor.focus();
79
+ this.language.hover.showProblemsAt(editor.getCaret());
80
+ }
80
81
  shift(changes) {
81
82
  for (const [server, diagnostics] of this.reports) {
82
83
  this.reports.set(server, diagnostics.map((diagnostic) => ({ ...diagnostic, range: shiftRange(diagnostic.range, changes) })));
@@ -1,5 +1,4 @@
1
1
  import { type Keymap } from '@adecore/editor/keymap';
2
- import { shortcutsOf } from './shortcut-keys.ts';
3
2
  import type { Editor, EditorPosition } from '@adecore/editor';
4
3
  import type { Location } from '@adecore/lsp';
5
4
  import { CodeActionsFeature } from './code-actions.ts';
@@ -16,6 +15,7 @@ import { PeekFeature } from './peek.ts';
16
15
  import { PickFeature } from './pick.ts';
17
16
  import { SymbolPickerFeature } from './symbol-picker.ts';
18
17
  import { RenameFeature } from './rename.ts';
18
+ import { shortcutsOf } from './shortcut-keys.ts';
19
19
  import { type Timers } from './timers.ts';
20
20
  import { SignatureFeature } from './signature.ts';
21
21
  import { SnippetFeature } from './snippet-session.ts';
@@ -51,7 +51,6 @@ export declare class EditorLanguage {
51
51
  private readonly disposers;
52
52
  private disposed;
53
53
  constructor(project: ProjectLanguage, editor: Editor, uri: string, languageId: string, timers?: Timers);
54
- private placeOpenedCaret;
55
54
  get uri(): string;
56
55
  get isDisposed(): boolean;
57
56
  get place(): Place;
@@ -62,4 +61,5 @@ export declare class EditorLanguage {
62
61
  onProvidersChanged(listener: () => void): () => void;
63
62
  onDispose(dispose: () => void): void;
64
63
  dispose(): void;
64
+ private placeOpenedCaret;
65
65
  }
@@ -1,5 +1,4 @@
1
1
  import { KEYMAP } from '@adecore/editor/keymap';
2
- import { shortcutsOf } from './shortcut-keys.js';
3
2
  import { CodeActionsFeature } from './code-actions.js';
4
3
  import { CodeVisionFeature } from './code-vision.js';
5
4
  import { CompletionFeature } from './completion.js';
@@ -15,6 +14,7 @@ import { PeekFeature } from './peek.js';
15
14
  import { PickFeature } from './pick.js';
16
15
  import { SymbolPickerFeature } from './symbol-picker.js';
17
16
  import { RenameFeature } from './rename.js';
17
+ import { shortcutsOf } from './shortcut-keys.js';
18
18
  import { createPopupStore } from './popups.js';
19
19
  import { realTimers } from './timers.js';
20
20
  import { InlayHintsFeature } from './inlay-hints.js';
@@ -88,15 +88,6 @@ export class EditorLanguage {
88
88
  this.placeOpenedCaret(editor.getCaret());
89
89
  this.onDispose(editor.onCaret((position) => this.placeOpenedCaret(position)));
90
90
  }
91
- /* A file opened from another one lands on the line; this puts the caret on the column the jump meant. */
92
- placeOpenedCaret(position) {
93
- const column = this.project.takeCaret(this.uri, position.line);
94
- if (column !== null && column.character !== position.character) {
95
- this.editor.setCaret(column);
96
- return;
97
- }
98
- this.project.noteCaret({ uri: this.uri, position });
99
- }
100
91
  get uri() {
101
92
  return this.document.uri;
102
93
  }
@@ -167,4 +158,13 @@ export class EditorLanguage {
167
158
  }
168
159
  this.document.release();
169
160
  }
161
+ /* A file opened from another one lands on the line; this puts the caret on the column the jump meant. */
162
+ placeOpenedCaret(position) {
163
+ const column = this.project.takeCaret(this.uri, position.line);
164
+ if (column !== null && column.character !== position.character) {
165
+ this.editor.setCaret(column);
166
+ return;
167
+ }
168
+ this.project.noteCaret({ uri: this.uri, position });
169
+ }
170
170
  }
@@ -0,0 +1 @@
1
+ export declare function messageOf(error: unknown): string;
@@ -0,0 +1,3 @@
1
+ export function messageOf(error) {
2
+ return error instanceof Error ? error.message : String(error);
3
+ }
package/dist/folding.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Refresher } from './refresher.js';
2
+ import { symbolSpansOf } from './symbols.js';
2
3
  import { realTimers } from './timers.js';
3
4
  const METHOD = 'textDocument/foldingRange';
4
5
  const PAUSE_MS = 500;
@@ -16,32 +17,12 @@ const BODIES = {
16
17
  14: 'value',
17
18
  23: 'class'
18
19
  };
19
- function walk(symbols, found) {
20
- for (const symbol of symbols) {
21
- const body = BODIES[symbol.kind];
22
- if (body !== undefined && symbol.range.end.line > symbol.range.start.line) {
23
- found.push({ range: symbol.range, body });
24
- }
25
- walk(symbol.children ?? [], found);
26
- }
27
- }
28
20
  /* The symbols that have a body of more than one line, as what the editor folds them by. */
29
21
  export function foldSymbolsOf(result) {
30
- const found = [];
31
- if (result === null || result.length === 0) {
32
- return found;
33
- }
34
- if ('range' in result[0]) {
35
- walk(result, found);
36
- return found;
37
- }
38
- for (const symbol of result) {
39
- const body = BODIES[symbol.kind];
40
- if (body !== undefined && symbol.location.range.end.line > symbol.location.range.start.line) {
41
- found.push({ range: symbol.location.range, body });
42
- }
43
- }
44
- return found;
22
+ return symbolSpansOf(result).flatMap(({ kind, range }) => {
23
+ const body = BODIES[kind];
24
+ return body !== undefined && range.end.line > range.start.line ? [{ range, body }] : [];
25
+ });
45
26
  }
46
27
  /* The ranges a server folds that span more than one line, with where a range starts and ends within its lines when it says. */
47
28
  export function foldRangesOf(ranges) {
package/dist/history.d.ts CHANGED
@@ -5,4 +5,5 @@ export declare class HistoryFeature {
5
5
  back(): boolean;
6
6
  forward(): boolean;
7
7
  recent(): void;
8
+ private visit;
8
9
  }
package/dist/history.js CHANGED
@@ -23,24 +23,21 @@ export class HistoryFeature {
23
23
  }
24
24
  /* Returns to where the caret was before the last jump; false when there is no such place. */
25
25
  back() {
26
- const place = this.language.project.history.back(this.language.place);
27
- if (place === null) {
28
- return false;
29
- }
30
- this.language.visit(place);
31
- return true;
26
+ return this.visit(this.language.project.history.back(this.language.place));
32
27
  }
33
28
  forward() {
34
- const place = this.language.project.history.forward(this.language.place);
35
- if (place === null) {
36
- return false;
37
- }
38
- this.language.visit(place);
39
- return true;
29
+ return this.visit(this.language.project.history.forward(this.language.place));
40
30
  }
41
31
  /* Lists the places of the history under the caret; choosing one jumps there like any other jump. */
42
32
  recent() {
43
33
  const places = this.language.project.history.recent(this.language.place);
44
34
  this.language.locations(places.map((place) => ({ uri: place.uri, range: { start: place.position, end: place.position } })), this.language.editor.getCaret(), this.language.project.i18n.t('editor:language.history.recent'));
45
35
  }
36
+ visit(place) {
37
+ if (place === null) {
38
+ return false;
39
+ }
40
+ this.language.visit(place);
41
+ return true;
42
+ }
46
43
  }
@@ -10,6 +10,7 @@ export interface HoverText {
10
10
  }
11
11
  export declare function splitSignatures(markdown: string): HoverText;
12
12
  export declare function hoverTextOf(hover: Hover): HoverText;
13
+ export declare function escapeMarkdown(text: string): string;
13
14
  export declare function isEmptyHover(text: HoverText): boolean;
14
15
  export declare function locationsOf(result: NavigationResult): Location[];
15
16
  export declare function sameRange(left: EditorRange, right: EditorRange): boolean;
@@ -35,10 +35,11 @@ export function hoverTextOf(hover) {
35
35
  if (typeof contents === 'string' || 'language' in contents) {
36
36
  return markedStringText(contents);
37
37
  }
38
- // Plain text is text, and not markdown that happens to have underscores in it.
39
- return contents.kind === 'plaintext'
40
- ? { signatures: [], markdown: contents.value.replace(/([\\`*_{}[\]()#+\-.!|<>~])/g, '\\$1').trim() }
41
- : splitSignatures(contents.value);
38
+ return contents.kind === 'plaintext' ? { signatures: [], markdown: escapeMarkdown(contents.value).trim() } : splitSignatures(contents.value);
39
+ }
40
+ /* Plain text as markdown that reads the same, its markdown characters escaped. */
41
+ export function escapeMarkdown(text) {
42
+ return text.replace(/([\\`*_{}[\]()#+\-.!|<>~])/g, '\\$1');
42
43
  }
43
44
  export function isEmptyHover(text) {
44
45
  return text.signatures.length === 0 && text.markdown === '';
package/dist/hover.d.ts CHANGED
@@ -4,10 +4,10 @@ import { type Timers } from './timers.ts';
4
4
  export declare class HoverFeature {
5
5
  private readonly language;
6
6
  private readonly timers;
7
- private showTimer;
8
- private countTimer;
9
7
  private readonly counts;
8
+ private showTimer;
10
9
  private hideTimer;
10
+ private countTimer;
11
11
  private inCard;
12
12
  private request;
13
13
  private lookup;
package/dist/hover.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { rangeHolds } from './diagnostics-model.js';
2
2
  import { hoverTextOf, isEmptyHover, locationsOf } from './hover-content.js';
3
+ import { lineTextOf } from './line-text.js';
3
4
  import { wordRangeAt } from './rename-model.js';
4
5
  import { isShortcut } from './shortcut-keys.js';
5
6
  import { realTimers } from './timers.js';
@@ -16,11 +17,11 @@ const TOAST_ID = 'language-hover';
16
17
  export class HoverFeature {
17
18
  language;
18
19
  timers;
19
- showTimer;
20
- countTimer;
21
20
  /* The counts asked for since the text last changed, by the symbol's position, so moving back to a name does not ask again. */
22
21
  counts = new Map();
22
+ showTimer;
23
23
  hideTimer;
24
+ countTimer;
24
25
  inCard = false;
25
26
  /* Counts every request, so an answer that arrives after the pointer moved on is dropped. */
26
27
  request = 0;
@@ -167,12 +168,15 @@ export class HoverFeature {
167
168
  service.hover(uri, position, { signal }).catch(() => null),
168
169
  service.supports('textDocument/definition', uri) ? service.definition(uri, position, { signal }).catch(() => null) : null
169
170
  ]);
170
- const text = hover === null ? null : hoverTextOf(hover);
171
- if (hover === null || text === null || isEmptyHover(text)) {
171
+ if (hover === null) {
172
+ return { info: null, range: null };
173
+ }
174
+ const text = hoverTextOf(hover);
175
+ if (isEmptyHover(text)) {
172
176
  return { info: null, range: null };
173
177
  }
174
178
  const { editor } = this.language;
175
- const line = editor.textInRange({ start: { line: position.line, character: 0 }, end: { line: position.line, character: Number.MAX_SAFE_INTEGER } });
179
+ const line = lineTextOf(editor, position.line);
176
180
  const word = wordRangeAt(line, position);
177
181
  return {
178
182
  info: {
@@ -251,10 +255,7 @@ export class HoverFeature {
251
255
  }
252
256
  /* Where the word at a position starts, which names the symbol for the cache. */
253
257
  wordStart(position) {
254
- const line = this.language.editor.textInRange({
255
- start: { line: position.line, character: 0 },
256
- end: { line: position.line, character: Number.MAX_SAFE_INTEGER }
257
- });
258
+ const line = lineTextOf(this.language.editor, position.line);
258
259
  return wordRangeAt(line, position)?.start.character ?? position.character;
259
260
  }
260
261
  }
@@ -1,3 +1,4 @@
1
+ import { lineTextOf } from './line-text.js';
1
2
  import { Refresher } from './refresher.js';
2
3
  import { realTimers } from './timers.js';
3
4
  const METHOD = 'textDocument/inlayHint';
@@ -25,9 +26,8 @@ export function covers(asked, visible) {
25
26
  return asked.start.line <= visible.start.line && asked.end.line >= visible.end.line;
26
27
  }
27
28
  /*
28
- * Types and parameter names the servers infer, drawn in the text as soft pills. They are asked for the
29
- * lines in view and a margin around them after a pause in typing or scrolling; until the answer comes the
30
- * last ones stay where their text went, so nothing blinks while typing. Scrolling asks again only once the
29
+ * Types and parameter names the servers infer, asked for the lines in view and a margin around them. The
30
+ * last ones stay until the answer comes, so nothing blinks while typing; scrolling asks again only once the
31
31
  * view leaves the lines that were asked for.
32
32
  */
33
33
  export class InlayHintsFeature {
@@ -43,7 +43,7 @@ export class InlayHintsFeature {
43
43
  }
44
44
  const range = withMargin(editor.getVisibleRange(), editor.positionAt(Number.MAX_SAFE_INTEGER), (line) => ({
45
45
  line,
46
- character: editor.textInRange({ start: { line, character: 0 }, end: { line, character: Number.MAX_SAFE_INTEGER } }).length
46
+ character: lineTextOf(editor, line).length
47
47
  }));
48
48
  this.asked = range;
49
49
  const hints = await service.inlayHints(uri, range, { signal });
@@ -1,4 +1,4 @@
1
- /* The highlighter ids `fs.read` answers with, by the id LSP names the same language. */
1
+ /* Highlighter ids mapped to the LSP id of the same language. */
2
2
  const LSP_IDS = {
3
3
  typescript: 'typescript',
4
4
  tsx: 'typescriptreact',
@@ -0,0 +1,3 @@
1
+ import type { Editor, EditorPosition } from '@adecore/editor';
2
+ export declare function lineTextOf(editor: Editor, line: number): string;
3
+ export declare function textBeforeOf(editor: Editor, position: EditorPosition): string;
@@ -0,0 +1,7 @@
1
+ export function lineTextOf(editor, line) {
2
+ return editor.textInRange({ start: { line, character: 0 }, end: { line, character: Number.MAX_SAFE_INTEGER } });
3
+ }
4
+ /* The text of a position's line up to the position. */
5
+ export function textBeforeOf(editor, position) {
6
+ return editor.textInRange({ start: { line: position.line, character: 0 }, end: position });
7
+ }
@@ -3,12 +3,19 @@ import { basenameOf } from './paths.js';
3
3
  import { rangeHolds } from './diagnostics-model.js';
4
4
  import { placesOfName } from './symbol-links.js';
5
5
  import { isShortcut } from './shortcut-keys.js';
6
+ import { messageOf } from './error-message.js';
6
7
  const METHODS = {
7
8
  definition: 'textDocument/definition',
8
9
  declaration: 'textDocument/declaration',
9
10
  typeDefinition: 'textDocument/typeDefinition',
10
11
  implementation: 'textDocument/implementation'
11
12
  };
13
+ const REQUESTS = {
14
+ definition: (service, uri, position) => service.definition(uri, position),
15
+ declaration: (service, uri, position) => service.declaration(uri, position),
16
+ typeDefinition: (service, uri, position) => service.typeDefinition(uri, position),
17
+ implementation: (service, uri, position) => service.implementation(uri, position)
18
+ };
12
19
  const SHORTCUT_KINDS = [
13
20
  ['definition', 'goToDefinition'],
14
21
  ['typeDefinition', 'goToTypeDefinition'],
@@ -81,18 +88,11 @@ export class NavigationFeature {
81
88
  }
82
89
  let result;
83
90
  try {
84
- const { service } = project;
85
- result = await (kind === 'definition'
86
- ? service.definition(uri, position)
87
- : kind === 'declaration'
88
- ? service.declaration(uri, position)
89
- : kind === 'typeDefinition'
90
- ? service.typeDefinition(uri, position)
91
- : service.implementation(uri, position));
91
+ result = await REQUESTS[kind](project.service, uri, position);
92
92
  }
93
93
  catch (error) {
94
94
  if (!(error instanceof StaleResultError)) {
95
- this.tell(this.say('failed', { message: error instanceof Error ? error.message : String(error) }));
95
+ this.tell(this.say('failed', { message: messageOf(error) }));
96
96
  }
97
97
  return;
98
98
  }
package/dist/paths.d.ts CHANGED
@@ -1 +1,2 @@
1
+ export declare function relativeTo(path: string, folder: string): string;
1
2
  export declare function basenameOf(path: string): string;
package/dist/paths.js CHANGED
@@ -1,3 +1,7 @@
1
+ /* A path inside `folder` relative to it; any other path as it is. */
2
+ export function relativeTo(path, folder) {
3
+ return path.startsWith(`${folder}/`) ? path.slice(folder.length + 1) : path;
4
+ }
1
5
  export function basenameOf(path) {
2
6
  return path.replaceAll('\\', '/').split('/').at(-1) ?? path;
3
7
  }
package/dist/peek.js CHANGED
@@ -2,6 +2,7 @@ import { StaleResultError } from '@adecore/lsp';
2
2
  import { uniquePlaces } from './navigation.js';
3
3
  import { PEEK_READ_FILES, definitionSnippetOf, peekFilesOf, snippetOf } from './peek-model.js';
4
4
  import { isShortcut } from './shortcut-keys.js';
5
+ import { messageOf } from './error-message.js';
5
6
  const TOAST_ID = 'language-peek';
6
7
  /* The block as the panel draws it: a hatched band, the header, eight lines of code and a hatched band, until the editor has measured it. */
7
8
  export const PEEK_HEIGHT = 241;
@@ -68,7 +69,7 @@ export class PeekFeature {
68
69
  }
69
70
  catch (error) {
70
71
  if (!(error instanceof StaleResultError)) {
71
- this.tell(this.say('failed', { message: error instanceof Error ? error.message : String(error) }));
72
+ this.tell(this.say('failed', { message: messageOf(error) }));
72
73
  }
73
74
  return;
74
75
  }
@@ -76,9 +77,15 @@ export class PeekFeature {
76
77
  return;
77
78
  }
78
79
  const { targets } = found;
79
- const unique = found.locations.filter((location, index) => found.locations.findIndex((other) => other.uri === location.uri &&
80
- other.range.start.line === location.range.start.line &&
81
- other.range.start.character === location.range.start.character) === index);
80
+ const seen = new Set();
81
+ const unique = found.locations.filter((location) => {
82
+ const key = placeKey(location);
83
+ if (seen.has(key)) {
84
+ return false;
85
+ }
86
+ seen.add(key);
87
+ return true;
88
+ });
82
89
  if (unique.length === 0) {
83
90
  this.tell(this.say(KINDS[kind].none));
84
91
  return;
@@ -3,15 +3,17 @@ import { fileUriToPath } from '@adecore/lsp';
3
3
  import { NavigationHistory } from './navigation-history.js';
4
4
  import { ProjectProblems } from './project-problems.js';
5
5
  import { applyWorkspaceEdit } from './workspace-edit.js';
6
+ import { relativeTo } from './paths.js';
7
+ /* Sends one editor's edits to the document, holding them until the document is open. */
6
8
  class DocumentSync {
7
9
  ready;
10
+ editor;
11
+ service;
12
+ uri;
13
+ stop;
8
14
  pending = [];
9
15
  opened = false;
10
16
  disposed = false;
11
- stop;
12
- service;
13
- uri;
14
- editor;
15
17
  constructor(service, uri, languageId, editor) {
16
18
  this.service = service;
17
19
  this.uri = uri;
@@ -26,6 +28,14 @@ class DocumentSync {
26
28
  // A failed open is also a document nothing can be asked of; the features' own requests say so.
27
29
  this.ready.catch(() => undefined);
28
30
  }
31
+ get text() {
32
+ return this.editor.getText();
33
+ }
34
+ dispose() {
35
+ this.disposed = true;
36
+ this.pending = [];
37
+ this.stop();
38
+ }
29
39
  changed(changes) {
30
40
  this.pending.push(...changes);
31
41
  if (this.opened) {
@@ -40,14 +50,6 @@ class DocumentSync {
40
50
  this.pending = [];
41
51
  void this.service.changeDocument(this.uri, changes).catch(() => undefined);
42
52
  }
43
- dispose() {
44
- this.disposed = true;
45
- this.pending = [];
46
- this.stop();
47
- }
48
- get text() {
49
- return this.editor.getText();
50
- }
51
53
  }
52
54
  /*
53
55
  * A shared language service and document ownership for the editors of a project. Two editors on the same
@@ -78,7 +80,7 @@ export class ProjectLanguage {
78
80
  return this.host.pathOfUri(uri);
79
81
  }
80
82
  const path = fileUriToPath(uri);
81
- return path !== null && this.folder !== '' && path.startsWith(`${this.folder}/`) ? path.slice(this.folder.length + 1) : path;
83
+ return path !== null && this.folder !== '' ? relativeTo(path, this.folder) : path;
82
84
  }
83
85
  /* Makes an edit of a language server: an open document takes it as one undo step and any other file gets an unsaved draft. */
84
86
  applyWorkspaceEdit(edit) {
@@ -1,5 +1,4 @@
1
- import type { Diagnostic } from '@adecore/lsp';
2
- import type { LanguageService } from '@adecore/lsp';
1
+ import type { Diagnostic, LanguageService } from '@adecore/lsp';
3
2
  import type { ProblemCounts } from './diagnostics.ts';
4
3
  export interface ProblemRow {
5
4
  readonly diagnostic: Diagnostic;
@@ -1,4 +1,4 @@
1
- import { comparePositions, severityOf } from './diagnostics-model.js';
1
+ import { comparePositions, problemCountsOf, severityOf } from './diagnostics-model.js';
2
2
  const SEVERITY_ORDER = { error: 0, warning: 1, info: 2, hint: 3 };
3
3
  /* Worst first, then in the order of the file. */
4
4
  export function compareRows(left, right) {
@@ -57,7 +57,5 @@ export class ProjectProblems {
57
57
  }
58
58
  }
59
59
  export function countsOf(files) {
60
- const rows = files.flatMap((file) => file.rows);
61
- const count = (severity) => rows.filter((row) => (row.diagnostic.severity ?? 1) === severity).length;
62
- return { error: count(1), warning: count(2), info: count(3) };
60
+ return problemCountsOf(files.flatMap((file) => file.rows.map((row) => row.diagnostic)));
63
61
  }
package/dist/refresher.js CHANGED
@@ -1,9 +1,8 @@
1
- import { StaleResultError } from '@adecore/lsp';
1
+ import { ErrorCodes, StaleResultError } from '@adecore/lsp';
2
2
  import { realTimers } from './timers.js';
3
3
  /*
4
- * Asks again for something a document's text decides, such as its colors or its hints: after a pause
5
- * in typing, when a server says what it offers changed, and never twice at once. A newer ask aborts the
6
- * one before it, and an answer for a text that has moved on is dropped, since another ask is on its way.
4
+ * Asks again for something a document's text decides, such as its colors or hints, never twice at once.
5
+ * A newer ask aborts the one before it, and an answer for a text that moved on is dropped.
7
6
  */
8
7
  export class Refresher {
9
8
  timer;
@@ -43,8 +42,14 @@ export class Refresher {
43
42
  this.controller?.abort();
44
43
  }
45
44
  }
46
- /* The failures of a server that is not up, does not offer the feature or was asked to stop, which are not worth a line in the console. */
45
+ const QUIET_CODES = new Set([
46
+ ErrorCodes.ServerNotInitialized,
47
+ ErrorCodes.MethodNotFound,
48
+ ErrorCodes.RequestCancelled,
49
+ ErrorCodes.ContentModified
50
+ ]);
51
+ /* A server that is not up, does not offer the feature or was asked to stop is not worth a line in the console. */
47
52
  function isQuiet(error) {
48
53
  const code = error?.code;
49
- return code === -32002 || code === -32601 || code === -32800 || code === -32801;
54
+ return code !== undefined && QUIET_CODES.has(code);
50
55
  }
package/dist/rename.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import type { EditorLanguage } from './editor-language.ts';
2
2
  export declare class RenameFeature {
3
- private tell;
4
3
  private readonly language;
5
4
  private session;
6
5
  constructor(language: EditorLanguage);
@@ -9,13 +8,14 @@ export declare class RenameFeature {
9
8
  submit(name: string, preview: boolean): Promise<void>;
10
9
  confirm(): Promise<void>;
11
10
  cancel(): void;
11
+ edited(): void;
12
12
  private apply;
13
13
  private fail;
14
- edited(): void;
15
14
  private finish;
16
15
  private light;
17
16
  private suggest;
18
17
  private linesOf;
19
18
  private publish;
19
+ private tell;
20
20
  private say;
21
21
  }