@adecore/editor-core 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 (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/fold-roles.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { isImportLine, rangeOf, textStart } from './lexical-folds.ts';
2
- import { isBlank } from './line-text.ts';
1
+ import { isImportLine, rangeOf } from './lexical-folds.ts';
2
+ import { isBlank, textStart } from './line-text.ts';
3
3
  import type { DocumentLine } from './rope.ts';
4
4
  import type { FoldHints, FoldRangeHint, FoldRole, FoldingRange } from './structure.ts';
5
5
 
@@ -23,26 +23,34 @@ export function commentRole(slice: (from: number, to: number) => string, from: n
23
23
  export function bracketRole(slice: (from: number, to: number) => string, from: number, language: string): FoldRole | undefined {
24
24
  const opener = slice(from, from + 1);
25
25
  const before = slice(Math.max(0, from - LOOK_BEHIND), from);
26
+
26
27
  if (language.toLowerCase() === 'php') {
27
28
  return opener === '[' && before.endsWith('#') ? 'attribute' : undefined;
28
29
  }
30
+
29
31
  if (!SCRIPT_LANGUAGES.test(language) || (opener !== '{' && opener !== '[')) {
30
32
  return undefined;
31
33
  }
34
+
32
35
  const head = before.slice(before.lastIndexOf('\n') + 1);
33
36
  const trimmed = before.trimEnd();
34
37
  const previous = trimmed.at(-1);
38
+
35
39
  if (previous === undefined) {
36
40
  return undefined;
37
41
  }
42
+
38
43
  const arrow = trimmed.endsWith('=>');
39
44
  const value = (/[=:(,[?]/.test(previous) && !arrow) || VALUE_WORDS.test(trimmed) || (arrow && opener === '[');
45
+
40
46
  if (!value) {
41
47
  return undefined;
42
48
  }
49
+
43
50
  if (opener === '[') {
44
51
  return 'array-literal';
45
52
  }
53
+
46
54
  // A `case x: {` and a `type A = {` are a block and a type, not a value.
47
55
  return /^\s*(?:case\b|default\s*:|(?:export\s+)?(?:declare\s+)?(?:type|interface)\b)/.test(head) ? undefined : 'object-literal';
48
56
  }
@@ -53,20 +61,26 @@ export function bracketRole(slice: (from: number, to: number) => string, from: n
53
61
  */
54
62
  export function markFileHeader(ranges: FoldingRange[], getLine: (line: number) => DocumentLine, lineCount: number, language: string): void {
55
63
  const candidate = ranges.find((range) => (range.kind === 'comment' || range.kind === 'line-comments') && range.startLine <= HEADER_LINES);
64
+
56
65
  if (candidate === undefined) {
57
66
  return;
58
67
  }
68
+
59
69
  for (let line = 0; line < candidate.startLine; line++) {
60
70
  const text = getLine(line).text;
71
+
61
72
  if (!isBlank(text) && !(line === 0 && /^(#!|<\?php\s*$)/.test(text))) {
62
73
  return;
63
74
  }
64
75
  }
76
+
65
77
  if (candidate.from > textStart(getLine(candidate.startLine))) {
66
78
  return;
67
79
  }
80
+
68
81
  const next = candidate.endLine + 1 < lineCount ? getLine(candidate.endLine + 1).text : '';
69
82
  const attached = candidate.role === 'doc-comment' && !isBlank(next) && !isImportLine(language, next.trimStart());
83
+
70
84
  if (!attached) {
71
85
  candidate.role = 'file-header';
72
86
  }
@@ -77,12 +91,15 @@ function serverRole(kind: string | undefined, text: string): FoldRole | undefine
77
91
  if (kind === 'imports') {
78
92
  return 'imports';
79
93
  }
94
+
80
95
  if (kind === 'region') {
81
96
  return 'region';
82
97
  }
98
+
83
99
  if (kind === 'comment') {
84
100
  return text.startsWith('/**') ? 'doc-comment' : undefined;
85
101
  }
102
+
86
103
  return text.startsWith('<') ? 'tag' : undefined;
87
104
  }
88
105
 
@@ -100,31 +117,40 @@ export function applyFoldHints(
100
117
  minimum: number
101
118
  ): FoldingRange[] {
102
119
  const result = ranges.map((range) => ({ ...range }));
120
+
103
121
  for (const symbol of hints.symbols ?? []) {
104
122
  const startLine = lineAt(symbol.from);
105
123
  const endLine = lineAt(Math.max(symbol.from, symbol.to - 1));
106
124
  const body = bodyOf(result, symbol, startLine, endLine, slice);
125
+
107
126
  if (body !== undefined) {
108
127
  body.range.role = body.role;
109
128
  }
110
129
  }
130
+
111
131
  const starts = new Set(result.map((range) => range.startLine));
132
+
112
133
  for (const hint of hints.ranges ?? []) {
113
134
  const startLine = lineAt(hint.from);
114
135
  const endLine = lineAt(hint.to);
115
136
  const same = result.find((range) => range.startLine === startLine && range.endLine === endLine);
137
+
116
138
  // The text's own fold of the same lines keeps its kind and role, and takes where the server hides from and shows again from.
117
139
  if (same !== undefined) {
118
140
  Object.assign(same, edgesOf(hint));
141
+
119
142
  continue;
120
143
  }
144
+
121
145
  if (endLine - startLine < minimum || starts.has(startLine)) {
122
146
  continue;
123
147
  }
148
+
124
149
  starts.add(startLine);
125
150
  const role = serverRole(hint.kind, getLine(startLine).text.trimStart());
126
151
  result.push({ ...rangeOf(startLine, endLine, getLine, 'server', role), ...edgesOf(hint) });
127
152
  }
153
+
128
154
  return result.sort((left, right) => left.startLine - right.startLine || right.endLine - left.endLine);
129
155
  }
130
156
 
@@ -144,20 +170,26 @@ function bodyOf(
144
170
  .filter((range) => range.kind === 'bracket' && range.from >= symbol.from && range.to <= symbol.to && slice(range.from, range.from + 1) === '{')
145
171
  .filter((range) => /^[\s;,]*$/.test(slice(range.to, symbol.to)))
146
172
  .sort((left, right) => right.to - left.to || left.from - right.from)[0];
173
+
147
174
  const range =
148
175
  brace ??
149
176
  ranges
150
177
  .filter((candidate) => candidate.kind === 'indentation' && candidate.endLine === endLine && candidate.startLine >= startLine)
151
178
  .sort((left, right) => left.startLine - right.startLine)[0];
179
+
152
180
  if (range === undefined) {
153
181
  return undefined;
154
182
  }
183
+
155
184
  if (symbol.body === 'class') {
156
185
  return { range, role: 'class-body' };
157
186
  }
187
+
158
188
  if (symbol.body === 'value') {
159
189
  const signature = slice(symbol.from, range.from);
190
+
160
191
  return /=>|\bfunction\b/.test(signature) ? { range, role: 'function-body' } : undefined;
161
192
  }
193
+
162
194
  return { range, role: symbol.body === 'method' ? 'method-body' : 'function-body' };
163
195
  }
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ export { findMatches, iterateMatches, findMatchFrom, replacementText, type FindM
5
5
  export { indentationColumn } from './structure.ts';
6
6
  export { findDocComments, MAX_DOC_COMMENTS, type DocComment } from './doc-comments.ts';
7
7
  export type { FoldHints, FoldRangeHint, FoldRole, FoldSymbolHint, FoldingOptions, FoldingRange } from './structure.ts';
8
+
8
9
  export type {
9
10
  ChangeSource,
10
11
  CommandOptions,
@@ -18,4 +19,5 @@ export type {
18
19
  Selection,
19
20
  TextEdit
20
21
  } from './types.ts';
22
+
21
23
  export { changedSpan, changedSpans, type TextSpan } from './text-span.ts';
package/src/languages.ts CHANGED
@@ -33,12 +33,15 @@ register(
33
33
  'typescript javascript typescriptreact javascriptreact tsx jsx ts js mjs cjs mts cts java c cpp c++ csharp cs go rust rs swift kotlin kt scala dart php scss less zig groq json jsonc json5 prisma astro svelte objective-c objc glsl hlsl proto protobuf hcl terraform',
34
34
  cLike
35
35
  );
36
+
36
37
  register('css', { line: null, block: slashStar });
37
38
  register('html xml svg xsl markdown md mdx vue', markup);
39
+
38
40
  register(
39
41
  'python py yaml yml shellscript shell sh bash zsh fish toml ruby rb perl r make makefile docker dockerfile dotenv ignore nix graphql powershell ps1 properties julia jl elixir ex exs coffeescript coffee cmake',
40
42
  hash
41
43
  );
44
+
42
45
  register('ini', { line: ';', block: null });
43
46
  register('sql', { line: '--', block: slashStar });
44
47
  register('lua', { line: '--', block: { open: '--[[', close: ']]' } });
@@ -48,18 +51,23 @@ register('plaintext text txt log', none);
48
51
  /* An id nothing knows has no comment syntax, which toggling a comment answers by doing nothing. */
49
52
  export function commentSyntax(language: string, region?: EditRegion | null): CommentSyntax {
50
53
  const id = language.toLowerCase();
54
+
51
55
  if (region === 'mixed') {
52
56
  return none;
53
57
  }
58
+
54
59
  if (region === 'jsx') {
55
60
  return { line: null, block: { open: '{/*', close: '*/}' } };
56
61
  }
62
+
57
63
  if (id === 'vue' || (id === 'php' && region !== null && region !== undefined)) {
58
64
  if (region === 'script') {
59
65
  return cLike;
60
66
  }
67
+
61
68
  return region === 'style' ? { line: null, block: slashStar } : markup;
62
69
  }
70
+
63
71
  return syntaxes.get(id) ?? none;
64
72
  }
65
73
 
@@ -71,14 +79,17 @@ export function isPlainText(language: string): boolean {
71
79
  /* The region of a `.vue` file a line is in, from the top-level tags above it. */
72
80
  export function vueRegionAt(lineText: (line: number) => string, line: number): VueRegion | null {
73
81
  let region: VueRegion | null = null;
82
+
74
83
  for (let index = 0; index <= line; index++) {
75
84
  const text = lineText(index);
76
85
  const open = /^<(template|script|style)\b/.exec(text);
86
+
77
87
  if (open) {
78
88
  region = open[1] as VueRegion;
79
89
  } else if (/^<\/(template|script|style)\s*>/.test(text) && index < line) {
80
90
  region = null;
81
91
  }
82
92
  }
93
+
83
94
  return region;
84
95
  }
@@ -1,4 +1,5 @@
1
1
  import { commentSyntax } from './languages.ts';
2
+ import { textStart } from './line-text.ts';
2
3
  import type { FoldRole, FoldingRange } from './structure.ts';
3
4
  import type { DocumentLine } from './rope.ts';
4
5
 
@@ -8,6 +9,7 @@ const IMPORT_WORDS = new Map<string, RegExp>();
8
9
  function register(languages: string, words: string): void {
9
10
  // The word must stand alone, and `import(` and `import.meta` are expressions and not an import.
10
11
  const pattern = new RegExp(`^(?:${words})\\b(?![(.=,;:])`);
12
+
11
13
  for (const language of languages.split(' ')) {
12
14
  IMPORT_WORDS.set(language, pattern);
13
15
  }
@@ -17,6 +19,7 @@ register(
17
19
  'typescript javascript typescriptreact javascriptreact tsx jsx ts js mjs cjs mts cts vue svelte astro java kotlin scala groovy go swift dart haskell elm',
18
20
  'import'
19
21
  );
22
+
20
23
  register('python py', 'import|from');
21
24
  register('php rust rs perl', 'use');
22
25
  register('csharp cs', 'using');
@@ -38,6 +41,7 @@ export interface LexicalFoldOptions {
38
41
 
39
42
  function bracketDelta(text: string): number {
40
43
  let depth = 0;
44
+
41
45
  for (const character of text) {
42
46
  if (character === '{' || character === '(' || character === '[') {
43
47
  depth++;
@@ -45,12 +49,8 @@ function bracketDelta(text: string): number {
45
49
  depth--;
46
50
  }
47
51
  }
48
- return depth;
49
- }
50
52
 
51
- /* The offset of the first character of a line that is not indentation. */
52
- export function textStart(line: DocumentLine): number {
53
- return line.start + (line.text.length - line.text.trimStart().length);
53
+ return depth;
54
54
  }
55
55
 
56
56
  /* A fold of whole lines, from the end of the first to the end of the last. */
@@ -85,50 +85,64 @@ export function lexicalFolds(lineCount: number, getLine: (line: number) => Docum
85
85
  let importEnd = -1;
86
86
  let commentStart = -1;
87
87
  let commentEnd = -1;
88
+
88
89
  const flushImports = (): void => {
89
90
  if (importStart !== -1 && importEnd > importStart) {
90
91
  result.push(rangeOf(importStart, importEnd, getLine, 'imports', 'imports'));
91
92
  }
93
+
92
94
  importStart = -1;
93
95
  };
96
+
94
97
  const flushComments = (): void => {
95
98
  if (commentStart !== -1 && commentEnd > commentStart) {
96
99
  result.push(rangeOf(commentStart, commentEnd, getLine, 'line-comments'));
97
100
  }
101
+
98
102
  commentStart = -1;
99
103
  };
104
+
100
105
  for (let line = 0; line < lineCount; line++) {
101
106
  const text = getLine(line).text;
102
107
  const trimmed = text.trimStart();
103
108
  const isRegionMarker = options.regions !== false && (REGION_START.test(text) || REGION_END.test(text));
109
+
104
110
  if (isRegionMarker) {
105
111
  flushComments();
112
+
106
113
  if (REGION_START.test(text)) {
107
114
  regions.push(line);
108
115
  } else {
109
116
  const start = regions.pop();
117
+
110
118
  if (start !== undefined && line > start) {
111
119
  result.push(rangeOf(start, line, getLine, 'region', 'region'));
112
120
  }
113
121
  }
122
+
114
123
  continue;
115
124
  }
125
+
116
126
  if (marker !== null && trimmed.startsWith(marker)) {
117
127
  commentStart = commentStart === -1 ? line : commentStart;
118
128
  commentEnd = line;
119
129
  } else {
120
130
  flushComments();
121
131
  }
132
+
122
133
  if (words === undefined) {
123
134
  continue;
124
135
  }
136
+
125
137
  if (words.test(trimmed)) {
126
138
  let last = line;
127
139
  let depth = bracketDelta(text);
140
+
128
141
  while (depth > 0 && last + 1 < lineCount) {
129
142
  last++;
130
143
  depth += bracketDelta(getLine(last).text);
131
144
  }
145
+
132
146
  importStart = importStart === -1 ? line : importStart;
133
147
  importEnd = last;
134
148
  line = last;
@@ -136,7 +150,9 @@ export function lexicalFolds(lineCount: number, getLine: (line: number) => Docum
136
150
  flushImports();
137
151
  }
138
152
  }
153
+
139
154
  flushComments();
140
155
  flushImports();
156
+
141
157
  return result;
142
158
  }
package/src/lexical.ts CHANGED
@@ -17,7 +17,7 @@ export function startsWord(text: string, at: number): boolean {
17
17
  */
18
18
  interface LanguageRules {
19
19
  slash: boolean;
20
- /* `any` is a comment wherever it stands, `boundary` only at the start of a word, as in shell and YAML. */
20
+ /* `any` is a comment wherever it stands, `boundary` only at the start of a word, which keeps `$#` and `${#list}` in shell code. */
21
21
  hash: false | 'any' | 'boundary';
22
22
  dash: boolean;
23
23
  block: boolean;
@@ -39,6 +39,7 @@ register(
39
39
  'typescript javascript typescriptreact javascriptreact tsx jsx ts js mjs cjs mts cts java c cpp c++ csharp cs go rust rs swift kotlin kt scala dart zig groq prisma astro svelte objective-c objc glsl hlsl proto protobuf scss less jsonc json5',
40
40
  { slash: true, block: true, braced: true }
41
41
  );
42
+
42
43
  register('php', { slash: true, hash: 'any', block: true, braced: true });
43
44
  register('vue', { slash: true, block: true, markup: true, braced: true });
44
45
  register('hcl terraform', { slash: true, hash: 'any', block: true, braced: true });
@@ -54,6 +55,33 @@ function rulesOf(language: string): LanguageRules {
54
55
  return languageRules.get(language.toLowerCase()) ?? NO_RULES;
55
56
  }
56
57
 
58
+ /* What the bracket and typing lexers read of a language, looked up once per scan. */
59
+ export interface LexerRules {
60
+ script: boolean;
61
+ php: boolean;
62
+ hash: boolean;
63
+ hashAtWordStart: boolean;
64
+ dash: boolean;
65
+ slash: boolean;
66
+ block: boolean;
67
+ markup: boolean;
68
+ }
69
+
70
+ export function lexerRules(language: string): LexerRules {
71
+ const rules = rulesOf(language);
72
+
73
+ return {
74
+ script: isScript(language),
75
+ php: isPhp(language),
76
+ hash: hasHashComments(language),
77
+ hashAtWordStart: rules.hash === 'boundary',
78
+ dash: rules.dash,
79
+ slash: rules.slash,
80
+ block: rules.block,
81
+ markup: rules.markup
82
+ };
83
+ }
84
+
57
85
  export function isScript(language: string): boolean {
58
86
  return /^(typescript|javascript|typescriptreact|javascriptreact|tsx|jsx|ts|js|vue)$/i.test(language);
59
87
  }
@@ -66,28 +94,6 @@ export function hasHashComments(language: string): boolean {
66
94
  return rulesOf(language).hash !== false;
67
95
  }
68
96
 
69
- /* Whether a `#` only starts a comment at the start of a word, which keeps `$#` and `${#list}` in shell code. */
70
- export function hashNeedsWordStart(language: string): boolean {
71
- return rulesOf(language).hash === 'boundary';
72
- }
73
-
74
- export function hasDashComments(language: string): boolean {
75
- return rulesOf(language).dash;
76
- }
77
-
78
- export function hasBlockComments(language: string): boolean {
79
- return rulesOf(language).block;
80
- }
81
-
82
- export function hasSlashComments(language: string): boolean {
83
- return rulesOf(language).slash;
84
- }
85
-
86
- export function hasMarkupComments(language: string): boolean {
87
- return rulesOf(language).markup;
88
- }
89
-
90
- /* Languages whose blocks are in braces: where a statement can be left open and a `case` label opens a block. */
91
97
  export function isBraced(language: string): boolean {
92
98
  return rulesOf(language).braced;
93
99
  }
@@ -103,9 +109,11 @@ export function hasSmartSemicolon(language: string): boolean {
103
109
 
104
110
  export function isWordCharacter(character: string): boolean {
105
111
  const code = character.charCodeAt(0);
112
+
106
113
  if (code < 128) {
107
114
  return (code >= 97 && code <= 122) || (code >= 65 && code <= 90) || (code >= 48 && code <= 57) || code === 95 || code === 36;
108
115
  }
116
+
109
117
  return /[\p{L}\p{N}_$]/u.test(character);
110
118
  }
111
119