diff --git a/src/node/server.ts b/src/node/server.ts index 0c0541b8..c73498c9 100644 --- a/src/node/server.ts +++ b/src/node/server.ts @@ -445,6 +445,15 @@ export abstract class Server { } } +interface StartPath { + path?: string[] | string; + workspace?: boolean; +} + +interface Settings { + lastVisited?: StartPath; +} + export class MainServer extends Server { public readonly _onDidClientConnect = new Emitter(); public readonly onDidClientConnect = this._onDidClientConnect.event; @@ -461,6 +470,8 @@ export class MainServer extends Server { private _proxyServer?: Promise; private readonly proxyTimeout = 5000; + private settings: Settings = {}; + public constructor(options: ServerOptions, args: ParsedArgs) { super(options); this.servicesPromise = this.initializeServices(args); @@ -528,44 +539,37 @@ export class MainServer extends Server { private async getRoot(request: http.IncomingMessage, parsedUrl: url.UrlWithParsedQuery): Promise { const filePath = path.join(this.rootPath, "out/vs/code/browser/workbench/workbench.html"); - let [content] = await Promise.all([ + let [content, startPath] = await Promise.all([ util.promisify(fs.readFile)(filePath, "utf8"), + this.getFirstValidPath([ + { path: parsedUrl.query.workspace, workspace: true }, + { path: parsedUrl.query.folder }, + (await this.readSettings()).lastVisited, + { path: this.options.folderUri } + ]), this.servicesPromise, ]); + if (startPath) { + this.writeSettings({ + lastVisited: { + path: startPath.uri.fsPath, + workspace: startPath.workspace + }, + }); + } + const logger = this.services.get(ILogService) as ILogService; logger.info("request.url", `"${request.url}"`); - const cwd = process.env.VSCODE_CWD || process.cwd(); - const remoteAuthority = request.headers.host as string; const transformer = getUriTransformer(remoteAuthority); - const validatePath = async (filePath: string[] | string | undefined, isDirectory: boolean, unsetFallback?: string): Promise => { - if (!filePath || filePath.length === 0) { - if (!unsetFallback) { - return undefined; - } - filePath = unsetFallback; - } else if (Array.isArray(filePath)) { - filePath = filePath[0]; - } - const uri = URI.file(sanitizeFilePath(filePath, cwd)); - try { - const stat = await util.promisify(fs.stat)(uri.fsPath); - if (isDirectory !== stat.isDirectory()) { - return undefined; - } - } catch (error) { - return undefined; - } - return transformer.transformOutgoing(uri); - }; const environment = this.services.get(IEnvironmentService) as IEnvironmentService; const options: Options = { WORKBENCH_WEB_CONGIGURATION: { - workspaceUri: await validatePath(parsedUrl.query.workspace, false), - folderUri: !parsedUrl.query.workspace ? await validatePath(parsedUrl.query.folder, true, this.options.folderUri) : undefined, + workspaceUri: startPath && startPath.workspace ? transformer.transformOutgoing(startPath.uri) : undefined, + folderUri: startPath && !startPath.workspace ? transformer.transformOutgoing(startPath.uri) : undefined, remoteAuthority, productConfiguration: product, }, @@ -581,6 +585,34 @@ export class MainServer extends Server { return { content, filePath }; } + /** + * Choose the first valid path. + */ + private async getFirstValidPath(startPaths: Array): Promise<{ uri: URI, workspace?: boolean} | undefined> { + const logger = this.services.get(ILogService) as ILogService; + const cwd = process.env.VSCODE_CWD || process.cwd(); + for (let i = 0; i < startPaths.length; ++i) { + const startPath = startPaths[i]; + if (!startPath) { + continue; + } + const paths = typeof startPath.path === "string" ? [startPath.path] : (startPath.path || []); + for (let j = 0; j < paths.length; ++j) { + const uri = URI.file(sanitizeFilePath(paths[j], cwd)); + try { + const stat = await util.promisify(fs.stat)(uri.fsPath); + // Workspace must be a file. + if (!!startPath.workspace !== stat.isDirectory()) { + return { uri, workspace: startPath.workspace }; + } + } catch (error) { + logger.warn(error.message); + } + } + } + return undefined; + } + private async connect(message: ConnectionTypeRequest, protocol: Protocol): Promise { if (product.commit && message.commit !== product.commit) { throw new Error(`Version mismatch (${message.commit} instead of ${product.commit})`); @@ -810,4 +842,41 @@ export class MainServer extends Server { } return path; } + + /** + * Return the file path for Coder settings. + */ + private get settingsPath(): string { + const environment = this.services.get(IEnvironmentService) as IEnvironmentService; + return path.join(environment.userDataPath, "coder.json"); + } + + /** + * Read settings from the file. On a failure return last known settings and + * log a warning. + * + */ + private async readSettings(): Promise { + try { + const raw = (await util.promisify(fs.readFile)(this.settingsPath, "utf8")).trim(); + this.settings = raw ? JSON.parse(raw) : {}; + } catch (error) { + if (error.code !== "ENOENT") { + (this.services.get(ILogService) as ILogService).warn(error.message); + } + } + return this.settings; + } + + /** + * Write settings combined with current settings. On failure log a warning. + */ + private async writeSettings(newSettings: Partial): Promise { + this.settings = { ...this.settings, ...newSettings }; + try { + await util.promisify(fs.writeFile)(this.settingsPath, JSON.stringify(this.settings)); + } catch (error) { + (this.services.get(ILogService) as ILogService).warn(error.message); + } + } }