@eclipse-docks/extension-terminal 0.0.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/dist/api.d.ts +8 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +2 -0
- package/dist/commands-backend.d.ts +24 -0
- package/dist/commands-backend.d.ts.map +1 -0
- package/dist/i18n.de-BVmQFS_U.js +35 -0
- package/dist/i18n.de-BVmQFS_U.js.map +1 -0
- package/dist/i18n.en-BzXd1VkS.js +35 -0
- package/dist/i18n.en-BzXd1VkS.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/js-terminal-backend-BcQIBeMe.js +741 -0
- package/dist/js-terminal-backend-BcQIBeMe.js.map +1 -0
- package/dist/js-terminal-backend.d.ts +21 -0
- package/dist/js-terminal-backend.d.ts.map +1 -0
- package/dist/line-input.d.ts +24 -0
- package/dist/line-input.d.ts.map +1 -0
- package/dist/shell-parser.d.ts +11 -0
- package/dist/shell-parser.d.ts.map +1 -0
- package/dist/terminal-api.d.ts +76 -0
- package/dist/terminal-api.d.ts.map +1 -0
- package/dist/terminal-extension-U1ZIJJzf.js +439 -0
- package/dist/terminal-extension-U1ZIJJzf.js.map +1 -0
- package/dist/terminal-extension.d.ts +2 -0
- package/dist/terminal-extension.d.ts.map +1 -0
- package/dist/terminal-panel.d.ts +20 -0
- package/dist/terminal-panel.d.ts.map +1 -0
- package/dist/terminal-persistence.d.ts +5 -0
- package/dist/terminal-persistence.d.ts.map +1 -0
- package/dist/terminal-service.d.ts +49 -0
- package/dist/terminal-service.d.ts.map +1 -0
- package/dist/terminal-theme.d.ts +6 -0
- package/dist/terminal-theme.d.ts.map +1 -0
- package/dist/terminal-view.d.ts +40 -0
- package/dist/terminal-view.d.ts.map +1 -0
- package/package.json +40 -0
|
@@ -0,0 +1,741 @@
|
|
|
1
|
+
import { PANEL_BOTTOM, commandRegistry, confirmDialog, contributionRegistry, createJsRuntime, i18n, publish, rootContext, subscribe, unsubscribe } from "@eclipse-docks/core";
|
|
2
|
+
import { html } from "@eclipse-docks/core/externals/lit";
|
|
3
|
+
//#region src/terminal-api.ts
|
|
4
|
+
var TARGET_TERMINAL_PROFILES = "system.terminal.profiles";
|
|
5
|
+
var VIEW_TERMINAL = "view.terminal";
|
|
6
|
+
var TERMINAL_INSTANCES_TABS_ID = "terminal-instances";
|
|
7
|
+
/** Dropdown target: contributors register items that create a terminal with a profile. */
|
|
8
|
+
var TERMINAL_NEW_DROPDOWN = `${TERMINAL_INSTANCES_TABS_ID}-new`;
|
|
9
|
+
var TERMINAL_PANEL_SETTINGS_KEY = "docks-terminal-panel";
|
|
10
|
+
var TOPIC_TERMINAL_OPENED = "events/terminal/opened";
|
|
11
|
+
var TOPIC_TERMINAL_CLOSED = "events/terminal/closed";
|
|
12
|
+
var TOPIC_TERMINAL_ACTIVE_CHANGED = "events/terminal/activeChanged";
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/terminal-persistence.ts
|
|
15
|
+
var MAX_PERSISTED_ENV_KEYS = 32;
|
|
16
|
+
function serializeTerminalState(state) {
|
|
17
|
+
return {
|
|
18
|
+
activeTerminalId: state.activeTerminalId,
|
|
19
|
+
preferredProfileId: state.preferredProfileId,
|
|
20
|
+
terminals: state.terminals.map(serializeTerminalEntry)
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function serializeTerminalEntry(entry) {
|
|
24
|
+
const opts = entry.creationOptions;
|
|
25
|
+
const env = opts.env ? Object.fromEntries(Object.entries(opts.env).slice(0, MAX_PERSISTED_ENV_KEYS)) : void 0;
|
|
26
|
+
return {
|
|
27
|
+
id: entry.id,
|
|
28
|
+
name: entry.name,
|
|
29
|
+
profileId: entry.profileId,
|
|
30
|
+
groupId: entry.groupId,
|
|
31
|
+
creationOptions: {
|
|
32
|
+
name: opts.name,
|
|
33
|
+
profileId: opts.profileId,
|
|
34
|
+
cwd: opts.cwd,
|
|
35
|
+
env,
|
|
36
|
+
groupId: opts.groupId
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function parseTerminalState(raw) {
|
|
41
|
+
if (!raw || typeof raw !== "object") return null;
|
|
42
|
+
const obj = raw;
|
|
43
|
+
if (!Array.isArray(obj.terminals)) return null;
|
|
44
|
+
const terminals = [];
|
|
45
|
+
for (const item of obj.terminals) {
|
|
46
|
+
if (!item || typeof item !== "object") continue;
|
|
47
|
+
const t = item;
|
|
48
|
+
if (typeof t.id !== "string" || typeof t.name !== "string" || typeof t.profileId !== "string") continue;
|
|
49
|
+
const creationOptions = t.creationOptions ?? {};
|
|
50
|
+
terminals.push({
|
|
51
|
+
id: t.id,
|
|
52
|
+
name: t.name,
|
|
53
|
+
profileId: t.profileId,
|
|
54
|
+
groupId: typeof t.groupId === "string" ? t.groupId : t.id,
|
|
55
|
+
creationOptions
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
terminals,
|
|
60
|
+
activeTerminalId: typeof obj.activeTerminalId === "string" ? obj.activeTerminalId : null,
|
|
61
|
+
preferredProfileId: typeof obj.preferredProfileId === "string" ? obj.preferredProfileId : null
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function emptyTerminalState() {
|
|
65
|
+
return {
|
|
66
|
+
terminals: [],
|
|
67
|
+
activeTerminalId: null,
|
|
68
|
+
preferredProfileId: null
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region src/terminal-service.ts
|
|
73
|
+
var t = await i18n(/* @__PURE__ */ Object.assign({
|
|
74
|
+
"./i18n.de.json": () => import("./i18n.de-BVmQFS_U.js"),
|
|
75
|
+
"./i18n.en.json": () => import("./i18n.en-BzXd1VkS.js")
|
|
76
|
+
}), true);
|
|
77
|
+
async function confirmKillTerminal(name) {
|
|
78
|
+
return confirmDialog(t.TERMINAL_KILL_CONFIRM({ name }));
|
|
79
|
+
}
|
|
80
|
+
var terminalCounter = 0;
|
|
81
|
+
function nextTerminalId() {
|
|
82
|
+
terminalCounter += 1;
|
|
83
|
+
return `terminal-${terminalCounter}`;
|
|
84
|
+
}
|
|
85
|
+
var TerminalInstance = class {
|
|
86
|
+
constructor(id, name, profileId, creationOptions, groupId) {
|
|
87
|
+
this.hidden = false;
|
|
88
|
+
this.backend = null;
|
|
89
|
+
this.id = id;
|
|
90
|
+
this.name = name;
|
|
91
|
+
this.profileId = profileId;
|
|
92
|
+
this.creationOptions = creationOptions;
|
|
93
|
+
this.groupId = groupId;
|
|
94
|
+
}
|
|
95
|
+
sendText(text, addNewLine = true) {
|
|
96
|
+
if (!this.backend) return;
|
|
97
|
+
this.backend.handleInput(text + (addNewLine ? "\r" : ""));
|
|
98
|
+
}
|
|
99
|
+
show(_preserveFocus) {
|
|
100
|
+
terminalService.focusTerminalPanel();
|
|
101
|
+
terminalService.activateTerminal(this.id);
|
|
102
|
+
this.hidden = false;
|
|
103
|
+
}
|
|
104
|
+
hide() {
|
|
105
|
+
this.hidden = true;
|
|
106
|
+
}
|
|
107
|
+
async dispose() {
|
|
108
|
+
await terminalService.disposeTerminal(this.id);
|
|
109
|
+
}
|
|
110
|
+
clear() {
|
|
111
|
+
this.clearScreen?.();
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
var TerminalService = class {
|
|
115
|
+
constructor() {
|
|
116
|
+
this.terminals = /* @__PURE__ */ new Map();
|
|
117
|
+
this.activeTerminalId = null;
|
|
118
|
+
this.preferredProfileId = null;
|
|
119
|
+
this.persistenceHandler = null;
|
|
120
|
+
this.persistCallback = null;
|
|
121
|
+
this.restoreStarted = false;
|
|
122
|
+
this.instanceTabsHost = null;
|
|
123
|
+
}
|
|
124
|
+
get activeTerminal() {
|
|
125
|
+
if (!this.activeTerminalId) return void 0;
|
|
126
|
+
return this.terminals.get(this.activeTerminalId);
|
|
127
|
+
}
|
|
128
|
+
getTerminals() {
|
|
129
|
+
return [...this.terminals.values()];
|
|
130
|
+
}
|
|
131
|
+
onDidOpenTerminal(callback) {
|
|
132
|
+
const token = subscribe(TOPIC_TERMINAL_OPENED, callback);
|
|
133
|
+
return () => unsubscribe(token);
|
|
134
|
+
}
|
|
135
|
+
onDidCloseTerminal(callback) {
|
|
136
|
+
const token = subscribe(TOPIC_TERMINAL_CLOSED, callback);
|
|
137
|
+
return () => unsubscribe(token);
|
|
138
|
+
}
|
|
139
|
+
onDidChangeActiveTerminal(callback) {
|
|
140
|
+
const token = subscribe(TOPIC_TERMINAL_ACTIVE_CHANGED, callback);
|
|
141
|
+
return () => unsubscribe(token);
|
|
142
|
+
}
|
|
143
|
+
setPersistenceHandlers(load, save) {
|
|
144
|
+
this.persistenceHandler = load;
|
|
145
|
+
this.persistCallback = save;
|
|
146
|
+
}
|
|
147
|
+
async restorePersistedTerminals() {
|
|
148
|
+
if (this.restoreStarted || !this.persistenceHandler) return;
|
|
149
|
+
this.restoreStarted = true;
|
|
150
|
+
const raw = await this.persistenceHandler();
|
|
151
|
+
const state = raw ? parseTerminalState(raw) : null;
|
|
152
|
+
if (!state || state.terminals.length === 0) return;
|
|
153
|
+
this.preferredProfileId = state.preferredProfileId;
|
|
154
|
+
for (const entry of state.terminals) await this.createTerminal({
|
|
155
|
+
...entry.creationOptions,
|
|
156
|
+
name: entry.name,
|
|
157
|
+
profileId: entry.profileId,
|
|
158
|
+
groupId: entry.groupId
|
|
159
|
+
}, entry.id);
|
|
160
|
+
if (state.activeTerminalId && this.terminals.has(state.activeTerminalId)) this.setActiveTerminal(state.activeTerminalId);
|
|
161
|
+
}
|
|
162
|
+
getProfiles() {
|
|
163
|
+
return contributionRegistry.getContributions(TARGET_TERMINAL_PROFILES);
|
|
164
|
+
}
|
|
165
|
+
resolveDefaultProfileId() {
|
|
166
|
+
if (this.preferredProfileId) {
|
|
167
|
+
const preferred = this.getProfiles().find((p) => p.id === this.preferredProfileId);
|
|
168
|
+
if (preferred) return preferred.id;
|
|
169
|
+
}
|
|
170
|
+
const profiles = this.getProfiles().filter((p) => !p.hidden);
|
|
171
|
+
return (profiles.find((p) => p.isDefault) ?? profiles[0])?.id ?? null;
|
|
172
|
+
}
|
|
173
|
+
setInstanceTabsHost(host) {
|
|
174
|
+
this.instanceTabsHost = host;
|
|
175
|
+
}
|
|
176
|
+
getInstanceTabs() {
|
|
177
|
+
return this.instanceTabsHost;
|
|
178
|
+
}
|
|
179
|
+
focusTerminalPanel() {
|
|
180
|
+
document.querySelector(`docks-tabs#${PANEL_BOTTOM}`)?.activate(VIEW_TERMINAL);
|
|
181
|
+
}
|
|
182
|
+
activateTerminal(id) {
|
|
183
|
+
const tabs = this.getInstanceTabs();
|
|
184
|
+
if (tabs?.has(id)) tabs.activate(id);
|
|
185
|
+
this.setActiveTerminal(id);
|
|
186
|
+
}
|
|
187
|
+
setActiveTerminal(id) {
|
|
188
|
+
if (this.activeTerminalId === id) return;
|
|
189
|
+
this.activeTerminalId = id;
|
|
190
|
+
publish(TOPIC_TERMINAL_ACTIVE_CHANGED, id ? this.terminals.get(id) : void 0);
|
|
191
|
+
this.persistState();
|
|
192
|
+
}
|
|
193
|
+
async createTerminal(options = {}, forcedId) {
|
|
194
|
+
const profileId = options.profileId ?? this.resolveDefaultProfileId();
|
|
195
|
+
if (!profileId) return void 0;
|
|
196
|
+
const profile = this.getProfiles().find((p) => p.id === profileId);
|
|
197
|
+
if (!profile) return void 0;
|
|
198
|
+
const id = forcedId ?? nextTerminalId();
|
|
199
|
+
if (forcedId) syncTerminalCounterFromId(forcedId);
|
|
200
|
+
const groupId = options.groupId ?? id;
|
|
201
|
+
const name = options.name ?? profile.label;
|
|
202
|
+
let backend;
|
|
203
|
+
try {
|
|
204
|
+
backend = await profile.create({
|
|
205
|
+
...options,
|
|
206
|
+
profileId,
|
|
207
|
+
name,
|
|
208
|
+
groupId
|
|
209
|
+
});
|
|
210
|
+
} catch {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const terminal = new TerminalInstance(id, name, profileId, {
|
|
214
|
+
...options,
|
|
215
|
+
profileId,
|
|
216
|
+
name,
|
|
217
|
+
groupId
|
|
218
|
+
}, groupId);
|
|
219
|
+
terminal.backend = backend;
|
|
220
|
+
terminal.hidden = options.hidden ?? false;
|
|
221
|
+
this.terminals.set(id, terminal);
|
|
222
|
+
this.openTerminalTab(terminal, profile.icon);
|
|
223
|
+
if (!options.hidden) {
|
|
224
|
+
this.focusTerminalPanel();
|
|
225
|
+
this.activateTerminal(id);
|
|
226
|
+
}
|
|
227
|
+
publish(TOPIC_TERMINAL_OPENED, terminal);
|
|
228
|
+
this.persistState();
|
|
229
|
+
return terminal;
|
|
230
|
+
}
|
|
231
|
+
openTerminalTab(terminal, icon) {
|
|
232
|
+
const tabs = this.getInstanceTabs();
|
|
233
|
+
if (!tabs) return;
|
|
234
|
+
const contribution = {
|
|
235
|
+
name: terminal.id,
|
|
236
|
+
label: terminal.name,
|
|
237
|
+
icon: icon ?? "terminal",
|
|
238
|
+
closable: true,
|
|
239
|
+
toolbar: false,
|
|
240
|
+
beforeClose: async () => confirmKillTerminal(terminal.name),
|
|
241
|
+
component: (tabId) => html`<docks-terminal-view id="${tabId}" .terminal=${terminal}></docks-terminal-view>`
|
|
242
|
+
};
|
|
243
|
+
if (tabs.has(terminal.id)) {
|
|
244
|
+
tabs.activate(terminal.id);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
tabs.open(contribution);
|
|
248
|
+
}
|
|
249
|
+
async disposeTerminal(id, options) {
|
|
250
|
+
const terminal = this.terminals.get(id);
|
|
251
|
+
if (!terminal) return;
|
|
252
|
+
if (!options?.skipTabRemoval) {
|
|
253
|
+
const tabs = this.getInstanceTabs();
|
|
254
|
+
if (tabs?.has(id)) {
|
|
255
|
+
await tabs.closeTab(new Event("close"), id);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
await terminal.backend?.close?.();
|
|
260
|
+
terminal.backend = null;
|
|
261
|
+
this.terminals.delete(id);
|
|
262
|
+
if (this.activeTerminalId === id) {
|
|
263
|
+
const remaining = [...this.terminals.keys()];
|
|
264
|
+
this.setActiveTerminal(remaining.length > 0 ? remaining[remaining.length - 1] : null);
|
|
265
|
+
}
|
|
266
|
+
publish(TOPIC_TERMINAL_CLOSED, terminal);
|
|
267
|
+
this.persistState();
|
|
268
|
+
}
|
|
269
|
+
async killAll() {
|
|
270
|
+
const tabs = this.getInstanceTabs();
|
|
271
|
+
const ids = [...this.terminals.keys()];
|
|
272
|
+
for (const id of ids) if (tabs?.has(id)) await tabs.closeTab(new Event("close"), id);
|
|
273
|
+
else await this.disposeTerminal(id, { skipTabRemoval: true });
|
|
274
|
+
}
|
|
275
|
+
renameActiveTerminal(name) {
|
|
276
|
+
const active = this.activeTerminal;
|
|
277
|
+
if (!active || !(active instanceof TerminalInstance)) return;
|
|
278
|
+
active.name = name;
|
|
279
|
+
this.getInstanceTabs()?.updateTabLabel(active.id, name);
|
|
280
|
+
this.persistState();
|
|
281
|
+
}
|
|
282
|
+
setPreferredProfile(profileId) {
|
|
283
|
+
this.preferredProfileId = profileId;
|
|
284
|
+
this.persistState();
|
|
285
|
+
}
|
|
286
|
+
clearActiveTerminal() {
|
|
287
|
+
this.activeTerminal?.clear?.();
|
|
288
|
+
}
|
|
289
|
+
async runTask(presentation, execute) {
|
|
290
|
+
let terminal = this.getTerminals().find((t) => t.profileId === presentation.profileId);
|
|
291
|
+
if (!terminal) terminal = await this.createTerminal({ profileId: presentation.profileId });
|
|
292
|
+
if (!terminal) return;
|
|
293
|
+
if (presentation.reveal !== "never") if (presentation.focus !== false) terminal.show();
|
|
294
|
+
else this.focusTerminalPanel();
|
|
295
|
+
if (presentation.clear) terminal.clear?.();
|
|
296
|
+
await execute(terminal);
|
|
297
|
+
}
|
|
298
|
+
async persistState() {
|
|
299
|
+
if (!this.persistCallback) return;
|
|
300
|
+
const state = {
|
|
301
|
+
activeTerminalId: this.activeTerminalId,
|
|
302
|
+
preferredProfileId: this.preferredProfileId,
|
|
303
|
+
terminals: [...this.terminals.values()].map((t) => ({
|
|
304
|
+
id: t.id,
|
|
305
|
+
name: t.name,
|
|
306
|
+
profileId: t.profileId,
|
|
307
|
+
groupId: t.groupId,
|
|
308
|
+
creationOptions: t.creationOptions
|
|
309
|
+
}))
|
|
310
|
+
};
|
|
311
|
+
await this.persistCallback(serializeTerminalState(state));
|
|
312
|
+
}
|
|
313
|
+
handleTabActivated(tabName) {
|
|
314
|
+
if (this.terminals.has(tabName)) this.setActiveTerminal(tabName);
|
|
315
|
+
}
|
|
316
|
+
handleTabClosed(tabName) {
|
|
317
|
+
if (this.terminals.has(tabName)) this.disposeTerminal(tabName, { skipTabRemoval: true });
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
function syncTerminalCounterFromId(id) {
|
|
321
|
+
const match = /^terminal-(\d+)$/.exec(id);
|
|
322
|
+
if (!match) return;
|
|
323
|
+
const n = Number.parseInt(match[1], 10);
|
|
324
|
+
if (n > terminalCounter) terminalCounter = n;
|
|
325
|
+
}
|
|
326
|
+
var terminalService = new TerminalService();
|
|
327
|
+
rootContext.put("terminalService", terminalService);
|
|
328
|
+
function registerTerminalProfile(contribution) {
|
|
329
|
+
contributionRegistry.registerContribution(TARGET_TERMINAL_PROFILES, contribution);
|
|
330
|
+
if (contribution.hidden) return;
|
|
331
|
+
const profileId = contribution.id;
|
|
332
|
+
contributionRegistry.registerContribution(TERMINAL_NEW_DROPDOWN, {
|
|
333
|
+
label: contribution.label,
|
|
334
|
+
icon: contribution.icon ?? "terminal",
|
|
335
|
+
component: () => html`
|
|
336
|
+
<docks-command
|
|
337
|
+
icon="${contribution.icon ?? "terminal"}"
|
|
338
|
+
title="${contribution.label}"
|
|
339
|
+
.action=${() => {
|
|
340
|
+
terminalService.setPreferredProfile(profileId);
|
|
341
|
+
terminalService.createTerminal({ profileId });
|
|
342
|
+
}}
|
|
343
|
+
>
|
|
344
|
+
${contribution.label}
|
|
345
|
+
</docks-command>
|
|
346
|
+
`
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
//#endregion
|
|
350
|
+
//#region src/shell-parser.ts
|
|
351
|
+
var NAMED_PARAM_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*=.*$/;
|
|
352
|
+
function tokenize(segment) {
|
|
353
|
+
const tokens = [];
|
|
354
|
+
let i = 0;
|
|
355
|
+
const len = segment.length;
|
|
356
|
+
while (i < len) {
|
|
357
|
+
while (i < len && /\s/.test(segment[i])) i++;
|
|
358
|
+
if (i >= len) break;
|
|
359
|
+
if (segment[i] === "\"") {
|
|
360
|
+
let end = i + 1;
|
|
361
|
+
while (end < len && segment[end] !== "\"") {
|
|
362
|
+
if (segment[end] === "\\") end++;
|
|
363
|
+
end++;
|
|
364
|
+
}
|
|
365
|
+
tokens.push(segment.slice(i + 1, end).replace(/\\"/g, "\""));
|
|
366
|
+
i = end + 1;
|
|
367
|
+
} else {
|
|
368
|
+
let start = i;
|
|
369
|
+
while (i < len && !/\s/.test(segment[i]) && segment[i] !== "\"") i++;
|
|
370
|
+
if (i > start) tokens.push(segment.slice(start, i));
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return tokens;
|
|
374
|
+
}
|
|
375
|
+
function splitChain(line) {
|
|
376
|
+
const result = [];
|
|
377
|
+
const regex = /(\s*&&\s*|\s*\|\s*)/g;
|
|
378
|
+
let lastIndex = 0;
|
|
379
|
+
let match;
|
|
380
|
+
while ((match = regex.exec(line)) !== null) {
|
|
381
|
+
const segment = line.slice(lastIndex, match.index).trim();
|
|
382
|
+
if (segment) {
|
|
383
|
+
const op = match[0].includes("|") ? "|" : "&&";
|
|
384
|
+
result.push({
|
|
385
|
+
segment,
|
|
386
|
+
operator: op
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
lastIndex = regex.lastIndex;
|
|
390
|
+
}
|
|
391
|
+
const tail = line.slice(lastIndex).trim();
|
|
392
|
+
if (tail) result.push({
|
|
393
|
+
segment: tail,
|
|
394
|
+
operator: null
|
|
395
|
+
});
|
|
396
|
+
return result;
|
|
397
|
+
}
|
|
398
|
+
function resolveParams(tokens, command) {
|
|
399
|
+
const params = {};
|
|
400
|
+
const positionals = [];
|
|
401
|
+
const paramNames = command?.parameters?.map((p) => p.name) ?? [];
|
|
402
|
+
for (const token of tokens) if (NAMED_PARAM_REGEX.test(token)) {
|
|
403
|
+
const eqIdx = token.indexOf("=");
|
|
404
|
+
const name = token.slice(0, eqIdx);
|
|
405
|
+
const value = token.slice(eqIdx + 1);
|
|
406
|
+
params[name] = value === "true" || value === "false" ? value === "true" : value;
|
|
407
|
+
} else positionals.push(token);
|
|
408
|
+
paramNames.forEach((name, i) => {
|
|
409
|
+
if (positionals[i] !== void 0 && !(name in params)) params[name] = positionals[i];
|
|
410
|
+
});
|
|
411
|
+
return params;
|
|
412
|
+
}
|
|
413
|
+
function parseShellLine(line, commandRegistry) {
|
|
414
|
+
return splitChain(line).map(({ segment, operator }) => {
|
|
415
|
+
const tokens = tokenize(segment);
|
|
416
|
+
if (tokens.length === 0) return {
|
|
417
|
+
command: {
|
|
418
|
+
commandId: "",
|
|
419
|
+
params: {}
|
|
420
|
+
},
|
|
421
|
+
operator
|
|
422
|
+
};
|
|
423
|
+
const commandId = tokens[0];
|
|
424
|
+
const command = commandRegistry.hasCommand(commandId) ? commandRegistry.getCommand(commandId) : void 0;
|
|
425
|
+
return {
|
|
426
|
+
command: {
|
|
427
|
+
commandId,
|
|
428
|
+
params: resolveParams(tokens.slice(1), command)
|
|
429
|
+
},
|
|
430
|
+
operator
|
|
431
|
+
};
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
//#endregion
|
|
435
|
+
//#region src/line-input.ts
|
|
436
|
+
/** Minimal readline-style editor for xterm-backed REPL backends. */
|
|
437
|
+
var LineInput = class {
|
|
438
|
+
constructor(write) {
|
|
439
|
+
this.write = write;
|
|
440
|
+
this.buffer = "";
|
|
441
|
+
this.cursor = 0;
|
|
442
|
+
this.history = [];
|
|
443
|
+
this.historyDraft = "";
|
|
444
|
+
this.historyIndex = 0;
|
|
445
|
+
}
|
|
446
|
+
reset() {
|
|
447
|
+
this.buffer = "";
|
|
448
|
+
this.cursor = 0;
|
|
449
|
+
this.historyDraft = "";
|
|
450
|
+
this.historyIndex = this.history.length;
|
|
451
|
+
}
|
|
452
|
+
/** Returns the submitted line on Enter, otherwise null. */
|
|
453
|
+
feed(data) {
|
|
454
|
+
if (data === "\r") {
|
|
455
|
+
const line = this.buffer;
|
|
456
|
+
this.pushHistory(line);
|
|
457
|
+
this.buffer = "";
|
|
458
|
+
this.cursor = 0;
|
|
459
|
+
this.write("\r\n");
|
|
460
|
+
return line;
|
|
461
|
+
}
|
|
462
|
+
if (data === "" || data === "\b") {
|
|
463
|
+
this.deleteBackward();
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
if (data === "\x1B[D" || data === "\x1BOD") {
|
|
467
|
+
this.move(-1);
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
if (data === "\x1B[C" || data === "\x1BOC") {
|
|
471
|
+
this.move(1);
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
if (data === "\x1B[A" || data === "\x1BOA") {
|
|
475
|
+
this.historyPrev();
|
|
476
|
+
return null;
|
|
477
|
+
}
|
|
478
|
+
if (data === "\x1B[B" || data === "\x1BOB") {
|
|
479
|
+
this.historyNext();
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
if (data === "\x1B[3~") {
|
|
483
|
+
this.deleteForward();
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
if (data === "\x1B[H" || data === "\x1BOH") {
|
|
487
|
+
this.cursorHome();
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
if (data === "\x1B[F" || data === "\x1BOF") {
|
|
491
|
+
this.cursorEnd();
|
|
492
|
+
return null;
|
|
493
|
+
}
|
|
494
|
+
if (data.startsWith("\x1B")) return null;
|
|
495
|
+
for (const char of data) {
|
|
496
|
+
if (char < " ") continue;
|
|
497
|
+
this.insert(char);
|
|
498
|
+
}
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
move(delta) {
|
|
502
|
+
const next = this.cursor + delta;
|
|
503
|
+
if (next < 0 || next > this.buffer.length) return;
|
|
504
|
+
const steps = Math.abs(delta);
|
|
505
|
+
this.cursor = next;
|
|
506
|
+
this.write(delta < 0 ? `\x1b[${steps}D` : `\x1b[${steps}C`);
|
|
507
|
+
}
|
|
508
|
+
cursorHome() {
|
|
509
|
+
if (this.cursor === 0) return;
|
|
510
|
+
this.write(`\x1b[${this.cursor}D`);
|
|
511
|
+
this.cursor = 0;
|
|
512
|
+
}
|
|
513
|
+
cursorEnd() {
|
|
514
|
+
const steps = this.buffer.length - this.cursor;
|
|
515
|
+
if (steps <= 0) return;
|
|
516
|
+
this.write(`\x1b[${steps}C`);
|
|
517
|
+
this.cursor = this.buffer.length;
|
|
518
|
+
}
|
|
519
|
+
insert(char) {
|
|
520
|
+
if (this.cursor === this.buffer.length) {
|
|
521
|
+
this.buffer += char;
|
|
522
|
+
this.cursor++;
|
|
523
|
+
this.write(char);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
const tail = this.buffer.slice(this.cursor);
|
|
527
|
+
this.buffer = this.buffer.slice(0, this.cursor) + char + tail;
|
|
528
|
+
this.cursor++;
|
|
529
|
+
this.write(char + tail + `\x1b[${tail.length}D`);
|
|
530
|
+
}
|
|
531
|
+
deleteBackward() {
|
|
532
|
+
if (this.cursor === 0) return;
|
|
533
|
+
if (this.cursor === this.buffer.length) {
|
|
534
|
+
this.buffer = this.buffer.slice(0, -1);
|
|
535
|
+
this.cursor--;
|
|
536
|
+
this.write("\b \b");
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
const after = this.buffer.slice(this.cursor);
|
|
540
|
+
this.buffer = this.buffer.slice(0, this.cursor - 1) + after;
|
|
541
|
+
this.cursor--;
|
|
542
|
+
this.write(`\b${after} \x1b[${after.length + 1}D`);
|
|
543
|
+
}
|
|
544
|
+
deleteForward() {
|
|
545
|
+
if (this.cursor >= this.buffer.length) return;
|
|
546
|
+
const after = this.buffer.slice(this.cursor + 1);
|
|
547
|
+
this.write(`${after} \x1b[${after.length + 1}D`);
|
|
548
|
+
this.buffer = this.buffer.slice(0, this.cursor) + after;
|
|
549
|
+
}
|
|
550
|
+
replaceBuffer(next) {
|
|
551
|
+
if (this.cursor > 0) this.write(`\x1b[${this.cursor}D`);
|
|
552
|
+
if (this.buffer.length > 0) this.write("\x1B[K");
|
|
553
|
+
this.buffer = next;
|
|
554
|
+
this.cursor = next.length;
|
|
555
|
+
if (next) this.write(next);
|
|
556
|
+
}
|
|
557
|
+
historyPrev() {
|
|
558
|
+
if (this.history.length === 0) return;
|
|
559
|
+
if (this.historyIndex === this.history.length) this.historyDraft = this.buffer;
|
|
560
|
+
if (this.historyIndex === 0) return;
|
|
561
|
+
this.historyIndex--;
|
|
562
|
+
this.replaceBuffer(this.history[this.historyIndex]);
|
|
563
|
+
}
|
|
564
|
+
historyNext() {
|
|
565
|
+
if (this.historyIndex >= this.history.length) return;
|
|
566
|
+
this.historyIndex++;
|
|
567
|
+
if (this.historyIndex === this.history.length) {
|
|
568
|
+
this.replaceBuffer(this.historyDraft);
|
|
569
|
+
this.historyDraft = "";
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
this.replaceBuffer(this.history[this.historyIndex]);
|
|
573
|
+
}
|
|
574
|
+
pushHistory(line) {
|
|
575
|
+
if (line.length > 0 && this.history[this.history.length - 1] !== line) this.history.push(line);
|
|
576
|
+
this.historyIndex = this.history.length;
|
|
577
|
+
this.historyDraft = "";
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
//#endregion
|
|
581
|
+
//#region src/commands-backend.ts
|
|
582
|
+
function mapOutputToParams(output, producerCommand) {
|
|
583
|
+
if (output == null) return {};
|
|
584
|
+
if (typeof output === "object" && !Array.isArray(output)) return output;
|
|
585
|
+
const firstOutput = producerCommand?.output?.[0];
|
|
586
|
+
if (firstOutput) return { [firstOutput.name]: output };
|
|
587
|
+
return {};
|
|
588
|
+
}
|
|
589
|
+
async function runShellLine(line, registry = commandRegistry) {
|
|
590
|
+
const segments = parseShellLine(line, registry);
|
|
591
|
+
const results = [];
|
|
592
|
+
let pipedOutput = null;
|
|
593
|
+
let lastProducerCommand;
|
|
594
|
+
for (let i = 0; i < segments.length; i++) {
|
|
595
|
+
const { command, operator } = segments[i];
|
|
596
|
+
if (!command.commandId) continue;
|
|
597
|
+
const cmd = registry.hasCommand(command.commandId) ? registry.getCommand(command.commandId) : void 0;
|
|
598
|
+
if (!cmd) return {
|
|
599
|
+
success: false,
|
|
600
|
+
results,
|
|
601
|
+
error: `Command not found: ${command.commandId}`
|
|
602
|
+
};
|
|
603
|
+
const mergedParams = {
|
|
604
|
+
...mapOutputToParams(pipedOutput ?? void 0, lastProducerCommand),
|
|
605
|
+
...command.params
|
|
606
|
+
};
|
|
607
|
+
const context = registry.createExecutionContext(mergedParams);
|
|
608
|
+
try {
|
|
609
|
+
const result = registry.execute(command.commandId, context);
|
|
610
|
+
const resolved = result instanceof Promise ? await result : result;
|
|
611
|
+
results.push(resolved);
|
|
612
|
+
if (operator === "|") {
|
|
613
|
+
pipedOutput = mapOutputToParams(resolved, cmd);
|
|
614
|
+
lastProducerCommand = cmd;
|
|
615
|
+
} else {
|
|
616
|
+
pipedOutput = null;
|
|
617
|
+
lastProducerCommand = void 0;
|
|
618
|
+
}
|
|
619
|
+
} catch (err) {
|
|
620
|
+
return {
|
|
621
|
+
success: false,
|
|
622
|
+
results,
|
|
623
|
+
error: err instanceof Error ? err.message : String(err)
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
return {
|
|
628
|
+
success: true,
|
|
629
|
+
results
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
function formatResult(result) {
|
|
633
|
+
if (result.error) return `\r\n\x1b[31m${result.error}\x1b[0m\r\n`;
|
|
634
|
+
if (result.results.length === 0) return "";
|
|
635
|
+
const last = result.results[result.results.length - 1];
|
|
636
|
+
if (last == null) return "";
|
|
637
|
+
if (typeof last === "object") return JSON.stringify(last, null, 2) + "\r\n";
|
|
638
|
+
return String(last) + "\r\n";
|
|
639
|
+
}
|
|
640
|
+
var CommandsBackend = class {
|
|
641
|
+
constructor() {
|
|
642
|
+
this.writeCallbacks = /* @__PURE__ */ new Set();
|
|
643
|
+
this.prompt = "> ";
|
|
644
|
+
this.lineInput = new LineInput((data) => this.emit(data));
|
|
645
|
+
}
|
|
646
|
+
onDidWrite(callback) {
|
|
647
|
+
this.writeCallbacks.add(callback);
|
|
648
|
+
return () => this.writeCallbacks.delete(callback);
|
|
649
|
+
}
|
|
650
|
+
emit(data) {
|
|
651
|
+
for (const cb of this.writeCallbacks) cb(data);
|
|
652
|
+
}
|
|
653
|
+
async open() {
|
|
654
|
+
this.emit("Commands\r\nType commands (supports && and |)\r\n\r\n" + this.prompt);
|
|
655
|
+
}
|
|
656
|
+
close() {
|
|
657
|
+
this.lineInput.reset();
|
|
658
|
+
}
|
|
659
|
+
setDimensions(_dimensions) {}
|
|
660
|
+
handleInput(data) {
|
|
661
|
+
const line = this.lineInput.feed(data);
|
|
662
|
+
if (line === null) return;
|
|
663
|
+
this.runLine(line);
|
|
664
|
+
}
|
|
665
|
+
async runLine(line) {
|
|
666
|
+
const trimmed = line.trim();
|
|
667
|
+
if (!trimmed) {
|
|
668
|
+
this.emit(this.prompt);
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
const result = await runShellLine(trimmed);
|
|
672
|
+
this.emit(formatResult(result));
|
|
673
|
+
this.emit(this.prompt);
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
//#endregion
|
|
677
|
+
//#region src/js-terminal-backend.ts
|
|
678
|
+
var JS_REPL_PROFILE_ID = "terminal.javascript-repl";
|
|
679
|
+
var PROMPT = "> ";
|
|
680
|
+
function formatJsResult(value) {
|
|
681
|
+
if (value === void 0) return "";
|
|
682
|
+
if (value === null) return "null\r\n";
|
|
683
|
+
if (typeof value === "object") return JSON.stringify(value, null, 2) + "\r\n";
|
|
684
|
+
return String(value) + "\r\n";
|
|
685
|
+
}
|
|
686
|
+
var JsReplBackend = class {
|
|
687
|
+
constructor() {
|
|
688
|
+
this.writeCallbacks = /* @__PURE__ */ new Set();
|
|
689
|
+
this.runtime = null;
|
|
690
|
+
this.running = false;
|
|
691
|
+
this.lineInput = new LineInput((data) => this.emit(data));
|
|
692
|
+
}
|
|
693
|
+
onDidWrite(callback) {
|
|
694
|
+
this.writeCallbacks.add(callback);
|
|
695
|
+
return () => this.writeCallbacks.delete(callback);
|
|
696
|
+
}
|
|
697
|
+
emit(data) {
|
|
698
|
+
for (const cb of this.writeCallbacks) cb(data);
|
|
699
|
+
}
|
|
700
|
+
getRuntime() {
|
|
701
|
+
if (!this.runtime) this.runtime = createJsRuntime();
|
|
702
|
+
return this.runtime;
|
|
703
|
+
}
|
|
704
|
+
async open() {
|
|
705
|
+
this.emit("JavaScript REPL\r\n\r\n" + PROMPT);
|
|
706
|
+
}
|
|
707
|
+
close() {
|
|
708
|
+
this.runtime?.close();
|
|
709
|
+
this.runtime = null;
|
|
710
|
+
this.lineInput.reset();
|
|
711
|
+
}
|
|
712
|
+
setDimensions(_dimensions) {}
|
|
713
|
+
handleInput(data) {
|
|
714
|
+
if (this.running) return;
|
|
715
|
+
const line = this.lineInput.feed(data);
|
|
716
|
+
if (line === null) return;
|
|
717
|
+
this.runLine(line);
|
|
718
|
+
}
|
|
719
|
+
async runLine(line) {
|
|
720
|
+
const trimmed = line.trim();
|
|
721
|
+
if (!trimmed) {
|
|
722
|
+
this.emit(PROMPT);
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
this.running = true;
|
|
726
|
+
try {
|
|
727
|
+
const output = formatJsResult(await this.getRuntime().execute(trimmed));
|
|
728
|
+
if (output) this.emit(output);
|
|
729
|
+
} catch (err) {
|
|
730
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
731
|
+
this.emit(`\x1b[31m${msg}\x1b[0m\r\n`);
|
|
732
|
+
} finally {
|
|
733
|
+
this.running = false;
|
|
734
|
+
this.emit(PROMPT);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
//#endregion
|
|
739
|
+
export { VIEW_TERMINAL as _, registerTerminalProfile as a, parseTerminalState as c, TERMINAL_INSTANCES_TABS_ID as d, TERMINAL_NEW_DROPDOWN as f, TOPIC_TERMINAL_OPENED as g, TOPIC_TERMINAL_CLOSED as h, LineInput as i, serializeTerminalState as l, TOPIC_TERMINAL_ACTIVE_CHANGED as m, JsReplBackend as n, terminalService as o, TERMINAL_PANEL_SETTINGS_KEY as p, CommandsBackend as r, emptyTerminalState as s, JS_REPL_PROFILE_ID as t, TARGET_TERMINAL_PROFILES as u };
|
|
740
|
+
|
|
741
|
+
//# sourceMappingURL=js-terminal-backend-BcQIBeMe.js.map
|