mrmd-editor 0.7.1 → 0.8.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/package.json +3 -1
- package/src/commands.js +112 -4
- package/src/comment-syntax.js +364 -39
- package/src/config/handlers.js +1 -2
- package/src/config/schema.js +46 -4
- package/src/document-template.js +2236 -0
- package/src/frontmatter-updater.js +204 -74
- package/src/grammar.js +758 -0
- package/src/index.js +1074 -55
- package/src/keymap.js +11 -2
- package/src/markdown/block-decorations.js +108 -5
- package/src/markdown/facets.js +37 -0
- package/src/markdown/html-inline.js +9 -5
- package/src/markdown/index.js +13 -3
- package/src/markdown/inline-commands.js +256 -0
- package/src/markdown/inline-model.js +578 -0
- package/src/markdown/inline-state.js +103 -0
- package/src/markdown/renderer.js +219 -12
- package/src/markdown/styles.js +290 -3
- package/src/markdown/widgets/alert-title.js +10 -8
- package/src/markdown/widgets/frontmatter.js +0 -6
- package/src/markdown/widgets/index.js +1 -0
- package/src/markdown/widgets/list-marker.js +29 -0
- package/src/markdown/wysiwyg.js +1158 -0
- package/src/mrp-types.js +2 -0
- package/src/output-widget.js +532 -18
- package/src/page-view-pagination.js +127 -0
- package/src/runtime-lsp.js +1757 -150
- package/src/section-controls/commands.js +617 -0
- package/src/section-controls/index.js +63 -0
- package/src/section-controls/plugin.js +165 -0
- package/src/section-controls/widgets.js +936 -0
- package/src/shell/ai-menu.js +11 -0
- package/src/shell/components/context-panel.js +572 -0
- package/src/shell/components/status-bar.js +10 -2
- package/src/shell/layouts/studio.js +206 -14
- package/src/shell/orchestrator-client.js +69 -0
- package/src/spellcheck.js +166 -0
- package/src/tables/README.md +97 -0
- package/src/tables/commands/insert-linked-table.js +122 -0
- package/src/tables/commands/open-table-workspace.js +43 -0
- package/src/tables/index.js +24 -0
- package/src/tables/jobs/client.js +158 -0
- package/src/tables/parsing/anchors.js +82 -0
- package/src/tables/parsing/linked-table-blocks.js +61 -0
- package/src/tables/state/linked-table-state.js +68 -0
- package/src/tables/widgets/linked-table-source-banner.js +77 -0
- package/src/tables/widgets/linked-table-widget.js +256 -0
- package/src/tables/workspace/controller.js +616 -0
- package/src/term-pty-client.js +51 -2
- package/src/term-widget.js +43 -3
- package/src/widgets/theme-utils.js +24 -16
- package/src/widgets/theme.js +1015 -1
- package/src/runtime-codelens/detector.js +0 -279
- package/src/runtime-codelens/index.js +0 -76
- package/src/runtime-codelens/plugin.js +0 -142
- package/src/runtime-codelens/styles.js +0 -184
- package/src/runtime-codelens/widgets.js +0 -216
|
@@ -1,279 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Runtime CodeLens Block Detection
|
|
3
|
-
*
|
|
4
|
-
* Detects blocks that should have runtime CodeLens:
|
|
5
|
-
* 1. `yaml config` blocks in mrmd.md containing session config
|
|
6
|
-
* 2. Frontmatter in documents containing session overrides
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import yaml from 'yaml';
|
|
10
|
-
|
|
11
|
-
/** Supported runtime languages */
|
|
12
|
-
const RUNTIME_LANGUAGES = ['python', 'bash', 'node', 'julia', 'r', 'shell'];
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* @typedef {Object} RuntimeConfig
|
|
16
|
-
* @property {string} language - Runtime language (python, node, etc.)
|
|
17
|
-
* @property {string} [name] - Session name
|
|
18
|
-
* @property {string} [venv] - Virtual environment path (Python)
|
|
19
|
-
* @property {string} [cwd] - Working directory
|
|
20
|
-
* @property {boolean} [autoStart] - Auto-start on project open
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* @typedef {Object} DetectedBlock
|
|
25
|
-
* @property {'config' | 'frontmatter'} type - Block type
|
|
26
|
-
* @property {number} start - Start character offset
|
|
27
|
-
* @property {number} end - End character offset
|
|
28
|
-
* @property {number} fenceLineEnd - End of opening fence/delimiter line (where to place widget)
|
|
29
|
-
* @property {string} content - Raw YAML content
|
|
30
|
-
* @property {RuntimeConfig[]} runtimes - Parsed runtime configurations
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Find all yaml config blocks containing session configuration
|
|
35
|
-
* @param {string} content - Document content
|
|
36
|
-
* @returns {DetectedBlock[]}
|
|
37
|
-
*/
|
|
38
|
-
export function findYamlConfigBlocks(content) {
|
|
39
|
-
const blocks = [];
|
|
40
|
-
const lines = content.split('\n');
|
|
41
|
-
|
|
42
|
-
let inBlock = false;
|
|
43
|
-
let blockStart = 0;
|
|
44
|
-
let fenceLineEnd = 0;
|
|
45
|
-
let blockContent = [];
|
|
46
|
-
let fenceChar = '';
|
|
47
|
-
let fenceLength = 0;
|
|
48
|
-
let charOffset = 0;
|
|
49
|
-
|
|
50
|
-
for (let i = 0; i < lines.length; i++) {
|
|
51
|
-
const line = lines[i];
|
|
52
|
-
const lineStart = charOffset;
|
|
53
|
-
const lineEnd = charOffset + line.length;
|
|
54
|
-
|
|
55
|
-
if (!inBlock) {
|
|
56
|
-
// Look for opening fence: ```yaml config or ~~~yaml config
|
|
57
|
-
const match = line.match(/^(`{3,}|~{3,})yaml\s+config\s*$/);
|
|
58
|
-
if (match) {
|
|
59
|
-
inBlock = true;
|
|
60
|
-
blockStart = lineStart;
|
|
61
|
-
fenceLineEnd = lineEnd;
|
|
62
|
-
fenceChar = match[1][0];
|
|
63
|
-
fenceLength = match[1].length;
|
|
64
|
-
blockContent = [];
|
|
65
|
-
}
|
|
66
|
-
} else {
|
|
67
|
-
// Look for closing fence (same char, at least same length)
|
|
68
|
-
const closingPattern = new RegExp(`^${fenceChar}{${fenceLength},}\\s*$`);
|
|
69
|
-
if (closingPattern.test(line)) {
|
|
70
|
-
// Parse the YAML content
|
|
71
|
-
const yamlContent = blockContent.join('\n');
|
|
72
|
-
// Content starts after the opening fence line + newline
|
|
73
|
-
const contentStartOffset = fenceLineEnd + 1;
|
|
74
|
-
const runtimes = extractRuntimes(yamlContent, contentStartOffset);
|
|
75
|
-
|
|
76
|
-
// Only include if it has runtime config
|
|
77
|
-
if (runtimes.length > 0) {
|
|
78
|
-
blocks.push({
|
|
79
|
-
type: 'config',
|
|
80
|
-
start: blockStart,
|
|
81
|
-
end: lineEnd,
|
|
82
|
-
fenceLineEnd,
|
|
83
|
-
content: yamlContent,
|
|
84
|
-
runtimes,
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
inBlock = false;
|
|
89
|
-
blockContent = [];
|
|
90
|
-
} else {
|
|
91
|
-
blockContent.push(line);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
charOffset += line.length + 1; // +1 for newline
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return blocks;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Find frontmatter block if it contains session configuration
|
|
103
|
-
* @param {string} content - Document content
|
|
104
|
-
* @returns {DetectedBlock | null}
|
|
105
|
-
*/
|
|
106
|
-
export function findSessionFrontmatter(content) {
|
|
107
|
-
// Frontmatter must start at the beginning with ---
|
|
108
|
-
if (!content.startsWith('---')) {
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Find the closing ---
|
|
113
|
-
const endMatch = content.indexOf('\n---', 3);
|
|
114
|
-
if (endMatch === -1) {
|
|
115
|
-
return null;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const yamlContent = content.slice(4, endMatch); // Skip opening ---\n
|
|
119
|
-
const runtimes = extractRuntimes(yamlContent);
|
|
120
|
-
|
|
121
|
-
// Only return if it has runtime config
|
|
122
|
-
if (runtimes.length === 0) {
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return {
|
|
127
|
-
type: 'frontmatter',
|
|
128
|
-
start: 0,
|
|
129
|
-
end: endMatch + 4, // Include closing ---
|
|
130
|
-
fenceLineEnd: 3, // End of opening ---
|
|
131
|
-
content: yamlContent,
|
|
132
|
-
runtimes,
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Extract runtime configurations from YAML content
|
|
138
|
-
*
|
|
139
|
-
* Supports both verbose and minimal syntax:
|
|
140
|
-
* - Verbose: session: { python: { venv: .venv } }
|
|
141
|
-
* - Minimal: python: .venv
|
|
142
|
-
* - Minimal object: python: { venv: .venv }
|
|
143
|
-
*
|
|
144
|
-
* @param {string} yamlContent - Raw YAML string
|
|
145
|
-
* @param {number} [contentStartOffset=0] - Character offset where YAML content starts in document
|
|
146
|
-
* @returns {RuntimeConfig[]}
|
|
147
|
-
*/
|
|
148
|
-
export function extractRuntimes(yamlContent, contentStartOffset = 0) {
|
|
149
|
-
const runtimes = [];
|
|
150
|
-
|
|
151
|
-
try {
|
|
152
|
-
const parsed = yaml.parse(yamlContent);
|
|
153
|
-
if (!parsed) return runtimes;
|
|
154
|
-
|
|
155
|
-
// Normalize: check for minimal syntax first, then verbose
|
|
156
|
-
// This matches the normalization in mrmd-project
|
|
157
|
-
const sessionConfig = normalizeToSessionConfig(parsed);
|
|
158
|
-
|
|
159
|
-
if (!sessionConfig) {
|
|
160
|
-
return runtimes;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Check for each supported runtime language
|
|
164
|
-
for (const language of RUNTIME_LANGUAGES) {
|
|
165
|
-
const config = sessionConfig[language];
|
|
166
|
-
if (config) {
|
|
167
|
-
// Find the line where this runtime is declared
|
|
168
|
-
// Try both minimal (python:) and verbose ( python: under session:)
|
|
169
|
-
let lineOffset = null;
|
|
170
|
-
|
|
171
|
-
// Try minimal syntax first (top-level python:)
|
|
172
|
-
const minimalPattern = new RegExp(`^(${language}:)`, 'm');
|
|
173
|
-
const minimalMatch = yamlContent.match(minimalPattern);
|
|
174
|
-
|
|
175
|
-
if (minimalMatch) {
|
|
176
|
-
const keyStart = minimalMatch.index;
|
|
177
|
-
const lineEnd = yamlContent.indexOf('\n', keyStart);
|
|
178
|
-
lineOffset = contentStartOffset + (lineEnd !== -1 ? lineEnd : keyStart + minimalMatch[1].length);
|
|
179
|
-
} else {
|
|
180
|
-
// Try verbose syntax (indented under session:)
|
|
181
|
-
const verbosePattern = new RegExp(`^( ${language}:)`, 'm');
|
|
182
|
-
const verboseMatch = yamlContent.match(verbosePattern);
|
|
183
|
-
|
|
184
|
-
if (verboseMatch) {
|
|
185
|
-
const keyStart = verboseMatch.index;
|
|
186
|
-
const lineEnd = yamlContent.indexOf('\n', keyStart);
|
|
187
|
-
lineOffset = contentStartOffset + (lineEnd !== -1 ? lineEnd : keyStart + verboseMatch[1].length);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
runtimes.push({
|
|
192
|
-
language,
|
|
193
|
-
name: config.name || 'default',
|
|
194
|
-
venv: config.venv,
|
|
195
|
-
cwd: config.cwd,
|
|
196
|
-
autoStart: config.auto_start ?? config.autoStart ?? true,
|
|
197
|
-
lineOffset, // Position to place widget for this runtime
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
} catch (e) {
|
|
202
|
-
// Invalid YAML - return empty
|
|
203
|
-
console.warn('[RuntimeCodeLens] Failed to parse YAML:', e.message);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return runtimes;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Normalize parsed YAML to session config format.
|
|
211
|
-
* Handles both minimal syntax (python: .venv) and verbose (session: { python: { venv: .venv } })
|
|
212
|
-
*
|
|
213
|
-
* @param {object} parsed - Parsed YAML object
|
|
214
|
-
* @returns {object | null} Session config object or null if no runtime config found
|
|
215
|
-
*/
|
|
216
|
-
function normalizeToSessionConfig(parsed) {
|
|
217
|
-
if (!parsed || typeof parsed !== 'object') {
|
|
218
|
-
return null;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// If already has session key, use it directly
|
|
222
|
-
if (parsed.session && typeof parsed.session === 'object') {
|
|
223
|
-
return parsed.session;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Check for minimal syntax (python:, bash:, etc. at top level)
|
|
227
|
-
const sessionConfig = {};
|
|
228
|
-
let hasRuntime = false;
|
|
229
|
-
|
|
230
|
-
for (const language of RUNTIME_LANGUAGES) {
|
|
231
|
-
if (language in parsed) {
|
|
232
|
-
const value = parsed[language];
|
|
233
|
-
hasRuntime = true;
|
|
234
|
-
|
|
235
|
-
if (typeof value === 'string') {
|
|
236
|
-
// Minimal string form: python: .venv -> { venv: '.venv' }
|
|
237
|
-
if (language === 'python') {
|
|
238
|
-
sessionConfig[language] = { venv: value };
|
|
239
|
-
} else {
|
|
240
|
-
sessionConfig[language] = { cwd: value };
|
|
241
|
-
}
|
|
242
|
-
} else if (typeof value === 'object' && value !== null) {
|
|
243
|
-
// Minimal object form: python: { venv: .venv }
|
|
244
|
-
sessionConfig[language] = value;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
return hasRuntime ? sessionConfig : null;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Find all blocks (config blocks + frontmatter) that need CodeLens
|
|
254
|
-
* @param {string} content - Document content
|
|
255
|
-
* @param {Object} options
|
|
256
|
-
* @param {boolean} [options.includeConfigBlocks=true] - Include yaml config blocks
|
|
257
|
-
* @param {boolean} [options.includeFrontmatter=true] - Include frontmatter
|
|
258
|
-
* @returns {DetectedBlock[]}
|
|
259
|
-
*/
|
|
260
|
-
export function findRuntimeBlocks(content, options = {}) {
|
|
261
|
-
const { includeConfigBlocks = true, includeFrontmatter = true } = options;
|
|
262
|
-
const blocks = [];
|
|
263
|
-
|
|
264
|
-
if (includeConfigBlocks) {
|
|
265
|
-
blocks.push(...findYamlConfigBlocks(content));
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
if (includeFrontmatter) {
|
|
269
|
-
const frontmatter = findSessionFrontmatter(content);
|
|
270
|
-
if (frontmatter) {
|
|
271
|
-
blocks.push(frontmatter);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Sort by position (frontmatter first if present)
|
|
276
|
-
blocks.sort((a, b) => a.start - b.start);
|
|
277
|
-
|
|
278
|
-
return blocks;
|
|
279
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Runtime CodeLens Extension
|
|
3
|
-
*
|
|
4
|
-
* Provides inline controls for starting/stopping/restarting runtimes
|
|
5
|
-
* directly above yaml config blocks and frontmatter.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```javascript
|
|
9
|
-
* import { createRuntimeCodeLensExtensions } from './runtime-codelens';
|
|
10
|
-
*
|
|
11
|
-
* const extensions = createRuntimeCodeLensExtensions({
|
|
12
|
-
* projectName: 'my-project',
|
|
13
|
-
* getSessionStatus: (name) => shellState.get(`runtimes.sessions.${name}`),
|
|
14
|
-
* onStart: async (runtime) => { await electronAPI.runtime.start(runtime); },
|
|
15
|
-
* onStop: async (name) => { await electronAPI.runtime.stop(name); },
|
|
16
|
-
* onRestart: async (name) => { await electronAPI.runtime.restart(name); },
|
|
17
|
-
* onRestartAll: async () => { ... },
|
|
18
|
-
* });
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import { runtimeCodeLensFacet, runtimeCodeLensPlugin, rebuildRuntimeCodeLensEffect } from './plugin.js';
|
|
23
|
-
import { injectRuntimeCodeLensStyles } from './styles.js';
|
|
24
|
-
|
|
25
|
-
// Re-export for external use
|
|
26
|
-
export { runtimeCodeLensFacet, runtimeCodeLensPlugin, rebuildRuntimeCodeLensEffect };
|
|
27
|
-
export { injectRuntimeCodeLensStyles, removeRuntimeCodeLensStyles } from './styles.js';
|
|
28
|
-
export { findRuntimeBlocks, findYamlConfigBlocks, findSessionFrontmatter } from './detector.js';
|
|
29
|
-
export { RuntimeCodeLensWidget } from './widgets.js';
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* @typedef {Object} RuntimeCodeLensOptions
|
|
33
|
-
* @property {string} [projectName='default'] - Project name for session naming
|
|
34
|
-
* @property {(name: string) => Object|null} getSessionStatus - Get session status by name
|
|
35
|
-
* @property {(runtime: Object) => void|Promise<void>} onStart - Start a runtime
|
|
36
|
-
* @property {(name: string) => void|Promise<void>} onStop - Stop a runtime
|
|
37
|
-
* @property {(name: string) => void|Promise<void>} onRestart - Restart a runtime
|
|
38
|
-
* @property {() => void|Promise<void>} [onRestartAll] - Restart all runtimes
|
|
39
|
-
*/
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Create CodeMirror extensions for runtime CodeLens
|
|
43
|
-
*
|
|
44
|
-
* @param {RuntimeCodeLensOptions} options - Configuration options
|
|
45
|
-
* @returns {import('@codemirror/state').Extension[]} Array of CodeMirror extensions
|
|
46
|
-
*/
|
|
47
|
-
export function createRuntimeCodeLensExtensions(options) {
|
|
48
|
-
// Inject styles on first use
|
|
49
|
-
injectRuntimeCodeLensStyles();
|
|
50
|
-
|
|
51
|
-
const context = {
|
|
52
|
-
projectName: options.projectName || 'default',
|
|
53
|
-
getSessionStatus: options.getSessionStatus || (() => null),
|
|
54
|
-
onStart: options.onStart || (() => {}),
|
|
55
|
-
onStop: options.onStop || (() => {}),
|
|
56
|
-
onRestart: options.onRestart || (() => {}),
|
|
57
|
-
onRestartAll: options.onRestartAll || (() => {}),
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
return [
|
|
61
|
-
runtimeCodeLensFacet.of(context),
|
|
62
|
-
runtimeCodeLensPlugin,
|
|
63
|
-
];
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Dispatch effect to rebuild runtime CodeLens decorations
|
|
68
|
-
* Call this when session status changes to update the UI
|
|
69
|
-
*
|
|
70
|
-
* @param {import('@codemirror/view').EditorView} view - Editor view
|
|
71
|
-
*/
|
|
72
|
-
export function rebuildRuntimeCodeLens(view) {
|
|
73
|
-
view.dispatch({
|
|
74
|
-
effects: rebuildRuntimeCodeLensEffect.of(null),
|
|
75
|
-
});
|
|
76
|
-
}
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Runtime CodeLens Plugin
|
|
3
|
-
*
|
|
4
|
-
* CodeMirror StateField that renders runtime control widgets
|
|
5
|
-
* above yaml config blocks and frontmatter with session config.
|
|
6
|
-
*
|
|
7
|
-
* Note: Block decorations MUST come from StateField, not ViewPlugin.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { EditorView, Decoration } from '@codemirror/view';
|
|
11
|
-
import { Facet, StateEffect, StateField } from '@codemirror/state';
|
|
12
|
-
import { findRuntimeBlocks } from './detector.js';
|
|
13
|
-
import { RuntimeCodeLensWidget } from './widgets.js';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* @typedef {Object} RuntimeCodeLensContext
|
|
17
|
-
* @property {(name: string) => Object|null} getSessionStatus - Get status for session name
|
|
18
|
-
* @property {(runtime: Object) => void} onStart - Start a runtime
|
|
19
|
-
* @property {(name: string) => void} onStop - Stop a runtime
|
|
20
|
-
* @property {(name: string) => void} onRestart - Restart a runtime
|
|
21
|
-
* @property {() => void} onRestartAll - Restart all runtimes
|
|
22
|
-
* @property {string} [projectName] - Project name for session naming
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Facet for passing context (callbacks, state) to the plugin
|
|
27
|
-
*/
|
|
28
|
-
export const runtimeCodeLensFacet = Facet.define({
|
|
29
|
-
combine: (values) => values[0] || null,
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Effect to trigger decoration rebuild (e.g., when session status changes)
|
|
34
|
-
*/
|
|
35
|
-
export const rebuildRuntimeCodeLensEffect = StateEffect.define();
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Get full session name (project:name format)
|
|
39
|
-
* @param {string} name - Runtime name from config
|
|
40
|
-
* @param {string} projectName - Project name
|
|
41
|
-
* @returns {string}
|
|
42
|
-
*/
|
|
43
|
-
function getSessionName(name, projectName) {
|
|
44
|
-
return `${projectName || 'default'}:${name || 'default'}`;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Build decorations for all detected runtime blocks
|
|
49
|
-
* @param {import('@codemirror/state').EditorState} state
|
|
50
|
-
* @returns {import('@codemirror/view').DecorationSet}
|
|
51
|
-
*/
|
|
52
|
-
function buildDecorations(state) {
|
|
53
|
-
const context = state.facet(runtimeCodeLensFacet);
|
|
54
|
-
|
|
55
|
-
if (!context) {
|
|
56
|
-
return Decoration.none;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const content = state.doc.toString();
|
|
60
|
-
const blocks = findRuntimeBlocks(content);
|
|
61
|
-
|
|
62
|
-
if (blocks.length === 0) {
|
|
63
|
-
return Decoration.none;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const decorations = [];
|
|
67
|
-
|
|
68
|
-
for (const block of blocks) {
|
|
69
|
-
// Create a separate widget for each runtime at its line position
|
|
70
|
-
for (const runtime of block.runtimes) {
|
|
71
|
-
const sessionName = getSessionName(runtime.name, context.projectName);
|
|
72
|
-
const status = context.getSessionStatus?.(sessionName) || null;
|
|
73
|
-
|
|
74
|
-
const runtimeWithStatus = {
|
|
75
|
-
...runtime,
|
|
76
|
-
name: sessionName,
|
|
77
|
-
status,
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
// Create widget for this single runtime
|
|
81
|
-
const widget = new RuntimeCodeLensWidget({
|
|
82
|
-
runtimes: [runtimeWithStatus],
|
|
83
|
-
callbacks: {
|
|
84
|
-
onStart: (rt) => context.onStart?.(rt),
|
|
85
|
-
onStop: (name) => context.onStop?.(name),
|
|
86
|
-
onRestart: (name) => context.onRestart?.(name),
|
|
87
|
-
onRestartAll: () => context.onRestartAll?.(),
|
|
88
|
-
},
|
|
89
|
-
blockType: block.type,
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
// Place widget at the runtime's line position, or fallback to block start
|
|
93
|
-
const position = runtime.lineOffset ?? block.fenceLineEnd;
|
|
94
|
-
|
|
95
|
-
decorations.push(
|
|
96
|
-
Decoration.widget({
|
|
97
|
-
widget,
|
|
98
|
-
side: 1, // After the position
|
|
99
|
-
block: true, // Render as block element (requires StateField)
|
|
100
|
-
}).range(position)
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return Decoration.set(decorations, true);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* StateField for runtime CodeLens decorations
|
|
110
|
-
* Block decorations must come from StateField, not ViewPlugin
|
|
111
|
-
*/
|
|
112
|
-
export const runtimeCodeLensField = StateField.define({
|
|
113
|
-
create(state) {
|
|
114
|
-
return buildDecorations(state);
|
|
115
|
-
},
|
|
116
|
-
|
|
117
|
-
update(decorations, tr) {
|
|
118
|
-
// Check if we need to rebuild
|
|
119
|
-
const hasRebuildEffect = tr.effects.some((e) => e.is(rebuildRuntimeCodeLensEffect));
|
|
120
|
-
|
|
121
|
-
if (tr.docChanged || hasRebuildEffect) {
|
|
122
|
-
return buildDecorations(tr.state);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Check if context changed
|
|
126
|
-
const oldContext = tr.startState.facet(runtimeCodeLensFacet);
|
|
127
|
-
const newContext = tr.state.facet(runtimeCodeLensFacet);
|
|
128
|
-
if (oldContext !== newContext) {
|
|
129
|
-
return buildDecorations(tr.state);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return decorations;
|
|
133
|
-
},
|
|
134
|
-
|
|
135
|
-
provide: (field) => EditorView.decorations.from(field),
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Extension array that includes the StateField
|
|
140
|
-
* Use this as the plugin export for backwards compatibility
|
|
141
|
-
*/
|
|
142
|
-
export const runtimeCodeLensPlugin = runtimeCodeLensField;
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Runtime CodeLens Styles
|
|
3
|
-
*
|
|
4
|
-
* Injects CSS for the runtime CodeLens widget.
|
|
5
|
-
* Uses CSS variables for theming compatibility.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
let stylesInjected = false;
|
|
9
|
-
|
|
10
|
-
const STYLES = `
|
|
11
|
-
/* Runtime CodeLens Container */
|
|
12
|
-
.cm-runtime-codelens {
|
|
13
|
-
display: flex;
|
|
14
|
-
flex-direction: column;
|
|
15
|
-
gap: 4px;
|
|
16
|
-
padding: 6px 10px;
|
|
17
|
-
margin: 2px 0 4px 0;
|
|
18
|
-
background: var(--runtime-codelens-bg, rgba(128, 128, 128, 0.08));
|
|
19
|
-
border-radius: 4px;
|
|
20
|
-
font-size: 12px;
|
|
21
|
-
font-family: var(--font-ui, system-ui, -apple-system, sans-serif);
|
|
22
|
-
line-height: 1.4;
|
|
23
|
-
border-left: 3px solid var(--runtime-codelens-border, rgba(128, 128, 128, 0.2));
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/* Individual runtime row */
|
|
27
|
-
.cm-runtime-codelens-row {
|
|
28
|
-
display: flex;
|
|
29
|
-
align-items: center;
|
|
30
|
-
gap: 8px;
|
|
31
|
-
min-height: 24px;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
.cm-runtime-codelens-row--all {
|
|
35
|
-
margin-top: 4px;
|
|
36
|
-
padding-top: 4px;
|
|
37
|
-
border-top: 1px solid var(--runtime-codelens-divider, rgba(128, 128, 128, 0.15));
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/* Language label */
|
|
41
|
-
.cm-runtime-codelens-label {
|
|
42
|
-
font-weight: 500;
|
|
43
|
-
color: var(--runtime-codelens-label, inherit);
|
|
44
|
-
min-width: 80px;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/* Session name badge */
|
|
48
|
-
.cm-runtime-codelens-name {
|
|
49
|
-
padding: 1px 6px;
|
|
50
|
-
background: var(--runtime-codelens-badge-bg, rgba(128, 128, 128, 0.15));
|
|
51
|
-
border-radius: 3px;
|
|
52
|
-
font-size: 11px;
|
|
53
|
-
color: var(--runtime-codelens-badge-text, inherit);
|
|
54
|
-
opacity: 0.8;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/* Actions container */
|
|
58
|
-
.cm-runtime-codelens-actions {
|
|
59
|
-
display: flex;
|
|
60
|
-
gap: 4px;
|
|
61
|
-
margin-left: auto;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/* Action buttons */
|
|
65
|
-
.cm-runtime-codelens-btn {
|
|
66
|
-
padding: 2px 8px;
|
|
67
|
-
border: none;
|
|
68
|
-
border-radius: 3px;
|
|
69
|
-
cursor: pointer;
|
|
70
|
-
background: var(--runtime-codelens-btn-bg, rgba(128, 128, 128, 0.12));
|
|
71
|
-
color: var(--runtime-codelens-btn-text, inherit);
|
|
72
|
-
font-size: 12px;
|
|
73
|
-
font-family: inherit;
|
|
74
|
-
transition: background 0.15s ease, transform 0.1s ease;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
.cm-runtime-codelens-btn:hover {
|
|
78
|
-
background: var(--runtime-codelens-btn-bg-hover, rgba(128, 128, 128, 0.2));
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
.cm-runtime-codelens-btn:active {
|
|
82
|
-
transform: scale(0.95);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/* Start button - slightly emphasized */
|
|
86
|
-
.cm-runtime-codelens-btn:first-child:not(.cm-runtime-codelens-btn--restart-all) {
|
|
87
|
-
background: var(--runtime-codelens-btn-start-bg, rgba(34, 197, 94, 0.15));
|
|
88
|
-
color: var(--runtime-codelens-btn-start-text, #22c55e);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
.cm-runtime-codelens-btn:first-child:not(.cm-runtime-codelens-btn--restart-all):hover {
|
|
92
|
-
background: var(--runtime-codelens-btn-start-bg-hover, rgba(34, 197, 94, 0.25));
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/* Restart All button */
|
|
96
|
-
.cm-runtime-codelens-btn--restart-all {
|
|
97
|
-
width: 100%;
|
|
98
|
-
padding: 4px 8px;
|
|
99
|
-
background: var(--runtime-codelens-btn-restart-all-bg, rgba(128, 128, 128, 0.1));
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/* Status indicator */
|
|
103
|
-
.cm-runtime-codelens-status {
|
|
104
|
-
display: flex;
|
|
105
|
-
align-items: center;
|
|
106
|
-
gap: 4px;
|
|
107
|
-
margin-left: 8px;
|
|
108
|
-
min-width: 100px;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
.cm-runtime-codelens-status-text {
|
|
112
|
-
opacity: 0.7;
|
|
113
|
-
font-size: 11px;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/* Status dot */
|
|
117
|
-
.cm-runtime-codelens-dot {
|
|
118
|
-
display: inline-block;
|
|
119
|
-
width: 8px;
|
|
120
|
-
height: 8px;
|
|
121
|
-
border-radius: 50%;
|
|
122
|
-
flex-shrink: 0;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
.cm-runtime-codelens-dot--green {
|
|
126
|
-
background: var(--runtime-codelens-dot-green, #22c55e);
|
|
127
|
-
box-shadow: 0 0 4px var(--runtime-codelens-dot-green, #22c55e);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
.cm-runtime-codelens-dot--yellow {
|
|
131
|
-
background: var(--runtime-codelens-dot-yellow, #eab308);
|
|
132
|
-
animation: pulse 1.5s ease-in-out infinite;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
.cm-runtime-codelens-dot--gray {
|
|
136
|
-
background: var(--runtime-codelens-dot-gray, #9ca3af);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
.cm-runtime-codelens-dot--red {
|
|
140
|
-
background: var(--runtime-codelens-dot-red, #ef4444);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
@keyframes pulse {
|
|
144
|
-
0%, 100% { opacity: 1; }
|
|
145
|
-
50% { opacity: 0.5; }
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/* Dark theme adjustments */
|
|
149
|
-
.cm-editor.cm-dark .cm-runtime-codelens,
|
|
150
|
-
.dark .cm-runtime-codelens {
|
|
151
|
-
--runtime-codelens-bg: rgba(255, 255, 255, 0.05);
|
|
152
|
-
--runtime-codelens-border: rgba(255, 255, 255, 0.1);
|
|
153
|
-
--runtime-codelens-divider: rgba(255, 255, 255, 0.1);
|
|
154
|
-
--runtime-codelens-btn-bg: rgba(255, 255, 255, 0.08);
|
|
155
|
-
--runtime-codelens-btn-bg-hover: rgba(255, 255, 255, 0.15);
|
|
156
|
-
--runtime-codelens-badge-bg: rgba(255, 255, 255, 0.1);
|
|
157
|
-
}
|
|
158
|
-
`;
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Inject runtime CodeLens styles into the document
|
|
162
|
-
* Safe to call multiple times - will only inject once
|
|
163
|
-
*/
|
|
164
|
-
export function injectRuntimeCodeLensStyles() {
|
|
165
|
-
if (stylesInjected) return;
|
|
166
|
-
|
|
167
|
-
const style = document.createElement('style');
|
|
168
|
-
style.id = 'cm-runtime-codelens-styles';
|
|
169
|
-
style.textContent = STYLES;
|
|
170
|
-
document.head.appendChild(style);
|
|
171
|
-
|
|
172
|
-
stylesInjected = true;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Remove injected styles (for cleanup/testing)
|
|
177
|
-
*/
|
|
178
|
-
export function removeRuntimeCodeLensStyles() {
|
|
179
|
-
const existing = document.getElementById('cm-runtime-codelens-styles');
|
|
180
|
-
if (existing) {
|
|
181
|
-
existing.remove();
|
|
182
|
-
stylesInjected = false;
|
|
183
|
-
}
|
|
184
|
-
}
|