@singapore-editor/lsp-plugin 0.1.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 (185) hide show
  1. package/README.md +103 -0
  2. package/dist/bufferSync.d.ts +12 -0
  3. package/dist/bufferSync.d.ts.map +1 -0
  4. package/dist/bufferSync.js +35 -0
  5. package/dist/bufferSync.js.map +1 -0
  6. package/dist/codeActions.d.ts +83 -0
  7. package/dist/codeActions.d.ts.map +1 -0
  8. package/dist/codeActions.js +294 -0
  9. package/dist/codeActions.js.map +1 -0
  10. package/dist/colorAlpha.d.ts +4 -0
  11. package/dist/colorAlpha.d.ts.map +1 -0
  12. package/dist/colorAlpha.js +28 -0
  13. package/dist/colorAlpha.js.map +1 -0
  14. package/dist/completion.d.ts +124 -0
  15. package/dist/completion.d.ts.map +1 -0
  16. package/dist/completion.js +609 -0
  17. package/dist/completion.js.map +1 -0
  18. package/dist/completionCommit.d.ts +19 -0
  19. package/dist/completionCommit.d.ts.map +1 -0
  20. package/dist/completionCommit.js +57 -0
  21. package/dist/completionCommit.js.map +1 -0
  22. package/dist/completionController.d.ts +89 -0
  23. package/dist/completionController.d.ts.map +1 -0
  24. package/dist/completionController.js +376 -0
  25. package/dist/completionController.js.map +1 -0
  26. package/dist/completionProviders.d.ts +50 -0
  27. package/dist/completionProviders.d.ts.map +1 -0
  28. package/dist/completionProviders.js +64 -0
  29. package/dist/completionProviders.js.map +1 -0
  30. package/dist/connectionContext.d.ts +7 -0
  31. package/dist/connectionContext.d.ts.map +1 -0
  32. package/dist/definitionLinkController.d.ts +54 -0
  33. package/dist/definitionLinkController.d.ts.map +1 -0
  34. package/dist/definitionLinkController.js +221 -0
  35. package/dist/definitionLinkController.js.map +1 -0
  36. package/dist/definitionNavigation.d.ts +76 -0
  37. package/dist/definitionNavigation.d.ts.map +1 -0
  38. package/dist/definitionNavigation.js +143 -0
  39. package/dist/definitionNavigation.js.map +1 -0
  40. package/dist/diagnosticNotes.d.ts +7 -0
  41. package/dist/diagnosticNotes.d.ts.map +1 -0
  42. package/dist/diagnosticNotes.js +37 -0
  43. package/dist/diagnosticNotes.js.map +1 -0
  44. package/dist/diagnosticProjection.d.ts +31 -0
  45. package/dist/diagnosticProjection.d.ts.map +1 -0
  46. package/dist/diagnosticProjection.js +79 -0
  47. package/dist/diagnosticProjection.js.map +1 -0
  48. package/dist/diagnostics.d.ts +16 -0
  49. package/dist/diagnostics.d.ts.map +1 -0
  50. package/dist/diagnostics.js +94 -0
  51. package/dist/diagnostics.js.map +1 -0
  52. package/dist/diagnosticsPresenter.d.ts +64 -0
  53. package/dist/diagnosticsPresenter.d.ts.map +1 -0
  54. package/dist/diagnosticsPresenter.js +305 -0
  55. package/dist/diagnosticsPresenter.js.map +1 -0
  56. package/dist/document.d.ts +69 -0
  57. package/dist/document.d.ts.map +1 -0
  58. package/dist/document.js +190 -0
  59. package/dist/document.js.map +1 -0
  60. package/dist/documentHighlightController.d.ts +34 -0
  61. package/dist/documentHighlightController.d.ts.map +1 -0
  62. package/dist/documentHighlightController.js +101 -0
  63. package/dist/documentHighlightController.js.map +1 -0
  64. package/dist/documentSnapshot.d.ts +9 -0
  65. package/dist/documentSnapshot.d.ts.map +1 -0
  66. package/dist/documentSnapshot.js +31 -0
  67. package/dist/documentSnapshot.js.map +1 -0
  68. package/dist/documentSync.d.ts +53 -0
  69. package/dist/documentSync.d.ts.map +1 -0
  70. package/dist/documentSync.js +334 -0
  71. package/dist/documentSync.js.map +1 -0
  72. package/dist/documentSyncController.d.ts +26 -0
  73. package/dist/documentSyncController.d.ts.map +1 -0
  74. package/dist/documentSyncController.js +24 -0
  75. package/dist/documentSyncController.js.map +1 -0
  76. package/dist/formatOnType.d.ts +43 -0
  77. package/dist/formatOnType.d.ts.map +1 -0
  78. package/dist/formatOnType.js +198 -0
  79. package/dist/formatOnType.js.map +1 -0
  80. package/dist/formatting.d.ts +18 -0
  81. package/dist/formatting.d.ts.map +1 -0
  82. package/dist/formatting.js +52 -0
  83. package/dist/formatting.js.map +1 -0
  84. package/dist/fuzzyMatch.d.ts +22 -0
  85. package/dist/fuzzyMatch.d.ts.map +1 -0
  86. package/dist/fuzzyMatch.js +216 -0
  87. package/dist/fuzzyMatch.js.map +1 -0
  88. package/dist/hoverParticipant.d.ts +24 -0
  89. package/dist/hoverParticipant.d.ts.map +1 -0
  90. package/dist/hoverParticipant.js +156 -0
  91. package/dist/hoverParticipant.js.map +1 -0
  92. package/dist/index.d.ts +18 -0
  93. package/dist/index.d.ts.map +1 -0
  94. package/dist/index.js +17 -0
  95. package/dist/keyCommands.d.ts +19 -0
  96. package/dist/keyCommands.d.ts.map +1 -0
  97. package/dist/keyCommands.js +21 -0
  98. package/dist/keyCommands.js.map +1 -0
  99. package/dist/lane.d.ts +28 -0
  100. package/dist/lane.d.ts.map +1 -0
  101. package/dist/lane.js +138 -0
  102. package/dist/lane.js.map +1 -0
  103. package/dist/lspConnection.d.ts +94 -0
  104. package/dist/lspConnection.d.ts.map +1 -0
  105. package/dist/lspConnection.js +195 -0
  106. package/dist/lspConnection.js.map +1 -0
  107. package/dist/lspConnectionPool.d.ts +34 -0
  108. package/dist/lspConnectionPool.d.ts.map +1 -0
  109. package/dist/lspConnectionPool.js +255 -0
  110. package/dist/lspConnectionPool.js.map +1 -0
  111. package/dist/minimalEdits.d.ts +21 -0
  112. package/dist/minimalEdits.d.ts.map +1 -0
  113. package/dist/minimalEdits.js +181 -0
  114. package/dist/minimalEdits.js.map +1 -0
  115. package/dist/paths.d.ts +6 -0
  116. package/dist/paths.d.ts.map +1 -0
  117. package/dist/paths.js +38 -0
  118. package/dist/paths.js.map +1 -0
  119. package/dist/plugin.d.ts +100 -0
  120. package/dist/plugin.d.ts.map +1 -0
  121. package/dist/plugin.js +837 -0
  122. package/dist/plugin.js.map +1 -0
  123. package/dist/plugin.styles.d.ts +31 -0
  124. package/dist/plugin.styles.d.ts.map +1 -0
  125. package/dist/plugin.styles.js +102 -0
  126. package/dist/plugin.styles.js.map +1 -0
  127. package/dist/pluginTypes.d.ts +41 -0
  128. package/dist/pluginTypes.d.ts.map +1 -0
  129. package/dist/pluginTypes.js +0 -0
  130. package/dist/pullDiagnostics.d.ts +30 -0
  131. package/dist/pullDiagnostics.d.ts.map +1 -0
  132. package/dist/pullDiagnostics.js +94 -0
  133. package/dist/pullDiagnostics.js.map +1 -0
  134. package/dist/renameWidget.d.ts +30 -0
  135. package/dist/renameWidget.d.ts.map +1 -0
  136. package/dist/renameWidget.js +117 -0
  137. package/dist/renameWidget.js.map +1 -0
  138. package/dist/semanticTokenDecoder.d.ts +81 -0
  139. package/dist/semanticTokenDecoder.d.ts.map +1 -0
  140. package/dist/semanticTokenDecoder.js +132 -0
  141. package/dist/semanticTokenDecoder.js.map +1 -0
  142. package/dist/semanticTokens.d.ts +47 -0
  143. package/dist/semanticTokens.d.ts.map +1 -0
  144. package/dist/semanticTokens.js +68 -0
  145. package/dist/semanticTokens.js.map +1 -0
  146. package/dist/serverSet.d.ts +69 -0
  147. package/dist/serverSet.d.ts.map +1 -0
  148. package/dist/serverSet.js +331 -0
  149. package/dist/serverSet.js.map +1 -0
  150. package/dist/signatureHelp.d.ts +40 -0
  151. package/dist/signatureHelp.d.ts.map +1 -0
  152. package/dist/signatureHelp.js +108 -0
  153. package/dist/signatureHelp.js.map +1 -0
  154. package/dist/signatureHelpController.d.ts +52 -0
  155. package/dist/signatureHelpController.d.ts.map +1 -0
  156. package/dist/signatureHelpController.js +160 -0
  157. package/dist/signatureHelpController.js.map +1 -0
  158. package/dist/sourceText.d.ts +22 -0
  159. package/dist/sourceText.d.ts.map +1 -0
  160. package/dist/sourceText.js +70 -0
  161. package/dist/sourceText.js.map +1 -0
  162. package/dist/types.d.ts +238 -0
  163. package/dist/types.d.ts.map +1 -0
  164. package/dist/types.js +17 -0
  165. package/dist/types.js.map +1 -0
  166. package/dist/viewDocumentSnapshot.d.ts +5 -0
  167. package/dist/viewDocumentSnapshot.d.ts.map +1 -0
  168. package/dist/viewDocumentSnapshot.js +12 -0
  169. package/dist/viewDocumentSnapshot.js.map +1 -0
  170. package/dist/websocket.d.ts +6 -0
  171. package/dist/websocket.d.ts.map +1 -0
  172. package/dist/websocket.js +4 -0
  173. package/dist/workspaceEdit.d.ts +66 -0
  174. package/dist/workspaceEdit.d.ts.map +1 -0
  175. package/dist/workspaceEdit.js +276 -0
  176. package/dist/workspaceEdit.js.map +1 -0
  177. package/dist/workspaceEditProvenance.d.ts +5 -0
  178. package/dist/workspaceEditProvenance.d.ts.map +1 -0
  179. package/dist/workspaceEditProvenance.js +16 -0
  180. package/dist/workspaceEditProvenance.js.map +1 -0
  181. package/dist/workspaceTextEdits.d.ts +60 -0
  182. package/dist/workspaceTextEdits.d.ts.map +1 -0
  183. package/dist/workspaceTextEdits.js +395 -0
  184. package/dist/workspaceTextEdits.js.map +1 -0
  185. package/package.json +118 -0
@@ -0,0 +1,609 @@
1
+ import { rangeAroundOffset, textReadSnapshotOf } from "./sourceText.js";
2
+ import { fuzzyMatch, looseFuzzyMatch } from "./fuzzyMatch.js";
3
+ import { lspPositionToOffsetInSnapshot } from "@singapore-editor/lsp";
4
+ import { createEditorCapabilityToken, parseSnippet, snippetInitialSelection } from "@singapore-editor/core/extensions";
5
+ import { createAnchoredSurface } from "@singapore-editor/plugin-ui/anchored-surface";
6
+ //#region src/completion.ts
7
+ var LANGUAGE_SERVER_COMPLETION_EDIT_FEATURE_ID = "editor.lsp-plugin.completion-edit";
8
+ var COMPLETION_REQUEST_DEBOUNCE_MS = 80;
9
+ var LANGUAGE_SERVER_COMPLETION_EDIT_FEATURE = createEditorCapabilityToken(LANGUAGE_SERVER_COMPLETION_EDIT_FEATURE_ID);
10
+ var COMPLETION_WIDGET_GAP_PX = 4;
11
+ var COMPLETION_WIDGET_WIDTH_PX = 320;
12
+ var COMPLETION_WIDGET_MAX_HEIGHT_PX = 280;
13
+ var COMPLETION_WIDGET_MARGIN_PX = 12;
14
+ var COMPLETION_WIDGET_CLASS_NAMESPACE = "lsp-plugin";
15
+ var COMPLETION_TRIGGER_CHARACTERS = /* @__PURE__ */ new Set([
16
+ ".",
17
+ "\"",
18
+ "'",
19
+ "`",
20
+ "/",
21
+ "@",
22
+ "<",
23
+ "#"
24
+ ]);
25
+ var COMPLETION_THEME_VARIABLES = [
26
+ "--editor-background",
27
+ "--editor-popup-background",
28
+ "--editor-foreground",
29
+ "--editor-caret-color"
30
+ ];
31
+ function createCompletionWidgetController(options) {
32
+ const classNames = completionWidgetClassNames(options.classNamespace);
33
+ const element = createCompletionWidgetElement(options.document, classNames);
34
+ options.document.body.append(element);
35
+ const surface = createAnchoredSurface({
36
+ element,
37
+ anchorClassName: `${classNames.root}-anchor`,
38
+ preferredPlacement: "bottom",
39
+ alignment: "start",
40
+ gapPx: COMPLETION_WIDGET_GAP_PX,
41
+ viewportMarginPx: COMPLETION_WIDGET_MARGIN_PX,
42
+ maxHeightPx: COMPLETION_WIDGET_MAX_HEIGHT_PX
43
+ });
44
+ let items = [];
45
+ let selectedIndex = 0;
46
+ let disposed = false;
47
+ const render = () => {
48
+ element.replaceChildren();
49
+ for (let index = 0; index < items.length; index += 1) element.append(completionRowElement(element.ownerDocument, items[index], index, classNames));
50
+ syncSelectedRows(element, selectedIndex, classNames);
51
+ };
52
+ const show = (showOptions) => {
53
+ items = showOptions.items;
54
+ selectedIndex = clampIndex(showOptions.selectedIndex ?? 0, items.length);
55
+ syncEditorThemeVariables(element, options.themeSource);
56
+ render();
57
+ element.hidden = items.length === 0;
58
+ if (element.hidden) surface.release();
59
+ else surface.place(showOptions.anchor);
60
+ };
61
+ const reanchor = (anchor) => {
62
+ if (element.hidden) return;
63
+ surface.place(anchor);
64
+ };
65
+ const hide = () => {
66
+ element.hidden = true;
67
+ surface.release();
68
+ element.replaceChildren();
69
+ items = [];
70
+ selectedIndex = 0;
71
+ };
72
+ const moveSelection = (delta) => {
73
+ if (items.length === 0) return;
74
+ selectedIndex = wrapIndex(selectedIndex + delta, items.length);
75
+ syncSelectedRows(element, selectedIndex, classNames);
76
+ };
77
+ const selectedItem = () => items[selectedIndex] ?? null;
78
+ const handlePointerDown = (event) => {
79
+ const row = completionRowTarget(event.target, classNames);
80
+ if (!row) return;
81
+ event.preventDefault();
82
+ event.stopPropagation();
83
+ selectedIndex = rowIndex(row);
84
+ syncSelectedRows(element, selectedIndex, classNames);
85
+ options.onSelect(selectedIndex);
86
+ };
87
+ element.addEventListener("pointerdown", handlePointerDown);
88
+ const dispose = () => {
89
+ if (disposed) return;
90
+ disposed = true;
91
+ element.removeEventListener("pointerdown", handlePointerDown);
92
+ surface.dispose();
93
+ element.remove();
94
+ };
95
+ return {
96
+ show,
97
+ reanchor,
98
+ hide,
99
+ isVisible: () => !element.hidden,
100
+ containsTarget: (target) => target instanceof Node && element.contains(target),
101
+ moveSelection,
102
+ selectedItem,
103
+ dispose
104
+ };
105
+ }
106
+ function completionListResult(result) {
107
+ if (!result) return {
108
+ items: [],
109
+ isIncomplete: false
110
+ };
111
+ if (isCompletionList(result)) return {
112
+ items: result.items ?? [],
113
+ isIncomplete: result.isIncomplete === true
114
+ };
115
+ return {
116
+ items: result,
117
+ isIncomplete: false
118
+ };
119
+ }
120
+ function completionTriggerFromChange(change) {
121
+ if (!change || change.kind !== "edit") return null;
122
+ if (change.edits.length !== 1) return null;
123
+ const edit = change.edits[0];
124
+ if (!edit) return null;
125
+ const character = typedCharacter(edit);
126
+ if (!character) return null;
127
+ if (COMPLETION_TRIGGER_CHARACTERS.has(character)) return {
128
+ triggerKind: 2,
129
+ triggerCharacter: character
130
+ };
131
+ if (isIdentifierCharacter(character)) return { triggerKind: 1 };
132
+ return null;
133
+ }
134
+ /**
135
+ * The character the user typed, out of an edit that may have written more than it.
136
+ *
137
+ * A delimiter that closes itself arrives as the pair in a single insertion with the caret left
138
+ * between the halves — which is exactly the keystroke a path or a member list is wanted for, so
139
+ * reading only the one-character edits would leave the quote triggers unreachable while the editor
140
+ * completes them. An insertion of one trigger character twice over is the whole of that shape;
141
+ * anything else two characters wide is text arriving from somewhere other than the keyboard.
142
+ */
143
+ function typedCharacter(edit) {
144
+ if (edit.text.length === 1) return edit.text;
145
+ if (edit.from !== edit.to || edit.text.length !== 2) return null;
146
+ const opener = edit.text[0] ?? "";
147
+ if (opener !== edit.text[1]) return null;
148
+ return COMPLETION_TRIGGER_CHARACTERS.has(opener) ? opener : null;
149
+ }
150
+ function completionApplication(request, item) {
151
+ const primary = completionPrimaryEdit(request, item);
152
+ if (!primary) return null;
153
+ const additional = additionalCompletionEdits(request, item.additionalTextEdits ?? []);
154
+ const edits = [primary.edit, ...additional];
155
+ const head = completionSelectionHead(primary.edit, additional);
156
+ const snippet = primary.snippet;
157
+ if (!snippet) return {
158
+ edits,
159
+ selection: {
160
+ anchor: head,
161
+ head,
162
+ affinity: request.caretAffinity
163
+ }
164
+ };
165
+ const insertionOffset = head - primary.edit.text.length;
166
+ const first = snippetInitialSelection(snippet, insertionOffset);
167
+ return {
168
+ edits,
169
+ selection: {
170
+ anchor: first.start,
171
+ head: first.end,
172
+ affinity: request.caretAffinity
173
+ },
174
+ snippetStops: snippetStopRanges(snippet, insertionOffset)
175
+ };
176
+ }
177
+ /**
178
+ * Whether an item could be applied at all against the text it was answered for.
179
+ *
180
+ * The ranges an item carries are positions in that text, and one that spans lines or that the caret
181
+ * was never inside describes a document this client did not ask about. Nothing downstream can rescue
182
+ * such an item, so a row for it would be one the reader can press Enter on and get nothing for.
183
+ */
184
+ function completionItemApplies(request, item) {
185
+ const textEdit = completionTextEdit(item);
186
+ if (!textEdit) return true;
187
+ return completionEditRange(request, textEdit.range) !== null;
188
+ }
189
+ function completionAnchorRange(length, offset) {
190
+ if (length === 0) return {
191
+ start: 0,
192
+ end: 0
193
+ };
194
+ const end = Math.max(1, Math.min(offset, length));
195
+ return {
196
+ start: end - 1,
197
+ end
198
+ };
199
+ }
200
+ function createCompletionEditFeature(context, timingName) {
201
+ return { applyCompletion(application) {
202
+ if (!context.hasDocument()) return false;
203
+ context.applyEdits(application.edits, timingName, application.selection);
204
+ if (application.snippetStops) context.startSnippetSession(application.snippetStops);
205
+ context.focusEditor();
206
+ return true;
207
+ } };
208
+ }
209
+ function createCompletionWidgetElement(document, classNames) {
210
+ const element = document.createElement("div");
211
+ element.className = classNames.root;
212
+ element.hidden = true;
213
+ element.setAttribute("role", "listbox");
214
+ Object.assign(element.style, {
215
+ zIndex: "1001",
216
+ width: `${COMPLETION_WIDGET_WIDTH_PX}px`,
217
+ maxWidth: `calc(100vw - 24px)`,
218
+ maxHeight: `${COMPLETION_WIDGET_MAX_HEIGHT_PX}px`,
219
+ overflowX: "hidden",
220
+ overflowY: "auto",
221
+ padding: "3px",
222
+ border: "1px solid color-mix(in srgb, var(--editor-foreground, #d4d4d8) 22%, transparent)",
223
+ borderRadius: "6px",
224
+ boxSizing: "border-box",
225
+ background: "var(--editor-popup-background, color-mix(in srgb, var(--editor-background, #18181b) 96%, var(--editor-foreground, #e4e4e7) 4%))",
226
+ color: "var(--editor-foreground, #e4e4e7)",
227
+ boxShadow: "0 12px 30px color-mix(in srgb, var(--editor-background, #000000) 60%, transparent)",
228
+ font: "12px/1.35 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
229
+ userSelect: "none",
230
+ scrollbarGutter: "stable"
231
+ });
232
+ return element;
233
+ }
234
+ function completionRowElement(document, item, index, classNames) {
235
+ const row = document.createElement("div");
236
+ row.className = classNames.item;
237
+ row.dataset.index = String(index);
238
+ row.setAttribute("role", "option");
239
+ Object.assign(row.style, {
240
+ display: "grid",
241
+ gridTemplateColumns: "44px minmax(0, 1fr) auto",
242
+ alignItems: "center",
243
+ gap: "8px",
244
+ minHeight: "24px",
245
+ padding: "2px 7px",
246
+ borderRadius: "4px",
247
+ cursor: "default",
248
+ whiteSpace: "nowrap"
249
+ });
250
+ row.append(completionKindElement(document, item.kind), completionLabelElement(document, item, classNames), completionDetailElement(document, item));
251
+ return row;
252
+ }
253
+ function completionKindElement(document, kind) {
254
+ const element = document.createElement("span");
255
+ element.textContent = completionKindLabel(kind);
256
+ Object.assign(element.style, {
257
+ minWidth: "0",
258
+ overflow: "hidden",
259
+ color: "color-mix(in srgb, var(--editor-foreground, #e4e4e7) 62%, transparent)",
260
+ fontSize: "10px",
261
+ textTransform: "uppercase"
262
+ });
263
+ return element;
264
+ }
265
+ function completionLabelElement(document, item, classNames) {
266
+ const element = document.createElement("span");
267
+ Object.assign(element.style, {
268
+ minWidth: "0",
269
+ overflow: "hidden",
270
+ textOverflow: "ellipsis"
271
+ });
272
+ appendCompletionLabel(element, item.label, completionLabelMatches.get(item) ?? [], classNames);
273
+ const detail = item.labelDetails?.detail;
274
+ if (detail) element.append(detail);
275
+ return element;
276
+ }
277
+ /**
278
+ * The label, with each run of characters the filter matched marked up.
279
+ *
280
+ * A list that reorders itself as the user types owes them the reason it did: with nothing marked, an
281
+ * entry that outranks the one above it looks arbitrary rather than earned.
282
+ */
283
+ function appendCompletionLabel(element, label, positions, classNames) {
284
+ let plainStart = 0;
285
+ let cursor = 0;
286
+ while (cursor < positions.length) {
287
+ const start = positions[cursor] ?? 0;
288
+ let end = start + 1;
289
+ while (positions[cursor + 1] === end) {
290
+ end += 1;
291
+ cursor += 1;
292
+ }
293
+ cursor += 1;
294
+ if (start > plainStart) element.append(label.slice(plainStart, start));
295
+ element.append(completionMatchElement(element.ownerDocument, label.slice(start, end), classNames));
296
+ plainStart = end;
297
+ }
298
+ if (plainStart < label.length) element.append(label.slice(plainStart));
299
+ }
300
+ function completionMatchElement(document, text, classNames) {
301
+ const element = document.createElement("span");
302
+ element.className = classNames.match;
303
+ element.textContent = text;
304
+ Object.assign(element.style, {
305
+ color: "color-mix(in srgb, var(--editor-caret-color, #60a5fa) 80%, var(--editor-foreground, #e4e4e7))",
306
+ fontWeight: "600"
307
+ });
308
+ return element;
309
+ }
310
+ function completionDetailElement(document, item) {
311
+ const element = document.createElement("span");
312
+ element.textContent = item.labelDetails?.description ?? item.detail ?? "";
313
+ Object.assign(element.style, {
314
+ minWidth: "0",
315
+ maxWidth: "140px",
316
+ overflow: "hidden",
317
+ color: "color-mix(in srgb, var(--editor-foreground, #e4e4e7) 58%, transparent)",
318
+ textOverflow: "ellipsis"
319
+ });
320
+ return element;
321
+ }
322
+ function syncSelectedRows(element, selectedIndex, classNames) {
323
+ const rows = element.querySelectorAll(`.${classNames.item}`);
324
+ for (const row of rows) {
325
+ const selected = rowIndex(row) === selectedIndex;
326
+ syncSelectedRow(row, selected);
327
+ if (selected) scrollRowIntoView(element, row);
328
+ }
329
+ }
330
+ /**
331
+ * Keeps the focused row inside the box.
332
+ *
333
+ * A hundred rows are offered in a box a dozen of them fit in, and the widget never takes the focus,
334
+ * so there is no scrollport the browser is moving on anyone's behalf: walking off the bottom edge —
335
+ * or wrapping to the end with a single ArrowUp — would leave a list with no highlight in it while
336
+ * Enter commits a row that was never on screen.
337
+ */
338
+ function scrollRowIntoView(element, row) {
339
+ const top = row.offsetTop;
340
+ const bottom = top + row.offsetHeight;
341
+ if (top < element.scrollTop) element.scrollTop = top;
342
+ else if (bottom > element.scrollTop + element.clientHeight) element.scrollTop = bottom - element.clientHeight;
343
+ }
344
+ function syncSelectedRow(row, selected) {
345
+ row.setAttribute("aria-selected", selected ? "true" : "false");
346
+ row.style.background = selected ? "color-mix(in srgb, var(--editor-caret-color, #60a5fa) 26%, transparent)" : "transparent";
347
+ }
348
+ function syncEditorThemeVariables(element, source) {
349
+ const style = source.ownerDocument.defaultView?.getComputedStyle(source);
350
+ if (!style) return;
351
+ for (const variable of COMPLETION_THEME_VARIABLES) {
352
+ const value = style.getPropertyValue(variable);
353
+ if (value) {
354
+ element.style.setProperty(variable, value);
355
+ continue;
356
+ }
357
+ element.style.removeProperty(variable);
358
+ }
359
+ }
360
+ function completionPrimaryEdit(request, item) {
361
+ const textEdit = completionTextEdit(item);
362
+ const range = textEdit ? completionEditRange(request, textEdit.range) : defaultCompletionReplacementRange(request.document, request.offset);
363
+ if (!range) return null;
364
+ const source = textEdit ? textEdit.newText : item.insertText ?? item.label;
365
+ const snippet = item.insertTextFormat === 2 ? parseSnippet(source, { insertion: {
366
+ textSnapshot: textReadSnapshotOf(request.document),
367
+ offset: range.start
368
+ } }) : null;
369
+ return {
370
+ snippet,
371
+ edit: {
372
+ from: range.start,
373
+ to: Math.max(range.start, range.end + request.caretOffset - request.offset),
374
+ text: snippet?.text ?? source
375
+ }
376
+ };
377
+ }
378
+ function completionTextEdit(item) {
379
+ const textEdit = item.textEdit;
380
+ if (!textEdit) return null;
381
+ if ("range" in textEdit) return textEdit;
382
+ return {
383
+ range: textEdit.replace,
384
+ newText: textEdit.newText
385
+ };
386
+ }
387
+ /**
388
+ * The item's replacement range in offsets, or null when it describes a document we never asked
389
+ * about: a range spanning lines, or one the caret was not inside, leaves nothing for the typed
390
+ * characters to be measured against.
391
+ */
392
+ function completionEditRange(request, range) {
393
+ if (range.start.line !== range.end.line) return null;
394
+ const start = lspPositionToOffsetInSnapshot(request.document, range.start);
395
+ const end = lspPositionToOffsetInSnapshot(request.document, range.end);
396
+ if (start > request.offset || end < request.offset) return null;
397
+ return {
398
+ start,
399
+ end
400
+ };
401
+ }
402
+ function additionalCompletionEdits(request, edits) {
403
+ return edits.map((edit) => {
404
+ const from = lspPositionToOffsetInSnapshot(request.document, edit.range.start);
405
+ const to = lspPositionToOffsetInSnapshot(request.document, edit.range.end);
406
+ const shift = from < request.offset ? 0 : request.caretOffset - request.offset;
407
+ return {
408
+ from: from + shift,
409
+ to: to + shift,
410
+ text: edit.newText
411
+ };
412
+ });
413
+ }
414
+ function completionSelectionHead(primary, additional) {
415
+ let head = primary.from + primary.text.length;
416
+ for (const edit of additional) {
417
+ if (edit.to > primary.from) continue;
418
+ head += edit.text.length - (edit.to - edit.from);
419
+ }
420
+ return Math.max(0, head);
421
+ }
422
+ function defaultCompletionReplacementRange(document, offset) {
423
+ const clamped = Math.max(0, Math.min(offset, document.textSnapshot.length));
424
+ return rangeAroundOffset(document, clamped, identifierRunAt);
425
+ }
426
+ function identifierRunAt(text, offset) {
427
+ let start = offset;
428
+ while (start > 0 && isIdentifierCharacter(text[start - 1] ?? "")) start -= 1;
429
+ let end = offset;
430
+ while (end < text.length && isIdentifierCharacter(text[end] ?? "")) end += 1;
431
+ return {
432
+ start,
433
+ end
434
+ };
435
+ }
436
+ /** Every tab stop in visit order and in document offsets, or undefined when there are none. */
437
+ function snippetStopRanges(snippet, insertionOffset) {
438
+ const stops = snippet.stops.flatMap((stop) => {
439
+ const caret = stop.ranges.findIndex((candidate) => !candidate.transform);
440
+ const range = stop.ranges[caret];
441
+ if (!range) return [];
442
+ const mirrors = stop.ranges.flatMap((mirror, index) => index === caret ? [] : [shiftedSnippetRange(mirror, insertionOffset)]);
443
+ const shifted = shiftedSnippetRange(range, insertionOffset);
444
+ return [mirrors.length > 0 ? {
445
+ ...shifted,
446
+ mirrors
447
+ } : shifted];
448
+ });
449
+ return stops.length > 0 ? stops : void 0;
450
+ }
451
+ function shiftedSnippetRange(range, insertionOffset) {
452
+ const shifted = {
453
+ end: insertionOffset + range.end,
454
+ start: insertionOffset + range.start
455
+ };
456
+ return range.transform ? {
457
+ ...shifted,
458
+ transform: range.transform
459
+ } : shifted;
460
+ }
461
+ function completionRowTarget(target, classNames) {
462
+ if (!(target instanceof Element)) return null;
463
+ return target.closest(`.${classNames.item}`);
464
+ }
465
+ function completionWidgetClassNames(namespace = COMPLETION_WIDGET_CLASS_NAMESPACE) {
466
+ const root = `editor-${namespace}-completion`;
467
+ return {
468
+ root,
469
+ item: `${root}-item`,
470
+ match: `${root}-match`
471
+ };
472
+ }
473
+ function rowIndex(row) {
474
+ const parsed = Number(row.dataset.index);
475
+ return Number.isFinite(parsed) ? parsed : 0;
476
+ }
477
+ function clampIndex(index, length) {
478
+ if (length <= 0) return 0;
479
+ return Math.min(Math.max(0, index), length - 1);
480
+ }
481
+ function wrapIndex(index, length) {
482
+ if (length <= 0) return 0;
483
+ return (index % length + length) % length;
484
+ }
485
+ function completionKindLabel(kind) {
486
+ if (kind === 2) return "meth";
487
+ if (kind === 3) return "fn";
488
+ if (kind === 5) return "field";
489
+ if (kind === 6) return "var";
490
+ if (kind === 7) return "class";
491
+ if (kind === 8) return "iface";
492
+ if (kind === 9) return "mod";
493
+ if (kind === 10) return "prop";
494
+ if (kind === 13) return "enum";
495
+ if (kind === 14) return "key";
496
+ if (kind === 20) return "member";
497
+ return "text";
498
+ }
499
+ function isIdentifierCharacter(value) {
500
+ return /^[A-Za-z0-9_$]$/.test(value);
501
+ }
502
+ function isCompletionList(value) {
503
+ return !Array.isArray(value);
504
+ }
505
+ /**
506
+ * Whether an accepted item still needs a `completionItem/resolve` round-trip.
507
+ *
508
+ * Servers routinely send a list whose items carry only a label, deferring documentation and — the
509
+ * one that matters — the import edit until resolve. Applying such an item directly is what silently
510
+ * drops auto-imports.
511
+ */
512
+ function completionNeedsResolve(item, serverCapabilities) {
513
+ if (item.additionalTextEdits !== void 0) return false;
514
+ return Boolean(serverCapabilities?.completionProvider?.resolveProvider);
515
+ }
516
+ /**
517
+ * Word being typed at `offset`, which is what the list should be filtered against. The server's
518
+ * answer is for the moment the request was sent; the user has usually typed more since.
519
+ */
520
+ function completionPrefix(text, offset) {
521
+ let start = Math.max(0, Math.min(offset, text.length));
522
+ while (start > 0 && isIdentifierCharacter(text[start - 1] ?? "")) start -= 1;
523
+ return text.slice(start, Math.max(0, Math.min(offset, text.length)));
524
+ }
525
+ /**
526
+ * Where each ranked item's label matched, kept beside the list instead of inside it.
527
+ *
528
+ * The items in a list are the server's own objects, and one of them goes back to the server on a
529
+ * resolve; the runs to mark are this client's reading of them. The widget is handed the items and
530
+ * nothing else, so this is where it reads that from.
531
+ */
532
+ var completionLabelMatches = /* @__PURE__ */ new WeakMap();
533
+ /**
534
+ * Each server list's last filter, hung on the list itself.
535
+ *
536
+ * A narrowing is an answer about the one list it was computed from, and that list is held by a
537
+ * single session in a single editor. Here it is out of reach of a second editor filtering its own
538
+ * list, and it is released along with the session holding the list; in a variable of its own it
539
+ * would be neither.
540
+ */
541
+ var completionRankings = /* @__PURE__ */ new WeakMap();
542
+ /**
543
+ * Filters and orders a completion list against what has been typed.
544
+ *
545
+ * An item earns its place by where the typed characters landed in it, so a word start, a camel-case
546
+ * hump and the far side of a separator all outrank the same letters found in the middle of a word.
547
+ * Ties fall back to the server's own `sortText`, which is how a server expresses relevance it knows
548
+ * and the client does not.
549
+ */
550
+ function rankCompletionItems(items, prefix) {
551
+ if (prefix.length === 0) {
552
+ completionRankings.delete(items);
553
+ for (const item of items) completionLabelMatches.delete(item);
554
+ return items;
555
+ }
556
+ const scored = [];
557
+ for (const item of narrowingSource(items, prefix)) {
558
+ const match = fuzzyMatch(prefix, item.filterText ?? item.label);
559
+ if (!match) continue;
560
+ completionLabelMatches.set(item, completionLabelPositions(item, prefix, match));
561
+ scored.push({
562
+ item,
563
+ score: match.score
564
+ });
565
+ }
566
+ const ranked = scored.sort((left, right) => right.score - left.score || compareCompletionOrder(left.item, right.item)).map((entry) => entry.item);
567
+ completionRankings.set(items, {
568
+ prefix,
569
+ ranked
570
+ });
571
+ return ranked;
572
+ }
573
+ /**
574
+ * The items worth scoring for this word: the ones that survived the shorter word, when this word is
575
+ * that word plus what has been typed since.
576
+ *
577
+ * Typing a character can only narrow, never widen — no item the longer word matches was passed over
578
+ * by the shorter one — so a server's list is scored in full once for the word it answered, and for
579
+ * every keystroke the list stays up after that, only where it is still standing.
580
+ */
581
+ function narrowingSource(items, prefix) {
582
+ const previous = completionRankings.get(items);
583
+ if (!previous) return items;
584
+ if (prefix.length <= previous.prefix.length || !prefix.startsWith(previous.prefix)) return items;
585
+ return previous.ranked;
586
+ }
587
+ /**
588
+ * The label positions to mark for an item, given the match its filter text earned.
589
+ *
590
+ * An item may be filtered by one string and displayed as another, and positions in the string that
591
+ * was matched address nothing in the string on screen. Such a label is matched again on its own
592
+ * terms, and loosely: part of what was typed has no reason to appear in it at all, and marking the
593
+ * part that does beats marking nothing. The rank stays the one the filter text earned.
594
+ */
595
+ function completionLabelPositions(item, prefix, match) {
596
+ const filterText = item.filterText;
597
+ if (filterText === void 0) return match.positions;
598
+ if (filterText.toLowerCase() === item.label.toLowerCase()) return match.positions;
599
+ return looseFuzzyMatch(prefix, item.label)?.positions ?? [];
600
+ }
601
+ function compareCompletionOrder(left, right) {
602
+ const leftKey = left.sortText ?? left.label;
603
+ const rightKey = right.sortText ?? right.label;
604
+ return leftKey.localeCompare(rightKey);
605
+ }
606
+ //#endregion
607
+ export { COMPLETION_REQUEST_DEBOUNCE_MS, LANGUAGE_SERVER_COMPLETION_EDIT_FEATURE, LANGUAGE_SERVER_COMPLETION_EDIT_FEATURE_ID, completionAnchorRange, completionApplication, completionItemApplies, completionListResult, completionNeedsResolve, completionPrefix, completionTriggerFromChange, createCompletionEditFeature, createCompletionWidgetController, rankCompletionItems };
608
+
609
+ //# sourceMappingURL=completion.js.map