@seljs/editor 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 (54) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/LICENSE.md +190 -0
  3. package/dist/completion/completion-items.d.ts +8 -0
  4. package/dist/completion/completion-items.d.ts.map +1 -0
  5. package/dist/completion/completion-items.js +29 -0
  6. package/dist/completion/index.d.ts +2 -0
  7. package/dist/completion/index.d.ts.map +1 -0
  8. package/dist/completion/index.js +1 -0
  9. package/dist/completion/schema-completion.d.ts +22 -0
  10. package/dist/completion/schema-completion.d.ts.map +1 -0
  11. package/dist/completion/schema-completion.js +220 -0
  12. package/dist/completion/tree-context.d.ts +23 -0
  13. package/dist/completion/tree-context.d.ts.map +1 -0
  14. package/dist/completion/tree-context.js +154 -0
  15. package/dist/editor/create-editor.d.ts +4 -0
  16. package/dist/editor/create-editor.d.ts.map +1 -0
  17. package/dist/editor/create-editor.js +14 -0
  18. package/dist/editor/editor-config.d.ts +4 -0
  19. package/dist/editor/editor-config.d.ts.map +1 -0
  20. package/dist/editor/editor-config.js +64 -0
  21. package/dist/editor/index.d.ts +6 -0
  22. package/dist/editor/index.d.ts.map +1 -0
  23. package/dist/editor/index.js +3 -0
  24. package/dist/editor/theme.d.ts +3 -0
  25. package/dist/editor/theme.d.ts.map +1 -0
  26. package/dist/editor/theme.js +43 -0
  27. package/dist/editor/type-display.d.ts +4 -0
  28. package/dist/editor/type-display.d.ts.map +1 -0
  29. package/dist/editor/type-display.js +75 -0
  30. package/dist/editor/types.d.ts +28 -0
  31. package/dist/editor/types.d.ts.map +1 -0
  32. package/dist/editor/types.js +1 -0
  33. package/dist/index.d.ts +5 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +4 -0
  36. package/dist/language/index.d.ts +2 -0
  37. package/dist/language/index.d.ts.map +1 -0
  38. package/dist/language/index.js +1 -0
  39. package/dist/language/semantic-highlighter.d.ts +4 -0
  40. package/dist/language/semantic-highlighter.d.ts.map +1 -0
  41. package/dist/language/semantic-highlighter.js +76 -0
  42. package/dist/language/tokenizer-config.d.ts +9 -0
  43. package/dist/language/tokenizer-config.d.ts.map +1 -0
  44. package/dist/language/tokenizer-config.js +6 -0
  45. package/dist/linting/diagnostic-mapper.d.ts +28 -0
  46. package/dist/linting/diagnostic-mapper.d.ts.map +1 -0
  47. package/dist/linting/diagnostic-mapper.js +46 -0
  48. package/dist/linting/index.d.ts +3 -0
  49. package/dist/linting/index.d.ts.map +1 -0
  50. package/dist/linting/index.js +2 -0
  51. package/dist/linting/sel-linter.d.ts +12 -0
  52. package/dist/linting/sel-linter.d.ts.map +1 -0
  53. package/dist/linting/sel-linter.js +28 -0
  54. package/package.json +71 -0
@@ -0,0 +1,154 @@
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
+ };
@@ -0,0 +1,4 @@
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
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,14 @@
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
+ }
@@ -0,0 +1,4 @@
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
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,64 @@
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
+ };
@@ -0,0 +1,6 @@
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
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,3 @@
1
+ export { createSELEditor } from "./create-editor";
2
+ export { buildExtensions } from "./editor-config";
3
+ export { selLightTheme, selDarkTheme } from "./theme";
@@ -0,0 +1,3 @@
1
+ export declare const selLightTheme: import("@codemirror/state").Extension;
2
+ export declare const selDarkTheme: import("@codemirror/state").Extension;
3
+ //# sourceMappingURL=theme.d.ts.map
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,43 @@
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 });
@@ -0,0 +1,4 @@
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
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,75 @@
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
+ }
@@ -0,0 +1,28 @@
1
+ import type { SELDiagnostic } from "../linting/diagnostic-mapper";
2
+ import type { Extension } from "@codemirror/state";
3
+ import type { SELSchema } from "@seljs/schema";
4
+ export interface SELEditorConfig {
5
+ /** Container element to mount into */
6
+ parent: HTMLElement;
7
+ /** Schema driving autocomplete and syntax highlighting */
8
+ schema: SELSchema;
9
+ /** Initial expression value */
10
+ value?: string;
11
+ /** Called on every expression change */
12
+ onChange?: (value: string) => void;
13
+ /** Validation function for error highlighting */
14
+ validate?: (expression: string) => SELDiagnostic[] | Promise<SELDiagnostic[]>;
15
+ /** Debounce delay for validation (default: 300ms) */
16
+ validateDelay?: number;
17
+ /** Dark mode */
18
+ dark?: boolean;
19
+ /** Whether the editor is read-only */
20
+ readOnly?: boolean;
21
+ /** Placeholder text */
22
+ placeholder?: string;
23
+ /** Show inferred output type below the editor */
24
+ showType?: boolean;
25
+ /** Additional CodeMirror extensions */
26
+ extensions?: Extension[];
27
+ }
28
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/editor/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE/C,MAAM,WAAW,eAAe;IAC/B,sCAAsC;IACtC,MAAM,EAAE,WAAW,CAAC;IAEpB,0DAA0D;IAC1D,MAAM,EAAE,SAAS,CAAC;IAElB,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,wCAAwC;IACxC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAEnC,iDAAiD;IACjD,QAAQ,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,aAAa,EAAE,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IAE9E,qDAAqD;IACrD,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,gBAAgB;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IAEf,sCAAsC;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,uCAAuC;IACvC,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC;CACzB"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ export * from "./language/index";
2
+ export * from "./completion/index";
3
+ export * from "./linting/index";
4
+ export * from "./editor/index";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from "./language/index";
2
+ export * from "./completion/index";
3
+ export * from "./linting/index";
4
+ export * from "./editor/index";
@@ -0,0 +1,2 @@
1
+ export * from "./tokenizer-config";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/language/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC"}
@@ -0,0 +1 @@
1
+ export * from "./tokenizer-config";
@@ -0,0 +1,4 @@
1
+ import { type Extension } from "@codemirror/state";
2
+ import type { TokenizerConfig } from "./tokenizer-config";
3
+ export declare const createSemanticHighlighter: (config: TokenizerConfig, dark?: boolean) => Extension;
4
+ //# sourceMappingURL=semantic-highlighter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semantic-highlighter.d.ts","sourceRoot":"","sources":["../../src/language/semantic-highlighter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,SAAS,EAAmB,MAAM,mBAAmB,CAAC;AAQpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AA0E1D,eAAO,MAAM,yBAAyB,GACrC,QAAQ,eAAe,EACvB,cAAY,KACV,SAgBF,CAAC"}
@@ -0,0 +1,76 @@
1
+ import { syntaxTree } from "@codemirror/language";
2
+ import { RangeSetBuilder } from "@codemirror/state";
3
+ import { Decoration, ViewPlugin, } from "@codemirror/view";
4
+ const LIGHT_COLORS = {
5
+ contract: "#00695c",
6
+ function: "#1565c0",
7
+ macro: "#6a1b9a",
8
+ variable: "#37474f",
9
+ };
10
+ const DARK_COLORS = {
11
+ contract: "#4db6ac",
12
+ function: "#64b5f6",
13
+ macro: "#ce93d8",
14
+ variable: "#b0bec5",
15
+ };
16
+ const createDecorations = (dark) => {
17
+ const colors = dark ? DARK_COLORS : LIGHT_COLORS;
18
+ return {
19
+ contract: Decoration.mark({
20
+ attributes: { style: `color: ${colors.contract}` },
21
+ }),
22
+ function: Decoration.mark({
23
+ attributes: { style: `color: ${colors.function}` },
24
+ }),
25
+ macro: Decoration.mark({ attributes: { style: `color: ${colors.macro}` } }),
26
+ variable: Decoration.mark({
27
+ attributes: { style: `color: ${colors.variable}` },
28
+ }),
29
+ };
30
+ };
31
+ const buildDecorations = (view, config, decos) => {
32
+ const builder = new RangeSetBuilder();
33
+ const tree = syntaxTree(view.state);
34
+ for (const { from, to } of view.visibleRanges) {
35
+ tree.iterate({
36
+ from,
37
+ to,
38
+ enter(node) {
39
+ if (node.name !== "Identifier") {
40
+ return;
41
+ }
42
+ const name = view.state.doc.sliceString(node.from, node.to);
43
+ let deco;
44
+ if (config.contractNames.has(name)) {
45
+ deco = decos.contract;
46
+ }
47
+ else if (config.functionNames.has(name)) {
48
+ deco = decos.function;
49
+ }
50
+ else if (config.macroNames.has(name)) {
51
+ deco = decos.macro;
52
+ }
53
+ else if (config.variableNames.has(name)) {
54
+ deco = decos.variable;
55
+ }
56
+ if (deco) {
57
+ builder.add(node.from, node.to, deco);
58
+ }
59
+ },
60
+ });
61
+ }
62
+ return builder.finish();
63
+ };
64
+ export const createSemanticHighlighter = function (config, dark = false) {
65
+ const decos = createDecorations(dark);
66
+ return ViewPlugin.define((view) => ({
67
+ decorations: buildDecorations(view, config, decos),
68
+ update(update) {
69
+ if (update.docChanged || update.viewportChanged) {
70
+ this.decorations = buildDecorations(update.view, config, decos);
71
+ }
72
+ },
73
+ }), {
74
+ decorations: (v) => v.decorations,
75
+ });
76
+ };
@@ -0,0 +1,9 @@
1
+ import type { SELSchema } from "@seljs/schema";
2
+ export interface TokenizerConfig {
3
+ contractNames: Set<string>;
4
+ functionNames: Set<string>;
5
+ macroNames: Set<string>;
6
+ variableNames: Set<string>;
7
+ }
8
+ export declare const createTokenizerConfig: (schema: SELSchema) => TokenizerConfig;
9
+ //# sourceMappingURL=tokenizer-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokenizer-config.d.ts","sourceRoot":"","sources":["../../src/language/tokenizer-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE/C,MAAM,WAAW,eAAe;IAC/B,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACxB,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC3B;AAED,eAAO,MAAM,qBAAqB,GAAI,QAAQ,SAAS,KAAG,eAKxD,CAAC"}
@@ -0,0 +1,6 @@
1
+ export const createTokenizerConfig = (schema) => ({
2
+ contractNames: new Set(schema.contracts.map((c) => c.name)),
3
+ functionNames: new Set(schema.functions.map((f) => f.name)),
4
+ macroNames: new Set(schema.macros.map((m) => m.name)),
5
+ variableNames: new Set(schema.variables.map((v) => v.name)),
6
+ });
@@ -0,0 +1,28 @@
1
+ import type { SELDiagnostic } from "@seljs/checker";
2
+ interface CheckResult {
3
+ valid: boolean;
4
+ error?: Error;
5
+ }
6
+ /**
7
+ * Maps a TypeCheckResult-shaped object or a caught Error to SELDiagnostic[].
8
+ *
9
+ * Handles both failure modes from `@seljs/runtime`'s `env.check()`:
10
+ * 1. Returned result: `{ valid: false, error: TypeError }`
11
+ * 2. Thrown exception: `SELParseError` or `SELTypeError`
12
+ *
13
+ * Usage:
14
+ * ```ts
15
+ * const validate = (expression: string): SELDiagnostic[] => {
16
+ * try {
17
+ * const result = env.check(expression);
18
+ * return mapCheckResult(result, expression.length);
19
+ * } catch (error) {
20
+ * return mapCheckResult(error as Error, expression.length);
21
+ * }
22
+ * };
23
+ * ```
24
+ */
25
+ declare const mapCheckResult: (resultOrError: CheckResult | Error, docLength: number) => SELDiagnostic[];
26
+ export { mapCheckResult };
27
+ export type { SELDiagnostic };
28
+ //# sourceMappingURL=diagnostic-mapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnostic-mapper.d.ts","sourceRoot":"","sources":["../../src/linting/diagnostic-mapper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD,UAAU,WAAW;IACpB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,KAAK,CAAC;CACd;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,QAAA,MAAM,cAAc,GACnB,eAAe,WAAW,GAAG,KAAK,EAClC,WAAW,MAAM,KACf,aAAa,EA4Bf,CAAC;AAEF,OAAO,EAAE,cAAc,EAAE,CAAC;AAC1B,YAAY,EAAE,aAAa,EAAE,CAAC"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Maps a TypeCheckResult-shaped object or a caught Error to SELDiagnostic[].
3
+ *
4
+ * Handles both failure modes from `@seljs/runtime`'s `env.check()`:
5
+ * 1. Returned result: `{ valid: false, error: TypeError }`
6
+ * 2. Thrown exception: `SELParseError` or `SELTypeError`
7
+ *
8
+ * Usage:
9
+ * ```ts
10
+ * const validate = (expression: string): SELDiagnostic[] => {
11
+ * try {
12
+ * const result = env.check(expression);
13
+ * return mapCheckResult(result, expression.length);
14
+ * } catch (error) {
15
+ * return mapCheckResult(error as Error, expression.length);
16
+ * }
17
+ * };
18
+ * ```
19
+ */
20
+ const mapCheckResult = (resultOrError, docLength) => {
21
+ // Thrown error path
22
+ if (resultOrError instanceof Error) {
23
+ return [
24
+ {
25
+ message: resultOrError.message,
26
+ severity: "error",
27
+ from: 0,
28
+ to: Math.max(0, docLength),
29
+ },
30
+ ];
31
+ }
32
+ // Returned result path
33
+ if (resultOrError.valid) {
34
+ return [];
35
+ }
36
+ const message = resultOrError.error?.message ?? "Invalid expression";
37
+ return [
38
+ {
39
+ message,
40
+ severity: "error",
41
+ from: 0,
42
+ to: Math.max(0, docLength),
43
+ },
44
+ ];
45
+ };
46
+ export { mapCheckResult };