@fazelstudio/floe 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/LICENSE +21 -0
  2. package/dist/src/cli/check.d.ts +14 -0
  3. package/dist/src/cli/check.d.ts.map +1 -0
  4. package/dist/src/cli/check.js +88 -0
  5. package/dist/src/cli/check.js.map +1 -0
  6. package/dist/src/cli/format.d.ts +9 -0
  7. package/dist/src/cli/format.d.ts.map +1 -0
  8. package/dist/src/cli/format.js +114 -0
  9. package/dist/src/cli/format.js.map +1 -0
  10. package/dist/src/cli/main.d.ts +19 -0
  11. package/dist/src/cli/main.d.ts.map +1 -0
  12. package/dist/src/cli/main.js +207 -0
  13. package/dist/src/cli/main.js.map +1 -0
  14. package/dist/src/cli/render.d.ts +7 -0
  15. package/dist/src/cli/render.d.ts.map +1 -0
  16. package/dist/src/cli/render.js +90 -0
  17. package/dist/src/cli/render.js.map +1 -0
  18. package/dist/src/diagnostics.d.ts +32 -0
  19. package/dist/src/diagnostics.d.ts.map +1 -0
  20. package/dist/src/diagnostics.js +4 -0
  21. package/dist/src/diagnostics.js.map +1 -0
  22. package/dist/src/index.d.ts +40 -0
  23. package/dist/src/index.d.ts.map +1 -0
  24. package/dist/src/index.js +64 -0
  25. package/dist/src/index.js.map +1 -0
  26. package/dist/src/language/completion.d.ts +6 -0
  27. package/dist/src/language/completion.d.ts.map +1 -0
  28. package/dist/src/language/completion.js +363 -0
  29. package/dist/src/language/completion.js.map +1 -0
  30. package/dist/src/language/definitions.d.ts +9 -0
  31. package/dist/src/language/definitions.d.ts.map +1 -0
  32. package/dist/src/language/definitions.js +70 -0
  33. package/dist/src/language/definitions.js.map +1 -0
  34. package/dist/src/language/diagnosticsService.d.ts +14 -0
  35. package/dist/src/language/diagnosticsService.d.ts.map +1 -0
  36. package/dist/src/language/diagnosticsService.js +37 -0
  37. package/dist/src/language/diagnosticsService.js.map +1 -0
  38. package/dist/src/language/folding.d.ts +8 -0
  39. package/dist/src/language/folding.d.ts.map +1 -0
  40. package/dist/src/language/folding.js +58 -0
  41. package/dist/src/language/folding.js.map +1 -0
  42. package/dist/src/language/formatting.d.ts +5 -0
  43. package/dist/src/language/formatting.d.ts.map +1 -0
  44. package/dist/src/language/formatting.js +274 -0
  45. package/dist/src/language/formatting.js.map +1 -0
  46. package/dist/src/language/highlighting.d.ts +9 -0
  47. package/dist/src/language/highlighting.d.ts.map +1 -0
  48. package/dist/src/language/highlighting.js +74 -0
  49. package/dist/src/language/highlighting.js.map +1 -0
  50. package/dist/src/language/hover.d.ts +6 -0
  51. package/dist/src/language/hover.d.ts.map +1 -0
  52. package/dist/src/language/hover.js +197 -0
  53. package/dist/src/language/hover.js.map +1 -0
  54. package/dist/src/language/indentation.d.ts +15 -0
  55. package/dist/src/language/indentation.d.ts.map +1 -0
  56. package/dist/src/language/indentation.js +97 -0
  57. package/dist/src/language/indentation.js.map +1 -0
  58. package/dist/src/language/index.d.ts +54 -0
  59. package/dist/src/language/index.d.ts.map +1 -0
  60. package/dist/src/language/index.js +85 -0
  61. package/dist/src/language/index.js.map +1 -0
  62. package/dist/src/language/references.d.ts +12 -0
  63. package/dist/src/language/references.d.ts.map +1 -0
  64. package/dist/src/language/references.js +35 -0
  65. package/dist/src/language/references.js.map +1 -0
  66. package/dist/src/language/rename.d.ts +18 -0
  67. package/dist/src/language/rename.d.ts.map +1 -0
  68. package/dist/src/language/rename.js +53 -0
  69. package/dist/src/language/rename.js.map +1 -0
  70. package/dist/src/language/semanticModel.d.ts +21 -0
  71. package/dist/src/language/semanticModel.d.ts.map +1 -0
  72. package/dist/src/language/semanticModel.js +43 -0
  73. package/dist/src/language/semanticModel.js.map +1 -0
  74. package/dist/src/language/symbols.d.ts +9 -0
  75. package/dist/src/language/symbols.d.ts.map +1 -0
  76. package/dist/src/language/symbols.js +160 -0
  77. package/dist/src/language/symbols.js.map +1 -0
  78. package/dist/src/language/types.d.ts +49 -0
  79. package/dist/src/language/types.d.ts.map +1 -0
  80. package/dist/src/language/types.js +2 -0
  81. package/dist/src/language/types.js.map +1 -0
  82. package/dist/src/language/utils.d.ts +24 -0
  83. package/dist/src/language/utils.d.ts.map +1 -0
  84. package/dist/src/language/utils.js +154 -0
  85. package/dist/src/language/utils.js.map +1 -0
  86. package/dist/src/layout/dagre.d.ts +13 -0
  87. package/dist/src/layout/dagre.d.ts.map +1 -0
  88. package/dist/src/layout/dagre.js +229 -0
  89. package/dist/src/layout/dagre.js.map +1 -0
  90. package/dist/src/layout/index.d.ts +15 -0
  91. package/dist/src/layout/index.d.ts.map +1 -0
  92. package/dist/src/layout/index.js +22 -0
  93. package/dist/src/layout/index.js.map +1 -0
  94. package/dist/src/layout/simple.d.ts +20 -0
  95. package/dist/src/layout/simple.d.ts.map +1 -0
  96. package/dist/src/layout/simple.js +674 -0
  97. package/dist/src/layout/simple.js.map +1 -0
  98. package/dist/src/layout/types.d.ts +65 -0
  99. package/dist/src/layout/types.d.ts.map +1 -0
  100. package/dist/src/layout/types.js +2 -0
  101. package/dist/src/layout/types.js.map +1 -0
  102. package/dist/src/lexer.d.ts +25 -0
  103. package/dist/src/lexer.d.ts.map +1 -0
  104. package/dist/src/lexer.js +280 -0
  105. package/dist/src/lexer.js.map +1 -0
  106. package/dist/src/lsp/documents.d.ts +24 -0
  107. package/dist/src/lsp/documents.d.ts.map +1 -0
  108. package/dist/src/lsp/documents.js +97 -0
  109. package/dist/src/lsp/documents.js.map +1 -0
  110. package/dist/src/lsp/server.d.ts +70 -0
  111. package/dist/src/lsp/server.d.ts.map +1 -0
  112. package/dist/src/lsp/server.js +623 -0
  113. package/dist/src/lsp/server.js.map +1 -0
  114. package/dist/src/lsp/utils.d.ts +47 -0
  115. package/dist/src/lsp/utils.d.ts.map +1 -0
  116. package/dist/src/lsp/utils.js +142 -0
  117. package/dist/src/lsp/utils.js.map +1 -0
  118. package/dist/src/parser.d.ts +106 -0
  119. package/dist/src/parser.d.ts.map +1 -0
  120. package/dist/src/parser.js +1018 -0
  121. package/dist/src/parser.js.map +1 -0
  122. package/dist/src/pipeline.d.ts +51 -0
  123. package/dist/src/pipeline.d.ts.map +1 -0
  124. package/dist/src/pipeline.js +65 -0
  125. package/dist/src/pipeline.js.map +1 -0
  126. package/dist/src/range.d.ts +24 -0
  127. package/dist/src/range.d.ts.map +1 -0
  128. package/dist/src/range.js +20 -0
  129. package/dist/src/range.js.map +1 -0
  130. package/dist/src/render/index.d.ts +3 -0
  131. package/dist/src/render/index.d.ts.map +1 -0
  132. package/dist/src/render/index.js +3 -0
  133. package/dist/src/render/index.js.map +1 -0
  134. package/dist/src/render/shapes.d.ts +20 -0
  135. package/dist/src/render/shapes.d.ts.map +1 -0
  136. package/dist/src/render/shapes.js +110 -0
  137. package/dist/src/render/shapes.js.map +1 -0
  138. package/dist/src/render/svg.d.ts +19 -0
  139. package/dist/src/render/svg.d.ts.map +1 -0
  140. package/dist/src/render/svg.js +227 -0
  141. package/dist/src/render/svg.js.map +1 -0
  142. package/dist/src/security/url.d.ts +7 -0
  143. package/dist/src/security/url.d.ts.map +1 -0
  144. package/dist/src/security/url.js +27 -0
  145. package/dist/src/security/url.js.map +1 -0
  146. package/dist/src/types.d.ts +111 -0
  147. package/dist/src/types.d.ts.map +1 -0
  148. package/dist/src/types.js +16 -0
  149. package/dist/src/types.js.map +1 -0
  150. package/dist/src/validator.d.ts +47 -0
  151. package/dist/src/validator.d.ts.map +1 -0
  152. package/dist/src/validator.js +235 -0
  153. package/dist/src/validator.js.map +1 -0
  154. package/package.json +90 -0
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Floe LSP Server — editor-independent Language Server
3
+ *
4
+ * Architecture:
5
+ * Editor
6
+ * ↓ (LSP JSON-RPC over stdio)
7
+ * Floe LSP Server (this file)
8
+ * ↓
9
+ * Floe Language Services (src/language/*)
10
+ * ↓
11
+ * Parser / Semantic Model
12
+ *
13
+ * Capabilities:
14
+ * - diagnostics (publishDiagnostics)
15
+ * - completion
16
+ * - hover
17
+ * - go-to-definition
18
+ * - references
19
+ * - rename
20
+ * - formatting
21
+ * - symbols (documentSymbol)
22
+ * - folding (foldingRange)
23
+ *
24
+ * Reuses existing language services rather than duplicate parser logic.
25
+ * No editor-specific semantic implementation.
26
+ * No eval / new Function — treat .floe as untrusted.
27
+ */
28
+ export interface LspServerOptions {
29
+ stdio?: boolean;
30
+ input?: NodeJS.ReadableStream;
31
+ output?: NodeJS.WritableStream;
32
+ }
33
+ export declare class FloeLspServer {
34
+ private docs;
35
+ private input;
36
+ private output;
37
+ private buffer;
38
+ private nextId;
39
+ private initialized;
40
+ private shutdownRequested;
41
+ constructor(opts?: LspServerOptions);
42
+ /**
43
+ * Start server listening on stdio
44
+ */
45
+ start(): Promise<void>;
46
+ private processBuffer;
47
+ private send;
48
+ private sendResponse;
49
+ private sendError;
50
+ private sendNotification;
51
+ private handleMessage;
52
+ private handleRequest;
53
+ private handleNotification;
54
+ private handleInitialize;
55
+ private handleDidOpen;
56
+ private handleDidChange;
57
+ private handleDidClose;
58
+ private publishDiagnostics;
59
+ private handleCompletion;
60
+ private handleHover;
61
+ private handleDefinition;
62
+ private handleReferences;
63
+ private handlePrepareRename;
64
+ private handleRename;
65
+ private handleFormatting;
66
+ private handleDocumentSymbol;
67
+ private handleFoldingRange;
68
+ }
69
+ export declare function startLspServer(opts?: LspServerOptions): Promise<FloeLspServer>;
70
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/lsp/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AA6BH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB,KAAK,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;CAChC;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,IAAI,CAAyB;IACrC,OAAO,CAAC,KAAK,CAAwB;IACrC,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,iBAAiB,CAAS;gBAEtB,IAAI,GAAE,gBAAqB;IAKvC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAyB5B,OAAO,CAAC,aAAa;IA6BrB,OAAO,CAAC,IAAI;IAMZ,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,gBAAgB;YAIV,aAAa;YAeb,aAAa;YA0Eb,kBAAkB;IA2ChC,OAAO,CAAC,gBAAgB;IAkCxB,OAAO,CAAC,aAAa;IAWrB,OAAO,CAAC,eAAe;IAqBvB,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,kBAAkB;IA4B1B,OAAO,CAAC,gBAAgB;IA0BxB,OAAO,CAAC,WAAW;IAoBnB,OAAO,CAAC,gBAAgB;IAqBxB,OAAO,CAAC,gBAAgB;IAmBxB,OAAO,CAAC,mBAAmB;IA0B3B,OAAO,CAAC,YAAY;IA+BpB,OAAO,CAAC,gBAAgB;IAsBxB,OAAO,CAAC,oBAAoB;IA8B5B,OAAO,CAAC,kBAAkB;CAmB3B;AA0BD,wBAAsB,cAAc,CAAC,IAAI,GAAE,gBAAqB,GAAG,OAAO,CAAC,aAAa,CAAC,CAIxF"}
@@ -0,0 +1,623 @@
1
+ /**
2
+ * Floe LSP Server — editor-independent Language Server
3
+ *
4
+ * Architecture:
5
+ * Editor
6
+ * ↓ (LSP JSON-RPC over stdio)
7
+ * Floe LSP Server (this file)
8
+ * ↓
9
+ * Floe Language Services (src/language/*)
10
+ * ↓
11
+ * Parser / Semantic Model
12
+ *
13
+ * Capabilities:
14
+ * - diagnostics (publishDiagnostics)
15
+ * - completion
16
+ * - hover
17
+ * - go-to-definition
18
+ * - references
19
+ * - rename
20
+ * - formatting
21
+ * - symbols (documentSymbol)
22
+ * - folding (foldingRange)
23
+ *
24
+ * Reuses existing language services rather than duplicate parser logic.
25
+ * No editor-specific semantic implementation.
26
+ * No eval / new Function — treat .floe as untrusted.
27
+ */
28
+ import * as readline from "node:readline";
29
+ import { DocumentManager } from "./documents.js";
30
+ import { offsetToLspPosition, lspPositionToOffset, floeRangeToLspRange, severityToLsp } from "./utils.js";
31
+ // Reuse language services — these are editor-independent
32
+ import { getDiagnostics } from "../language/diagnosticsService.js";
33
+ import { getCompletions } from "../language/completion.js";
34
+ import { getHover } from "../language/hover.js";
35
+ import { getDefinition } from "../language/definitions.js";
36
+ import { getReferences } from "../language/references.js";
37
+ import { rename } from "../language/rename.js";
38
+ import { format } from "../language/formatting.js";
39
+ import { getSymbols } from "../language/symbols.js";
40
+ import { getFoldingRanges } from "../language/folding.js";
41
+ export class FloeLspServer {
42
+ docs = new DocumentManager();
43
+ input;
44
+ output;
45
+ buffer = "";
46
+ nextId = 1;
47
+ initialized = false;
48
+ shutdownRequested = false;
49
+ constructor(opts = {}) {
50
+ this.input = opts.input ?? process.stdin;
51
+ this.output = opts.output ?? process.stdout;
52
+ }
53
+ /**
54
+ * Start server listening on stdio
55
+ */
56
+ async start() {
57
+ this.input.on("data", (chunk) => {
58
+ this.buffer += chunk.toString("utf-8");
59
+ this.processBuffer();
60
+ });
61
+ this.input.on("end", () => {
62
+ if (!this.shutdownRequested) {
63
+ // Client closed
64
+ process.exit(0);
65
+ }
66
+ });
67
+ // Handle errors
68
+ this.input.on("error", (err) => {
69
+ console.error(`LSP input error: ${err.message}`);
70
+ });
71
+ // Also handle SIGPIPE? Ensure we stay alive
72
+ // For testing, return immediately; for prod, keep alive
73
+ // We don't block here; event loop will keep running via stdin listener
74
+ if (this.input.isTTY) {
75
+ // No-op for test environments where input is not a stream
76
+ }
77
+ }
78
+ processBuffer() {
79
+ while (true) {
80
+ // LSP header: Content-Length: <num>\r\n\r\n
81
+ const headerEnd = this.buffer.indexOf("\r\n\r\n");
82
+ if (headerEnd === -1)
83
+ break;
84
+ const header = this.buffer.slice(0, headerEnd);
85
+ const match = header.match(/Content-Length:\s*(\d+)/i);
86
+ if (!match) {
87
+ // Malformed header, discard
88
+ this.buffer = this.buffer.slice(headerEnd + 4);
89
+ continue;
90
+ }
91
+ const length = parseInt(match[1], 10);
92
+ const totalLength = headerEnd + 4 + length;
93
+ if (this.buffer.length < totalLength)
94
+ break; // Wait for more data
95
+ const body = this.buffer.slice(headerEnd + 4, totalLength);
96
+ this.buffer = this.buffer.slice(totalLength);
97
+ try {
98
+ const msg = JSON.parse(body);
99
+ this.handleMessage(msg);
100
+ }
101
+ catch (e) {
102
+ this.sendError(null, -32700, `Parse error: ${e instanceof Error ? e.message : String(e)}`);
103
+ }
104
+ }
105
+ }
106
+ send(msg) {
107
+ const body = JSON.stringify(msg);
108
+ const header = `Content-Length: ${Buffer.byteLength(body, "utf-8")}\r\n\r\n`;
109
+ this.output.write(header + body);
110
+ }
111
+ sendResponse(id, result) {
112
+ this.send({ jsonrpc: "2.0", id, result });
113
+ }
114
+ sendError(id, code, message, data) {
115
+ this.send({ jsonrpc: "2.0", id, error: { code, message, data } });
116
+ }
117
+ sendNotification(method, params) {
118
+ this.send({ jsonrpc: "2.0", method, params });
119
+ }
120
+ async handleMessage(msg) {
121
+ if (msg.method) {
122
+ // Request or notification
123
+ if (msg.id !== undefined) {
124
+ // Request - needs response
125
+ await this.handleRequest(msg);
126
+ }
127
+ else {
128
+ // Notification
129
+ await this.handleNotification(msg);
130
+ }
131
+ }
132
+ else if (msg.id !== undefined && (msg.result !== undefined || msg.error !== undefined)) {
133
+ // Response to our request (not used currently)
134
+ }
135
+ }
136
+ async handleRequest(msg) {
137
+ const method = msg.method;
138
+ const id = msg.id ?? null;
139
+ const params = msg.params;
140
+ try {
141
+ switch (method) {
142
+ case "initialize": {
143
+ const result = this.handleInitialize(params);
144
+ this.sendResponse(id, result);
145
+ break;
146
+ }
147
+ case "shutdown": {
148
+ this.shutdownRequested = true;
149
+ this.sendResponse(id, null);
150
+ break;
151
+ }
152
+ case "textDocument/completion": {
153
+ const result = this.handleCompletion(params);
154
+ this.sendResponse(id, result);
155
+ break;
156
+ }
157
+ case "textDocument/hover": {
158
+ const result = this.handleHover(params);
159
+ this.sendResponse(id, result);
160
+ break;
161
+ }
162
+ case "textDocument/definition": {
163
+ const result = this.handleDefinition(params);
164
+ this.sendResponse(id, result);
165
+ break;
166
+ }
167
+ case "textDocument/references": {
168
+ const result = this.handleReferences(params);
169
+ this.sendResponse(id, result);
170
+ break;
171
+ }
172
+ case "textDocument/rename": {
173
+ const result = this.handleRename(params);
174
+ this.sendResponse(id, result);
175
+ break;
176
+ }
177
+ case "textDocument/formatting": {
178
+ const result = this.handleFormatting(params);
179
+ this.sendResponse(id, result);
180
+ break;
181
+ }
182
+ case "textDocument/documentSymbol": {
183
+ const result = this.handleDocumentSymbol(params);
184
+ this.sendResponse(id, result);
185
+ break;
186
+ }
187
+ case "textDocument/prepareRename": {
188
+ const result = this.handlePrepareRename(params);
189
+ this.sendResponse(id, result);
190
+ break;
191
+ }
192
+ case "textDocument/foldingRange": {
193
+ const result = this.handleFoldingRange(params);
194
+ this.sendResponse(id, result);
195
+ break;
196
+ }
197
+ default: {
198
+ // Method not found
199
+ this.sendError(id, -32601, `Method not found: ${method}`);
200
+ break;
201
+ }
202
+ }
203
+ }
204
+ catch (e) {
205
+ const msgText = e instanceof Error ? e.message : String(e);
206
+ this.sendError(id, -32603, `Internal error: ${msgText}`);
207
+ }
208
+ }
209
+ async handleNotification(msg) {
210
+ const method = msg.method;
211
+ const params = msg.params;
212
+ switch (method) {
213
+ case "initialized": {
214
+ this.initialized = true;
215
+ break;
216
+ }
217
+ case "exit": {
218
+ process.exit(this.shutdownRequested ? 0 : 1);
219
+ break;
220
+ }
221
+ case "textDocument/didOpen": {
222
+ this.handleDidOpen(params);
223
+ break;
224
+ }
225
+ case "textDocument/didChange": {
226
+ this.handleDidChange(params);
227
+ break;
228
+ }
229
+ case "textDocument/didClose": {
230
+ this.handleDidClose(params);
231
+ break;
232
+ }
233
+ case "textDocument/didSave": {
234
+ // Re-validate on save
235
+ if (params?.textDocument?.uri) {
236
+ this.publishDiagnostics(params.textDocument.uri);
237
+ }
238
+ break;
239
+ }
240
+ case "$/cancelRequest": {
241
+ // Ignore
242
+ break;
243
+ }
244
+ default: {
245
+ // Unknown notification - ignore
246
+ break;
247
+ }
248
+ }
249
+ }
250
+ handleInitialize(params) {
251
+ // Client capabilities ignored for now; we advertise our capabilities
252
+ return {
253
+ capabilities: {
254
+ textDocumentSync: {
255
+ openClose: true,
256
+ change: 1, // Full
257
+ save: { includeText: false },
258
+ },
259
+ completionProvider: {
260
+ triggerCharacters: [" ", "[", "-", ">", ":"],
261
+ resolveProvider: false,
262
+ },
263
+ hoverProvider: true,
264
+ definitionProvider: true,
265
+ referencesProvider: true,
266
+ renameProvider: {
267
+ prepareProvider: true,
268
+ },
269
+ documentFormattingProvider: true,
270
+ documentSymbolProvider: true,
271
+ foldingRangeProvider: true,
272
+ diagnosticProvider: {
273
+ interFileDependencies: false,
274
+ workspaceDiagnostics: false,
275
+ },
276
+ },
277
+ serverInfo: {
278
+ name: "floe-lsp",
279
+ version: "1.0.0",
280
+ },
281
+ };
282
+ }
283
+ handleDidOpen(params) {
284
+ const doc = params?.textDocument;
285
+ if (!doc?.uri || typeof doc.text !== "string")
286
+ return;
287
+ const uri = doc.uri;
288
+ const text = doc.text;
289
+ const version = doc.version ?? 1;
290
+ const languageId = doc.languageId ?? "floe";
291
+ this.docs.open(uri, languageId, version, text);
292
+ this.publishDiagnostics(uri);
293
+ }
294
+ handleDidChange(params) {
295
+ const doc = params?.textDocument;
296
+ const changes = params?.contentChanges;
297
+ if (!doc?.uri || !Array.isArray(changes))
298
+ return;
299
+ const uri = doc.uri;
300
+ const version = doc.version ?? 1;
301
+ // LSP spec: if changes have range, it's incremental; if no range, full
302
+ const hasRanges = changes.some((c) => c.range !== undefined);
303
+ if (hasRanges) {
304
+ // Incremental changes: need to apply ranges sequentially based on evolving text
305
+ // For Full sync (change:1), changes will be single entry with full text and no range, but handle both
306
+ this.docs.applyIncremental(uri, version, changes);
307
+ }
308
+ else {
309
+ // Full text sync
310
+ const text = changes[0]?.text ?? "";
311
+ this.docs.update(uri, version, text);
312
+ }
313
+ this.publishDiagnostics(uri);
314
+ }
315
+ handleDidClose(params) {
316
+ const uri = params?.textDocument?.uri;
317
+ if (!uri)
318
+ return;
319
+ this.docs.close(uri);
320
+ // Clear diagnostics for closed file
321
+ this.sendNotification("textDocument/publishDiagnostics", { uri, diagnostics: [] });
322
+ }
323
+ publishDiagnostics(uri) {
324
+ const doc = this.docs.get(uri);
325
+ if (!doc)
326
+ return;
327
+ const text = doc.text;
328
+ // Reuse language service diagnostics (editor-independent)
329
+ let diagnostics = [];
330
+ try {
331
+ const floeDiags = getDiagnostics(text);
332
+ diagnostics = floeDiags.map((d) => {
333
+ const lspRange = floeRangeToLspRange(text, d.range);
334
+ return {
335
+ range: lspRange,
336
+ severity: severityToLsp(d.severity),
337
+ code: d.code,
338
+ source: "floe",
339
+ message: d.message,
340
+ };
341
+ });
342
+ }
343
+ catch {
344
+ // Never crash on malformed input
345
+ diagnostics = [];
346
+ }
347
+ this.sendNotification("textDocument/publishDiagnostics", {
348
+ uri,
349
+ diagnostics,
350
+ });
351
+ }
352
+ handleCompletion(params) {
353
+ const uri = params?.textDocument?.uri;
354
+ const pos = params?.position;
355
+ if (!uri || !pos)
356
+ return null;
357
+ const doc = this.docs.get(uri);
358
+ if (!doc)
359
+ return null;
360
+ const offset = lspPositionToOffset(doc.text, pos);
361
+ try {
362
+ const items = getCompletions(doc.text, offset);
363
+ // Map to LSP CompletionItem
364
+ const lspItems = items.map((it) => ({
365
+ label: it.label,
366
+ kind: completionKindToLsp(it.kind),
367
+ detail: it.detail,
368
+ insertText: it.insertText ?? it.label,
369
+ sortText: it.sortText,
370
+ }));
371
+ return {
372
+ isIncomplete: false,
373
+ items: lspItems,
374
+ };
375
+ }
376
+ catch {
377
+ return { isIncomplete: false, items: [] };
378
+ }
379
+ }
380
+ handleHover(params) {
381
+ const uri = params?.textDocument?.uri;
382
+ const pos = params?.position;
383
+ if (!uri || !pos)
384
+ return null;
385
+ const doc = this.docs.get(uri);
386
+ if (!doc)
387
+ return null;
388
+ const offset = lspPositionToOffset(doc.text, pos);
389
+ try {
390
+ const hover = getHover(doc.text, offset);
391
+ if (!hover)
392
+ return null;
393
+ const lspRange = hover.range ? floeRangeToLspRange(doc.text, hover.range) : undefined;
394
+ return {
395
+ contents: hover.contents.map((c) => ({ language: "markdown", value: c })),
396
+ range: lspRange,
397
+ };
398
+ }
399
+ catch {
400
+ return null;
401
+ }
402
+ }
403
+ handleDefinition(params) {
404
+ const uri = params?.textDocument?.uri;
405
+ const pos = params?.position;
406
+ if (!uri || !pos)
407
+ return null;
408
+ const doc = this.docs.get(uri);
409
+ if (!doc)
410
+ return null;
411
+ const offset = lspPositionToOffset(doc.text, pos);
412
+ try {
413
+ const def = getDefinition(doc.text, offset);
414
+ if (!def)
415
+ return null;
416
+ const lspRange = floeRangeToLspRange(doc.text, def.range);
417
+ return {
418
+ uri,
419
+ range: lspRange,
420
+ };
421
+ // Alternatively return Location[] array
422
+ }
423
+ catch {
424
+ return null;
425
+ }
426
+ }
427
+ handleReferences(params) {
428
+ const uri = params?.textDocument?.uri;
429
+ const pos = params?.position;
430
+ if (!uri || !pos)
431
+ return [];
432
+ const doc = this.docs.get(uri);
433
+ if (!doc)
434
+ return [];
435
+ const offset = lspPositionToOffset(doc.text, pos);
436
+ try {
437
+ const refs = getReferences(doc.text, offset);
438
+ // getReferences returns Location with range only; need to attach uri
439
+ return refs.map((r) => ({
440
+ uri,
441
+ range: floeRangeToLspRange(doc.text, r.range),
442
+ }));
443
+ }
444
+ catch {
445
+ return [];
446
+ }
447
+ }
448
+ handlePrepareRename(params) {
449
+ const uri = params?.textDocument?.uri;
450
+ const pos = params?.position;
451
+ if (!uri || !pos)
452
+ return null;
453
+ const doc = this.docs.get(uri);
454
+ if (!doc)
455
+ return null;
456
+ const offset = lspPositionToOffset(doc.text, pos);
457
+ try {
458
+ // Use hover or definition to determine if at identifier
459
+ // We can try getReferences; if empty, not renameable
460
+ const refs = getReferences(doc.text, offset);
461
+ if (refs.length === 0)
462
+ return null;
463
+ // Also check that offset is on identifier via tokenization? For simplicity return range of first ref
464
+ // Find token range at offset
465
+ // Reuse getDefinition's logic: if definition exists, we have range
466
+ const def = getDefinition(doc.text, offset);
467
+ if (!def)
468
+ return null;
469
+ return {
470
+ range: floeRangeToLspRange(doc.text, def.range),
471
+ placeholder: doc.text.slice(def.range.start.offset, def.range.end.offset),
472
+ };
473
+ }
474
+ catch {
475
+ return null;
476
+ }
477
+ }
478
+ handleRename(params) {
479
+ const uri = params?.textDocument?.uri;
480
+ const pos = params?.position;
481
+ const newName = params?.newName;
482
+ if (!uri || !pos || typeof newName !== "string")
483
+ return null;
484
+ const doc = this.docs.get(uri);
485
+ if (!doc)
486
+ return null;
487
+ const offset = lspPositionToOffset(doc.text, pos);
488
+ try {
489
+ const result = rename(doc.text, offset, newName);
490
+ if (result.error) {
491
+ // LSP rename error should be shown as response error or null?
492
+ // Return error viaJson RPC error? Instead return null and let client show? We'll send error
493
+ // But spec says rename returns WorkspaceEdit or null; invalid newName should be handled as error
494
+ // We'll return null and also send window/showMessage? For now return error as null and client will handle.
495
+ // Better to send error response via throw? But handler already catches.
496
+ // So we return null and log
497
+ return null;
498
+ }
499
+ // result.edits is array of TextEdit with Floe Range
500
+ const changes = {};
501
+ changes[uri] = result.edits.map((e) => ({
502
+ range: floeRangeToLspRange(doc.text, e.range),
503
+ newText: e.newText,
504
+ }));
505
+ return { changes };
506
+ }
507
+ catch {
508
+ return null;
509
+ }
510
+ }
511
+ handleFormatting(params) {
512
+ const uri = params?.textDocument?.uri;
513
+ if (!uri)
514
+ return null;
515
+ const doc = this.docs.get(uri);
516
+ if (!doc)
517
+ return null;
518
+ try {
519
+ const formatted = format(doc.text);
520
+ if (formatted === doc.text)
521
+ return [];
522
+ // Single edit covering whole document
523
+ const start = { line: 0, character: 0 };
524
+ const end = offsetToLspPosition(doc.text, doc.text.length);
525
+ return [
526
+ {
527
+ range: { start, end },
528
+ newText: formatted,
529
+ },
530
+ ];
531
+ }
532
+ catch {
533
+ return null;
534
+ }
535
+ }
536
+ handleDocumentSymbol(params) {
537
+ const uri = params?.textDocument?.uri;
538
+ if (!uri)
539
+ return [];
540
+ const doc = this.docs.get(uri);
541
+ if (!doc)
542
+ return [];
543
+ try {
544
+ const symbols = getSymbols(doc.text);
545
+ // Convert to LSP DocumentSymbol (hierarchical)
546
+ const toLspSymbol = (s) => {
547
+ const lspRange = floeRangeToLspRange(doc.text, s.range);
548
+ const selectionRange = s.selectionRange ? floeRangeToLspRange(doc.text, s.selectionRange) : lspRange;
549
+ const kind = symbolKindToLsp(s.kind);
550
+ const result = {
551
+ name: s.name,
552
+ kind,
553
+ range: lspRange,
554
+ selectionRange,
555
+ detail: s.detail,
556
+ };
557
+ if (s.children && s.children.length > 0) {
558
+ result.children = s.children.map(toLspSymbol);
559
+ }
560
+ return result;
561
+ };
562
+ return symbols.map(toLspSymbol);
563
+ }
564
+ catch {
565
+ return [];
566
+ }
567
+ }
568
+ handleFoldingRange(params) {
569
+ const uri = params?.textDocument?.uri;
570
+ if (!uri)
571
+ return [];
572
+ const doc = this.docs.get(uri);
573
+ if (!doc)
574
+ return [];
575
+ try {
576
+ const ranges = getFoldingRanges(doc.text);
577
+ return ranges.map((r) => ({
578
+ startLine: r.startLine - 1,
579
+ endLine: r.endLine - 1,
580
+ startCharacter: r.startColumn ? r.startColumn - 1 : undefined,
581
+ endCharacter: r.endColumn ? r.endColumn - 1 : undefined,
582
+ kind: r.kind ?? "region",
583
+ collapsedText: r.collapsedText,
584
+ }));
585
+ }
586
+ catch {
587
+ return [];
588
+ }
589
+ }
590
+ }
591
+ function completionKindToLsp(kind) {
592
+ switch (kind) {
593
+ case "keyword": return 14; // Keyword
594
+ case "type": return 8; // Interface or Type? Use 8 Interface
595
+ case "variable": return 6; // Variable
596
+ case "value": return 12; // Value
597
+ case "constant": return 21; // Constant
598
+ default: return 1; // Text
599
+ }
600
+ }
601
+ function symbolKindToLsp(kind) {
602
+ switch (kind) {
603
+ case "node": return 5; // Class? Use 5 Class for node, or 13 Variable? Choose 5
604
+ case "group": return 4; // Class? Use 4 for group container? Actually 2 Module, 4 Class
605
+ case "edge": return 13; // Variable? Use 13 Variable for edge
606
+ case "direction": return 14; // Constant? Use 14 Constant
607
+ case "meta": return 13; // Variable
608
+ case "annotation": return 13;
609
+ case "link": return 13;
610
+ default: return 1; // File
611
+ }
612
+ }
613
+ export async function startLspServer(opts = {}) {
614
+ const server = new FloeLspServer(opts);
615
+ await server.start();
616
+ return server;
617
+ }
618
+ // If run directly via node
619
+ if (import.meta.url === `file://${process.argv[1]?.replace(/\\/g, "/")}`) {
620
+ const server = new FloeLspServer({ stdio: true });
621
+ server.start();
622
+ }
623
+ //# sourceMappingURL=server.js.map