Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
### Changed

- Always enable verbose (`-v`) flag when a log directory is configured (`coder.proxyLogDir`).
- Automatically start a workspace if it is opened but not running.
- Automatically start a workspace without prompting if it is explicitly opened but not running.

### Added

Expand Down
260 changes: 124 additions & 136 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ export class Commands {
throw new Error("You are not logged in");
}
if (item instanceof AgentTreeItem) {
await openWorkspace(
await this.openWorkspace(
baseUrl,
item.workspace,
item.agent,
Expand All @@ -465,7 +465,13 @@ export class Commands {
// User declined to pick an agent.
return;
}
await openWorkspace(baseUrl, item.workspace, agent, undefined, true);
await this.openWorkspace(
baseUrl,
item.workspace,
agent,
undefined,
true,
);
} else {
throw new Error("Unable to open unknown sidebar item");
}
Expand Down Expand Up @@ -583,7 +589,7 @@ export class Commands {
return;
}

await openWorkspace(baseUrl, workspace, agent, folderPath, openRecent);
await this.openWorkspace(baseUrl, workspace, agent, folderPath, openRecent);
}

/**
Expand All @@ -605,15 +611,49 @@ export class Commands {
throw new Error("You are not logged in");
}

await openDevContainer(
const remoteAuthority = toRemoteAuthority(
baseUrl,
workspaceOwner,
workspaceName,
workspaceAgent,
devContainerName,
devContainerFolder,
localWorkspaceFolder,
localConfigFile,
);

const hostPath = localWorkspaceFolder ? localWorkspaceFolder : undefined;
const configFile =
hostPath && localConfigFile
? {
path: localConfigFile,
scheme: "vscode-fileHost",
}
: undefined;
const devContainer = Buffer.from(
JSON.stringify({
containerName: devContainerName,
hostPath,
configFile,
localDocker: false,
}),
"utf-8",
).toString("hex");

const type = localWorkspaceFolder ? "dev-container" : "attached-container";
const devContainerAuthority = `${type}+${devContainer}@${remoteAuthority}`;

let newWindow = true;
if (!vscode.workspace.workspaceFolders?.length) {
newWindow = false;
}

// Only set the memento if when opening a new folder
await this.storage.setFirstConnect();
await vscode.commands.executeCommand(
"vscode.openFolder",
vscode.Uri.from({
scheme: "vscode-remote",
authority: devContainerAuthority,
path: devContainerFolder,
}),
newWindow,
);
}

Expand Down Expand Up @@ -722,141 +762,89 @@ export class Commands {
}
return agents;
}
}

/**
* Given a workspace and agent, build the host name, find a directory to open,
* and pass both to the Remote SSH plugin in the form of a remote authority
* URI.
*
* If provided, folderPath is always used, otherwise expanded_directory from
* the agent is used.
*/
async function openWorkspace(
baseUrl: string,
workspace: Workspace,
agent: WorkspaceAgent,
folderPath: string | undefined,
openRecent: boolean = false,
) {
const remoteAuthority = toRemoteAuthority(
baseUrl,
workspace.owner_name,
workspace.name,
agent.name,
);

let newWindow = true;
// Open in the existing window if no workspaces are open.
if (!vscode.workspace.workspaceFolders?.length) {
newWindow = false;
}

if (!folderPath) {
folderPath = agent.expanded_directory;
}

// If the agent had no folder or we have been asked to open the most recent,
// we can try to open a recently opened folder/workspace.
if (!folderPath || openRecent) {
const output: {
workspaces: { folderUri: vscode.Uri; remoteAuthority: string }[];
} = await vscode.commands.executeCommand("_workbench.getRecentlyOpened");
const opened = output.workspaces.filter(
// Remove recents that do not belong to this connection. The remote
// authority maps to a workspace/agent combination (using the SSH host
// name). There may also be some legacy connections that still may
// reference a workspace without an agent name, which will be missed.
(opened) => opened.folderUri?.authority === remoteAuthority,
/**
* Given a workspace and agent, build the host name, find a directory to open,
* and pass both to the Remote SSH plugin in the form of a remote authority
* URI.
*
* If provided, folderPath is always used, otherwise expanded_directory from
* the agent is used.
*/
async openWorkspace(
baseUrl: string,
workspace: Workspace,
agent: WorkspaceAgent,
folderPath: string | undefined,
openRecent: boolean = false,
) {
const remoteAuthority = toRemoteAuthority(
baseUrl,
workspace.owner_name,
workspace.name,
agent.name,
);

// openRecent will always use the most recent. Otherwise, if there are
// multiple we ask the user which to use.
if (opened.length === 1 || (opened.length > 1 && openRecent)) {
folderPath = opened[0].folderUri.path;
} else if (opened.length > 1) {
const items = opened.map((f) => f.folderUri.path);
folderPath = await vscode.window.showQuickPick(items, {
title: "Select a recently opened folder",
});
if (!folderPath) {
// User aborted.
return;
}
let newWindow = true;
// Open in the existing window if no workspaces are open.
if (!vscode.workspace.workspaceFolders?.length) {
newWindow = false;
}
}

if (folderPath) {
await vscode.commands.executeCommand(
"vscode.openFolder",
vscode.Uri.from({
scheme: "vscode-remote",
authority: remoteAuthority,
path: folderPath,
}),
// Open this in a new window!
newWindow,
);
return;
}
if (!folderPath) {
folderPath = agent.expanded_directory;
}

// This opens the workspace without an active folder opened.
await vscode.commands.executeCommand("vscode.newWindow", {
remoteAuthority: remoteAuthority,
reuseWindow: !newWindow,
});
}
// If the agent had no folder or we have been asked to open the most recent,
// we can try to open a recently opened folder/workspace.
if (!folderPath || openRecent) {
const output: {
workspaces: { folderUri: vscode.Uri; remoteAuthority: string }[];
} = await vscode.commands.executeCommand("_workbench.getRecentlyOpened");
const opened = output.workspaces.filter(
// Remove recents that do not belong to this connection. The remote
// authority maps to a workspace/agent combination (using the SSH host
// name). There may also be some legacy connections that still may
// reference a workspace without an agent name, which will be missed.
(opened) => opened.folderUri?.authority === remoteAuthority,
);

async function openDevContainer(
baseUrl: string,
workspaceOwner: string,
workspaceName: string,
workspaceAgent: string,
devContainerName: string,
devContainerFolder: string,
localWorkspaceFolder: string = "",
localConfigFile: string = "",
) {
const remoteAuthority = toRemoteAuthority(
baseUrl,
workspaceOwner,
workspaceName,
workspaceAgent,
);

const hostPath = localWorkspaceFolder ? localWorkspaceFolder : undefined;
const configFile =
hostPath && localConfigFile
? {
path: localConfigFile,
scheme: "vscode-fileHost",
// openRecent will always use the most recent. Otherwise, if there are
// multiple we ask the user which to use.
if (opened.length === 1 || (opened.length > 1 && openRecent)) {
folderPath = opened[0].folderUri.path;
} else if (opened.length > 1) {
const items = opened.map((f) => f.folderUri.path);
folderPath = await vscode.window.showQuickPick(items, {
title: "Select a recently opened folder",
});
if (!folderPath) {
// User aborted.
return;
}
: undefined;
const devContainer = Buffer.from(
JSON.stringify({
containerName: devContainerName,
hostPath,
configFile,
localDocker: false,
}),
"utf-8",
).toString("hex");

const type = localWorkspaceFolder ? "dev-container" : "attached-container";
const devContainerAuthority = `${type}+${devContainer}@${remoteAuthority}`;

let newWindow = true;
if (!vscode.workspace.workspaceFolders?.length) {
newWindow = false;
}
}
}

await vscode.commands.executeCommand(
"vscode.openFolder",
vscode.Uri.from({
scheme: "vscode-remote",
authority: devContainerAuthority,
path: devContainerFolder,
}),
newWindow,
);
// Only set the memento if when opening a new folder/window
await this.storage.setFirstConnect();
if (folderPath) {
await vscode.commands.executeCommand(
"vscode.openFolder",
vscode.Uri.from({
scheme: "vscode-remote",
authority: remoteAuthority,
path: folderPath,
}),
// Open this in a new window!
newWindow,
);
return;
}

// This opens the workspace without an active folder opened.
await vscode.commands.executeCommand("vscode.newWindow", {
remoteAuthority: remoteAuthority,
reuseWindow: !newWindow,
});
}
}
8 changes: 7 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
ctx.logUri,
);

// Try to clear this flag ASAP then pass it around if needed
const isFirstConnect = await storage.getAndClearFirstConnect();

// This client tracks the current login and will be used through the life of
// the plugin to poll workspaces for the current login, as well as being used
// in commands that operate on the current login.
Expand Down Expand Up @@ -309,7 +312,10 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
ctx.extensionMode,
);
try {
const details = await remote.setup(vscodeProposed.env.remoteAuthority);
const details = await remote.setup(
vscodeProposed.env.remoteAuthority,
isFirstConnect,
);
if (details) {
// Authenticate the plugin client which is used in the sidebar to display
// workspaces belonging to this deployment.
Expand Down
Loading