@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.
- package/dist/editor.d.ts +30 -3
- package/dist/editor.js +416 -43
- package/dist/index.d.ts +5 -1
- package/dist/index.js +5 -1
- package/dist/lsps/index.d.ts +5 -0
- package/dist/lsps/index.js +9 -2
- package/dist/lsps/typescript.d.ts +3 -1
- package/dist/lsps/typescript.js +8 -17
- package/dist/panels/settings.d.ts +22 -0
- package/dist/panels/settings.js +267 -0
- package/dist/panels/terminal.d.ts +3 -0
- package/dist/panels/terminal.js +76 -0
- package/dist/panels/toolbar.d.ts +53 -3
- package/dist/panels/toolbar.js +1336 -164
- package/dist/panels/toolbar.test.js +20 -14
- package/dist/rpc/transport.d.ts +2 -11
- package/dist/rpc/transport.js +19 -35
- package/dist/themes/index.js +226 -13
- package/dist/themes/vscode.js +3 -2
- package/dist/types.d.ts +5 -0
- package/dist/utils/fs.d.ts +22 -3
- package/dist/utils/fs.js +126 -21
- package/dist/utils/lsp.d.ts +26 -15
- package/dist/utils/lsp.js +79 -44
- package/dist/utils/search.d.ts +2 -0
- package/dist/utils/search.js +13 -4
- package/dist/utils/typescript-defaults.d.ts +57 -0
- package/dist/utils/typescript-defaults.js +208 -0
- package/dist/utils/typescript-defaults.test.d.ts +1 -0
- package/dist/utils/typescript-defaults.test.js +197 -0
- package/dist/workers/fs.worker.d.ts +4 -8
- package/dist/workers/fs.worker.js +30 -60
- package/dist/workers/javascript.worker.js +11 -9
- package/package.json +8 -4
- package/dist/assets/clike-C8IJ2oj_.js +0 -1
- package/dist/assets/cmake-BQqOBYOt.js +0 -1
- package/dist/assets/dockerfile-C_y-rIpk.js +0 -1
- package/dist/assets/fs.worker-BwEqZcql.ts +0 -109
- package/dist/assets/go-CTD25R5P.js +0 -1
- package/dist/assets/haskell-BWDZoCOh.js +0 -1
- package/dist/assets/index-9HdhmM_Y.js +0 -1
- package/dist/assets/index-C-QhPFHP.js +0 -3
- package/dist/assets/index-C3BnE2cG.js +0 -222
- package/dist/assets/index-CGx5MZO7.js +0 -6
- package/dist/assets/index-CIuq3uTk.js +0 -1
- package/dist/assets/index-CXFONXS8.js +0 -1
- package/dist/assets/index-D5Z27j1C.js +0 -1
- package/dist/assets/index-DWOBdRjn.js +0 -1
- package/dist/assets/index-Dvu-FFzd.js +0 -1
- package/dist/assets/index-Dx_VuNNd.js +0 -1
- package/dist/assets/index-I0dlv-r3.js +0 -1
- package/dist/assets/index-MGle_v2x.js +0 -1
- package/dist/assets/index-N-GE7HTU.js +0 -1
- package/dist/assets/index-aEsF5o-7.js +0 -2
- package/dist/assets/index-as7ELo0J.js +0 -1
- package/dist/assets/index-gUUzXNuP.js +0 -1
- package/dist/assets/index-pGm0qkrJ.js +0 -13
- package/dist/assets/javascript.worker-C1zGArKk.js +0 -527
- package/dist/assets/lua-BgMRiT3U.js +0 -1
- package/dist/assets/perl-CdXCOZ3F.js +0 -1
- package/dist/assets/process-Dw9K5EnD.js +0 -1357
- package/dist/assets/properties-C78fOPTZ.js +0 -1
- package/dist/assets/ruby-B2Rjki9n.js +0 -1
- package/dist/assets/shell-CjFT_Tl9.js +0 -1
- package/dist/assets/swift-BzpIVaGY.js +0 -1
- package/dist/assets/toml-BXUEaScT.js +0 -1
- package/dist/assets/vb-CmGdzxic.js +0 -1
- package/dist/e2e/example.spec.d.ts +0 -5
- package/dist/e2e/example.spec.js +0 -44
- package/dist/index.html +0 -16
- package/dist/resources/config.json +0 -13
- package/dist/snapshot.bin +0 -0
- package/dist/styles.css +0 -7
package/dist/lsps/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/lsps/index.js
CHANGED
|
@@ -30,8 +30,10 @@ const languageSupportMap = {
|
|
|
30
30
|
return less();
|
|
31
31
|
},
|
|
32
32
|
json: async () => {
|
|
33
|
-
|
|
34
|
-
|
|
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>[];
|
package/dist/lsps/typescript.js
CHANGED
|
@@ -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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
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
|
-
|
|
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,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
|
+
}
|
package/dist/panels/toolbar.d.ts
CHANGED
|
@@ -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
|
|
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;
|