@difizen/libro-language-client 0.1.18

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 (298) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +0 -0
  3. package/es/common/api.d.ts +38 -0
  4. package/es/common/api.d.ts.map +1 -0
  5. package/es/common/api.js +9 -0
  6. package/es/common/callHierarchy.d.ts +39 -0
  7. package/es/common/callHierarchy.d.ts.map +1 -0
  8. package/es/common/callHierarchy.js +139 -0
  9. package/es/common/client.d.ts +482 -0
  10. package/es/common/client.d.ts.map +1 -0
  11. package/es/common/client.js +2731 -0
  12. package/es/common/codeAction.d.ts +22 -0
  13. package/es/common/codeAction.d.ts.map +1 -0
  14. package/es/common/codeAction.js +149 -0
  15. package/es/common/codeConverter.d.ts +81 -0
  16. package/es/common/codeConverter.d.ts.map +1 -0
  17. package/es/common/codeConverter.js +1040 -0
  18. package/es/common/codeLens.d.ts +26 -0
  19. package/es/common/codeLens.d.ts.map +1 -0
  20. package/es/common/codeLens.js +125 -0
  21. package/es/common/colorProvider.d.ts +27 -0
  22. package/es/common/colorProvider.d.ts.map +1 -0
  23. package/es/common/colorProvider.js +104 -0
  24. package/es/common/completion.d.ts +22 -0
  25. package/es/common/completion.d.ts.map +1 -0
  26. package/es/common/completion.js +130 -0
  27. package/es/common/configuration.d.ts +71 -0
  28. package/es/common/configuration.d.ts.map +1 -0
  29. package/es/common/configuration.js +292 -0
  30. package/es/common/declaration.d.ts +18 -0
  31. package/es/common/declaration.d.ts.map +1 -0
  32. package/es/common/declaration.js +88 -0
  33. package/es/common/definition.d.ts +18 -0
  34. package/es/common/definition.d.ts.map +1 -0
  35. package/es/common/definition.js +80 -0
  36. package/es/common/diagnostic.d.ts +125 -0
  37. package/es/common/diagnostic.d.ts.map +1 -0
  38. package/es/common/diagnostic.js +1442 -0
  39. package/es/common/documentHighlight.d.ts +17 -0
  40. package/es/common/documentHighlight.d.ts.map +1 -0
  41. package/es/common/documentHighlight.js +73 -0
  42. package/es/common/documentLink.d.ts +21 -0
  43. package/es/common/documentLink.d.ts.map +1 -0
  44. package/es/common/documentLink.js +90 -0
  45. package/es/common/documentSymbol.d.ts +20 -0
  46. package/es/common/documentSymbol.d.ts.map +1 -0
  47. package/es/common/documentSymbol.js +134 -0
  48. package/es/common/executeCommand.d.ts +22 -0
  49. package/es/common/executeCommand.d.ts.map +1 -0
  50. package/es/common/executeCommand.js +117 -0
  51. package/es/common/features.d.ts +421 -0
  52. package/es/common/features.d.ts.map +1 -0
  53. package/es/common/features.js +576 -0
  54. package/es/common/fileOperations.d.ts +118 -0
  55. package/es/common/fileOperations.d.ts.map +1 -0
  56. package/es/common/fileOperations.js +705 -0
  57. package/es/common/fileSystemWatcher.d.ts +19 -0
  58. package/es/common/fileSystemWatcher.d.ts.map +1 -0
  59. package/es/common/fileSystemWatcher.js +173 -0
  60. package/es/common/foldingRange.d.ts +22 -0
  61. package/es/common/foldingRange.d.ts.map +1 -0
  62. package/es/common/foldingRange.js +127 -0
  63. package/es/common/formatting.d.ts +41 -0
  64. package/es/common/formatting.d.ts.map +1 -0
  65. package/es/common/formatting.js +233 -0
  66. package/es/common/hover.d.ts +18 -0
  67. package/es/common/hover.d.ts.map +1 -0
  68. package/es/common/hover.js +80 -0
  69. package/es/common/implementation.d.ts +18 -0
  70. package/es/common/implementation.d.ts.map +1 -0
  71. package/es/common/implementation.js +88 -0
  72. package/es/common/inlayHint.d.ts +23 -0
  73. package/es/common/inlayHint.d.ts.map +1 -0
  74. package/es/common/inlayHint.js +187 -0
  75. package/es/common/inlineCompletion.d.ts +20 -0
  76. package/es/common/inlineCompletion.d.ts.map +1 -0
  77. package/es/common/inlineCompletion.js +74 -0
  78. package/es/common/inlineValue.d.ts +21 -0
  79. package/es/common/inlineValue.d.ts.map +1 -0
  80. package/es/common/inlineValue.js +124 -0
  81. package/es/common/linkedEditingRange.d.ts +23 -0
  82. package/es/common/linkedEditingRange.d.ts.map +1 -0
  83. package/es/common/linkedEditingRange.js +94 -0
  84. package/es/common/notebook.d.ts +97 -0
  85. package/es/common/notebook.d.ts.map +1 -0
  86. package/es/common/notebook.js +1444 -0
  87. package/es/common/progress.d.ts +12 -0
  88. package/es/common/progress.d.ts.map +1 -0
  89. package/es/common/progress.js +75 -0
  90. package/es/common/progressPart.d.ts +25 -0
  91. package/es/common/progressPart.d.ts.map +1 -0
  92. package/es/common/progressPart.js +147 -0
  93. package/es/common/protocolCallHierarchyItem.d.ts +9 -0
  94. package/es/common/protocolCallHierarchyItem.d.ts.map +1 -0
  95. package/es/common/protocolCallHierarchyItem.js +34 -0
  96. package/es/common/protocolCodeAction.d.ts +7 -0
  97. package/es/common/protocolCodeAction.d.ts.map +1 -0
  98. package/es/common/protocolCodeAction.js +32 -0
  99. package/es/common/protocolCodeLens.d.ts +7 -0
  100. package/es/common/protocolCodeLens.d.ts.map +1 -0
  101. package/es/common/protocolCodeLens.js +29 -0
  102. package/es/common/protocolCompletionItem.d.ts +13 -0
  103. package/es/common/protocolCompletionItem.d.ts.map +1 -0
  104. package/es/common/protocolCompletionItem.js +29 -0
  105. package/es/common/protocolConverter.d.ts +174 -0
  106. package/es/common/protocolConverter.d.ts.map +1 -0
  107. package/es/common/protocolConverter.js +1982 -0
  108. package/es/common/protocolDiagnostic.d.ts +20 -0
  109. package/es/common/protocolDiagnostic.d.ts.map +1 -0
  110. package/es/common/protocolDiagnostic.js +46 -0
  111. package/es/common/protocolDocumentLink.d.ts +8 -0
  112. package/es/common/protocolDocumentLink.d.ts.map +1 -0
  113. package/es/common/protocolDocumentLink.js +29 -0
  114. package/es/common/protocolInlayHint.d.ts +8 -0
  115. package/es/common/protocolInlayHint.d.ts.map +1 -0
  116. package/es/common/protocolInlayHint.js +29 -0
  117. package/es/common/protocolTypeHierarchyItem.d.ts +9 -0
  118. package/es/common/protocolTypeHierarchyItem.d.ts.map +1 -0
  119. package/es/common/protocolTypeHierarchyItem.js +34 -0
  120. package/es/common/protocolWorkspaceSymbol.d.ts +9 -0
  121. package/es/common/protocolWorkspaceSymbol.d.ts.map +1 -0
  122. package/es/common/protocolWorkspaceSymbol.js +36 -0
  123. package/es/common/reference.d.ts +22 -0
  124. package/es/common/reference.d.ts.map +1 -0
  125. package/es/common/reference.js +78 -0
  126. package/es/common/rename.d.ts +29 -0
  127. package/es/common/rename.d.ts.map +1 -0
  128. package/es/common/rename.js +132 -0
  129. package/es/common/selectionRange.d.ts +18 -0
  130. package/es/common/selectionRange.d.ts.map +1 -0
  131. package/es/common/selectionRange.js +108 -0
  132. package/es/common/semanticTokens.d.ts +36 -0
  133. package/es/common/semanticTokens.d.ts.map +1 -0
  134. package/es/common/semanticTokens.js +226 -0
  135. package/es/common/signatureHelp.d.ts +18 -0
  136. package/es/common/signatureHelp.d.ts.map +1 -0
  137. package/es/common/signatureHelp.js +103 -0
  138. package/es/common/textSynchronization.d.ts +104 -0
  139. package/es/common/textSynchronization.d.ts.map +1 -0
  140. package/es/common/textSynchronization.js +771 -0
  141. package/es/common/typeDefinition.d.ts +18 -0
  142. package/es/common/typeDefinition.d.ts.map +1 -0
  143. package/es/common/typeDefinition.js +89 -0
  144. package/es/common/typeHierarchy.d.ts +33 -0
  145. package/es/common/typeHierarchy.d.ts.map +1 -0
  146. package/es/common/typeHierarchy.js +138 -0
  147. package/es/common/utils/async.d.ts +42 -0
  148. package/es/common/utils/async.d.ts.map +1 -0
  149. package/es/common/utils/async.js +441 -0
  150. package/es/common/utils/is.d.ts +13 -0
  151. package/es/common/utils/is.d.ts.map +1 -0
  152. package/es/common/utils/is.js +52 -0
  153. package/es/common/utils/uuid.d.ts +23 -0
  154. package/es/common/utils/uuid.d.ts.map +1 -0
  155. package/es/common/utils/uuid.js +85 -0
  156. package/es/common/vscodeAdaptor/convertor.d.ts +7 -0
  157. package/es/common/vscodeAdaptor/convertor.d.ts.map +1 -0
  158. package/es/common/vscodeAdaptor/convertor.js +66 -0
  159. package/es/common/vscodeAdaptor/diagnosticCollection.d.ts +33 -0
  160. package/es/common/vscodeAdaptor/diagnosticCollection.d.ts.map +1 -0
  161. package/es/common/vscodeAdaptor/diagnosticCollection.js +310 -0
  162. package/es/common/vscodeAdaptor/extHostTypes.d.ts +1496 -0
  163. package/es/common/vscodeAdaptor/extHostTypes.d.ts.map +1 -0
  164. package/es/common/vscodeAdaptor/extHostTypes.js +3825 -0
  165. package/es/common/vscodeAdaptor/fileWatcher.d.ts +19 -0
  166. package/es/common/vscodeAdaptor/fileWatcher.d.ts.map +1 -0
  167. package/es/common/vscodeAdaptor/fileWatcher.js +45 -0
  168. package/es/common/vscodeAdaptor/hostTypeUtil.d.ts +192 -0
  169. package/es/common/vscodeAdaptor/hostTypeUtil.d.ts.map +1 -0
  170. package/es/common/vscodeAdaptor/hostTypeUtil.js +566 -0
  171. package/es/common/vscodeAdaptor/libro-fs.d.ts +21 -0
  172. package/es/common/vscodeAdaptor/libro-fs.d.ts.map +1 -0
  173. package/es/common/vscodeAdaptor/libro-fs.js +64 -0
  174. package/es/common/vscodeAdaptor/libroWindow.d.ts +21 -0
  175. package/es/common/vscodeAdaptor/libroWindow.d.ts.map +1 -0
  176. package/es/common/vscodeAdaptor/libroWindow.js +75 -0
  177. package/es/common/vscodeAdaptor/libroWorkspace.d.ts +33 -0
  178. package/es/common/vscodeAdaptor/libroWorkspace.d.ts.map +1 -0
  179. package/es/common/vscodeAdaptor/libroWorkspace.js +250 -0
  180. package/es/common/vscodeAdaptor/lspEnv.d.ts +8 -0
  181. package/es/common/vscodeAdaptor/lspEnv.d.ts.map +1 -0
  182. package/es/common/vscodeAdaptor/lspEnv.js +31 -0
  183. package/es/common/vscodeAdaptor/monaco-converter.d.ts +229 -0
  184. package/es/common/vscodeAdaptor/monaco-converter.d.ts.map +1 -0
  185. package/es/common/vscodeAdaptor/monaco-converter.js +1613 -0
  186. package/es/common/vscodeAdaptor/monacoLanguages.d.ts +48 -0
  187. package/es/common/vscodeAdaptor/monacoLanguages.d.ts.map +1 -0
  188. package/es/common/vscodeAdaptor/monacoLanguages.js +484 -0
  189. package/es/common/vscodeAdaptor/services.d.ts +85 -0
  190. package/es/common/vscodeAdaptor/services.d.ts.map +1 -0
  191. package/es/common/vscodeAdaptor/services.js +3 -0
  192. package/es/common/vscodeAdaptor/typings.d.ts +10 -0
  193. package/es/common/vscodeAdaptor/util.d.ts +3 -0
  194. package/es/common/vscodeAdaptor/util.d.ts.map +1 -0
  195. package/es/common/vscodeAdaptor/util.js +6 -0
  196. package/es/common/vscodeAdaptor/vscodeAdaptor.d.ts +77 -0
  197. package/es/common/vscodeAdaptor/vscodeAdaptor.d.ts.map +1 -0
  198. package/es/common/vscodeAdaptor/vscodeAdaptor.js +124 -0
  199. package/es/common/workspaceFolder.d.ts +32 -0
  200. package/es/common/workspaceFolder.d.ts.map +1 -0
  201. package/es/common/workspaceFolder.js +204 -0
  202. package/es/common/workspaceSymbol.d.ts +21 -0
  203. package/es/common/workspaceSymbol.d.ts.map +1 -0
  204. package/es/common/workspaceSymbol.js +101 -0
  205. package/es/constants.d.ts +2 -0
  206. package/es/constants.d.ts.map +1 -0
  207. package/es/constants.js +1 -0
  208. package/es/index.d.ts +6 -0
  209. package/es/index.d.ts.map +1 -0
  210. package/es/index.js +5 -0
  211. package/es/libro-language-client-contribution.d.ts +10 -0
  212. package/es/libro-language-client-contribution.d.ts.map +1 -0
  213. package/es/libro-language-client-contribution.js +143 -0
  214. package/es/libro-language-client-manager.d.ts +34 -0
  215. package/es/libro-language-client-manager.d.ts.map +1 -0
  216. package/es/libro-language-client-manager.js +277 -0
  217. package/es/libro-language-client.d.ts +27 -0
  218. package/es/libro-language-client.d.ts.map +1 -0
  219. package/es/libro-language-client.js +141 -0
  220. package/es/module.d.ts +3 -0
  221. package/es/module.d.ts.map +1 -0
  222. package/es/module.js +13 -0
  223. package/package.json +69 -0
  224. package/src/common/api.ts +155 -0
  225. package/src/common/callHierarchy.ts +269 -0
  226. package/src/common/client.ts +3192 -0
  227. package/src/common/codeAction.ts +237 -0
  228. package/src/common/codeConverter.ts +1409 -0
  229. package/src/common/codeLens.ts +188 -0
  230. package/src/common/colorProvider.ts +192 -0
  231. package/src/common/completion.ts +281 -0
  232. package/src/common/configuration.ts +338 -0
  233. package/src/common/declaration.ts +140 -0
  234. package/src/common/definition.ts +138 -0
  235. package/src/common/diagnostic.ts +1408 -0
  236. package/src/common/documentHighlight.ts +140 -0
  237. package/src/common/documentLink.ts +180 -0
  238. package/src/common/documentSymbol.ts +186 -0
  239. package/src/common/executeCommand.ts +129 -0
  240. package/src/common/features.ts +1157 -0
  241. package/src/common/fileOperations.ts +635 -0
  242. package/src/common/fileSystemWatcher.ts +184 -0
  243. package/src/common/foldingRange.ts +160 -0
  244. package/src/common/formatting.ts +465 -0
  245. package/src/common/hover.ts +133 -0
  246. package/src/common/implementation.ts +142 -0
  247. package/src/common/inlayHint.ts +201 -0
  248. package/src/common/inlineCompletion.ts +160 -0
  249. package/src/common/inlineValue.ts +158 -0
  250. package/src/common/linkedEditingRange.ts +141 -0
  251. package/src/common/notebook.ts +1443 -0
  252. package/src/common/progress.ts +61 -0
  253. package/src/common/progressPart.ts +151 -0
  254. package/src/common/protocolCallHierarchyItem.ts +29 -0
  255. package/src/common/protocolCodeAction.ts +17 -0
  256. package/src/common/protocolCodeLens.ts +15 -0
  257. package/src/common/protocolCompletionItem.ts +22 -0
  258. package/src/common/protocolConverter.ts +2627 -0
  259. package/src/common/protocolDiagnostic.ts +47 -0
  260. package/src/common/protocolDocumentLink.ts +17 -0
  261. package/src/common/protocolInlayHint.ts +21 -0
  262. package/src/common/protocolTypeHierarchyItem.ts +29 -0
  263. package/src/common/protocolWorkspaceSymbol.ts +39 -0
  264. package/src/common/reference.ts +144 -0
  265. package/src/common/rename.ts +230 -0
  266. package/src/common/selectionRange.ts +136 -0
  267. package/src/common/semanticTokens.ts +383 -0
  268. package/src/common/signatureHelp.ts +170 -0
  269. package/src/common/textSynchronization.ts +819 -0
  270. package/src/common/typeDefinition.ts +146 -0
  271. package/src/common/typeHierarchy.ts +248 -0
  272. package/src/common/utils/async.ts +354 -0
  273. package/src/common/utils/is.ts +63 -0
  274. package/src/common/utils/uuid.ts +136 -0
  275. package/src/common/vscodeAdaptor/convertor.ts +73 -0
  276. package/src/common/vscodeAdaptor/diagnosticCollection.ts +238 -0
  277. package/src/common/vscodeAdaptor/extHostTypes.ts +4498 -0
  278. package/src/common/vscodeAdaptor/fileWatcher.ts +36 -0
  279. package/src/common/vscodeAdaptor/hostTypeUtil.ts +539 -0
  280. package/src/common/vscodeAdaptor/libro-fs.ts +51 -0
  281. package/src/common/vscodeAdaptor/libroWindow.ts +85 -0
  282. package/src/common/vscodeAdaptor/libroWorkspace.ts +261 -0
  283. package/src/common/vscodeAdaptor/lspEnv.ts +16 -0
  284. package/src/common/vscodeAdaptor/monaco-converter.ts +1800 -0
  285. package/src/common/vscodeAdaptor/monacoLanguages.ts +511 -0
  286. package/src/common/vscodeAdaptor/services.ts +278 -0
  287. package/src/common/vscodeAdaptor/typings.d.ts +10 -0
  288. package/src/common/vscodeAdaptor/util.ts +7 -0
  289. package/src/common/vscodeAdaptor/vscodeAdaptor.ts +122 -0
  290. package/src/common/workspaceFolder.ts +236 -0
  291. package/src/common/workspaceSymbol.ts +166 -0
  292. package/src/constants.ts +1 -0
  293. package/src/index.spec.ts +7 -0
  294. package/src/index.ts +5 -0
  295. package/src/libro-language-client-contribution.ts +49 -0
  296. package/src/libro-language-client-manager.ts +131 -0
  297. package/src/libro-language-client.ts +100 -0
  298. package/src/module.ts +19 -0
@@ -0,0 +1,1443 @@
1
+ /* eslint-disable no-inner-declarations */
2
+ /* --------------------------------------------------------------------------------------------
3
+ * Copyright (c) Microsoft Corporation. All rights reserved.
4
+ * Licensed under the MIT License. See License.txt in the project root for license information.
5
+ * ------------------------------------------------------------------------------------------ */
6
+
7
+ import * as proto from '@difizen/vscode-languageserver-protocol';
8
+ import type {
9
+ StaticRegistrationOptions,
10
+ NotebookDocumentFilter,
11
+ TextDocumentItem,
12
+ NotebookCellTextDocumentFilter,
13
+ LSPAny,
14
+ } from '@difizen/vscode-languageserver-protocol';
15
+ import * as minimatch from 'minimatch';
16
+ import type {
17
+ TextDocument,
18
+ Disposable,
19
+ NotebookDocument,
20
+ NotebookCell,
21
+ TextDocumentChangeEvent,
22
+ NotebookCellExecutionSummary,
23
+ DocumentSelector,
24
+ NotebookDocumentChangeEvent,
25
+ } from 'vscode';
26
+
27
+ import { LibroCellURIScheme } from '../constants.js';
28
+
29
+ import type * as _c2p from './codeConverter.js';
30
+ import type {
31
+ DynamicFeature,
32
+ FeatureClient,
33
+ RegistrationData,
34
+ FeatureState,
35
+ } from './features.js';
36
+ import * as Is from './utils/is.js';
37
+ import * as UUID from './utils/uuid.js';
38
+ import { NotebookCellKind } from './vscodeAdaptor/vscodeAdaptor.js';
39
+ import { workspace, languages } from './vscodeAdaptor/vscodeAdaptor.js';
40
+
41
+ function ensure<T, K extends keyof T>(target: T, key: K): T[K] {
42
+ if (target[key] === void 0) {
43
+ target[key] = {} as any;
44
+ }
45
+ return target[key];
46
+ }
47
+
48
+ type $LSPObject = { [key: string]: LSPAny };
49
+ type $LSPArray = LSPAny[];
50
+
51
+ namespace Converter {
52
+ export namespace c2p {
53
+ export function asVersionedNotebookDocumentIdentifier(
54
+ notebookDocument: NotebookDocument,
55
+ base: _c2p.Converter,
56
+ ): proto.VersionedNotebookDocumentIdentifier {
57
+ return {
58
+ version: notebookDocument.version,
59
+ uri: base.asUri(notebookDocument.uri),
60
+ };
61
+ }
62
+ export function asNotebookDocument(
63
+ notebookDocument: NotebookDocument,
64
+ cells: NotebookCell[],
65
+ base: _c2p.Converter,
66
+ ): proto.NotebookDocument {
67
+ const result = proto.NotebookDocument.create(
68
+ base.asUri(notebookDocument.uri),
69
+ notebookDocument.notebookType,
70
+ notebookDocument.version,
71
+ asNotebookCells(cells, base),
72
+ );
73
+ if (Object.keys(notebookDocument.metadata).length > 0) {
74
+ result.metadata = asMetadata(notebookDocument.metadata);
75
+ }
76
+ return result;
77
+ }
78
+ export function asNotebookCells(
79
+ cells: NotebookCell[],
80
+ base: _c2p.Converter,
81
+ ): proto.NotebookCell[] {
82
+ return cells.map((cell) => asNotebookCell(cell, base));
83
+ }
84
+ export function asMetadata(metadata: { [key: string]: any }): $LSPObject {
85
+ const seen: Set<any> = new Set();
86
+ return deepCopy(seen, metadata);
87
+ }
88
+ export function asNotebookCell(
89
+ cell: NotebookCell,
90
+ base: _c2p.Converter,
91
+ ): proto.NotebookCell {
92
+ const result = proto.NotebookCell.create(
93
+ asNotebookCellKind(cell.kind),
94
+ base.asUri(cell.document.uri),
95
+ );
96
+ if (Object.keys(cell.metadata).length > 0) {
97
+ result.metadata = asMetadata(cell.metadata);
98
+ }
99
+ if (
100
+ cell.executionSummary !== undefined &&
101
+ Is.number(cell.executionSummary.executionOrder) &&
102
+ Is.boolean(cell.executionSummary.success)
103
+ ) {
104
+ result.executionSummary = {
105
+ executionOrder: cell.executionSummary.executionOrder,
106
+ success: cell.executionSummary.success,
107
+ };
108
+ }
109
+ return result;
110
+ }
111
+ function asNotebookCellKind(kind: NotebookCellKind): proto.NotebookCellKind {
112
+ switch (kind) {
113
+ case NotebookCellKind.Markup:
114
+ return proto.NotebookCellKind.Markup;
115
+ case NotebookCellKind.Code:
116
+ return proto.NotebookCellKind.Code;
117
+ }
118
+ }
119
+ function deepCopy(seen: Set<any>, value: { [key: string]: any }): $LSPObject;
120
+ function deepCopy(seen: Set<any>, value: any[]): $LSPArray;
121
+ function deepCopy(
122
+ seen: Set<any>,
123
+ value: { [key: string]: any } | any[],
124
+ ): $LSPArray | $LSPObject {
125
+ if (seen.has(value)) {
126
+ throw new Error(`Can't deep copy cyclic structures.`);
127
+ }
128
+ if (Array.isArray(value)) {
129
+ const result: $LSPArray = [];
130
+ for (const elem of value) {
131
+ if ((elem !== null && typeof elem === 'object') || Array.isArray(elem)) {
132
+ result.push(deepCopy(seen, elem));
133
+ } else {
134
+ if (elem instanceof RegExp) {
135
+ throw new Error(`Can't transfer regular expressions to the server`);
136
+ }
137
+ result.push(elem);
138
+ }
139
+ }
140
+ return result;
141
+ } else {
142
+ const props = Object.keys(value);
143
+ const result: $LSPObject = Object.create(null);
144
+ for (const prop of props) {
145
+ const elem = value[prop];
146
+ if ((elem !== null && typeof elem === 'object') || Array.isArray(elem)) {
147
+ result[prop] = deepCopy(seen, elem);
148
+ } else {
149
+ if (elem instanceof RegExp) {
150
+ throw new Error(`Can't transfer regular expressions to the server`);
151
+ }
152
+ result[prop] = elem;
153
+ }
154
+ }
155
+ return result;
156
+ }
157
+ }
158
+ type TextContent = Required<
159
+ Required<Required<proto.NotebookDocumentChangeEvent>['cells']>['textContent']
160
+ >[0];
161
+ export function asTextContentChange(
162
+ event: TextDocumentChangeEvent,
163
+ base: _c2p.Converter,
164
+ ): TextContent {
165
+ const params = base.asChangeTextDocumentParams(
166
+ event,
167
+ event.document.uri,
168
+ event.document.version,
169
+ );
170
+ return { document: params.textDocument, changes: params.contentChanges };
171
+ }
172
+ export function asNotebookDocumentChangeEvent(
173
+ event: VNotebookDocumentChangeEvent,
174
+ base: _c2p.Converter,
175
+ ): proto.NotebookDocumentChangeEvent {
176
+ const result: proto.NotebookDocumentChangeEvent = Object.create(null);
177
+ if (event.metadata) {
178
+ result.metadata = Converter.c2p.asMetadata(event.metadata);
179
+ }
180
+ if (event.cells !== undefined) {
181
+ const cells: Required<proto.NotebookDocumentChangeEvent>['cells'] =
182
+ Object.create(null);
183
+ const changedCells = event.cells;
184
+ if (changedCells.structure) {
185
+ cells.structure = {
186
+ array: {
187
+ start: changedCells.structure.array.start,
188
+ deleteCount: changedCells.structure.array.deleteCount,
189
+ cells:
190
+ changedCells.structure.array.cells !== undefined
191
+ ? changedCells.structure.array.cells.map((cell) =>
192
+ Converter.c2p.asNotebookCell(cell, base),
193
+ )
194
+ : undefined,
195
+ },
196
+ didOpen:
197
+ changedCells.structure.didOpen !== undefined
198
+ ? changedCells.structure.didOpen.map(
199
+ (cell) => base.asOpenTextDocumentParams(cell.document).textDocument,
200
+ )
201
+ : undefined,
202
+ didClose:
203
+ changedCells.structure.didClose !== undefined
204
+ ? changedCells.structure.didClose.map(
205
+ (cell) =>
206
+ base.asCloseTextDocumentParams(cell.document).textDocument,
207
+ )
208
+ : undefined,
209
+ };
210
+ }
211
+ if (changedCells.data !== undefined) {
212
+ cells.data = changedCells.data.map((cell) =>
213
+ Converter.c2p.asNotebookCell(cell, base),
214
+ );
215
+ }
216
+ if (changedCells.textContent !== undefined) {
217
+ cells.textContent = changedCells.textContent.map((event) =>
218
+ Converter.c2p.asTextContentChange(event, base),
219
+ );
220
+ }
221
+ if (Object.keys(cells).length > 0) {
222
+ result.cells = cells;
223
+ }
224
+ }
225
+ return result;
226
+ }
227
+ }
228
+ }
229
+
230
+ namespace $NotebookCell {
231
+ type ComputeDiffReturnType = {
232
+ start: number;
233
+ deleteCount: number;
234
+ cells?: NotebookCell[];
235
+ };
236
+ export function computeDiff(
237
+ originalCells: NotebookCell[],
238
+ modifiedCells: NotebookCell[],
239
+ compareMetadata: boolean,
240
+ ): ComputeDiffReturnType | undefined {
241
+ const originalLength = originalCells.length;
242
+ const modifiedLength = modifiedCells.length;
243
+ let startIndex = 0;
244
+ while (
245
+ startIndex < modifiedLength &&
246
+ startIndex < originalLength &&
247
+ equals(originalCells[startIndex], modifiedCells[startIndex], compareMetadata)
248
+ ) {
249
+ startIndex++;
250
+ }
251
+ if (startIndex < modifiedLength && startIndex < originalLength) {
252
+ let originalEndIndex = originalLength - 1;
253
+ let modifiedEndIndex = modifiedLength - 1;
254
+ while (
255
+ originalEndIndex >= 0 &&
256
+ modifiedEndIndex >= 0 &&
257
+ equals(
258
+ originalCells[originalEndIndex],
259
+ modifiedCells[modifiedEndIndex],
260
+ compareMetadata,
261
+ )
262
+ ) {
263
+ originalEndIndex--;
264
+ modifiedEndIndex--;
265
+ }
266
+
267
+ const deleteCount = originalEndIndex + 1 - startIndex;
268
+ const newCells =
269
+ startIndex === modifiedEndIndex + 1
270
+ ? undefined
271
+ : modifiedCells.slice(startIndex, modifiedEndIndex + 1);
272
+ return newCells !== undefined
273
+ ? { start: startIndex, deleteCount, cells: newCells }
274
+ : { start: startIndex, deleteCount };
275
+ } else if (startIndex < modifiedLength) {
276
+ return {
277
+ start: startIndex,
278
+ deleteCount: 0,
279
+ cells: modifiedCells.slice(startIndex),
280
+ };
281
+ } else if (startIndex < originalLength) {
282
+ return { start: startIndex, deleteCount: originalLength - startIndex };
283
+ } else {
284
+ // The two arrays are the same.
285
+ return undefined;
286
+ }
287
+ }
288
+
289
+ /**
290
+ * We only sync kind, document, execution and metadata to the server. So we only need to compare those.
291
+ */
292
+ function equals(
293
+ one: NotebookCell,
294
+ other: NotebookCell,
295
+ compareMetaData = true,
296
+ ): boolean {
297
+ if (
298
+ one.kind !== other.kind ||
299
+ one.document.uri.toString() !== other.document.uri.toString() ||
300
+ one.document.languageId !== other.document.languageId ||
301
+ !equalsExecution(one.executionSummary, other.executionSummary)
302
+ ) {
303
+ return false;
304
+ }
305
+ return (
306
+ !compareMetaData ||
307
+ (compareMetaData && equalsMetadata(one.metadata, other.metadata))
308
+ );
309
+ }
310
+
311
+ function equalsExecution(
312
+ one: NotebookCellExecutionSummary | undefined,
313
+ other: NotebookCellExecutionSummary | undefined,
314
+ ): boolean {
315
+ if (one === other) {
316
+ return true;
317
+ }
318
+ if (one === undefined || other === undefined) {
319
+ return false;
320
+ }
321
+ return (
322
+ one.executionOrder === other.executionOrder &&
323
+ one.success === other.success &&
324
+ equalsTiming(one.timing, other.timing)
325
+ );
326
+ }
327
+
328
+ function equalsTiming(
329
+ one: { startTime: number; endTime: number } | undefined,
330
+ other: { startTime: number; endTime: number } | undefined,
331
+ ): boolean {
332
+ if (one === other) {
333
+ return true;
334
+ }
335
+ if (one === undefined || other === undefined) {
336
+ return false;
337
+ }
338
+ return one.startTime === other.startTime && one.endTime === other.endTime;
339
+ }
340
+
341
+ function equalsMetadata(one: any, other: any | undefined): boolean {
342
+ if (one === other) {
343
+ return true;
344
+ }
345
+ if (one === null || one === undefined || other === null || other === undefined) {
346
+ return false;
347
+ }
348
+ if (typeof one !== typeof other) {
349
+ return false;
350
+ }
351
+ if (typeof one !== 'object') {
352
+ return false;
353
+ }
354
+ const oneArray = Array.isArray(one);
355
+ const otherArray = Array.isArray(other);
356
+ if (oneArray !== otherArray) {
357
+ return false;
358
+ }
359
+
360
+ if (oneArray && otherArray) {
361
+ if (one.length !== other.length) {
362
+ return false;
363
+ }
364
+ for (let i = 0; i < one.length; i++) {
365
+ if (!equalsMetadata(one[i], other[i])) {
366
+ return false;
367
+ }
368
+ }
369
+ }
370
+ if (isObjectLiteral(one) && isObjectLiteral(other)) {
371
+ const oneKeys = Object.keys(one);
372
+ const otherKeys = Object.keys(other);
373
+
374
+ if (oneKeys.length !== otherKeys.length) {
375
+ return false;
376
+ }
377
+
378
+ oneKeys.sort();
379
+ otherKeys.sort();
380
+ if (!equalsMetadata(oneKeys, otherKeys)) {
381
+ return false;
382
+ }
383
+ for (let i = 0; i < oneKeys.length; i++) {
384
+ const prop = oneKeys[i];
385
+ if (!equalsMetadata((one as $LSPObject)[prop], (other as $LSPObject)[prop])) {
386
+ return false;
387
+ }
388
+ }
389
+ return true;
390
+ }
391
+ return false;
392
+ }
393
+
394
+ export function isObjectLiteral(value: any): value is object {
395
+ return value !== null && typeof value === 'object';
396
+ }
397
+ }
398
+
399
+ namespace $NotebookDocumentFilter {
400
+ export function matchNotebook(
401
+ filter: string | NotebookDocumentFilter,
402
+ notebookDocument: NotebookDocument,
403
+ ): boolean {
404
+ if (typeof filter === 'string') {
405
+ return filter === '*' || notebookDocument.notebookType === filter;
406
+ }
407
+ if (
408
+ filter.notebookType !== undefined &&
409
+ filter.notebookType !== '*' &&
410
+ notebookDocument.notebookType !== filter.notebookType
411
+ ) {
412
+ return false;
413
+ }
414
+ const uri = notebookDocument.uri;
415
+ if (
416
+ filter.scheme !== undefined &&
417
+ filter.scheme !== '*' &&
418
+ uri.scheme !== filter.scheme
419
+ ) {
420
+ return false;
421
+ }
422
+ if (filter.pattern !== undefined) {
423
+ const matcher = new minimatch.Minimatch(filter.pattern, { noext: true });
424
+ if (!matcher.makeRe()) {
425
+ return false;
426
+ }
427
+ if (!matcher.match(uri.fsPath)) {
428
+ return false;
429
+ }
430
+ }
431
+ return true;
432
+ }
433
+ }
434
+
435
+ namespace $NotebookDocumentSyncOptions {
436
+ export function asDocumentSelector(
437
+ options: proto.NotebookDocumentSyncOptions,
438
+ ): proto.DocumentSelector {
439
+ const selector = options.notebookSelector;
440
+ const result: proto.DocumentSelector = [];
441
+ for (const element of selector) {
442
+ const notebookType =
443
+ (typeof element.notebook === 'string'
444
+ ? element.notebook
445
+ : element.notebook?.notebookType) ?? '*';
446
+ const scheme =
447
+ typeof element.notebook === 'string' ? undefined : element.notebook?.scheme;
448
+ const pattern =
449
+ typeof element.notebook === 'string' ? undefined : element.notebook?.pattern;
450
+ if (element.cells !== undefined) {
451
+ for (const cell of element.cells) {
452
+ result.push(asDocumentFilter(notebookType, scheme, pattern, cell.language));
453
+ }
454
+ } else {
455
+ result.push(asDocumentFilter(notebookType, scheme, pattern, undefined));
456
+ }
457
+ }
458
+ return result;
459
+ }
460
+
461
+ function asDocumentFilter(
462
+ notebookType: string,
463
+ scheme: string | undefined,
464
+ pattern: string | undefined,
465
+ language: string | undefined,
466
+ ): proto.NotebookCellTextDocumentFilter {
467
+ return scheme === undefined && pattern === undefined
468
+ ? { notebook: notebookType, language }
469
+ : { notebook: { notebookType, scheme, pattern }, language };
470
+ }
471
+ }
472
+
473
+ type SyncInfo = {
474
+ /**
475
+ * The synced VS Code notebook cells.
476
+ */
477
+ cells: NotebookCell[];
478
+
479
+ /**
480
+ * A set of VS Code URIs of the synced
481
+ * VS Code notebook cell text documents.
482
+ */
483
+ uris: Set<string>;
484
+ };
485
+
486
+ namespace SyncInfo {
487
+ export function create(cells: NotebookCell[]): SyncInfo {
488
+ return {
489
+ cells,
490
+ uris: new Set(cells.map((cell) => cell.document.uri.toString())),
491
+ };
492
+ }
493
+ }
494
+
495
+ export type VNotebookDocumentChangeEvent = {
496
+ /**
497
+ * The notebook document
498
+ */
499
+ notebook: NotebookDocument;
500
+
501
+ /**
502
+ * The changed meta data if any.
503
+ */
504
+ metadata?: { [key: string]: any };
505
+
506
+ /**
507
+ * Changes to cells.
508
+ */
509
+ cells?: {
510
+ /**
511
+ * Changes to the cell structure to add or
512
+ * remove cells.
513
+ */
514
+ structure?: {
515
+ /**
516
+ * The change to the cell array.
517
+ */
518
+ array: { start: number; deleteCount: number; cells?: NotebookCell[] };
519
+
520
+ /**
521
+ * Additional opened cell text documents.
522
+ */
523
+ didOpen?: NotebookCell[];
524
+
525
+ /**
526
+ * Additional closed cell text documents.
527
+ */
528
+ didClose?: NotebookCell[];
529
+ };
530
+
531
+ /**
532
+ * Changes to notebook cells properties like its
533
+ * kind or metadata.
534
+ */
535
+ data?: NotebookCell[];
536
+
537
+ /**
538
+ * Changes to the text content of notebook cells.
539
+ */
540
+ textContent?: TextDocumentChangeEvent[];
541
+ };
542
+ };
543
+
544
+ export type NotebookDocumentOptions = {
545
+ filterCells?(
546
+ notebookDocument: NotebookDocument,
547
+ cells: NotebookCell[],
548
+ ): NotebookCell[];
549
+ };
550
+
551
+ export type $NotebookDocumentOptions = {
552
+ notebookDocumentOptions?: NotebookDocumentOptions;
553
+ };
554
+
555
+ export type NotebookDocumentMiddleware = {
556
+ notebooks?: {
557
+ didOpen?: (
558
+ this: void,
559
+ notebookDocument: NotebookDocument,
560
+ cells: NotebookCell[],
561
+ next: (
562
+ this: void,
563
+ notebookDocument: NotebookDocument,
564
+ cells: NotebookCell[],
565
+ ) => Promise<void>,
566
+ ) => Promise<void>;
567
+ didSave?: (
568
+ this: void,
569
+ notebookDocument: NotebookDocument,
570
+ next: (this: void, notebookDocument: NotebookDocument) => Promise<void>,
571
+ ) => Promise<void>;
572
+ didChange?: (
573
+ this: void,
574
+ event: VNotebookDocumentChangeEvent,
575
+ next: (this: void, event: VNotebookDocumentChangeEvent) => Promise<void>,
576
+ ) => Promise<void>;
577
+ didClose?: (
578
+ this: void,
579
+ notebookDocument: NotebookDocument,
580
+ cells: NotebookCell[],
581
+ next: (
582
+ this: void,
583
+ notebookDocument: NotebookDocument,
584
+ cells: NotebookCell[],
585
+ ) => Promise<void>,
586
+ ) => Promise<void>;
587
+ };
588
+ };
589
+
590
+ export interface NotebookDocumentSyncFeatureShape {
591
+ sendDidOpenNotebookDocument(notebookDocument: NotebookDocument): Promise<void>;
592
+ sendDidSaveNotebookDocument(notebookDocument: NotebookDocument): Promise<void>;
593
+ sendDidChangeNotebookDocument(event: VNotebookDocumentChangeEvent): Promise<void>;
594
+ sendDidCloseNotebookDocument(notebookDocument: NotebookDocument): Promise<void>;
595
+ }
596
+
597
+ class NotebookDocumentSyncFeatureProvider implements NotebookDocumentSyncFeatureShape {
598
+ private readonly client: FeatureClient<
599
+ NotebookDocumentMiddleware,
600
+ $NotebookDocumentOptions
601
+ >;
602
+ private readonly options: proto.NotebookDocumentSyncOptions;
603
+ private readonly notebookSyncInfo: Map<string, SyncInfo>;
604
+ private readonly notebookDidOpen: Set<string>;
605
+ private readonly disposables: Disposable[];
606
+ private readonly selector: DocumentSelector;
607
+
608
+ constructor(
609
+ client: FeatureClient<NotebookDocumentMiddleware, $NotebookDocumentOptions>,
610
+ options: proto.NotebookDocumentSyncOptions,
611
+ ) {
612
+ this.client = client;
613
+ this.options = options;
614
+ this.notebookSyncInfo = new Map();
615
+ this.notebookDidOpen = new Set();
616
+ this.disposables = [];
617
+ this.selector = client.protocol2CodeConverter.asDocumentSelector(
618
+ $NotebookDocumentSyncOptions.asDocumentSelector(options),
619
+ );
620
+
621
+ // open
622
+ workspace.onDidOpenNotebookDocument(
623
+ (notebookDocument) => {
624
+ this.notebookDidOpen.add(notebookDocument.uri.toString());
625
+ this.didOpen(notebookDocument);
626
+ },
627
+ undefined,
628
+ this.disposables,
629
+ );
630
+ for (const notebookDocument of workspace.notebookDocuments) {
631
+ this.notebookDidOpen.add(notebookDocument.uri.toString());
632
+ this.didOpen(notebookDocument);
633
+ }
634
+
635
+ // Notebook document changed.
636
+ workspace.onDidChangeNotebookDocument(
637
+ (event) => this.didChangeNotebookDocument(event),
638
+ undefined,
639
+ this.disposables,
640
+ );
641
+
642
+ //save
643
+ if (this.options.save === true) {
644
+ workspace.onDidSaveNotebookDocument(
645
+ (notebookDocument) => this.didSave(notebookDocument),
646
+ undefined,
647
+ this.disposables,
648
+ );
649
+ }
650
+
651
+ // close
652
+ workspace.onDidCloseNotebookDocument(
653
+ (notebookDocument) => {
654
+ this.didClose(notebookDocument);
655
+ this.notebookDidOpen.delete(notebookDocument.uri.toString());
656
+ },
657
+ undefined,
658
+ this.disposables,
659
+ );
660
+ }
661
+
662
+ public getState(): FeatureState {
663
+ for (const notebook of workspace.notebookDocuments) {
664
+ const matchingCells = this.getMatchingCells(notebook);
665
+ if (matchingCells !== undefined) {
666
+ return {
667
+ kind: 'document',
668
+ id: '$internal',
669
+ registrations: true,
670
+ matches: true,
671
+ };
672
+ }
673
+ }
674
+ return { kind: 'document', id: '$internal', registrations: true, matches: false };
675
+ }
676
+
677
+ public get mode(): 'notebook' {
678
+ return 'notebook';
679
+ }
680
+
681
+ public handles(textDocument: TextDocument): boolean {
682
+ return languages.match(this.selector, textDocument) > 0;
683
+ }
684
+
685
+ public didOpenNotebookCellTextDocument(
686
+ notebookDocument: NotebookDocument,
687
+ cell: NotebookCell,
688
+ ): void {
689
+ if (languages.match(this.selector, cell.document) === 0) {
690
+ return;
691
+ }
692
+ if (!this.notebookDidOpen.has(notebookDocument.uri.toString())) {
693
+ // We have never received an open notification for the notebook document.
694
+ // VS Code guarantees that we first get cell document open and then
695
+ // notebook open. So simply wait for the notebook open.
696
+ return;
697
+ }
698
+ const syncInfo = this.notebookSyncInfo.get(notebookDocument.uri.toString());
699
+ // In VS Code we receive a notebook open before a cell document open.
700
+ // The document and the cell is synced.
701
+ const cellMatches = this.cellMatches(notebookDocument, cell);
702
+ if (syncInfo !== undefined) {
703
+ const cellIsSynced = syncInfo.uris.has(cell.document.uri.toString());
704
+ if ((cellMatches && cellIsSynced) || (!cellMatches && !cellIsSynced)) {
705
+ // The cell doesn't match and was not synced or it matches and is synced.
706
+ // In both cases nothing to do.
707
+ //
708
+ // Note that if the language mode of a document changes we remove the
709
+ // cell and add it back to update the language mode on the server side.
710
+ return;
711
+ }
712
+ if (cellMatches) {
713
+ // don't use cells from above since there might be more matching cells in the notebook
714
+ // Since we had a matching cell above we will have matching cells now.
715
+ const matchingCells = this.getMatchingCells(notebookDocument);
716
+ if (matchingCells !== undefined) {
717
+ const event = this.asNotebookDocumentChangeEvent(
718
+ notebookDocument,
719
+ undefined,
720
+ syncInfo,
721
+ matchingCells,
722
+ );
723
+ if (event !== undefined) {
724
+ this.doSendChange(event, matchingCells).catch(() => {
725
+ /* handled in send change */
726
+ });
727
+ }
728
+ }
729
+ }
730
+ } else {
731
+ // No sync info. But we have a open event for the notebook document
732
+ // itself. If the cell matches then we need to send an open with
733
+ // exactly that cell.
734
+ if (cellMatches) {
735
+ this.doSendOpen(notebookDocument, [cell]).catch(() => {
736
+ /* handled in open */
737
+ });
738
+ }
739
+ }
740
+ }
741
+
742
+ public didChangeNotebookCellTextDocument(
743
+ notebookDocument: NotebookDocument,
744
+ event: TextDocumentChangeEvent,
745
+ ): void {
746
+ // No match with the selector
747
+ if (languages.match(this.selector, event.document) === 0) {
748
+ return;
749
+ }
750
+ this.doSendChange(
751
+ {
752
+ notebook: notebookDocument,
753
+ cells: { textContent: [event] },
754
+ },
755
+ undefined,
756
+ ).catch(() => {
757
+ /* error handled in doSendChange */
758
+ });
759
+ }
760
+
761
+ public didCloseNotebookCellTextDocument(
762
+ notebookDocument: NotebookDocument,
763
+ cell: NotebookCell,
764
+ ): void {
765
+ const syncInfo = this.notebookSyncInfo.get(notebookDocument.uri.toString());
766
+ if (syncInfo === undefined) {
767
+ // The notebook document got never synced. So it doesn't matter if a cell
768
+ // document closes.
769
+ return;
770
+ }
771
+ const cellUri = cell.document.uri;
772
+ const index = syncInfo.cells.findIndex(
773
+ (item) => item.document.uri.toString() === cellUri.toString(),
774
+ );
775
+ if (index === -1) {
776
+ // The cell never got synced or it got deleted and we now received the document
777
+ // close event.
778
+ return;
779
+ }
780
+ if (index === 0 && syncInfo.cells.length === 1) {
781
+ // The last cell. Close the notebook document in the server.
782
+ this.doSendClose(notebookDocument, syncInfo.cells).catch(() => {
783
+ /* error handled in doSendClose */
784
+ });
785
+ } else {
786
+ const newCells = syncInfo.cells.slice();
787
+ const deleted = newCells.splice(index, 1);
788
+ this.doSendChange(
789
+ {
790
+ notebook: notebookDocument,
791
+ cells: {
792
+ structure: {
793
+ array: { start: index, deleteCount: 1 },
794
+ didClose: deleted,
795
+ },
796
+ },
797
+ },
798
+ newCells,
799
+ ).catch(() => {
800
+ /* error handled in doSendChange */
801
+ });
802
+ }
803
+ }
804
+
805
+ public dispose(): void {
806
+ for (const disposable of this.disposables) {
807
+ disposable.dispose();
808
+ }
809
+ }
810
+
811
+ private didOpen(
812
+ notebookDocument: NotebookDocument,
813
+ matchingCells: NotebookCell[] | undefined = this.getMatchingCells(notebookDocument),
814
+ syncInfo: SyncInfo | undefined = this.notebookSyncInfo.get(
815
+ notebookDocument.uri.toString(),
816
+ ),
817
+ ): void {
818
+ if (syncInfo !== undefined) {
819
+ if (matchingCells !== undefined) {
820
+ const event = this.asNotebookDocumentChangeEvent(
821
+ notebookDocument,
822
+ undefined,
823
+ syncInfo,
824
+ matchingCells,
825
+ );
826
+ if (event !== undefined) {
827
+ this.doSendChange(event, matchingCells).catch(() => {
828
+ /* handled in send change */
829
+ });
830
+ }
831
+ } else {
832
+ this.doSendClose(notebookDocument, []).catch(() => {
833
+ /* handled in send close */
834
+ });
835
+ }
836
+ } else {
837
+ // Check if we need to sync the notebook document.
838
+ if (matchingCells === undefined) {
839
+ return;
840
+ }
841
+
842
+ this.doSendOpen(notebookDocument, matchingCells).catch(() => {
843
+ /* error handled in doSendOpen */
844
+ });
845
+ }
846
+ }
847
+
848
+ private didChangeNotebookDocument(event: NotebookDocumentChangeEvent): void {
849
+ const notebookDocument = event.notebook;
850
+ const syncInfo = this.notebookSyncInfo.get(notebookDocument.uri.toString());
851
+ if (syncInfo === undefined) {
852
+ // We have no changes to the cells. Since the notebook wasn't synced
853
+ // it will not be synced now.
854
+ if (event.contentChanges.length === 0) {
855
+ return;
856
+ }
857
+
858
+ // Check if we have new matching cells.
859
+ const cells = this.getMatchingCells(notebookDocument);
860
+
861
+ // No matching cells and the notebook never synced. So still no need
862
+ // to sync it.
863
+ if (cells === undefined) {
864
+ return;
865
+ }
866
+
867
+ // Open the notebook document and ignore the rest of the changes
868
+ // this the notebooks will be synced with the correct settings.
869
+ this.didOpen(notebookDocument, cells, syncInfo);
870
+ } else {
871
+ // The notebook is synced. First check if we have no matching
872
+ // cells anymore and if so close the notebook
873
+ const cells = this.getMatchingCells(notebookDocument);
874
+ if (cells === undefined) {
875
+ this.didClose(notebookDocument, syncInfo);
876
+ return;
877
+ }
878
+ const newEvent = this.asNotebookDocumentChangeEvent(
879
+ event.notebook,
880
+ event,
881
+ syncInfo,
882
+ cells,
883
+ );
884
+ if (newEvent !== undefined) {
885
+ this.doSendChange(newEvent, cells).catch(() => {
886
+ /* error handled in doSendChange */
887
+ });
888
+ }
889
+ }
890
+ }
891
+
892
+ private didSave(notebookDocument: NotebookDocument): void {
893
+ const syncInfo = this.notebookSyncInfo.get(notebookDocument.uri.toString());
894
+ if (syncInfo === undefined) {
895
+ return;
896
+ }
897
+ this.doSendSave(notebookDocument).catch(() => {
898
+ /* error handled in doSendSave */
899
+ });
900
+ }
901
+
902
+ private didClose(
903
+ notebookDocument: NotebookDocument,
904
+ syncInfo: SyncInfo | undefined = this.notebookSyncInfo.get(
905
+ notebookDocument.uri.toString(),
906
+ ),
907
+ ): void {
908
+ if (syncInfo === undefined) {
909
+ return;
910
+ }
911
+ const syncedCells = notebookDocument
912
+ .getCells()
913
+ .filter((cell) => syncInfo.uris.has(cell.document.uri.toString()));
914
+ this.doSendClose(notebookDocument, syncedCells).catch(() => {
915
+ /* error handled in doSendClose */
916
+ });
917
+ }
918
+
919
+ public async sendDidOpenNotebookDocument(
920
+ notebookDocument: NotebookDocument,
921
+ ): Promise<void> {
922
+ const cells = this.getMatchingCells(notebookDocument);
923
+ if (cells === undefined) {
924
+ return;
925
+ }
926
+ return this.doSendOpen(notebookDocument, cells);
927
+ }
928
+
929
+ private async doSendOpen(
930
+ notebookDocument: NotebookDocument,
931
+ cells: NotebookCell[],
932
+ ): Promise<void> {
933
+ const send = async (
934
+ notebookDocument: NotebookDocument,
935
+ cells: NotebookCell[],
936
+ ): Promise<void> => {
937
+ const nb = Converter.c2p.asNotebookDocument(
938
+ notebookDocument,
939
+ cells,
940
+ this.client.code2ProtocolConverter,
941
+ );
942
+ const cellDocuments: TextDocumentItem[] = cells.map((cell) =>
943
+ this.client.code2ProtocolConverter.asTextDocumentItem(cell.document),
944
+ );
945
+ try {
946
+ await this.client.sendNotification(
947
+ proto.DidOpenNotebookDocumentNotification.type,
948
+ {
949
+ notebookDocument: nb,
950
+ cellTextDocuments: cellDocuments,
951
+ },
952
+ );
953
+ } catch (error) {
954
+ this.client.error('Sending DidOpenNotebookDocumentNotification failed', error);
955
+ throw error;
956
+ }
957
+ };
958
+ const middleware = this.client.middleware?.notebooks;
959
+ this.notebookSyncInfo.set(notebookDocument.uri.toString(), SyncInfo.create(cells));
960
+ return middleware?.didOpen !== undefined
961
+ ? middleware.didOpen(notebookDocument, cells, send)
962
+ : send(notebookDocument, cells);
963
+ }
964
+
965
+ public async sendDidChangeNotebookDocument(
966
+ event: VNotebookDocumentChangeEvent,
967
+ ): Promise<void> {
968
+ return this.doSendChange(event, undefined);
969
+ }
970
+
971
+ private async doSendChange(
972
+ event: VNotebookDocumentChangeEvent,
973
+ cells: NotebookCell[] | undefined = this.getMatchingCells(event.notebook),
974
+ ): Promise<void> {
975
+ const send = async (event: VNotebookDocumentChangeEvent): Promise<void> => {
976
+ try {
977
+ await this.client.sendNotification(
978
+ proto.DidChangeNotebookDocumentNotification.type,
979
+ {
980
+ notebookDocument: Converter.c2p.asVersionedNotebookDocumentIdentifier(
981
+ event.notebook,
982
+ this.client.code2ProtocolConverter,
983
+ ),
984
+ change: Converter.c2p.asNotebookDocumentChangeEvent(
985
+ event,
986
+ this.client.code2ProtocolConverter,
987
+ ),
988
+ },
989
+ );
990
+ } catch (error) {
991
+ this.client.error(
992
+ 'Sending DidChangeNotebookDocumentNotification failed',
993
+ error,
994
+ );
995
+ throw error;
996
+ }
997
+ };
998
+ const middleware = this.client.middleware?.notebooks;
999
+ if (event.cells?.structure !== undefined) {
1000
+ this.notebookSyncInfo.set(
1001
+ event.notebook.uri.toString(),
1002
+ SyncInfo.create(cells ?? []),
1003
+ );
1004
+ }
1005
+ return middleware?.didChange !== undefined
1006
+ ? middleware?.didChange(event, send)
1007
+ : send(event);
1008
+ }
1009
+
1010
+ public async sendDidSaveNotebookDocument(
1011
+ notebookDocument: NotebookDocument,
1012
+ ): Promise<void> {
1013
+ return this.doSendSave(notebookDocument);
1014
+ }
1015
+
1016
+ private async doSendSave(notebookDocument: NotebookDocument): Promise<void> {
1017
+ const send = async (notebookDocument: NotebookDocument): Promise<void> => {
1018
+ try {
1019
+ await this.client.sendNotification(
1020
+ proto.DidSaveNotebookDocumentNotification.type,
1021
+ {
1022
+ notebookDocument: {
1023
+ uri: this.client.code2ProtocolConverter.asUri(notebookDocument.uri),
1024
+ },
1025
+ },
1026
+ );
1027
+ } catch (error) {
1028
+ this.client.error('Sending DidSaveNotebookDocumentNotification failed', error);
1029
+ throw error;
1030
+ }
1031
+ };
1032
+ const middleware = this.client.middleware?.notebooks;
1033
+ return middleware?.didSave !== undefined
1034
+ ? middleware.didSave(notebookDocument, send)
1035
+ : send(notebookDocument);
1036
+ }
1037
+
1038
+ public async sendDidCloseNotebookDocument(
1039
+ notebookDocument: NotebookDocument,
1040
+ ): Promise<void> {
1041
+ return this.doSendClose(
1042
+ notebookDocument,
1043
+ this.getMatchingCells(notebookDocument) ?? [],
1044
+ );
1045
+ }
1046
+
1047
+ private async doSendClose(
1048
+ notebookDocument: NotebookDocument,
1049
+ cells: NotebookCell[],
1050
+ ): Promise<void> {
1051
+ const send = async (
1052
+ notebookDocument: NotebookDocument,
1053
+ cells: NotebookCell[],
1054
+ ): Promise<void> => {
1055
+ try {
1056
+ await this.client.sendNotification(
1057
+ proto.DidCloseNotebookDocumentNotification.type,
1058
+ {
1059
+ notebookDocument: {
1060
+ uri: this.client.code2ProtocolConverter.asUri(notebookDocument.uri),
1061
+ },
1062
+ cellTextDocuments: cells.map((cell) =>
1063
+ this.client.code2ProtocolConverter.asTextDocumentIdentifier(
1064
+ cell.document,
1065
+ ),
1066
+ ),
1067
+ },
1068
+ );
1069
+ } catch (error) {
1070
+ this.client.error('Sending DidCloseNotebookDocumentNotification failed', error);
1071
+ throw error;
1072
+ }
1073
+ };
1074
+ const middleware = this.client.middleware?.notebooks;
1075
+ this.notebookSyncInfo.delete(notebookDocument.uri.toString());
1076
+ return middleware?.didClose !== undefined
1077
+ ? middleware.didClose(notebookDocument, cells, send)
1078
+ : send(notebookDocument, cells);
1079
+ }
1080
+
1081
+ private asNotebookDocumentChangeEvent(
1082
+ notebook: NotebookDocument,
1083
+ event: NotebookDocumentChangeEvent | undefined,
1084
+ syncInfo: SyncInfo,
1085
+ matchingCells: NotebookCell[],
1086
+ ): VNotebookDocumentChangeEvent | undefined {
1087
+ if (event !== undefined && event.notebook !== notebook) {
1088
+ throw new Error('Notebook must be identical');
1089
+ }
1090
+ const result: VNotebookDocumentChangeEvent = {
1091
+ notebook: notebook,
1092
+ };
1093
+
1094
+ if (event?.metadata !== undefined) {
1095
+ result.metadata = Converter.c2p.asMetadata(event.metadata);
1096
+ }
1097
+
1098
+ let matchingCellsSet: Set<string> | undefined;
1099
+ if (event?.cellChanges !== undefined && event.cellChanges.length > 0) {
1100
+ const data: NotebookCell[] = [];
1101
+ // Only consider the new matching cells.
1102
+ matchingCellsSet = new Set(
1103
+ matchingCells.map((cell) => cell.document.uri.toString()),
1104
+ );
1105
+ for (const cellChange of event.cellChanges) {
1106
+ if (
1107
+ matchingCellsSet.has(cellChange.cell.document.uri.toString()) &&
1108
+ (cellChange.executionSummary !== undefined ||
1109
+ cellChange.metadata !== undefined)
1110
+ ) {
1111
+ data.push(cellChange.cell);
1112
+ }
1113
+ }
1114
+ if (data.length > 0) {
1115
+ result.cells = result.cells ?? {};
1116
+ result.cells.data = data;
1117
+ }
1118
+ }
1119
+
1120
+ if (
1121
+ ((event?.contentChanges !== undefined && event.contentChanges.length > 0) ||
1122
+ event === undefined) &&
1123
+ syncInfo !== undefined &&
1124
+ matchingCells !== undefined
1125
+ ) {
1126
+ // We still have matching cells. Check if the cell changes
1127
+ // affect the notebook on the server side.
1128
+ const oldCells = syncInfo.cells;
1129
+ const newCells = matchingCells;
1130
+
1131
+ // meta data changes are reported using on the cell itself. So we can ignore comparing
1132
+ // it which has a positive effect on performance.
1133
+ const diff = $NotebookCell.computeDiff(oldCells, newCells, false);
1134
+ let addedCells: Map<string, NotebookCell> | undefined;
1135
+ let removedCells: Map<string, NotebookCell> | undefined;
1136
+ if (diff !== undefined) {
1137
+ addedCells =
1138
+ diff.cells === undefined
1139
+ ? new Map()
1140
+ : new Map(diff.cells.map((cell) => [cell.document.uri.toString(), cell]));
1141
+ removedCells =
1142
+ diff.deleteCount === 0
1143
+ ? new Map()
1144
+ : new Map(
1145
+ oldCells
1146
+ .slice(diff.start, diff.start + diff.deleteCount)
1147
+ .map((cell) => [cell.document.uri.toString(), cell]),
1148
+ );
1149
+
1150
+ // Remove the onces that got deleted and inserted again.
1151
+ for (const key of Array.from(removedCells.keys())) {
1152
+ if (addedCells.has(key)) {
1153
+ removedCells.delete(key);
1154
+ addedCells.delete(key);
1155
+ }
1156
+ }
1157
+ result.cells = result.cells ?? {};
1158
+ type structure = Required<
1159
+ Required<Required<VNotebookDocumentChangeEvent>['cells']>['structure']
1160
+ >;
1161
+ const didOpen: structure['didOpen'] = [];
1162
+ const didClose: structure['didClose'] = [];
1163
+ if (addedCells.size > 0 || removedCells.size > 0) {
1164
+ for (const cell of addedCells.values()) {
1165
+ didOpen.push(cell);
1166
+ }
1167
+ for (const cell of removedCells.values()) {
1168
+ didClose.push(cell);
1169
+ }
1170
+ }
1171
+ result.cells.structure = {
1172
+ array: diff,
1173
+ didOpen,
1174
+ didClose,
1175
+ };
1176
+ }
1177
+ }
1178
+ // The notebook is a property as well.
1179
+ return Object.keys(result).length > 1 ? result : undefined;
1180
+ }
1181
+
1182
+ private getMatchingCells(
1183
+ notebookDocument: NotebookDocument,
1184
+ cells: NotebookCell[] = notebookDocument.getCells(),
1185
+ ): NotebookCell[] | undefined {
1186
+ if (this.options.notebookSelector === undefined) {
1187
+ return undefined;
1188
+ }
1189
+ for (const item of this.options.notebookSelector) {
1190
+ if (
1191
+ item.notebook === undefined ||
1192
+ $NotebookDocumentFilter.matchNotebook(item.notebook, notebookDocument)
1193
+ ) {
1194
+ const filtered = this.filterCells(notebookDocument, cells, item.cells);
1195
+ return filtered.length === 0 ? undefined : filtered;
1196
+ }
1197
+ }
1198
+ return undefined;
1199
+ }
1200
+
1201
+ private cellMatches(notebookDocument: NotebookDocument, cell: NotebookCell) {
1202
+ const cells = this.getMatchingCells(notebookDocument, [cell]);
1203
+ return cells !== undefined && cells[0] === cell;
1204
+ }
1205
+
1206
+ private filterCells(
1207
+ notebookDocument: NotebookDocument,
1208
+ cells: NotebookCell[],
1209
+ cellSelector: undefined | { language: string }[],
1210
+ ): NotebookCell[] {
1211
+ const filtered =
1212
+ cellSelector !== undefined
1213
+ ? cells.filter((cell) => {
1214
+ const cellLanguage = cell.document.languageId;
1215
+ return cellSelector.some(
1216
+ (filter) => filter.language === '*' || cellLanguage === filter.language,
1217
+ );
1218
+ })
1219
+ : cells;
1220
+ return typeof this.client.clientOptions.notebookDocumentOptions?.filterCells ===
1221
+ 'function'
1222
+ ? this.client.clientOptions.notebookDocumentOptions.filterCells(
1223
+ notebookDocument,
1224
+ filtered,
1225
+ )
1226
+ : filtered;
1227
+ }
1228
+ }
1229
+
1230
+ export type $NotebookCellTextDocumentFilter = NotebookCellTextDocumentFilter & {
1231
+ sync: true;
1232
+ };
1233
+
1234
+ export type NotebookDocumentProviderShape = {
1235
+ getProvider(notebookCell: NotebookCell): NotebookDocumentSyncFeatureShape | undefined;
1236
+ };
1237
+
1238
+ export class NotebookDocumentSyncFeature
1239
+ implements
1240
+ DynamicFeature<proto.NotebookDocumentSyncRegistrationOptions>,
1241
+ NotebookDocumentProviderShape
1242
+ {
1243
+ public static readonly CellScheme: string = LibroCellURIScheme;
1244
+
1245
+ private readonly client: FeatureClient<
1246
+ NotebookDocumentMiddleware,
1247
+ $NotebookDocumentOptions
1248
+ >;
1249
+ private readonly registrations: Map<string, NotebookDocumentSyncFeatureProvider>;
1250
+ private dedicatedChannel: DocumentSelector | undefined;
1251
+
1252
+ constructor(
1253
+ client: FeatureClient<NotebookDocumentMiddleware, $NotebookDocumentOptions>,
1254
+ ) {
1255
+ this.client = client;
1256
+ this.registrations = new Map();
1257
+ this.registrationType = proto.NotebookDocumentSyncRegistrationType.type;
1258
+ // We don't receive an event for cells where the document changes its language mode
1259
+ // Since we allow servers to filter on the language mode we fire such an event ourselves.
1260
+ workspace.onDidOpenTextDocument((textDocument) => {
1261
+ if (textDocument.uri.scheme !== NotebookDocumentSyncFeature.CellScheme) {
1262
+ return;
1263
+ }
1264
+ const [notebookDocument, notebookCell] =
1265
+ this.findNotebookDocumentAndCell(textDocument);
1266
+ if (notebookDocument === undefined || notebookCell === undefined) {
1267
+ return;
1268
+ }
1269
+ for (const provider of this.registrations.values()) {
1270
+ if (provider instanceof NotebookDocumentSyncFeatureProvider) {
1271
+ provider.didOpenNotebookCellTextDocument(notebookDocument, notebookCell);
1272
+ }
1273
+ }
1274
+ });
1275
+ workspace.onDidChangeTextDocument((event) => {
1276
+ if (event.contentChanges.length === 0) {
1277
+ return;
1278
+ }
1279
+ const textDocument = event.document;
1280
+ if (textDocument.uri.scheme !== NotebookDocumentSyncFeature.CellScheme) {
1281
+ return;
1282
+ }
1283
+ const [notebookDocument] = this.findNotebookDocumentAndCell(textDocument);
1284
+ if (notebookDocument === undefined) {
1285
+ return;
1286
+ }
1287
+ for (const provider of this.registrations.values()) {
1288
+ if (provider instanceof NotebookDocumentSyncFeatureProvider) {
1289
+ provider.didChangeNotebookCellTextDocument(notebookDocument, event);
1290
+ }
1291
+ }
1292
+ });
1293
+ workspace.onDidCloseTextDocument((textDocument) => {
1294
+ if (textDocument.uri.scheme !== NotebookDocumentSyncFeature.CellScheme) {
1295
+ return;
1296
+ }
1297
+ // There are two cases when we receive a close for a text document
1298
+ // 1: the cell got removed. This is handled in `onDidChangeNotebookCells`
1299
+ // 2: the language mode of a cell changed. This keeps the URI stable so
1300
+ // we will still find the cell and the notebook document.
1301
+ const [notebookDocument, notebookCell] =
1302
+ this.findNotebookDocumentAndCell(textDocument);
1303
+ if (notebookDocument === undefined || notebookCell === undefined) {
1304
+ return;
1305
+ }
1306
+ for (const provider of this.registrations.values()) {
1307
+ if (provider instanceof NotebookDocumentSyncFeatureProvider) {
1308
+ provider.didCloseNotebookCellTextDocument(notebookDocument, notebookCell);
1309
+ }
1310
+ }
1311
+ });
1312
+ }
1313
+
1314
+ getState(): FeatureState {
1315
+ if (this.registrations.size === 0) {
1316
+ return {
1317
+ kind: 'document',
1318
+ id: this.registrationType.method,
1319
+ registrations: false,
1320
+ matches: false,
1321
+ };
1322
+ }
1323
+ for (const provider of this.registrations.values()) {
1324
+ const state = provider.getState();
1325
+ if (
1326
+ state.kind === 'document' &&
1327
+ state.registrations === true &&
1328
+ state.matches === true
1329
+ ) {
1330
+ return {
1331
+ kind: 'document',
1332
+ id: this.registrationType.method,
1333
+ registrations: true,
1334
+ matches: true,
1335
+ };
1336
+ }
1337
+ }
1338
+ return {
1339
+ kind: 'document',
1340
+ id: this.registrationType.method,
1341
+ registrations: true,
1342
+ matches: false,
1343
+ };
1344
+ }
1345
+
1346
+ public readonly registrationType: proto.RegistrationType<proto.NotebookDocumentSyncRegistrationOptions>;
1347
+
1348
+ public fillClientCapabilities(capabilities: proto.ClientCapabilities): void {
1349
+ const synchronization = ensure(
1350
+ ensure(capabilities, 'notebookDocument')!,
1351
+ 'synchronization',
1352
+ )!;
1353
+ synchronization.dynamicRegistration = true;
1354
+ synchronization.executionSummarySupport = true;
1355
+ }
1356
+
1357
+ public preInitialize(capabilities: proto.ServerCapabilities<any>): void {
1358
+ const options = capabilities.notebookDocumentSync;
1359
+ if (options === undefined) {
1360
+ return;
1361
+ }
1362
+ this.dedicatedChannel = this.client.protocol2CodeConverter.asDocumentSelector(
1363
+ $NotebookDocumentSyncOptions.asDocumentSelector(options),
1364
+ );
1365
+ }
1366
+
1367
+ public initialize(capabilities: proto.ServerCapabilities<any>): void {
1368
+ const options = capabilities.notebookDocumentSync;
1369
+ if (options === undefined) {
1370
+ return;
1371
+ }
1372
+ const id = (options as StaticRegistrationOptions).id ?? UUID.generateUuid();
1373
+ this.register({ id, registerOptions: options });
1374
+ }
1375
+
1376
+ public register(
1377
+ data: RegistrationData<proto.NotebookDocumentSyncRegistrationOptions>,
1378
+ ): void {
1379
+ const provider = new NotebookDocumentSyncFeatureProvider(
1380
+ this.client,
1381
+ data.registerOptions,
1382
+ );
1383
+ this.registrations.set(data.id, provider);
1384
+ }
1385
+
1386
+ public unregister(id: string): void {
1387
+ const provider = this.registrations.get(id);
1388
+ if (provider !== undefined) {
1389
+ this.registrations.delete(id);
1390
+ provider.dispose();
1391
+ }
1392
+ }
1393
+
1394
+ public clear(): void {
1395
+ for (const provider of this.registrations.values()) {
1396
+ provider.dispose();
1397
+ }
1398
+ this.registrations.clear();
1399
+ }
1400
+
1401
+ public handles(textDocument: TextDocument): boolean {
1402
+ if (textDocument.uri.scheme !== NotebookDocumentSyncFeature.CellScheme) {
1403
+ return false;
1404
+ }
1405
+ if (
1406
+ this.dedicatedChannel !== undefined &&
1407
+ languages.match(this.dedicatedChannel, textDocument) > 0
1408
+ ) {
1409
+ return true;
1410
+ }
1411
+ for (const provider of this.registrations.values()) {
1412
+ if (provider.handles(textDocument)) {
1413
+ return true;
1414
+ }
1415
+ }
1416
+ return false;
1417
+ }
1418
+
1419
+ public getProvider(
1420
+ notebookCell: NotebookCell,
1421
+ ): NotebookDocumentSyncFeatureShape | undefined {
1422
+ for (const provider of this.registrations.values()) {
1423
+ if (provider.handles(notebookCell.document)) {
1424
+ return provider;
1425
+ }
1426
+ }
1427
+ return undefined;
1428
+ }
1429
+
1430
+ private findNotebookDocumentAndCell(
1431
+ textDocument: TextDocument,
1432
+ ): [NotebookDocument | undefined, NotebookCell | undefined] {
1433
+ const uri = textDocument.uri.toString();
1434
+ for (const notebookDocument of workspace.notebookDocuments) {
1435
+ for (const cell of notebookDocument.getCells()) {
1436
+ if (cell.document.uri.toString() === uri) {
1437
+ return [notebookDocument, cell];
1438
+ }
1439
+ }
1440
+ }
1441
+ return [undefined, undefined];
1442
+ }
1443
+ }