@adecore/editor-core 0.38.0 → 0.40.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.
Files changed (53) hide show
  1. package/dist/block-folds.js +5 -2
  2. package/dist/bracket-tracker.js +4 -3
  3. package/dist/brackets.d.ts +2 -12
  4. package/dist/brackets.js +3 -15
  5. package/dist/closer-tracker.js +0 -1
  6. package/dist/doc-comments.js +0 -1
  7. package/dist/document.d.ts +2 -0
  8. package/dist/document.js +33 -35
  9. package/dist/edit-regions.js +3 -4
  10. package/dist/enter.d.ts +2 -0
  11. package/dist/enter.js +11 -9
  12. package/dist/fold-roles.js +2 -2
  13. package/dist/lexical-folds.d.ts +0 -1
  14. package/dist/lexical-folds.js +1 -4
  15. package/dist/lexical.d.ts +11 -5
  16. package/dist/lexical.js +13 -17
  17. package/dist/line-commands.js +7 -15
  18. package/dist/line-text.d.ts +2 -0
  19. package/dist/line-text.js +4 -0
  20. package/dist/markup-regions.js +2 -3
  21. package/dist/paste.js +2 -7
  22. package/dist/structure.js +2 -1
  23. package/dist/typing-context.js +5 -15
  24. package/package.json +2 -2
  25. package/src/block-folds.ts +39 -2
  26. package/src/bracket-tracker.ts +55 -12
  27. package/src/brackets.ts +35 -42
  28. package/src/closer-tracker.ts +6 -1
  29. package/src/comments.ts +103 -0
  30. package/src/doc-comments.ts +19 -1
  31. package/src/document.ts +505 -36
  32. package/src/edit-regions.ts +26 -4
  33. package/src/enter.ts +104 -9
  34. package/src/fold-roles.ts +34 -2
  35. package/src/index.ts +2 -0
  36. package/src/languages.ts +11 -0
  37. package/src/lexical-folds.ts +21 -5
  38. package/src/lexical.ts +31 -23
  39. package/src/line-commands.ts +81 -17
  40. package/src/line-text.ts +7 -0
  41. package/src/markup-regions.ts +101 -3
  42. package/src/offsets.ts +10 -0
  43. package/src/paste.ts +20 -7
  44. package/src/php-markup.ts +23 -0
  45. package/src/preserve-case.ts +12 -0
  46. package/src/rope.ts +70 -0
  47. package/src/script-source.ts +10 -0
  48. package/src/search.ts +38 -0
  49. package/src/structure.ts +29 -1
  50. package/src/text-span.ts +53 -0
  51. package/src/typing-context.ts +65 -41
  52. package/src/typing-handlers.ts +28 -0
  53. package/src/words.ts +10 -0
package/src/structure.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { bracketRole, commentRole } from './fold-roles.ts';
2
2
  import { openers } from './lexical.ts';
3
+ import { whitespaceOf } from './line-text.ts';
3
4
  import type { DocumentLine } from './rope.ts';
4
5
 
5
6
  /* A bracket pair, string or block comment. `inner*` is the range without its delimiters. */
@@ -99,14 +100,17 @@ interface Header {
99
100
  /* Clamped to 1 through 16, with 4 for anything that is not a finite number. */
100
101
  export function tabWidth(options: { tabSize?: number }): number {
101
102
  const requested = options.tabSize ?? 4;
103
+
102
104
  return Number.isFinite(requested) ? Math.max(1, Math.min(16, Math.trunc(requested))) : 4;
103
105
  }
104
106
 
105
107
  export function indentationColumn(text: string, tabSize: number): number {
106
108
  let column = 0;
109
+
107
110
  for (const character of text) {
108
111
  column += character === '\t' ? tabSize - (column % tabSize) : 1;
109
112
  }
113
+
110
114
  return column;
111
115
  }
112
116
 
@@ -114,25 +118,31 @@ export function indentationColumn(text: string, tabSize: number): number {
114
118
  export function scanStructure(text: string): StructureRange[] {
115
119
  const result: StructureRange[] = [];
116
120
  const stack: { from: number; closer: string }[] = [];
121
+
117
122
  for (let offset = 0; offset < text.length; offset++) {
118
123
  const character = text[offset]!;
124
+
119
125
  if (character === '/' && text[offset + 1] === '/') {
120
126
  const end = text.indexOf('\n', offset + 2);
121
127
  offset = end === -1 ? text.length : end;
122
128
  } else if (character === '/' && text[offset + 1] === '*') {
123
129
  const end = text.indexOf('*/', offset + 2);
130
+
124
131
  if (end === -1) {
125
132
  break;
126
133
  }
134
+
127
135
  result.push({ from: offset, to: end + 2, innerFrom: offset + 2, innerTo: end, kind: 'comment' });
128
136
  offset = end + 1;
129
137
  } else if (character === '"' || character === "'" || character === '`') {
130
138
  const start = offset;
139
+
131
140
  for (offset++; offset < text.length; offset++) {
132
141
  if (text[offset] === '\\') {
133
142
  offset++;
134
143
  } else if (text[offset] === character) {
135
144
  result.push({ from: start, to: offset + 1, innerFrom: start + 1, innerTo: offset, kind: 'string' });
145
+
136
146
  break;
137
147
  } else if (character !== '`' && (text[offset] === '\n' || text[offset] === '\r')) {
138
148
  break;
@@ -145,6 +155,7 @@ export function scanStructure(text: string): StructureRange[] {
145
155
  result.push({ from: opener.from, to: offset + 1, innerFrom: opener.from + 1, innerTo: offset, kind: 'bracket' });
146
156
  }
147
157
  }
158
+
148
159
  return result.sort((left, right) => left.from - right.from || right.to - left.to);
149
160
  }
150
161
 
@@ -161,21 +172,28 @@ function indentationFolds(lineCount: number, getLine: (line: number) => Document
161
172
 
162
173
  for (let line = 0; line < lineCount; line++) {
163
174
  const text = getLine(line).text;
175
+
164
176
  if (!text.trim()) {
165
177
  continue;
166
178
  }
167
- const indent = indentationColumn(text.match(/^[\t ]*/)?.[0] ?? '', tabSize);
179
+
180
+ const indent = indentationColumn(whitespaceOf(text), tabSize);
181
+
168
182
  while (headers.length > 0 && indent <= headers.at(-1)!.indent) {
169
183
  close(headers.pop()!, previous!.line);
170
184
  }
185
+
171
186
  if (previous && indent > previous.indent) {
172
187
  headers.push(previous);
173
188
  }
189
+
174
190
  previous = { line, indent };
175
191
  }
192
+
176
193
  while (headers.length > 0) {
177
194
  close(headers.pop()!, previous!.line);
178
195
  }
196
+
179
197
  return folds;
180
198
  }
181
199
 
@@ -190,12 +208,15 @@ export function deriveFoldingRanges(
190
208
  ): FoldingRange[] {
191
209
  const minimum = Number.isFinite(options.minLines) ? Math.max(1, Math.trunc(options.minLines!)) : 1;
192
210
  const result: FoldingRange[] = [];
211
+
193
212
  for (const range of ranges) {
194
213
  if (range.kind === 'string' || (range.kind === 'bracket' ? options.brackets === false : options.comments === false)) {
195
214
  continue;
196
215
  }
216
+
197
217
  const startLine = lineAt(range.from);
198
218
  const endLine = lineAt(range.to - 1);
219
+
199
220
  if (endLine - startLine >= minimum) {
200
221
  const role =
201
222
  slice === undefined
@@ -203,29 +224,36 @@ export function deriveFoldingRanges(
203
224
  : range.kind === 'comment'
204
225
  ? commentRole(slice, range.from, range.to)
205
226
  : bracketRole(slice, range.from, options.language ?? '');
227
+
206
228
  result.push({ from: range.from, to: range.to, startLine, endLine, kind: range.kind, ...(role === undefined ? {} : { role }) });
207
229
  }
208
230
  }
231
+
209
232
  if (options.indentation) {
210
233
  // One push per fold: a long file has more of them than V8 takes as arguments in one call.
211
234
  for (const fold of indentationFolds(lineCount, getLine, tabWidth(options), minimum)) {
212
235
  result.push(fold);
213
236
  }
214
237
  }
238
+
215
239
  result.sort((left, right) => left.startLine - right.startLine || right.endLine - left.endLine);
240
+
216
241
  return dedupeFoldingRanges(result);
217
242
  }
218
243
 
219
244
  /* Of ranges that span the same lines, sorted outermost first, the first stays and takes the role of a later one when it has none. */
220
245
  export function dedupeFoldingRanges(sorted: readonly FoldingRange[]): FoldingRange[] {
221
246
  const unique: FoldingRange[] = [];
247
+
222
248
  for (const range of sorted) {
223
249
  const last = unique.at(-1);
250
+
224
251
  if (last === undefined || range.startLine !== last.startLine || range.endLine !== last.endLine) {
225
252
  unique.push(range);
226
253
  } else if (last.role === undefined && range.role !== undefined) {
227
254
  last.role = range.role;
228
255
  }
229
256
  }
257
+
230
258
  return unique;
231
259
  }
package/src/text-span.ts CHANGED
@@ -23,22 +23,29 @@ export function changedSpan(before: string, after: string): TextSpan | null {
23
23
  if (before === after) {
24
24
  return null;
25
25
  }
26
+
26
27
  const limit = Math.min(before.length, after.length);
27
28
  let start = 0;
29
+
28
30
  while (start < limit && before.charCodeAt(start) === after.charCodeAt(start)) {
29
31
  start += 1;
30
32
  }
33
+
31
34
  // Never between the halves of a surrogate pair, which would leave a lone half on either side.
32
35
  if (start > 0 && isHighSurrogate(before.charCodeAt(start - 1))) {
33
36
  start -= 1;
34
37
  }
38
+
35
39
  let tail = 0;
40
+
36
41
  while (tail < limit - start && before.charCodeAt(before.length - 1 - tail) === after.charCodeAt(after.length - 1 - tail)) {
37
42
  tail += 1;
38
43
  }
44
+
39
45
  if (tail > 0 && isLowSurrogate(before.charCodeAt(before.length - tail))) {
40
46
  tail -= 1;
41
47
  }
48
+
42
49
  return { start, end: before.length - tail, text: after.slice(start, after.length - tail) };
43
50
  }
44
51
 
@@ -59,13 +66,16 @@ interface LineHunk {
59
66
  function splitLines(text: string): string[] {
60
67
  const lines: string[] = [];
61
68
  let from = 0;
69
+
62
70
  for (let at = text.indexOf('\n'); at >= 0; at = text.indexOf('\n', from)) {
63
71
  lines.push(text.slice(from, at + 1));
64
72
  from = at + 1;
65
73
  }
74
+
66
75
  if (from < text.length) {
67
76
  lines.push(text.slice(from));
68
77
  }
78
+
69
79
  return lines;
70
80
  }
71
81
 
@@ -82,38 +92,50 @@ function diffLineHunks(before: Int32Array, after: Int32Array): LineHunk[] | null
82
92
  const trace: Int32Array[] = [];
83
93
  let work = 0;
84
94
  let found = -1;
95
+
85
96
  for (let edits = 0; edits <= maxEdits && found < 0; edits++) {
86
97
  trace.push(frontier.slice());
98
+
87
99
  for (let diagonal = -edits; diagonal <= edits; diagonal += 2) {
88
100
  let x: number;
101
+
89
102
  if (diagonal === -edits || (diagonal !== edits && frontier[base + diagonal - 1]! < frontier[base + diagonal + 1]!)) {
90
103
  x = frontier[base + diagonal + 1]!;
91
104
  } else {
92
105
  x = frontier[base + diagonal - 1]! + 1;
93
106
  }
107
+
94
108
  let y = x - diagonal;
109
+
95
110
  while (x < rows && y < columns && before[x] === after[y]) {
96
111
  x++;
97
112
  y++;
98
113
  work++;
99
114
  }
115
+
100
116
  work++;
101
117
  frontier[base + diagonal] = x;
118
+
102
119
  if (x >= rows && y >= columns) {
103
120
  found = edits;
121
+
104
122
  break;
105
123
  }
106
124
  }
125
+
107
126
  if (work > DIFF_WORK_LIMIT) {
108
127
  return null;
109
128
  }
110
129
  }
130
+
111
131
  if (found < 0) {
112
132
  return null;
113
133
  }
134
+
114
135
  const operations: { kind: 'delete' | 'insert'; x: number; y: number }[] = [];
115
136
  let x = rows;
116
137
  let y = columns;
138
+
117
139
  for (let edits = found; edits > 0; edits--) {
118
140
  const previous = trace[edits]!;
119
141
  const diagonal = x - y;
@@ -121,10 +143,12 @@ function diffLineHunks(before: Int32Array, after: Int32Array): LineHunk[] | null
121
143
  const previousDiagonal = fromAbove ? diagonal + 1 : diagonal - 1;
122
144
  const previousX = previous[base + previousDiagonal]!;
123
145
  const previousY = previousX - previousDiagonal;
146
+
124
147
  while (x > previousX && y > previousY) {
125
148
  x--;
126
149
  y--;
127
150
  }
151
+
128
152
  if (fromAbove) {
129
153
  y--;
130
154
  operations.push({ kind: 'insert', x, y });
@@ -133,9 +157,11 @@ function diffLineHunks(before: Int32Array, after: Int32Array): LineHunk[] | null
133
157
  operations.push({ kind: 'delete', x, y });
134
158
  }
135
159
  }
160
+
136
161
  operations.reverse();
137
162
  const hunks: LineHunk[] = [];
138
163
  let open: { beforeFrom: number; afterFrom: number; beforeTo: number; afterTo: number } | null = null;
164
+
139
165
  for (const operation of operations) {
140
166
  if (open !== null && open.beforeTo === operation.x && open.afterTo === operation.y) {
141
167
  if (operation.kind === 'delete') {
@@ -143,11 +169,14 @@ function diffLineHunks(before: Int32Array, after: Int32Array): LineHunk[] | null
143
169
  } else {
144
170
  open.afterTo++;
145
171
  }
172
+
146
173
  continue;
147
174
  }
175
+
148
176
  if (open !== null) {
149
177
  hunks.push(open);
150
178
  }
179
+
151
180
  open = {
152
181
  beforeFrom: operation.x,
153
182
  afterFrom: operation.y,
@@ -155,9 +184,11 @@ function diffLineHunks(before: Int32Array, after: Int32Array): LineHunk[] | null
155
184
  afterTo: operation.y + (operation.kind === 'insert' ? 1 : 0)
156
185
  };
157
186
  }
187
+
158
188
  if (open !== null) {
159
189
  hunks.push(open);
160
190
  }
191
+
161
192
  return hunks;
162
193
  }
163
194
 
@@ -169,62 +200,84 @@ function diffLineHunks(before: Int32Array, after: Int32Array): LineHunk[] | null
169
200
  */
170
201
  export function changedSpans(before: string, after: string): TextSpan[] {
171
202
  const outer = changedSpan(before, after);
203
+
172
204
  if (outer === null) {
173
205
  return [];
174
206
  }
207
+
175
208
  const afterEnd = after.length - (before.length - outer.end);
209
+
176
210
  if (outer.end - outer.start + (afterEnd - outer.start) > DIFF_TEXT_LIMIT) {
177
211
  return [outer];
178
212
  }
213
+
179
214
  // Whole lines on both sides of the change, so the diff compares lines and not their halves.
180
215
  const lineStart = outer.start === 0 ? 0 : before.lastIndexOf('\n', outer.start - 1) + 1;
216
+
181
217
  const lineEnd = (at: string, from: number): number => {
182
218
  const newline = at.indexOf('\n', from);
219
+
183
220
  return newline < 0 ? at.length : newline + 1;
184
221
  };
222
+
185
223
  const beforeEnd = lineEnd(before, outer.end > outer.start ? outer.end - 1 : outer.end);
186
224
  const afterLineEnd = beforeEnd - outer.end + afterEnd;
187
225
  const beforeLines = splitLines(before.slice(lineStart, beforeEnd));
188
226
  const afterLines = splitLines(after.slice(lineStart, afterLineEnd));
227
+
189
228
  if (beforeLines.length < 2 && afterLines.length < 2) {
190
229
  return [outer];
191
230
  }
231
+
192
232
  const ids = new Map<string, number>();
233
+
193
234
  const intern = (lines: string[]): Int32Array => {
194
235
  const result = new Int32Array(lines.length);
236
+
195
237
  for (let i = 0; i < lines.length; i++) {
196
238
  const line = lines[i]!;
197
239
  let id = ids.get(line);
240
+
198
241
  if (id === undefined) {
199
242
  id = ids.size;
200
243
  ids.set(line, id);
201
244
  }
245
+
202
246
  result[i] = id;
203
247
  }
248
+
204
249
  return result;
205
250
  };
251
+
206
252
  const hunks = diffLineHunks(intern(beforeLines), intern(afterLines));
253
+
207
254
  if (hunks === null || hunks.length === 0) {
208
255
  return [outer];
209
256
  }
257
+
210
258
  const beforeOffsets = lineOffsets(beforeLines, lineStart);
211
259
  const afterOffsets = lineOffsets(afterLines, lineStart);
212
260
  const spans: TextSpan[] = [];
261
+
213
262
  for (const hunk of hunks) {
214
263
  const beforeFrom = beforeOffsets[hunk.beforeFrom]!;
215
264
  const afterFrom = afterOffsets[hunk.afterFrom]!;
216
265
  const inner = changedSpan(before.slice(beforeFrom, beforeOffsets[hunk.beforeTo]), after.slice(afterFrom, afterOffsets[hunk.afterTo]));
266
+
217
267
  if (inner !== null) {
218
268
  spans.push({ start: beforeFrom + inner.start, end: beforeFrom + inner.end, text: inner.text });
219
269
  }
220
270
  }
271
+
221
272
  return spans;
222
273
  }
223
274
 
224
275
  function lineOffsets(lines: readonly string[], base: number): number[] {
225
276
  const offsets = [base];
277
+
226
278
  for (const line of lines) {
227
279
  offsets.push(offsets[offsets.length - 1]! + line.length);
228
280
  }
281
+
229
282
  return offsets;
230
283
  }