@seljs/editor 1.0.0 → 1.0.1-beta.9

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 (103) hide show
  1. package/dist/completion/completion-items.cjs +36 -0
  2. package/dist/completion/completion-items.mjs +32 -0
  3. package/dist/completion/index.cjs +1 -0
  4. package/dist/completion/index.d.mts +1 -0
  5. package/dist/completion/index.mjs +2 -0
  6. package/dist/completion/schema-completion.cjs +182 -0
  7. package/dist/completion/schema-completion.d.cts +25 -0
  8. package/dist/completion/schema-completion.d.mts +25 -0
  9. package/dist/completion/schema-completion.mjs +181 -0
  10. package/dist/completion/tree-context.cjs +108 -0
  11. package/dist/completion/tree-context.mjs +108 -0
  12. package/dist/editor/create-editor.cjs +16 -0
  13. package/dist/editor/create-editor.d.cts +7 -0
  14. package/dist/editor/create-editor.d.mts +7 -0
  15. package/dist/editor/create-editor.mjs +16 -0
  16. package/dist/editor/editor-config.cjs +49 -0
  17. package/dist/editor/editor-config.d.cts +7 -0
  18. package/dist/editor/editor-config.d.mts +7 -0
  19. package/dist/editor/editor-config.mjs +49 -0
  20. package/dist/editor/index.cjs +3 -0
  21. package/dist/editor/index.d.cts +6 -0
  22. package/dist/editor/index.d.mts +6 -0
  23. package/dist/editor/index.mjs +4 -0
  24. package/dist/editor/theme.cjs +35 -0
  25. package/dist/editor/theme.d.cts +7 -0
  26. package/dist/editor/theme.d.mts +7 -0
  27. package/dist/editor/theme.mjs +34 -0
  28. package/dist/editor/type-display.cjs +71 -0
  29. package/dist/editor/type-display.mjs +71 -0
  30. package/dist/editor/types.d.cts +31 -0
  31. package/dist/editor/types.d.mts +31 -0
  32. package/dist/index.cjs +20 -0
  33. package/dist/index.d.cts +10 -0
  34. package/dist/index.d.mts +10 -0
  35. package/dist/index.mjs +11 -0
  36. package/dist/language/semantic-highlighter.cjs +55 -0
  37. package/dist/language/semantic-highlighter.mjs +55 -0
  38. package/dist/language/tokenizer-config.cjs +9 -0
  39. package/dist/language/tokenizer-config.d.cts +12 -0
  40. package/dist/language/tokenizer-config.d.mts +12 -0
  41. package/dist/language/tokenizer-config.mjs +9 -0
  42. package/dist/linting/diagnostic-mapper.cjs +37 -0
  43. package/dist/linting/{diagnostic-mapper.d.ts → diagnostic-mapper.d.cts} +7 -6
  44. package/dist/linting/diagnostic-mapper.d.mts +29 -0
  45. package/dist/linting/diagnostic-mapper.mjs +37 -0
  46. package/dist/linting/index.cjs +2 -0
  47. package/dist/linting/index.d.mts +2 -0
  48. package/dist/linting/index.mjs +3 -0
  49. package/dist/linting/sel-linter.cjs +28 -0
  50. package/dist/linting/sel-linter.d.cts +13 -0
  51. package/dist/linting/sel-linter.d.mts +13 -0
  52. package/dist/linting/sel-linter.mjs +28 -0
  53. package/package.json +29 -22
  54. package/dist/completion/completion-items.d.ts +0 -8
  55. package/dist/completion/completion-items.d.ts.map +0 -1
  56. package/dist/completion/completion-items.js +0 -29
  57. package/dist/completion/index.d.ts +0 -2
  58. package/dist/completion/index.d.ts.map +0 -1
  59. package/dist/completion/index.js +0 -1
  60. package/dist/completion/schema-completion.d.ts +0 -22
  61. package/dist/completion/schema-completion.d.ts.map +0 -1
  62. package/dist/completion/schema-completion.js +0 -220
  63. package/dist/completion/tree-context.d.ts +0 -23
  64. package/dist/completion/tree-context.d.ts.map +0 -1
  65. package/dist/completion/tree-context.js +0 -154
  66. package/dist/editor/create-editor.d.ts +0 -4
  67. package/dist/editor/create-editor.d.ts.map +0 -1
  68. package/dist/editor/create-editor.js +0 -14
  69. package/dist/editor/editor-config.d.ts +0 -4
  70. package/dist/editor/editor-config.d.ts.map +0 -1
  71. package/dist/editor/editor-config.js +0 -64
  72. package/dist/editor/index.d.ts +0 -6
  73. package/dist/editor/index.d.ts.map +0 -1
  74. package/dist/editor/index.js +0 -3
  75. package/dist/editor/theme.d.ts +0 -3
  76. package/dist/editor/theme.d.ts.map +0 -1
  77. package/dist/editor/theme.js +0 -43
  78. package/dist/editor/type-display.d.ts +0 -4
  79. package/dist/editor/type-display.d.ts.map +0 -1
  80. package/dist/editor/type-display.js +0 -75
  81. package/dist/editor/types.d.ts +0 -28
  82. package/dist/editor/types.d.ts.map +0 -1
  83. package/dist/editor/types.js +0 -1
  84. package/dist/index.d.ts +0 -5
  85. package/dist/index.d.ts.map +0 -1
  86. package/dist/index.js +0 -4
  87. package/dist/language/index.d.ts +0 -2
  88. package/dist/language/index.d.ts.map +0 -1
  89. package/dist/language/index.js +0 -1
  90. package/dist/language/semantic-highlighter.d.ts +0 -4
  91. package/dist/language/semantic-highlighter.d.ts.map +0 -1
  92. package/dist/language/semantic-highlighter.js +0 -76
  93. package/dist/language/tokenizer-config.d.ts +0 -9
  94. package/dist/language/tokenizer-config.d.ts.map +0 -1
  95. package/dist/language/tokenizer-config.js +0 -6
  96. package/dist/linting/diagnostic-mapper.d.ts.map +0 -1
  97. package/dist/linting/diagnostic-mapper.js +0 -46
  98. package/dist/linting/index.d.ts +0 -3
  99. package/dist/linting/index.d.ts.map +0 -1
  100. package/dist/linting/index.js +0 -2
  101. package/dist/linting/sel-linter.d.ts +0 -12
  102. package/dist/linting/sel-linter.d.ts.map +0 -1
  103. package/dist/linting/sel-linter.js +0 -28
@@ -1,220 +0,0 @@
1
- import { autocompletion, } from "@codemirror/autocomplete";
2
- import { isTypeCompatible } from "@seljs/checker";
3
- import { createContractCompletions, createFunctionCompletions, createMacroCompletions, createMethodCompletions, createVariableCompletions, } from "./completion-items";
4
- import { getCompletionContext } from "./tree-context";
5
- /**
6
- * Determine the CodeMirror completion type from a checker CompletionItem.
7
- * Method details start with "(" (e.g., "(owner: sol_address): sol_int"),
8
- * while struct field details are plain type names (e.g., "sol_int").
9
- */
10
- const completionKind = (item) => item.detail?.startsWith("(") ? "method" : "property";
11
- export class SchemaCompletionProvider {
12
- topLevelCompletions;
13
- allDotCompletions;
14
- checker;
15
- constructor(schema, checker) {
16
- this.checker = checker;
17
- const contractCompletions = createContractCompletions(schema.contracts);
18
- const variableCompletions = createVariableCompletions(schema.variables);
19
- const freeFunctions = schema.functions.filter((f) => !f.receiverType);
20
- const functionCompletions = createFunctionCompletions(freeFunctions);
21
- const macroCompletions = createMacroCompletions(schema.macros);
22
- const atomCompletions = [
23
- { label: "true", type: "keyword" },
24
- { label: "false", type: "keyword" },
25
- { label: "null", type: "keyword" },
26
- ];
27
- this.topLevelCompletions = [
28
- ...contractCompletions,
29
- ...variableCompletions,
30
- ...functionCompletions,
31
- ...atomCompletions,
32
- ];
33
- // Broad matching: all methods + macros + struct fields + receiver methods
34
- const allMethods = [];
35
- for (const contract of schema.contracts) {
36
- allMethods.push(...createMethodCompletions(contract));
37
- }
38
- const structFields = [];
39
- for (const type of schema.types) {
40
- if (type.kind === "struct" && type.fields) {
41
- for (const field of type.fields) {
42
- structFields.push({
43
- label: field.name,
44
- type: "property",
45
- detail: field.type,
46
- info: field.description,
47
- });
48
- }
49
- }
50
- }
51
- const receiverMethods = [];
52
- for (const fn of schema.functions) {
53
- if (fn.receiverType) {
54
- receiverMethods.push({
55
- label: fn.name,
56
- type: "method",
57
- detail: fn.signature,
58
- info: fn.description,
59
- });
60
- }
61
- }
62
- this.allDotCompletions = [
63
- ...allMethods,
64
- ...macroCompletions,
65
- ...structFields,
66
- ...receiverMethods,
67
- ];
68
- }
69
- completionSource = (context) => {
70
- const treeCtx = getCompletionContext(context.state, context.pos);
71
- switch (treeCtx.kind) {
72
- case "dot-access":
73
- return this.handleDotAccess(context, treeCtx);
74
- case "call-arg":
75
- return this.handleCallArg(context, treeCtx);
76
- case "top-level":
77
- return this.handleTopLevel(context, treeCtx);
78
- }
79
- };
80
- handleDotAccess(context, treeCtx) {
81
- const { receiverText, from } = treeCtx;
82
- // Skip numeric identifiers (e.g., 3.14)
83
- if (/^\d+$/.test(receiverText)) {
84
- return this.handleTopLevel(context, { kind: "top-level", from });
85
- }
86
- // Use checker's direct API — tree-context already provides receiverText
87
- const info = this.checker.dotCompletions(receiverText);
88
- // Checker resolved the type and found members
89
- if (info.items.length > 0) {
90
- return {
91
- from,
92
- options: info.items.map((item) => ({
93
- label: item.label,
94
- type: completionKind(item),
95
- detail: item.detail,
96
- info: item.description,
97
- })),
98
- filter: true,
99
- };
100
- }
101
- /*
102
- * Checker resolved to a known type but no members — no completions
103
- * When receiverType !== receiverText, the checker actually resolved the type
104
- */
105
- if (info.receiverType !== receiverText) {
106
- return null;
107
- }
108
- // Unresolved receiver (checker echoes identifier back) — broad fallback
109
- return this.allDotCompletions.length
110
- ? { from, options: this.allDotCompletions, filter: true }
111
- : null;
112
- }
113
- handleCallArg(context, treeCtx) {
114
- // Use checker's direct API with structured context from tree
115
- const expectedType = this.checker.expectedTypeFor({
116
- kind: "function-arg",
117
- functionName: treeCtx.functionName,
118
- receiverName: treeCtx.receiverName,
119
- paramIndex: treeCtx.paramIndex,
120
- });
121
- if (expectedType) {
122
- // Filter pre-built top-level completions by type compatibility
123
- const filtered = this.topLevelCompletions.filter((c) => {
124
- if (c.type === "keyword") {
125
- // null is dyn — always compatible
126
- if (c.label === "null") {
127
- return true;
128
- }
129
- if (c.label === "true" || c.label === "false") {
130
- return isTypeCompatible("bool", expectedType);
131
- }
132
- }
133
- // Variables have their type in detail
134
- if (c.type === "variable" && c.detail) {
135
- return isTypeCompatible(c.detail, expectedType);
136
- }
137
- // Contracts: check contract type name compatibility
138
- if (c.type === "class" && c.label) {
139
- return isTypeCompatible(`SEL_Contract_${c.label}`, expectedType);
140
- }
141
- // Functions — include (return type not easily available)
142
- return true;
143
- });
144
- if (filtered.length > 0) {
145
- const wordMatch = context.matchBefore(/\w+/);
146
- const from = wordMatch?.from ?? treeCtx.from;
147
- return { from, options: filtered, filter: true };
148
- }
149
- }
150
- // No expected type or no compatible items — fall back to top-level
151
- return this.handleTopLevel(context, {
152
- kind: "top-level",
153
- from: treeCtx.from,
154
- });
155
- }
156
- handleTopLevel(context, treeCtx) {
157
- const wordMatch = context.matchBefore(/\w+/);
158
- const from = wordMatch?.from ?? treeCtx.from;
159
- const hasText = !!wordMatch;
160
- // Type-aware narrowing
161
- const narrowed = this.narrowByExpectedType(context, from);
162
- if (narrowed && narrowed.length > 0) {
163
- return { from, options: narrowed, filter: true };
164
- }
165
- // Unfiltered top-level
166
- if (hasText) {
167
- return this.topLevelCompletions.length
168
- ? { from, options: this.topLevelCompletions, filter: true }
169
- : null;
170
- }
171
- // Explicit activation (Ctrl+Space) with no text
172
- if (context.explicit && this.topLevelCompletions.length) {
173
- return {
174
- from: context.pos,
175
- options: this.topLevelCompletions,
176
- filter: true,
177
- };
178
- }
179
- return null;
180
- }
181
- /**
182
- * Use the checker to infer the expected type at cursor position
183
- * and filter completions to only type-compatible items.
184
- * Returns undefined when narrowing is not possible.
185
- */
186
- narrowByExpectedType(context, from) {
187
- const doc = context.state.doc.toString();
188
- const info = this.checker.completionsAt(doc, from);
189
- if (!info.expectedType) {
190
- return undefined;
191
- }
192
- const expectedType = info.expectedType;
193
- // Filter checker items by type compatibility
194
- const filtered = info.items
195
- .filter((item) => isTypeCompatible(item.type, expectedType))
196
- .map((item) => ({
197
- label: item.label,
198
- type: item.type.startsWith("SEL_Contract_")
199
- ? "class"
200
- : item.detail
201
- ? "function"
202
- : "variable",
203
- detail: item.detail ?? item.type,
204
- info: item.description,
205
- }));
206
- // Also include keywords when compatible
207
- if (isTypeCompatible("bool", expectedType)) {
208
- filtered.push({ label: "true", type: "keyword" }, { label: "false", type: "keyword" });
209
- }
210
- // null is dyn — always compatible
211
- filtered.push({ label: "null", type: "keyword" });
212
- return filtered.length > 0 ? filtered : undefined;
213
- }
214
- }
215
- export const createSchemaCompletion = (schema, checker) => {
216
- const provider = new SchemaCompletionProvider(schema, checker);
217
- return autocompletion({
218
- override: [provider.completionSource],
219
- });
220
- };
@@ -1,23 +0,0 @@
1
- import type { EditorState } from "@codemirror/state";
2
- /**
3
- * Completion context extracted from the syntax tree at the cursor position.
4
- */
5
- export type TreeCompletionContext = {
6
- kind: "dot-access";
7
- receiverText: string;
8
- from: number;
9
- } | {
10
- kind: "call-arg";
11
- functionName: string;
12
- receiverName?: string;
13
- paramIndex: number;
14
- from: number;
15
- } | {
16
- kind: "top-level";
17
- from: number;
18
- };
19
- /**
20
- * Extract completion context from the Lezer syntax tree at the given position.
21
- */
22
- export declare const getCompletionContext: (state: EditorState, pos: number) => TreeCompletionContext;
23
- //# sourceMappingURL=tree-context.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"tree-context.d.ts","sourceRoot":"","sources":["../../src/completion/tree-context.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AA+KrD;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAC9B;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC1D;IACA,IAAI,EAAE,UAAU,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACZ,GACD;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvC;;GAEG;AACH,eAAO,MAAM,oBAAoB,GAChC,OAAO,WAAW,EAClB,KAAK,MAAM,KACT,qBAsBF,CAAC"}
@@ -1,154 +0,0 @@
1
- import { syntaxTree } from "@codemirror/language";
2
- /**
3
- * Walk up the tree looking for a MemberExpression ancestor.
4
- * A MemberExpression has two Identifier children (or child nodes) separated by ".".
5
- * The dot position is: firstChild.to + 1 (the "." character is between them).
6
- * We are in dot-access context when pos >= dotPos.
7
- */
8
- const findDotAccess = (state, node, pos) => {
9
- let current = node;
10
- while (current) {
11
- /*
12
- * Stop at ArgList boundary — don't match outer MemberExpressions
13
- * that wrap the entire call (e.g., `erc20.balanceOf(user)` at `user`)
14
- */
15
- if (current.name === "ArgList") {
16
- break;
17
- }
18
- if (current.name === "MemberExpression" ||
19
- current.name === "OptionalExpression") {
20
- const firstChild = current.firstChild;
21
- if (firstChild) {
22
- /*
23
- * The dot is immediately after the first child
24
- * For MemberExpression: firstChild.to is where the "." starts
25
- * (the token "." is not a named node, it's between firstChild and secondChild)
26
- */
27
- // position after "."
28
- const dotPos = firstChild.to + 1;
29
- if (pos >= dotPos) {
30
- const receiverText = state.doc.sliceString(current.from, firstChild.to);
31
- return {
32
- kind: "dot-access",
33
- receiverText,
34
- from: dotPos,
35
- };
36
- }
37
- }
38
- }
39
- current = current.parent;
40
- }
41
- return undefined;
42
- };
43
- /**
44
- * Count commas in ArgList text that appear before pos.
45
- * Commas are anonymous tokens not exposed as named children,
46
- * so we scan the raw source text.
47
- */
48
- const countCommasBefore = (argList, state, pos) => {
49
- /*
50
- * Only scan from the opening "(" up to pos
51
- * skip "("
52
- */
53
- const open = argList.from + 1;
54
- const end = Math.min(pos, argList.to);
55
- const text = state.doc.sliceString(open, end);
56
- let count = 0;
57
- for (const ch of text) {
58
- if (ch === ",") {
59
- count++;
60
- }
61
- }
62
- return count;
63
- };
64
- /**
65
- * Extract function name and optional receiver name from a CallExpression node.
66
- *
67
- * Tree shapes:
68
- * foo(...) → CallExpression > Identifier, ArgList
69
- * erc20.foo(...) → CallExpression > MemberExpression(Identifier "erc20", Identifier "foo"), ArgList
70
- */
71
- const extractCallInfo = (state, callExpr) => {
72
- // The callee is everything before the ArgList (first child)
73
- const callee = callExpr.firstChild;
74
- if (!callee) {
75
- return undefined;
76
- }
77
- if (callee.name === "Identifier") {
78
- // Simple function call: foo(...)
79
- return { functionName: state.doc.sliceString(callee.from, callee.to) };
80
- }
81
- if (callee.name === "MemberExpression") {
82
- /*
83
- * Method call: receiver.method(...)
84
- * MemberExpression has two children: receiver and method name (Identifier)
85
- */
86
- const lastChild = callee.lastChild;
87
- if (lastChild?.name === "Identifier") {
88
- const functionName = state.doc.sliceString(lastChild.from, lastChild.to);
89
- // Receiver is first child of MemberExpression
90
- const receiverNode = callee.firstChild;
91
- const receiverName = receiverNode?.name === "Identifier"
92
- ? state.doc.sliceString(receiverNode.from, receiverNode.to)
93
- : undefined;
94
- return { functionName, receiverName };
95
- }
96
- }
97
- return undefined;
98
- };
99
- const findWordStart = (state, pos) => {
100
- const text = state.doc.sliceString(0, pos);
101
- const match = /\w+$/.exec(text);
102
- return match ? pos - match[0].length : pos;
103
- };
104
- /**
105
- * Walk up the tree looking for an ArgList ancestor.
106
- * When found, count commas before pos to get paramIndex,
107
- * then extract function name and optional receiver from the CallExpression.
108
- */
109
- const findCallArg = (state, node, pos) => {
110
- let current = node;
111
- while (current) {
112
- if (current.name === "ArgList") {
113
- const paramIndex = countCommasBefore(current, state, pos);
114
- const callExpr = current.parent;
115
- if (callExpr?.name === "CallExpression") {
116
- const info = extractCallInfo(state, callExpr);
117
- if (info) {
118
- return {
119
- kind: "call-arg",
120
- functionName: info.functionName,
121
- receiverName: info.receiverName,
122
- paramIndex,
123
- from: pos,
124
- };
125
- }
126
- }
127
- }
128
- current = current.parent;
129
- }
130
- return undefined;
131
- };
132
- /**
133
- * Extract completion context from the Lezer syntax tree at the given position.
134
- */
135
- export const getCompletionContext = (state, pos) => {
136
- const tree = syntaxTree(state);
137
- const node = tree.resolveInner(pos, -1);
138
- /*
139
- * Check for dot-access first: cursor is after a "." in a MemberExpression
140
- * This must come before call-arg so that dot-access inside parentheses
141
- * (e.g., `contract.method(contract.)`) is handled correctly.
142
- */
143
- const dotAccess = findDotAccess(state, node, pos);
144
- if (dotAccess) {
145
- return dotAccess;
146
- }
147
- // Check for call-arg: cursor is inside an ArgList
148
- const callArg = findCallArg(state, node, pos);
149
- if (callArg) {
150
- return callArg;
151
- }
152
- // Default: top-level completion
153
- return { kind: "top-level", from: findWordStart(state, pos) };
154
- };
@@ -1,4 +0,0 @@
1
- import { EditorView } from "@codemirror/view";
2
- import type { SELEditorConfig } from "./types";
3
- export declare function createSELEditor(config: SELEditorConfig): EditorView;
4
- //# sourceMappingURL=create-editor.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"create-editor.d.ts","sourceRoot":"","sources":["../../src/editor/create-editor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAI9C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,UAAU,CAYnE"}
@@ -1,14 +0,0 @@
1
- import { EditorState } from "@codemirror/state";
2
- import { EditorView } from "@codemirror/view";
3
- import { buildExtensions } from "./editor-config";
4
- export function createSELEditor(config) {
5
- const extensions = buildExtensions(config);
6
- const state = EditorState.create({
7
- doc: config.value ?? "",
8
- extensions,
9
- });
10
- return new EditorView({
11
- state,
12
- parent: config.parent,
13
- });
14
- }
@@ -1,4 +0,0 @@
1
- import { type Extension } from "@codemirror/state";
2
- import type { SELEditorConfig } from "./types";
3
- export declare const buildExtensions: (config: SELEditorConfig) => Extension[];
4
- //# sourceMappingURL=editor-config.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"editor-config.d.ts","sourceRoot":"","sources":["../../src/editor/editor-config.ts"],"names":[],"mappings":"AAGA,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAYhE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,eAAO,MAAM,eAAe,GAAI,QAAQ,eAAe,KAAG,SAAS,EAqElE,CAAC"}
@@ -1,64 +0,0 @@
1
- import { closeBrackets, closeBracketsKeymap } from "@codemirror/autocomplete";
2
- import { defaultKeymap, history, historyKeymap } from "@codemirror/commands";
3
- import { bracketMatching } from "@codemirror/language";
4
- import { EditorState } from "@codemirror/state";
5
- import { EditorView, keymap, placeholder } from "@codemirror/view";
6
- import { celLanguageSupport } from "@seljs/cel-lezer";
7
- import { SELChecker, rules } from "@seljs/checker";
8
- import { selDarkTheme, selLightTheme } from "./theme";
9
- import { createTypeDisplay } from "./type-display";
10
- import { createSchemaCompletion } from "../completion/schema-completion";
11
- import { createSemanticHighlighter } from "../language/semantic-highlighter";
12
- import { createTokenizerConfig } from "../language/tokenizer-config";
13
- import { createSELLinter } from "../linting/sel-linter";
14
- export const buildExtensions = (config) => {
15
- const checker = new SELChecker(config.schema, { rules: [...rules.builtIn] });
16
- const extensions = [];
17
- // Language support (includes syntax highlighting)
18
- extensions.push(celLanguageSupport(config.dark));
19
- extensions.push(bracketMatching());
20
- // Semantic highlighting (schema-aware identifier coloring)
21
- const tokenizerConfig = createTokenizerConfig(config.schema);
22
- extensions.push(createSemanticHighlighter(tokenizerConfig, config.dark));
23
- // Autocomplete (type-aware via checker)
24
- extensions.push(createSchemaCompletion(config.schema, checker));
25
- extensions.push(closeBrackets());
26
- // Keybindings
27
- extensions.push(keymap.of([...closeBracketsKeymap, ...defaultKeymap, ...historyKeymap]));
28
- extensions.push(history());
29
- // Theme
30
- extensions.push(config.dark ? selDarkTheme : selLightTheme);
31
- // Validation / linting (built-in checker used when no validate callback provided)
32
- const validate = config.validate ??
33
- ((expression) => checker.check(expression).diagnostics);
34
- extensions.push(createSELLinter({
35
- validate,
36
- delay: config.validateDelay,
37
- }));
38
- // onChange listener
39
- if (config.onChange) {
40
- const onChange = config.onChange;
41
- extensions.push(EditorView.updateListener.of((update) => {
42
- if (update.docChanged) {
43
- onChange(update.state.doc.toString());
44
- }
45
- }));
46
- }
47
- // Read-only
48
- if (config.readOnly) {
49
- extensions.push(EditorState.readOnly.of(true));
50
- }
51
- // Placeholder
52
- if (config.placeholder) {
53
- extensions.push(placeholder(config.placeholder));
54
- }
55
- // Type display panel
56
- if (config.showType) {
57
- extensions.push(createTypeDisplay(checker, config.dark ?? false));
58
- }
59
- // User-provided extensions (last, so they can override)
60
- if (config.extensions) {
61
- extensions.push(...config.extensions);
62
- }
63
- return extensions;
64
- };
@@ -1,6 +0,0 @@
1
- export { createSELEditor } from "./create-editor";
2
- export { buildExtensions } from "./editor-config";
3
- export { selLightTheme, selDarkTheme } from "./theme";
4
- export type { SELEditorConfig } from "./types";
5
- export type { EditorView } from "@codemirror/view";
6
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/editor/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACtD,YAAY,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
@@ -1,3 +0,0 @@
1
- export { createSELEditor } from "./create-editor";
2
- export { buildExtensions } from "./editor-config";
3
- export { selLightTheme, selDarkTheme } from "./theme";
@@ -1,3 +0,0 @@
1
- export declare const selLightTheme: import("@codemirror/state").Extension;
2
- export declare const selDarkTheme: import("@codemirror/state").Extension;
3
- //# sourceMappingURL=theme.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../src/editor/theme.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,aAAa,uCAoBxB,CAAC;AAEH,eAAO,MAAM,YAAY,uCAuBxB,CAAC"}
@@ -1,43 +0,0 @@
1
- import { EditorView } from "@codemirror/view";
2
- export const selLightTheme = EditorView.theme({
3
- "&": {
4
- fontSize: "14px",
5
- backgroundColor: "#ffffff",
6
- color: "#1e1e1e",
7
- },
8
- ".cm-content": {
9
- caretColor: "#000000",
10
- fontFamily: "'JetBrains Mono', 'Fira Code', 'Consolas', monospace",
11
- padding: "4px 0",
12
- },
13
- "&.cm-focused .cm-cursor": {
14
- borderLeftColor: "#000000",
15
- },
16
- "&.cm-focused .cm-selectionBackground, .cm-selectionBackground": {
17
- backgroundColor: "#d7d4f0",
18
- },
19
- ".cm-gutters": {
20
- display: "none",
21
- },
22
- });
23
- export const selDarkTheme = EditorView.theme({
24
- "&": {
25
- fontSize: "14px",
26
- backgroundColor: "#1e1e1e",
27
- color: "#d4d4d4",
28
- },
29
- ".cm-content": {
30
- caretColor: "#ffffff",
31
- fontFamily: "'JetBrains Mono', 'Fira Code', 'Consolas', monospace",
32
- padding: "4px 0",
33
- },
34
- "&.cm-focused .cm-cursor": {
35
- borderLeftColor: "#ffffff",
36
- },
37
- "&.cm-focused .cm-selectionBackground, .cm-selectionBackground": {
38
- backgroundColor: "#264f78",
39
- },
40
- ".cm-gutters": {
41
- display: "none",
42
- },
43
- }, { dark: true });
@@ -1,4 +0,0 @@
1
- import { type Extension } from "@codemirror/state";
2
- import type { SELChecker } from "@seljs/checker";
3
- export declare function createTypeDisplay(checker: SELChecker, dark: boolean): Extension;
4
- //# sourceMappingURL=type-display.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"type-display.d.ts","sourceRoot":"","sources":["../../src/editor/type-display.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAA2B,MAAM,mBAAmB,CAAC;AAG5E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AA6DjD,wBAAgB,iBAAiB,CAChC,OAAO,EAAE,UAAU,EACnB,IAAI,EAAE,OAAO,GACX,SAAS,CA4BX"}
@@ -1,75 +0,0 @@
1
- import { StateEffect, StateField } from "@codemirror/state";
2
- import { EditorView, showPanel } from "@codemirror/view";
3
- const setType = StateEffect.define();
4
- const typeField = StateField.define({
5
- create: () => null,
6
- update(value, tr) {
7
- for (const e of tr.effects) {
8
- if (e.is(setType)) {
9
- return e.value;
10
- }
11
- }
12
- return value;
13
- },
14
- });
15
- function createTypePanel(dark) {
16
- return (view) => {
17
- const dom = document.createElement("div");
18
- dom.className = "sel-type-display";
19
- dom.style.cssText = [
20
- "display: flex",
21
- "align-items: center",
22
- "gap: 6px",
23
- `padding: 3px 8px`,
24
- "font-size: 12px",
25
- `font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace`,
26
- `color: ${dark ? "#9ca3af" : "#6b7280"}`,
27
- `background: ${dark ? "#262626" : "#f9fafb"}`,
28
- `border-top: 1px solid ${dark ? "#374151" : "#e5e7eb"}`,
29
- ].join("; ");
30
- const update = () => {
31
- const type = view.state.field(typeField);
32
- dom.textContent = "";
33
- if (type) {
34
- const label = document.createElement("span");
35
- label.style.color = dark ? "#6b7280" : "#9ca3af";
36
- label.textContent = "output";
37
- const typeSpan = document.createElement("span");
38
- typeSpan.style.color = dark ? "#93c5fd" : "#2563eb";
39
- typeSpan.style.fontWeight = "500";
40
- typeSpan.textContent = type;
41
- dom.append(label, " ", typeSpan);
42
- }
43
- };
44
- update();
45
- return {
46
- dom,
47
- update: () => {
48
- update();
49
- },
50
- };
51
- };
52
- }
53
- export function createTypeDisplay(checker, dark) {
54
- let debounceTimer;
55
- const plugin = EditorView.updateListener.of((update) => {
56
- if (!update.docChanged && !update.startState.field(typeField, false)) {
57
- return;
58
- }
59
- if (debounceTimer) {
60
- clearTimeout(debounceTimer);
61
- }
62
- debounceTimer = setTimeout(() => {
63
- const doc = update.state.doc.toString().trim();
64
- if (!doc) {
65
- update.view.dispatch({ effects: setType.of(null) });
66
- return;
67
- }
68
- const result = checker.check(doc);
69
- update.view.dispatch({
70
- effects: setType.of(result.valid ? (result.type ?? null) : null),
71
- });
72
- }, 200);
73
- });
74
- return [typeField, plugin, showPanel.of(createTypePanel(dark))];
75
- }