@adecore/editor-react 0.39.0 → 0.40.1

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 (154) hide show
  1. package/dist/CallHierarchyPanel.js +30 -55
  2. package/dist/CodeAuthorsCard.js +0 -1
  3. package/dist/CompletionPopup.js +150 -181
  4. package/dist/DiffView.d.ts +1 -1
  5. package/dist/DiffView.js +2 -29
  6. package/dist/PickPopup.js +54 -80
  7. package/dist/SplitDiffView.js +27 -57
  8. package/dist/SymbolPicker.js +2 -8
  9. package/dist/code-actions-model.js +2 -9
  10. package/dist/code-actions.js +10 -11
  11. package/dist/code-vision.js +2 -1
  12. package/dist/completion-model.js +2 -2
  13. package/dist/completion.js +13 -34
  14. package/dist/definition-link.d.ts +1 -0
  15. package/dist/definition-link.js +22 -18
  16. package/dist/diagnostics-model.d.ts +1 -0
  17. package/dist/diagnostics-model.js +3 -1
  18. package/dist/diagnostics.js +1 -2
  19. package/dist/diff-syntax.d.ts +1 -0
  20. package/dist/diff-syntax.js +31 -0
  21. package/dist/doc-comment-model.d.ts +1 -0
  22. package/dist/doc-comment-model.js +1 -1
  23. package/dist/folding.js +2 -3
  24. package/dist/format-on-type.js +6 -10
  25. package/dist/hover-content.d.ts +0 -2
  26. package/dist/hover-content.js +1 -9
  27. package/dist/inlay-hints.d.ts +0 -1
  28. package/dist/inlay-hints.js +11 -41
  29. package/dist/navigation.js +4 -14
  30. package/dist/peek.js +2 -12
  31. package/dist/popup-focus.d.ts +3 -1
  32. package/dist/popup-focus.js +33 -0
  33. package/dist/recent-choices.d.ts +0 -1
  34. package/dist/recent-choices.js +0 -4
  35. package/dist/rendered-docs.d.ts +1 -0
  36. package/dist/rendered-docs.js +15 -28
  37. package/dist/snippet-field-transforms.d.ts +5 -0
  38. package/dist/snippet-field-transforms.js +27 -16
  39. package/dist/snippet-session.js +8 -20
  40. package/dist/symbol-links.js +3 -10
  41. package/dist/symbol-picker-model.d.ts +1 -0
  42. package/dist/symbol-picker-model.js +7 -0
  43. package/dist/unique.d.ts +10 -0
  44. package/dist/unique.js +15 -0
  45. package/package.json +9 -9
  46. package/src/AnchoredPopup.tsx +2 -0
  47. package/src/BaselineStatus.tsx +1 -0
  48. package/src/CallHierarchyPanel.tsx +16 -6
  49. package/src/ChangeReview.tsx +7 -0
  50. package/src/CodeAuthorsCard.tsx +2 -5
  51. package/src/CompletionPopup.tsx +21 -15
  52. package/src/DiffView.tsx +53 -30
  53. package/src/EditorContextMenu.tsx +7 -0
  54. package/src/EditorView.tsx +28 -0
  55. package/src/FindReplace.tsx +8 -0
  56. package/src/HoverCard.tsx +8 -0
  57. package/src/HoverSections.tsx +11 -0
  58. package/src/LanguagePopups.tsx +1 -0
  59. package/src/PanelResizeHandle.tsx +8 -0
  60. package/src/PathText.tsx +1 -0
  61. package/src/PeekPanel.tsx +13 -0
  62. package/src/PickPopup.tsx +7 -6
  63. package/src/RenameCard.tsx +13 -0
  64. package/src/RenderedDocs.tsx +11 -0
  65. package/src/SplitDiffView.tsx +91 -56
  66. package/src/SymbolPicker.tsx +10 -18
  67. package/src/call-hierarchy-model.ts +10 -0
  68. package/src/call-hierarchy.ts +82 -0
  69. package/src/code-actions-model.ts +21 -9
  70. package/src/code-actions.ts +83 -7
  71. package/src/code-authors.ts +20 -0
  72. package/src/code-vision-model.ts +37 -0
  73. package/src/code-vision.ts +47 -3
  74. package/src/completion-call.ts +19 -0
  75. package/src/completion-model.ts +58 -2
  76. package/src/completion.ts +156 -30
  77. package/src/conflict-model.ts +4 -0
  78. package/src/context-menu-rows.ts +1 -0
  79. package/src/context-menu.ts +7 -0
  80. package/src/definition-link.ts +42 -18
  81. package/src/diagnostics-model.ts +19 -1
  82. package/src/diagnostics.ts +31 -2
  83. package/src/diff-expansion.ts +9 -0
  84. package/src/diff-overlays.tsx +8 -0
  85. package/src/diff-syntax.ts +54 -0
  86. package/src/diff-wordmarks.ts +8 -0
  87. package/src/doc-comment-model.ts +51 -1
  88. package/src/editor-language.ts +11 -0
  89. package/src/find-query.ts +7 -0
  90. package/src/folding.ts +10 -3
  91. package/src/format-on-type.ts +23 -10
  92. package/src/highlight-layers.ts +3 -0
  93. package/src/highlights.ts +18 -0
  94. package/src/history.ts +11 -0
  95. package/src/holder.ts +3 -0
  96. package/src/hover-content.ts +34 -17
  97. package/src/hover.ts +57 -0
  98. package/src/index.ts +4 -0
  99. package/src/inlay-hints.ts +86 -47
  100. package/src/key-priority.ts +2 -0
  101. package/src/line-edits.ts +8 -0
  102. package/src/models.ts +6 -0
  103. package/src/navigation-history.ts +5 -0
  104. package/src/navigation.ts +28 -13
  105. package/src/panel-size.ts +7 -0
  106. package/src/patch-decorations.ts +39 -0
  107. package/src/patch-layout.ts +47 -0
  108. package/src/peek-model.ts +21 -0
  109. package/src/peek.ts +49 -13
  110. package/src/pick.ts +35 -0
  111. package/src/popup-focus.ts +11 -1
  112. package/src/popup-placement.ts +5 -0
  113. package/src/popups.ts +2 -6
  114. package/src/project-language.ts +33 -0
  115. package/src/project-problems.ts +11 -0
  116. package/src/proposal-model.ts +40 -0
  117. package/src/recent-choices.ts +3 -5
  118. package/src/refresher.ts +3 -0
  119. package/src/rename-model.ts +28 -0
  120. package/src/rename.ts +56 -0
  121. package/src/rendered-docs.ts +89 -19
  122. package/src/review-group.ts +4 -0
  123. package/src/row-host.ts +12 -0
  124. package/src/selection-ranges.ts +6 -0
  125. package/src/semantic-delta.ts +7 -0
  126. package/src/semantic-model.ts +11 -0
  127. package/src/semantic-tokens.ts +37 -0
  128. package/src/shortcut-keys.ts +3 -0
  129. package/src/signature-model.ts +7 -0
  130. package/src/signature.ts +27 -0
  131. package/src/snippet-field-transforms.ts +48 -13
  132. package/src/snippet-indent.ts +10 -0
  133. package/src/snippet-range-order.ts +5 -0
  134. package/src/snippet-session.ts +173 -31
  135. package/src/snippet-transform-client.ts +14 -0
  136. package/src/snippet-transform-format.ts +98 -0
  137. package/src/snippet-transform-protocol.ts +5 -0
  138. package/src/snippet-transform-queue.ts +53 -0
  139. package/src/snippet-transform-worker.ts +14 -0
  140. package/src/snippet-variables.ts +9 -0
  141. package/src/snippet.ts +107 -0
  142. package/src/split-alignment.ts +10 -0
  143. package/src/split-scroll.ts +8 -0
  144. package/src/symbol-links.ts +9 -12
  145. package/src/symbol-picker-model.ts +25 -0
  146. package/src/symbol-picker.ts +9 -0
  147. package/src/symbols.ts +21 -0
  148. package/src/testing.ts +7 -0
  149. package/src/timers.ts +5 -0
  150. package/src/unique.ts +23 -0
  151. package/src/use-problem-counts.ts +4 -0
  152. package/src/view-state.ts +9 -0
  153. package/src/word-diff.ts +5 -0
  154. package/src/workspace-edit.ts +84 -0
@@ -62,6 +62,14 @@ export function letterOfKind(kind: number): string {
62
62
 
63
63
  export type SymbolTone = 'callable' | 'value' | 'type' | 'other';
64
64
 
65
+ /* The badge colors of a tone, shared by the symbol picker and the suggestions list. */
66
+ export const TONE_CLASSES: Record<SymbolTone, string> = {
67
+ callable: 'bg-accent-soft text-accent',
68
+ value: 'bg-status-running/15 text-status-running',
69
+ type: 'bg-status-idle/15 text-status-idle',
70
+ other: 'bg-surface-hover text-text-muted'
71
+ };
72
+
65
73
  const TONES: Record<SymbolGroup, SymbolTone> = {
66
74
  namespaces: 'other',
67
75
  classes: 'type',
@@ -100,18 +108,22 @@ function isHierarchical(symbols: readonly (DocumentSymbol | SymbolInformation)[]
100
108
  /* The symbols of a file, parents before their children, in the order of the file. */
101
109
  export function entriesOf(result: DocumentSymbolResult): SymbolEntry[] {
102
110
  const entries: SymbolEntry[] = [];
111
+
103
112
  const add = (name: string, kind: number, container: string, detail: string, at: EditorRange['start']): void => {
104
113
  entries.push({ id: String(entries.length), name, kind, container, detail, line: at.line, character: at.character });
105
114
  };
115
+
106
116
  const walk = (symbols: readonly DocumentSymbol[], container: string): void => {
107
117
  for (const symbol of symbols) {
108
118
  add(symbol.name, symbol.kind, container, symbol.detail ?? '', symbol.selectionRange.start);
109
119
  walk(symbol.children ?? [], container === '' ? symbol.name : `${container}.${symbol.name}`);
110
120
  }
111
121
  };
122
+
112
123
  if (result === null || result.length === 0) {
113
124
  return entries;
114
125
  }
126
+
115
127
  if (isHierarchical(result)) {
116
128
  walk(result, '');
117
129
  } else {
@@ -119,6 +131,7 @@ export function entriesOf(result: DocumentSymbolResult): SymbolEntry[] {
119
131
  add(symbol.name, symbol.kind, symbol.containerName ?? '', '', symbol.location.range.start);
120
132
  }
121
133
  }
134
+
122
135
  return entries;
123
136
  }
124
137
 
@@ -126,22 +139,29 @@ export function entriesOf(result: DocumentSymbolResult): SymbolEntry[] {
126
139
  export function scoreOf(name: string, query: string): number {
127
140
  const haystack = name.toLowerCase();
128
141
  const needle = query.toLowerCase();
142
+
129
143
  if (needle === '') {
130
144
  return 1;
131
145
  }
146
+
132
147
  if (haystack.startsWith(needle)) {
133
148
  return 3;
134
149
  }
150
+
135
151
  if (haystack.includes(needle)) {
136
152
  return 2;
137
153
  }
154
+
138
155
  let from = 0;
156
+
139
157
  for (const character of needle) {
140
158
  from = haystack.indexOf(character, from) + 1;
159
+
141
160
  if (from === 0) {
142
161
  return 0;
143
162
  }
144
163
  }
164
+
145
165
  return 1;
146
166
  }
147
167
 
@@ -149,6 +169,7 @@ export function scoreOf(name: string, query: string): number {
149
169
  export function filterEntries(entries: readonly SymbolEntry[], query: string): SymbolEntry[] {
150
170
  const trimmed = query.trim();
151
171
  const scored = entries.map((entry, index) => ({ entry, index, score: scoreOf(entry.name, trimmed) })).filter((candidate) => candidate.score > 0);
172
+
152
173
  return scored
153
174
  .sort((left, right) => (trimmed === '' ? left.index - right.index : right.score - left.score || left.index - right.index))
154
175
  .map((candidate) => candidate.entry);
@@ -163,6 +184,7 @@ export interface EntryGroup {
163
184
  export function groupEntries(entries: readonly SymbolEntry[]): EntryGroup[] {
164
185
  return SYMBOL_GROUPS.flatMap((group) => {
165
186
  const own = entries.filter((entry) => groupOfKind(entry.kind) === group);
187
+
166
188
  return own.length === 0 ? [] : [{ group, entries: own }];
167
189
  });
168
190
  }
@@ -177,10 +199,13 @@ export function modeOf(input: string): PickerMode {
177
199
  if (input.startsWith('#')) {
178
200
  return { mode: 'workspace', text: input.slice(1).trim() };
179
201
  }
202
+
180
203
  if (input.startsWith(':')) {
181
204
  const line = Number.parseInt(input.slice(1), 10);
205
+
182
206
  return { mode: 'line', line: Number.isFinite(line) && line > 0 ? line : null };
183
207
  }
208
+
184
209
  return { mode: 'symbol', text: input };
185
210
  }
186
211
 
@@ -18,13 +18,17 @@ export class SymbolPickerFeature {
18
18
 
19
19
  constructor(language: EditorLanguage) {
20
20
  this.language = language;
21
+
21
22
  const off = language.onKey((event) => {
22
23
  if (!isShortcut(this.language.shortcuts.goToSymbol, event)) {
23
24
  return false;
24
25
  }
26
+
25
27
  void this.open();
28
+
26
29
  return true;
27
30
  }, KEY_PRIORITY.symbolPicker);
31
+
28
32
  language.onDispose(() => {
29
33
  off();
30
34
  this.close();
@@ -33,10 +37,13 @@ export class SymbolPickerFeature {
33
37
 
34
38
  async open(): Promise<void> {
35
39
  const { project, uri, popups } = this.language;
40
+
36
41
  if (!project.service.supports('textDocument/documentSymbol', uri)) {
37
42
  this.language.project.host.notify?.({ id: TOAST_ID, kind: 'error', title: this.language.project.i18n.t('editor:language.symbols.unavailable') });
43
+
38
44
  return;
39
45
  }
46
+
40
47
  try {
41
48
  const entries = entriesOf(await project.service.documentSymbols(uri));
42
49
  popups.setState({ symbols: { file: basenameOf(this.language.uri), entries } });
@@ -73,9 +80,11 @@ export class SymbolPickerFeature {
73
80
  /* The symbols of the project that answer to a name, which is a request to the server and so waits for a pause in the typing. */
74
81
  async search(text: string, signal: AbortSignal): Promise<WorkspaceEntry[]> {
75
82
  const { project, uri } = this.language;
83
+
76
84
  if (text === '' || !project.service.supports('workspace/symbol', uri)) {
77
85
  return [];
78
86
  }
87
+
79
88
  return workspaceEntriesOf(await project.service.workspaceSymbols(uri, text, { signal }));
80
89
  }
81
90
 
package/src/symbols.ts CHANGED
@@ -41,13 +41,17 @@ function walk(symbols: readonly DocumentSymbol[], spans: SymbolSpan[]): void {
41
41
  /* Every symbol of an answer, a parent before its children, whether the server sent a tree or a flat list. */
42
42
  export function symbolSpansOf(result: DocumentSymbolResult): SymbolSpan[] {
43
43
  const spans: SymbolSpan[] = [];
44
+
44
45
  if (result === null || result.length === 0) {
45
46
  return spans;
46
47
  }
48
+
47
49
  if ('range' in result[0]!) {
48
50
  walk(result as DocumentSymbol[], spans);
51
+
49
52
  return spans;
50
53
  }
54
+
51
55
  return (result as SymbolInformation[]).map((symbol) => ({ name: symbol.name, kind: symbol.kind, range: symbol.location.range }));
52
56
  }
53
57
 
@@ -60,29 +64,37 @@ const PRELUDE = /^\s*(?:$|\/\/|\/\*|\*|#\[)/;
60
64
  */
61
65
  function headerLineOf({ name, range, nameLine }: SymbolSpan, lines: readonly string[] | undefined): number {
62
66
  const { start, end } = range;
67
+
63
68
  if (nameLine !== undefined && nameLine >= start.line && nameLine <= end.line) {
64
69
  return nameLine;
65
70
  }
71
+
66
72
  if (lines === undefined) {
67
73
  return start.line;
68
74
  }
75
+
69
76
  const last = Math.min(end.line, lines.length - 1);
70
77
  let first = start.line;
78
+
71
79
  while (first < last && PRELUDE.test(lines[first] ?? '')) {
72
80
  first++;
73
81
  }
82
+
74
83
  const short = name.slice(name.lastIndexOf('\\') + 1);
84
+
75
85
  for (let line = first; line <= last; line++) {
76
86
  if (!PRELUDE.test(lines[line] ?? '') && (lines[line] ?? '').includes(short)) {
77
87
  return line;
78
88
  }
79
89
  }
90
+
80
91
  return first;
81
92
  }
82
93
 
83
94
  function blockOf(span: SymbolSpan, lines: readonly string[] | undefined): EditorBlock | null {
84
95
  const { name, kind, range } = span;
85
96
  const label = KINDS[kind];
97
+
86
98
  // A one-line symbol has no body to pin, and a property or an enum member is part of the block around it.
87
99
  return label === undefined || range.end.line <= range.start.line
88
100
  ? null
@@ -96,6 +108,7 @@ function blockOf(span: SymbolSpan, lines: readonly string[] | undefined): Editor
96
108
  export function blocksOf(result: DocumentSymbolResult, text?: string): EditorBlock[] {
97
109
  const lines = text?.split('\n');
98
110
  const blocks = symbolSpansOf(result).flatMap((span) => blockOf(span, lines) ?? []);
111
+
99
112
  return blocks.sort((left, right) => left.startLine - right.startLine || right.endLine - left.endLine);
100
113
  }
101
114
 
@@ -109,14 +122,18 @@ export class SymbolsFeature {
109
122
 
110
123
  constructor(language: EditorLanguage, timers: Timers = realTimers) {
111
124
  const { editor, project, uri } = language;
125
+
112
126
  this.refresher = new Refresher(
113
127
  async (signal) => {
114
128
  if (!project.service.supports(METHOD, uri)) {
115
129
  return;
116
130
  }
131
+
117
132
  const result = await project.service.documentSymbols(uri, { signal });
133
+
118
134
  if (!signal.aborted) {
119
135
  editor.setBlocks(result === null ? null : blocksOf(result, editor.getText()));
136
+
120
137
  for (const listener of [...this.listeners]) {
121
138
  listener(result);
122
139
  }
@@ -125,10 +142,13 @@ export class SymbolsFeature {
125
142
  PAUSE_MS,
126
143
  timers
127
144
  );
145
+
128
146
  const edits = editor.onTextChange(() => this.refresher.later());
147
+
129
148
  const providers = language.onProvidersChanged(() => {
130
149
  this.refresher.now();
131
150
  });
151
+
132
152
  language.onDispose(() => {
133
153
  edits();
134
154
  providers();
@@ -140,6 +160,7 @@ export class SymbolsFeature {
140
160
  /* Called with every answer that was not replaced before it came. */
141
161
  onResult(listener: (result: DocumentSymbolResult) => void): () => void {
142
162
  this.listeners.add(listener);
163
+
143
164
  return () => {
144
165
  this.listeners.delete(listener);
145
166
  };
package/src/testing.ts CHANGED
@@ -72,9 +72,11 @@ export class FakeLanguageService implements LanguageService {
72
72
 
73
73
  async changeDocument(uri: string, changes: readonly ContentChange[]): Promise<void> {
74
74
  const held = this.documents.get(uri);
75
+
75
76
  if (held === undefined) {
76
77
  throw new LspError('Document is not open', ErrorCodes.ServerNotInitialized);
77
78
  }
79
+
78
80
  held.text = applyContentChanges(held.text, changes);
79
81
  held.version++;
80
82
  this.calls.push({ method: 'change', uri, params: changes, options: {} });
@@ -95,11 +97,13 @@ export class FakeLanguageService implements LanguageService {
95
97
 
96
98
  onProvidersChanged(listener: (uri: string) => void): Disposable {
97
99
  this.changes.add(listener);
100
+
98
101
  return { dispose: () => this.changes.delete(listener) };
99
102
  }
100
103
 
101
104
  onDiagnostics(listener: (report: DiagnosticsReport) => void): Disposable {
102
105
  this.diagnostics.add(listener);
106
+
103
107
  return { dispose: () => this.diagnostics.delete(listener) };
104
108
  }
105
109
 
@@ -119,15 +123,18 @@ export class FakeLanguageService implements LanguageService {
119
123
  if (!this.supports(method, uri)) {
120
124
  throw new LspError(`${method} is unavailable`, ErrorCodes.MethodNotFound);
121
125
  }
126
+
122
127
  options.signal?.throwIfAborted();
123
128
  const version = this.documents.get(uri)?.version;
124
129
  const call = { method, uri, params, options };
125
130
  this.calls.push(call);
126
131
  const result = await this.answers.get(method)?.(call);
127
132
  options.signal?.throwIfAborted();
133
+
128
134
  if (this.documents.get(uri)?.version !== version) {
129
135
  throw new StaleResultError(uri);
130
136
  }
137
+
131
138
  return result as Result;
132
139
  }
133
140
 
package/src/timers.ts CHANGED
@@ -18,6 +18,7 @@ export class ManualTimers implements Timers {
18
18
  set(callback: () => void, delay: number): unknown {
19
19
  const id = this.next++;
20
20
  this.pending.set(id, { at: this.now + delay, callback });
21
+
21
22
  return id;
22
23
  }
23
24
 
@@ -27,15 +28,19 @@ export class ManualTimers implements Timers {
27
28
 
28
29
  advance(milliseconds: number): void {
29
30
  const until = this.now + milliseconds;
31
+
30
32
  for (;;) {
31
33
  const due = [...this.pending].filter(([, entry]) => entry.at <= until).sort((left, right) => left[1].at - right[1].at)[0];
34
+
32
35
  if (due === undefined) {
33
36
  break;
34
37
  }
38
+
35
39
  this.now = due[1].at;
36
40
  this.pending.delete(due[0]);
37
41
  due[1].callback();
38
42
  }
43
+
39
44
  this.now = until;
40
45
  }
41
46
 
package/src/unique.ts ADDED
@@ -0,0 +1,23 @@
1
+ /* The items in order, without any whose key an earlier one had. */
2
+ export function uniqueBy<T>(items: readonly T[], keyOf: (item: T) => string): T[] {
3
+ const seen = new Set<string>();
4
+
5
+ return items.filter((item) => {
6
+ const key = keyOf(item);
7
+
8
+ if (seen.has(key)) {
9
+ return false;
10
+ }
11
+
12
+ seen.add(key);
13
+
14
+ return true;
15
+ });
16
+ }
17
+
18
+ export function placeKey(location: {
19
+ readonly uri: string;
20
+ readonly range: { readonly start: { readonly line: number; readonly character: number } };
21
+ }): string {
22
+ return `${location.uri}\0${location.range.start.line}\0${location.range.start.character}`;
23
+ }
@@ -6,12 +6,15 @@ const NONE: ProblemCounts = { error: 0, warning: 0, info: 0 };
6
6
 
7
7
  function countsStore(language: EditorLanguage | null): { get(): ProblemCounts; subscribe(listener: () => void): () => void } {
8
8
  let current = language?.diagnostics.counts() ?? NONE;
9
+
9
10
  return {
10
11
  get: () => {
11
12
  const next = language?.diagnostics.counts() ?? NONE;
13
+
12
14
  if (next.error !== current.error || next.warning !== current.warning || next.info !== current.info) {
13
15
  current = next;
14
16
  }
17
+
15
18
  return current;
16
19
  },
17
20
  subscribe: (listener) => language?.diagnostics.onChange(listener) ?? (() => undefined)
@@ -21,5 +24,6 @@ function countsStore(language: EditorLanguage | null): { get(): ProblemCounts; s
21
24
  /* How many errors, warnings and infos the file has, said again when the servers report. */
22
25
  export function useProblemCounts(language: EditorLanguage | null): ProblemCounts {
23
26
  const store = useMemo(() => countsStore(language), [language]);
27
+
24
28
  return useSyncExternalStore(store.subscribe, store.get);
25
29
  }
package/src/view-state.ts CHANGED
@@ -32,12 +32,15 @@ export function openingPlace(
32
32
  foldDefaults: readonly FoldRole[]
33
33
  ): { line?: number; column?: number; scrollTop?: number; folds?: EditorFolds; foldDefaults?: readonly FoldRole[] } {
34
34
  const folds = last === undefined ? { foldDefaults } : { folds: last.folds };
35
+
35
36
  if (reveal !== null) {
36
37
  return { line: reveal.line, ...folds };
37
38
  }
39
+
38
40
  if (last !== undefined) {
39
41
  return { line: last.line, column: last.column, scrollTop: last.scrollTop, ...folds };
40
42
  }
43
+
41
44
  return { scrollTop: placeholderScroll, ...folds };
42
45
  }
43
46
 
@@ -48,6 +51,7 @@ function movedKey(key: string, from: string, to: string): string | null {
48
51
  if (key === from) {
49
52
  return to;
50
53
  }
54
+
51
55
  return key.startsWith(`${from}/`) ? `${to}${key.slice(from.length)}` : null;
52
56
  }
53
57
 
@@ -59,13 +63,16 @@ function movedKey(key: string, from: string, to: string): string | null {
59
63
  export function followViewStates(endpointId: string, from: string, to: string): void {
60
64
  const oldKey = endpointKey(endpointId, from);
61
65
  const newKey = endpointKey(endpointId, to);
66
+
62
67
  for (const [key, state] of [...viewStates]) {
63
68
  const target = movedKey(key, oldKey, newKey);
69
+
64
70
  if (target !== null) {
65
71
  viewStates.delete(key);
66
72
  viewStates.set(target, state);
67
73
  }
68
74
  }
75
+
69
76
  moves.push({ from: oldKey, to: newKey });
70
77
  }
71
78
 
@@ -73,10 +80,12 @@ export function followViewStates(endpointId: string, from: string, to: string):
73
80
  export function viewStateKey(key: string): string {
74
81
  for (const { from, to } of moves) {
75
82
  const target = movedKey(key, from, to);
83
+
76
84
  if (target !== null) {
77
85
  return target;
78
86
  }
79
87
  }
88
+
80
89
  return key;
81
90
  }
82
91
 
package/src/word-diff.ts CHANGED
@@ -16,18 +16,23 @@ function replacedRanges(removed: string, added: string): Array<[number, number]>
16
16
  const after = added.match(TOKEN) ?? [];
17
17
  const starts: number[] = [];
18
18
  let offset = 0;
19
+
19
20
  for (const token of before) {
20
21
  starts.push(offset);
21
22
  offset += token.length;
22
23
  }
24
+
23
25
  starts.push(offset);
24
26
  const ranges: Array<[number, number]> = [];
27
+
25
28
  for (const change of diffLines(before, after)) {
26
29
  const text = removed.slice(starts[change.baseStart]!, starts[change.baseEnd]!);
30
+
27
31
  if (text.trim() !== '') {
28
32
  const start = starts[change.baseStart]! + (text.length - text.trimStart().length);
29
33
  ranges.push([start, start + text.trim().length]);
30
34
  }
31
35
  }
36
+
32
37
  return ranges;
33
38
  }