@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,305 @@
1
+ import { viewDocumentSnapshot } from "./viewDocumentSnapshot.js";
2
+ import { combineDiagnosticsFreshness, diagnosticHighlightGroups, summarizeDiagnostics } from "./diagnostics.js";
3
+ import { readColorAlpha } from "./colorAlpha.js";
4
+ import { DEPRECATED_DIAGNOSTIC_STYLE, DIAGNOSTIC_MARKER_COLORS, DIAGNOSTIC_STYLES, UNNECESSARY_DIAGNOSTIC_OPACITY } from "./plugin.styles.js";
5
+ import { lspPositionToOffsetInSnapshot } from "@singapore-editor/lsp";
6
+ import { EDITOR_MINIMAP_FEATURE } from "@singapore-editor/core/extensions";
7
+ import { editorThemesEqual } from "@singapore-editor/core/rendering";
8
+ //#region src/diagnosticsPresenter.ts
9
+ var LSP_DIAGNOSTIC_ERROR = 1;
10
+ var LSP_DIAGNOSTIC_WARNING = 2;
11
+ var LSP_DIAGNOSTIC_INFORMATION = 3;
12
+ var LSP_DIAGNOSTIC_HINT = 4;
13
+ var DIAGNOSTIC_LAYERS = [
14
+ "error",
15
+ "warning",
16
+ "information",
17
+ "hint",
18
+ "deprecated",
19
+ "unnecessary"
20
+ ];
21
+ var DIAGNOSTIC_LAYER_STYLES = {
22
+ ...DIAGNOSTIC_STYLES,
23
+ deprecated: DEPRECATED_DIAGNOSTIC_STYLE,
24
+ unnecessary: { overlay: { dim: 1 } }
25
+ };
26
+ var DIAGNOSTIC_MINIMAP_Z_INDEX = {
27
+ error: 40,
28
+ warning: 30,
29
+ information: 20,
30
+ hint: 10
31
+ };
32
+ var DiagnosticsPresenter = class {
33
+ context;
34
+ options;
35
+ highlightNames;
36
+ markerClaim = null;
37
+ unnecessaryDimCache = null;
38
+ constructor(context, prefix, options) {
39
+ this.context = context;
40
+ this.options = options;
41
+ this.highlightNames = createHighlightNames(prefix, options.highlightNameNamespace);
42
+ }
43
+ render(document, diagnostics) {
44
+ this.renderHighlights(document, diagnostics);
45
+ this.renderMinimapMarkers(diagnostics);
46
+ }
47
+ invalidateTheme() {
48
+ this.unnecessaryDimCache = null;
49
+ }
50
+ clear() {
51
+ this.releaseMarkerClaim();
52
+ this.clearMinimapMarkers();
53
+ for (const name of Object.values(this.highlightNames)) this.context.clearRangeHighlight(name);
54
+ }
55
+ publishSummary(uri, version, diagnostics) {
56
+ this.options.onDiagnostics?.(summarizeDiagnostics(uri, version, diagnostics));
57
+ }
58
+ moveMarker(active, diagnostics, direction) {
59
+ if (!active) return false;
60
+ const selection = this.context.getSnapshot().selections[0];
61
+ if (!selection) return false;
62
+ const target = diagnosticMarkerTarget(active, diagnostics, selection.headOffset, direction);
63
+ if (!target) return false;
64
+ const timingName = `${this.options.markerTimingNamePrefix}.${direction}`;
65
+ this.context.setSelection(target.range.start, target.range.end, timingName, { revealOffset: target.range.start });
66
+ this.context.focusEditor();
67
+ this.publishMarker(active, target, direction);
68
+ return true;
69
+ }
70
+ publishMarker(active, target, direction) {
71
+ this.releaseMarkerClaim();
72
+ if (!this.options.onDidNavigateDiagnostic) return;
73
+ try {
74
+ const claim = this.options.onDidNavigateDiagnostic({
75
+ anchor: markerAnchor(target.range),
76
+ diagnostic: target.diagnostic,
77
+ direction,
78
+ documentUri: active.uri,
79
+ textVersion: active.textVersion
80
+ });
81
+ if (claim.kind === "claimed") this.markerClaim = claim;
82
+ } catch (error) {
83
+ this.options.onError?.(error);
84
+ }
85
+ }
86
+ releaseMarkerClaim() {
87
+ const claim = this.markerClaim;
88
+ this.markerClaim = null;
89
+ claim?.dispose();
90
+ }
91
+ renderHighlights(document, diagnostics) {
92
+ const groups = diagnosticHighlightGroups(document, diagnostics);
93
+ for (const layer of DIAGNOSTIC_LAYERS) this.context.setRangeHighlight(this.highlightNames[layer], groups[layer], layer === "unnecessary" && groups.unnecessary.length > 0 ? { overlay: { dim: this.unnecessaryDim() } } : DIAGNOSTIC_LAYER_STYLES[layer]);
94
+ }
95
+ unnecessaryDim() {
96
+ this.unnecessaryDimCache ??= readColorAlpha(this.context.scrollElement, UNNECESSARY_DIAGNOSTIC_OPACITY);
97
+ return this.unnecessaryDimCache;
98
+ }
99
+ renderMinimapMarkers(diagnostics) {
100
+ const minimap = this.minimapFeature();
101
+ if (!minimap) return;
102
+ minimap.setDecorations(this.options.minimapSourceId, diagnosticMinimapDecorations(this.context.getSnapshot().lineCount, diagnostics));
103
+ }
104
+ clearMinimapMarkers() {
105
+ this.minimapFeature()?.clearDecorations(this.options.minimapSourceId);
106
+ }
107
+ minimapFeature() {
108
+ return this.context.getFeature(EDITOR_MINIMAP_FEATURE);
109
+ }
110
+ };
111
+ var CompositeDiagnosticsPresenter = class {
112
+ presenter;
113
+ laneIds;
114
+ onDiagnostics;
115
+ theme = null;
116
+ updateTheme(theme) {
117
+ if (editorThemesEqual(this.theme, theme)) return;
118
+ this.theme = theme;
119
+ this.presenter.invalidateTheme();
120
+ this.renderCombined();
121
+ }
122
+ #batches = /* @__PURE__ */ new Map();
123
+ #freshness = /* @__PURE__ */ new Map();
124
+ #diagnostics = [];
125
+ constructor(presenter, laneIds, onDiagnostics) {
126
+ this.presenter = presenter;
127
+ this.laneIds = laneIds;
128
+ this.onDiagnostics = onDiagnostics;
129
+ }
130
+ get diagnostics() {
131
+ return this.#diagnostics;
132
+ }
133
+ forLane(laneId, onDiagnostics) {
134
+ return {
135
+ clear: () => {
136
+ const current = this.#batches.get(laneId);
137
+ if (!current) return;
138
+ this.#batches.set(laneId, {
139
+ ...current,
140
+ diagnostics: []
141
+ });
142
+ this.refreshDiagnostics();
143
+ this.renderCombined();
144
+ },
145
+ render: (document, diagnostics) => {
146
+ const current = this.#batches.get(laneId);
147
+ this.#batches.set(laneId, {
148
+ diagnostics,
149
+ document,
150
+ uri: current?.uri ?? null,
151
+ version: current?.version ?? null
152
+ });
153
+ this.refreshDiagnostics();
154
+ this.renderCombined();
155
+ },
156
+ publishSummary: (uri, version, diagnostics, freshness) => {
157
+ this.#freshness.set(laneId, freshness);
158
+ const current = this.#batches.get(laneId);
159
+ if (!current && diagnostics.length === 0) {
160
+ onDiagnostics?.(summarizeDiagnostics(uri, version, diagnostics, freshness));
161
+ this.publishCombinedSummary();
162
+ return;
163
+ }
164
+ this.#batches.set(laneId, {
165
+ diagnostics,
166
+ document: current?.document ?? null,
167
+ uri,
168
+ version
169
+ });
170
+ this.refreshDiagnostics();
171
+ onDiagnostics?.(summarizeDiagnostics(uri, version, diagnostics, freshness));
172
+ this.publishCombinedSummary();
173
+ }
174
+ };
175
+ }
176
+ clear() {
177
+ this.#batches.clear();
178
+ this.#freshness.clear();
179
+ this.refreshDiagnostics();
180
+ this.presenter.clear();
181
+ }
182
+ moveMarker(active, direction) {
183
+ return this.presenter.moveMarker(active, this.diagnostics, direction);
184
+ }
185
+ renderCombined() {
186
+ const document = this.currentDocument();
187
+ if (document === null) {
188
+ this.presenter.clear();
189
+ return;
190
+ }
191
+ this.presenter.render(document, this.diagnostics);
192
+ }
193
+ refreshDiagnostics() {
194
+ this.#diagnostics = this.laneIds.flatMap((id) => this.#batches.get(id)?.diagnostics ?? []);
195
+ }
196
+ publishCombinedSummary() {
197
+ const current = this.currentBatch();
198
+ const freshness = combineDiagnosticsFreshness(this.laneIds.flatMap((id) => this.#freshness.get(id) ?? []));
199
+ this.onDiagnostics?.(summarizeDiagnostics(current?.uri ?? null, current?.version ?? null, this.diagnostics, freshness));
200
+ }
201
+ currentDocument() {
202
+ for (const id of this.laneIds) {
203
+ const batch = this.#batches.get(id);
204
+ if (batch?.document && batch.diagnostics.length > 0) return batch.document;
205
+ }
206
+ for (const id of this.laneIds) {
207
+ const batch = this.#batches.get(id);
208
+ if (batch?.document) return batch.document;
209
+ }
210
+ return null;
211
+ }
212
+ currentBatch() {
213
+ for (const id of this.laneIds) {
214
+ const batch = this.#batches.get(id);
215
+ if (batch && batch.diagnostics.length > 0) return batch;
216
+ }
217
+ for (const id of this.laneIds) {
218
+ const batch = this.#batches.get(id);
219
+ if (batch) return batch;
220
+ }
221
+ return null;
222
+ }
223
+ };
224
+ function createHighlightNames(prefix, namespace) {
225
+ return {
226
+ error: `${prefix}-${namespace}-error`,
227
+ warning: `${prefix}-${namespace}-warning`,
228
+ information: `${prefix}-${namespace}-information`,
229
+ hint: `${prefix}-${namespace}-hint`,
230
+ deprecated: `${prefix}-${namespace}-deprecated`,
231
+ unnecessary: `${prefix}-${namespace}-unnecessary`
232
+ };
233
+ }
234
+ function diagnosticMinimapDecorations(lineCount, diagnostics) {
235
+ return diagnostics.flatMap((diagnostic) => diagnosticMinimapDecoration(lineCount, diagnostic));
236
+ }
237
+ function diagnosticMinimapDecoration(lineCount, diagnostic) {
238
+ if (lineCount <= 0) return [];
239
+ const severity = minimapSeverityForDiagnostic(diagnostic);
240
+ const startLineNumber = clampLineNumber(diagnostic.range.start.line + 1, lineCount);
241
+ return [{
242
+ startLineNumber,
243
+ startColumn: 1,
244
+ endLineNumber: Math.max(startLineNumber, clampLineNumber(diagnosticEndLineNumber(diagnostic), lineCount)),
245
+ endColumn: 1,
246
+ color: DIAGNOSTIC_MARKER_COLORS[severity],
247
+ position: "inline",
248
+ zIndex: DIAGNOSTIC_MINIMAP_Z_INDEX[severity]
249
+ }];
250
+ }
251
+ function diagnosticEndLineNumber(diagnostic) {
252
+ const start = diagnostic.range.start;
253
+ const end = diagnostic.range.end;
254
+ if (end.line > start.line && end.character === 0) return end.line;
255
+ return end.line + 1;
256
+ }
257
+ function minimapSeverityForDiagnostic(diagnostic) {
258
+ if (diagnostic.severity === LSP_DIAGNOSTIC_WARNING) return "warning";
259
+ if (diagnostic.severity === LSP_DIAGNOSTIC_INFORMATION) return "information";
260
+ if (diagnostic.severity === LSP_DIAGNOSTIC_HINT) return "hint";
261
+ if (diagnostic.severity === LSP_DIAGNOSTIC_ERROR) return "error";
262
+ return "error";
263
+ }
264
+ function clampLineNumber(lineNumber, lineCount) {
265
+ return Math.min(Math.max(1, lineNumber), lineCount);
266
+ }
267
+ function diagnosticMarkerTarget(document, diagnostics, offset, direction) {
268
+ const targets = diagnostics.flatMap((diagnostic) => diagnosticTarget(document, diagnostic)).sort((left, right) => compareOffsetRanges(left.range, right.range));
269
+ if (targets.length === 0) return null;
270
+ if (direction === "next") return targets.find((target) => target.range.start > offset) ?? targets[0] ?? null;
271
+ return targets.toReversed().find((target) => target.range.start < offset) ?? targets.at(-1) ?? null;
272
+ }
273
+ function diagnosticTarget(document, diagnostic) {
274
+ const start = lspPositionToOffsetInSnapshot(document, diagnostic.range.start);
275
+ const end = lspPositionToOffsetInSnapshot(document, diagnostic.range.end);
276
+ if (end < start) return [];
277
+ return [{
278
+ diagnostic,
279
+ range: {
280
+ start,
281
+ end
282
+ }
283
+ }];
284
+ }
285
+ function markerAnchor(range) {
286
+ if (range.start === range.end) return {
287
+ kind: "point",
288
+ offset: range.start,
289
+ bias: "right"
290
+ };
291
+ return {
292
+ kind: "range",
293
+ start: range.start,
294
+ end: range.end,
295
+ startBias: "right",
296
+ endBias: "left"
297
+ };
298
+ }
299
+ function compareOffsetRanges(left, right) {
300
+ return left.start - right.start || left.end - right.end;
301
+ }
302
+ //#endregion
303
+ export { CompositeDiagnosticsPresenter, DiagnosticsPresenter, viewDocumentSnapshot };
304
+
305
+ //# sourceMappingURL=diagnosticsPresenter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnosticsPresenter.js","names":[],"sources":["../src/diagnosticsPresenter.ts"],"sourcesContent":["import {\n EDITOR_MINIMAP_FEATURE,\n type EditorMinimapDecoration,\n type EditorMinimapFeature,\n type EditorViewContributionContext,\n} from '@singapore-editor/core/extensions'\nimport { lspPositionToOffsetInSnapshot, type LspTextDocumentSnapshot } from '@singapore-editor/lsp'\nimport {\n editorThemesEqual,\n type EditorTheme,\n type VirtualizedTextHighlightStyle,\n} from '@singapore-editor/core/rendering'\nimport { readColorAlpha } from './colorAlpha'\nimport type * as lsp from 'vscode-languageserver-protocol'\n\nimport {\n combineDiagnosticsFreshness,\n diagnosticHighlightGroups,\n summarizeDiagnostics,\n type LanguageServerDiagnosticHighlightLayer,\n type LanguageServerDiagnosticSeverity,\n} from './diagnostics'\nimport {\n DEPRECATED_DIAGNOSTIC_STYLE,\n DIAGNOSTIC_MARKER_COLORS,\n DIAGNOSTIC_STYLES,\n UNNECESSARY_DIAGNOSTIC_OPACITY,\n} from './plugin.styles'\nimport type { OffsetRange } from '@singapore-editor/plugin-ui/offset-range'\nimport type {\n LanguageServerDiagnosticMarkerClaim,\n LanguageServerDiagnosticMarkerEvent,\n LanguageServerDiagnosticSummary,\n LanguageServerDiagnosticsFreshness,\n} from './types'\n\nexport { viewDocumentSnapshot } from './viewDocumentSnapshot'\n\nconst LSP_DIAGNOSTIC_ERROR = 1\nconst LSP_DIAGNOSTIC_WARNING = 2\nconst LSP_DIAGNOSTIC_INFORMATION = 3\nconst LSP_DIAGNOSTIC_HINT = 4\n\nconst DIAGNOSTIC_LAYERS: readonly LanguageServerDiagnosticHighlightLayer[] = [\n 'error',\n 'warning',\n 'information',\n 'hint',\n 'deprecated',\n 'unnecessary',\n]\n\nconst DIAGNOSTIC_LAYER_STYLES: Record<\n LanguageServerDiagnosticHighlightLayer,\n VirtualizedTextHighlightStyle\n> = {\n ...DIAGNOSTIC_STYLES,\n deprecated: DEPRECATED_DIAGNOSTIC_STYLE,\n unnecessary: { overlay: { dim: 1 } },\n}\n\nconst DIAGNOSTIC_MINIMAP_Z_INDEX: Record<LanguageServerDiagnosticSeverity, number> = {\n error: 40,\n warning: 30,\n information: 20,\n hint: 10,\n}\n\nexport type DiagnosticsPresenterActiveDocument = LspTextDocumentSnapshot & {\n readonly textVersion: number\n readonly uri: lsp.DocumentUri\n}\n\nexport type DiagnosticsPresenterMarkerDirection = 'next' | 'previous'\n\nexport type DiagnosticsPresenterOptions = {\n readonly minimapSourceId: string\n readonly highlightNameNamespace: string\n readonly markerTimingNamePrefix: string\n readonly onDiagnostics?: (summary: ReturnType<typeof summarizeDiagnostics>) => void\n readonly onDidNavigateDiagnostic?: (\n event: LanguageServerDiagnosticMarkerEvent,\n ) => LanguageServerDiagnosticMarkerClaim\n readonly onError?: (error: unknown) => void\n}\n\nexport class DiagnosticsPresenter {\n private readonly highlightNames: Record<LanguageServerDiagnosticHighlightLayer, string>\n private markerClaim: Extract<LanguageServerDiagnosticMarkerClaim, { kind: 'claimed' }> | null =\n null\n private unnecessaryDimCache: number | null = null\n\n public constructor(\n private readonly context: EditorViewContributionContext,\n prefix: string,\n private readonly options: DiagnosticsPresenterOptions,\n ) {\n this.highlightNames = createHighlightNames(prefix, options.highlightNameNamespace)\n }\n\n public render(document: LspTextDocumentSnapshot, diagnostics: readonly lsp.Diagnostic[]): void {\n this.renderHighlights(document, diagnostics)\n this.renderMinimapMarkers(diagnostics)\n }\n\n public invalidateTheme(): void {\n this.unnecessaryDimCache = null\n }\n\n public clear(): void {\n this.releaseMarkerClaim()\n this.clearMinimapMarkers()\n for (const name of Object.values(this.highlightNames)) this.context.clearRangeHighlight(name)\n }\n\n public publishSummary(\n uri: lsp.DocumentUri,\n version: number | null,\n diagnostics: readonly lsp.Diagnostic[],\n ): void {\n this.options.onDiagnostics?.(summarizeDiagnostics(uri, version, diagnostics))\n }\n\n public moveMarker(\n active: DiagnosticsPresenterActiveDocument | null,\n diagnostics: readonly lsp.Diagnostic[],\n direction: DiagnosticsPresenterMarkerDirection,\n ): boolean {\n if (!active) return false\n\n const selection = this.context.getSnapshot().selections[0]\n if (!selection) return false\n\n const target = diagnosticMarkerTarget(active, diagnostics, selection.headOffset, direction)\n if (!target) return false\n\n const timingName = `${this.options.markerTimingNamePrefix}.${direction}`\n this.context.setSelection(target.range.start, target.range.end, timingName, {\n revealOffset: target.range.start,\n })\n this.context.focusEditor()\n this.publishMarker(active, target, direction)\n return true\n }\n\n private publishMarker(\n active: DiagnosticsPresenterActiveDocument,\n target: DiagnosticMarkerTarget,\n direction: DiagnosticsPresenterMarkerDirection,\n ): void {\n this.releaseMarkerClaim()\n if (!this.options.onDidNavigateDiagnostic) return\n\n try {\n const claim = this.options.onDidNavigateDiagnostic({\n anchor: markerAnchor(target.range),\n diagnostic: target.diagnostic,\n direction,\n documentUri: active.uri,\n textVersion: active.textVersion,\n })\n if (claim.kind === 'claimed') this.markerClaim = claim\n } catch (error) {\n this.options.onError?.(error)\n }\n }\n\n private releaseMarkerClaim(): void {\n const claim = this.markerClaim\n this.markerClaim = null\n claim?.dispose()\n }\n\n private renderHighlights(\n document: LspTextDocumentSnapshot,\n diagnostics: readonly lsp.Diagnostic[],\n ): void {\n const groups = diagnosticHighlightGroups(document, diagnostics)\n for (const layer of DIAGNOSTIC_LAYERS) {\n this.context.setRangeHighlight(\n this.highlightNames[layer],\n groups[layer],\n layer === 'unnecessary' && groups.unnecessary.length > 0\n ? { overlay: { dim: this.unnecessaryDim() } }\n : DIAGNOSTIC_LAYER_STYLES[layer],\n )\n }\n }\n\n // Reading the alpha forces a style recalc, and it only moves with the theme.\n private unnecessaryDim(): number {\n this.unnecessaryDimCache ??= readColorAlpha(\n this.context.scrollElement,\n UNNECESSARY_DIAGNOSTIC_OPACITY,\n )\n return this.unnecessaryDimCache\n }\n\n private renderMinimapMarkers(diagnostics: readonly lsp.Diagnostic[]): void {\n const minimap = this.minimapFeature()\n if (!minimap) return\n\n minimap.setDecorations(\n this.options.minimapSourceId,\n diagnosticMinimapDecorations(this.context.getSnapshot().lineCount, diagnostics),\n )\n }\n\n private clearMinimapMarkers(): void {\n this.minimapFeature()?.clearDecorations(this.options.minimapSourceId)\n }\n\n private minimapFeature(): EditorMinimapFeature | null {\n return this.context.getFeature(EDITOR_MINIMAP_FEATURE)\n }\n}\n\ntype DiagnosticBatch = {\n readonly diagnostics: readonly lsp.Diagnostic[]\n readonly document: LspTextDocumentSnapshot | null\n readonly uri: lsp.DocumentUri | null\n readonly version: number | null\n}\n\nexport type CompositeDiagnosticsLanePresenter = {\n clear(): void\n render(document: LspTextDocumentSnapshot, diagnostics: readonly lsp.Diagnostic[]): void\n publishSummary(\n uri: lsp.DocumentUri,\n version: number | null,\n diagnostics: readonly lsp.Diagnostic[],\n freshness: LanguageServerDiagnosticsFreshness,\n ): void\n}\n\nexport class CompositeDiagnosticsPresenter {\n private theme: EditorTheme | null = null\n\n public updateTheme(theme: EditorTheme | null): void {\n if (editorThemesEqual(this.theme, theme)) return\n this.theme = theme\n this.presenter.invalidateTheme()\n this.renderCombined()\n }\n\n readonly #batches = new Map<string, DiagnosticBatch>()\n readonly #freshness = new Map<string, LanguageServerDiagnosticsFreshness>()\n #diagnostics: readonly lsp.Diagnostic[] = []\n\n public constructor(\n private readonly presenter: DiagnosticsPresenter,\n private readonly laneIds: readonly string[],\n private readonly onDiagnostics?: (summary: LanguageServerDiagnosticSummary) => void,\n ) {}\n\n public get diagnostics(): readonly lsp.Diagnostic[] {\n return this.#diagnostics\n }\n\n public forLane(\n laneId: string,\n onDiagnostics?: (summary: LanguageServerDiagnosticSummary) => void,\n ): CompositeDiagnosticsLanePresenter {\n return {\n clear: () => {\n const current = this.#batches.get(laneId)\n if (!current) return\n\n this.#batches.set(laneId, { ...current, diagnostics: [] })\n this.refreshDiagnostics()\n this.renderCombined()\n },\n render: (document, diagnostics) => {\n const current = this.#batches.get(laneId)\n this.#batches.set(laneId, {\n diagnostics,\n document,\n uri: current?.uri ?? null,\n version: current?.version ?? null,\n })\n this.refreshDiagnostics()\n this.renderCombined()\n },\n publishSummary: (uri, version, diagnostics, freshness) => {\n this.#freshness.set(laneId, freshness)\n const current = this.#batches.get(laneId)\n if (!current && diagnostics.length === 0) {\n onDiagnostics?.(summarizeDiagnostics(uri, version, diagnostics, freshness))\n this.publishCombinedSummary()\n return\n }\n\n this.#batches.set(laneId, {\n diagnostics,\n document: current?.document ?? null,\n uri,\n version,\n })\n this.refreshDiagnostics()\n onDiagnostics?.(summarizeDiagnostics(uri, version, diagnostics, freshness))\n this.publishCombinedSummary()\n },\n }\n }\n\n public clear(): void {\n this.#batches.clear()\n this.#freshness.clear()\n this.refreshDiagnostics()\n this.presenter.clear()\n }\n\n public moveMarker(\n active: DiagnosticsPresenterActiveDocument | null,\n direction: DiagnosticsPresenterMarkerDirection,\n ): boolean {\n return this.presenter.moveMarker(active, this.diagnostics, direction)\n }\n\n private renderCombined(): void {\n const document = this.currentDocument()\n if (document === null) {\n this.presenter.clear()\n return\n }\n\n this.presenter.render(document, this.diagnostics)\n }\n\n private refreshDiagnostics(): void {\n this.#diagnostics = this.laneIds.flatMap((id) => this.#batches.get(id)?.diagnostics ?? [])\n }\n\n private publishCombinedSummary(): void {\n const current = this.currentBatch()\n const freshness = combineDiagnosticsFreshness(\n this.laneIds.flatMap((id) => this.#freshness.get(id) ?? []),\n )\n this.onDiagnostics?.(\n summarizeDiagnostics(\n current?.uri ?? null,\n current?.version ?? null,\n this.diagnostics,\n freshness,\n ),\n )\n }\n\n private currentDocument(): LspTextDocumentSnapshot | null {\n for (const id of this.laneIds) {\n const batch = this.#batches.get(id)\n if (batch?.document && batch.diagnostics.length > 0) return batch.document\n }\n\n for (const id of this.laneIds) {\n const batch = this.#batches.get(id)\n if (batch?.document) return batch.document\n }\n\n return null\n }\n\n private currentBatch(): DiagnosticBatch | null {\n for (const id of this.laneIds) {\n const batch = this.#batches.get(id)\n if (batch && batch.diagnostics.length > 0) return batch\n }\n\n for (const id of this.laneIds) {\n const batch = this.#batches.get(id)\n if (batch) return batch\n }\n\n return null\n }\n}\n\nfunction createHighlightNames(\n prefix: string,\n namespace: string,\n): Record<LanguageServerDiagnosticHighlightLayer, string> {\n return {\n error: `${prefix}-${namespace}-error`,\n warning: `${prefix}-${namespace}-warning`,\n information: `${prefix}-${namespace}-information`,\n hint: `${prefix}-${namespace}-hint`,\n deprecated: `${prefix}-${namespace}-deprecated`,\n unnecessary: `${prefix}-${namespace}-unnecessary`,\n }\n}\n\nfunction diagnosticMinimapDecorations(\n lineCount: number,\n diagnostics: readonly lsp.Diagnostic[],\n): readonly EditorMinimapDecoration[] {\n return diagnostics.flatMap((diagnostic) => diagnosticMinimapDecoration(lineCount, diagnostic))\n}\n\nfunction diagnosticMinimapDecoration(\n lineCount: number,\n diagnostic: lsp.Diagnostic,\n): readonly EditorMinimapDecoration[] {\n if (lineCount <= 0) return []\n\n const severity = minimapSeverityForDiagnostic(diagnostic)\n const startLineNumber = clampLineNumber(diagnostic.range.start.line + 1, lineCount)\n const endLineNumber = Math.max(\n startLineNumber,\n clampLineNumber(diagnosticEndLineNumber(diagnostic), lineCount),\n )\n return [\n {\n startLineNumber,\n startColumn: 1,\n endLineNumber,\n endColumn: 1,\n color: DIAGNOSTIC_MARKER_COLORS[severity],\n position: 'inline',\n zIndex: DIAGNOSTIC_MINIMAP_Z_INDEX[severity],\n },\n ]\n}\n\nfunction diagnosticEndLineNumber(diagnostic: lsp.Diagnostic): number {\n const start = diagnostic.range.start\n const end = diagnostic.range.end\n if (end.line > start.line && end.character === 0) return end.line\n return end.line + 1\n}\n\nfunction minimapSeverityForDiagnostic(\n diagnostic: lsp.Diagnostic,\n): LanguageServerDiagnosticSeverity {\n if (diagnostic.severity === LSP_DIAGNOSTIC_WARNING) return 'warning'\n if (diagnostic.severity === LSP_DIAGNOSTIC_INFORMATION) return 'information'\n if (diagnostic.severity === LSP_DIAGNOSTIC_HINT) return 'hint'\n if (diagnostic.severity === LSP_DIAGNOSTIC_ERROR) return 'error'\n return 'error'\n}\n\nfunction clampLineNumber(lineNumber: number, lineCount: number): number {\n return Math.min(Math.max(1, lineNumber), lineCount)\n}\n\ntype DiagnosticMarkerTarget = {\n readonly diagnostic: lsp.Diagnostic\n readonly range: OffsetRange\n}\n\nfunction diagnosticMarkerTarget(\n document: LspTextDocumentSnapshot,\n diagnostics: readonly lsp.Diagnostic[],\n offset: number,\n direction: DiagnosticsPresenterMarkerDirection,\n): DiagnosticMarkerTarget | null {\n const targets = diagnostics\n .flatMap((diagnostic) => diagnosticTarget(document, diagnostic))\n .sort((left, right) => compareOffsetRanges(left.range, right.range))\n if (targets.length === 0) return null\n if (direction === 'next') {\n return targets.find((target) => target.range.start > offset) ?? targets[0] ?? null\n }\n\n return (\n targets.toReversed().find((target) => target.range.start < offset) ?? targets.at(-1) ?? null\n )\n}\n\nfunction diagnosticTarget(\n document: LspTextDocumentSnapshot,\n diagnostic: lsp.Diagnostic,\n): readonly DiagnosticMarkerTarget[] {\n const start = lspPositionToOffsetInSnapshot(document, diagnostic.range.start)\n const end = lspPositionToOffsetInSnapshot(document, diagnostic.range.end)\n if (end < start) return []\n return [{ diagnostic, range: { start, end } }]\n}\n\nfunction markerAnchor(range: OffsetRange): LanguageServerDiagnosticMarkerEvent['anchor'] {\n if (range.start === range.end) return { kind: 'point', offset: range.start, bias: 'right' }\n\n return {\n kind: 'range',\n start: range.start,\n end: range.end,\n startBias: 'right',\n endBias: 'left',\n }\n}\n\nfunction compareOffsetRanges(left: OffsetRange, right: OffsetRange): number {\n return left.start - right.start || left.end - right.end\n}\n"],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ import type { DocumentSessionChange, EditorTextBuffer } from '@singapore-editor/core/document';
2
+ import type { EditorContributionChange, EditorDisposable, EditorViewContributionUpdateKind } from '@singapore-editor/core/extensions';
3
+ import type { LspTextDocumentSnapshot } from '@singapore-editor/lsp';
4
+ import type * as lsp from 'vscode-languageserver-protocol';
5
+ import { DocumentSync } from './documentSync';
6
+ import { type AcquiredLanguageServerLane, type LanguageServerResolvedLaneOptions } from './lane';
7
+ import type { LanguageServerDocumentSnapshot, LanguageServerDiagnosticsFreshness, LanguageServerDocumentSyncOptions, LanguageServerLaneOptions, LanguageServerStatus, OnApplyWorkspaceEdit } from './types';
8
+ /** A view's share of one lane's diagnostics for the document. */
9
+ export type DocumentLaneDiagnosticsObserver = {
10
+ clear(): void;
11
+ render(document: LspTextDocumentSnapshot, diagnostics: readonly lsp.Diagnostic[]): void;
12
+ publishSummary(uri: lsp.DocumentUri, version: number | null, diagnostics: readonly lsp.Diagnostic[], freshness: LanguageServerDiagnosticsFreshness): void;
13
+ };
14
+ type DocumentSource = {
15
+ getSnapshot(): LanguageServerDocumentSnapshot;
16
+ subscribe?(listener: (change: DocumentSessionChange) => void): () => void;
17
+ };
18
+ export type LanguageServerDocumentOptions = {
19
+ readonly buffer: EditorTextBuffer;
20
+ readonly uri: string;
21
+ readonly languageId: string;
22
+ readonly documentId?: string;
23
+ readonly lanes: readonly LanguageServerLaneOptions[];
24
+ readonly onApplyWorkspaceEdit?: OnApplyWorkspaceEdit;
25
+ readonly controller?: LanguageServerDocumentSyncOptions['controller'];
26
+ };
27
+ /** A document's protocol state. Views borrow it; its creator disposes it. */
28
+ export declare class LanguageServerDocument {
29
+ readonly lanes: readonly DocumentLanguageServerLane[];
30
+ readonly syncOptions: LanguageServerDocumentSyncOptions;
31
+ private readonly unsubscribe;
32
+ private disposed;
33
+ constructor(source: DocumentSource, options: {
34
+ readonly lanes: readonly LanguageServerResolvedLaneOptions[];
35
+ readonly documentSync: LanguageServerDocumentSyncOptions;
36
+ });
37
+ synchronize(change?: EditorContributionChange | null, kind?: EditorViewContributionUpdateKind): void;
38
+ dispose(): void;
39
+ }
40
+ export declare function createLanguageServerDocument(options: LanguageServerDocumentOptions): LanguageServerDocument;
41
+ export declare class DocumentLanguageServerLane {
42
+ private readonly source;
43
+ readonly options: LanguageServerResolvedLaneOptions;
44
+ private connectionStatus;
45
+ get status(): LanguageServerStatus;
46
+ readonly connection: AcquiredLanguageServerLane;
47
+ readonly sync: DocumentSync;
48
+ private readonly pullDiagnostics;
49
+ private readonly registration;
50
+ private readonly observers;
51
+ /** Whether the server has answered for the active document since it became active. */
52
+ private diagnosticsReceived;
53
+ private diagnosticsVersion;
54
+ private unavailable;
55
+ private readonly listeners;
56
+ private disposed;
57
+ constructor(source: DocumentSource, options: LanguageServerResolvedLaneOptions, syncOptions: LanguageServerDocumentSyncOptions);
58
+ /** How far the active document's diagnostics can be trusted. */
59
+ get diagnosticsFreshness(): LanguageServerDiagnosticsFreshness;
60
+ attach(presenter: DocumentLaneDiagnosticsObserver, onConnectionChange: () => void): EditorDisposable;
61
+ synchronize(change?: EditorContributionChange | null, kind?: EditorViewContributionUpdateKind): void;
62
+ dispose(): void;
63
+ private notify;
64
+ private publishSummary;
65
+ /** The diagnostics did not change, but how far they can be trusted did. */
66
+ private republishSummary;
67
+ }
68
+ export {};
69
+ //# sourceMappingURL=document.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../src/document.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,190 @@
1
+ import { bufferDocumentSnapshot } from "./documentSnapshot.js";
2
+ import { DocumentSync } from "./documentSync.js";
3
+ import { acquireResolvedLanguageServerLane, resolveLanguageServerLaneOptions } from "./lane.js";
4
+ import { summarizeDiagnostics } from "./diagnostics.js";
5
+ import { PullDiagnosticsController } from "./pullDiagnostics.js";
6
+ //#region src/document.ts
7
+ /** A document's protocol state. Views borrow it; its creator disposes it. */
8
+ var LanguageServerDocument = class {
9
+ lanes;
10
+ syncOptions;
11
+ unsubscribe;
12
+ disposed = false;
13
+ constructor(source, options) {
14
+ this.syncOptions = options.documentSync;
15
+ this.lanes = options.lanes.map((lane) => new DocumentLanguageServerLane(source, lane, options.documentSync));
16
+ this.unsubscribe = source.subscribe?.((change) => this.synchronize(change));
17
+ }
18
+ synchronize(change = null, kind = "content") {
19
+ if (this.disposed) return;
20
+ for (const lane of this.lanes) lane.synchronize(change, kind);
21
+ }
22
+ dispose() {
23
+ if (this.disposed) return;
24
+ this.disposed = true;
25
+ this.unsubscribe?.();
26
+ for (const lane of this.lanes) lane.dispose();
27
+ }
28
+ };
29
+ function createLanguageServerDocument(options) {
30
+ return new LanguageServerDocument({
31
+ getSnapshot: () => bufferDocumentSnapshot(options),
32
+ subscribe: (listener) => options.buffer.subscribe(({ change }) => listener(change))
33
+ }, {
34
+ lanes: options.lanes.map((lane) => resolveLanguageServerLaneOptions({
35
+ ...lane,
36
+ onApplyWorkspaceEdit: options.onApplyWorkspaceEdit ?? lane.onApplyWorkspaceEdit
37
+ })),
38
+ documentSync: {
39
+ controller: options.controller,
40
+ uriForDocument: () => options.uri
41
+ }
42
+ });
43
+ }
44
+ var DocumentLanguageServerLane = class {
45
+ source;
46
+ options;
47
+ connectionStatus = "idle";
48
+ get status() {
49
+ return this.connectionStatus;
50
+ }
51
+ connection;
52
+ sync;
53
+ pullDiagnostics;
54
+ registration;
55
+ observers = /* @__PURE__ */ new Set();
56
+ /** Whether the server has answered for the active document since it became active. */
57
+ diagnosticsReceived = false;
58
+ diagnosticsVersion = null;
59
+ unavailable = false;
60
+ listeners = /* @__PURE__ */ new Set();
61
+ disposed = false;
62
+ constructor(source, options, syncOptions) {
63
+ this.source = source;
64
+ this.options = options;
65
+ this.connection = acquireResolvedLanguageServerLane({
66
+ ...options,
67
+ onStatusChange: (status) => {
68
+ this.connectionStatus = status;
69
+ options.onStatusChange?.(status);
70
+ }
71
+ }, {
72
+ onReady: () => {
73
+ this.unavailable = false;
74
+ this.synchronize();
75
+ if (!this.pullDiagnostics?.pending) this.pullDiagnostics?.synchronize();
76
+ this.republishSummary();
77
+ this.notify();
78
+ },
79
+ onReconnecting: () => {
80
+ this.unavailable = true;
81
+ this.pullDiagnostics?.cancel();
82
+ this.sync.clearDiagnostics();
83
+ this.notify();
84
+ },
85
+ onDiagnosticRefresh: () => this.pullDiagnostics?.refresh(),
86
+ onPublishDiagnostics: (params) => {
87
+ if (options.features.diagnostics !== void 0) this.sync.publishDiagnostics(params);
88
+ },
89
+ onUnavailable: () => {
90
+ this.connectionStatus = "error";
91
+ this.unavailable = true;
92
+ this.pullDiagnostics?.cancel();
93
+ this.sync.clearDiagnostics();
94
+ this.notify();
95
+ }
96
+ });
97
+ this.sync = new DocumentSync(this.connection.workspace, {
98
+ clear: () => {
99
+ for (const observer of this.observers) observer.clear();
100
+ },
101
+ render: (document, diagnostics) => {
102
+ for (const observer of this.observers) observer.render(document, diagnostics);
103
+ },
104
+ publishSummary: (uri, version, diagnostics, kind) => {
105
+ this.diagnosticsReceived = kind === "result";
106
+ this.diagnosticsVersion = version;
107
+ this.publishSummary(uri, version, diagnostics);
108
+ }
109
+ }, {
110
+ ...syncOptions,
111
+ logicalRevisionScope: this.connection.logicalRevisionScope,
112
+ onDocumentClosed: () => this.notify()
113
+ });
114
+ this.pullDiagnostics = options.features.diagnostics === void 0 ? null : new PullDiagnosticsController({
115
+ client: this.connection.client,
116
+ getDocument: () => {
117
+ const active = this.sync.activeDocument;
118
+ return active ? {
119
+ uri: active.uri,
120
+ version: active.lspVersion
121
+ } : null;
122
+ },
123
+ publish: (document, items) => this.sync.pullDiagnostics(document.uri, document.version, items),
124
+ onRequestError: (error) => options.onRequestError?.("textDocument/diagnostic", error),
125
+ onPendingChange: () => this.republishSummary()
126
+ });
127
+ this.registration = syncOptions.controller?.register({
128
+ getSnapshot: () => source.getSnapshot(),
129
+ sync: this.sync,
130
+ workspace: this.connection.workspace
131
+ });
132
+ this.connection.ready.catch(() => void 0);
133
+ }
134
+ /** How far the active document's diagnostics can be trusted. */
135
+ get diagnosticsFreshness() {
136
+ if (this.unavailable) return "unavailable";
137
+ if (this.pullDiagnostics?.pending) return this.diagnosticsReceived ? "refreshing" : "awaiting";
138
+ return this.diagnosticsReceived ? "current" : "silent";
139
+ }
140
+ attach(presenter, onConnectionChange) {
141
+ this.observers.add(presenter);
142
+ this.listeners.add(onConnectionChange);
143
+ const active = this.sync.activeDocument;
144
+ if (active) {
145
+ presenter.render(active, this.sync.diagnostics);
146
+ presenter.publishSummary(active.uri, this.diagnosticsVersion, this.sync.diagnostics, this.diagnosticsFreshness);
147
+ }
148
+ return { dispose: () => {
149
+ this.observers.delete(presenter);
150
+ this.listeners.delete(onConnectionChange);
151
+ } };
152
+ }
153
+ synchronize(change = null, kind = "content") {
154
+ if (this.disposed || !this.connection.isReady()) return;
155
+ const snapshot = this.source.getSnapshot();
156
+ if (!this.sync.shouldSync(kind, snapshot)) return;
157
+ const before = this.sync.activeDocument;
158
+ this.sync.sync(snapshot, change);
159
+ if (before !== this.sync.activeDocument) this.pullDiagnostics?.synchronize();
160
+ }
161
+ dispose() {
162
+ if (this.disposed) return;
163
+ this.disposed = true;
164
+ this.registration?.dispose();
165
+ this.pullDiagnostics?.dispose();
166
+ this.sync.close();
167
+ this.connection.release();
168
+ this.observers.clear();
169
+ this.listeners.clear();
170
+ }
171
+ notify() {
172
+ for (const listener of this.listeners) listener();
173
+ }
174
+ publishSummary(uri, version, diagnostics) {
175
+ if (this.disposed) return;
176
+ const freshness = this.diagnosticsFreshness;
177
+ this.options.onDiagnostics?.(summarizeDiagnostics(uri, version, diagnostics, freshness));
178
+ for (const observer of this.observers) observer.publishSummary(uri, version, diagnostics, freshness);
179
+ }
180
+ /** The diagnostics did not change, but how far they can be trusted did. */
181
+ republishSummary() {
182
+ const active = this.sync?.activeDocument;
183
+ if (!active) return;
184
+ this.publishSummary(active.uri, this.diagnosticsVersion, this.sync.diagnostics);
185
+ }
186
+ };
187
+ //#endregion
188
+ export { DocumentLanguageServerLane, LanguageServerDocument, createLanguageServerDocument };
189
+
190
+ //# sourceMappingURL=document.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document.js","names":[],"sources":["../src/document.ts"],"sourcesContent":["import type { DocumentSessionChange, EditorTextBuffer } from '@singapore-editor/core/document'\nimport type {\n EditorContributionChange,\n EditorDisposable,\n EditorViewContributionUpdateKind,\n} from '@singapore-editor/core/extensions'\nimport type { LspTextDocumentSnapshot } from '@singapore-editor/lsp'\nimport type * as lsp from 'vscode-languageserver-protocol'\nimport { DocumentSync } from './documentSync'\nimport { summarizeDiagnostics } from './diagnostics'\nimport {\n acquireResolvedLanguageServerLane,\n resolveLanguageServerLaneOptions,\n type AcquiredLanguageServerLane,\n type LanguageServerResolvedLaneOptions,\n} from './lane'\nimport { PullDiagnosticsController } from './pullDiagnostics'\nimport type {\n LanguageServerDocumentSnapshot,\n LanguageServerDiagnosticsFreshness,\n LanguageServerDocumentSyncOptions,\n LanguageServerLaneOptions,\n LanguageServerStatus,\n OnApplyWorkspaceEdit,\n} from './types'\nimport { bufferDocumentSnapshot } from './documentSnapshot'\n\n/** A view's share of one lane's diagnostics for the document. */\nexport type DocumentLaneDiagnosticsObserver = {\n clear(): void\n render(document: LspTextDocumentSnapshot, diagnostics: readonly lsp.Diagnostic[]): void\n publishSummary(\n uri: lsp.DocumentUri,\n version: number | null,\n diagnostics: readonly lsp.Diagnostic[],\n freshness: LanguageServerDiagnosticsFreshness,\n ): void\n}\n\ntype DocumentSource = {\n getSnapshot(): LanguageServerDocumentSnapshot\n subscribe?(listener: (change: DocumentSessionChange) => void): () => void\n}\n\nexport type LanguageServerDocumentOptions = {\n readonly buffer: EditorTextBuffer\n readonly uri: string\n readonly languageId: string\n readonly documentId?: string\n readonly lanes: readonly LanguageServerLaneOptions[]\n readonly onApplyWorkspaceEdit?: OnApplyWorkspaceEdit\n readonly controller?: LanguageServerDocumentSyncOptions['controller']\n}\n\n/** A document's protocol state. Views borrow it; its creator disposes it. */\nexport class LanguageServerDocument {\n readonly lanes: readonly DocumentLanguageServerLane[]\n readonly syncOptions: LanguageServerDocumentSyncOptions\n private readonly unsubscribe: (() => void) | undefined\n private disposed = false\n\n constructor(\n source: DocumentSource,\n options: {\n readonly lanes: readonly LanguageServerResolvedLaneOptions[]\n readonly documentSync: LanguageServerDocumentSyncOptions\n },\n ) {\n this.syncOptions = options.documentSync\n this.lanes = options.lanes.map(\n (lane) => new DocumentLanguageServerLane(source, lane, options.documentSync),\n )\n this.unsubscribe = source.subscribe?.((change) => this.synchronize(change))\n }\n\n synchronize(\n change: EditorContributionChange | null = null,\n kind: EditorViewContributionUpdateKind = 'content',\n ): void {\n if (this.disposed) return\n for (const lane of this.lanes) lane.synchronize(change, kind)\n }\n\n dispose(): void {\n if (this.disposed) return\n this.disposed = true\n this.unsubscribe?.()\n for (const lane of this.lanes) lane.dispose()\n }\n}\n\nexport function createLanguageServerDocument(\n options: LanguageServerDocumentOptions,\n): LanguageServerDocument {\n return new LanguageServerDocument(\n {\n getSnapshot: () => bufferDocumentSnapshot(options),\n subscribe: (listener) => options.buffer.subscribe(({ change }) => listener(change)),\n },\n {\n lanes: options.lanes.map((lane) =>\n resolveLanguageServerLaneOptions({\n ...lane,\n onApplyWorkspaceEdit: options.onApplyWorkspaceEdit ?? lane.onApplyWorkspaceEdit,\n }),\n ),\n documentSync: {\n controller: options.controller,\n uriForDocument: () => options.uri,\n },\n },\n )\n}\n\nexport class DocumentLanguageServerLane {\n private connectionStatus: LanguageServerStatus = 'idle'\n\n get status(): LanguageServerStatus {\n return this.connectionStatus\n }\n readonly connection: AcquiredLanguageServerLane\n readonly sync: DocumentSync\n private readonly pullDiagnostics: PullDiagnosticsController | null\n private readonly registration: EditorDisposable | undefined\n private readonly observers = new Set<DocumentLaneDiagnosticsObserver>()\n /** Whether the server has answered for the active document since it became active. */\n private diagnosticsReceived = false\n private diagnosticsVersion: number | null = null\n private unavailable = false\n private readonly listeners = new Set<() => void>()\n private disposed = false\n\n constructor(\n private readonly source: DocumentSource,\n readonly options: LanguageServerResolvedLaneOptions,\n syncOptions: LanguageServerDocumentSyncOptions,\n ) {\n this.connection = acquireResolvedLanguageServerLane(\n {\n ...options,\n onStatusChange: (status) => {\n this.connectionStatus = status\n options.onStatusChange?.(status)\n },\n },\n {\n onReady: () => {\n this.unavailable = false\n this.synchronize()\n // After a reconnect the document is the same one; only a new request can refresh it.\n if (!this.pullDiagnostics?.pending) this.pullDiagnostics?.synchronize()\n this.republishSummary()\n this.notify()\n },\n onReconnecting: () => {\n this.unavailable = true\n this.pullDiagnostics?.cancel()\n this.sync.clearDiagnostics()\n this.notify()\n },\n onDiagnosticRefresh: () => this.pullDiagnostics?.refresh(),\n onPublishDiagnostics: (params) => {\n if (options.features.diagnostics !== undefined) this.sync.publishDiagnostics(params)\n },\n onUnavailable: () => {\n this.connectionStatus = 'error'\n this.unavailable = true\n this.pullDiagnostics?.cancel()\n this.sync.clearDiagnostics()\n this.notify()\n },\n },\n )\n this.sync = new DocumentSync(\n this.connection.workspace,\n {\n clear: () => {\n for (const observer of this.observers) observer.clear()\n },\n render: (document, diagnostics) => {\n for (const observer of this.observers) observer.render(document, diagnostics)\n },\n publishSummary: (uri, version, diagnostics, kind) => {\n this.diagnosticsReceived = kind === 'result'\n this.diagnosticsVersion = version\n this.publishSummary(uri, version, diagnostics)\n },\n },\n {\n ...syncOptions,\n logicalRevisionScope: this.connection.logicalRevisionScope,\n onDocumentClosed: () => this.notify(),\n },\n )\n this.pullDiagnostics =\n options.features.diagnostics === undefined\n ? null\n : new PullDiagnosticsController({\n client: this.connection.client,\n getDocument: () => {\n const active = this.sync.activeDocument\n return active ? { uri: active.uri, version: active.lspVersion } : null\n },\n publish: (document, items) =>\n this.sync.pullDiagnostics(document.uri, document.version, items),\n onRequestError: (error) => options.onRequestError?.('textDocument/diagnostic', error),\n onPendingChange: () => this.republishSummary(),\n })\n this.registration = syncOptions.controller?.register({\n getSnapshot: () => source.getSnapshot(),\n sync: this.sync,\n workspace: this.connection.workspace,\n })\n void this.connection.ready.catch(() => undefined)\n }\n\n /** How far the active document's diagnostics can be trusted. */\n get diagnosticsFreshness(): LanguageServerDiagnosticsFreshness {\n if (this.unavailable) return 'unavailable'\n if (this.pullDiagnostics?.pending) return this.diagnosticsReceived ? 'refreshing' : 'awaiting'\n return this.diagnosticsReceived ? 'current' : 'silent'\n }\n\n attach(\n presenter: DocumentLaneDiagnosticsObserver,\n onConnectionChange: () => void,\n ): EditorDisposable {\n this.observers.add(presenter)\n this.listeners.add(onConnectionChange)\n const active = this.sync.activeDocument\n if (active) {\n presenter.render(active, this.sync.diagnostics)\n presenter.publishSummary(\n active.uri,\n this.diagnosticsVersion,\n this.sync.diagnostics,\n this.diagnosticsFreshness,\n )\n }\n return {\n dispose: () => {\n this.observers.delete(presenter)\n this.listeners.delete(onConnectionChange)\n },\n }\n }\n\n synchronize(\n change: EditorContributionChange | null = null,\n kind: EditorViewContributionUpdateKind = 'content',\n ): void {\n if (this.disposed || !this.connection.isReady()) return\n const snapshot = this.source.getSnapshot()\n if (!this.sync.shouldSync(kind, snapshot)) return\n const before = this.sync.activeDocument\n this.sync.sync(snapshot, change)\n if (before !== this.sync.activeDocument) this.pullDiagnostics?.synchronize()\n }\n\n dispose(): void {\n if (this.disposed) return\n this.disposed = true\n this.registration?.dispose()\n this.pullDiagnostics?.dispose()\n this.sync.close()\n this.connection.release()\n this.observers.clear()\n this.listeners.clear()\n }\n\n private notify(): void {\n for (const listener of this.listeners) listener()\n }\n\n private publishSummary(\n uri: lsp.DocumentUri,\n version: number | null,\n diagnostics: readonly lsp.Diagnostic[],\n ): void {\n if (this.disposed) return\n const freshness = this.diagnosticsFreshness\n this.options.onDiagnostics?.(summarizeDiagnostics(uri, version, diagnostics, freshness))\n for (const observer of this.observers)\n observer.publishSummary(uri, version, diagnostics, freshness)\n }\n\n /** The diagnostics did not change, but how far they can be trusted did. */\n private republishSummary(): void {\n const active = this.sync?.activeDocument\n if (!active) return\n this.publishSummary(active.uri, this.diagnosticsVersion, this.sync.diagnostics)\n }\n}\n"],"mappings":"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"}