@adecore/editor-react 0.17.0-beta.1 → 0.17.0-beta.2

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/LICENSE +1 -1
  2. package/README.md +35 -30
  3. package/dist/EditorView.d.ts +3 -2
  4. package/dist/EditorView.js +2 -2
  5. package/dist/LanguagePopups.d.ts +3 -1
  6. package/dist/LanguagePopups.js +3 -3
  7. package/dist/code-actions.d.ts +1 -0
  8. package/dist/code-actions.js +19 -22
  9. package/dist/code-authors.d.ts +2 -1
  10. package/dist/code-authors.js +2 -3
  11. package/dist/code-vision-model.d.ts +2 -1
  12. package/dist/code-vision-model.js +3 -4
  13. package/dist/code-vision.js +12 -10
  14. package/dist/completion.js +1 -2
  15. package/dist/editor-language.d.ts +1 -0
  16. package/dist/editor-language.js +22 -0
  17. package/dist/folding.js +3 -5
  18. package/dist/history.js +1 -2
  19. package/dist/host-types.d.ts +2 -0
  20. package/dist/hover.js +1 -2
  21. package/dist/inlay-hints.js +3 -5
  22. package/dist/navigation.d.ts +1 -0
  23. package/dist/navigation.js +8 -9
  24. package/dist/peek.d.ts +1 -0
  25. package/dist/peek.js +6 -7
  26. package/dist/project-language.d.ts +2 -0
  27. package/dist/project-language.js +3 -0
  28. package/dist/rename.d.ts +1 -0
  29. package/dist/rename.js +10 -11
  30. package/dist/semantic-tokens.js +3 -5
  31. package/dist/symbol-picker.js +2 -3
  32. package/dist/symbols.js +3 -5
  33. package/package.json +5 -5
  34. package/src/EditorView.tsx +5 -3
  35. package/src/LanguagePopups.tsx +8 -4
  36. package/src/code-actions.ts +20 -23
  37. package/src/code-authors.ts +3 -3
  38. package/src/code-vision-model.ts +4 -4
  39. package/src/code-vision.ts +12 -10
  40. package/src/completion.ts +1 -2
  41. package/src/editor-language.ts +26 -0
  42. package/src/folding.ts +3 -5
  43. package/src/history.ts +1 -2
  44. package/src/host-types.ts +3 -0
  45. package/src/hover.ts +1 -2
  46. package/src/inlay-hints.ts +3 -5
  47. package/src/navigation.ts +9 -10
  48. package/src/peek.ts +7 -8
  49. package/src/project-language.ts +3 -0
  50. package/src/rename.ts +11 -12
  51. package/src/semantic-tokens.ts +3 -5
  52. package/src/symbol-picker.ts +2 -3
  53. package/src/symbols.ts +3 -5
@@ -145,6 +145,32 @@ export class EditorLanguage {
145
145
  this.pick.open({ anchor, title, groups: [{ title: null, rows }], accept: (id) => this.goTo(places[Number(id)]!) });
146
146
  }
147
147
 
148
+ /*
149
+ * Runs `listener` once the document is open and again whenever the service says what it can ask for
150
+ * this document changed. A service that is ready before the document opens never says so, so the
151
+ * opening counts as the first change.
152
+ */
153
+ onProvidersChanged(listener: () => void): () => void {
154
+ let listening = true;
155
+ void this.document.ready.then(
156
+ () => {
157
+ if (listening && !this.disposed) {
158
+ listener();
159
+ }
160
+ },
161
+ () => undefined
162
+ );
163
+ const providers = this.project.service.onProvidersChanged((uri) => {
164
+ if (uri === this.uri) {
165
+ listener();
166
+ }
167
+ });
168
+ return () => {
169
+ listening = false;
170
+ providers.dispose();
171
+ };
172
+ }
173
+
148
174
  /* Runs `dispose` when the editor lets go of its document. */
149
175
  onDispose(dispose: () => void): void {
150
176
  this.disposers.push(dispose);
package/src/folding.ts CHANGED
@@ -91,15 +91,13 @@ export class FoldingFeature {
91
91
  this.publish();
92
92
  });
93
93
  const edits = editor.onTextChange(() => this.refresher.later());
94
- const providers = project.service.onProvidersChanged((changed) => {
95
- if (changed === uri) {
96
- this.refresher.now();
97
- }
94
+ const providers = language.onProvidersChanged(() => {
95
+ this.refresher.now();
98
96
  });
99
97
  language.onDispose(() => {
100
98
  symbols();
101
99
  edits();
102
- providers.dispose();
100
+ providers();
103
101
  this.refresher.dispose();
104
102
  editor.setFoldHints(null);
105
103
  });
package/src/history.ts CHANGED
@@ -1,4 +1,3 @@
1
- import i18next from 'i18next';
2
1
  import type { EditorLanguage } from './editor-language.ts';
3
2
  import { isShortcut } from './shortcut-keys.ts';
4
3
 
@@ -51,7 +50,7 @@ export class HistoryFeature {
51
50
  this.language.locations(
52
51
  places.map((place) => ({ uri: place.uri, range: { start: place.position, end: place.position } })),
53
52
  this.language.editor.getCaret(),
54
- i18next.t('editor:language.history.recent')
53
+ this.language.project.i18n.t('editor:language.history.recent')
55
54
  );
56
55
  }
57
56
  }
package/src/host-types.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { i18n } from 'i18next';
1
2
  import type { Keymap } from '@adecore/editor/keymap';
2
3
  import type { ProjectFiles } from './workspace-edit.ts';
3
4
  import type { Place } from './navigation-history.ts';
@@ -37,6 +38,8 @@ export interface RenameSuggestionsRequest {
37
38
  }
38
39
 
39
40
  export interface LanguageHost {
41
+ /* The instance the app hands `UIProvider`, which the features read their words from too; the default instance without one. */
42
+ readonly i18n?: i18n;
40
43
  readonly folder?: string;
41
44
  readonly files?: ProjectFiles;
42
45
  readonly apple?: boolean;
package/src/hover.ts CHANGED
@@ -1,4 +1,3 @@
1
- import i18next from 'i18next';
2
1
  import type { EditorHover, EditorPosition, EditorRange } from '@adecore/editor';
3
2
  import type { EditorLanguage } from './editor-language.ts';
4
3
  import { rangeHolds, type Problem } from './diagnostics-model.ts';
@@ -212,7 +211,7 @@ export class HoverFeature {
212
211
  if (problems.length === 0 && info === null) {
213
212
  this.hide();
214
213
  if (pinned) {
215
- this.language.project.host.notify?.({ id: TOAST_ID, kind: 'error', title: i18next.t('editor:language.hover.none') });
214
+ this.language.project.host.notify?.({ id: TOAST_ID, kind: 'error', title: this.language.project.i18n.t('editor:language.hover.none') });
216
215
  }
217
216
  return;
218
217
  }
@@ -71,15 +71,13 @@ export class InlayHintsFeature {
71
71
  this.refresher.later();
72
72
  }
73
73
  });
74
- const providers = project.service.onProvidersChanged((changed) => {
75
- if (changed === uri) {
76
- this.refresher.now();
77
- }
74
+ const providers = language.onProvidersChanged(() => {
75
+ this.refresher.now();
78
76
  });
79
77
  language.onDispose(() => {
80
78
  edits();
81
79
  scrolls();
82
- providers.dispose();
80
+ providers();
83
81
  this.refresher.dispose();
84
82
  editor.setInlayHints([]);
85
83
  });
package/src/navigation.ts CHANGED
@@ -1,4 +1,3 @@
1
- import i18next from 'i18next';
2
1
  import { StaleResultError, fileUriToPath, type Location, type LocationLink, type NavigationResult, type Range } from '@adecore/lsp';
3
2
  import type { EditorPosition } from '@adecore/editor';
4
3
  import { basenameOf } from './paths.ts';
@@ -24,10 +23,6 @@ const SHORTCUT_KINDS: readonly [NavigationKind, 'goToDefinition' | 'goToTypeDefi
24
23
 
25
24
  const TOAST_ID = 'language-navigation';
26
25
 
27
- function say(key: string, options?: Record<string, unknown>): string {
28
- return i18next.t(`editor:language.navigation.${key}`, options);
29
- }
30
-
31
26
  /* The places of a result, each once: the two servers of a Vue file may both know the same one. `target` is the whole declaration when the server gave it. */
32
27
  export function uniquePlaces(result: NavigationResult): { location: Location; target: Range | null }[] {
33
28
  const seen = new Set<string>();
@@ -96,7 +91,7 @@ export class NavigationFeature {
96
91
  async go(kind: NavigationKind, position: EditorPosition = this.language.editor.getCaret()): Promise<void> {
97
92
  const { project, uri, editor } = this.language;
98
93
  if (!this.supports(kind)) {
99
- this.tell(say('unavailable'));
94
+ this.tell(this.say('unavailable'));
100
95
  return;
101
96
  }
102
97
  let result: NavigationResult;
@@ -111,20 +106,20 @@ export class NavigationFeature {
111
106
  : service.implementation(uri, position));
112
107
  } catch (error) {
113
108
  if (!(error instanceof StaleResultError)) {
114
- this.tell(say('failed', { message: error instanceof Error ? error.message : String(error) }));
109
+ this.tell(this.say('failed', { message: error instanceof Error ? error.message : String(error) }));
115
110
  }
116
111
  return;
117
112
  }
118
113
  const places = uniqueLocations(result);
119
114
  if (places.length === 0) {
120
- this.tell(say('none'));
115
+ this.tell(this.say('none'));
121
116
  } else if (kind === 'definition' && places.length === 1 && places[0]!.uri === uri && rangeHolds(places[0]!.range, position)) {
122
117
  // The name is its own definition, so what a person wants to see is where it is used.
123
118
  void this.language.peek.open(position);
124
119
  } else if (places.length === 1) {
125
120
  this.language.goTo(places[0]!);
126
121
  } else {
127
- this.language.locations(places, position, say(`titles.${kind}`));
122
+ this.language.locations(places, position, this.say(`titles.${kind}`));
128
123
  }
129
124
  editor.focus();
130
125
  }
@@ -148,7 +143,7 @@ export class NavigationFeature {
148
143
  if (places.length === 1) {
149
144
  this.language.goTo(places[0]!);
150
145
  } else if (places.length > 1) {
151
- this.language.locations(places, anchor, say('titles.symbol'));
146
+ this.language.locations(places, anchor, this.say('titles.symbol'));
152
147
  }
153
148
  } catch {
154
149
  // A name that cannot be found is a link that does nothing, not an error.
@@ -158,6 +153,10 @@ export class NavigationFeature {
158
153
  private tell(title: string): void {
159
154
  this.language.project.host.notify?.({ id: TOAST_ID, kind: 'error', title });
160
155
  }
156
+
157
+ private say(key: string, options?: Record<string, unknown>): string {
158
+ return this.language.project.i18n.t(`editor:language.navigation.${key}`, options);
159
+ }
161
160
  }
162
161
 
163
162
  /* How a place reads in a list: the file and the line, with the folder it is in. */
package/src/peek.ts CHANGED
@@ -1,4 +1,3 @@
1
- import i18next from 'i18next';
2
1
  import { StaleResultError, type Location, type Range } from '@adecore/lsp';
3
2
  import type { EditorPosition } from '@adecore/editor';
4
3
  import type { EditorLanguage } from './editor-language.ts';
@@ -22,10 +21,6 @@ function placeKey(location: Location): string {
22
21
  return `${location.uri}\0${location.range.start.line}\0${location.range.start.character}`;
23
22
  }
24
23
 
25
- function say(key: string, options?: Record<string, unknown>): string {
26
- return i18next.t(`editor:language.peek.${key}`, options);
27
- }
28
-
29
24
  /*
30
25
  * The references of the name at the caret, between the lines of the file under the line it is on. The
31
26
  * editor keeps the keyboard: arrows pick a reference, Enter goes to it and Escape closes the peek; typing
@@ -75,7 +70,7 @@ export class PeekFeature {
75
70
  const { editor, project, uri } = this.language;
76
71
  const method = KINDS[kind].method;
77
72
  if (!project.service.supports(method, uri)) {
78
- this.tell(say(KINDS[kind].unavailable));
73
+ this.tell(this.say(KINDS[kind].unavailable));
79
74
  return;
80
75
  }
81
76
  const token = ++this.token;
@@ -84,7 +79,7 @@ export class PeekFeature {
84
79
  found = await this.fetch(kind, position);
85
80
  } catch (error) {
86
81
  if (!(error instanceof StaleResultError)) {
87
- this.tell(say('failed', { message: error instanceof Error ? error.message : String(error) }));
82
+ this.tell(this.say('failed', { message: error instanceof Error ? error.message : String(error) }));
88
83
  }
89
84
  return;
90
85
  }
@@ -102,7 +97,7 @@ export class PeekFeature {
102
97
  ) === index
103
98
  );
104
99
  if (unique.length === 0) {
105
- this.tell(say(KINDS[kind].none));
100
+ this.tell(this.say(KINDS[kind].none));
106
101
  return;
107
102
  }
108
103
  const uris = [...new Set(unique.map((location) => location.uri))].slice(0, PEEK_READ_FILES);
@@ -245,4 +240,8 @@ export class PeekFeature {
245
240
  private tell(title: string): void {
246
241
  this.language.project.host.notify?.({ id: TOAST_ID, kind: 'error', title });
247
242
  }
243
+
244
+ private say(key: string, options?: Record<string, unknown>): string {
245
+ return this.language.project.i18n.t(`editor:language.peek.${key}`, options);
246
+ }
248
247
  }
@@ -1,3 +1,4 @@
1
+ import i18next, { type i18n } from 'i18next';
1
2
  import { fileUriToPath, type ApplyWorkspaceEditResult, type ContentChange, type LanguageService, type WorkspaceEdit } from '@adecore/lsp';
2
3
  import type { Editor, EditorPosition } from '@adecore/editor';
3
4
  import { NavigationHistory, type Place } from './navigation-history.ts';
@@ -78,6 +79,7 @@ interface Holder {
78
79
  export class ProjectLanguage {
79
80
  readonly service: LanguageService;
80
81
  readonly host: LanguageHost;
82
+ readonly i18n: i18n;
81
83
  readonly folder: string;
82
84
  readonly problems: ProjectProblems;
83
85
  readonly history = new NavigationHistory();
@@ -90,6 +92,7 @@ export class ProjectLanguage {
90
92
  constructor(service: LanguageService, host: LanguageHost = {}) {
91
93
  this.service = service;
92
94
  this.host = host;
95
+ this.i18n = host.i18n ?? i18next;
93
96
  this.folder = host.folder ?? '';
94
97
  this.files = host.files ?? null;
95
98
  this.problems = new ProjectProblems(service, (uri) => this.pathOfLocation(uri) ?? uri);
package/src/rename.ts CHANGED
@@ -1,4 +1,3 @@
1
- import i18next from 'i18next';
2
1
  import { StaleResultError, type Location, type PrepareRenameResult, type WorkspaceEdit } from '@adecore/lsp';
3
2
  import type { EditorRange } from '@adecore/editor';
4
3
  import type { EditorLanguage } from './editor-language.ts';
@@ -13,10 +12,6 @@ const SAMPLE_LOCATIONS = 8;
13
12
  const SAMPLE_OTHER_FILES = 3;
14
13
  const CONTEXT_LINES = 3;
15
14
 
16
- function say(key: string, options?: Record<string, unknown>): string {
17
- return i18next.t(`editor:language.rename.${key}`, options);
18
- }
19
-
20
15
  interface Session {
21
16
  readonly target: RenameTarget;
22
17
  readonly original: string;
@@ -67,7 +62,7 @@ export class RenameFeature {
67
62
  async start(): Promise<void> {
68
63
  const { editor, project, uri } = this.language;
69
64
  if (!project.service.supports(METHOD, uri)) {
70
- this.tell('error', say('unavailable'));
65
+ this.tell('error', this.say('unavailable'));
71
66
  return;
72
67
  }
73
68
  this.cancel();
@@ -78,7 +73,7 @@ export class RenameFeature {
78
73
  answer = prepare ? await project.service.prepareRename(uri, caret) : { defaultBehavior: true };
79
74
  } catch (error) {
80
75
  if (!(error instanceof StaleResultError)) {
81
- this.tell('error', say('nothing'));
76
+ this.tell('error', this.say('nothing'));
82
77
  }
83
78
  return;
84
79
  }
@@ -92,7 +87,7 @@ export class RenameFeature {
92
87
  )
93
88
  );
94
89
  if (target === null) {
95
- this.tell('error', say('nothing'));
90
+ this.tell('error', this.say('nothing'));
96
91
  return;
97
92
  }
98
93
  const session: Session = { target, original: editor.textInRange(target.range), edit: null, names: new AbortController() };
@@ -120,7 +115,7 @@ export class RenameFeature {
120
115
  return;
121
116
  }
122
117
  if (edit === null) {
123
- this.fail(say('refused'));
118
+ this.fail(this.say('refused'));
124
119
  return;
125
120
  }
126
121
  session.edit = edit;
@@ -140,7 +135,7 @@ export class RenameFeature {
140
135
  this.publish({ phase: 'preview', busy: false, name: next, files });
141
136
  } catch (error) {
142
137
  if (this.session === session) {
143
- this.fail(error instanceof StaleResultError ? say('stale') : error instanceof Error ? error.message : String(error));
138
+ this.fail(error instanceof StaleResultError ? this.say('stale') : error instanceof Error ? error.message : String(error));
144
139
  }
145
140
  }
146
141
  }
@@ -168,7 +163,7 @@ export class RenameFeature {
168
163
  if (result.applied) {
169
164
  this.finish();
170
165
  } else {
171
- this.fail(result.failureReason ?? say('refused'));
166
+ this.fail(result.failureReason ?? this.say('refused'));
172
167
  }
173
168
  }
174
169
 
@@ -176,7 +171,7 @@ export class RenameFeature {
176
171
  private fail(message: string): void {
177
172
  if (this.session !== null) {
178
173
  this.session.edit = null;
179
- this.publish({ phase: 'input', busy: false, files: [], error: say('failed', { message }) });
174
+ this.publish({ phase: 'input', busy: false, files: [], error: this.say('failed', { message }) });
180
175
  }
181
176
  }
182
177
 
@@ -288,4 +283,8 @@ export class RenameFeature {
288
283
  }
289
284
  });
290
285
  }
286
+
287
+ private say(key: string, options?: Record<string, unknown>): string {
288
+ return this.language.project.i18n.t(`editor:language.rename.${key}`, options);
289
+ }
291
290
  }
@@ -33,14 +33,12 @@ export class SemanticTokensFeature {
33
33
  timers
34
34
  );
35
35
  const edits = editor.onTextChange(() => this.refresher.later());
36
- const providers = project.service.onProvidersChanged((changed) => {
37
- if (changed === uri) {
38
- this.refresher.now();
39
- }
36
+ const providers = language.onProvidersChanged(() => {
37
+ this.refresher.now();
40
38
  });
41
39
  language.onDispose(() => {
42
40
  edits();
43
- providers.dispose();
41
+ providers();
44
42
  this.refresher.dispose();
45
43
  editor.setSemanticTokens(null);
46
44
  });
@@ -1,4 +1,3 @@
1
- import i18next from 'i18next';
2
1
  import { StaleResultError, type Location } from '@adecore/lsp';
3
2
  import { basenameOf } from './paths.ts';
4
3
  import type { EditorLanguage } from './editor-language.ts';
@@ -33,7 +32,7 @@ export class SymbolPickerFeature {
33
32
  async open(): Promise<void> {
34
33
  const { project, uri, popups } = this.language;
35
34
  if (!project.service.supports('textDocument/documentSymbol', uri)) {
36
- this.language.project.host.notify?.({ id: TOAST_ID, kind: 'error', title: i18next.t('editor:language.symbols.unavailable') });
35
+ this.language.project.host.notify?.({ id: TOAST_ID, kind: 'error', title: this.language.project.i18n.t('editor:language.symbols.unavailable') });
37
36
  return;
38
37
  }
39
38
  try {
@@ -44,7 +43,7 @@ export class SymbolPickerFeature {
44
43
  this.language.project.host.notify?.({
45
44
  id: TOAST_ID,
46
45
  kind: 'error',
47
- title: i18next.t('editor:language.symbols.failed', { message: error instanceof Error ? error.message : String(error) })
46
+ title: this.language.project.i18n.t('editor:language.symbols.failed', { message: error instanceof Error ? error.message : String(error) })
48
47
  });
49
48
  }
50
49
  }
package/src/symbols.ts CHANGED
@@ -89,14 +89,12 @@ export class SymbolsFeature {
89
89
  timers
90
90
  );
91
91
  const edits = editor.onTextChange(() => this.refresher.later());
92
- const providers = project.service.onProvidersChanged((changed) => {
93
- if (changed === uri) {
94
- this.refresher.now();
95
- }
92
+ const providers = language.onProvidersChanged(() => {
93
+ this.refresher.now();
96
94
  });
97
95
  language.onDispose(() => {
98
96
  edits();
99
- providers.dispose();
97
+ providers();
100
98
  this.refresher.dispose();
101
99
  editor.setBlocks(null);
102
100
  });