|
// retoor <retoor@molodetz.nl>
|
|
|
|
const fs = require("fs");
|
|
const vscode = require("vscode");
|
|
|
|
const AGENT_PATH = "/usr/bin/dpc";
|
|
const AGENT_TERMINAL = "DevPlace Code";
|
|
const SHELL_TERMINAL = "pravda@workspace";
|
|
const BOOT_KEY = "devplace.bootMarker";
|
|
const PANEL_STEPS = { short: 0, normal: 2, tall: 5, maximized: 0 };
|
|
|
|
class Profile {
|
|
constructor() {
|
|
this.data = Object.assign(
|
|
{
|
|
theme: "devplace-dark",
|
|
layout: "standard",
|
|
panel_preset: "tall",
|
|
boot_agent: "dpc",
|
|
boot_shell: true,
|
|
trust_all: true,
|
|
},
|
|
this.fromFile(),
|
|
this.fromEnv(),
|
|
);
|
|
}
|
|
|
|
fromFile() {
|
|
const path = process.env.DEVPLACE_EDITOR_PROFILE;
|
|
if (!path) return {};
|
|
try {
|
|
const parsed = JSON.parse(fs.readFileSync(path, "utf8"));
|
|
return parsed && parsed.editor ? parsed.editor : {};
|
|
} catch (error) {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
fromEnv() {
|
|
const values = {};
|
|
if (process.env.DEVPLACE_EDITOR_PANEL_PRESET) {
|
|
values.panel_preset = process.env.DEVPLACE_EDITOR_PANEL_PRESET;
|
|
}
|
|
if (process.env.DEVPLACE_EDITOR_BOOT_AGENT) {
|
|
values.boot_agent = process.env.DEVPLACE_EDITOR_BOOT_AGENT;
|
|
}
|
|
if (process.env.DEVPLACE_EDITOR_BOOT_SHELL) {
|
|
values.boot_shell = process.env.DEVPLACE_EDITOR_BOOT_SHELL === "1";
|
|
}
|
|
return values;
|
|
}
|
|
|
|
get bootMarker() {
|
|
return process.env.DEVPLACE_CONTAINER_BOOT || `host-${process.pid}`;
|
|
}
|
|
|
|
get wantsAgent() {
|
|
return this.data.boot_agent === "dpc" && fs.existsSync(AGENT_PATH);
|
|
}
|
|
|
|
get wantsShell() {
|
|
return Boolean(this.data.boot_shell);
|
|
}
|
|
|
|
get panelPreset() {
|
|
return this.data.panel_preset || "tall";
|
|
}
|
|
}
|
|
|
|
class BootTerminals {
|
|
constructor(profile, memento) {
|
|
this.profile = profile;
|
|
this.memento = memento;
|
|
}
|
|
|
|
alreadyBooted() {
|
|
return this.memento.get(BOOT_KEY) === this.profile.bootMarker;
|
|
}
|
|
|
|
async open() {
|
|
if (this.alreadyBooted()) return false;
|
|
await this.memento.update(BOOT_KEY, this.profile.bootMarker);
|
|
const shell = this.profile.wantsShell ? this.createShell() : null;
|
|
const agent = this.profile.wantsAgent ? this.createAgent() : null;
|
|
if (agent) agent.show(true);
|
|
else if (shell) shell.show(true);
|
|
return Boolean(agent || shell);
|
|
}
|
|
|
|
createAgent() {
|
|
return vscode.window.createTerminal({
|
|
name: AGENT_TERMINAL,
|
|
shellPath: AGENT_PATH,
|
|
iconPath: new vscode.ThemeIcon("rocket"),
|
|
isTransient: false,
|
|
});
|
|
}
|
|
|
|
createShell() {
|
|
return vscode.window.createTerminal({
|
|
name: SHELL_TERMINAL,
|
|
shellPath: "/bin/bash",
|
|
shellArgs: ["-l"],
|
|
iconPath: new vscode.ThemeIcon("terminal-bash"),
|
|
isTransient: false,
|
|
});
|
|
}
|
|
}
|
|
|
|
class Layout {
|
|
constructor(profile) {
|
|
this.profile = profile;
|
|
}
|
|
|
|
async apply(panelIsOpen) {
|
|
const preset = this.profile.panelPreset;
|
|
if (!panelIsOpen) return;
|
|
if (preset === "maximized") {
|
|
await vscode.commands.executeCommand("workbench.action.toggleMaximizedPanel");
|
|
return;
|
|
}
|
|
const steps = PANEL_STEPS[preset] === undefined ? 5 : PANEL_STEPS[preset];
|
|
for (let index = 0; index < steps; index += 1) {
|
|
await vscode.commands.executeCommand("workbench.action.increaseViewSize");
|
|
}
|
|
}
|
|
}
|
|
|
|
class Presence {
|
|
constructor(context) {
|
|
this.context = context;
|
|
this.item = vscode.window.createStatusBarItem(
|
|
vscode.StatusBarAlignment.Left,
|
|
100,
|
|
);
|
|
}
|
|
|
|
get projectTitle() {
|
|
return process.env.DEVPLACE_PROJECT_TITLE || "DevPlace";
|
|
}
|
|
|
|
register() {
|
|
this.item.text = `$(rocket) ${this.projectTitle}`;
|
|
this.item.tooltip = this.tooltip();
|
|
this.item.command = "devplace.openProject";
|
|
this.item.show();
|
|
this.context.subscriptions.push(this.item);
|
|
this.registerCommands();
|
|
}
|
|
|
|
tooltip() {
|
|
const owner = process.env.DEVPLACE_WORKSPACE_OWNER || "";
|
|
const name = process.env.DEVPLACE_TUNNEL_NAME || "";
|
|
return `DevPlace workspace ${name}${owner ? ` for ${owner}` : ""}`;
|
|
}
|
|
|
|
registerCommands() {
|
|
const commands = {
|
|
"devplace.runAgent": () => this.runAgent(),
|
|
"devplace.openProject": () => this.open(process.env.DEVPLACE_PROJECT_URL),
|
|
"devplace.openWorkspacePage": () =>
|
|
this.open(process.env.DEVPLACE_WORKSPACE_URL),
|
|
"devplace.openDocs": () => this.openDocs(),
|
|
"devplace.showTunnels": () => this.showTunnels(),
|
|
};
|
|
for (const [name, handler] of Object.entries(commands)) {
|
|
this.context.subscriptions.push(
|
|
vscode.commands.registerCommand(name, handler),
|
|
);
|
|
}
|
|
}
|
|
|
|
runAgent() {
|
|
const terminal = vscode.window.createTerminal({
|
|
name: AGENT_TERMINAL,
|
|
shellPath: AGENT_PATH,
|
|
iconPath: new vscode.ThemeIcon("rocket"),
|
|
});
|
|
terminal.show(true);
|
|
}
|
|
|
|
openDocs() {
|
|
const base = process.env.DEVPLACE_BASE_URL || "";
|
|
this.open(base ? `${base}/docs/workspace-editor.html` : "");
|
|
}
|
|
|
|
open(url) {
|
|
if (!url) {
|
|
vscode.window.showWarningMessage(
|
|
"DevPlace has not published a site URL for this workspace yet.",
|
|
);
|
|
return;
|
|
}
|
|
vscode.env.openExternal(vscode.Uri.parse(url));
|
|
}
|
|
|
|
async showTunnels() {
|
|
const path = process.env.DEVPLACE_TUNNEL_MANIFEST;
|
|
const rows = this.readManifest(path);
|
|
if (!rows.length) {
|
|
vscode.window.showInformationMessage(
|
|
"This workspace has no public tunnels yet.",
|
|
);
|
|
return;
|
|
}
|
|
const picked = await vscode.window.showQuickPick(
|
|
rows.map((row) => ({
|
|
label: row.label || row.hostname,
|
|
description: row.url,
|
|
detail: `port ${row.container_port} - ${row.status}`,
|
|
url: row.url,
|
|
})),
|
|
{ placeHolder: "Open a public tunnel" },
|
|
);
|
|
if (picked) this.open(picked.url);
|
|
}
|
|
|
|
readManifest(path) {
|
|
if (!path) return [];
|
|
try {
|
|
const parsed = JSON.parse(fs.readFileSync(path, "utf8"));
|
|
return Array.isArray(parsed.tunnels) ? parsed.tunnels : [];
|
|
} catch (error) {
|
|
return [];
|
|
}
|
|
}
|
|
}
|
|
|
|
async function stage(output, name, run) {
|
|
try {
|
|
return await run();
|
|
} catch (error) {
|
|
output.appendLine(`${name} failed: ${error}`);
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
async function activate(context) {
|
|
const output = vscode.window.createOutputChannel("DevPlace");
|
|
context.subscriptions.push(output);
|
|
const profile = new Profile();
|
|
|
|
await stage(output, "presence", () => new Presence(context).register());
|
|
const opened = await stage(output, "terminals", () =>
|
|
new BootTerminals(profile, context.workspaceState).open(),
|
|
);
|
|
await stage(output, "layout", () => new Layout(profile).apply(Boolean(opened)));
|
|
}
|
|
|
|
function deactivate() {}
|
|
|
|
module.exports = { activate, deactivate };
|