@checkstack/ui 0.2.4 → 0.3.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.
- package/CHANGELOG.md +30 -0
- package/package.json +10 -3
- package/src/components/CodeEditor/CodeEditor.tsx +420 -0
- package/src/components/CodeEditor/index.ts +10 -0
- package/src/components/CodeEditor/languageSupport/enterBehavior.test.ts +173 -0
- package/src/components/CodeEditor/languageSupport/enterBehavior.ts +35 -0
- package/src/components/CodeEditor/languageSupport/index.ts +22 -0
- package/src/components/CodeEditor/languageSupport/json.test.ts +271 -0
- package/src/components/CodeEditor/languageSupport/json.ts +240 -0
- package/src/components/CodeEditor/languageSupport/markdown.test.ts +245 -0
- package/src/components/CodeEditor/languageSupport/markdown.ts +183 -0
- package/src/components/CodeEditor/languageSupport/types.ts +48 -0
- package/src/components/CodeEditor/languageSupport/xml.test.ts +236 -0
- package/src/components/CodeEditor/languageSupport/xml.ts +194 -0
- package/src/components/CodeEditor/languageSupport/yaml.test.ts +200 -0
- package/src/components/CodeEditor/languageSupport/yaml.ts +205 -0
- package/src/components/DynamicForm/DynamicForm.tsx +2 -24
- package/src/components/DynamicForm/DynamicOptionsField.tsx +48 -21
- package/src/components/DynamicForm/FormField.tsx +38 -70
- package/src/components/DynamicForm/JsonField.tsx +19 -25
- package/src/components/DynamicForm/KeyValueEditor.tsx +314 -0
- package/src/components/DynamicForm/MultiTypeEditorField.tsx +465 -0
- package/src/components/DynamicForm/index.ts +13 -0
- package/src/components/DynamicForm/types.ts +14 -8
- package/src/components/DynamicForm/utils.test.ts +390 -0
- package/src/components/DynamicForm/utils.ts +142 -3
- package/src/index.ts +1 -1
- package/src/components/TemplateEditor.test.ts +0 -156
- package/src/components/TemplateEditor.tsx +0 -435
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
isValidMarkdownTemplatePosition,
|
|
5
|
+
calculateMarkdownIndentation,
|
|
6
|
+
} from "./markdown";
|
|
7
|
+
|
|
8
|
+
describe("isValidMarkdownTemplatePosition", () => {
|
|
9
|
+
describe("valid text positions", () => {
|
|
10
|
+
it("returns true for plain text", () => {
|
|
11
|
+
expect(isValidMarkdownTemplatePosition("Hello world")).toBe(true);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("returns true after heading", () => {
|
|
15
|
+
expect(isValidMarkdownTemplatePosition("# Title\n")).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("returns true in paragraph", () => {
|
|
19
|
+
expect(isValidMarkdownTemplatePosition("Some text here")).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("returns true after bold text", () => {
|
|
23
|
+
expect(isValidMarkdownTemplatePosition("**bold** and more")).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("returns true after italic text", () => {
|
|
27
|
+
expect(isValidMarkdownTemplatePosition("*italic* text")).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("returns true in list item", () => {
|
|
31
|
+
expect(isValidMarkdownTemplatePosition("- item text")).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("returns true in numbered list item", () => {
|
|
35
|
+
expect(isValidMarkdownTemplatePosition("1. first item")).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("returns true in blockquote", () => {
|
|
39
|
+
expect(isValidMarkdownTemplatePosition("> quote text")).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("code positions (invalid)", () => {
|
|
44
|
+
it("returns false inside code block", () => {
|
|
45
|
+
expect(isValidMarkdownTemplatePosition("```\ncode here")).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("returns false inside inline code", () => {
|
|
49
|
+
expect(isValidMarkdownTemplatePosition("Here is `some code")).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("returns true after closed code block", () => {
|
|
53
|
+
expect(isValidMarkdownTemplatePosition("```\ncode\n```\n")).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("returns true after closed inline code", () => {
|
|
57
|
+
expect(isValidMarkdownTemplatePosition("Here is `code` and more")).toBe(
|
|
58
|
+
true,
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("returns false in code block with language", () => {
|
|
63
|
+
expect(isValidMarkdownTemplatePosition("```javascript\nvar x")).toBe(
|
|
64
|
+
false,
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("returns true after code block with content", () => {
|
|
69
|
+
expect(isValidMarkdownTemplatePosition("```js\ncode\n```\ntext")).toBe(
|
|
70
|
+
true,
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe("edge cases", () => {
|
|
76
|
+
it("returns true for empty line after heading", () => {
|
|
77
|
+
expect(isValidMarkdownTemplatePosition("# Heading\n\n")).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("returns true for link text", () => {
|
|
81
|
+
expect(isValidMarkdownTemplatePosition("[link text")).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("returns true after link", () => {
|
|
85
|
+
expect(isValidMarkdownTemplatePosition("[text](url) more")).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("returns false for multiple unclosed code blocks", () => {
|
|
89
|
+
expect(isValidMarkdownTemplatePosition("```\n```\n```\ncode")).toBe(
|
|
90
|
+
false,
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("returns true for multiple closed code blocks", () => {
|
|
95
|
+
expect(isValidMarkdownTemplatePosition("```\n```\n```\n```\ntext")).toBe(
|
|
96
|
+
true,
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("calculateMarkdownIndentation", () => {
|
|
103
|
+
const INDENT = 2;
|
|
104
|
+
|
|
105
|
+
// ============================================================================
|
|
106
|
+
// Basic indentation
|
|
107
|
+
// ============================================================================
|
|
108
|
+
describe("basic indentation", () => {
|
|
109
|
+
it("returns 0 for empty string", () => {
|
|
110
|
+
expect(calculateMarkdownIndentation("", INDENT)).toBe(0);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("returns 0 for simple text", () => {
|
|
114
|
+
expect(calculateMarkdownIndentation("Hello world", INDENT)).toBe(0);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("returns 0 after heading", () => {
|
|
118
|
+
expect(calculateMarkdownIndentation("# Heading\n", INDENT)).toBe(0);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("returns 0 after paragraph", () => {
|
|
122
|
+
expect(calculateMarkdownIndentation("Some text.\n", INDENT)).toBe(0);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// ============================================================================
|
|
127
|
+
// List indentation
|
|
128
|
+
// ============================================================================
|
|
129
|
+
describe("list indentation", () => {
|
|
130
|
+
it("maintains list indentation after list item", () => {
|
|
131
|
+
expect(calculateMarkdownIndentation("- item\n", INDENT)).toBe(0);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("maintains nested list indentation", () => {
|
|
135
|
+
expect(calculateMarkdownIndentation(" - nested item\n", INDENT)).toBe(2);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("handles numbered list", () => {
|
|
139
|
+
expect(calculateMarkdownIndentation("1. item\n", INDENT)).toBe(0);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("handles deeply nested list", () => {
|
|
143
|
+
const text = `- item
|
|
144
|
+
- nested
|
|
145
|
+
- deep\n`;
|
|
146
|
+
expect(calculateMarkdownIndentation(text, INDENT)).toBe(4);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// ============================================================================
|
|
151
|
+
// Template handling
|
|
152
|
+
// ============================================================================
|
|
153
|
+
describe("template handling", () => {
|
|
154
|
+
it("handles templates in text", () => {
|
|
155
|
+
expect(calculateMarkdownIndentation("Hello {{name}}", INDENT)).toBe(0);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("handles templates in list items", () => {
|
|
159
|
+
expect(calculateMarkdownIndentation("- {{item}}\n", INDENT)).toBe(0);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("handles templates in headings", () => {
|
|
163
|
+
expect(calculateMarkdownIndentation("# {{title}}\n", INDENT)).toBe(0);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// ============================================================================
|
|
168
|
+
// Code block indentation
|
|
169
|
+
// ============================================================================
|
|
170
|
+
describe("code block indentation", () => {
|
|
171
|
+
it("returns 0 after code block start", () => {
|
|
172
|
+
expect(calculateMarkdownIndentation("```\n", INDENT)).toBe(0);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("returns 0 inside code block", () => {
|
|
176
|
+
expect(calculateMarkdownIndentation("```\ncode line\n", INDENT)).toBe(0);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("returns 0 after code block end", () => {
|
|
180
|
+
expect(calculateMarkdownIndentation("```\ncode\n```\n", INDENT)).toBe(0);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// ============================================================================
|
|
185
|
+
// Edge cases
|
|
186
|
+
// ============================================================================
|
|
187
|
+
describe("edge cases", () => {
|
|
188
|
+
it("handles only newline", () => {
|
|
189
|
+
expect(calculateMarkdownIndentation("\n", INDENT)).toBe(0);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("handles multiple newlines", () => {
|
|
193
|
+
expect(calculateMarkdownIndentation("\n\n\n", INDENT)).toBe(0);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("handles blockquote", () => {
|
|
197
|
+
expect(calculateMarkdownIndentation("> quote\n", INDENT)).toBe(0);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("handles horizontal rule", () => {
|
|
201
|
+
expect(calculateMarkdownIndentation("---\n", INDENT)).toBe(0);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("handles table row", () => {
|
|
205
|
+
expect(calculateMarkdownIndentation("| col1 | col2 |\n", INDENT)).toBe(0);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// ============================================================================
|
|
210
|
+
// Real-world document structures
|
|
211
|
+
// ============================================================================
|
|
212
|
+
describe("real-world document structures", () => {
|
|
213
|
+
it("handles README-style document", () => {
|
|
214
|
+
const text = `# Project
|
|
215
|
+
|
|
216
|
+
Some description.
|
|
217
|
+
|
|
218
|
+
## Features
|
|
219
|
+
|
|
220
|
+
- Feature 1
|
|
221
|
+
- Feature 2\n`;
|
|
222
|
+
expect(calculateMarkdownIndentation(text, INDENT)).toBe(0);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("handles nested list structure", () => {
|
|
226
|
+
const text = `## Tasks
|
|
227
|
+
|
|
228
|
+
- Task 1
|
|
229
|
+
- Subtask A
|
|
230
|
+
- Subtask B\n`;
|
|
231
|
+
expect(calculateMarkdownIndentation(text, INDENT)).toBe(2);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("handles code example in docs", () => {
|
|
235
|
+
const text = `## Usage
|
|
236
|
+
|
|
237
|
+
\`\`\`javascript
|
|
238
|
+
const x = 1;
|
|
239
|
+
\`\`\`
|
|
240
|
+
|
|
241
|
+
More text\n`;
|
|
242
|
+
expect(calculateMarkdownIndentation(text, INDENT)).toBe(0);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
});
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { markdown } from "@codemirror/lang-markdown";
|
|
2
|
+
import { Decoration } from "@codemirror/view";
|
|
3
|
+
import type { LanguageSupport, DecorationRange } from "./types";
|
|
4
|
+
|
|
5
|
+
// Decoration marks for Markdown syntax highlighting using inline styles
|
|
6
|
+
const mdHeadingMark = Decoration.mark({
|
|
7
|
+
attributes: { style: "color: hsl(280, 65%, 60%); font-weight: bold" },
|
|
8
|
+
});
|
|
9
|
+
const mdBoldMark = Decoration.mark({
|
|
10
|
+
attributes: { style: "font-weight: bold" },
|
|
11
|
+
});
|
|
12
|
+
const mdItalicMark = Decoration.mark({
|
|
13
|
+
attributes: { style: "font-style: italic" },
|
|
14
|
+
});
|
|
15
|
+
const mdCodeMark = Decoration.mark({
|
|
16
|
+
attributes: {
|
|
17
|
+
style:
|
|
18
|
+
"color: hsl(142.1, 76.2%, 36.3%); background: hsla(0, 0%, 50%, 0.1); border-radius: 3px; padding: 0 2px",
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
const mdLinkMark = Decoration.mark({
|
|
22
|
+
attributes: { style: "color: hsl(217.2, 91.2%, 59.8%)" },
|
|
23
|
+
});
|
|
24
|
+
const templateMark = Decoration.mark({
|
|
25
|
+
attributes: { style: "color: hsl(190, 70%, 50%); font-weight: 500" },
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Check if cursor is in a valid Markdown template position.
|
|
30
|
+
* Templates can appear almost anywhere in Markdown (text content).
|
|
31
|
+
* @internal Exported for testing
|
|
32
|
+
*/
|
|
33
|
+
export function isValidMarkdownTemplatePosition(text: string): boolean {
|
|
34
|
+
// In Markdown, templates are valid almost everywhere except:
|
|
35
|
+
// - Inside code blocks (``` ... ```)
|
|
36
|
+
// - Inside inline code (` ... `)
|
|
37
|
+
|
|
38
|
+
// Check for unclosed code blocks
|
|
39
|
+
const codeBlockMatches = text.match(/```/g) || [];
|
|
40
|
+
if (codeBlockMatches.length % 2 === 1) {
|
|
41
|
+
return false; // Inside a code block
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Check if we're inside inline code on the current line
|
|
45
|
+
const lastNewline = text.lastIndexOf("\n");
|
|
46
|
+
const currentLine = text.slice(lastNewline + 1);
|
|
47
|
+
const backticks = (currentLine.match(/`/g) || []).length;
|
|
48
|
+
|
|
49
|
+
// If odd number of backticks, we're inside inline code
|
|
50
|
+
if (backticks % 2 === 1) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Valid in all other positions
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Build Markdown + template decorations.
|
|
60
|
+
*/
|
|
61
|
+
function buildMarkdownDecorations(doc: string): DecorationRange[] {
|
|
62
|
+
const ranges: DecorationRange[] = [];
|
|
63
|
+
|
|
64
|
+
// Match templates first (highest priority)
|
|
65
|
+
const templateRegex = /\{\{[\w.[\]]*\}\}/g;
|
|
66
|
+
let match;
|
|
67
|
+
while ((match = templateRegex.exec(doc)) !== null) {
|
|
68
|
+
ranges.push({
|
|
69
|
+
from: match.index,
|
|
70
|
+
to: match.index + match[0].length,
|
|
71
|
+
decoration: templateMark,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Match headings (# ... or ## ... etc.)
|
|
76
|
+
const headingRegex = /^(#{1,6})\s+.+$/gm;
|
|
77
|
+
while ((match = headingRegex.exec(doc)) !== null) {
|
|
78
|
+
ranges.push({
|
|
79
|
+
from: match.index,
|
|
80
|
+
to: match.index + match[0].length,
|
|
81
|
+
decoration: mdHeadingMark,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Match bold (**text** or __text__)
|
|
86
|
+
const boldRegex = /(\*\*|__)(?!\s)(.+?)(?<!\s)\1/g;
|
|
87
|
+
while ((match = boldRegex.exec(doc)) !== null) {
|
|
88
|
+
ranges.push({
|
|
89
|
+
from: match.index,
|
|
90
|
+
to: match.index + match[0].length,
|
|
91
|
+
decoration: mdBoldMark,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Match italic (*text* or _text_) - but not **
|
|
96
|
+
const italicRegex = /(?<!\*|\w)(\*|_)(?!\*|\s)(.+?)(?<!\s|\*)\1(?!\*|\w)/g;
|
|
97
|
+
while ((match = italicRegex.exec(doc)) !== null) {
|
|
98
|
+
ranges.push({
|
|
99
|
+
from: match.index,
|
|
100
|
+
to: match.index + match[0].length,
|
|
101
|
+
decoration: mdItalicMark,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Match inline code (`code`)
|
|
106
|
+
const codeRegex = /`[^`\n]+`/g;
|
|
107
|
+
while ((match = codeRegex.exec(doc)) !== null) {
|
|
108
|
+
ranges.push({
|
|
109
|
+
from: match.index,
|
|
110
|
+
to: match.index + match[0].length,
|
|
111
|
+
decoration: mdCodeMark,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Match links [text](url)
|
|
116
|
+
const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
117
|
+
while ((match = linkRegex.exec(doc)) !== null) {
|
|
118
|
+
ranges.push({
|
|
119
|
+
from: match.index,
|
|
120
|
+
to: match.index + match[0].length,
|
|
121
|
+
decoration: mdLinkMark,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Sort by position
|
|
126
|
+
ranges.sort((a, b) => a.from - b.from || a.to - b.to);
|
|
127
|
+
|
|
128
|
+
// Remove overlaps (templates take priority)
|
|
129
|
+
const filtered: DecorationRange[] = [];
|
|
130
|
+
for (const range of ranges) {
|
|
131
|
+
const overlaps = filtered.some(
|
|
132
|
+
(existing) =>
|
|
133
|
+
(range.from >= existing.from && range.from < existing.to) ||
|
|
134
|
+
(range.to > existing.from && range.to <= existing.to),
|
|
135
|
+
);
|
|
136
|
+
if (!overlaps) {
|
|
137
|
+
filtered.push(range);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return filtered;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Calculate indentation for Markdown.
|
|
146
|
+
* Markdown uses list indentation.
|
|
147
|
+
* @internal Exported for testing
|
|
148
|
+
*/
|
|
149
|
+
export function calculateMarkdownIndentation(
|
|
150
|
+
textBefore: string,
|
|
151
|
+
_indentUnit: number,
|
|
152
|
+
): number {
|
|
153
|
+
const lines = textBefore.split("\n");
|
|
154
|
+
if (lines.length === 0) return 0;
|
|
155
|
+
|
|
156
|
+
// Find the last non-empty line
|
|
157
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
158
|
+
const line = lines[i];
|
|
159
|
+
if (line.trim().length === 0) continue;
|
|
160
|
+
|
|
161
|
+
// Count leading spaces
|
|
162
|
+
const leadingSpaces = line.match(/^(\s*)/)?.[1].length ?? 0;
|
|
163
|
+
|
|
164
|
+
// If line is a list item, next line should be indented if continuing
|
|
165
|
+
if (/^\s*[-*+]\s/.test(line) || /^\s*\d+\.\s/.test(line)) {
|
|
166
|
+
return leadingSpaces;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return leadingSpaces;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return 0;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Markdown language support for CodeEditor with template expression handling.
|
|
177
|
+
*/
|
|
178
|
+
export const markdownLanguageSupport: LanguageSupport = {
|
|
179
|
+
extension: markdown(),
|
|
180
|
+
buildDecorations: buildMarkdownDecorations,
|
|
181
|
+
isValidTemplatePosition: isValidMarkdownTemplatePosition,
|
|
182
|
+
calculateIndentation: calculateMarkdownIndentation,
|
|
183
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Extension } from "@codemirror/state";
|
|
2
|
+
import type { Decoration } from "@codemirror/view";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A decoration range to be applied to the document.
|
|
6
|
+
*/
|
|
7
|
+
export interface DecorationRange {
|
|
8
|
+
from: number;
|
|
9
|
+
to: number;
|
|
10
|
+
decoration: Decoration;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Interface for language-specific support in CodeEditor.
|
|
15
|
+
* Each language (JSON, YAML, etc.) implements this interface to provide
|
|
16
|
+
* syntax highlighting that works correctly with {{template}} expressions.
|
|
17
|
+
*/
|
|
18
|
+
export interface LanguageSupport {
|
|
19
|
+
/**
|
|
20
|
+
* The CodeMirror language extension for this language.
|
|
21
|
+
* Provides indentation, bracket matching, folding, etc.
|
|
22
|
+
*/
|
|
23
|
+
extension: Extension;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Build decoration ranges for syntax highlighting.
|
|
27
|
+
* This is used to override the Lezer parser's highlighting when templates
|
|
28
|
+
* are present (since {{}} confuses most language parsers).
|
|
29
|
+
* @param doc The document text
|
|
30
|
+
* @returns Array of decoration ranges to apply
|
|
31
|
+
*/
|
|
32
|
+
buildDecorations: (doc: string) => DecorationRange[];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if the cursor position is valid for template insertion.
|
|
36
|
+
* @param textBefore The document text before the cursor
|
|
37
|
+
* @returns true if templates can be inserted at this position
|
|
38
|
+
*/
|
|
39
|
+
isValidTemplatePosition: (textBefore: string) => boolean;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Calculate the indentation level for a given line.
|
|
43
|
+
* This is used to provide correct indentation that won't be confused by templates.
|
|
44
|
+
* @param textBefore All text before the current line
|
|
45
|
+
* @returns Number of spaces to indent
|
|
46
|
+
*/
|
|
47
|
+
calculateIndentation: (textBefore: string, indentUnit: number) => number;
|
|
48
|
+
}
|