diff --git a/.gitignore b/.gitignore index d2ee77a3..ffaa56b4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ dist out .DS_Store release -.cache \ No newline at end of file +.vscode +.cache diff --git a/.travis.yml b/.travis.yml index 158ad64f..4f9878df 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: node_js node_js: - 8.15.0 env: - - VSCODE_VERSION="1.33.0" MAJOR_VERSION="1" VERSION="$MAJOR_VERSION.$TRAVIS_BUILD_NUMBER-vsc$VSCODE_VERSION" + - VSCODE_VERSION="1.33.1" MAJOR_VERSION="1" VERSION="$MAJOR_VERSION.$TRAVIS_BUILD_NUMBER-vsc$VSCODE_VERSION" matrix: include: - os: linux diff --git a/Dockerfile b/Dockerfile index 8edaf8a9..05d04022 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,7 +24,10 @@ RUN apt-get update && apt-get install -y \ git \ locales \ sudo \ - dumb-init + dumb-init \ + vim \ + curl \ + wget RUN locale-gen en_US.UTF-8 # We unfortunately cannot use update-locale because docker will not use the env variables diff --git a/README.md b/README.md index 00b82faa..547c336c 100644 --- a/README.md +++ b/README.md @@ -23,10 +23,6 @@ docker run -it -p 127.0.0.1:8443:8443 -v "${PWD}:/home/coder/project" codercom/c ## Getting Started -### Hosted - -[Try `code-server` now](https://coder.com/signup) for free at coder.com. - ### Docker See docker oneliner mentioned above. Dockerfile is at [/Dockerfile](/Dockerfile). diff --git a/build/tasks.ts b/build/tasks.ts index b43316e1..3d7a563a 100644 --- a/build/tasks.ts +++ b/build/tasks.ts @@ -12,7 +12,7 @@ const libPath = path.join(__dirname, "../lib"); const vscodePath = path.join(libPath, "vscode"); const defaultExtensionsPath = path.join(libPath, "extensions"); const pkgsPath = path.join(__dirname, "../packages"); -const vscodeVersion = process.env.VSCODE_VERSION || "1.33.0"; +const vscodeVersion = process.env.VSCODE_VERSION || "1.33.1"; const vsSourceUrl = `https://codesrv-ci.cdr.sh/vstar-${vscodeVersion}.tar.gz`; const buildServerBinary = register("build:server:binary", async (runner) => { diff --git a/doc/admin/install/digitalocean.md b/doc/admin/install/digitalocean.md index 1a83d7bd..1a1b7dd5 100644 --- a/doc/admin/install/digitalocean.md +++ b/doc/admin/install/digitalocean.md @@ -39,7 +39,7 @@ If you're just starting out, we recommend [installing code-server locally](../.. > To ensure the connection between you and your server is encrypted view our guide on [securing your setup](../../security/ssl.md) - Finally start the code-server ``` - sudo ./code-server-linux -p 80 + sudo ./code-server -p 80 ``` > For instructions on how to keep the server running after you end your SSH session please checkout [how to use systemd](https://www.linode.com/docs/quick-answers/linux/start-service-at-boot/) to start linux based services if they are killed - When you visit the public IP for your Digital Ocean instance, you will be greeted with this page. Code-server is using a self-signed SSL certificate for easy setup. To proceed to the IDE, click **"Advanced"** diff --git a/doc/self-hosted/index.md b/doc/self-hosted/index.md index 8626599c..c3ee6a99 100644 --- a/doc/self-hosted/index.md +++ b/doc/self-hosted/index.md @@ -62,7 +62,7 @@ OPTIONS > Example: `code-server -h 127.0.0.1` ### Open - You can have the server automatically open the VS Code in your browser on startup by using the `code server -o` or `code-server --open` flags + You can have the server automatically open the VS Code in your browser on startup by using the `code-server -o` or `code-server --open` flags ### Port By default, code-server will use `8443` as its port. This can be changed by using `code-server -p` or `code-server --port=` followed by the port you want to use. diff --git a/packages/app/browser/src/app.html b/packages/app/browser/src/app.html index 99e23733..dadd6d54 100644 --- a/packages/app/browser/src/app.html +++ b/packages/app/browser/src/app.html @@ -7,22 +7,24 @@ -
-
<- Back
-

code-server

-

- Enter server password -

-
- - -
-
- -
-
+
+
+
+ <- Back

code-server

+

+ Enter server password +

+
+ + +
+
+ +
+
+
\ No newline at end of file diff --git a/packages/app/browser/src/app.ts b/packages/app/browser/src/app.ts index ddb35e7e..9e377845 100644 --- a/packages/app/browser/src/app.ts +++ b/packages/app/browser/src/app.ts @@ -20,11 +20,14 @@ window.addEventListener("message", (event) => { }); const password = document.getElementById("password") as HTMLInputElement; -const submit = document.getElementById("submit") as HTMLButtonElement; -if (!submit) { - throw new Error("No submit button found"); +const form = document.getElementById("login-form") as HTMLFormElement; + +if (!form) { + throw new Error("No password form found"); } -submit.addEventListener("click", () => { + +form.addEventListener("submit", (e) => { + e.preventDefault(); document.cookie = `password=${password.value}`; location.reload(); }); @@ -38,4 +41,4 @@ const errorDisplay = document.getElementById("error-display") as HTMLDivElement; if (document.referrer === document.location.href && matches) { errorDisplay.innerText = "Password is incorrect!"; -} \ No newline at end of file +} diff --git a/packages/ide/src/fill/electron.ts b/packages/ide/src/fill/electron.ts index f0ec6dc6..624b0319 100644 --- a/packages/ide/src/fill/electron.ts +++ b/packages/ide/src/fill/electron.ts @@ -45,7 +45,8 @@ const newCreateElement = (tagName: K): HT }, set: (value: string): void => { if (value) { - value = value.replace(/file:\/\//g, "/resource"); + const resourceBaseUrl = location.pathname.replace(/\/$/, "") + "/resource"; + value = value.replace(/file:\/\//g, resourceBaseUrl); } oldSrc!.set!.call(img, value); }, @@ -66,7 +67,8 @@ const newCreateElement = (tagName: K): HT }, set: (value: string): void => { if (value) { - value = value.replace(/file:\/\//g, "/resource"); + const resourceBaseUrl = location.pathname.replace(/\/$/, "") + "/resource"; + value = value.replace(/file:\/\//g, resourceBaseUrl); } oldInnerHtml!.set!.call(style, value); }, @@ -80,7 +82,8 @@ const newCreateElement = (tagName: K): HT if (sheet && !overridden) { const oldInsertRule = sheet.insertRule; sheet.insertRule = (rule: string, index?: number): void => { - rule = rule.replace(/file:\/\//g, "/resource"); + const resourceBaseUrl = location.pathname.replace(/\/$/, "") + "/resource"; + rule = rule.replace(/file:\/\//g, resourceBaseUrl); oldInsertRule.call(sheet, rule, index); }; overridden = true; @@ -145,8 +148,9 @@ const newCreateElement = (tagName: K): HT (view as any).send = (channel: string, ...args: any[]): void => { // tslint:disable-line no-any if (args[0] && typeof args[0] === "object" && args[0].contents) { // TODO - args[0].contents = (args[0].contents as string).replace(/"(file:\/\/[^"]*)"/g, (m1) => `"/resource${m1}"`); - args[0].contents = (args[0].contents as string).replace(/"vscode-resource:([^"]*)"/g, (m, m1) => `"/resource${m1}"`); + const resourceBaseUrl = location.pathname.replace(/\/$/, "") + "/resource"; + args[0].contents = (args[0].contents as string).replace(/"(file:\/\/[^"]*)"/g, (m1) => `"${resourceBaseUrl}${m1}"`); + args[0].contents = (args[0].contents as string).replace(/"vscode-resource:([^"]*)"/g, (m, m1) => `"${resourceBaseUrl}${m1}"`); args[0].contents = (args[0].contents as string).replace(/style-src vscode-core-resource:/g, "style-src 'self'"); } if (view.contentWindow) { diff --git a/packages/ide/src/retry.ts b/packages/ide/src/retry.ts index 66a1575d..aefa7977 100644 --- a/packages/ide/src/retry.ts +++ b/packages/ide/src/retry.ts @@ -75,7 +75,7 @@ export class Retry { // Times are in seconds. private readonly retryMinDelay = 1; - private readonly retryMaxDelay = 10; + private readonly retryMaxDelay = 3; private readonly maxImmediateRetries = 5; private readonly retryExponent = 1.5; private blocked: string | boolean | undefined; diff --git a/packages/server/src/cli.ts b/packages/server/src/cli.ts index 79010628..ef18fd93 100644 --- a/packages/server/src/cli.ts +++ b/packages/server/src/cli.ts @@ -200,7 +200,13 @@ const bold = (text: string | number): string | number => { const webpackConfig = require(path.resolve(__dirname, "..", "..", "web", "webpack.config.js")); const compiler = require("webpack")(webpackConfig); app.use(require("webpack-dev-middleware")(compiler, { - logger, + logger: { + trace: (m: string): void => logger.trace("webpack", field("message", m)), + debug: (m: string): void => logger.debug("webpack", field("message", m)), + info: (m: string): void => logger.info("webpack", field("message", m)), + warn: (m: string): void => logger.warn("webpack", field("message", m)), + error: (m: string): void => logger.error("webpack", field("message", m)), + }, publicPath: webpackConfig.output.publicPath, stats: webpackConfig.stats, })); diff --git a/packages/vscode/src/dialog.scss b/packages/vscode/src/dialog.scss index 35627012..2d08911f 100644 --- a/packages/vscode/src/dialog.scss +++ b/packages/vscode/src/dialog.scss @@ -2,6 +2,7 @@ --primary: #2A2E37; --border: black; --faded: #a0a1a5; + --disabled: #888; --header-background: #161616; --header-foreground: white; --list-active-selection-background: rgb(0, 120, 160); @@ -101,6 +102,12 @@ background-color: var(--list-active-selection-background); color: var(--list-active-selection-foreground); } + + &.disabled, &.disabled:hover { + background-color: var(--primary); + color: var(--disabled); + cursor: initial; + } } } @@ -134,6 +141,11 @@ color: white; } } + + button[disabled], button[disabled]:hover { + color: var(--disabled); + cursor: initial; + } } } diff --git a/packages/vscode/src/dialog.ts b/packages/vscode/src/dialog.ts index 4601b6d0..1d480bd6 100644 --- a/packages/vscode/src/dialog.ts +++ b/packages/vscode/src/dialog.ts @@ -16,6 +16,9 @@ import { IThemeService } from "vs/platform/theme/common/themeService"; import { workbench } from "./workbench"; import "./dialog.scss"; +/** + * Describes the type of dialog to show. + */ export enum DialogType { NewFolder, Save, @@ -68,8 +71,12 @@ interface DialogEntry { readonly isDirectory: boolean; readonly size: number; readonly lastModified: string; + readonly isDisabled?: boolean; } +/** + * Open and save dialogs. + */ class Dialog { private _path: string | undefined; @@ -265,8 +272,7 @@ class Dialog { } if (element.isDirectory) { this.path = element.fullPath; - } else { - // Open + } else if (!(this.options as OpenDialogOptions).properties.openDirectory) { this.selectEmitter.emit(element.fullPath); } }); @@ -282,12 +288,18 @@ class Dialog { }); buttonsNode.appendChild(cancelBtn); const confirmBtn = document.createElement("button"); - confirmBtn.innerText = "Confirm"; + const openFile = (this.options as OpenDialogOptions).properties.openFile; + confirmBtn.innerText = openFile ? "Open" : "Confirm"; confirmBtn.addEventListener("click", () => { - if (this._path) { + if (this._path && !openFile) { this.selectEmitter.emit(this._path); } }); + // Since a single click opens a file, the only time this button can be + // used is on a directory, which is invalid for opening files. + if (openFile) { + confirmBtn.disabled = true; + } buttonsNode.appendChild(confirmBtn); this.root.appendChild(buttonsNode); this.entryList.layout(); @@ -303,6 +315,9 @@ class Dialog { return this.errorEmitter.event; } + /** + * Remove the dialog. + */ public dispose(): void { this.selectEmitter.dispose(); this.errorEmitter.dispose(); @@ -310,6 +325,9 @@ class Dialog { this.background.remove(); } + /** + * Build and insert the path shown at the top of the dialog. + */ private buildPath(): void { while (this.pathNode.lastChild) { this.pathNode.removeChild(this.pathNode.lastChild); @@ -376,6 +394,9 @@ class Dialog { return (this.entryList).typeFilterController.filter._pattern; } + /** + * List the files and return dialog entries. + */ private async list(directory: string): Promise> { const paths = (await util.promisify(fs.readdir)(directory)).sort(); const stats = await Promise.all(paths.map(p => util.promisify(fs.stat)(path.join(directory, p)))); @@ -386,6 +407,8 @@ class Dialog { isDirectory: stat.isDirectory(), lastModified: stat.mtime.toDateString(), size: stat.size, + // If we are opening a directory, show files as disabled. + isDisabled: !stat.isDirectory() && (this.options as OpenDialogOptions).properties.openDirectory, })); } } @@ -397,11 +420,17 @@ interface DialogEntryData { label: HighlightedLabel; } +/** + * Rendering for the different parts of a dialog entry. + */ class DialogEntryRenderer implements ITreeRenderer { public get templateId(): string { return "dialog-entry"; } + /** + * Append and return containers for each part of the dialog entry. + */ public renderTemplate(container: HTMLElement): DialogEntryData { addClass(container, "dialog-entry"); addClass(container, "dialog-grid"); @@ -422,6 +451,9 @@ class DialogEntryRenderer implements ITreeRenderer, _index: number, templateData: DialogEntryData): void { templateData.icon.className = "dialog-entry-icon monaco-icon-label"; const classes = getIconClasses( @@ -444,8 +476,19 @@ class DialogEntryRenderer implements ITreeRenderer { + public async pickFileFolderAndOpen(options: INativeOpenDialogOptions): Promise { showOpenDialog({ - ...(_options.dialogOptions || {}), + ...(options.dialogOptions || {}), properties: { openFile: true, openDirectory: true, @@ -66,9 +66,9 @@ export class WindowsService implements IWindowsService { }); } - public async pickFileAndOpen(_options: INativeOpenDialogOptions): Promise { + public async pickFileAndOpen(options: INativeOpenDialogOptions): Promise { showOpenDialog({ - ...(_options.dialogOptions || {}), + ...(options.dialogOptions || {}), properties: { openFile: true, }, @@ -84,9 +84,15 @@ export class WindowsService implements IWindowsService { }); } - public async pickFolderAndOpen(_options: INativeOpenDialogOptions): Promise { + public async pickFolderAndOpen(options: INativeOpenDialogOptions): Promise { + if (!options.dialogOptions) { + options.dialogOptions = {}; + } + if (!options.dialogOptions.title) { + options.dialogOptions.title = "Open Folder"; + } showOpenDialog({ - ...(_options.dialogOptions || {}), + ...(options.dialogOptions || {}), properties: { openDirectory: true, }, @@ -97,9 +103,9 @@ export class WindowsService implements IWindowsService { }); } - public async pickWorkspaceAndOpen(_options: INativeOpenDialogOptions): Promise { + public async pickWorkspaceAndOpen(options: INativeOpenDialogOptions): Promise { showOpenDialog({ - ...(_options.dialogOptions || {}), + ...(options.dialogOptions || {}), properties: { openDirectory: true, }, diff --git a/scripts/vstar.sh b/scripts/vstar.sh index 83588063..c7be0b71 100755 --- a/scripts/vstar.sh +++ b/scripts/vstar.sh @@ -4,7 +4,7 @@ set -euxo pipefail # Builds a tarfile containing vscode sourcefiles neccessary for CI. # Done outside the CI and uploaded to object storage to reduce CI time. -branch=1.33.0 +branch=1.33.1 dir=/tmp/vstar outfile=/tmp/vstar-$branch.tar.gz rm -rf $dir diff --git a/scripts/webpack.general.config.js b/scripts/webpack.general.config.js index 4da6ca88..17a056ba 100644 --- a/scripts/webpack.general.config.js +++ b/scripts/webpack.general.config.js @@ -21,7 +21,7 @@ module.exports = (options = {}) => ({ // they are parsed as URIs and will throw errors if not fully formed. // The !! prefix causes it to ignore other loaders (doesn't work). search: "require\\.toUrl\\(", - replace: "location.protocol + '//' + location.host + '/' + require('!!file-loader?name=[path][name].[ext]!' + ", + replace: "location.protocol + '//' + location.host + location.pathname.replace(/\\/$/,'') + '/' + require('!!file-loader?name=[path][name].[ext]!' + ", flags: "g", }, { search: "require\\.__\\$__nodeRequire",