@joinezco/codeblock 0.0.8 → 0.0.10

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 (73) hide show
  1. package/dist/editor.d.ts +30 -3
  2. package/dist/editor.js +416 -43
  3. package/dist/index.d.ts +5 -1
  4. package/dist/index.js +5 -1
  5. package/dist/lsps/index.d.ts +5 -0
  6. package/dist/lsps/index.js +9 -2
  7. package/dist/lsps/typescript.d.ts +3 -1
  8. package/dist/lsps/typescript.js +8 -17
  9. package/dist/panels/settings.d.ts +22 -0
  10. package/dist/panels/settings.js +267 -0
  11. package/dist/panels/terminal.d.ts +3 -0
  12. package/dist/panels/terminal.js +76 -0
  13. package/dist/panels/toolbar.d.ts +53 -3
  14. package/dist/panels/toolbar.js +1336 -164
  15. package/dist/panels/toolbar.test.js +20 -14
  16. package/dist/rpc/transport.d.ts +2 -11
  17. package/dist/rpc/transport.js +19 -35
  18. package/dist/themes/index.js +226 -13
  19. package/dist/themes/vscode.js +3 -2
  20. package/dist/types.d.ts +5 -0
  21. package/dist/utils/fs.d.ts +22 -3
  22. package/dist/utils/fs.js +126 -21
  23. package/dist/utils/lsp.d.ts +26 -15
  24. package/dist/utils/lsp.js +79 -44
  25. package/dist/utils/search.d.ts +2 -0
  26. package/dist/utils/search.js +13 -4
  27. package/dist/utils/typescript-defaults.d.ts +57 -0
  28. package/dist/utils/typescript-defaults.js +208 -0
  29. package/dist/utils/typescript-defaults.test.d.ts +1 -0
  30. package/dist/utils/typescript-defaults.test.js +197 -0
  31. package/dist/workers/fs.worker.d.ts +4 -8
  32. package/dist/workers/fs.worker.js +30 -60
  33. package/dist/workers/javascript.worker.js +11 -9
  34. package/package.json +8 -4
  35. package/dist/assets/clike-C8IJ2oj_.js +0 -1
  36. package/dist/assets/cmake-BQqOBYOt.js +0 -1
  37. package/dist/assets/dockerfile-C_y-rIpk.js +0 -1
  38. package/dist/assets/fs.worker-BwEqZcql.ts +0 -109
  39. package/dist/assets/go-CTD25R5P.js +0 -1
  40. package/dist/assets/haskell-BWDZoCOh.js +0 -1
  41. package/dist/assets/index-9HdhmM_Y.js +0 -1
  42. package/dist/assets/index-C-QhPFHP.js +0 -3
  43. package/dist/assets/index-C3BnE2cG.js +0 -222
  44. package/dist/assets/index-CGx5MZO7.js +0 -6
  45. package/dist/assets/index-CIuq3uTk.js +0 -1
  46. package/dist/assets/index-CXFONXS8.js +0 -1
  47. package/dist/assets/index-D5Z27j1C.js +0 -1
  48. package/dist/assets/index-DWOBdRjn.js +0 -1
  49. package/dist/assets/index-Dvu-FFzd.js +0 -1
  50. package/dist/assets/index-Dx_VuNNd.js +0 -1
  51. package/dist/assets/index-I0dlv-r3.js +0 -1
  52. package/dist/assets/index-MGle_v2x.js +0 -1
  53. package/dist/assets/index-N-GE7HTU.js +0 -1
  54. package/dist/assets/index-aEsF5o-7.js +0 -2
  55. package/dist/assets/index-as7ELo0J.js +0 -1
  56. package/dist/assets/index-gUUzXNuP.js +0 -1
  57. package/dist/assets/index-pGm0qkrJ.js +0 -13
  58. package/dist/assets/javascript.worker-C1zGArKk.js +0 -527
  59. package/dist/assets/lua-BgMRiT3U.js +0 -1
  60. package/dist/assets/perl-CdXCOZ3F.js +0 -1
  61. package/dist/assets/process-Dw9K5EnD.js +0 -1357
  62. package/dist/assets/properties-C78fOPTZ.js +0 -1
  63. package/dist/assets/ruby-B2Rjki9n.js +0 -1
  64. package/dist/assets/shell-CjFT_Tl9.js +0 -1
  65. package/dist/assets/swift-BzpIVaGY.js +0 -1
  66. package/dist/assets/toml-BXUEaScT.js +0 -1
  67. package/dist/assets/vb-CmGdzxic.js +0 -1
  68. package/dist/e2e/example.spec.d.ts +0 -5
  69. package/dist/e2e/example.spec.js +0 -44
  70. package/dist/index.html +0 -16
  71. package/dist/resources/config.json +0 -13
  72. package/dist/snapshot.bin +0 -0
  73. package/dist/styles.css +0 -7
@@ -43,6 +43,10 @@ export declare const extOrLanguageToLanguageId: {
43
43
  readonly ts: "javascript";
44
44
  readonly jsx: "javascript";
45
45
  readonly tsx: "javascript";
46
+ readonly mjs: "javascript";
47
+ readonly cjs: "javascript";
48
+ readonly mts: "javascript";
49
+ readonly cts: "javascript";
46
50
  readonly python: "python";
47
51
  readonly py: "python";
48
52
  readonly ruby: "ruby";
@@ -80,6 +84,7 @@ export declare const extOrLanguageToLanguageId: {
80
84
  readonly yaml: "yaml";
81
85
  readonly yml: "yaml";
82
86
  readonly xml: "xml";
87
+ readonly svg: "xml";
83
88
  readonly markdown: "markdown";
84
89
  readonly md: "markdown";
85
90
  readonly toml: "toml";
@@ -30,8 +30,10 @@ const languageSupportMap = {
30
30
  return less();
31
31
  },
32
32
  json: async () => {
33
- const { json } = await import('@codemirror/lang-json');
34
- return json();
33
+ // Use JavaScript mode for JSON files — it handles both strict JSON
34
+ // and lenient JSON-like syntax (unquoted keys, trailing commas, comments)
35
+ const { javascript } = await import('@codemirror/lang-javascript');
36
+ return javascript();
35
37
  },
36
38
  xml: async () => {
37
39
  const { xml } = await import('@codemirror/lang-xml');
@@ -147,6 +149,10 @@ export const extOrLanguageToLanguageId = {
147
149
  ts: 'javascript',
148
150
  jsx: 'javascript',
149
151
  tsx: 'javascript',
152
+ mjs: 'javascript',
153
+ cjs: 'javascript',
154
+ mts: 'javascript',
155
+ cts: 'javascript',
150
156
  python: 'python',
151
157
  py: 'python',
152
158
  ruby: 'ruby',
@@ -184,6 +190,7 @@ export const extOrLanguageToLanguageId = {
184
190
  yaml: 'yaml',
185
191
  yml: 'yaml',
186
192
  xml: 'xml',
193
+ svg: 'xml',
187
194
  markdown: 'markdown',
188
195
  md: 'markdown',
189
196
  toml: 'toml',
@@ -3,8 +3,10 @@ import { Connection } from '@volar/language-server/browser';
3
3
  export type CreateTypescriptEnvironmentArgs = {
4
4
  connection: Connection;
5
5
  fs: VfsInterface;
6
+ /** Pre-resolved lib file contents keyed by path, for synchronous cache population */
7
+ libFiles?: Record<string, string>;
6
8
  };
7
- export declare const createLanguageServer: ({ connection, fs }: CreateTypescriptEnvironmentArgs) => Promise<{
9
+ export declare const createLanguageServer: ({ connection, fs, libFiles }: CreateTypescriptEnvironmentArgs) => Promise<{
8
10
  initializeParams: import("vscode-languageserver-protocol").InitializeParams;
9
11
  project: import("@volar/language-server").LanguageServerProject;
10
12
  languageServicePlugins: import("@volar/language-service").LanguageServicePlugin<any>[];
@@ -10,7 +10,7 @@ function getLanguageServicePlugins(_ts) {
10
10
  ];
11
11
  return plugins;
12
12
  }
13
- export const createLanguageServer = async ({ connection, fs }) => {
13
+ export const createLanguageServer = async ({ connection, fs, libFiles }) => {
14
14
  const server = createServerBase(connection, {
15
15
  timer: {
16
16
  setImmediate: (callback, ...args) => {
@@ -18,31 +18,22 @@ export const createLanguageServer = async ({ connection, fs }) => {
18
18
  },
19
19
  },
20
20
  });
21
- server.fileSystem.install('file', new VolarFs(fs));
22
- server.onInitialize((params) => {
23
- console.debug('ts server on init', params);
24
- });
25
- connection.onShutdown(() => {
26
- console.debug('ts server shutdown');
27
- });
21
+ const volarFs = new VolarFs(fs);
22
+ if (libFiles) {
23
+ volarFs.preloadFromMap(libFiles);
24
+ }
25
+ server.fileSystem.install('file', volarFs);
28
26
  connection.onInitialize(async (params) => {
29
27
  const languageServicePlugins = getLanguageServicePlugins(ts);
30
28
  return server.initialize(params, createTypeScriptProject(
31
29
  // @ts-ignore
32
- ts, undefined, async () => ({
33
- // rootUri: params.rootUri,
30
+ ts, undefined, async (_ctx) => ({
34
31
  languagePlugins: []
35
32
  })), languageServicePlugins);
36
33
  });
37
34
  connection.onInitialized(() => {
38
35
  server.initialized();
39
- const extensions = [
40
- '.tsx',
41
- '.jsx',
42
- '.js',
43
- '.ts'
44
- ];
45
- server.fileWatcher.watchFiles([`**/*.{${extensions.join(',')}}`]);
36
+ server.fileWatcher.watchFiles(['**/*.{tsx,jsx,js,ts,json}']);
46
37
  });
47
38
  return server;
48
39
  };
@@ -0,0 +1,22 @@
1
+ import { EditorView } from "@codemirror/view";
2
+ import { Facet, StateField } from "@codemirror/state";
3
+ export interface EditorSettings {
4
+ theme: 'light' | 'dark' | 'system';
5
+ fontSize: number;
6
+ fontFamily: string;
7
+ autosave: boolean;
8
+ lineWrap: boolean;
9
+ lspLogEnabled: boolean;
10
+ agentUrl: string;
11
+ terminalEnabled: boolean;
12
+ maxVisibleLines: number;
13
+ showLineNumbers: boolean;
14
+ showFoldGutter: boolean;
15
+ autoHideToolbar: boolean;
16
+ }
17
+ /** Facet carrying initial settings so settingsField.create() can read them without circular imports. */
18
+ export declare const InitialSettingsFacet: Facet<Partial<EditorSettings>, Partial<EditorSettings>>;
19
+ export declare const updateSettingsEffect: import("@codemirror/state").StateEffectType<Partial<EditorSettings>>;
20
+ export declare const settingsField: StateField<EditorSettings>;
21
+ export declare function resolveThemeDark(theme: EditorSettings['theme']): boolean;
22
+ export declare function createSettingsOverlay(view: EditorView): HTMLElement;
@@ -0,0 +1,267 @@
1
+ import { EditorView } from "@codemirror/view";
2
+ import { Facet, StateEffect, StateField } from "@codemirror/state";
3
+ import { setThemeEffect, lineWrappingCompartment } from "../editor";
4
+ const defaultSettings = {
5
+ theme: 'system',
6
+ fontSize: 16,
7
+ fontFamily: '',
8
+ autosave: true,
9
+ lineWrap: false,
10
+ lspLogEnabled: false,
11
+ agentUrl: '',
12
+ terminalEnabled: false,
13
+ maxVisibleLines: 0,
14
+ showLineNumbers: true,
15
+ showFoldGutter: true,
16
+ autoHideToolbar: false,
17
+ };
18
+ /** Facet carrying initial settings so settingsField.create() can read them without circular imports. */
19
+ export const InitialSettingsFacet = Facet.define({
20
+ combine: (values) => Object.assign({}, ...values),
21
+ });
22
+ export const updateSettingsEffect = StateEffect.define();
23
+ export const settingsField = StateField.define({
24
+ create(state) {
25
+ const initial = state.facet(InitialSettingsFacet);
26
+ return { ...defaultSettings, ...initial };
27
+ },
28
+ update(value, tr) {
29
+ for (const e of tr.effects) {
30
+ if (e.is(updateSettingsEffect)) {
31
+ return { ...value, ...e.value };
32
+ }
33
+ }
34
+ return value;
35
+ }
36
+ });
37
+ export function resolveThemeDark(theme) {
38
+ if (theme === 'system') {
39
+ return window.matchMedia('(prefers-color-scheme: dark)').matches;
40
+ }
41
+ return theme === 'dark';
42
+ }
43
+ const themeIcons = {
44
+ light: '☀️',
45
+ dark: '🌙',
46
+ system: '🌓',
47
+ };
48
+ // Settings overlay component (no header — toolbar shows "settings.json" and cog becomes ✕)
49
+ export function createSettingsOverlay(view) {
50
+ const settings = view.state.field(settingsField);
51
+ const overlay = document.createElement("div");
52
+ overlay.className = "cm-settings-overlay";
53
+ // Theme section
54
+ const themeSection = document.createElement("div");
55
+ themeSection.className = "cm-settings-section";
56
+ const themeTitle = document.createElement("div");
57
+ themeTitle.className = "cm-settings-section-title";
58
+ themeTitle.textContent = "Theme";
59
+ themeSection.appendChild(themeTitle);
60
+ // Font size
61
+ const fontSizeRow = document.createElement("div");
62
+ fontSizeRow.className = "cm-settings-row";
63
+ const fontSizeLabel = document.createElement("label");
64
+ fontSizeLabel.textContent = "Font size";
65
+ const fontSizeControl = document.createElement("div");
66
+ fontSizeControl.className = "cm-settings-control";
67
+ const fontSizeRange = document.createElement("input");
68
+ fontSizeRange.type = "range";
69
+ fontSizeRange.className = "cm-settings-font-size-range";
70
+ fontSizeRange.min = "8";
71
+ fontSizeRange.max = "48";
72
+ fontSizeRange.step = "1";
73
+ fontSizeRange.value = String(settings.fontSize);
74
+ const fontSizeInput = document.createElement("input");
75
+ fontSizeInput.type = "number";
76
+ fontSizeInput.className = "cm-settings-font-size-input";
77
+ fontSizeInput.min = "1";
78
+ fontSizeInput.max = "128";
79
+ fontSizeInput.value = String(settings.fontSize);
80
+ const fontSizePx = document.createElement("span");
81
+ fontSizePx.textContent = "px";
82
+ // Use 'change' on the range (fires on mouseup) to avoid feedback loop:
83
+ // dragging changes font size → overlay relayouts → slider thumb shifts
84
+ // relative to pointer → triggers another input event → runaway resizing.
85
+ fontSizeRange.addEventListener("change", () => {
86
+ const size = Number(fontSizeRange.value);
87
+ fontSizeInput.value = String(size);
88
+ view.dispatch({ effects: updateSettingsEffect.of({ fontSize: size }) });
89
+ });
90
+ fontSizeInput.addEventListener("input", () => {
91
+ let size = Number(fontSizeInput.value);
92
+ if (isNaN(size) || size < 1)
93
+ return;
94
+ size = Math.min(128, size);
95
+ fontSizeRange.value = String(size);
96
+ view.dispatch({ effects: updateSettingsEffect.of({ fontSize: size }) });
97
+ });
98
+ fontSizeControl.appendChild(fontSizeRange);
99
+ fontSizeControl.appendChild(fontSizeInput);
100
+ fontSizeControl.appendChild(fontSizePx);
101
+ fontSizeRow.appendChild(fontSizeLabel);
102
+ fontSizeRow.appendChild(fontSizeControl);
103
+ themeSection.appendChild(fontSizeRow);
104
+ // Font family
105
+ const fontFamilyRow = document.createElement("div");
106
+ fontFamilyRow.className = "cm-settings-row";
107
+ const fontFamilyLabel = document.createElement("label");
108
+ fontFamilyLabel.textContent = "Font family";
109
+ const fontFamilySelect = document.createElement("select");
110
+ fontFamilySelect.className = "cm-settings-select";
111
+ const fontOptions = [
112
+ { label: "System default", value: "" },
113
+ { label: "UbuntuMono Nerd Font", value: '"UbuntuMono NF", monospace' },
114
+ ];
115
+ for (const opt of fontOptions) {
116
+ const option = document.createElement("option");
117
+ option.value = opt.value;
118
+ option.textContent = opt.label;
119
+ if (settings.fontFamily === opt.value)
120
+ option.selected = true;
121
+ fontFamilySelect.appendChild(option);
122
+ }
123
+ fontFamilySelect.addEventListener("change", () => {
124
+ view.dispatch({ effects: updateSettingsEffect.of({ fontFamily: fontFamilySelect.value }) });
125
+ });
126
+ fontFamilyRow.appendChild(fontFamilyLabel);
127
+ fontFamilyRow.appendChild(fontFamilySelect);
128
+ themeSection.appendChild(fontFamilyRow);
129
+ // Color theme radio — with emoji icons
130
+ const colorThemeRow = document.createElement("div");
131
+ colorThemeRow.className = "cm-settings-row";
132
+ const colorThemeLabel = document.createElement("label");
133
+ colorThemeLabel.textContent = "Color theme";
134
+ const radioGroup = document.createElement("div");
135
+ radioGroup.className = "cm-settings-radio-group";
136
+ for (const t of ['light', 'dark', 'system']) {
137
+ const radio = document.createElement("input");
138
+ radio.type = "radio";
139
+ radio.name = "cm-color-theme";
140
+ radio.value = t;
141
+ radio.id = `cm-theme-${t}`;
142
+ if (settings.theme === t)
143
+ radio.checked = true;
144
+ radio.addEventListener("change", () => {
145
+ if (radio.checked) {
146
+ view.dispatch({
147
+ effects: [
148
+ updateSettingsEffect.of({ theme: t }),
149
+ setThemeEffect.of({ dark: resolveThemeDark(t) }),
150
+ ]
151
+ });
152
+ }
153
+ });
154
+ const radioLabel = document.createElement("label");
155
+ radioLabel.htmlFor = `cm-theme-${t}`;
156
+ radioLabel.textContent = themeIcons[t];
157
+ radioLabel.title = t.charAt(0).toUpperCase() + t.slice(1);
158
+ radioGroup.appendChild(radio);
159
+ radioGroup.appendChild(radioLabel);
160
+ }
161
+ colorThemeRow.appendChild(colorThemeLabel);
162
+ colorThemeRow.appendChild(radioGroup);
163
+ themeSection.appendChild(colorThemeRow);
164
+ overlay.appendChild(themeSection);
165
+ // Editor section
166
+ const editorSection = document.createElement("div");
167
+ editorSection.className = "cm-settings-section";
168
+ const editorTitle = document.createElement("div");
169
+ editorTitle.className = "cm-settings-section-title";
170
+ editorTitle.textContent = "Editor";
171
+ editorSection.appendChild(editorTitle);
172
+ const autosaveRow = document.createElement("div");
173
+ autosaveRow.className = "cm-settings-row";
174
+ const autosaveLabel = document.createElement("label");
175
+ autosaveLabel.textContent = "Autosave";
176
+ autosaveLabel.htmlFor = "cm-autosave";
177
+ const autosaveCheckbox = document.createElement("input");
178
+ autosaveCheckbox.type = "checkbox";
179
+ autosaveCheckbox.id = "cm-autosave";
180
+ autosaveCheckbox.checked = settings.autosave;
181
+ autosaveCheckbox.addEventListener("change", () => {
182
+ view.dispatch({ effects: updateSettingsEffect.of({ autosave: autosaveCheckbox.checked }) });
183
+ });
184
+ autosaveRow.appendChild(autosaveLabel);
185
+ autosaveRow.appendChild(autosaveCheckbox);
186
+ editorSection.appendChild(autosaveRow);
187
+ // Line wrap toggle
188
+ const lineWrapRow = document.createElement("div");
189
+ lineWrapRow.className = "cm-settings-row";
190
+ const lineWrapLabel = document.createElement("label");
191
+ lineWrapLabel.textContent = "Line wrap";
192
+ lineWrapLabel.htmlFor = "cm-line-wrap";
193
+ const lineWrapCheckbox = document.createElement("input");
194
+ lineWrapCheckbox.type = "checkbox";
195
+ lineWrapCheckbox.id = "cm-line-wrap";
196
+ lineWrapCheckbox.checked = settings.lineWrap;
197
+ lineWrapCheckbox.addEventListener("change", () => {
198
+ view.dispatch({
199
+ effects: [
200
+ updateSettingsEffect.of({ lineWrap: lineWrapCheckbox.checked }),
201
+ lineWrappingCompartment.reconfigure(lineWrapCheckbox.checked ? EditorView.lineWrapping : []),
202
+ ]
203
+ });
204
+ });
205
+ lineWrapRow.appendChild(lineWrapLabel);
206
+ lineWrapRow.appendChild(lineWrapCheckbox);
207
+ editorSection.appendChild(lineWrapRow);
208
+ // LSP log toggle
209
+ const lspLogRow = document.createElement("div");
210
+ lspLogRow.className = "cm-settings-row";
211
+ const lspLogLabel = document.createElement("label");
212
+ lspLogLabel.textContent = "LSP server log";
213
+ lspLogLabel.htmlFor = "cm-lsp-log";
214
+ const lspLogCheckbox = document.createElement("input");
215
+ lspLogCheckbox.type = "checkbox";
216
+ lspLogCheckbox.id = "cm-lsp-log";
217
+ lspLogCheckbox.checked = settings.lspLogEnabled;
218
+ lspLogCheckbox.addEventListener("change", () => {
219
+ view.dispatch({ effects: updateSettingsEffect.of({ lspLogEnabled: lspLogCheckbox.checked }) });
220
+ });
221
+ lspLogRow.appendChild(lspLogLabel);
222
+ lspLogRow.appendChild(lspLogCheckbox);
223
+ editorSection.appendChild(lspLogRow);
224
+ overlay.appendChild(editorSection);
225
+ // AI Agent section
226
+ const aiSection = document.createElement("div");
227
+ aiSection.className = "cm-settings-section";
228
+ const aiTitle = document.createElement("div");
229
+ aiTitle.className = "cm-settings-section-title";
230
+ aiTitle.textContent = "AI Agent";
231
+ aiSection.appendChild(aiTitle);
232
+ const agentRow = document.createElement("div");
233
+ agentRow.className = "cm-settings-row";
234
+ const agentLabel = document.createElement("label");
235
+ agentLabel.textContent = "Agent URL";
236
+ const agentInput = document.createElement("input");
237
+ agentInput.type = "text";
238
+ agentInput.className = "cm-settings-input";
239
+ agentInput.placeholder = "OpenAPI-compatible endpoint";
240
+ agentInput.value = settings.agentUrl;
241
+ agentInput.addEventListener("change", () => {
242
+ view.dispatch({ effects: updateSettingsEffect.of({ agentUrl: agentInput.value }) });
243
+ });
244
+ agentRow.appendChild(agentLabel);
245
+ agentRow.appendChild(agentInput);
246
+ aiSection.appendChild(agentRow);
247
+ // TODO: integrate via @marimo-team/codemirror-ai
248
+ overlay.appendChild(aiSection);
249
+ // Terminal section
250
+ const termSection = document.createElement("div");
251
+ termSection.className = "cm-settings-section";
252
+ const termTitle = document.createElement("div");
253
+ termTitle.className = "cm-settings-section-title";
254
+ termTitle.textContent = "Terminal";
255
+ termSection.appendChild(termTitle);
256
+ const termRow = document.createElement("div");
257
+ termRow.className = "cm-settings-row";
258
+ const termBtn = document.createElement("button");
259
+ termBtn.className = "cm-settings-button cm-settings-button-disabled";
260
+ termBtn.textContent = "Terminal (coming soon)";
261
+ termBtn.disabled = true;
262
+ termRow.appendChild(termBtn);
263
+ termSection.appendChild(termRow);
264
+ // TODO: ghostty-web + wanix integration
265
+ overlay.appendChild(termSection);
266
+ return overlay;
267
+ }
@@ -0,0 +1,3 @@
1
+ import { EditorView } from "@codemirror/view";
2
+ /** Open (or toggle) the terminal panel on the given editor view. */
3
+ export declare function openTerminal(view: EditorView): Promise<void>;
@@ -0,0 +1,76 @@
1
+ import { showPanel } from "@codemirror/view";
2
+ import { terminalCompartment } from "../editor";
3
+ import { settingsField, resolveThemeDark } from "./settings";
4
+ let ghosttyModule = null;
5
+ async function ensureGhostty() {
6
+ if (ghosttyModule)
7
+ return ghosttyModule;
8
+ // Dynamic import — ghostty-web is loaded only when the terminal is opened.
9
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
10
+ const mod = await import('ghostty-web');
11
+ await mod.init();
12
+ ghosttyModule = mod;
13
+ return mod;
14
+ }
15
+ const CLOSE_ICON = '\uf00d'; // nf-fa-close
16
+ function createTerminalPanel(view) {
17
+ const dom = document.createElement("div");
18
+ dom.className = "cm-terminal-panel";
19
+ // Header bar with close button
20
+ const header = document.createElement("div");
21
+ header.className = "cm-terminal-header";
22
+ const title = document.createElement("span");
23
+ title.className = "cm-terminal-title";
24
+ title.textContent = "Terminal";
25
+ header.appendChild(title);
26
+ const closeBtn = document.createElement("button");
27
+ closeBtn.className = "cm-terminal-close";
28
+ closeBtn.style.fontFamily = 'var(--cm-icon-font-family)';
29
+ closeBtn.textContent = CLOSE_ICON;
30
+ closeBtn.title = "Close terminal";
31
+ closeBtn.addEventListener("click", () => {
32
+ view.dispatch({
33
+ effects: terminalCompartment.reconfigure([]),
34
+ });
35
+ });
36
+ header.appendChild(closeBtn);
37
+ dom.appendChild(header);
38
+ // Terminal container
39
+ const container = document.createElement("div");
40
+ container.className = "cm-terminal-container";
41
+ dom.appendChild(container);
42
+ let terminal = null;
43
+ // Lazy-load ghostty-web and mount
44
+ ensureGhostty().then(({ Terminal }) => {
45
+ if (!dom.isConnected)
46
+ return;
47
+ const settings = view.state.field(settingsField);
48
+ const dark = resolveThemeDark(settings.theme);
49
+ terminal = new Terminal({
50
+ fontSize: settings.fontSize,
51
+ theme: dark
52
+ ? { background: '#1e1e1e', foreground: '#d4d4d4' }
53
+ : { background: '#ffffff', foreground: '#1e1e1e' },
54
+ });
55
+ terminal.open(container);
56
+ terminal.write('\x1b[1;32m$\x1b[0m Terminal ready (no backend connected)\r\n');
57
+ }).catch((err) => {
58
+ container.textContent = `Failed to load terminal: ${err.message}`;
59
+ container.style.padding = '8px';
60
+ container.style.color = '#f44';
61
+ });
62
+ return {
63
+ dom,
64
+ top: false,
65
+ destroy() {
66
+ terminal?.dispose?.();
67
+ terminal = null;
68
+ },
69
+ };
70
+ }
71
+ /** Open (or toggle) the terminal panel on the given editor view. */
72
+ export async function openTerminal(view) {
73
+ view.dispatch({
74
+ effects: terminalCompartment.reconfigure(showPanel.of(createTerminalPanel)),
75
+ });
76
+ }
@@ -3,18 +3,68 @@ import { StateField } from "@codemirror/state";
3
3
  import { HighlightedSearch } from "../utils/search";
4
4
  export interface CommandResult {
5
5
  id: string;
6
- type: 'create-file' | 'rename-file';
6
+ type: 'create-file' | 'save-as' | 'rename-file' | 'import-local-files' | 'import-local-folder' | 'open-file' | 'settings' | 'open-terminal' | 'file-action';
7
7
  icon: string;
8
+ iconColor?: string;
8
9
  query: string;
9
10
  requiresInput?: boolean;
11
+ /** For type 'file-action': callback executed when the command is selected. */
12
+ action?: (view: EditorView) => void;
10
13
  }
11
- export type SearchResult = HighlightedSearch | CommandResult;
14
+ export interface FileActionEntry {
15
+ /** File extensions this command applies to (e.g. ['svg']) */
16
+ extensions: string[];
17
+ /** Display label */
18
+ label: string;
19
+ /** Nerd Font icon glyph */
20
+ icon: string;
21
+ /** Callback when selected */
22
+ action: (view: EditorView) => void;
23
+ }
24
+ /** Register a command that appears when files with matching extensions are open. */
25
+ export declare function registerFileAction(entry: FileActionEntry): void;
26
+ export interface SettingsEntry {
27
+ id: string;
28
+ settingKey: string;
29
+ type: 'settings-toggle' | 'settings-cycle' | 'settings-input';
30
+ icon: string;
31
+ currentValue: string;
32
+ }
33
+ export interface BrowseEntry {
34
+ id: string;
35
+ type: 'browse-directory' | 'browse-file' | 'browse-parent';
36
+ icon: string;
37
+ iconColor?: string;
38
+ fullPath: string;
39
+ }
40
+ export type SearchResult = HighlightedSearch | CommandResult | BrowseEntry | SettingsEntry;
12
41
  export interface NamingMode {
13
42
  active: boolean;
14
- type: 'create-file' | 'rename-file';
43
+ type: 'create-file' | 'save-as' | 'rename-file';
15
44
  originalQuery: string;
16
45
  languageExtension?: string;
17
46
  }
47
+ export interface BrowseMode {
48
+ active: boolean;
49
+ currentPath: string;
50
+ filter: string;
51
+ }
52
+ export interface SettingsMode {
53
+ active: boolean;
54
+ filter: string;
55
+ editing: string | null;
56
+ }
57
+ export interface DeleteMode {
58
+ active: boolean;
59
+ filePath: string;
60
+ }
61
+ export interface OverwriteMode {
62
+ active: boolean;
63
+ filePath: string;
64
+ action: 'save-as' | 'create-file' | 'rename';
65
+ /** For rename: the old path to delete after overwrite */
66
+ oldPath?: string;
67
+ }
18
68
  export declare const setSearchResults: import("@codemirror/state").StateEffectType<SearchResult[]>;
19
69
  export declare const searchResultsField: StateField<SearchResult[]>;
20
70
  export declare const toolbarPanel: (view: EditorView) => Panel;