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.
Files changed (58) hide show
  1. package/package.json +3 -1
  2. package/src/commands.js +112 -4
  3. package/src/comment-syntax.js +364 -39
  4. package/src/config/handlers.js +1 -2
  5. package/src/config/schema.js +46 -4
  6. package/src/document-template.js +2236 -0
  7. package/src/frontmatter-updater.js +204 -74
  8. package/src/grammar.js +758 -0
  9. package/src/index.js +1074 -55
  10. package/src/keymap.js +11 -2
  11. package/src/markdown/block-decorations.js +108 -5
  12. package/src/markdown/facets.js +37 -0
  13. package/src/markdown/html-inline.js +9 -5
  14. package/src/markdown/index.js +13 -3
  15. package/src/markdown/inline-commands.js +256 -0
  16. package/src/markdown/inline-model.js +578 -0
  17. package/src/markdown/inline-state.js +103 -0
  18. package/src/markdown/renderer.js +219 -12
  19. package/src/markdown/styles.js +290 -3
  20. package/src/markdown/widgets/alert-title.js +10 -8
  21. package/src/markdown/widgets/frontmatter.js +0 -6
  22. package/src/markdown/widgets/index.js +1 -0
  23. package/src/markdown/widgets/list-marker.js +29 -0
  24. package/src/markdown/wysiwyg.js +1158 -0
  25. package/src/mrp-types.js +2 -0
  26. package/src/output-widget.js +532 -18
  27. package/src/page-view-pagination.js +127 -0
  28. package/src/runtime-lsp.js +1757 -150
  29. package/src/section-controls/commands.js +617 -0
  30. package/src/section-controls/index.js +63 -0
  31. package/src/section-controls/plugin.js +165 -0
  32. package/src/section-controls/widgets.js +936 -0
  33. package/src/shell/ai-menu.js +11 -0
  34. package/src/shell/components/context-panel.js +572 -0
  35. package/src/shell/components/status-bar.js +10 -2
  36. package/src/shell/layouts/studio.js +206 -14
  37. package/src/shell/orchestrator-client.js +69 -0
  38. package/src/spellcheck.js +166 -0
  39. package/src/tables/README.md +97 -0
  40. package/src/tables/commands/insert-linked-table.js +122 -0
  41. package/src/tables/commands/open-table-workspace.js +43 -0
  42. package/src/tables/index.js +24 -0
  43. package/src/tables/jobs/client.js +158 -0
  44. package/src/tables/parsing/anchors.js +82 -0
  45. package/src/tables/parsing/linked-table-blocks.js +61 -0
  46. package/src/tables/state/linked-table-state.js +68 -0
  47. package/src/tables/widgets/linked-table-source-banner.js +77 -0
  48. package/src/tables/widgets/linked-table-widget.js +256 -0
  49. package/src/tables/workspace/controller.js +616 -0
  50. package/src/term-pty-client.js +51 -2
  51. package/src/term-widget.js +43 -3
  52. package/src/widgets/theme-utils.js +24 -16
  53. package/src/widgets/theme.js +1015 -1
  54. package/src/runtime-codelens/detector.js +0 -279
  55. package/src/runtime-codelens/index.js +0 -76
  56. package/src/runtime-codelens/plugin.js +0 -142
  57. package/src/runtime-codelens/styles.js +0 -184
  58. 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
- }