@adecore/editor-react 0.44.1 → 0.44.3
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.
- package/dist/completion.d.ts +1 -0
- package/dist/completion.js +9 -1
- package/dist/editor-language.js +3 -3
- package/dist/history.js +4 -1
- package/dist/inline-completion.d.ts +1 -0
- package/dist/inline-completion.js +11 -4
- package/dist/key-priority.d.ts +1 -0
- package/dist/key-priority.js +2 -0
- package/dist/project-language.d.ts +1 -0
- package/dist/project-language.js +4 -0
- package/package.json +6 -6
- package/src/completion.ts +15 -1
- package/src/editor-language.ts +3 -3
- package/src/history.ts +5 -1
- package/src/inline-completion.ts +12 -3
- package/src/key-priority.ts +2 -0
- package/src/project-language.ts +5 -0
package/dist/completion.d.ts
CHANGED
package/dist/completion.js
CHANGED
|
@@ -658,7 +658,7 @@ export class CompletionFeature {
|
|
|
658
658
|
this.textRevision++;
|
|
659
659
|
// The rows were given, so typing only filters them.
|
|
660
660
|
if (this.shown && this.isOpen) {
|
|
661
|
-
if (change.source === 'input' && change.changes.length === 1 &&
|
|
661
|
+
if (change.source === 'input' && change.changes.length === 1 && this.typedIntoShown(change.changes[0])) {
|
|
662
662
|
this.refilter();
|
|
663
663
|
} else {
|
|
664
664
|
this.close();
|
|
@@ -702,6 +702,14 @@ export class CompletionFeature {
|
|
|
702
702
|
this.close();
|
|
703
703
|
}
|
|
704
704
|
}
|
|
705
|
+
/* Whether a change put word characters into the typed prefix of the given rows; anything else leaves their ranges pointing at other text. */
|
|
706
|
+
typedIntoShown({
|
|
707
|
+
range,
|
|
708
|
+
text
|
|
709
|
+
}) {
|
|
710
|
+
const start = this.start;
|
|
711
|
+
return text !== '' && isEmptyRange(range) && start !== null && range.start.line === start.line && range.start.character >= start.character && Array.from(text).every(isIdentifierCharacter);
|
|
712
|
+
}
|
|
705
713
|
caretMoved(position) {
|
|
706
714
|
if (this.start === null) {
|
|
707
715
|
return;
|
package/dist/editor-language.js
CHANGED
|
@@ -240,9 +240,9 @@ export class EditorLanguage {
|
|
|
240
240
|
async prepareSave(reason = 1) {
|
|
241
241
|
const {
|
|
242
242
|
service
|
|
243
|
-
} = this
|
|
243
|
+
} = this;
|
|
244
244
|
await service.willSave?.(this.uri, reason).catch(() => undefined);
|
|
245
|
-
if (service.willSaveWaitUntil === undefined) {
|
|
245
|
+
if (this.disposed || service.willSaveWaitUntil === undefined) {
|
|
246
246
|
return;
|
|
247
247
|
}
|
|
248
248
|
const controller = new AbortController();
|
|
@@ -251,7 +251,7 @@ export class EditorLanguage {
|
|
|
251
251
|
const edits = await service.willSaveWaitUntil(this.uri, reason, {
|
|
252
252
|
signal: controller.signal
|
|
253
253
|
});
|
|
254
|
-
if (!controller.signal.aborted && edits !== null && edits.length > 0) {
|
|
254
|
+
if (!this.disposed && !controller.signal.aborted && edits !== null && edits.length > 0) {
|
|
255
255
|
this.editor.applyEdits(edits.map(edit => ({
|
|
256
256
|
range: edit.range,
|
|
257
257
|
text: edit.newText
|
package/dist/history.js
CHANGED
|
@@ -82,7 +82,10 @@ export class HistoryFeature {
|
|
|
82
82
|
project,
|
|
83
83
|
uri
|
|
84
84
|
} = this.language;
|
|
85
|
-
|
|
85
|
+
// The places belong to the file and the other editors of it hear the same change, so one editor moves them.
|
|
86
|
+
if (project.holdsFirst(uri, editor)) {
|
|
87
|
+
project.edits.map(uri, change.changes);
|
|
88
|
+
}
|
|
86
89
|
this.editing = true;
|
|
87
90
|
queueMicrotask(() => {
|
|
88
91
|
this.editing = false;
|
|
@@ -62,7 +62,7 @@ export class InlineCompletionFeature {
|
|
|
62
62
|
const {
|
|
63
63
|
editor
|
|
64
64
|
} = language;
|
|
65
|
-
const offs = [language.onKey(event => this.key(event), KEY_PRIORITY.inlineCompletion), editor.onTextChange(change => this.edited(change)), editor.onCaret(() => queueMicrotask(() => this.moved()))];
|
|
65
|
+
const offs = [language.onKey(event => this.key(event), KEY_PRIORITY.inlineCompletion), language.onKey(event => this.tab(event), KEY_PRIORITY.inlineAccept), editor.onTextChange(change => this.edited(change)), editor.onCaret(() => queueMicrotask(() => this.moved()))];
|
|
66
66
|
language.onDispose(() => {
|
|
67
67
|
this.hide();
|
|
68
68
|
for (const off of offs) {
|
|
@@ -139,6 +139,16 @@ export class InlineCompletionFeature {
|
|
|
139
139
|
this.language.editor.setGhostText(null);
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
|
+
/* Tab takes the suggestion on screen before any feature that expands on Tab; without one it is left alone. */
|
|
143
|
+
tab(event) {
|
|
144
|
+
if (this.current === null || event.isComposing) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
if (event.key === 'Tab' && !event.shiftKey && !event.altKey && !event.ctrlKey && !event.metaKey) {
|
|
148
|
+
return this.accept();
|
|
149
|
+
}
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
142
152
|
key(event) {
|
|
143
153
|
if (isShortcut(this.language.shortcuts.suggestInline, event) && this.enabled) {
|
|
144
154
|
this.trigger();
|
|
@@ -147,9 +157,6 @@ export class InlineCompletionFeature {
|
|
|
147
157
|
if (this.current === null || event.isComposing) {
|
|
148
158
|
return false;
|
|
149
159
|
}
|
|
150
|
-
if (event.key === 'Tab' && !event.shiftKey && !event.altKey && !event.ctrlKey && !event.metaKey) {
|
|
151
|
-
return this.accept();
|
|
152
|
-
}
|
|
153
160
|
if (event.key === 'Escape') {
|
|
154
161
|
this.hide();
|
|
155
162
|
return true;
|
package/dist/key-priority.d.ts
CHANGED
package/dist/key-priority.js
CHANGED
|
@@ -7,6 +7,8 @@ export const KEY_PRIORITY = {
|
|
|
7
7
|
diagnostics: 1500,
|
|
8
8
|
hover: 1400,
|
|
9
9
|
completion: 1300,
|
|
10
|
+
// Tab only: a suggestion on screen is taken before a template or an abbreviation expands, so the spacing of a hundred bends here too.
|
|
11
|
+
inlineAccept: 1250,
|
|
10
12
|
snippets: 1200,
|
|
11
13
|
// Between snippets and markup, so the spacing of a hundred bends here: Tab has to reach the templates after a running snippet and before Emmet.
|
|
12
14
|
templates: 1150,
|
|
@@ -57,6 +57,7 @@ export declare class ProjectLanguage {
|
|
|
57
57
|
noteCaret(place: Place): void;
|
|
58
58
|
jumpTo(place: Place): void;
|
|
59
59
|
takeCaret(uri: string, line: number): EditorPosition | null;
|
|
60
|
+
holdsFirst(uri: string, editor: Editor): boolean;
|
|
60
61
|
openEditors(): readonly {
|
|
61
62
|
uri: string;
|
|
62
63
|
editor: Editor;
|
package/dist/project-language.js
CHANGED
|
@@ -278,6 +278,10 @@ export class ProjectLanguage {
|
|
|
278
278
|
this.pendingCaret = null;
|
|
279
279
|
return pending.position;
|
|
280
280
|
}
|
|
281
|
+
/* Whether the editor is the first to hold the document, the one whose changes stand for the document's. */
|
|
282
|
+
holdsFirst(uri, editor) {
|
|
283
|
+
return this.holders.get(uri)?.editors[0] === editor;
|
|
284
|
+
}
|
|
281
285
|
/* The editors that hold a document, one per uri in the order the documents were first opened. */
|
|
282
286
|
openEditors() {
|
|
283
287
|
return [...this.holders].flatMap(([uri, holder]) => holder.editors[0] === undefined ? [] : [{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adecore/editor-react",
|
|
3
|
-
"version": "0.44.
|
|
3
|
+
"version": "0.44.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Language-enabled editors, feature coordination and React popups over injected language services.",
|
|
6
6
|
"license": "FSL-1.1-MIT",
|
|
@@ -62,9 +62,9 @@
|
|
|
62
62
|
"test": "bun test --conditions=source"
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@adecore/editor": "0.44.
|
|
66
|
-
"@adecore/lsp": "0.44.
|
|
67
|
-
"@adecore/merge": "0.44.
|
|
65
|
+
"@adecore/editor": "0.44.3",
|
|
66
|
+
"@adecore/lsp": "0.44.3",
|
|
67
|
+
"@adecore/merge": "0.44.3",
|
|
68
68
|
"emmet": "^2.4.11",
|
|
69
69
|
"lucide-react": "^1.61.0",
|
|
70
70
|
"react-markdown": "^10.1.0",
|
|
@@ -73,14 +73,14 @@
|
|
|
73
73
|
"zustand": "^5.0.16"
|
|
74
74
|
},
|
|
75
75
|
"peerDependencies": {
|
|
76
|
-
"@adecore/ui": "^0.44.
|
|
76
|
+
"@adecore/ui": "^0.44.3",
|
|
77
77
|
"i18next": "^26.4.2",
|
|
78
78
|
"react": "^19.3.0",
|
|
79
79
|
"react-dom": "^19.3.0",
|
|
80
80
|
"react-i18next": "^17.0.15"
|
|
81
81
|
},
|
|
82
82
|
"devDependencies": {
|
|
83
|
-
"@adecore/ui": "0.44.
|
|
83
|
+
"@adecore/ui": "0.44.3",
|
|
84
84
|
"@types/bun": "^1.4.4",
|
|
85
85
|
"@types/react": "^19.3.0",
|
|
86
86
|
"@types/react-dom": "^19.3.0",
|
package/src/completion.ts
CHANGED
|
@@ -728,7 +728,7 @@ export class CompletionFeature {
|
|
|
728
728
|
|
|
729
729
|
// The rows were given, so typing only filters them.
|
|
730
730
|
if (this.shown && this.isOpen) {
|
|
731
|
-
if (change.source === 'input' && change.changes.length === 1 &&
|
|
731
|
+
if (change.source === 'input' && change.changes.length === 1 && this.typedIntoShown(change.changes[0]!)) {
|
|
732
732
|
this.refilter();
|
|
733
733
|
} else {
|
|
734
734
|
this.close();
|
|
@@ -768,6 +768,20 @@ export class CompletionFeature {
|
|
|
768
768
|
}
|
|
769
769
|
}
|
|
770
770
|
|
|
771
|
+
/* Whether a change put word characters into the typed prefix of the given rows; anything else leaves their ranges pointing at other text. */
|
|
772
|
+
private typedIntoShown({ range, text }: EditorContentChange): boolean {
|
|
773
|
+
const start = this.start;
|
|
774
|
+
|
|
775
|
+
return (
|
|
776
|
+
text !== '' &&
|
|
777
|
+
isEmptyRange(range) &&
|
|
778
|
+
start !== null &&
|
|
779
|
+
range.start.line === start.line &&
|
|
780
|
+
range.start.character >= start.character &&
|
|
781
|
+
Array.from(text).every(isIdentifierCharacter)
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
|
|
771
785
|
private caretMoved(position: EditorPosition): void {
|
|
772
786
|
if (this.start === null) {
|
|
773
787
|
return;
|
package/src/editor-language.ts
CHANGED
|
@@ -249,16 +249,16 @@ export class EditorLanguage {
|
|
|
249
249
|
* as the specification allows. Call it, then save, then let the service send `didSave`.
|
|
250
250
|
*/
|
|
251
251
|
async prepareSave(reason: TextDocumentSaveReason = 1): Promise<void> {
|
|
252
|
-
const { service } = this
|
|
252
|
+
const { service } = this;
|
|
253
253
|
await service.willSave?.(this.uri, reason).catch(() => undefined);
|
|
254
|
-
if (service.willSaveWaitUntil === undefined) {
|
|
254
|
+
if (this.disposed || service.willSaveWaitUntil === undefined) {
|
|
255
255
|
return;
|
|
256
256
|
}
|
|
257
257
|
const controller = new AbortController();
|
|
258
258
|
const timer = this.timers.set(() => controller.abort(), SAVE_PARTICIPANT_MS);
|
|
259
259
|
try {
|
|
260
260
|
const edits = await service.willSaveWaitUntil(this.uri, reason, { signal: controller.signal });
|
|
261
|
-
if (!controller.signal.aborted && edits !== null && edits.length > 0) {
|
|
261
|
+
if (!this.disposed && !controller.signal.aborted && edits !== null && edits.length > 0) {
|
|
262
262
|
this.editor.applyEdits(edits.map((edit) => ({ range: edit.range, text: edit.newText })));
|
|
263
263
|
}
|
|
264
264
|
} catch {
|
package/src/history.ts
CHANGED
|
@@ -98,7 +98,11 @@ export class HistoryFeature {
|
|
|
98
98
|
private edited(change: EditorTextChange): void {
|
|
99
99
|
const { editor, project, uri } = this.language;
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
// The places belong to the file and the other editors of it hear the same change, so one editor moves them.
|
|
102
|
+
if (project.holdsFirst(uri, editor)) {
|
|
103
|
+
project.edits.map(uri, change.changes);
|
|
104
|
+
}
|
|
105
|
+
|
|
102
106
|
this.editing = true;
|
|
103
107
|
|
|
104
108
|
queueMicrotask(() => {
|
package/src/inline-completion.ts
CHANGED
|
@@ -69,6 +69,7 @@ export class InlineCompletionFeature {
|
|
|
69
69
|
const { editor } = language;
|
|
70
70
|
const offs = [
|
|
71
71
|
language.onKey((event) => this.key(event), KEY_PRIORITY.inlineCompletion),
|
|
72
|
+
language.onKey((event) => this.tab(event), KEY_PRIORITY.inlineAccept),
|
|
72
73
|
editor.onTextChange((change) => this.edited(change)),
|
|
73
74
|
editor.onCaret(() => queueMicrotask(() => this.moved()))
|
|
74
75
|
];
|
|
@@ -146,6 +147,17 @@ export class InlineCompletionFeature {
|
|
|
146
147
|
}
|
|
147
148
|
}
|
|
148
149
|
|
|
150
|
+
/* Tab takes the suggestion on screen before any feature that expands on Tab; without one it is left alone. */
|
|
151
|
+
private tab(event: KeyboardEvent): boolean {
|
|
152
|
+
if (this.current === null || event.isComposing) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
if (event.key === 'Tab' && !event.shiftKey && !event.altKey && !event.ctrlKey && !event.metaKey) {
|
|
156
|
+
return this.accept();
|
|
157
|
+
}
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
|
|
149
161
|
private key(event: KeyboardEvent): boolean {
|
|
150
162
|
if (isShortcut(this.language.shortcuts.suggestInline, event) && this.enabled) {
|
|
151
163
|
this.trigger();
|
|
@@ -154,9 +166,6 @@ export class InlineCompletionFeature {
|
|
|
154
166
|
if (this.current === null || event.isComposing) {
|
|
155
167
|
return false;
|
|
156
168
|
}
|
|
157
|
-
if (event.key === 'Tab' && !event.shiftKey && !event.altKey && !event.ctrlKey && !event.metaKey) {
|
|
158
|
-
return this.accept();
|
|
159
|
-
}
|
|
160
169
|
if (event.key === 'Escape') {
|
|
161
170
|
this.hide();
|
|
162
171
|
return true;
|
package/src/key-priority.ts
CHANGED
|
@@ -9,6 +9,8 @@ export const KEY_PRIORITY = {
|
|
|
9
9
|
diagnostics: 1500,
|
|
10
10
|
hover: 1400,
|
|
11
11
|
completion: 1300,
|
|
12
|
+
// Tab only: a suggestion on screen is taken before a template or an abbreviation expands, so the spacing of a hundred bends here too.
|
|
13
|
+
inlineAccept: 1250,
|
|
12
14
|
snippets: 1200,
|
|
13
15
|
// Between snippets and markup, so the spacing of a hundred bends here: Tab has to reach the templates after a running snippet and before Emmet.
|
|
14
16
|
templates: 1150,
|
package/src/project-language.ts
CHANGED
|
@@ -323,6 +323,11 @@ export class ProjectLanguage {
|
|
|
323
323
|
return pending.position;
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
+
/* Whether the editor is the first to hold the document, the one whose changes stand for the document's. */
|
|
327
|
+
holdsFirst(uri: string, editor: Editor): boolean {
|
|
328
|
+
return this.holders.get(uri)?.editors[0] === editor;
|
|
329
|
+
}
|
|
330
|
+
|
|
326
331
|
/* The editors that hold a document, one per uri in the order the documents were first opened. */
|
|
327
332
|
openEditors(): readonly { uri: string; editor: Editor }[] {
|
|
328
333
|
return [...this.holders].flatMap(([uri, holder]) => (holder.editors[0] === undefined ? [] : [{ uri, editor: holder.editors[0] }]));
|