@adecore/editor-react 0.42.0 → 0.44.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 (150) 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 +245 -104
  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.d.ts +28 -0
  17. package/dist/completion.js +83 -27
  18. package/dist/context-menu-rows.d.ts +1 -1
  19. package/dist/context-menu-rows.js +4 -0
  20. package/dist/definition-link.js +15 -5
  21. package/dist/document-colors.d.ts +24 -0
  22. package/dist/document-colors.js +194 -0
  23. package/dist/document-links.d.ts +22 -0
  24. package/dist/document-links.js +133 -0
  25. package/dist/document-query.d.ts +15 -0
  26. package/dist/document-query.js +54 -0
  27. package/dist/edit-locations.d.ts +20 -0
  28. package/dist/edit-locations.js +136 -0
  29. package/dist/editor-language.d.ts +23 -1
  30. package/dist/editor-language.js +83 -2
  31. package/dist/emmet.d.ts +13 -0
  32. package/dist/emmet.js +62 -0
  33. package/dist/folding.js +2 -3
  34. package/dist/format-on-type.js +3 -5
  35. package/dist/highlights.js +2 -3
  36. package/dist/history.d.ts +7 -0
  37. package/dist/history.js +79 -1
  38. package/dist/host-types.d.ts +10 -0
  39. package/dist/index.d.ts +13 -2
  40. package/dist/index.js +10 -0
  41. package/dist/inlay-hints.js +2 -2
  42. package/dist/inline-completion.d.ts +39 -0
  43. package/dist/inline-completion.js +221 -0
  44. package/dist/key-priority.d.ts +9 -5
  45. package/dist/key-priority.js +11 -6
  46. package/dist/linked-editing.d.ts +26 -0
  47. package/dist/linked-editing.js +215 -0
  48. package/dist/live-templates.d.ts +16 -0
  49. package/dist/live-templates.js +289 -0
  50. package/dist/locales/en.json +61 -2
  51. package/dist/locales/nl.json +61 -2
  52. package/dist/markup-context.d.ts +55 -0
  53. package/dist/markup-context.js +408 -0
  54. package/dist/markup.d.ts +39 -0
  55. package/dist/markup.js +465 -0
  56. package/dist/navigation.js +4 -6
  57. package/dist/paste-history.d.ts +7 -0
  58. package/dist/paste-history.js +104 -0
  59. package/dist/peek.js +3 -4
  60. package/dist/pick.d.ts +21 -2
  61. package/dist/pick.js +152 -19
  62. package/dist/popups.d.ts +10 -0
  63. package/dist/popups.js +2 -1
  64. package/dist/postfix.d.ts +21 -0
  65. package/dist/postfix.js +212 -0
  66. package/dist/project-language.d.ts +20 -1
  67. package/dist/project-language.js +132 -18
  68. package/dist/rename.js +8 -11
  69. package/dist/rendered-docs.js +1 -2
  70. package/dist/selection-ranges.js +2 -3
  71. package/dist/shortcut-keys.d.ts +24 -0
  72. package/dist/signature.js +3 -6
  73. package/dist/snippet-insert.d.ts +10 -0
  74. package/dist/snippet-insert.js +217 -0
  75. package/dist/snippet-session.d.ts +2 -2
  76. package/dist/snippet-session.js +3 -3
  77. package/dist/snippet.d.ts +7 -1
  78. package/dist/snippet.js +65 -8
  79. package/dist/surround-constructs.d.ts +10 -0
  80. package/dist/surround-constructs.js +100 -0
  81. package/dist/surround.d.ts +18 -0
  82. package/dist/surround.js +399 -0
  83. package/dist/symbol-picker.js +4 -6
  84. package/dist/symbols.js +22 -3
  85. package/dist/template-builtins.d.ts +3 -0
  86. package/dist/template-builtins.js +46 -0
  87. package/dist/template-context.d.ts +12 -0
  88. package/dist/template-context.js +56 -0
  89. package/dist/templates.d.ts +59 -0
  90. package/dist/templates.js +103 -0
  91. package/dist/testing.d.ts +23 -1
  92. package/dist/testing.js +114 -0
  93. package/package.json +7 -6
  94. package/src/CallHierarchyPanel.tsx +26 -11
  95. package/src/EditorContextMenu.tsx +7 -0
  96. package/src/LanguagePopups.tsx +4 -0
  97. package/src/PickPopup.tsx +47 -5
  98. package/src/WrapCard.tsx +64 -0
  99. package/src/call-hierarchy-model.ts +25 -3
  100. package/src/call-hierarchy.ts +62 -20
  101. package/src/clipboard-history.ts +102 -0
  102. package/src/code-actions.ts +48 -11
  103. package/src/code-vision.ts +3 -3
  104. package/src/completion.ts +123 -25
  105. package/src/context-menu-rows.ts +3 -2
  106. package/src/definition-link.ts +17 -5
  107. package/src/document-colors.ts +189 -0
  108. package/src/document-links.ts +129 -0
  109. package/src/document-query.ts +71 -0
  110. package/src/edit-locations.ts +162 -0
  111. package/src/editor-language.ts +86 -3
  112. package/src/emmet-module.d.ts +44 -0
  113. package/src/emmet.ts +73 -0
  114. package/src/folding.ts +3 -3
  115. package/src/format-on-type.ts +5 -5
  116. package/src/highlights.ts +3 -3
  117. package/src/history.ts +81 -1
  118. package/src/host-types.ts +17 -0
  119. package/src/index.ts +28 -3
  120. package/src/inlay-hints.ts +2 -2
  121. package/src/inline-completion.ts +221 -0
  122. package/src/key-priority.ts +11 -6
  123. package/src/linked-editing.ts +199 -0
  124. package/src/live-templates.ts +315 -0
  125. package/src/locales/en.json +61 -2
  126. package/src/locales/nl.json +61 -2
  127. package/src/markup-context.ts +387 -0
  128. package/src/markup.ts +377 -0
  129. package/src/navigation.ts +6 -6
  130. package/src/paste-history.ts +111 -0
  131. package/src/peek.ts +4 -4
  132. package/src/pick.ts +218 -20
  133. package/src/popups.ts +21 -1
  134. package/src/postfix.ts +224 -0
  135. package/src/project-language.ts +139 -19
  136. package/src/rename.ts +11 -11
  137. package/src/rendered-docs.ts +2 -2
  138. package/src/selection-ranges.ts +3 -3
  139. package/src/signature.ts +6 -6
  140. package/src/snippet-insert.ts +258 -0
  141. package/src/snippet-session.ts +9 -4
  142. package/src/snippet.ts +82 -7
  143. package/src/surround-constructs.ts +85 -0
  144. package/src/surround.ts +430 -0
  145. package/src/symbol-picker.ts +6 -6
  146. package/src/symbols.ts +30 -4
  147. package/src/template-builtins.ts +94 -0
  148. package/src/template-context.ts +65 -0
  149. package/src/templates.ts +197 -0
  150. package/src/testing.ts +126 -1
@@ -0,0 +1,65 @@
1
+ import type { EditorPosition } from '@adecore/editor';
2
+ import type { EditorLanguage } from './editor-language.ts';
3
+ import type { LiveTemplate, TemplateLanguage, TemplatePlace } from './templates.ts';
4
+
5
+ /* The completion item kind of a snippet. */
6
+ export const SNIPPET_KIND = 15;
7
+
8
+ /* Sorts a template row after the rows of the same score that a service answers. */
9
+ export function sortAfterServer(key: string): string {
10
+ return `￿${key}`;
11
+ }
12
+
13
+ /* The families a language id counts as; TypeScript builds on JavaScript. */
14
+ export function familiesOf(languageId: string): readonly TemplateLanguage[] {
15
+ switch (languageId) {
16
+ case 'javascript':
17
+ case 'js':
18
+ case 'mjs':
19
+ case 'cjs':
20
+ case 'jsx':
21
+ case 'javascriptreact':
22
+ return ['javascript'];
23
+ case 'typescript':
24
+ case 'ts':
25
+ case 'mts':
26
+ case 'cts':
27
+ case 'tsx':
28
+ case 'typescriptreact':
29
+ return ['javascript', 'typescript'];
30
+ case 'php':
31
+ return ['php'];
32
+ case 'css':
33
+ case 'scss':
34
+ case 'less':
35
+ return ['css'];
36
+ case 'html':
37
+ case 'xhtml':
38
+ case 'vue':
39
+ return ['html'];
40
+ case 'xml':
41
+ case 'svg':
42
+ case 'xsl':
43
+ return ['xml'];
44
+ default:
45
+ return ['other'];
46
+ }
47
+ }
48
+
49
+ /* What a position is written in. */
50
+ export interface TemplateContext {
51
+ readonly families: readonly TemplateLanguage[];
52
+ readonly place: TemplatePlace;
53
+ }
54
+
55
+ /* The families and the place at a position; an editor that cannot tell has the language of the document, in code. */
56
+ export function templateContextAt(language: EditorLanguage, position: EditorPosition): TemplateContext {
57
+ const place = language.editor.placeAt?.(position);
58
+
59
+ return place === undefined ? { families: familiesOf(language.languageId), place: 'code' } : { families: familiesOf(place.language), place: place.kind };
60
+ }
61
+
62
+ /* Whether a template shares a family with the context and applies to its place. */
63
+ export function templateApplies(template: LiveTemplate, context: TemplateContext): boolean {
64
+ return template.languages.some((family) => context.families.includes(family)) && (template.places ?? ['code']).includes(context.place);
65
+ }
@@ -0,0 +1,197 @@
1
+ import { BUILT_IN_LIVE_TEMPLATES, BUILT_IN_POSTFIX_TEMPLATES } from './template-builtins.ts';
2
+ import { checkSnippet } from './snippet.ts';
3
+
4
+ export { BUILT_IN_LIVE_TEMPLATES, BUILT_IN_POSTFIX_TEMPLATES };
5
+ export { type TemplatesFeature } from './live-templates.ts';
6
+
7
+ /* The families of language a live template belongs to; a family also matches the languages that build on it. */
8
+ export type TemplateLanguage = 'javascript' | 'typescript' | 'php' | 'css' | 'html' | 'xml' | 'other';
9
+
10
+ /* What the text at the caret is: code, a comment or a string. */
11
+ export type TemplatePlace = 'code' | 'comment' | 'string';
12
+
13
+ const MAX_KEY_LENGTH = 40;
14
+ const MAX_BODY_LENGTH = 20_000;
15
+ const POSTFIX_LANGUAGES: readonly string[] = ['javascript', 'typescript', 'php'];
16
+ const LANGUAGES: readonly string[] = ['javascript', 'typescript', 'php', 'css', 'html', 'xml', 'other'];
17
+ const PLACES: readonly string[] = ['code', 'comment', 'string'];
18
+
19
+ /* A text that Tab expands after its key, in the syntax of a snippet. */
20
+ export interface LiveTemplate {
21
+ /* Stable; a built-in one is `builtin.<language>.<key>`. */
22
+ readonly id: string;
23
+
24
+ /* One to 40 characters, none of them whitespace. */
25
+ readonly key: string;
26
+
27
+ /* Snippet syntax; the leading tabs of a line are one indentation unit each. */
28
+ readonly body: string;
29
+ readonly description?: string;
30
+ readonly languages: readonly TemplateLanguage[];
31
+
32
+ /* Where it applies; `['code']` without it. */
33
+ readonly places?: readonly TemplatePlace[];
34
+
35
+ /* What Insert Live Template sorts by; the first language without it. */
36
+ readonly group?: string;
37
+ }
38
+
39
+ /* A text that Tab expands after a dot and its key, around the expression before the dot. */
40
+ export interface PostfixTemplate {
41
+ readonly id: string;
42
+
43
+ /* Without the dot; identifier characters only. */
44
+ readonly key: string;
45
+
46
+ /* Snippet syntax, in which `$EXPR` is the expression. */
47
+ readonly body: string;
48
+ readonly description?: string;
49
+
50
+ /* Shown at the end of its row, as in `if (expr) {}`. */
51
+ readonly example?: string;
52
+ readonly languages: readonly ('javascript' | 'typescript' | 'php')[];
53
+
54
+ /* `any` without it. */
55
+ readonly expression?: 'any' | 'statement';
56
+ }
57
+
58
+ /* Each is on unless false. */
59
+ export interface TemplateSettings {
60
+ /* Live templates are rows of the suggestion list. */
61
+ readonly liveInCompletion?: boolean;
62
+
63
+ /* Tab after a dot and a key expands a postfix template. */
64
+ readonly postfix?: boolean;
65
+
66
+ /* Postfix templates are rows of the suggestion list. */
67
+ readonly postfixInCompletion?: boolean;
68
+ }
69
+
70
+ /* What Save as Live Template hands the host. */
71
+ export interface LiveTemplateDraft {
72
+ readonly body: string;
73
+ readonly languages: readonly TemplateLanguage[];
74
+ readonly places: readonly TemplatePlace[];
75
+ }
76
+
77
+ export interface TemplateRegistryOptions {
78
+ /* Whether the built-in templates are part of the registry; true by default. */
79
+ readonly builtins?: boolean;
80
+ }
81
+
82
+ export interface TemplateUpdate {
83
+ readonly live?: readonly LiveTemplate[];
84
+ readonly postfix?: readonly PostfixTemplate[];
85
+
86
+ /* Ids of the built-in templates that are off. */
87
+ readonly disabled?: readonly string[];
88
+ readonly settings?: TemplateSettings;
89
+ }
90
+
91
+ /*
92
+ * The templates the editors of a project know: the built-in ones and the ones a host sets. A host that keeps
93
+ * templates feeds them through `set`, and the editors read the registry on every use.
94
+ */
95
+ export class TemplateRegistry {
96
+ private readonly builtins: boolean;
97
+ private readonly listeners = new Set<() => void>();
98
+ private own: { live: readonly LiveTemplate[]; postfix: readonly PostfixTemplate[] } = { live: [], postfix: [] };
99
+ private off: ReadonlySet<string> = new Set();
100
+ private current: Required<TemplateSettings> = { liveInCompletion: true, postfix: true, postfixInCompletion: true };
101
+ private liveList: readonly LiveTemplate[];
102
+ private postfixList: readonly PostfixTemplate[];
103
+
104
+ constructor(options: TemplateRegistryOptions = {}) {
105
+ this.builtins = options.builtins !== false;
106
+ this.liveList = this.listed(BUILT_IN_LIVE_TEMPLATES, []);
107
+ this.postfixList = this.listed(BUILT_IN_POSTFIX_TEMPLATES, []);
108
+ }
109
+
110
+ /* The built-in templates that are not disabled, then the host's. */
111
+ get live(): readonly LiveTemplate[] {
112
+ return this.liveList;
113
+ }
114
+
115
+ get postfix(): readonly PostfixTemplate[] {
116
+ return this.postfixList;
117
+ }
118
+
119
+ get settings(): Required<TemplateSettings> {
120
+ return this.current;
121
+ }
122
+
123
+ /* Replaces what the update names. A template that fails `checkTemplate` is dropped; this never throws. */
124
+ set(update: TemplateUpdate): void {
125
+ this.own = {
126
+ live: update.live === undefined ? this.own.live : update.live.filter((template) => checkTemplate(template, 'live') === null),
127
+ postfix: update.postfix === undefined ? this.own.postfix : update.postfix.filter((template) => checkTemplate(template, 'postfix') === null)
128
+ };
129
+ this.off = update.disabled === undefined ? this.off : new Set(update.disabled);
130
+
131
+ if (update.settings !== undefined) {
132
+ this.current = {
133
+ liveInCompletion: update.settings.liveInCompletion !== false,
134
+ postfix: update.settings.postfix !== false,
135
+ postfixInCompletion: update.settings.postfixInCompletion !== false
136
+ };
137
+ }
138
+
139
+ this.liveList = this.listed(BUILT_IN_LIVE_TEMPLATES, this.own.live);
140
+ this.postfixList = this.listed(BUILT_IN_POSTFIX_TEMPLATES, this.own.postfix);
141
+
142
+ for (const listener of [...this.listeners]) {
143
+ listener();
144
+ }
145
+ }
146
+
147
+ /* Calls `listener` after every `set`. Returns the function that stops it. */
148
+ onChange(listener: () => void): () => void {
149
+ this.listeners.add(listener);
150
+
151
+ return () => {
152
+ this.listeners.delete(listener);
153
+ };
154
+ }
155
+
156
+ private listed<T extends { readonly id: string }>(builtins: readonly T[], own: readonly T[]): readonly T[] {
157
+ return [...(this.builtins ? builtins.filter((template) => !this.off.has(template.id)) : []), ...own];
158
+ }
159
+ }
160
+
161
+ /*
162
+ * Why a template cannot be used, in English for a host to show, or null. `kind` says which rules apply; without it a
163
+ * template with an `expression` or an `example` counts as a postfix one.
164
+ */
165
+ export function checkTemplate(template: LiveTemplate | PostfixTemplate, kind?: 'live' | 'postfix'): string | null {
166
+ const postfix = kind === undefined ? 'expression' in template || 'example' in template : kind === 'postfix';
167
+
168
+ if (template.id === '') {
169
+ return 'The template has no id.';
170
+ }
171
+
172
+ if (template.key === '' || template.key.length > MAX_KEY_LENGTH) {
173
+ return `The key is one to ${MAX_KEY_LENGTH} characters.`;
174
+ }
175
+
176
+ if (postfix ? !/^[\p{L}\p{N}_$]+$/u.test(template.key) : /\s/.test(template.key)) {
177
+ return postfix ? 'The key holds letters, digits, underscores and dollar signs only.' : 'The key holds no whitespace.';
178
+ }
179
+
180
+ if (template.body.trim() === '') {
181
+ return 'The text is empty.';
182
+ }
183
+
184
+ if (template.body.length > MAX_BODY_LENGTH) {
185
+ return `The text is longer than ${MAX_BODY_LENGTH} characters.`;
186
+ }
187
+
188
+ if (template.languages.length === 0 || template.languages.some((language) => !(postfix ? POSTFIX_LANGUAGES : LANGUAGES).includes(language))) {
189
+ return 'The languages are empty or not known.';
190
+ }
191
+
192
+ if (!postfix && (template as LiveTemplate).places?.some((place) => !PLACES.includes(place)) === true) {
193
+ return 'The places are not known.';
194
+ }
195
+
196
+ return checkSnippet(template.body);
197
+ }
package/src/testing.ts CHANGED
@@ -38,7 +38,24 @@ import {
38
38
  type SignatureHelpContext,
39
39
  type TextEdit,
40
40
  type WorkspaceEdit,
41
- type WorkspaceSymbolResult
41
+ type WorkspaceSymbolResult,
42
+ type Color,
43
+ type ColorInformation,
44
+ type ColorPresentation,
45
+ type DocumentLink,
46
+ type InlineCompletionContext,
47
+ type InlineCompletionResult,
48
+ type InlineValue,
49
+ type InlineValueContext,
50
+ type LinkedEditingRanges,
51
+ type Moniker,
52
+ type PreviousResultId,
53
+ type ServerMessage,
54
+ type ShowDocumentParams,
55
+ type ShowDocumentResult,
56
+ type TextDocumentSaveReason,
57
+ type TypeHierarchyItem,
58
+ type WorkspaceDiagnosticReport
42
59
  } from '@adecore/lsp';
43
60
 
44
61
  export { ManualTimers } from './timers.ts';
@@ -57,6 +74,8 @@ export class FakeLanguageService implements LanguageService {
57
74
  private readonly answers = new Map<string, (call: LanguageCall) => unknown>();
58
75
  private readonly diagnostics = new Set<(report: DiagnosticsReport) => void>();
59
76
  private readonly changes = new Set<(uri: string) => void>();
77
+ private readonly messages = new Set<(message: ServerMessage) => void>();
78
+ private showDocument: ((params: ShowDocumentParams) => Promise<ShowDocumentResult>) | null = null;
60
79
 
61
80
  respond(method: string, answer: (call: LanguageCall) => unknown, provider: ProviderOptions = {}): void {
62
81
  this.answers.set(method, answer);
@@ -271,4 +290,110 @@ export class FakeLanguageService implements LanguageService {
271
290
  resolveCodeLens(uri: string, lens: CodeLens, options: LanguageRequestOptions = {}): Promise<CodeLens> {
272
291
  return this.request<CodeLens>('codeLens/resolve', uri, lens, options);
273
292
  }
293
+
294
+ documentLinks(uri: string, options: LanguageRequestOptions = {}): Promise<DocumentLink[] | null> {
295
+ return this.request<DocumentLink[] | null>('textDocument/documentLink', uri, {}, options);
296
+ }
297
+
298
+ resolveDocumentLink(uri: string, link: DocumentLink, options: LanguageRequestOptions = {}): Promise<DocumentLink> {
299
+ return this.request<DocumentLink>('documentLink/resolve', uri, link, options);
300
+ }
301
+
302
+ documentColors(uri: string, options: LanguageRequestOptions = {}): Promise<ColorInformation[] | null> {
303
+ return this.request<ColorInformation[] | null>('textDocument/documentColor', uri, {}, options);
304
+ }
305
+
306
+ colorPresentations(uri: string, color: Color, range: Range, options: LanguageRequestOptions = {}): Promise<ColorPresentation[] | null> {
307
+ return this.request<ColorPresentation[] | null>('textDocument/colorPresentation', uri, { color, range }, options);
308
+ }
309
+
310
+ linkedEditingRanges(uri: string, position: Position, options: LanguageRequestOptions = {}): Promise<LinkedEditingRanges | null> {
311
+ return this.request<LinkedEditingRanges | null>('textDocument/linkedEditingRange', uri, { position }, options);
312
+ }
313
+
314
+ monikers(uri: string, position: Position, options: LanguageRequestOptions = {}): Promise<Moniker[] | null> {
315
+ return this.request<Moniker[] | null>('textDocument/moniker', uri, { position }, options);
316
+ }
317
+
318
+ prepareTypeHierarchy(uri: string, position: Position, options: LanguageRequestOptions = {}): Promise<TypeHierarchyItem[] | null> {
319
+ return this.request<TypeHierarchyItem[] | null>('textDocument/prepareTypeHierarchy', uri, { position }, options);
320
+ }
321
+
322
+ supertypes(uri: string, item: TypeHierarchyItem, options: LanguageRequestOptions = {}): Promise<TypeHierarchyItem[] | null> {
323
+ return this.request<TypeHierarchyItem[] | null>('typeHierarchy/supertypes', uri, { item }, options);
324
+ }
325
+
326
+ subtypes(uri: string, item: TypeHierarchyItem, options: LanguageRequestOptions = {}): Promise<TypeHierarchyItem[] | null> {
327
+ return this.request<TypeHierarchyItem[] | null>('typeHierarchy/subtypes', uri, { item }, options);
328
+ }
329
+
330
+ inlineValues(uri: string, range: Range, context: InlineValueContext, options: LanguageRequestOptions = {}): Promise<InlineValue[] | null> {
331
+ return this.request<InlineValue[] | null>('textDocument/inlineValue', uri, { range, context }, options);
332
+ }
333
+
334
+ inlineCompletions(
335
+ uri: string,
336
+ position: Position,
337
+ context: InlineCompletionContext,
338
+ options: LanguageRequestOptions = {}
339
+ ): Promise<InlineCompletionResult> {
340
+ return this.request<InlineCompletionResult>('textDocument/inlineCompletion', uri, { position, context }, options);
341
+ }
342
+
343
+ rangesFormatting(uri: string, ranges: readonly Range[], formatting: FormattingOptions, options: LanguageRequestOptions = {}): Promise<TextEdit[] | null> {
344
+ return this.request<TextEdit[] | null>('textDocument/rangesFormatting', uri, { ranges, options: formatting }, options);
345
+ }
346
+
347
+ async willSave(uri: string, reason: TextDocumentSaveReason = 1): Promise<void> {
348
+ this.calls.push({ method: 'textDocument/willSave', uri, params: { reason }, options: {} });
349
+ }
350
+
351
+ async willSaveWaitUntil(uri: string, reason: TextDocumentSaveReason = 1, options: LanguageRequestOptions = {}): Promise<TextEdit[] | null> {
352
+ return this.supports('textDocument/willSaveWaitUntil', uri)
353
+ ? this.request<TextEdit[] | null>('textDocument/willSaveWaitUntil', uri, { reason }, options)
354
+ : null;
355
+ }
356
+
357
+ workspaceDiagnostics(
358
+ uri: string,
359
+ previousResultIds: readonly PreviousResultId[] = [],
360
+ options: LanguageRequestOptions = {}
361
+ ): Promise<WorkspaceDiagnosticReport> {
362
+ return this.request<WorkspaceDiagnosticReport>('workspace/diagnostic', uri, { previousResultIds }, options);
363
+ }
364
+
365
+ async textDocumentContent(uri: string, options: LanguageRequestOptions = {}): Promise<string | null> {
366
+ if (!this.supports('workspace/textDocumentContent', uri)) {
367
+ return null;
368
+ }
369
+ return (await this.request<{ text: string } | null>('workspace/textDocumentContent', uri, { uri }, options))?.text ?? null;
370
+ }
371
+
372
+ onServerMessage(listener: (message: ServerMessage) => void): Disposable {
373
+ this.messages.add(listener);
374
+ return { dispose: () => this.messages.delete(listener) };
375
+ }
376
+
377
+ onShowDocument(handler: (params: ShowDocumentParams) => Promise<ShowDocumentResult>): Disposable {
378
+ this.showDocument = handler;
379
+ return {
380
+ dispose: () => {
381
+ if (this.showDocument === handler) {
382
+ this.showDocument = null;
383
+ }
384
+ }
385
+ };
386
+ }
387
+
388
+ /* Sends a message as a server would. */
389
+ message(message: ServerMessage): void {
390
+ for (const listener of this.messages) {
391
+ listener(message);
392
+ }
393
+ }
394
+
395
+ /* Asks the host to show a document as a server would; false when nothing handles it. */
396
+ async show(params: ShowDocumentParams): Promise<boolean> {
397
+ return (await this.showDocument?.(params))?.success ?? false;
398
+ }
274
399
  }