@adecore/editor-react 0.42.0 → 0.43.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 (122) hide show
  1. package/dist/CallHierarchyPanel.js +177 -181
  2. package/dist/EditorContextMenu.js +118 -105
  3. package/dist/LanguagePopups.js +7 -0
  4. package/dist/PickPopup.js +190 -70
  5. package/dist/WrapCard.d.ts +8 -0
  6. package/dist/WrapCard.js +172 -0
  7. package/dist/call-hierarchy-model.d.ts +4 -2
  8. package/dist/call-hierarchy-model.js +20 -0
  9. package/dist/call-hierarchy.d.ts +3 -0
  10. package/dist/call-hierarchy.js +54 -22
  11. package/dist/clipboard-history.d.ts +31 -0
  12. package/dist/clipboard-history.js +70 -0
  13. package/dist/code-actions.d.ts +3 -1
  14. package/dist/code-actions.js +50 -10
  15. package/dist/code-vision.js +2 -3
  16. package/dist/completion.js +10 -15
  17. package/dist/context-menu-rows.d.ts +1 -1
  18. package/dist/context-menu-rows.js +4 -0
  19. package/dist/definition-link.js +15 -5
  20. package/dist/document-colors.d.ts +24 -0
  21. package/dist/document-colors.js +194 -0
  22. package/dist/document-links.d.ts +22 -0
  23. package/dist/document-links.js +133 -0
  24. package/dist/document-query.d.ts +15 -0
  25. package/dist/document-query.js +54 -0
  26. package/dist/edit-locations.d.ts +20 -0
  27. package/dist/edit-locations.js +136 -0
  28. package/dist/editor-language.d.ts +19 -1
  29. package/dist/editor-language.js +77 -2
  30. package/dist/emmet.d.ts +13 -0
  31. package/dist/emmet.js +62 -0
  32. package/dist/folding.js +2 -3
  33. package/dist/format-on-type.js +3 -5
  34. package/dist/highlights.js +2 -3
  35. package/dist/history.d.ts +7 -0
  36. package/dist/history.js +79 -1
  37. package/dist/host-types.d.ts +6 -0
  38. package/dist/index.d.ts +11 -2
  39. package/dist/index.js +8 -0
  40. package/dist/inlay-hints.js +2 -2
  41. package/dist/inline-completion.d.ts +39 -0
  42. package/dist/inline-completion.js +221 -0
  43. package/dist/key-priority.d.ts +8 -5
  44. package/dist/key-priority.js +9 -6
  45. package/dist/linked-editing.d.ts +26 -0
  46. package/dist/linked-editing.js +215 -0
  47. package/dist/locales/en.json +31 -2
  48. package/dist/locales/nl.json +31 -2
  49. package/dist/markup-context.d.ts +37 -0
  50. package/dist/markup-context.js +291 -0
  51. package/dist/markup.d.ts +36 -0
  52. package/dist/markup.js +453 -0
  53. package/dist/navigation.js +4 -6
  54. package/dist/paste-history.d.ts +7 -0
  55. package/dist/paste-history.js +104 -0
  56. package/dist/peek.js +3 -4
  57. package/dist/pick.d.ts +20 -2
  58. package/dist/pick.js +141 -18
  59. package/dist/popups.d.ts +9 -0
  60. package/dist/popups.js +2 -1
  61. package/dist/project-language.d.ts +18 -1
  62. package/dist/project-language.js +129 -18
  63. package/dist/rename.js +8 -11
  64. package/dist/rendered-docs.js +1 -2
  65. package/dist/selection-ranges.js +2 -3
  66. package/dist/shortcut-keys.d.ts +19 -0
  67. package/dist/signature.js +3 -6
  68. package/dist/snippet-session.d.ts +2 -2
  69. package/dist/snippet-session.js +3 -3
  70. package/dist/symbol-picker.js +4 -6
  71. package/dist/symbols.js +22 -3
  72. package/dist/testing.d.ts +23 -1
  73. package/dist/testing.js +114 -0
  74. package/package.json +7 -6
  75. package/src/CallHierarchyPanel.tsx +26 -11
  76. package/src/EditorContextMenu.tsx +7 -0
  77. package/src/LanguagePopups.tsx +4 -0
  78. package/src/PickPopup.tsx +40 -5
  79. package/src/WrapCard.tsx +64 -0
  80. package/src/call-hierarchy-model.ts +25 -3
  81. package/src/call-hierarchy.ts +62 -20
  82. package/src/clipboard-history.ts +102 -0
  83. package/src/code-actions.ts +48 -11
  84. package/src/code-vision.ts +3 -3
  85. package/src/completion.ts +15 -15
  86. package/src/context-menu-rows.ts +3 -2
  87. package/src/definition-link.ts +17 -5
  88. package/src/document-colors.ts +189 -0
  89. package/src/document-links.ts +129 -0
  90. package/src/document-query.ts +71 -0
  91. package/src/edit-locations.ts +162 -0
  92. package/src/editor-language.ts +80 -3
  93. package/src/emmet-module.d.ts +44 -0
  94. package/src/emmet.ts +73 -0
  95. package/src/folding.ts +3 -3
  96. package/src/format-on-type.ts +5 -5
  97. package/src/highlights.ts +3 -3
  98. package/src/history.ts +81 -1
  99. package/src/host-types.ts +10 -0
  100. package/src/index.ts +12 -3
  101. package/src/inlay-hints.ts +2 -2
  102. package/src/inline-completion.ts +221 -0
  103. package/src/key-priority.ts +9 -6
  104. package/src/linked-editing.ts +199 -0
  105. package/src/locales/en.json +31 -2
  106. package/src/locales/nl.json +31 -2
  107. package/src/markup-context.ts +262 -0
  108. package/src/markup.ts +367 -0
  109. package/src/navigation.ts +6 -6
  110. package/src/paste-history.ts +111 -0
  111. package/src/peek.ts +4 -4
  112. package/src/pick.ts +201 -19
  113. package/src/popups.ts +19 -1
  114. package/src/project-language.ts +136 -19
  115. package/src/rename.ts +11 -11
  116. package/src/rendered-docs.ts +2 -2
  117. package/src/selection-ranges.ts +3 -3
  118. package/src/signature.ts +6 -6
  119. package/src/snippet-session.ts +9 -4
  120. package/src/symbol-picker.ts +6 -6
  121. package/src/symbols.ts +30 -4
  122. package/src/testing.ts +126 -1
@@ -0,0 +1,194 @@
1
+ import { DocumentQuery } from './document-query.js';
2
+ import { realTimers } from './timers.js';
3
+ const METHOD = 'textDocument/documentColor';
4
+ const OWNER = 'document-colors';
5
+ const DELAY_MS = 500;
6
+ const ID_PREFIX = 'color-';
7
+ const OTHER_COLOR = 'other';
8
+ /* A color of LSP, each part from 0 to 1, as CSS draws it. */
9
+ export function cssColorOf(color) {
10
+ const part = value => String(Math.round(Math.min(1, Math.max(0, value)) * 1000) / 1000);
11
+ return `color(srgb ${part(color.red)} ${part(color.green)} ${part(color.blue)} / ${part(color.alpha)})`;
12
+ }
13
+ /* `#rrggbb` of an `<input type="color">` as a color of LSP, opaque. */
14
+ export function colorOfHex(value) {
15
+ const match = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(value);
16
+ if (match === null) {
17
+ return null;
18
+ }
19
+ return {
20
+ red: parseInt(match[1], 16) / 255,
21
+ green: parseInt(match[2], 16) / 255,
22
+ blue: parseInt(match[3], 16) / 255,
23
+ alpha: 1
24
+ };
25
+ }
26
+ function hexOf(color) {
27
+ const part = value => Math.round(Math.min(1, Math.max(0, value)) * 255).toString(16).padStart(2, '0');
28
+ return `#${part(color.red)}${part(color.green)}${part(color.blue)}`;
29
+ }
30
+ /* The edits a presentation makes: its own text edit, or its label over the color, and the edits that come with it. */
31
+ export function presentationEdits(presentation, information) {
32
+ return [presentation.textEdit ?? {
33
+ range: information.range,
34
+ newText: presentation.label
35
+ }, ...(presentation.additionalTextEdits ?? [])];
36
+ }
37
+ /*
38
+ * The colors a server finds in the text, such as `#ff0000` in a stylesheet, each with a swatch before it. A press on
39
+ * a swatch lists the ways the server writes that color, so `rgb()` can become `#hex`, and offers a color of the
40
+ * platform's picker, which keeps the way the color was written when the server offers it.
41
+ */
42
+ export class DocumentColorsFeature {
43
+ language;
44
+ colors = [];
45
+ query;
46
+ enabled = true;
47
+ constructor(language, timers = realTimers) {
48
+ this.language = language;
49
+ this.query = new DocumentQuery(language, timers, DELAY_MS, signal => this.enabled && this.supported ? language.service.documentColors(language.uri, {
50
+ signal
51
+ }) : Promise.resolve(null), colors => this.show(colors ?? []));
52
+ const {
53
+ editor
54
+ } = language;
55
+ if (editor.onInlayClick !== undefined) {
56
+ language.onDispose(editor.onInlayClick(click => this.pressed(click)));
57
+ }
58
+ language.onDispose(() => editor.setInlayHints([], OWNER));
59
+ }
60
+ get supported() {
61
+ const {
62
+ service
63
+ } = this.language;
64
+ return service.documentColors !== undefined && service.supports(METHOD, this.language.uri);
65
+ }
66
+ /* The colors of the current text, empty while an edit waits for the next answer. */
67
+ all() {
68
+ return this.colors;
69
+ }
70
+ /* Off, there are no swatches; on unless false. */
71
+ configure(settings) {
72
+ this.enabled = settings.enabled !== false;
73
+ if (this.enabled) {
74
+ this.query.run();
75
+ } else {
76
+ this.show([]);
77
+ }
78
+ }
79
+ /* The ways the server writes a color found in the text, for a menu of the host. */
80
+ async presentations(information, color = information.color) {
81
+ const {
82
+ service
83
+ } = this.language;
84
+ if (service.colorPresentations === undefined) {
85
+ return [];
86
+ }
87
+ try {
88
+ return (await service.colorPresentations(this.language.uri, color, information.range)) ?? [];
89
+ } catch {
90
+ return [];
91
+ }
92
+ }
93
+ /* Writes a presentation in place of the color it was asked for; false when the text moved on since. */
94
+ apply(presentation, information) {
95
+ if (!this.colors.includes(information)) {
96
+ return false;
97
+ }
98
+ const changes = presentationEdits(presentation, information).map(edit => ({
99
+ range: edit.range,
100
+ text: edit.newText
101
+ }));
102
+ return this.language.editor.applyEdits(changes);
103
+ }
104
+ show(colors) {
105
+ this.colors = colors;
106
+ const hints = colors.map((information, index) => ({
107
+ id: `${ID_PREFIX}${index}`,
108
+ position: information.range.start,
109
+ label: this.language.project.i18n.t('editor:language.colors.swatch', {
110
+ color: hexOf(information.color)
111
+ }),
112
+ swatch: cssColorOf(information.color),
113
+ paddingRight: true
114
+ }));
115
+ this.language.editor.setInlayHints(hints, OWNER);
116
+ }
117
+ pressed(click) {
118
+ if (!click.id.startsWith(ID_PREFIX)) {
119
+ return false;
120
+ }
121
+ const information = this.colors[Number(click.id.slice(ID_PREFIX.length))];
122
+ if (information === undefined) {
123
+ return false;
124
+ }
125
+ void this.open(information);
126
+ return true;
127
+ }
128
+ async open(information) {
129
+ const presentations = await this.presentations(information);
130
+ if (!this.colors.includes(information)) {
131
+ return;
132
+ }
133
+ const {
134
+ pick,
135
+ project
136
+ } = this.language;
137
+ const rows = presentations.map((presentation, index) => ({
138
+ id: String(index),
139
+ label: presentation.label,
140
+ detail: ''
141
+ }));
142
+ pick.open({
143
+ anchor: information.range.start,
144
+ title: project.i18n.t('editor:language.colors.title'),
145
+ groups: [{
146
+ title: null,
147
+ rows
148
+ }, {
149
+ title: null,
150
+ rows: [{
151
+ id: OTHER_COLOR,
152
+ label: project.i18n.t('editor:language.colors.other'),
153
+ detail: ''
154
+ }]
155
+ }],
156
+ accept: id => {
157
+ if (id === OTHER_COLOR) {
158
+ this.choose(information, presentations);
159
+ } else {
160
+ this.apply(presentations[Number(id)], information);
161
+ }
162
+ }
163
+ });
164
+ }
165
+ /* The platform's color picker; the new color is written the way the old one was when the server offers that. */
166
+ choose(information, before) {
167
+ if (typeof document === 'undefined') {
168
+ return;
169
+ }
170
+ const written = this.language.editor.textInRange(information.range);
171
+ const form = Math.max(0, before.findIndex(presentation => presentation.label === written));
172
+ const input = document.createElement('input');
173
+ input.type = 'color';
174
+ input.value = hexOf(information.color);
175
+ input.addEventListener('change', () => {
176
+ const color = colorOfHex(input.value);
177
+ if (color === null) {
178
+ return;
179
+ }
180
+ void this.presentations(information, {
181
+ ...color,
182
+ alpha: information.color.alpha
183
+ }).then(presentations => {
184
+ const presentation = presentations[form] ?? presentations[0];
185
+ if (presentation !== undefined) {
186
+ this.apply(presentation, information);
187
+ }
188
+ });
189
+ }, {
190
+ once: true
191
+ });
192
+ input.click();
193
+ }
194
+ }
@@ -0,0 +1,22 @@
1
+ import type { EditorPosition, EditorRange } from '@adecore/editor';
2
+ import type { DocumentLink } from '@adecore/lsp';
3
+ import type { EditorLanguage } from './editor-language.ts';
4
+ import { type Timers } from './timers.ts';
5
+ export declare function linkPosition(target: string): {
6
+ readonly uri: string;
7
+ readonly position: EditorPosition;
8
+ };
9
+ export declare class DocumentLinksFeature {
10
+ private readonly language;
11
+ private links;
12
+ private readonly query;
13
+ constructor(language: EditorLanguage, timers?: Timers);
14
+ get supported(): boolean;
15
+ all(): readonly DocumentLink[];
16
+ linkAt(position: EditorPosition): DocumentLink | null;
17
+ rangeAt(position: EditorPosition): EditorRange | null;
18
+ refresh(): void;
19
+ follow(link: DocumentLink): Promise<boolean>;
20
+ private resolve;
21
+ }
22
+ export declare function openTarget(language: EditorLanguage, target: string, selection?: EditorRange): void;
@@ -0,0 +1,133 @@
1
+ import { comparePositions } from './diagnostics-model.js';
2
+ import { DocumentQuery } from './document-query.js';
3
+ import { realTimers } from './timers.js';
4
+ const METHOD = 'textDocument/documentLink';
5
+ const RESOLVE = 'documentLink/resolve';
6
+ /* How long after the last edit the links are asked again. */
7
+ const DELAY_MS = 500;
8
+ /* `#L12`, `#12` or `#L12,4`: a line, from one, and a column, from one, the form editors write into a link. */
9
+ const LINE_FRAGMENT = /^#L?(\d+)(?:[,:C](\d+))?$/i;
10
+ /* Where a link to a file of the editor lands, from the line its fragment names; the start of the file without one. */
11
+ export function linkPosition(target) {
12
+ const hash = target.indexOf('#');
13
+ const fragment = hash === -1 ? null : LINE_FRAGMENT.exec(target.slice(hash));
14
+ if (fragment === null) {
15
+ return {
16
+ uri: target,
17
+ position: {
18
+ line: 0,
19
+ character: 0
20
+ }
21
+ };
22
+ }
23
+ return {
24
+ uri: target.slice(0, hash),
25
+ position: {
26
+ line: Math.max(0, Number(fragment[1]) - 1),
27
+ character: Math.max(0, Number(fragment[2] ?? 1) - 1)
28
+ }
29
+ };
30
+ }
31
+ /* Whether a target opens in the editor: a file, or a document of a scheme a server serves. A web address opens outside. */
32
+ function opensInEditor(target) {
33
+ return !/^(https?|mailto|ftp):/i.test(target);
34
+ }
35
+ /*
36
+ * The links a server finds in the text, such as an import path or a web address in a comment. Mod+click on one
37
+ * follows it, and while Mod is held the link under the pointer is drawn as one (`DefinitionLinkFeature` draws it).
38
+ * A link without a target asks the server for it first.
39
+ */
40
+ export class DocumentLinksFeature {
41
+ language;
42
+ links = [];
43
+ query;
44
+ constructor(language, timers = realTimers) {
45
+ this.language = language;
46
+ this.query = new DocumentQuery(language, timers, DELAY_MS, signal => this.supported ? language.service.documentLinks(language.uri, {
47
+ signal
48
+ }) : Promise.resolve(null), links => {
49
+ this.links = links ?? [];
50
+ });
51
+ const off = language.editor.onClick(click => {
52
+ if (!click.mod || click.alt || click.shift) {
53
+ return false;
54
+ }
55
+ const link = this.linkAt(click.position);
56
+ if (link === null) {
57
+ return false;
58
+ }
59
+ void this.follow(link);
60
+ return true;
61
+ });
62
+ language.onDispose(off);
63
+ }
64
+ get supported() {
65
+ const {
66
+ service
67
+ } = this.language;
68
+ return service.documentLinks !== undefined && service.supports(METHOD, this.language.uri);
69
+ }
70
+ /* The links of the current text, empty while an edit waits for the next answer. */
71
+ all() {
72
+ return this.links;
73
+ }
74
+ /* The link whose range holds `position`, its end left out. */
75
+ linkAt(position) {
76
+ return this.links.find(link => comparePositions(link.range.start, position) <= 0 && comparePositions(position, link.range.end) < 0) ?? null;
77
+ }
78
+ /* The range of the link at `position`, which the pointer draws as a link while Mod is held. */
79
+ rangeAt(position) {
80
+ return this.linkAt(position)?.range ?? null;
81
+ }
82
+ refresh() {
83
+ this.query.run();
84
+ }
85
+ /* Opens what a link points at; false when it has no target, even after asking. */
86
+ async follow(link) {
87
+ const target = link.target ?? (await this.resolve(link))?.target;
88
+ if (target === undefined) {
89
+ return false;
90
+ }
91
+ openTarget(this.language, target);
92
+ return true;
93
+ }
94
+ async resolve(link) {
95
+ const {
96
+ service
97
+ } = this.language;
98
+ if (service.resolveDocumentLink === undefined || service.providerOptions(RESOLVE, this.language.uri)?.resolveProvider !== true) {
99
+ return null;
100
+ }
101
+ try {
102
+ return await service.resolveDocumentLink(this.language.uri, link);
103
+ } catch {
104
+ return null;
105
+ }
106
+ }
107
+ }
108
+ /* Opens a file or a document of a server in the editor, and a web address through the host. */
109
+ export function openTarget(language, target, selection) {
110
+ if (!opensInEditor(target)) {
111
+ const {
112
+ host
113
+ } = language.project;
114
+ if (host.openExternal !== undefined) {
115
+ host.openExternal(target);
116
+ } else if (typeof window !== 'undefined') {
117
+ window.open(target, '_blank', 'noopener,noreferrer');
118
+ }
119
+ return;
120
+ }
121
+ const {
122
+ uri,
123
+ position
124
+ } = linkPosition(target);
125
+ const range = selection ?? {
126
+ start: position,
127
+ end: position
128
+ };
129
+ language.goTo({
130
+ uri,
131
+ range
132
+ });
133
+ }
@@ -0,0 +1,15 @@
1
+ import type { EditorLanguage } from './editor-language.ts';
2
+ import type { Timers } from './timers.ts';
3
+ export declare class DocumentQuery<Value> {
4
+ private readonly language;
5
+ private readonly timers;
6
+ private readonly delay;
7
+ private readonly ask;
8
+ private readonly apply;
9
+ private timer;
10
+ private controller;
11
+ private generation;
12
+ constructor(language: EditorLanguage, timers: Timers, delay: number, ask: (signal: AbortSignal) => Promise<Value | null>, apply: (value: Value | null) => void);
13
+ run(): void;
14
+ private cancel;
15
+ }
@@ -0,0 +1,54 @@
1
+ /*
2
+ * Asks the servers for something of the whole document: once it is open, a moment after the last edit, and
3
+ * whenever what the servers offer changes. An edit clears the answer at once, since its ranges point into a
4
+ * text that is gone; an answer for an older text is dropped.
5
+ */
6
+ export class DocumentQuery {
7
+ language;
8
+ timers;
9
+ delay;
10
+ ask;
11
+ apply;
12
+ timer = null;
13
+ controller = null;
14
+ generation = 0;
15
+ constructor(language, timers, delay, ask, apply) {
16
+ this.language = language;
17
+ this.timers = timers;
18
+ this.delay = delay;
19
+ this.ask = ask;
20
+ this.apply = apply;
21
+ const offs = [language.onProvidersChanged(() => this.run()), language.editor.onTextChange(() => {
22
+ this.cancel();
23
+ this.apply(null);
24
+ this.timer = this.timers.set(() => this.run(), this.delay);
25
+ })];
26
+ language.onDispose(() => {
27
+ this.cancel();
28
+ for (const off of offs) {
29
+ off();
30
+ }
31
+ });
32
+ }
33
+ /* Asks again now, as a refresh of the host does. */
34
+ run() {
35
+ this.cancel();
36
+ const generation = this.generation;
37
+ const controller = new AbortController();
38
+ this.controller = controller;
39
+ this.ask(controller.signal).then(value => {
40
+ if (generation === this.generation && !this.language.isDisposed) {
41
+ this.apply(value);
42
+ }
43
+ },
44
+ // A server that fails or refuses answers nothing; the next edit asks again.
45
+ () => undefined);
46
+ }
47
+ cancel() {
48
+ this.generation++;
49
+ this.timers.clear(this.timer);
50
+ this.timer = null;
51
+ this.controller?.abort();
52
+ this.controller = null;
53
+ }
54
+ }
@@ -0,0 +1,20 @@
1
+ import type { EditorContentChange, EditorPosition, EditorRange } from '@adecore/editor';
2
+ export interface EditPlace {
3
+ readonly uri: string;
4
+ readonly selections: readonly EditorRange[];
5
+ }
6
+ export declare class EditLocations {
7
+ private readonly limit;
8
+ private entries;
9
+ private index;
10
+ private arriving;
11
+ constructor(limit?: number);
12
+ get places(): readonly EditPlace[];
13
+ record(place: EditPlace): void;
14
+ moved(uri: string, position: EditorPosition): void;
15
+ previous(current: EditPlace | null): EditPlace | null;
16
+ next(current: EditPlace | null): EditPlace | null;
17
+ map(uri: string, changes: readonly EditorContentChange[]): void;
18
+ forget(matches: (uri: string) => boolean): void;
19
+ private arrive;
20
+ }
@@ -0,0 +1,136 @@
1
+ const LIMIT = 150;
2
+ /* Two places are one when they are in one file with as many carets, and every caret is within three lines of its twin. */
3
+ function isSame(left, right) {
4
+ return right !== null && left.uri === right.uri && left.selections.length === right.selections.length && left.selections.every((selection, index) => Math.abs(selection.end.line - right.selections[index].end.line) < 4);
5
+ }
6
+ function before(left, right) {
7
+ return left.line < right.line || left.line === right.line && left.character < right.character;
8
+ }
9
+ /* Where the text of a change ends, in the text after the change. */
10
+ function endOf(change) {
11
+ const lines = change.text.split('\n');
12
+ const {
13
+ start
14
+ } = change.range;
15
+ return lines.length === 1 ? {
16
+ line: start.line,
17
+ character: start.character + change.text.length
18
+ } : {
19
+ line: start.line + lines.length - 1,
20
+ character: lines[lines.length - 1].length
21
+ };
22
+ }
23
+ /* A position after a change: untouched in front of it, at the start when the change replaced the text around it, moved along behind it. */
24
+ function follow(position, change) {
25
+ const {
26
+ start,
27
+ end
28
+ } = change.range;
29
+ if (!before(start, position)) {
30
+ return position;
31
+ }
32
+ if (before(position, end)) {
33
+ return start;
34
+ }
35
+ const landed = endOf(change);
36
+ return position.line === end.line ? {
37
+ line: landed.line,
38
+ character: landed.character + position.character - end.character
39
+ } : {
40
+ line: position.line + landed.line - end.line,
41
+ character: position.character
42
+ };
43
+ }
44
+ /*
45
+ * Where the project was edited, for Last Edit Location and Next Edit Location. An edit within three lines of the
46
+ * last one is the same place, so typing a paragraph is one stop. A caret that moves without an edit puts the walk
47
+ * back at the end, and so does an edit.
48
+ */
49
+ export class EditLocations {
50
+ limit;
51
+ entries = [];
52
+ index = 0;
53
+ arriving = null;
54
+ constructor(limit = LIMIT) {
55
+ this.limit = limit;
56
+ }
57
+ get places() {
58
+ return this.entries;
59
+ }
60
+ /* An edit left the carets here. */
61
+ record(place) {
62
+ const last = this.entries.at(-1);
63
+ if (last !== undefined && isSame(last, place)) {
64
+ this.entries[this.entries.length - 1] = place;
65
+ } else {
66
+ this.entries.push(place);
67
+ if (this.entries.length > this.limit) {
68
+ this.entries.shift();
69
+ }
70
+ }
71
+ this.index = this.entries.length;
72
+ this.arriving = null;
73
+ }
74
+ /* The caret moved without an edit. The move that lands on the place a walk went to does not count. */
75
+ moved(uri, position) {
76
+ if (this.arriving?.uri === uri && this.arriving.line === position.line) {
77
+ return;
78
+ }
79
+ this.arriving = null;
80
+ this.index = this.entries.length;
81
+ }
82
+ /* The place of an earlier edit that is not the one the carets are on; null when there is none. */
83
+ previous(current) {
84
+ for (let i = this.index - 1; i >= 0; i--) {
85
+ if (!isSame(this.entries[i], current)) {
86
+ this.index = i;
87
+ return this.arrive(this.entries[i]);
88
+ }
89
+ }
90
+ return null;
91
+ }
92
+ /* The place of a later edit; it moves one past the place it returns, so Last Edit Location and Next Edit Location are not exact opposites. */
93
+ next(current) {
94
+ for (let i = this.index; i < this.entries.length; i++) {
95
+ if (!isSame(this.entries[i], current)) {
96
+ this.index = i + 1;
97
+ return this.arrive(this.entries[i]);
98
+ }
99
+ }
100
+ return null;
101
+ }
102
+ /* The places of a file follow its edits, in the order the editor made them. */
103
+ map(uri, changes) {
104
+ if (!this.entries.some(entry => entry.uri === uri)) {
105
+ return;
106
+ }
107
+ this.entries = this.entries.map(entry => {
108
+ if (entry.uri !== uri) {
109
+ return entry;
110
+ }
111
+ const selections = changes.reduce((current, change) => current.map(selection => ({
112
+ start: follow(selection.start, change),
113
+ end: follow(selection.end, change)
114
+ })), entry.selections);
115
+ return {
116
+ uri,
117
+ selections
118
+ };
119
+ });
120
+ }
121
+ /* Drops the places of files that are gone. */
122
+ forget(matches) {
123
+ this.entries = this.entries.filter(entry => !matches(entry.uri));
124
+ this.index = this.entries.length;
125
+ if (this.arriving !== null && matches(this.arriving.uri)) {
126
+ this.arriving = null;
127
+ }
128
+ }
129
+ arrive(place) {
130
+ this.arriving = {
131
+ uri: place.uri,
132
+ line: place.selections.at(-1).end.line
133
+ };
134
+ return place;
135
+ }
136
+ }
@@ -1,6 +1,10 @@
1
1
  import { type Keymap } from '@adecore/editor/keymap';
2
2
  import type { Editor, EditorKeyHandler, EditorPosition } from '@adecore/editor';
3
- import type { Location } from '@adecore/lsp';
3
+ import type { LanguageService, Location, TextDocumentSaveReason } from '@adecore/lsp';
4
+ import { DocumentColorsFeature } from './document-colors.ts';
5
+ import { DocumentLinksFeature } from './document-links.ts';
6
+ import { InlineCompletionFeature } from './inline-completion.ts';
7
+ import { LinkedEditingFeature } from './linked-editing.ts';
4
8
  import { CallHierarchyFeature } from './call-hierarchy.ts';
5
9
  import { CodeActionsFeature } from './code-actions.ts';
6
10
  import { CodeVisionFeature } from './code-vision.ts';
@@ -12,7 +16,9 @@ import { FormatOnTypeFeature } from './format-on-type.ts';
12
16
  import { HighlightsFeature } from './highlights.ts';
13
17
  import { HistoryFeature } from './history.ts';
14
18
  import { HoverFeature } from './hover.ts';
19
+ import { MarkupFeature } from './markup.ts';
15
20
  import { NavigationFeature } from './navigation.ts';
21
+ import { PasteHistoryFeature } from './paste-history.ts';
16
22
  import { PeekFeature } from './peek.ts';
17
23
  import { PickFeature } from './pick.ts';
18
24
  import { SymbolPickerFeature } from './symbol-picker.ts';
@@ -29,6 +35,7 @@ import type { Place } from './navigation-history.ts';
29
35
  import type { LanguageDocumentHandle, ProjectLanguage } from './project-language.ts';
30
36
  export interface EditorLanguageOptions {
31
37
  semanticTokens?: SemanticTokensOptions;
38
+ serve?: boolean;
32
39
  }
33
40
  export declare class EditorLanguage {
34
41
  readonly editor: Editor;
@@ -38,6 +45,7 @@ export declare class EditorLanguage {
38
45
  readonly project: ProjectLanguage;
39
46
  readonly document: LanguageDocumentHandle;
40
47
  readonly languageId: string;
48
+ readonly served: boolean;
41
49
  readonly popups: import("./popups.ts").PopupStore;
42
50
  readonly diagnostics: DiagnosticsFeature;
43
51
  readonly hover: HoverFeature;
@@ -45,6 +53,7 @@ export declare class EditorLanguage {
45
53
  readonly completion: CompletionFeature;
46
54
  readonly signature: SignatureFeature;
47
55
  readonly snippets: SnippetFeature;
56
+ readonly markup: MarkupFeature;
48
57
  readonly highlights: HighlightsFeature;
49
58
  readonly pick: PickFeature;
50
59
  readonly codeActions: CodeActionsFeature;
@@ -60,11 +69,18 @@ export declare class EditorLanguage {
60
69
  readonly codeVision: CodeVisionFeature;
61
70
  readonly formatOnType: FormatOnTypeFeature;
62
71
  readonly renderedDocs: RenderedDocsFeature;
72
+ readonly pasteHistory: PasteHistoryFeature;
73
+ readonly links: DocumentLinksFeature;
74
+ readonly colors: DocumentColorsFeature;
75
+ readonly linkedEditing: LinkedEditingFeature;
76
+ readonly inlineCompletion: InlineCompletionFeature;
63
77
  private readonly disposers;
64
78
  private readonly keys;
79
+ private readonly timers;
65
80
  private disposed;
66
81
  constructor(project: ProjectLanguage, editor: Editor, uri: string, languageId: string, timers?: Timers, options?: EditorLanguageOptions);
67
82
  get uri(): string;
83
+ get service(): LanguageService;
68
84
  get isDisposed(): boolean;
69
85
  get place(): Place;
70
86
  goTo(location: Location): void;
@@ -73,7 +89,9 @@ export declare class EditorLanguage {
73
89
  locations(places: readonly Location[], anchor: EditorPosition, title: string): void;
74
90
  onProvidersChanged(listener: () => void): () => void;
75
91
  onKey(handler: EditorKeyHandler, priority?: number): () => void;
92
+ prepareSave(reason?: TextDocumentSaveReason): Promise<void>;
76
93
  onDispose(dispose: () => void): void;
77
94
  dispose(): void;
95
+ private otherTexts;
78
96
  private placeOpenedCaret;
79
97
  }