mirror of
https://github.com/coder/code-server.git
synced 2024-12-04 23:03:06 +08:00
4a250be79a
This should eliminate potential noise in the diffs for the patch since different versions seem to default to different hash lengths.
3447 lines
142 KiB
Diff
3447 lines
142 KiB
Diff
diff --git a/.gitignore b/.gitignore
|
|
index 0fe46b6eadc4ccc819fbf342ee1071bb657792b3..e545e004cef31fa5f40ba8df6a2317ea5b69ddb5 100644
|
|
--- a/.gitignore
|
|
+++ b/.gitignore
|
|
@@ -25,7 +25,6 @@ out-vscode-reh-web-pkg/
|
|
out-vscode-web/
|
|
out-vscode-web-min/
|
|
out-vscode-web-pkg/
|
|
-src/vs/server
|
|
resources/server
|
|
build/node_modules
|
|
coverage/
|
|
diff --git a/.yarnrc b/.yarnrc
|
|
deleted file mode 100644
|
|
index 135e10442a7e5184cf8c47615322bb7d622855d9..0000000000000000000000000000000000000000
|
|
--- a/.yarnrc
|
|
+++ /dev/null
|
|
@@ -1,3 +0,0 @@
|
|
-disturl "https://atom.io/download/electron"
|
|
-target "7.3.2"
|
|
-runtime "electron"
|
|
diff --git a/build/gulpfile.reh.js b/build/gulpfile.reh.js
|
|
index f2ea1bd37010b1eb8a43ce9beaae4a88810f6e2d..3f660f9981921ec465d2b8809a1a5ea5663f4c1f 100644
|
|
--- a/build/gulpfile.reh.js
|
|
+++ b/build/gulpfile.reh.js
|
|
@@ -52,6 +52,7 @@ gulp.task('vscode-reh-web-linux-x64-min', noop);
|
|
gulp.task('vscode-reh-web-linux-alpine-min', noop);
|
|
|
|
function getNodeVersion() {
|
|
+ return process.versions.node;
|
|
const yarnrc = fs.readFileSync(path.join(REPO_ROOT, 'remote', '.yarnrc'), 'utf8');
|
|
const target = /^target "(.*)"$/m.exec(yarnrc)[1];
|
|
return target;
|
|
diff --git a/build/lib/node.js b/build/lib/node.js
|
|
index 403ae3d9657f823019542e739fc39292db20e4fe..738ee8cee0e79aa239af10e1abefc9e836b8ce33 100644
|
|
--- a/build/lib/node.js
|
|
+++ b/build/lib/node.js
|
|
@@ -5,11 +5,8 @@
|
|
*--------------------------------------------------------------------------------------------*/
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
const path = require("path");
|
|
-const fs = require("fs");
|
|
const root = path.dirname(path.dirname(__dirname));
|
|
-const yarnrcPath = path.join(root, 'remote', '.yarnrc');
|
|
-const yarnrc = fs.readFileSync(yarnrcPath, 'utf8');
|
|
-const version = /^target\s+"([^"]+)"$/m.exec(yarnrc)[1];
|
|
+const version = process.versions.node;
|
|
const node = process.platform === 'win32' ? 'node.exe' : 'node';
|
|
const nodePath = path.join(root, '.build', 'node', `v${version}`, `${process.platform}-${process.arch}`, node);
|
|
console.log(nodePath);
|
|
diff --git a/build/lib/node.ts b/build/lib/node.ts
|
|
index 64397034461b1661f82007c141cbf4c039a3b722..c53dccf4dc0a99122ed96cf10c2eb632bb25059e 100644
|
|
--- a/build/lib/node.ts
|
|
+++ b/build/lib/node.ts
|
|
@@ -4,13 +4,10 @@
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import * as path from 'path';
|
|
-import * as fs from 'fs';
|
|
|
|
const root = path.dirname(path.dirname(__dirname));
|
|
-const yarnrcPath = path.join(root, 'remote', '.yarnrc');
|
|
-const yarnrc = fs.readFileSync(yarnrcPath, 'utf8');
|
|
-const version = /^target\s+"([^"]+)"$/m.exec(yarnrc)![1];
|
|
+const version = process.versions.node;
|
|
const node = process.platform === 'win32' ? 'node.exe' : 'node';
|
|
const nodePath = path.join(root, '.build', 'node', `v${version}`, `${process.platform}-${process.arch}`, node);
|
|
|
|
-console.log(nodePath);
|
|
\ No newline at end of file
|
|
+console.log(nodePath);
|
|
diff --git a/build/lib/util.js b/build/lib/util.js
|
|
index e552a036f89bd581644459fd5c27fe4ae1379f62..169e8614b9f6a2bd68446144ab7e1ce5c6d49b64 100644
|
|
--- a/build/lib/util.js
|
|
+++ b/build/lib/util.js
|
|
@@ -257,6 +257,7 @@ function streamToPromise(stream) {
|
|
}
|
|
exports.streamToPromise = streamToPromise;
|
|
function getElectronVersion() {
|
|
+ return process.versions.node;
|
|
const yarnrc = fs.readFileSync(path.join(root, '.yarnrc'), 'utf8');
|
|
const target = /^target "(.*)"$/m.exec(yarnrc)[1];
|
|
return target;
|
|
diff --git a/build/lib/util.ts b/build/lib/util.ts
|
|
index 035c7e95ea3006bb3dabd68bbf54db80de4aaaf2..4ff8dcfe6b21a0ec8064ebc7bb05506b8f1faa91 100644
|
|
--- a/build/lib/util.ts
|
|
+++ b/build/lib/util.ts
|
|
@@ -322,6 +322,7 @@ export function streamToPromise(stream: NodeJS.ReadWriteStream): Promise<void> {
|
|
}
|
|
|
|
export function getElectronVersion(): string {
|
|
+ return process.versions.node;
|
|
const yarnrc = fs.readFileSync(path.join(root, '.yarnrc'), 'utf8');
|
|
const target = /^target "(.*)"$/m.exec(yarnrc)![1];
|
|
return target;
|
|
diff --git a/build/npm/postinstall.js b/build/npm/postinstall.js
|
|
index 8f8b0019a7792a993fbd6bf95b013b596aa2935a..ea054c725bea2eec342e12b07314241aa18a4951 100644
|
|
--- a/build/npm/postinstall.js
|
|
+++ b/build/npm/postinstall.js
|
|
@@ -33,10 +33,11 @@ function yarnInstall(location, opts) {
|
|
|
|
yarnInstall('extensions'); // node modules shared by all extensions
|
|
|
|
-if (!(process.platform === 'win32' && (process.arch === 'arm64' || process.env['npm_config_arch'] === 'arm64'))) {
|
|
- yarnInstall('remote'); // node modules used by vscode server
|
|
- yarnInstall('remote/web'); // node modules used by vscode web
|
|
-}
|
|
+// NOTE@coder: Skip these dependencies since we don't use them.
|
|
+// if (!(process.platform === 'win32' && (process.arch === 'arm64' || process.env['npm_config_arch'] === 'arm64'))) {
|
|
+// yarnInstall('remote'); // node modules used by vscode server
|
|
+// yarnInstall('remote/web'); // node modules used by vscode web
|
|
+// }
|
|
|
|
const allExtensionFolders = fs.readdirSync('extensions');
|
|
const extensions = allExtensionFolders.filter(e => {
|
|
@@ -69,9 +70,9 @@ runtime "${runtime}"`;
|
|
}
|
|
|
|
yarnInstall(`build`); // node modules required for build
|
|
-yarnInstall('test/automation'); // node modules required for smoketest
|
|
-yarnInstall('test/smoke'); // node modules required for smoketest
|
|
-yarnInstall('test/integration/browser'); // node modules required for integration
|
|
+// yarnInstall('test/automation'); // node modules required for smoketest
|
|
+// yarnInstall('test/smoke'); // node modules required for smoketest
|
|
+// yarnInstall('test/integration/browser'); // node modules required for integration
|
|
yarnInstallBuildDependencies(); // node modules for watching, specific to host node version, not electron
|
|
|
|
cp.execSync('git config pull.rebase true');
|
|
diff --git a/build/npm/preinstall.js b/build/npm/preinstall.js
|
|
index cb88d37adefd4882f61a2711fdd7f72b89e1a6e3..6b3253af0a3a0aa4d75456379ef1c00f4cb98d13 100644
|
|
--- a/build/npm/preinstall.js
|
|
+++ b/build/npm/preinstall.js
|
|
@@ -8,8 +8,9 @@ let err = false;
|
|
const majorNodeVersion = parseInt(/^(\d+)\./.exec(process.versions.node)[1]);
|
|
|
|
if (majorNodeVersion < 10 || majorNodeVersion >= 13) {
|
|
- console.error('\033[1;31m*** Please use node >=10 and <=12.\033[0;0m');
|
|
- err = true;
|
|
+ // We are ok building above Node 12.
|
|
+ // console.error('\033[1;31m*** Please use node >=10 and <=12.\033[0;0m');
|
|
+ // err = true;
|
|
}
|
|
|
|
const cp = require('child_process');
|
|
diff --git a/coder.js b/coder.js
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..9cb693af63b86b4a6b35c442e6ea501a1076d18a
|
|
--- /dev/null
|
|
+++ b/coder.js
|
|
@@ -0,0 +1,63 @@
|
|
+// This must be ran from VS Code's root.
|
|
+const gulp = require("gulp");
|
|
+const path = require("path");
|
|
+const _ = require("underscore");
|
|
+const buildfile = require("./src/buildfile");
|
|
+const common = require("./build/lib/optimize");
|
|
+const util = require("./build/lib/util");
|
|
+const deps = require("./build/dependencies");
|
|
+
|
|
+const vscodeEntryPoints = _.flatten([
|
|
+ buildfile.entrypoint("vs/workbench/workbench.web.api"),
|
|
+ buildfile.entrypoint("vs/server/entry"),
|
|
+ buildfile.base,
|
|
+ buildfile.workbenchWeb,
|
|
+ buildfile.workerExtensionHost,
|
|
+ buildfile.keyboardMaps,
|
|
+ buildfile.entrypoint("vs/platform/files/node/watcher/unix/watcherApp", ["vs/css", "vs/nls"]),
|
|
+ buildfile.entrypoint("vs/platform/files/node/watcher/nsfw/watcherApp", ["vs/css", "vs/nls"]),
|
|
+ buildfile.entrypoint("vs/workbench/services/extensions/node/extensionHostProcess", ["vs/css", "vs/nls"]),
|
|
+]);
|
|
+
|
|
+const vscodeResources = [
|
|
+ "out-build/vs/server/fork.js",
|
|
+ "!out-build/vs/server/doc/**",
|
|
+ "out-build/vs/workbench/services/extensions/worker/extensionHostWorkerMain.js",
|
|
+ "out-build/bootstrap.js",
|
|
+ "out-build/bootstrap-fork.js",
|
|
+ "out-build/bootstrap-amd.js",
|
|
+ 'out-build/bootstrap-node.js',
|
|
+ "out-build/paths.js",
|
|
+ 'out-build/vs/**/*.{svg,png,html,ttf}',
|
|
+ "!out-build/vs/code/browser/workbench/*.html",
|
|
+ '!out-build/vs/code/electron-browser/**',
|
|
+ "out-build/vs/base/common/performance.js",
|
|
+ "out-build/vs/base/node/languagePacks.js",
|
|
+ 'out-build/vs/base/browser/ui/codicons/codicon/**',
|
|
+ "out-build/vs/workbench/browser/media/*-theme.css",
|
|
+ "out-build/vs/workbench/contrib/debug/**/*.json",
|
|
+ "out-build/vs/workbench/contrib/externalTerminal/**/*.scpt",
|
|
+ "out-build/vs/workbench/contrib/webview/browser/pre/*.js",
|
|
+ "out-build/vs/**/markdown.css",
|
|
+ "out-build/vs/workbench/contrib/tasks/**/*.json",
|
|
+ "out-build/vs/platform/files/**/*.md",
|
|
+ "!**/test/**"
|
|
+];
|
|
+
|
|
+gulp.task("optimize", gulp.series(
|
|
+ util.rimraf("out-vscode"),
|
|
+ common.optimizeTask({
|
|
+ src: "out-build",
|
|
+ entryPoints: vscodeEntryPoints,
|
|
+ resources: vscodeResources,
|
|
+ loaderConfig: common.loaderConfig(),
|
|
+ out: "out-vscode",
|
|
+ inlineAmdImages: true,
|
|
+ bundleInfo: undefined
|
|
+ }),
|
|
+));
|
|
+
|
|
+gulp.task("minify", gulp.series(
|
|
+ util.rimraf("out-vscode-min"),
|
|
+ common.minifyTask("out-vscode")
|
|
+));
|
|
diff --git a/extensions/postinstall.js b/extensions/postinstall.js
|
|
index da4fa3e9d0443d679dfbab1000b434af2ae01afd..50f3e1144f8057883dea8b91ec2f7073458dbd94 100644
|
|
--- a/extensions/postinstall.js
|
|
+++ b/extensions/postinstall.js
|
|
@@ -24,6 +24,9 @@ function processRoot() {
|
|
rimraf.sync(filePath);
|
|
}
|
|
}
|
|
+
|
|
+ // Delete .bin so it doesn't contain broken symlinks that trip up nfpm.
|
|
+ rimraf.sync(path.join(__dirname, 'node_modules', '.bin'));
|
|
}
|
|
|
|
function processLib() {
|
|
diff --git a/package.json b/package.json
|
|
index 226f51a1ec55fc31ae90e175bc1ab62d74eb6294..5c4e5af5f69c0fd994f4b54cb7a9dfb20f868bfa 100644
|
|
--- a/package.json
|
|
+++ b/package.json
|
|
@@ -45,7 +45,11 @@
|
|
"watch-web": "gulp watch-web --max_old_space_size=4095",
|
|
"eslint": "eslint -c .eslintrc.json --rulesdir ./build/lib/eslint --ext .ts --ext .js ./src/vs ./extensions"
|
|
},
|
|
+ "dependencies_comment": "Move rimraf to dependencies because it is used in the postinstall script.",
|
|
"dependencies": {
|
|
+ "@coder/logger": "^1.1.12",
|
|
+ "@coder/node-browser": "^1.0.8",
|
|
+ "@coder/requirefs": "^1.1.5",
|
|
"applicationinsights": "1.0.8",
|
|
"chokidar": "3.2.3",
|
|
"graceful-fs": "4.2.3",
|
|
@@ -59,6 +63,7 @@
|
|
"native-keymap": "2.1.2",
|
|
"native-watchdog": "1.3.0",
|
|
"node-pty": "0.10.0-beta8",
|
|
+ "rimraf": "^2.2.8",
|
|
"semver-umd": "^5.5.7",
|
|
"spdlog": "^0.11.1",
|
|
"sudo-prompt": "9.1.1",
|
|
@@ -159,7 +164,6 @@
|
|
"pump": "^1.0.1",
|
|
"queue": "3.0.6",
|
|
"rcedit": "^1.1.0",
|
|
- "rimraf": "^2.2.8",
|
|
"sinon": "^1.17.2",
|
|
"source-map": "^0.4.4",
|
|
"style-loader": "^1.0.0",
|
|
@@ -190,5 +194,8 @@
|
|
"windows-foreground-love": "0.2.0",
|
|
"windows-mutex": "0.3.0",
|
|
"windows-process-tree": "0.2.4"
|
|
+ },
|
|
+ "resolutions": {
|
|
+ "minimist": "^1.2.5"
|
|
}
|
|
}
|
|
diff --git a/product.json b/product.json
|
|
index 2b884d18f301b86bf29e3a8f1343cfb651f8727c..518b935b837dd21251089bdd317d6c3c03756d7b 100644
|
|
--- a/product.json
|
|
+++ b/product.json
|
|
@@ -20,7 +20,7 @@
|
|
"darwinBundleIdentifier": "com.visualstudio.code.oss",
|
|
"linuxIconName": "com.visualstudio.code.oss",
|
|
"licenseFileName": "LICENSE.txt",
|
|
- "reportIssueUrl": "https://github.com/Microsoft/vscode/issues/new",
|
|
+ "reportIssueUrl": "https://github.com/cdr/code-server/issues/new",
|
|
"urlProtocol": "code-oss",
|
|
"extensionAllowedProposedApi": [
|
|
"ms-vscode.vscode-js-profile-flame",
|
|
diff --git a/remote/.yarnrc b/remote/.yarnrc
|
|
deleted file mode 100644
|
|
index 1e16cde724c7703d2836b3641de48c99f7f47e68..0000000000000000000000000000000000000000
|
|
--- a/remote/.yarnrc
|
|
+++ /dev/null
|
|
@@ -1,3 +0,0 @@
|
|
-disturl "http://nodejs.org/dist"
|
|
-target "12.4.0"
|
|
-runtime "node"
|
|
diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts
|
|
index 1286c5117a4cae9d6075ed36f32f6414897d705b..e60dd11d03992800853e76d4d68b8ff211da7627 100644
|
|
--- a/src/vs/base/common/network.ts
|
|
+++ b/src/vs/base/common/network.ts
|
|
@@ -111,16 +111,17 @@ class RemoteAuthoritiesImpl {
|
|
if (host && host.indexOf(':') !== -1) {
|
|
host = `[${host}]`;
|
|
}
|
|
- const port = this._ports[authority];
|
|
+ // const port = this._ports[authority];
|
|
const connectionToken = this._connectionTokens[authority];
|
|
let query = `path=${encodeURIComponent(uri.path)}`;
|
|
if (typeof connectionToken === 'string') {
|
|
query += `&tkn=${encodeURIComponent(connectionToken)}`;
|
|
}
|
|
+ // NOTE@coder: Changed this to work against the current path.
|
|
return URI.from({
|
|
scheme: platform.isWeb ? this._preferredWebSchema : Schemas.vscodeRemoteResource,
|
|
- authority: `${host}:${port}`,
|
|
- path: `/vscode-remote-resource`,
|
|
+ authority: window.location.host,
|
|
+ path: `${window.location.pathname.replace(/\/+$/, '')}/vscode-remote-resource`,
|
|
query
|
|
});
|
|
}
|
|
diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts
|
|
index 0bbc5d6ef911b1e98d26ad796873a9b6b7fb04ec..61f139b9c557b9c46e5a9640ab0e37a6fb7692ee 100644
|
|
--- a/src/vs/base/common/platform.ts
|
|
+++ b/src/vs/base/common/platform.ts
|
|
@@ -59,6 +59,17 @@ if (typeof navigator === 'object' && !isElectronRenderer) {
|
|
_isWeb = true;
|
|
_locale = navigator.language;
|
|
_language = _locale;
|
|
+ // NOTE@coder: Make languages work.
|
|
+ const el = typeof document !== 'undefined' && document.getElementById('vscode-remote-nls-configuration');
|
|
+ const rawNlsConfig = el && el.getAttribute('data-settings');
|
|
+ if (rawNlsConfig) {
|
|
+ try {
|
|
+ const nlsConfig: NLSConfig = JSON.parse(rawNlsConfig);
|
|
+ _locale = nlsConfig.locale;
|
|
+ _translationsConfigFile = nlsConfig._translationsConfigFile;
|
|
+ _language = nlsConfig.availableLanguages['*'] || LANGUAGE_DEFAULT;
|
|
+ } catch (error) { /* Oh well. */ }
|
|
+ }
|
|
} else if (typeof process === 'object') {
|
|
_isWindows = (process.platform === 'win32');
|
|
_isMacintosh = (process.platform === 'darwin');
|
|
diff --git a/src/vs/base/common/processes.ts b/src/vs/base/common/processes.ts
|
|
index c52f7b3774f399d3fa161682316b20d807072806..08a87fa970f159f84691c5068cf5e38f0926015c 100644
|
|
--- a/src/vs/base/common/processes.ts
|
|
+++ b/src/vs/base/common/processes.ts
|
|
@@ -110,7 +110,8 @@ export function sanitizeProcessEnvironment(env: IProcessEnvironment, ...preserve
|
|
/^ELECTRON_.+$/,
|
|
/^GOOGLE_API_KEY$/,
|
|
/^VSCODE_.+$/,
|
|
- /^SNAP(|_.*)$/
|
|
+ /^SNAP(|_.*)$/,
|
|
+ /^CODE_SERVER_.+$/,
|
|
];
|
|
const envKeys = Object.keys(env);
|
|
envKeys
|
|
diff --git a/src/vs/base/common/uriIpc.ts b/src/vs/base/common/uriIpc.ts
|
|
index ef2291d49b13c9c995afc90eab9c92afabc2b3b4..29b2f9dfc2b7fa998ac1188db06dee95419fcd5b 100644
|
|
--- a/src/vs/base/common/uriIpc.ts
|
|
+++ b/src/vs/base/common/uriIpc.ts
|
|
@@ -5,6 +5,7 @@
|
|
|
|
import { URI, UriComponents } from 'vs/base/common/uri';
|
|
import { MarshalledObject } from 'vs/base/common/marshalling';
|
|
+import { Schemas } from './network';
|
|
|
|
export interface IURITransformer {
|
|
transformIncoming(uri: UriComponents): UriComponents;
|
|
@@ -31,29 +32,35 @@ function toJSON(uri: URI): UriComponents {
|
|
|
|
export class URITransformer implements IURITransformer {
|
|
|
|
- private readonly _uriTransformer: IRawURITransformer;
|
|
-
|
|
- constructor(uriTransformer: IRawURITransformer) {
|
|
- this._uriTransformer = uriTransformer;
|
|
+ constructor(private readonly remoteAuthority: string) {
|
|
}
|
|
|
|
+ // NOTE@coder: Coming in from the browser it'll be vscode-remote so it needs
|
|
+ // to be transformed into file.
|
|
public transformIncoming(uri: UriComponents): UriComponents {
|
|
- const result = this._uriTransformer.transformIncoming(uri);
|
|
- return (result === uri ? uri : toJSON(URI.from(result)));
|
|
+ return uri.scheme === Schemas.vscodeRemote
|
|
+ ? toJSON(URI.file(uri.path))
|
|
+ : uri;
|
|
}
|
|
|
|
+ // NOTE@coder: Going out to the browser it'll be file so it needs to be
|
|
+ // transformed into vscode-remote.
|
|
public transformOutgoing(uri: UriComponents): UriComponents {
|
|
- const result = this._uriTransformer.transformOutgoing(uri);
|
|
- return (result === uri ? uri : toJSON(URI.from(result)));
|
|
+ return uri.scheme === Schemas.file
|
|
+ ? toJSON(URI.from({ authority: this.remoteAuthority, scheme: Schemas.vscodeRemote, path: uri.path }))
|
|
+ : uri;
|
|
}
|
|
|
|
public transformOutgoingURI(uri: URI): URI {
|
|
- const result = this._uriTransformer.transformOutgoing(uri);
|
|
- return (result === uri ? uri : URI.from(result));
|
|
+ return uri.scheme === Schemas.file
|
|
+ ? URI.from({ authority: this.remoteAuthority, scheme: Schemas.vscodeRemote, path:uri.path })
|
|
+ : uri;
|
|
}
|
|
|
|
public transformOutgoingScheme(scheme: string): string {
|
|
- return this._uriTransformer.transformOutgoingScheme(scheme);
|
|
+ return scheme === Schemas.file
|
|
+ ? Schemas.vscodeRemote
|
|
+ : scheme;
|
|
}
|
|
}
|
|
|
|
@@ -152,4 +159,4 @@ export function transformAndReviveIncomingURIs<T>(obj: T, transformer: IURITrans
|
|
return obj;
|
|
}
|
|
return result;
|
|
-}
|
|
\ No newline at end of file
|
|
+}
|
|
diff --git a/src/vs/base/node/languagePacks.js b/src/vs/base/node/languagePacks.js
|
|
index 2c64061da7b01aef0bfe3cec851da232ca9461c8..c0ef8faedd406c38bf9c55bbbdbbb060046492d9 100644
|
|
--- a/src/vs/base/node/languagePacks.js
|
|
+++ b/src/vs/base/node/languagePacks.js
|
|
@@ -128,7 +128,10 @@ function factory(nodeRequire, path, fs, perf) {
|
|
function getLanguagePackConfigurations(userDataPath) {
|
|
const configFile = path.join(userDataPath, 'languagepacks.json');
|
|
try {
|
|
- return nodeRequire(configFile);
|
|
+ // NOTE@coder: Swapped require with readFile since require is cached and
|
|
+ // we don't restart the server-side portion of code-server when the
|
|
+ // language changes.
|
|
+ return JSON.parse(fs.readFileSync(configFile, "utf8"));
|
|
} catch (err) {
|
|
// Do nothing. If we can't read the file we have no
|
|
// language pack config.
|
|
diff --git a/src/vs/code/browser/workbench/workbench.ts b/src/vs/code/browser/workbench/workbench.ts
|
|
index c629f7fffa1faad78e5d9907fd38aec289db0428..c266e1fb06ffc44f1ec4230ac59fd094a53b257b 100644
|
|
--- a/src/vs/code/browser/workbench/workbench.ts
|
|
+++ b/src/vs/code/browser/workbench/workbench.ts
|
|
@@ -13,6 +13,8 @@ import { isFolderToOpen, isWorkspaceToOpen } from 'vs/platform/windows/common/wi
|
|
import { isEqual } from 'vs/base/common/resources';
|
|
import { isStandalone } from 'vs/base/browser/browser';
|
|
import { localize } from 'vs/nls';
|
|
+import { Schemas } from 'vs/base/common/network';
|
|
+import { encodePath } from 'vs/server/node/util';
|
|
|
|
interface ICredential {
|
|
service: string;
|
|
@@ -243,12 +245,18 @@ class WorkspaceProvider implements IWorkspaceProvider {
|
|
|
|
// Folder
|
|
else if (isFolderToOpen(workspace)) {
|
|
- targetHref = `${document.location.origin}${document.location.pathname}?${WorkspaceProvider.QUERY_PARAM_FOLDER}=${encodeURIComponent(workspace.folderUri.toString())}`;
|
|
+ const target = workspace.folderUri.scheme === Schemas.vscodeRemote
|
|
+ ? encodePath(workspace.folderUri.path)
|
|
+ : encodeURIComponent(workspace.folderUri.toString());
|
|
+ targetHref = `${document.location.origin}${document.location.pathname}?${WorkspaceProvider.QUERY_PARAM_FOLDER}=${target}`;
|
|
}
|
|
|
|
// Workspace
|
|
else if (isWorkspaceToOpen(workspace)) {
|
|
- targetHref = `${document.location.origin}${document.location.pathname}?${WorkspaceProvider.QUERY_PARAM_WORKSPACE}=${encodeURIComponent(workspace.workspaceUri.toString())}`;
|
|
+ const target = workspace.workspaceUri.scheme === Schemas.vscodeRemote
|
|
+ ? encodePath(workspace.workspaceUri.path)
|
|
+ : encodeURIComponent(workspace.workspaceUri.toString());
|
|
+ targetHref = `${document.location.origin}${document.location.pathname}?${WorkspaceProvider.QUERY_PARAM_WORKSPACE}=${target}`;
|
|
}
|
|
|
|
// Append payload if any
|
|
@@ -285,7 +293,22 @@ class WorkspaceProvider implements IWorkspaceProvider {
|
|
throw new Error('Missing web configuration element');
|
|
}
|
|
|
|
- const config: IWorkbenchConstructionOptions & { folderUri?: UriComponents, workspaceUri?: UriComponents } = JSON.parse(configElementAttribute);
|
|
+ const config: IWorkbenchConstructionOptions & { folderUri?: UriComponents, workspaceUri?: UriComponents } = {
|
|
+ webviewEndpoint: `${window.location.origin}${window.location.pathname.replace(/\/+$/, '')}/webview/`,
|
|
+ ...JSON.parse(configElementAttribute),
|
|
+ };
|
|
+
|
|
+ // Strip the protocol from the authority if it exists.
|
|
+ const normalizeAuthority = (authority: string): string => authority.replace(/^https?:\/\//, "");
|
|
+ if (config.remoteAuthority) {
|
|
+ (config as any).remoteAuthority = normalizeAuthority(config.remoteAuthority);
|
|
+ }
|
|
+ if (config.workspaceUri && config.workspaceUri.authority) {
|
|
+ config.workspaceUri.authority = normalizeAuthority(config.workspaceUri.authority);
|
|
+ }
|
|
+ if (config.folderUri && config.folderUri.authority) {
|
|
+ config.folderUri.authority = normalizeAuthority(config.folderUri.authority);
|
|
+ }
|
|
|
|
// Revive static extension locations
|
|
if (Array.isArray(config.staticExtensions)) {
|
|
@@ -297,40 +320,7 @@ class WorkspaceProvider implements IWorkspaceProvider {
|
|
// Find workspace to open and payload
|
|
let foundWorkspace = false;
|
|
let workspace: IWorkspace;
|
|
- let payload = Object.create(null);
|
|
-
|
|
- const query = new URL(document.location.href).searchParams;
|
|
- query.forEach((value, key) => {
|
|
- switch (key) {
|
|
-
|
|
- // Folder
|
|
- case WorkspaceProvider.QUERY_PARAM_FOLDER:
|
|
- workspace = { folderUri: URI.parse(value) };
|
|
- foundWorkspace = true;
|
|
- break;
|
|
-
|
|
- // Workspace
|
|
- case WorkspaceProvider.QUERY_PARAM_WORKSPACE:
|
|
- workspace = { workspaceUri: URI.parse(value) };
|
|
- foundWorkspace = true;
|
|
- break;
|
|
-
|
|
- // Empty
|
|
- case WorkspaceProvider.QUERY_PARAM_EMPTY_WINDOW:
|
|
- workspace = undefined;
|
|
- foundWorkspace = true;
|
|
- break;
|
|
-
|
|
- // Payload
|
|
- case WorkspaceProvider.QUERY_PARAM_PAYLOAD:
|
|
- try {
|
|
- payload = JSON.parse(value);
|
|
- } catch (error) {
|
|
- console.error(error); // possible invalid JSON
|
|
- }
|
|
- break;
|
|
- }
|
|
- });
|
|
+ let payload = config.workspaceProvider?.payload || Object.create(null);
|
|
|
|
// If no workspace is provided through the URL, check for config attribute from server
|
|
if (!foundWorkspace) {
|
|
diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts
|
|
index 2379b626c81321afe18267c69c6903efbfa354f4..28f8971cf398b048c7d8f56df2e9dc4e32aadb6d 100644
|
|
--- a/src/vs/platform/environment/node/argv.ts
|
|
+++ b/src/vs/platform/environment/node/argv.ts
|
|
@@ -8,6 +8,8 @@ import { localize } from 'vs/nls';
|
|
import { isWindows } from 'vs/base/common/platform';
|
|
|
|
export interface ParsedArgs {
|
|
+ 'extra-extensions-dir'?: string[];
|
|
+ 'extra-builtin-extensions-dir'?: string[];
|
|
_: string[];
|
|
'folder-uri'?: string[]; // undefined or array of 1 or more
|
|
'file-uri'?: string[]; // undefined or array of 1 or more
|
|
@@ -141,6 +143,8 @@ export const OPTIONS: OptionDescriptions<Required<ParsedArgs>> = {
|
|
'extensions-dir': { type: 'string', deprecates: 'extensionHomePath', cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") },
|
|
'extensions-download-dir': { type: 'string' },
|
|
'builtin-extensions-dir': { type: 'string' },
|
|
+ 'extra-builtin-extensions-dir': { type: 'string[]', cat: 'o', description: 'Path to an extra builtin extension directory.' },
|
|
+ 'extra-extensions-dir': { type: 'string[]', cat: 'o', description: 'Path to an extra user extension directory.' },
|
|
'list-extensions': { type: 'boolean', cat: 'e', description: localize('listExtensions', "List the installed extensions.") },
|
|
'show-versions': { type: 'boolean', cat: 'e', description: localize('showVersions', "Show versions of installed extensions, when using --list-extension.") },
|
|
'category': { type: 'string', cat: 'e', description: localize('category', "Filters installed extensions by provided category, when using --list-extension.") },
|
|
@@ -403,4 +407,3 @@ export function buildHelpMessage(productName: string, executableName: string, ve
|
|
export function buildVersionMessage(version: string | undefined, commit: string | undefined): string {
|
|
return `${version || localize('unknownVersion', "Unknown version")}\n${commit || localize('unknownCommit', "Unknown commit")}\n${process.arch}`;
|
|
}
|
|
-
|
|
diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts
|
|
index 5c0dc4ad4ae79a172bed4bc3d6440cdf6dd22386..38b8c7573a872d587c5f3f6c5e0521d2bd918daa 100644
|
|
--- a/src/vs/platform/environment/node/environmentService.ts
|
|
+++ b/src/vs/platform/environment/node/environmentService.ts
|
|
@@ -38,6 +38,8 @@ export interface INativeEnvironmentService extends IEnvironmentService {
|
|
extensionsPath?: string;
|
|
extensionsDownloadPath: string;
|
|
builtinExtensionsPath: string;
|
|
+ extraExtensionPaths: string[];
|
|
+ extraBuiltinExtensionPaths: string[];
|
|
|
|
driverHandle?: string;
|
|
driverVerbose: boolean;
|
|
@@ -180,6 +182,13 @@ export class EnvironmentService implements INativeEnvironmentService {
|
|
return resources.joinPath(this.userHome, product.dataFolderName, 'extensions').fsPath;
|
|
}
|
|
|
|
+ @memoize get extraExtensionPaths(): string[] {
|
|
+ return (this._args['extra-extensions-dir'] || []).map((p) => <string>parsePathArg(p, process));
|
|
+ }
|
|
+ @memoize get extraBuiltinExtensionPaths(): string[] {
|
|
+ return (this._args['extra-builtin-extensions-dir'] || []).map((p) => <string>parsePathArg(p, process));
|
|
+ }
|
|
+
|
|
@memoize
|
|
get extensionDevelopmentLocationURI(): URI[] | undefined {
|
|
const s = this._args.extensionDevelopmentPath;
|
|
diff --git a/src/vs/platform/extensionManagement/node/extensionsScanner.ts b/src/vs/platform/extensionManagement/node/extensionsScanner.ts
|
|
index 575b2aafc3802cd6f5f943930e30de9f2c2690de..873181f967856759e3dc001e5bbe06e2f9eb676a 100644
|
|
--- a/src/vs/platform/extensionManagement/node/extensionsScanner.ts
|
|
+++ b/src/vs/platform/extensionManagement/node/extensionsScanner.ts
|
|
@@ -85,7 +85,7 @@ export class ExtensionsScanner extends Disposable {
|
|
}
|
|
|
|
async scanAllUserExtensions(): Promise<ILocalExtension[]> {
|
|
- return this.scanExtensionsInDir(this.extensionsPath, ExtensionType.User);
|
|
+ return this.scanExtensionsInDirs(this.extensionsPath, this.environmentService.extraExtensionPaths, ExtensionType.User);
|
|
}
|
|
|
|
async extractUserExtension(identifierWithVersion: ExtensionIdentifierWithVersion, zipPath: string, token: CancellationToken): Promise<ILocalExtension> {
|
|
@@ -211,7 +211,13 @@ export class ExtensionsScanner extends Disposable {
|
|
|
|
private async scanExtensionsInDir(dir: string, type: ExtensionType): Promise<ILocalExtension[]> {
|
|
const limiter = new Limiter<any>(10);
|
|
- const extensionsFolders = await pfs.readdir(dir);
|
|
+ const extensionsFolders = await pfs.readdir(dir)
|
|
+ .catch((error) => {
|
|
+ if (error.code !== 'ENOENT') {
|
|
+ throw error;
|
|
+ }
|
|
+ return <string[]>[];
|
|
+ });
|
|
const extensions = await Promise.all<ILocalExtension>(extensionsFolders.map(extensionFolder => limiter.queue(() => this.scanExtension(extensionFolder, dir, type))));
|
|
return extensions.filter(e => e && e.identifier);
|
|
}
|
|
@@ -241,7 +247,7 @@ export class ExtensionsScanner extends Disposable {
|
|
}
|
|
|
|
private async scanDefaultSystemExtensions(): Promise<ILocalExtension[]> {
|
|
- const result = await this.scanExtensionsInDir(this.systemExtensionsPath, ExtensionType.System);
|
|
+ const result = await this.scanExtensionsInDirs(this.systemExtensionsPath, this.environmentService.extraBuiltinExtensionPaths, ExtensionType.System);
|
|
this.logService.trace('Scanned system extensions:', result.length);
|
|
return result;
|
|
}
|
|
@@ -345,4 +351,9 @@ export class ExtensionsScanner extends Disposable {
|
|
}
|
|
});
|
|
}
|
|
+
|
|
+ private async scanExtensionsInDirs(dir: string, dirs: string[], type: ExtensionType): Promise<ILocalExtension[]>{
|
|
+ const results = await Promise.all([dir, ...dirs].map((path) => this.scanExtensionsInDir(path, type)));
|
|
+ return results.reduce((flat, current) => flat.concat(current), []);
|
|
+ }
|
|
}
|
|
diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts
|
|
index 3370a608b4b54c238a6ea69a92cdfcd4817cab57..37b3592d39d8ee3aed5455d599612485ea621323 100644
|
|
--- a/src/vs/platform/product/common/product.ts
|
|
+++ b/src/vs/platform/product/common/product.ts
|
|
@@ -30,6 +30,12 @@ if (isWeb) {
|
|
],
|
|
});
|
|
}
|
|
+ // NOTE@coder: Add the ability to inject settings from the server.
|
|
+ const el = document.getElementById('vscode-remote-product-configuration');
|
|
+ const rawProductConfiguration = el && el.getAttribute('data-settings');
|
|
+ if (rawProductConfiguration) {
|
|
+ Object.assign(product, JSON.parse(rawProductConfiguration));
|
|
+ }
|
|
}
|
|
|
|
// Node: AMD loader
|
|
diff --git a/src/vs/platform/product/common/productService.ts b/src/vs/platform/product/common/productService.ts
|
|
index 040c869d94ceb278350c1d752f55712feedda379..bf16defcf7bc4229dedbbe9eae8a965e996c69d9 100644
|
|
--- a/src/vs/platform/product/common/productService.ts
|
|
+++ b/src/vs/platform/product/common/productService.ts
|
|
@@ -30,6 +30,8 @@ export type ConfigurationSyncStore = {
|
|
};
|
|
|
|
export interface IProductConfiguration {
|
|
+ readonly codeServerVersion?: string;
|
|
+
|
|
readonly version: string;
|
|
readonly date?: string;
|
|
readonly quality?: string;
|
|
diff --git a/src/vs/platform/remote/browser/browserSocketFactory.ts b/src/vs/platform/remote/browser/browserSocketFactory.ts
|
|
index 3715cbb8e6ee41c3d9b5090918d243b723ae2d00..c65de8ad37e727d66da97a8f8b170cbcef87181b 100644
|
|
--- a/src/vs/platform/remote/browser/browserSocketFactory.ts
|
|
+++ b/src/vs/platform/remote/browser/browserSocketFactory.ts
|
|
@@ -208,7 +208,8 @@ export class BrowserSocketFactory implements ISocketFactory {
|
|
}
|
|
|
|
connect(host: string, port: number, query: string, callback: IConnectCallback): void {
|
|
- const socket = this._webSocketFactory.create(`ws://${host}:${port}/?${query}&skipWebSocketFrames=false`);
|
|
+ // NOTE@coder: Modified to work against the current path.
|
|
+ const socket = this._webSocketFactory.create(`${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.host}${window.location.pathname}?${query}&skipWebSocketFrames=false`);
|
|
const errorListener = socket.onError((err) => callback(err, undefined));
|
|
socket.onOpen(() => {
|
|
errorListener.dispose();
|
|
@@ -216,6 +217,3 @@ export class BrowserSocketFactory implements ISocketFactory {
|
|
});
|
|
}
|
|
}
|
|
-
|
|
-
|
|
-
|
|
diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts
|
|
index 2185bb5228c3f9603f307237e7f146fe386708d8..35463ca6520a7da2308d01a51ef2d3e544f10a10 100644
|
|
--- a/src/vs/platform/remote/common/remoteAgentConnection.ts
|
|
+++ b/src/vs/platform/remote/common/remoteAgentConnection.ts
|
|
@@ -89,7 +89,7 @@ async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptio
|
|
options.socketFactory.connect(
|
|
options.host,
|
|
options.port,
|
|
- `reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`,
|
|
+ `type=${connectionTypeToString(connectionType)}&reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`,
|
|
(err: any, socket: ISocket | undefined) => {
|
|
if (err || !socket) {
|
|
options.logService.error(`${logPrefix} socketFactory.connect() failed. Error:`);
|
|
diff --git a/src/vs/platform/storage/browser/storageService.ts b/src/vs/platform/storage/browser/storageService.ts
|
|
index 59b1baf9120cb0ccf1ebc425ed708224b5513d41..cf9805554b91176ac2521963a7775711e287e4cc 100644
|
|
--- a/src/vs/platform/storage/browser/storageService.ts
|
|
+++ b/src/vs/platform/storage/browser/storageService.ts
|
|
@@ -116,8 +116,8 @@ export class BrowserStorageService extends Disposable implements IStorageService
|
|
return this.getStorage(scope).getNumber(key, fallbackValue);
|
|
}
|
|
|
|
- store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): void {
|
|
- this.getStorage(scope).set(key, value);
|
|
+ store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): Promise<void> {
|
|
+ return this.getStorage(scope).set(key, value);
|
|
}
|
|
|
|
remove(key: string, scope: StorageScope): void {
|
|
diff --git a/src/vs/platform/storage/common/storage.ts b/src/vs/platform/storage/common/storage.ts
|
|
index 1623957cb18eedbf968cca6231d226b587f51935..d366438d54d36a86bd416aeb9f802ad9015f601c 100644
|
|
--- a/src/vs/platform/storage/common/storage.ts
|
|
+++ b/src/vs/platform/storage/common/storage.ts
|
|
@@ -83,7 +83,7 @@ export interface IStorageService {
|
|
* The scope argument allows to define the scope of the storage
|
|
* operation to either the current workspace only or all workspaces.
|
|
*/
|
|
- store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): void;
|
|
+ store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): Promise<void> | void;
|
|
|
|
/**
|
|
* Delete an element stored under the provided key from storage.
|
|
diff --git a/src/vs/platform/storage/node/storageService.ts b/src/vs/platform/storage/node/storageService.ts
|
|
index 75514fe5a4fabdc885556311954ab016c593bac3..62d97c60488856dfde0bb64fea85032b2e49bb94 100644
|
|
--- a/src/vs/platform/storage/node/storageService.ts
|
|
+++ b/src/vs/platform/storage/node/storageService.ts
|
|
@@ -204,8 +204,8 @@ export class NativeStorageService extends Disposable implements IStorageService
|
|
return this.getStorage(scope).getNumber(key, fallbackValue);
|
|
}
|
|
|
|
- store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): void {
|
|
- this.getStorage(scope).set(key, value);
|
|
+ store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): Promise<void> {
|
|
+ return this.getStorage(scope).set(key, value);
|
|
}
|
|
|
|
remove(key: string, scope: StorageScope): void {
|
|
diff --git a/src/vs/server/browser/client.ts b/src/vs/server/browser/client.ts
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..3c0703b7174ad792a4b42841e96ee93765d71601
|
|
--- /dev/null
|
|
+++ b/src/vs/server/browser/client.ts
|
|
@@ -0,0 +1,189 @@
|
|
+import { Emitter } from 'vs/base/common/event';
|
|
+import { URI } from 'vs/base/common/uri';
|
|
+import { localize } from 'vs/nls';
|
|
+import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
|
|
+import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
|
+import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
|
+import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
|
|
+import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
|
+import { Registry } from 'vs/platform/registry/common/platform';
|
|
+import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
|
|
+import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
|
+import { INodeProxyService, NodeProxyChannelClient } from 'vs/server/common/nodeProxy';
|
|
+import { TelemetryChannelClient } from 'vs/server/common/telemetry';
|
|
+import 'vs/workbench/contrib/localizations/browser/localizations.contribution';
|
|
+import { LocalizationsService } from 'vs/workbench/services/localizations/electron-browser/localizationsService';
|
|
+import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
|
+import { Options } from 'vs/server/ipc.d';
|
|
+import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
|
+
|
|
+class TelemetryService extends TelemetryChannelClient {
|
|
+ public constructor(
|
|
+ @IRemoteAgentService remoteAgentService: IRemoteAgentService,
|
|
+ ) {
|
|
+ super(remoteAgentService.getConnection()!.getChannel('telemetry'));
|
|
+ }
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Remove extra slashes in a URL.
|
|
+ */
|
|
+export const normalize = (url: string, keepTrailing = false): string => {
|
|
+ return url.replace(/\/\/+/g, "/").replace(/\/+$/, keepTrailing ? "/" : "");
|
|
+};
|
|
+
|
|
+/**
|
|
+ * Get options embedded in the HTML.
|
|
+ */
|
|
+export const getOptions = <T extends Options>(): T => {
|
|
+ try {
|
|
+ return JSON.parse(document.getElementById("coder-options")!.getAttribute("data-settings")!);
|
|
+ } catch (error) {
|
|
+ return {} as T;
|
|
+ }
|
|
+};
|
|
+
|
|
+const options = getOptions();
|
|
+
|
|
+const TELEMETRY_SECTION_ID = 'telemetry';
|
|
+Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfiguration({
|
|
+ 'id': TELEMETRY_SECTION_ID,
|
|
+ 'order': 110,
|
|
+ 'type': 'object',
|
|
+ 'title': localize('telemetryConfigurationTitle', 'Telemetry'),
|
|
+ 'properties': {
|
|
+ 'telemetry.enableTelemetry': {
|
|
+ 'type': 'boolean',
|
|
+ 'description': localize('telemetry.enableTelemetry', 'Enable usage data and errors to be sent to a Microsoft online service.'),
|
|
+ 'default': !options.disableTelemetry,
|
|
+ 'tags': ['usesOnlineServices']
|
|
+ }
|
|
+ }
|
|
+});
|
|
+
|
|
+class NodeProxyService extends NodeProxyChannelClient implements INodeProxyService {
|
|
+ private readonly _onClose = new Emitter<void>();
|
|
+ public readonly onClose = this._onClose.event;
|
|
+ private readonly _onDown = new Emitter<void>();
|
|
+ public readonly onDown = this._onDown.event;
|
|
+ private readonly _onUp = new Emitter<void>();
|
|
+ public readonly onUp = this._onUp.event;
|
|
+
|
|
+ public constructor(
|
|
+ @IRemoteAgentService remoteAgentService: IRemoteAgentService,
|
|
+ ) {
|
|
+ super(remoteAgentService.getConnection()!.getChannel('nodeProxy'));
|
|
+ remoteAgentService.getConnection()!.onDidStateChange((state) => {
|
|
+ switch (state.type) {
|
|
+ case PersistentConnectionEventType.ConnectionGain:
|
|
+ return this._onUp.fire();
|
|
+ case PersistentConnectionEventType.ConnectionLost:
|
|
+ return this._onDown.fire();
|
|
+ case PersistentConnectionEventType.ReconnectionPermanentFailure:
|
|
+ return this._onClose.fire();
|
|
+ }
|
|
+ });
|
|
+ }
|
|
+}
|
|
+
|
|
+registerSingleton(ILocalizationsService, LocalizationsService);
|
|
+registerSingleton(INodeProxyService, NodeProxyService);
|
|
+registerSingleton(ITelemetryService, TelemetryService);
|
|
+
|
|
+/**
|
|
+ * This is called by vs/workbench/browser/web.main.ts after the workbench has
|
|
+ * been initialized so we can initialize our own client-side code.
|
|
+ */
|
|
+export const initialize = async (services: ServiceCollection): Promise<void> => {
|
|
+ const event = new CustomEvent('ide-ready');
|
|
+ window.dispatchEvent(event);
|
|
+
|
|
+ if (parent) {
|
|
+ // Tell the parent loading has completed.
|
|
+ parent.postMessage({ event: 'loaded' }, window.location.origin);
|
|
+
|
|
+ // Proxy or stop proxing events as requested by the parent.
|
|
+ const listeners = new Map<string, (event: Event) => void>();
|
|
+ window.addEventListener('message', (parentEvent) => {
|
|
+ const eventName = parentEvent.data.bind || parentEvent.data.unbind;
|
|
+ if (eventName) {
|
|
+ const oldListener = listeners.get(eventName);
|
|
+ if (oldListener) {
|
|
+ document.removeEventListener(eventName, oldListener);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (parentEvent.data.bind && parentEvent.data.prop) {
|
|
+ const listener = (event: Event) => {
|
|
+ parent.postMessage({
|
|
+ event: parentEvent.data.event,
|
|
+ [parentEvent.data.prop]: event[parentEvent.data.prop as keyof Event]
|
|
+ }, window.location.origin);
|
|
+ };
|
|
+ listeners.set(parentEvent.data.bind, listener);
|
|
+ document.addEventListener(parentEvent.data.bind, listener);
|
|
+ }
|
|
+ });
|
|
+ }
|
|
+
|
|
+ if (!window.isSecureContext) {
|
|
+ (services.get(INotificationService) as INotificationService).notify({
|
|
+ severity: Severity.Warning,
|
|
+ message: 'code-server is being accessed over an insecure domain. Web views, the clipboard, and other functionality will not work as expected.',
|
|
+ actions: {
|
|
+ primary: [{
|
|
+ id: 'understand',
|
|
+ label: 'I understand',
|
|
+ tooltip: '',
|
|
+ class: undefined,
|
|
+ enabled: true,
|
|
+ checked: true,
|
|
+ dispose: () => undefined,
|
|
+ run: () => {
|
|
+ return Promise.resolve();
|
|
+ }
|
|
+ }],
|
|
+ }
|
|
+ });
|
|
+ }
|
|
+
|
|
+ // This will be used to set the background color while VS Code loads.
|
|
+ const theme = (services.get(IStorageService) as IStorageService).get("colorThemeData", StorageScope.GLOBAL);
|
|
+ if (theme) {
|
|
+ localStorage.setItem("colorThemeData", theme);
|
|
+ }
|
|
+};
|
|
+
|
|
+export interface Query {
|
|
+ [key: string]: string | undefined;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Split a string up to the delimiter. If the delimiter doesn't exist the first
|
|
+ * item will have all the text and the second item will be an empty string.
|
|
+ */
|
|
+export const split = (str: string, delimiter: string): [string, string] => {
|
|
+ const index = str.indexOf(delimiter);
|
|
+ return index !== -1 ? [str.substring(0, index).trim(), str.substring(index + 1)] : [str, ''];
|
|
+};
|
|
+
|
|
+/**
|
|
+ * Return the URL modified with the specified query variables. It's pretty
|
|
+ * stupid so it probably doesn't cover any edge cases. Undefined values will
|
|
+ * unset existing values. Doesn't allow duplicates.
|
|
+ */
|
|
+export const withQuery = (url: string, replace: Query): string => {
|
|
+ const uri = URI.parse(url);
|
|
+ const query = { ...replace };
|
|
+ uri.query.split('&').forEach((kv) => {
|
|
+ const [key, value] = split(kv, '=');
|
|
+ if (!(key in query)) {
|
|
+ query[key] = value;
|
|
+ }
|
|
+ });
|
|
+ return uri.with({
|
|
+ query: Object.keys(query)
|
|
+ .filter((k) => typeof query[k] !== 'undefined')
|
|
+ .map((k) => `${k}=${query[k]}`).join('&'),
|
|
+ }).toString(true);
|
|
+};
|
|
diff --git a/src/vs/server/browser/extHostNodeProxy.ts b/src/vs/server/browser/extHostNodeProxy.ts
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..ed7c078077b0c375758529959b280e091436113a
|
|
--- /dev/null
|
|
+++ b/src/vs/server/browser/extHostNodeProxy.ts
|
|
@@ -0,0 +1,46 @@
|
|
+import { Emitter } from 'vs/base/common/event';
|
|
+import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
|
+import { ExtHostNodeProxyShape, MainContext, MainThreadNodeProxyShape } from 'vs/workbench/api/common/extHost.protocol';
|
|
+import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
|
+
|
|
+export class ExtHostNodeProxy implements ExtHostNodeProxyShape {
|
|
+ _serviceBrand: any;
|
|
+
|
|
+ private readonly _onMessage = new Emitter<string>();
|
|
+ public readonly onMessage = this._onMessage.event;
|
|
+ private readonly _onClose = new Emitter<void>();
|
|
+ public readonly onClose = this._onClose.event;
|
|
+ private readonly _onDown = new Emitter<void>();
|
|
+ public readonly onDown = this._onDown.event;
|
|
+ private readonly _onUp = new Emitter<void>();
|
|
+ public readonly onUp = this._onUp.event;
|
|
+
|
|
+ private readonly proxy: MainThreadNodeProxyShape;
|
|
+
|
|
+ constructor(@IExtHostRpcService rpc: IExtHostRpcService) {
|
|
+ this.proxy = rpc.getProxy(MainContext.MainThreadNodeProxy);
|
|
+ }
|
|
+
|
|
+ public $onMessage(message: string): void {
|
|
+ this._onMessage.fire(message);
|
|
+ }
|
|
+
|
|
+ public $onClose(): void {
|
|
+ this._onClose.fire();
|
|
+ }
|
|
+
|
|
+ public $onUp(): void {
|
|
+ this._onUp.fire();
|
|
+ }
|
|
+
|
|
+ public $onDown(): void {
|
|
+ this._onDown.fire();
|
|
+ }
|
|
+
|
|
+ public send(message: string): void {
|
|
+ this.proxy.$send(message);
|
|
+ }
|
|
+}
|
|
+
|
|
+export interface IExtHostNodeProxy extends ExtHostNodeProxy { }
|
|
+export const IExtHostNodeProxy = createDecorator<IExtHostNodeProxy>('IExtHostNodeProxy');
|
|
diff --git a/src/vs/server/browser/mainThreadNodeProxy.ts b/src/vs/server/browser/mainThreadNodeProxy.ts
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0d2e93edae2baf34d27b7b52be0bf4960f244531
|
|
--- /dev/null
|
|
+++ b/src/vs/server/browser/mainThreadNodeProxy.ts
|
|
@@ -0,0 +1,37 @@
|
|
+import { IDisposable } from 'vs/base/common/lifecycle';
|
|
+import { INodeProxyService } from 'vs/server/common/nodeProxy';
|
|
+import { ExtHostContext, IExtHostContext, MainContext, MainThreadNodeProxyShape } from 'vs/workbench/api/common/extHost.protocol';
|
|
+import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
|
+
|
|
+@extHostNamedCustomer(MainContext.MainThreadNodeProxy)
|
|
+export class MainThreadNodeProxy implements MainThreadNodeProxyShape {
|
|
+ private disposed = false;
|
|
+ private disposables = <IDisposable[]>[];
|
|
+
|
|
+ constructor(
|
|
+ extHostContext: IExtHostContext,
|
|
+ @INodeProxyService private readonly proxyService: INodeProxyService,
|
|
+ ) {
|
|
+ if (!extHostContext.remoteAuthority) { // HACK: A terrible way to detect if running in the worker.
|
|
+ const proxy = extHostContext.getProxy(ExtHostContext.ExtHostNodeProxy);
|
|
+ this.disposables = [
|
|
+ this.proxyService.onMessage((message: string) => proxy.$onMessage(message)),
|
|
+ this.proxyService.onClose(() => proxy.$onClose()),
|
|
+ this.proxyService.onDown(() => proxy.$onDown()),
|
|
+ this.proxyService.onUp(() => proxy.$onUp()),
|
|
+ ];
|
|
+ }
|
|
+ }
|
|
+
|
|
+ $send(message: string): void {
|
|
+ if (!this.disposed) {
|
|
+ this.proxyService.send(message);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ dispose(): void {
|
|
+ this.disposables.forEach((d) => d.dispose());
|
|
+ this.disposables = [];
|
|
+ this.disposed = true;
|
|
+ }
|
|
+}
|
|
diff --git a/src/vs/server/browser/worker.ts b/src/vs/server/browser/worker.ts
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..5ae44cdc856bf81326a4c516b8be9afb2c746a67
|
|
--- /dev/null
|
|
+++ b/src/vs/server/browser/worker.ts
|
|
@@ -0,0 +1,56 @@
|
|
+import { Client } from '@coder/node-browser';
|
|
+import { fromTar } from '@coder/requirefs';
|
|
+import { URI } from 'vs/base/common/uri';
|
|
+import { ILogService } from 'vs/platform/log/common/log';
|
|
+import { ExtensionActivationTimesBuilder } from 'vs/workbench/api/common/extHostExtensionActivator';
|
|
+import { IExtHostNodeProxy } from './extHostNodeProxy';
|
|
+
|
|
+export const loadCommonJSModule = async <T>(
|
|
+ module: URI,
|
|
+ activationTimesBuilder: ExtensionActivationTimesBuilder,
|
|
+ nodeProxy: IExtHostNodeProxy,
|
|
+ logService: ILogService,
|
|
+ vscode: any,
|
|
+): Promise<T> => {
|
|
+ const fetchUri = URI.from({
|
|
+ scheme: self.location.protocol.replace(':', ''),
|
|
+ authority: self.location.host,
|
|
+ path: self.location.pathname.replace(/\/static\/([^\/]+)\/.*$/, '/static/$1\/'),
|
|
+ query: `tar=${encodeURIComponent(module.path)}`,
|
|
+ });
|
|
+ const response = await fetch(fetchUri.toString(true));
|
|
+ if (response.status !== 200) {
|
|
+ throw new Error(`Failed to download extension "${module}"`);
|
|
+ }
|
|
+ const client = new Client(nodeProxy, { logger: logService });
|
|
+ const init = await client.handshake();
|
|
+ const buffer = new Uint8Array(await response.arrayBuffer());
|
|
+ const rfs = fromTar(buffer);
|
|
+ (<any>self).global = self;
|
|
+ rfs.provide('vscode', vscode);
|
|
+ Object.keys(client.modules).forEach((key) => {
|
|
+ const mod = (client.modules as any)[key];
|
|
+ if (key === 'process') {
|
|
+ (<any>self).process = mod;
|
|
+ (<any>self).process.env = init.env;
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ rfs.provide(key, mod);
|
|
+ switch (key) {
|
|
+ case 'buffer':
|
|
+ (<any>self).Buffer = mod.Buffer;
|
|
+ break;
|
|
+ case 'timers':
|
|
+ (<any>self).setImmediate = mod.setImmediate;
|
|
+ break;
|
|
+ }
|
|
+ });
|
|
+
|
|
+ try {
|
|
+ activationTimesBuilder.codeLoadingStart();
|
|
+ return rfs.require('.');
|
|
+ } finally {
|
|
+ activationTimesBuilder.codeLoadingStop();
|
|
+ }
|
|
+};
|
|
diff --git a/src/vs/server/common/nodeProxy.ts b/src/vs/server/common/nodeProxy.ts
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..14b9de879ceab4c1976770fa7810d276c5aa3e36
|
|
--- /dev/null
|
|
+++ b/src/vs/server/common/nodeProxy.ts
|
|
@@ -0,0 +1,47 @@
|
|
+import { ReadWriteConnection } from '@coder/node-browser';
|
|
+import { Event } from 'vs/base/common/event';
|
|
+import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
|
|
+import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
|
+
|
|
+export const INodeProxyService = createDecorator<INodeProxyService>('nodeProxyService');
|
|
+
|
|
+export interface INodeProxyService extends ReadWriteConnection {
|
|
+ _serviceBrand: any;
|
|
+ send(message: string): void;
|
|
+ onMessage: Event<string>;
|
|
+ onUp: Event<void>;
|
|
+ onClose: Event<void>;
|
|
+ onDown: Event<void>;
|
|
+}
|
|
+
|
|
+export class NodeProxyChannel implements IServerChannel {
|
|
+ constructor(private service: INodeProxyService) {}
|
|
+
|
|
+ listen(_: unknown, event: string): Event<any> {
|
|
+ switch (event) {
|
|
+ case 'onMessage': return this.service.onMessage;
|
|
+ }
|
|
+ throw new Error(`Invalid listen ${event}`);
|
|
+ }
|
|
+
|
|
+ async call(_: unknown, command: string, args?: any): Promise<any> {
|
|
+ switch (command) {
|
|
+ case 'send': return this.service.send(args[0]);
|
|
+ }
|
|
+ throw new Error(`Invalid call ${command}`);
|
|
+ }
|
|
+}
|
|
+
|
|
+export class NodeProxyChannelClient {
|
|
+ _serviceBrand: any;
|
|
+
|
|
+ public readonly onMessage: Event<string>;
|
|
+
|
|
+ constructor(private readonly channel: IChannel) {
|
|
+ this.onMessage = this.channel.listen<string>('onMessage');
|
|
+ }
|
|
+
|
|
+ public send(data: string): void {
|
|
+ this.channel.call('send', [data]);
|
|
+ }
|
|
+}
|
|
diff --git a/src/vs/server/common/telemetry.ts b/src/vs/server/common/telemetry.ts
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..4ea6d95d36aaac07dbd4d0e16ab3c1bba255f683
|
|
--- /dev/null
|
|
+++ b/src/vs/server/common/telemetry.ts
|
|
@@ -0,0 +1,65 @@
|
|
+import { ITelemetryData } from 'vs/base/common/actions';
|
|
+import { Event } from 'vs/base/common/event';
|
|
+import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
|
|
+import { ClassifiedEvent, GDPRClassification, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings';
|
|
+import { ITelemetryInfo, ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
|
+
|
|
+export class TelemetryChannel implements IServerChannel {
|
|
+ constructor(private service: ITelemetryService) {}
|
|
+
|
|
+ listen(_: unknown, event: string): Event<any> {
|
|
+ throw new Error(`Invalid listen ${event}`);
|
|
+ }
|
|
+
|
|
+ call(_: unknown, command: string, args?: any): Promise<any> {
|
|
+ switch (command) {
|
|
+ case 'publicLog': return this.service.publicLog(args[0], args[1], args[2]);
|
|
+ case 'publicLog2': return this.service.publicLog2(args[0], args[1], args[2]);
|
|
+ case 'publicLogError': return this.service.publicLogError(args[0], args[1]);
|
|
+ case 'publicLogError2': return this.service.publicLogError2(args[0], args[1]);
|
|
+ case 'setEnabled': return Promise.resolve(this.service.setEnabled(args[0]));
|
|
+ case 'getTelemetryInfo': return this.service.getTelemetryInfo();
|
|
+ case 'setExperimentProperty': return Promise.resolve(this.service.setExperimentProperty(args[0], args[1]));
|
|
+ }
|
|
+ throw new Error(`Invalid call ${command}`);
|
|
+ }
|
|
+}
|
|
+
|
|
+export class TelemetryChannelClient implements ITelemetryService {
|
|
+ _serviceBrand: any;
|
|
+
|
|
+ // These don't matter; telemetry is sent to the Node side which decides
|
|
+ // whether to send the telemetry event.
|
|
+ public isOptedIn = true;
|
|
+ public sendErrorTelemetry = true;
|
|
+
|
|
+ constructor(private readonly channel: IChannel) {}
|
|
+
|
|
+ public publicLog(eventName: string, data?: ITelemetryData, anonymizeFilePaths?: boolean): Promise<void> {
|
|
+ return this.channel.call('publicLog', [eventName, data, anonymizeFilePaths]);
|
|
+ }
|
|
+
|
|
+ public publicLog2<E extends ClassifiedEvent<T> = never, T extends GDPRClassification<T> = never>(eventName: string, data?: StrictPropertyCheck<T, E>, anonymizeFilePaths?: boolean): Promise<void> {
|
|
+ return this.channel.call('publicLog2', [eventName, data, anonymizeFilePaths]);
|
|
+ }
|
|
+
|
|
+ public publicLogError(errorEventName: string, data?: ITelemetryData): Promise<void> {
|
|
+ return this.channel.call('publicLogError', [errorEventName, data]);
|
|
+ }
|
|
+
|
|
+ public publicLogError2<E extends ClassifiedEvent<T> = never, T extends GDPRClassification<T> = never>(eventName: string, data?: StrictPropertyCheck<T, E>): Promise<void> {
|
|
+ return this.channel.call('publicLogError2', [eventName, data]);
|
|
+ }
|
|
+
|
|
+ public setEnabled(value: boolean): void {
|
|
+ this.channel.call('setEnable', [value]);
|
|
+ }
|
|
+
|
|
+ public getTelemetryInfo(): Promise<ITelemetryInfo> {
|
|
+ return this.channel.call('getTelemetryInfo');
|
|
+ }
|
|
+
|
|
+ public setExperimentProperty(name: string, value: string): void {
|
|
+ this.channel.call('setExperimentProperty', [name, value]);
|
|
+ }
|
|
+}
|
|
diff --git a/src/vs/server/entry.ts b/src/vs/server/entry.ts
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..ab020fbb4e4ab3748cc807765ff9c362389faafa
|
|
--- /dev/null
|
|
+++ b/src/vs/server/entry.ts
|
|
@@ -0,0 +1,78 @@
|
|
+import { field } from '@coder/logger';
|
|
+import { setUnexpectedErrorHandler } from 'vs/base/common/errors';
|
|
+import { CodeServerMessage, VscodeMessage } from 'vs/server/ipc';
|
|
+import { logger } from 'vs/server/node/logger';
|
|
+import { enableCustomMarketplace } from 'vs/server/node/marketplace';
|
|
+import { Vscode } from 'vs/server/node/server';
|
|
+
|
|
+setUnexpectedErrorHandler((error) => logger.warn(error instanceof Error ? error.message : error));
|
|
+enableCustomMarketplace();
|
|
+
|
|
+/**
|
|
+ * Ensure we control when the process exits.
|
|
+ */
|
|
+const exit = process.exit;
|
|
+process.exit = function(code?: number) {
|
|
+ logger.warn(`process.exit() was prevented: ${code || 'unknown code'}.`);
|
|
+} as (code?: number) => never;
|
|
+
|
|
+// Kill VS Code if the parent process dies.
|
|
+if (typeof process.env.CODE_SERVER_PARENT_PID !== 'undefined') {
|
|
+ const parentPid = parseInt(process.env.CODE_SERVER_PARENT_PID, 10);
|
|
+ setInterval(() => {
|
|
+ try {
|
|
+ process.kill(parentPid, 0); // Throws an exception if the process doesn't exist anymore.
|
|
+ } catch (e) {
|
|
+ exit();
|
|
+ }
|
|
+ }, 5000);
|
|
+} else {
|
|
+ logger.error('no parent process');
|
|
+ exit(1);
|
|
+}
|
|
+
|
|
+const vscode = new Vscode();
|
|
+const send = (message: VscodeMessage): void => {
|
|
+ if (!process.send) {
|
|
+ throw new Error('not spawned with IPC');
|
|
+ }
|
|
+ process.send(message);
|
|
+};
|
|
+
|
|
+// Wait for the init message then start up VS Code. Subsequent messages will
|
|
+// return new workbench options without starting a new instance.
|
|
+process.on('message', async (message: CodeServerMessage, socket) => {
|
|
+ logger.debug('got message from code-server', field('message', message));
|
|
+ switch (message.type) {
|
|
+ case 'init':
|
|
+ try {
|
|
+ const options = await vscode.initialize(message.options);
|
|
+ send({ type: 'options', id: message.id, options });
|
|
+ } catch (error) {
|
|
+ logger.error(error.message);
|
|
+ logger.error(error.stack);
|
|
+ exit(1);
|
|
+ }
|
|
+ break;
|
|
+ case 'cli':
|
|
+ try {
|
|
+ await vscode.cli(message.args);
|
|
+ exit(0);
|
|
+ } catch (error) {
|
|
+ logger.error(error.message);
|
|
+ logger.error(error.stack);
|
|
+ exit(1);
|
|
+ }
|
|
+ break;
|
|
+ case 'socket':
|
|
+ vscode.handleWebSocket(socket, message.query);
|
|
+ break;
|
|
+ }
|
|
+});
|
|
+if (!process.send) {
|
|
+ logger.error('not spawned with IPC');
|
|
+ exit(1);
|
|
+} else {
|
|
+ // This lets the parent know the child is ready to receive messages.
|
|
+ send({ type: 'ready' });
|
|
+}
|
|
diff --git a/src/vs/server/fork.js b/src/vs/server/fork.js
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..56331ff1fc32bbd82e769aaecb551e427f798ec3
|
|
--- /dev/null
|
|
+++ b/src/vs/server/fork.js
|
|
@@ -0,0 +1,3 @@
|
|
+// This must be a JS file otherwise when it gets compiled it turns into AMD
|
|
+// syntax which will not work without the right loader.
|
|
+require('../../bootstrap-amd').load('vs/server/entry');
|
|
diff --git a/src/vs/server/ipc.d.ts b/src/vs/server/ipc.d.ts
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..33b28cf2d53746ee9c50c056ac2e087dcee0a4e2
|
|
--- /dev/null
|
|
+++ b/src/vs/server/ipc.d.ts
|
|
@@ -0,0 +1,131 @@
|
|
+/**
|
|
+ * External interfaces for integration into code-server over IPC. No vs imports
|
|
+ * should be made in this file.
|
|
+ */
|
|
+export interface Options {
|
|
+ disableTelemetry: boolean
|
|
+}
|
|
+
|
|
+export interface InitMessage {
|
|
+ type: 'init';
|
|
+ id: string;
|
|
+ options: VscodeOptions;
|
|
+}
|
|
+
|
|
+export type Query = { [key: string]: string | string[] | undefined };
|
|
+
|
|
+export interface SocketMessage {
|
|
+ type: 'socket';
|
|
+ query: Query;
|
|
+}
|
|
+
|
|
+export interface CliMessage {
|
|
+ type: 'cli';
|
|
+ args: Args;
|
|
+}
|
|
+
|
|
+export interface OpenCommandPipeArgs {
|
|
+ type: 'open';
|
|
+ fileURIs?: string[];
|
|
+ folderURIs: string[];
|
|
+ forceNewWindow?: boolean;
|
|
+ diffMode?: boolean;
|
|
+ addMode?: boolean;
|
|
+ gotoLineMode?: boolean;
|
|
+ forceReuseWindow?: boolean;
|
|
+ waitMarkerFilePath?: string;
|
|
+}
|
|
+
|
|
+export type CodeServerMessage = InitMessage | SocketMessage | CliMessage;
|
|
+
|
|
+export interface ReadyMessage {
|
|
+ type: 'ready';
|
|
+}
|
|
+
|
|
+export interface OptionsMessage {
|
|
+ id: string;
|
|
+ type: 'options';
|
|
+ options: WorkbenchOptions;
|
|
+}
|
|
+
|
|
+export type VscodeMessage = ReadyMessage | OptionsMessage;
|
|
+
|
|
+export interface StartPath {
|
|
+ url: string;
|
|
+ workspace: boolean;
|
|
+}
|
|
+
|
|
+export interface Args {
|
|
+ 'user-data-dir'?: string;
|
|
+
|
|
+ 'enable-proposed-api'?: string[];
|
|
+ 'extensions-dir'?: string;
|
|
+ 'builtin-extensions-dir'?: string;
|
|
+ 'extra-extensions-dir'?: string[];
|
|
+ 'extra-builtin-extensions-dir'?: string[];
|
|
+
|
|
+ locale?: string
|
|
+
|
|
+ log?: string;
|
|
+ verbose?: boolean;
|
|
+
|
|
+ _: string[];
|
|
+}
|
|
+
|
|
+export interface VscodeOptions {
|
|
+ readonly args: Args;
|
|
+ readonly remoteAuthority: string;
|
|
+ readonly startPath?: StartPath;
|
|
+}
|
|
+
|
|
+export interface VscodeOptionsMessage extends VscodeOptions {
|
|
+ readonly id: string;
|
|
+}
|
|
+
|
|
+export interface UriComponents {
|
|
+ readonly scheme: string;
|
|
+ readonly authority: string;
|
|
+ readonly path: string;
|
|
+ readonly query: string;
|
|
+ readonly fragment: string;
|
|
+}
|
|
+
|
|
+export interface NLSConfiguration {
|
|
+ locale: string;
|
|
+ availableLanguages: {
|
|
+ [key: string]: string;
|
|
+ };
|
|
+ pseudo?: boolean;
|
|
+ _languagePackSupport?: boolean;
|
|
+}
|
|
+
|
|
+export interface WorkbenchOptions {
|
|
+ readonly workbenchWebConfiguration: {
|
|
+ readonly remoteAuthority?: string;
|
|
+ readonly folderUri?: UriComponents;
|
|
+ readonly workspaceUri?: UriComponents;
|
|
+ readonly logLevel?: number;
|
|
+ readonly workspaceProvider?: {
|
|
+ payload: [
|
|
+ ["userDataPath", string],
|
|
+ ["enableProposedApi", string],
|
|
+ ];
|
|
+ };
|
|
+ };
|
|
+ readonly remoteUserDataUri: UriComponents;
|
|
+ readonly productConfiguration: {
|
|
+ codeServerVersion?: string;
|
|
+ readonly extensionsGallery?: {
|
|
+ readonly serviceUrl: string;
|
|
+ readonly itemUrl: string;
|
|
+ readonly controlUrl: string;
|
|
+ readonly recommendationsUrl: string;
|
|
+ };
|
|
+ };
|
|
+ readonly nlsConfiguration: NLSConfiguration;
|
|
+ readonly commit: string;
|
|
+}
|
|
+
|
|
+export interface WorkbenchOptionsMessage {
|
|
+ id: string;
|
|
+}
|
|
diff --git a/src/vs/server/node/channel.ts b/src/vs/server/node/channel.ts
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..e10cc9c218b27d859a523be3db5b8a30ef90d953
|
|
--- /dev/null
|
|
+++ b/src/vs/server/node/channel.ts
|
|
@@ -0,0 +1,360 @@
|
|
+import { Server } from '@coder/node-browser';
|
|
+import * as path from 'path';
|
|
+import { VSBuffer } from 'vs/base/common/buffer';
|
|
+import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
|
+import { Emitter, Event } from 'vs/base/common/event';
|
|
+import { IDisposable } from 'vs/base/common/lifecycle';
|
|
+import { OS } from 'vs/base/common/platform';
|
|
+import { ReadableStreamEventPayload } from 'vs/base/common/stream';
|
|
+import { URI, UriComponents } from 'vs/base/common/uri';
|
|
+import { transformOutgoingURIs } from 'vs/base/common/uriIpc';
|
|
+import { IServerChannel } from 'vs/base/parts/ipc/common/ipc';
|
|
+import { IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics';
|
|
+import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
|
|
+import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
|
+import { FileDeleteOptions, FileOpenOptions, FileOverwriteOptions, FileReadStreamOptions, FileType, FileWriteOptions, IStat, IWatchOptions } from 'vs/platform/files/common/files';
|
|
+import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
|
|
+import { ILogService } from 'vs/platform/log/common/log';
|
|
+import product from 'vs/platform/product/common/product';
|
|
+import { IRemoteAgentEnvironment, RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment';
|
|
+import { ITelemetryData, ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
|
+import { INodeProxyService } from 'vs/server/common/nodeProxy';
|
|
+import { getTranslations } from 'vs/server/node/nls';
|
|
+import { getUriTransformer } from 'vs/server/node/util';
|
|
+import { IFileChangeDto } from 'vs/workbench/api/common/extHost.protocol';
|
|
+import { ExtensionScanner, ExtensionScannerInput } from 'vs/workbench/services/extensions/node/extensionPoints';
|
|
+
|
|
+/**
|
|
+ * Extend the file provider to allow unwatching.
|
|
+ */
|
|
+class Watcher extends DiskFileSystemProvider {
|
|
+ public readonly watches = new Map<number, IDisposable>();
|
|
+
|
|
+ public dispose(): void {
|
|
+ this.watches.forEach((w) => w.dispose());
|
|
+ this.watches.clear();
|
|
+ super.dispose();
|
|
+ }
|
|
+
|
|
+ public _watch(req: number, resource: URI, opts: IWatchOptions): void {
|
|
+ this.watches.set(req, this.watch(resource, opts));
|
|
+ }
|
|
+
|
|
+ public unwatch(req: number): void {
|
|
+ this.watches.get(req)!.dispose();
|
|
+ this.watches.delete(req);
|
|
+ }
|
|
+}
|
|
+
|
|
+export class FileProviderChannel implements IServerChannel<RemoteAgentConnectionContext>, IDisposable {
|
|
+ private readonly provider: DiskFileSystemProvider;
|
|
+ private readonly watchers = new Map<string, Watcher>();
|
|
+
|
|
+ public constructor(
|
|
+ private readonly environmentService: INativeEnvironmentService,
|
|
+ private readonly logService: ILogService,
|
|
+ ) {
|
|
+ this.provider = new DiskFileSystemProvider(this.logService);
|
|
+ }
|
|
+
|
|
+ public listen(context: RemoteAgentConnectionContext, event: string, args?: any): Event<any> {
|
|
+ switch (event) {
|
|
+ case 'filechange': return this.filechange(context, args[0]);
|
|
+ case 'readFileStream': return this.readFileStream(args[0], args[1]);
|
|
+ }
|
|
+
|
|
+ throw new Error(`Invalid listen '${event}'`);
|
|
+ }
|
|
+
|
|
+ private filechange(context: RemoteAgentConnectionContext, session: string): Event<IFileChangeDto[]> {
|
|
+ const emitter = new Emitter<IFileChangeDto[]>({
|
|
+ onFirstListenerAdd: () => {
|
|
+ const provider = new Watcher(this.logService);
|
|
+ this.watchers.set(session, provider);
|
|
+ const transformer = getUriTransformer(context.remoteAuthority);
|
|
+ provider.onDidChangeFile((events) => {
|
|
+ emitter.fire(events.map((event) => ({
|
|
+ ...event,
|
|
+ resource: transformer.transformOutgoing(event.resource),
|
|
+ })));
|
|
+ });
|
|
+ provider.onDidErrorOccur((event) => this.logService.error(event));
|
|
+ },
|
|
+ onLastListenerRemove: () => {
|
|
+ this.watchers.get(session)!.dispose();
|
|
+ this.watchers.delete(session);
|
|
+ },
|
|
+ });
|
|
+
|
|
+ return emitter.event;
|
|
+ }
|
|
+
|
|
+ private readFileStream(resource: UriComponents, opts: FileReadStreamOptions): Event<ReadableStreamEventPayload<VSBuffer>> {
|
|
+ const cts = new CancellationTokenSource();
|
|
+ const fileStream = this.provider.readFileStream(this.transform(resource), opts, cts.token);
|
|
+ const emitter = new Emitter<ReadableStreamEventPayload<VSBuffer>>({
|
|
+ onFirstListenerAdd: () => {
|
|
+ fileStream.on('data', (data) => emitter.fire(VSBuffer.wrap(data)));
|
|
+ fileStream.on('error', (error) => emitter.fire(error));
|
|
+ fileStream.on('end', () => emitter.fire('end'));
|
|
+ },
|
|
+ onLastListenerRemove: () => cts.cancel(),
|
|
+ });
|
|
+
|
|
+ return emitter.event;
|
|
+ }
|
|
+
|
|
+ public call(_: unknown, command: string, args?: any): Promise<any> {
|
|
+ switch (command) {
|
|
+ case 'stat': return this.stat(args[0]);
|
|
+ case 'open': return this.open(args[0], args[1]);
|
|
+ case 'close': return this.close(args[0]);
|
|
+ case 'read': return this.read(args[0], args[1], args[2]);
|
|
+ case 'readFile': return this.readFile(args[0]);
|
|
+ case 'write': return this.write(args[0], args[1], args[2], args[3], args[4]);
|
|
+ case 'writeFile': return this.writeFile(args[0], args[1], args[2]);
|
|
+ case 'delete': return this.delete(args[0], args[1]);
|
|
+ case 'mkdir': return this.mkdir(args[0]);
|
|
+ case 'readdir': return this.readdir(args[0]);
|
|
+ case 'rename': return this.rename(args[0], args[1], args[2]);
|
|
+ case 'copy': return this.copy(args[0], args[1], args[2]);
|
|
+ case 'watch': return this.watch(args[0], args[1], args[2], args[3]);
|
|
+ case 'unwatch': return this.unwatch(args[0], args[1]);
|
|
+ }
|
|
+
|
|
+ throw new Error(`Invalid call '${command}'`);
|
|
+ }
|
|
+
|
|
+ public dispose(): void {
|
|
+ this.watchers.forEach((w) => w.dispose());
|
|
+ this.watchers.clear();
|
|
+ }
|
|
+
|
|
+ private async stat(resource: UriComponents): Promise<IStat> {
|
|
+ return this.provider.stat(this.transform(resource));
|
|
+ }
|
|
+
|
|
+ private async open(resource: UriComponents, opts: FileOpenOptions): Promise<number> {
|
|
+ return this.provider.open(this.transform(resource), opts);
|
|
+ }
|
|
+
|
|
+ private async close(fd: number): Promise<void> {
|
|
+ return this.provider.close(fd);
|
|
+ }
|
|
+
|
|
+ private async read(fd: number, pos: number, length: number): Promise<[VSBuffer, number]> {
|
|
+ const buffer = VSBuffer.alloc(length);
|
|
+ const bytesRead = await this.provider.read(fd, pos, buffer.buffer, 0, length);
|
|
+ return [buffer, bytesRead];
|
|
+ }
|
|
+
|
|
+ private async readFile(resource: UriComponents): Promise<VSBuffer> {
|
|
+ return VSBuffer.wrap(await this.provider.readFile(this.transform(resource)));
|
|
+ }
|
|
+
|
|
+ private write(fd: number, pos: number, buffer: VSBuffer, offset: number, length: number): Promise<number> {
|
|
+ return this.provider.write(fd, pos, buffer.buffer, offset, length);
|
|
+ }
|
|
+
|
|
+ private writeFile(resource: UriComponents, buffer: VSBuffer, opts: FileWriteOptions): Promise<void> {
|
|
+ return this.provider.writeFile(this.transform(resource), buffer.buffer, opts);
|
|
+ }
|
|
+
|
|
+ private async delete(resource: UriComponents, opts: FileDeleteOptions): Promise<void> {
|
|
+ return this.provider.delete(this.transform(resource), opts);
|
|
+ }
|
|
+
|
|
+ private async mkdir(resource: UriComponents): Promise<void> {
|
|
+ return this.provider.mkdir(this.transform(resource));
|
|
+ }
|
|
+
|
|
+ private async readdir(resource: UriComponents): Promise<[string, FileType][]> {
|
|
+ return this.provider.readdir(this.transform(resource));
|
|
+ }
|
|
+
|
|
+ private async rename(resource: UriComponents, target: UriComponents, opts: FileOverwriteOptions): Promise<void> {
|
|
+ return this.provider.rename(this.transform(resource), URI.from(target), opts);
|
|
+ }
|
|
+
|
|
+ private copy(resource: UriComponents, target: UriComponents, opts: FileOverwriteOptions): Promise<void> {
|
|
+ return this.provider.copy(this.transform(resource), URI.from(target), opts);
|
|
+ }
|
|
+
|
|
+ private async watch(session: string, req: number, resource: UriComponents, opts: IWatchOptions): Promise<void> {
|
|
+ this.watchers.get(session)!._watch(req, this.transform(resource), opts);
|
|
+ }
|
|
+
|
|
+ private async unwatch(session: string, req: number): Promise<void> {
|
|
+ this.watchers.get(session)!.unwatch(req);
|
|
+ }
|
|
+
|
|
+ private transform(resource: UriComponents): URI {
|
|
+ // Used for walkthrough content.
|
|
+ if (/^\/static[^/]*\//.test(resource.path)) {
|
|
+ return URI.file(this.environmentService.appRoot + resource.path.replace(/^\/static[^/]*\//, '/'));
|
|
+ // Used by the webview service worker to load resources.
|
|
+ } else if (resource.path === '/vscode-resource' && resource.query) {
|
|
+ try {
|
|
+ const query = JSON.parse(resource.query);
|
|
+ if (query.requestResourcePath) {
|
|
+ return URI.file(query.requestResourcePath);
|
|
+ }
|
|
+ } catch (error) { /* Carry on. */ }
|
|
+ }
|
|
+ return URI.from(resource);
|
|
+ }
|
|
+}
|
|
+
|
|
+// See ../../workbench/services/remote/common/remoteAgentEnvironmentChannel.ts
|
|
+export class ExtensionEnvironmentChannel implements IServerChannel {
|
|
+ public constructor(
|
|
+ private readonly environment: INativeEnvironmentService,
|
|
+ private readonly log: ILogService,
|
|
+ private readonly telemetry: ITelemetryService,
|
|
+ private readonly connectionToken: string,
|
|
+ ) {}
|
|
+
|
|
+ public listen(_: unknown, event: string): Event<any> {
|
|
+ throw new Error(`Invalid listen '${event}'`);
|
|
+ }
|
|
+
|
|
+ public async call(context: any, command: string, args: any): Promise<any> {
|
|
+ switch (command) {
|
|
+ case 'getEnvironmentData':
|
|
+ return transformOutgoingURIs(
|
|
+ await this.getEnvironmentData(),
|
|
+ getUriTransformer(context.remoteAuthority),
|
|
+ );
|
|
+ case 'scanExtensions':
|
|
+ return transformOutgoingURIs(
|
|
+ await this.scanExtensions(args.language),
|
|
+ getUriTransformer(context.remoteAuthority),
|
|
+ );
|
|
+ case 'getDiagnosticInfo': return this.getDiagnosticInfo();
|
|
+ case 'disableTelemetry': return this.disableTelemetry();
|
|
+ case 'logTelemetry': return this.logTelemetry(args[0], args[1]);
|
|
+ case 'flushTelemetry': return this.flushTelemetry();
|
|
+ }
|
|
+ throw new Error(`Invalid call '${command}'`);
|
|
+ }
|
|
+
|
|
+ private async getEnvironmentData(): Promise<IRemoteAgentEnvironment> {
|
|
+ return {
|
|
+ pid: process.pid,
|
|
+ connectionToken: this.connectionToken,
|
|
+ appRoot: URI.file(this.environment.appRoot),
|
|
+ settingsPath: this.environment.settingsResource,
|
|
+ logsPath: URI.file(this.environment.logsPath),
|
|
+ extensionsPath: URI.file(this.environment.extensionsPath!),
|
|
+ extensionHostLogsPath: URI.file(path.join(this.environment.logsPath, 'extension-host')),
|
|
+ globalStorageHome: this.environment.globalStorageHome,
|
|
+ workspaceStorageHome: this.environment.workspaceStorageHome,
|
|
+ userHome: this.environment.userHome,
|
|
+ os: OS,
|
|
+ };
|
|
+ }
|
|
+
|
|
+ private async scanExtensions(language: string): Promise<IExtensionDescription[]> {
|
|
+ const translations = await getTranslations(language, this.environment.userDataPath);
|
|
+
|
|
+ const scanMultiple = (isBuiltin: boolean, isUnderDevelopment: boolean, paths: string[]): Promise<IExtensionDescription[][]> => {
|
|
+ return Promise.all(paths.map((path) => {
|
|
+ return ExtensionScanner.scanExtensions(new ExtensionScannerInput(
|
|
+ product.version,
|
|
+ product.commit,
|
|
+ language,
|
|
+ !!process.env.VSCODE_DEV,
|
|
+ path,
|
|
+ isBuiltin,
|
|
+ isUnderDevelopment,
|
|
+ translations,
|
|
+ ), this.log);
|
|
+ }));
|
|
+ };
|
|
+
|
|
+ const scanBuiltin = async (): Promise<IExtensionDescription[][]> => {
|
|
+ return scanMultiple(true, false, [this.environment.builtinExtensionsPath, ...this.environment.extraBuiltinExtensionPaths]);
|
|
+ };
|
|
+
|
|
+ const scanInstalled = async (): Promise<IExtensionDescription[][]> => {
|
|
+ return scanMultiple(false, true, [this.environment.extensionsPath!, ...this.environment.extraExtensionPaths]);
|
|
+ };
|
|
+
|
|
+ return Promise.all([scanBuiltin(), scanInstalled()]).then((allExtensions) => {
|
|
+ const uniqueExtensions = new Map<string, IExtensionDescription>();
|
|
+ allExtensions.forEach((multipleExtensions) => {
|
|
+ multipleExtensions.forEach((extensions) => {
|
|
+ extensions.forEach((extension) => {
|
|
+ const id = ExtensionIdentifier.toKey(extension.identifier);
|
|
+ if (uniqueExtensions.has(id)) {
|
|
+ const oldPath = uniqueExtensions.get(id)!.extensionLocation.fsPath;
|
|
+ const newPath = extension.extensionLocation.fsPath;
|
|
+ this.log.warn(`${oldPath} has been overridden ${newPath}`);
|
|
+ }
|
|
+ uniqueExtensions.set(id, {
|
|
+ ...extension,
|
|
+ // Force extensions that should run on the client due to latency
|
|
+ // issues.
|
|
+ extensionKind: extension.identifier.value === 'vscodevim.vim'
|
|
+ ? [ 'web' ]
|
|
+ : extension.extensionKind,
|
|
+ });
|
|
+ });
|
|
+ });
|
|
+ });
|
|
+ return Array.from(uniqueExtensions.values());
|
|
+ });
|
|
+ }
|
|
+
|
|
+ private getDiagnosticInfo(): Promise<IDiagnosticInfo> {
|
|
+ throw new Error('not implemented');
|
|
+ }
|
|
+
|
|
+ private async disableTelemetry(): Promise<void> {
|
|
+ this.telemetry.setEnabled(false);
|
|
+ }
|
|
+
|
|
+ private async logTelemetry(eventName: string, data: ITelemetryData): Promise<void> {
|
|
+ this.telemetry.publicLog(eventName, data);
|
|
+ }
|
|
+
|
|
+ private async flushTelemetry(): Promise<void> {
|
|
+ // We always send immediately at the moment.
|
|
+ }
|
|
+}
|
|
+
|
|
+export class NodeProxyService implements INodeProxyService {
|
|
+ public _serviceBrand = undefined;
|
|
+
|
|
+ public readonly server: Server;
|
|
+
|
|
+ private readonly _onMessage = new Emitter<string>();
|
|
+ public readonly onMessage = this._onMessage.event;
|
|
+ private readonly _$onMessage = new Emitter<string>();
|
|
+ public readonly $onMessage = this._$onMessage.event;
|
|
+ public readonly _onDown = new Emitter<void>();
|
|
+ public readonly onDown = this._onDown.event;
|
|
+ public readonly _onUp = new Emitter<void>();
|
|
+ public readonly onUp = this._onUp.event;
|
|
+
|
|
+ // Unused because the server connection will never permanently close.
|
|
+ private readonly _onClose = new Emitter<void>();
|
|
+ public readonly onClose = this._onClose.event;
|
|
+
|
|
+ public constructor() {
|
|
+ // TODO: down/up
|
|
+ this.server = new Server({
|
|
+ onMessage: this.$onMessage,
|
|
+ onClose: this.onClose,
|
|
+ onDown: this.onDown,
|
|
+ onUp: this.onUp,
|
|
+ send: (message: string): void => {
|
|
+ this._onMessage.fire(message);
|
|
+ }
|
|
+ });
|
|
+ }
|
|
+
|
|
+ public send(message: string): void {
|
|
+ this._$onMessage.fire(message);
|
|
+ }
|
|
+}
|
|
diff --git a/src/vs/server/node/connection.ts b/src/vs/server/node/connection.ts
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..36e80fb6966ae2cb53c98f3d31e2193d00c509c3
|
|
--- /dev/null
|
|
+++ b/src/vs/server/node/connection.ts
|
|
@@ -0,0 +1,157 @@
|
|
+import * as cp from 'child_process';
|
|
+import { getPathFromAmdModule } from 'vs/base/common/amd';
|
|
+import { VSBuffer } from 'vs/base/common/buffer';
|
|
+import { Emitter } from 'vs/base/common/event';
|
|
+import { ISocket } from 'vs/base/parts/ipc/common/ipc.net';
|
|
+import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
|
|
+import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
|
|
+import { ILogService } from 'vs/platform/log/common/log';
|
|
+import { getNlsConfiguration } from 'vs/server/node/nls';
|
|
+import { Protocol } from 'vs/server/node/protocol';
|
|
+import { IExtHostReadyMessage } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
|
|
+
|
|
+export abstract class Connection {
|
|
+ private readonly _onClose = new Emitter<void>();
|
|
+ public readonly onClose = this._onClose.event;
|
|
+ private disposed = false;
|
|
+ private _offline: number | undefined;
|
|
+
|
|
+ public constructor(protected protocol: Protocol, public readonly token: string) {}
|
|
+
|
|
+ public get offline(): number | undefined {
|
|
+ return this._offline;
|
|
+ }
|
|
+
|
|
+ public reconnect(socket: ISocket, buffer: VSBuffer): void {
|
|
+ this._offline = undefined;
|
|
+ this.doReconnect(socket, buffer);
|
|
+ }
|
|
+
|
|
+ public dispose(): void {
|
|
+ if (!this.disposed) {
|
|
+ this.disposed = true;
|
|
+ this.doDispose();
|
|
+ this._onClose.fire();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected setOffline(): void {
|
|
+ if (!this._offline) {
|
|
+ this._offline = Date.now();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Set up the connection on a new socket.
|
|
+ */
|
|
+ protected abstract doReconnect(socket: ISocket, buffer: VSBuffer): void;
|
|
+ protected abstract doDispose(): void;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Used for all the IPC channels.
|
|
+ */
|
|
+export class ManagementConnection extends Connection {
|
|
+ public constructor(protected protocol: Protocol, token: string) {
|
|
+ super(protocol, token);
|
|
+ protocol.onClose(() => this.dispose()); // Explicit close.
|
|
+ protocol.onSocketClose(() => this.setOffline()); // Might reconnect.
|
|
+ }
|
|
+
|
|
+ protected doDispose(): void {
|
|
+ this.protocol.sendDisconnect();
|
|
+ this.protocol.dispose();
|
|
+ this.protocol.getUnderlyingSocket().destroy();
|
|
+ }
|
|
+
|
|
+ protected doReconnect(socket: ISocket, buffer: VSBuffer): void {
|
|
+ this.protocol.beginAcceptReconnection(socket, buffer);
|
|
+ this.protocol.endAcceptReconnection();
|
|
+ }
|
|
+}
|
|
+
|
|
+export class ExtensionHostConnection extends Connection {
|
|
+ private process?: cp.ChildProcess;
|
|
+
|
|
+ public constructor(
|
|
+ locale:string, protocol: Protocol, buffer: VSBuffer, token: string,
|
|
+ private readonly log: ILogService,
|
|
+ private readonly environment: INativeEnvironmentService,
|
|
+ ) {
|
|
+ super(protocol, token);
|
|
+ this.protocol.dispose();
|
|
+ this.spawn(locale, buffer).then((p) => this.process = p);
|
|
+ this.protocol.getUnderlyingSocket().pause();
|
|
+ }
|
|
+
|
|
+ protected doDispose(): void {
|
|
+ if (this.process) {
|
|
+ this.process.kill();
|
|
+ }
|
|
+ this.protocol.getUnderlyingSocket().destroy();
|
|
+ }
|
|
+
|
|
+ protected doReconnect(socket: ISocket, buffer: VSBuffer): void {
|
|
+ // This is just to set the new socket.
|
|
+ this.protocol.beginAcceptReconnection(socket, null);
|
|
+ this.protocol.dispose();
|
|
+ this.sendInitMessage(buffer);
|
|
+ }
|
|
+
|
|
+ private sendInitMessage(buffer: VSBuffer): void {
|
|
+ const socket = this.protocol.getUnderlyingSocket();
|
|
+ socket.pause();
|
|
+ this.process!.send({ // Process must be set at this point.
|
|
+ type: 'VSCODE_EXTHOST_IPC_SOCKET',
|
|
+ initialDataChunk: (buffer.buffer as Buffer).toString('base64'),
|
|
+ skipWebSocketFrames: this.protocol.getSocket() instanceof NodeSocket,
|
|
+ }, socket);
|
|
+ }
|
|
+
|
|
+ private async spawn(locale: string, buffer: VSBuffer): Promise<cp.ChildProcess> {
|
|
+ const config = await getNlsConfiguration(locale, this.environment.userDataPath);
|
|
+ const proc = cp.fork(
|
|
+ getPathFromAmdModule(require, 'bootstrap-fork'),
|
|
+ [ '--type=extensionHost' ],
|
|
+ {
|
|
+ env: {
|
|
+ ...process.env,
|
|
+ AMD_ENTRYPOINT: 'vs/workbench/services/extensions/node/extensionHostProcess',
|
|
+ PIPE_LOGGING: 'true',
|
|
+ VERBOSE_LOGGING: 'true',
|
|
+ VSCODE_EXTHOST_WILL_SEND_SOCKET: 'true',
|
|
+ VSCODE_HANDLES_UNCAUGHT_ERRORS: 'true',
|
|
+ VSCODE_LOG_STACK: 'false',
|
|
+ VSCODE_LOG_LEVEL: process.env.LOG_LEVEL,
|
|
+ VSCODE_NLS_CONFIG: JSON.stringify(config),
|
|
+ },
|
|
+ silent: true,
|
|
+ },
|
|
+ );
|
|
+
|
|
+ proc.on('error', () => this.dispose());
|
|
+ proc.on('exit', () => this.dispose());
|
|
+ if (proc.stdout && proc.stderr) {
|
|
+ proc.stdout.setEncoding('utf8').on('data', (d) => this.log.info('Extension host stdout', d));
|
|
+ proc.stderr.setEncoding('utf8').on('data', (d) => this.log.error('Extension host stderr', d));
|
|
+ }
|
|
+ proc.on('message', (event) => {
|
|
+ if (event && event.type === '__$console') {
|
|
+ const severity = (<any>this.log)[event.severity] ? event.severity : 'info';
|
|
+ (<any>this.log)[severity]('Extension host', event.arguments);
|
|
+ }
|
|
+ if (event && event.type === 'VSCODE_EXTHOST_DISCONNECTED') {
|
|
+ this.setOffline();
|
|
+ }
|
|
+ });
|
|
+
|
|
+ const listen = (message: IExtHostReadyMessage) => {
|
|
+ if (message.type === 'VSCODE_EXTHOST_IPC_READY') {
|
|
+ proc.removeListener('message', listen);
|
|
+ this.sendInitMessage(buffer);
|
|
+ }
|
|
+ };
|
|
+
|
|
+ return proc.on('message', listen);
|
|
+ }
|
|
+}
|
|
diff --git a/src/vs/server/node/insights.ts b/src/vs/server/node/insights.ts
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..a0ece345f28f06afb2af12fe4901ad228b2475a4
|
|
--- /dev/null
|
|
+++ b/src/vs/server/node/insights.ts
|
|
@@ -0,0 +1,124 @@
|
|
+import * as appInsights from 'applicationinsights';
|
|
+import * as https from 'https';
|
|
+import * as http from 'http';
|
|
+import * as os from 'os';
|
|
+
|
|
+class Channel {
|
|
+ public get _sender() {
|
|
+ throw new Error('unimplemented');
|
|
+ }
|
|
+ public get _buffer() {
|
|
+ throw new Error('unimplemented');
|
|
+ }
|
|
+
|
|
+ public setUseDiskRetryCaching(): void {
|
|
+ throw new Error('unimplemented');
|
|
+ }
|
|
+ public send(): void {
|
|
+ throw new Error('unimplemented');
|
|
+ }
|
|
+ public triggerSend(): void {
|
|
+ throw new Error('unimplemented');
|
|
+ }
|
|
+}
|
|
+
|
|
+export class TelemetryClient {
|
|
+ public context: any = undefined;
|
|
+ public commonProperties: any = undefined;
|
|
+ public config: any = {};
|
|
+
|
|
+ public channel: any = new Channel();
|
|
+
|
|
+ public addTelemetryProcessor(): void {
|
|
+ throw new Error('unimplemented');
|
|
+ }
|
|
+
|
|
+ public clearTelemetryProcessors(): void {
|
|
+ throw new Error('unimplemented');
|
|
+ }
|
|
+
|
|
+ public runTelemetryProcessors(): void {
|
|
+ throw new Error('unimplemented');
|
|
+ }
|
|
+
|
|
+ public trackTrace(): void {
|
|
+ throw new Error('unimplemented');
|
|
+ }
|
|
+
|
|
+ public trackMetric(): void {
|
|
+ throw new Error('unimplemented');
|
|
+ }
|
|
+
|
|
+ public trackException(): void {
|
|
+ throw new Error('unimplemented');
|
|
+ }
|
|
+
|
|
+ public trackRequest(): void {
|
|
+ throw new Error('unimplemented');
|
|
+ }
|
|
+
|
|
+ public trackDependency(): void {
|
|
+ throw new Error('unimplemented');
|
|
+ }
|
|
+
|
|
+ public track(): void {
|
|
+ throw new Error('unimplemented');
|
|
+ }
|
|
+
|
|
+ public trackNodeHttpRequestSync(): void {
|
|
+ throw new Error('unimplemented');
|
|
+ }
|
|
+
|
|
+ public trackNodeHttpRequest(): void {
|
|
+ throw new Error('unimplemented');
|
|
+ }
|
|
+
|
|
+ public trackNodeHttpDependency(): void {
|
|
+ throw new Error('unimplemented');
|
|
+ }
|
|
+
|
|
+ public trackEvent(options: appInsights.Contracts.EventTelemetry): void {
|
|
+ if (!options.properties) {
|
|
+ options.properties = {};
|
|
+ }
|
|
+ if (!options.measurements) {
|
|
+ options.measurements = {};
|
|
+ }
|
|
+
|
|
+ try {
|
|
+ const cpus = os.cpus();
|
|
+ options.measurements.cores = cpus.length;
|
|
+ options.properties['common.cpuModel'] = cpus[0].model;
|
|
+ } catch (error) {}
|
|
+
|
|
+ try {
|
|
+ options.measurements.memoryFree = os.freemem();
|
|
+ options.measurements.memoryTotal = os.totalmem();
|
|
+ } catch (error) {}
|
|
+
|
|
+ try {
|
|
+ options.properties['common.shell'] = os.userInfo().shell;
|
|
+ options.properties['common.release'] = os.release();
|
|
+ options.properties['common.arch'] = os.arch();
|
|
+ } catch (error) {}
|
|
+
|
|
+ try {
|
|
+ const url = process.env.TELEMETRY_URL || 'https://v1.telemetry.coder.com/track';
|
|
+ const request = (/^http:/.test(url) ? http : https).request(url, {
|
|
+ method: 'POST',
|
|
+ headers: {
|
|
+ 'Content-Type': 'application/json',
|
|
+ },
|
|
+ });
|
|
+ request.on('error', () => { /* We don't care. */ });
|
|
+ request.write(JSON.stringify(options));
|
|
+ request.end();
|
|
+ } catch (error) {}
|
|
+ }
|
|
+
|
|
+ public flush(options: { callback: (v: string) => void }): void {
|
|
+ if (options.callback) {
|
|
+ options.callback('');
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/vs/server/node/ipc.ts b/src/vs/server/node/ipc.ts
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..5e560eb46e6a0a18c91e440c655ac0d44b09b6dd
|
|
--- /dev/null
|
|
+++ b/src/vs/server/node/ipc.ts
|
|
@@ -0,0 +1,61 @@
|
|
+import * as cp from 'child_process';
|
|
+import { Emitter } from 'vs/base/common/event';
|
|
+
|
|
+enum ControlMessage {
|
|
+ okToChild = 'ok>',
|
|
+ okFromChild = 'ok<',
|
|
+}
|
|
+
|
|
+interface RelaunchMessage {
|
|
+ type: 'relaunch';
|
|
+ version: string;
|
|
+}
|
|
+
|
|
+export type Message = RelaunchMessage;
|
|
+
|
|
+class IpcMain {
|
|
+ protected readonly _onMessage = new Emitter<Message>();
|
|
+ public readonly onMessage = this._onMessage.event;
|
|
+
|
|
+ public handshake(child?: cp.ChildProcess): Promise<void> {
|
|
+ return new Promise((resolve, reject) => {
|
|
+ const target = child || process;
|
|
+ if (!target.send) {
|
|
+ throw new Error('Not spawned with IPC enabled');
|
|
+ }
|
|
+ target.on('message', (message) => {
|
|
+ if (message === child ? ControlMessage.okFromChild : ControlMessage.okToChild) {
|
|
+ target.removeAllListeners();
|
|
+ target.on('message', (msg) => this._onMessage.fire(msg));
|
|
+ if (child) {
|
|
+ target.send!(ControlMessage.okToChild);
|
|
+ }
|
|
+ resolve();
|
|
+ }
|
|
+ });
|
|
+ if (child) {
|
|
+ child.once('error', reject);
|
|
+ child.once('exit', (code) => {
|
|
+ const error = new Error(`Unexpected exit with code ${code}`);
|
|
+ (error as any).code = code;
|
|
+ reject(error);
|
|
+ });
|
|
+ } else {
|
|
+ target.send(ControlMessage.okFromChild);
|
|
+ }
|
|
+ });
|
|
+ }
|
|
+
|
|
+ public relaunch(version: string): void {
|
|
+ this.send({ type: 'relaunch', version });
|
|
+ }
|
|
+
|
|
+ private send(message: Message): void {
|
|
+ if (!process.send) {
|
|
+ throw new Error('Not a child process with IPC enabled');
|
|
+ }
|
|
+ process.send(message);
|
|
+ }
|
|
+}
|
|
+
|
|
+export const ipcMain = new IpcMain();
|
|
diff --git a/src/vs/server/node/logger.ts b/src/vs/server/node/logger.ts
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..2a39c524aaa1b4031e04a631842f30b6fec3d98a
|
|
--- /dev/null
|
|
+++ b/src/vs/server/node/logger.ts
|
|
@@ -0,0 +1,2 @@
|
|
+import { logger as baseLogger } from '@coder/logger';
|
|
+export const logger = baseLogger.named('vscode');
|
|
diff --git a/src/vs/server/node/marketplace.ts b/src/vs/server/node/marketplace.ts
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..8956fc40d48448b9932036c4c286464881807338
|
|
--- /dev/null
|
|
+++ b/src/vs/server/node/marketplace.ts
|
|
@@ -0,0 +1,174 @@
|
|
+import * as fs from 'fs';
|
|
+import * as path from 'path';
|
|
+import * as tarStream from 'tar-stream';
|
|
+import * as util from 'util';
|
|
+import { CancellationToken } from 'vs/base/common/cancellation';
|
|
+import { mkdirp } from 'vs/base/node/pfs';
|
|
+import * as vszip from 'vs/base/node/zip';
|
|
+import * as nls from 'vs/nls';
|
|
+import product from 'vs/platform/product/common/product';
|
|
+
|
|
+// We will be overriding these, so keep a reference to the original.
|
|
+const vszipExtract = vszip.extract;
|
|
+const vszipBuffer = vszip.buffer;
|
|
+
|
|
+export interface IExtractOptions {
|
|
+ overwrite?: boolean;
|
|
+ /**
|
|
+ * Source path within the TAR/ZIP archive. Only the files
|
|
+ * contained in this path will be extracted.
|
|
+ */
|
|
+ sourcePath?: string;
|
|
+}
|
|
+
|
|
+export interface IFile {
|
|
+ path: string;
|
|
+ contents?: Buffer | string;
|
|
+ localPath?: string;
|
|
+}
|
|
+
|
|
+export const tar = async (tarPath: string, files: IFile[]): Promise<string> => {
|
|
+ const pack = tarStream.pack();
|
|
+ const chunks: Buffer[] = [];
|
|
+ const ended = new Promise<Buffer>((resolve) => {
|
|
+ pack.on('end', () => resolve(Buffer.concat(chunks)));
|
|
+ });
|
|
+ pack.on('data', (chunk: Buffer) => chunks.push(chunk));
|
|
+ for (let i = 0; i < files.length; i++) {
|
|
+ const file = files[i];
|
|
+ pack.entry({ name: file.path }, file.contents);
|
|
+ }
|
|
+ pack.finalize();
|
|
+ await util.promisify(fs.writeFile)(tarPath, await ended);
|
|
+ return tarPath;
|
|
+};
|
|
+
|
|
+export const extract = async (archivePath: string, extractPath: string, options: IExtractOptions = {}, token: CancellationToken): Promise<void> => {
|
|
+ try {
|
|
+ await extractTar(archivePath, extractPath, options, token);
|
|
+ } catch (error) {
|
|
+ if (error.toString().includes('Invalid tar header')) {
|
|
+ await vszipExtract(archivePath, extractPath, options, token);
|
|
+ }
|
|
+ }
|
|
+};
|
|
+
|
|
+export const buffer = (targetPath: string, filePath: string): Promise<Buffer> => {
|
|
+ return new Promise<Buffer>(async (resolve, reject) => {
|
|
+ try {
|
|
+ let done: boolean = false;
|
|
+ await extractAssets(targetPath, new RegExp(filePath), (assetPath: string, data: Buffer) => {
|
|
+ if (path.normalize(assetPath) === path.normalize(filePath)) {
|
|
+ done = true;
|
|
+ resolve(data);
|
|
+ }
|
|
+ });
|
|
+ if (!done) {
|
|
+ throw new Error('couldn\'t find asset ' + filePath);
|
|
+ }
|
|
+ } catch (error) {
|
|
+ if (error.toString().includes('Invalid tar header')) {
|
|
+ vszipBuffer(targetPath, filePath).then(resolve).catch(reject);
|
|
+ } else {
|
|
+ reject(error);
|
|
+ }
|
|
+ }
|
|
+ });
|
|
+};
|
|
+
|
|
+const extractAssets = async (tarPath: string, match: RegExp, callback: (path: string, data: Buffer) => void): Promise<void> => {
|
|
+ return new Promise<void>((resolve, reject): void => {
|
|
+ const extractor = tarStream.extract();
|
|
+ const fail = (error: Error) => {
|
|
+ extractor.destroy();
|
|
+ reject(error);
|
|
+ };
|
|
+ extractor.once('error', fail);
|
|
+ extractor.on('entry', async (header, stream, next) => {
|
|
+ const name = header.name;
|
|
+ if (match.test(name)) {
|
|
+ extractData(stream).then((data) => {
|
|
+ callback(name, data);
|
|
+ next();
|
|
+ }).catch(fail);
|
|
+ } else {
|
|
+ stream.on('end', () => next());
|
|
+ stream.resume(); // Just drain it.
|
|
+ }
|
|
+ });
|
|
+ extractor.on('finish', resolve);
|
|
+ fs.createReadStream(tarPath).pipe(extractor);
|
|
+ });
|
|
+};
|
|
+
|
|
+const extractData = (stream: NodeJS.ReadableStream): Promise<Buffer> => {
|
|
+ return new Promise((resolve, reject): void => {
|
|
+ const fileData: Buffer[] = [];
|
|
+ stream.on('error', reject);
|
|
+ stream.on('end', () => resolve(Buffer.concat(fileData)));
|
|
+ stream.on('data', (data) => fileData.push(data));
|
|
+ });
|
|
+};
|
|
+
|
|
+const extractTar = async (tarPath: string, targetPath: string, options: IExtractOptions = {}, token: CancellationToken): Promise<void> => {
|
|
+ return new Promise<void>((resolve, reject): void => {
|
|
+ const sourcePathRegex = new RegExp(options.sourcePath ? `^${options.sourcePath}` : '');
|
|
+ const extractor = tarStream.extract();
|
|
+ const fail = (error: Error) => {
|
|
+ extractor.destroy();
|
|
+ reject(error);
|
|
+ };
|
|
+ extractor.once('error', fail);
|
|
+ extractor.on('entry', async (header, stream, next) => {
|
|
+ const nextEntry = (): void => {
|
|
+ stream.on('end', () => next());
|
|
+ stream.resume();
|
|
+ };
|
|
+
|
|
+ const rawName = path.normalize(header.name);
|
|
+ if (token.isCancellationRequested || !sourcePathRegex.test(rawName)) {
|
|
+ return nextEntry();
|
|
+ }
|
|
+
|
|
+ const fileName = rawName.replace(sourcePathRegex, '');
|
|
+ const targetFileName = path.join(targetPath, fileName);
|
|
+ if (/\/$/.test(fileName)) {
|
|
+ return mkdirp(targetFileName).then(nextEntry);
|
|
+ }
|
|
+
|
|
+ const dirName = path.dirname(fileName);
|
|
+ const targetDirName = path.join(targetPath, dirName);
|
|
+ if (targetDirName.indexOf(targetPath) !== 0) {
|
|
+ return fail(new Error(nls.localize('invalid file', 'Error extracting {0}. Invalid file.', fileName)));
|
|
+ }
|
|
+
|
|
+ await mkdirp(targetDirName, undefined);
|
|
+
|
|
+ const fstream = fs.createWriteStream(targetFileName, { mode: header.mode });
|
|
+ fstream.once('close', () => next());
|
|
+ fstream.once('error', fail);
|
|
+ stream.pipe(fstream);
|
|
+ });
|
|
+ extractor.once('finish', resolve);
|
|
+ fs.createReadStream(tarPath).pipe(extractor);
|
|
+ });
|
|
+};
|
|
+
|
|
+/**
|
|
+ * Override original functionality so we can use a custom marketplace with
|
|
+ * either tars or zips.
|
|
+ */
|
|
+export const enableCustomMarketplace = (): void => {
|
|
+ (<any>product).extensionsGallery = { // Use `any` to override readonly.
|
|
+ serviceUrl: process.env.SERVICE_URL || 'https://extensions.coder.com/api',
|
|
+ itemUrl: process.env.ITEM_URL || '',
|
|
+ controlUrl: '',
|
|
+ recommendationsUrl: '',
|
|
+ ...(product.extensionsGallery || {}),
|
|
+ };
|
|
+
|
|
+ const target = vszip as typeof vszip;
|
|
+ target.zip = tar;
|
|
+ target.extract = extract;
|
|
+ target.buffer = buffer;
|
|
+};
|
|
diff --git a/src/vs/server/node/nls.ts b/src/vs/server/node/nls.ts
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..3d428a57d31f29c40f9c3ce45f715b443badf4e9
|
|
--- /dev/null
|
|
+++ b/src/vs/server/node/nls.ts
|
|
@@ -0,0 +1,88 @@
|
|
+import * as fs from 'fs';
|
|
+import * as path from 'path';
|
|
+import * as util from 'util';
|
|
+import { getPathFromAmdModule } from 'vs/base/common/amd';
|
|
+import * as lp from 'vs/base/node/languagePacks';
|
|
+import product from 'vs/platform/product/common/product';
|
|
+import { Translations } from 'vs/workbench/services/extensions/common/extensionPoints';
|
|
+
|
|
+const configurations = new Map<string, Promise<lp.NLSConfiguration>>();
|
|
+const metadataPath = path.join(getPathFromAmdModule(require, ''), 'nls.metadata.json');
|
|
+
|
|
+export const isInternalConfiguration = (config: lp.NLSConfiguration): config is lp.InternalNLSConfiguration => {
|
|
+ return config && !!(<lp.InternalNLSConfiguration>config)._languagePackId;
|
|
+};
|
|
+
|
|
+const DefaultConfiguration = {
|
|
+ locale: 'en',
|
|
+ availableLanguages: {},
|
|
+};
|
|
+
|
|
+export const getNlsConfiguration = async (locale: string, userDataPath: string): Promise<lp.NLSConfiguration> => {
|
|
+ const id = `${locale}: ${userDataPath}`;
|
|
+ if (!configurations.has(id)) {
|
|
+ configurations.set(id, new Promise(async (resolve) => {
|
|
+ const config = product.commit && await util.promisify(fs.exists)(metadataPath)
|
|
+ ? await lp.getNLSConfiguration(product.commit, userDataPath, metadataPath, locale)
|
|
+ : DefaultConfiguration;
|
|
+ if (isInternalConfiguration(config)) {
|
|
+ config._languagePackSupport = true;
|
|
+ }
|
|
+ // If the configuration has no results keep trying since code-server
|
|
+ // doesn't restart when a language is installed so this result would
|
|
+ // persist (the plugin might not be installed yet or something).
|
|
+ if (config.locale !== 'en' && config.locale !== 'en-us' && Object.keys(config.availableLanguages).length === 0) {
|
|
+ configurations.delete(id);
|
|
+ }
|
|
+ resolve(config);
|
|
+ }));
|
|
+ }
|
|
+ return configurations.get(id)!;
|
|
+};
|
|
+
|
|
+export const getTranslations = async (locale: string, userDataPath: string): Promise<Translations> => {
|
|
+ const config = await getNlsConfiguration(locale, userDataPath);
|
|
+ if (isInternalConfiguration(config)) {
|
|
+ try {
|
|
+ return JSON.parse(await util.promisify(fs.readFile)(config._translationsConfigFile, 'utf8'));
|
|
+ } catch (error) { /* Nothing yet. */}
|
|
+ }
|
|
+ return {};
|
|
+};
|
|
+
|
|
+export const getLocaleFromConfig = async (userDataPath: string): Promise<string> => {
|
|
+ const files = ['locale.json', 'argv.json'];
|
|
+ for (let i = 0; i < files.length; ++i) {
|
|
+ try {
|
|
+ const localeConfigUri = path.join(userDataPath, 'User', files[i]);
|
|
+ const content = stripComments(await util.promisify(fs.readFile)(localeConfigUri, 'utf8'));
|
|
+ return JSON.parse(content).locale;
|
|
+ } catch (error) { /* Ignore. */ }
|
|
+ }
|
|
+ return 'en';
|
|
+};
|
|
+
|
|
+// Taken from src/main.js in the main VS Code source.
|
|
+const stripComments = (content: string): string => {
|
|
+ const regexp = /('(?:[^\\']*(?:\\.)?)*')|('(?:[^\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g;
|
|
+
|
|
+ return content.replace(regexp, (match, _m1, _m2, m3, m4) => {
|
|
+ // Only one of m1, m2, m3, m4 matches
|
|
+ if (m3) {
|
|
+ // A block comment. Replace with nothing
|
|
+ return '';
|
|
+ } else if (m4) {
|
|
+ // A line comment. If it ends in \r?\n then keep it.
|
|
+ const length_1 = m4.length;
|
|
+ if (length_1 > 2 && m4[length_1 - 1] === '\n') {
|
|
+ return m4[length_1 - 2] === '\r' ? '\r\n' : '\n';
|
|
+ }
|
|
+ else {
|
|
+ return '';
|
|
+ }
|
|
+ } else {
|
|
+ // We match a string
|
|
+ return match;
|
|
+ }
|
|
+ });
|
|
+};
|
|
diff --git a/src/vs/server/node/protocol.ts b/src/vs/server/node/protocol.ts
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..3c74512192aec6220216bc8563b3127b9cfd5fbf
|
|
--- /dev/null
|
|
+++ b/src/vs/server/node/protocol.ts
|
|
@@ -0,0 +1,73 @@
|
|
+import * as net from 'net';
|
|
+import { VSBuffer } from 'vs/base/common/buffer';
|
|
+import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net';
|
|
+import { NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
|
|
+import { AuthRequest, ConnectionTypeRequest, HandshakeMessage } from 'vs/platform/remote/common/remoteAgentConnection';
|
|
+
|
|
+export interface SocketOptions {
|
|
+ readonly reconnectionToken: string;
|
|
+ readonly reconnection: boolean;
|
|
+ readonly skipWebSocketFrames: boolean;
|
|
+}
|
|
+
|
|
+export class Protocol extends PersistentProtocol {
|
|
+ public constructor(socket: net.Socket, public readonly options: SocketOptions) {
|
|
+ super(
|
|
+ options.skipWebSocketFrames
|
|
+ ? new NodeSocket(socket)
|
|
+ : new WebSocketNodeSocket(new NodeSocket(socket)),
|
|
+ );
|
|
+ }
|
|
+
|
|
+ public getUnderlyingSocket(): net.Socket {
|
|
+ const socket = this.getSocket();
|
|
+ return socket instanceof NodeSocket
|
|
+ ? socket.socket
|
|
+ : (socket as WebSocketNodeSocket).socket.socket;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Perform a handshake to get a connection request.
|
|
+ */
|
|
+ public handshake(): Promise<ConnectionTypeRequest> {
|
|
+ return new Promise((resolve, reject) => {
|
|
+ const handler = this.onControlMessage((rawMessage) => {
|
|
+ try {
|
|
+ const message = JSON.parse(rawMessage.toString());
|
|
+ switch (message.type) {
|
|
+ case 'auth': return this.authenticate(message);
|
|
+ case 'connectionType':
|
|
+ handler.dispose();
|
|
+ return resolve(message);
|
|
+ default: throw new Error('Unrecognized message type');
|
|
+ }
|
|
+ } catch (error) {
|
|
+ handler.dispose();
|
|
+ reject(error);
|
|
+ }
|
|
+ });
|
|
+ });
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * TODO: This ignores the authentication process entirely for now.
|
|
+ */
|
|
+ private authenticate(_message: AuthRequest): void {
|
|
+ this.sendMessage({ type: 'sign', data: '' });
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * TODO: implement.
|
|
+ */
|
|
+ public tunnel(): void {
|
|
+ throw new Error('Tunnel is not implemented yet');
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Send a handshake message. In the case of the extension host, it just sends
|
|
+ * back a debug port.
|
|
+ */
|
|
+ public sendMessage(message: HandshakeMessage | { debugPort?: number } ): void {
|
|
+ this.sendControl(VSBuffer.fromString(JSON.stringify(message)));
|
|
+ }
|
|
+}
|
|
diff --git a/src/vs/server/node/server.ts b/src/vs/server/node/server.ts
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..4b88fedb2f05d300fb50978e63721d4d04b7fb5f
|
|
--- /dev/null
|
|
+++ b/src/vs/server/node/server.ts
|
|
@@ -0,0 +1,285 @@
|
|
+import * as fs from 'fs';
|
|
+import * as net from 'net';
|
|
+import * as path from 'path';
|
|
+import { Emitter } from 'vs/base/common/event';
|
|
+import { Schemas } from 'vs/base/common/network';
|
|
+import { URI } from 'vs/base/common/uri';
|
|
+import { getMachineId } from 'vs/base/node/id';
|
|
+import { ClientConnectionEvent, createChannelReceiver, IPCServer, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
|
|
+import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner';
|
|
+import { main } from "vs/code/node/cliProcessMain";
|
|
+import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
|
+import { ConfigurationService } from 'vs/platform/configuration/common/configurationService';
|
|
+import { ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc';
|
|
+import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
|
+import { ParsedArgs } from 'vs/platform/environment/node/argv';
|
|
+import { EnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
|
|
+import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService';
|
|
+import { IExtensionGalleryService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
|
+import { ExtensionManagementChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc';
|
|
+import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
|
|
+import { IFileService } from 'vs/platform/files/common/files';
|
|
+import { FileService } from 'vs/platform/files/common/fileService';
|
|
+import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
|
|
+import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
|
+import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
|
|
+import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
|
+import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
|
|
+import { LocalizationsService } from 'vs/platform/localizations/node/localizations';
|
|
+import { getLogLevel, ILogService } from 'vs/platform/log/common/log';
|
|
+import { LoggerChannel } from 'vs/platform/log/common/logIpc';
|
|
+import { SpdLogService } from 'vs/platform/log/node/spdlogService';
|
|
+import product from 'vs/platform/product/common/product';
|
|
+import { IProductService } from 'vs/platform/product/common/productService';
|
|
+import { ConnectionType, ConnectionTypeRequest } from 'vs/platform/remote/common/remoteAgentConnection';
|
|
+import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment';
|
|
+import { IRequestService } from 'vs/platform/request/common/request';
|
|
+import { RequestChannel } from 'vs/platform/request/common/requestIpc';
|
|
+import { RequestService } from 'vs/platform/request/node/requestService';
|
|
+import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry';
|
|
+import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
|
+import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
|
|
+import { combinedAppender, LogAppender, NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
|
|
+import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender';
|
|
+import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
|
|
+import { INodeProxyService, NodeProxyChannel } from 'vs/server/common/nodeProxy';
|
|
+import { TelemetryChannel } from 'vs/server/common/telemetry';
|
|
+import { Query, VscodeOptions, WorkbenchOptions } from 'vs/server/ipc';
|
|
+import { ExtensionEnvironmentChannel, FileProviderChannel, NodeProxyService } from 'vs/server/node/channel';
|
|
+import { Connection, ExtensionHostConnection, ManagementConnection } from 'vs/server/node/connection';
|
|
+import { TelemetryClient } from 'vs/server/node/insights';
|
|
+import { logger } from 'vs/server/node/logger';
|
|
+import { getLocaleFromConfig, getNlsConfiguration } from 'vs/server/node/nls';
|
|
+import { Protocol } from 'vs/server/node/protocol';
|
|
+import { getUriTransformer } from 'vs/server/node/util';
|
|
+import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from "vs/workbench/services/remote/common/remoteAgentFileSystemChannel";
|
|
+import { RemoteExtensionLogFileName } from 'vs/workbench/services/remote/common/remoteAgentService';
|
|
+
|
|
+export class Vscode {
|
|
+ public readonly _onDidClientConnect = new Emitter<ClientConnectionEvent>();
|
|
+ public readonly onDidClientConnect = this._onDidClientConnect.event;
|
|
+ private readonly ipc = new IPCServer<RemoteAgentConnectionContext>(this.onDidClientConnect);
|
|
+
|
|
+ private readonly maxExtraOfflineConnections = 0;
|
|
+ private readonly connections = new Map<ConnectionType, Map<string, Connection>>();
|
|
+
|
|
+ private readonly services = new ServiceCollection();
|
|
+ private servicesPromise?: Promise<void>;
|
|
+
|
|
+ public async cli(args: ParsedArgs): Promise<void> {
|
|
+ return main(args);
|
|
+ }
|
|
+
|
|
+ public async initialize(options: VscodeOptions): Promise<WorkbenchOptions> {
|
|
+ const transformer = getUriTransformer(options.remoteAuthority);
|
|
+ if (!this.servicesPromise) {
|
|
+ this.servicesPromise = this.initializeServices(options.args);
|
|
+ }
|
|
+ await this.servicesPromise;
|
|
+ const environment = this.services.get(IEnvironmentService) as INativeEnvironmentService;
|
|
+ const startPath = options.startPath;
|
|
+ const parseUrl = (url: string): URI => {
|
|
+ // This might be a fully-specified URL or just a path.
|
|
+ try {
|
|
+ return URI.parse(url, true);
|
|
+ } catch (error) {
|
|
+ return URI.from({
|
|
+ scheme: Schemas.vscodeRemote,
|
|
+ authority: options.remoteAuthority,
|
|
+ path: url,
|
|
+ });
|
|
+ }
|
|
+ };
|
|
+ return {
|
|
+ workbenchWebConfiguration: {
|
|
+ workspaceUri: startPath && startPath.workspace ? parseUrl(startPath.url) : undefined,
|
|
+ folderUri: startPath && !startPath.workspace ? parseUrl(startPath.url) : undefined,
|
|
+ remoteAuthority: options.remoteAuthority,
|
|
+ logLevel: getLogLevel(environment),
|
|
+ workspaceProvider: {
|
|
+ payload: [
|
|
+ ["userDataPath", environment.userDataPath],
|
|
+ ["enableProposedApi", JSON.stringify(options.args["enable-proposed-api"] || [])]
|
|
+ ],
|
|
+ },
|
|
+ },
|
|
+ remoteUserDataUri: transformer.transformOutgoing(URI.file(environment.userDataPath)),
|
|
+ productConfiguration: product,
|
|
+ nlsConfiguration: await getNlsConfiguration(environment.args.locale || await getLocaleFromConfig(environment.userDataPath), environment.userDataPath),
|
|
+ commit: product.commit || 'development',
|
|
+ };
|
|
+ }
|
|
+
|
|
+ public async handleWebSocket(socket: net.Socket, query: Query): Promise<true> {
|
|
+ if (!query.reconnectionToken) {
|
|
+ throw new Error('Reconnection token is missing from query parameters');
|
|
+ }
|
|
+ const protocol = new Protocol(socket, {
|
|
+ reconnectionToken: <string>query.reconnectionToken,
|
|
+ reconnection: query.reconnection === 'true',
|
|
+ skipWebSocketFrames: query.skipWebSocketFrames === 'true',
|
|
+ });
|
|
+ try {
|
|
+ await this.connect(await protocol.handshake(), protocol);
|
|
+ } catch (error) {
|
|
+ protocol.sendMessage({ type: 'error', reason: error.message });
|
|
+ protocol.dispose();
|
|
+ protocol.getSocket().dispose();
|
|
+ }
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ private async connect(message: ConnectionTypeRequest, protocol: Protocol): Promise<void> {
|
|
+ if (product.commit && message.commit !== product.commit) {
|
|
+ logger.warn(`Version mismatch (${message.commit} instead of ${product.commit})`);
|
|
+ }
|
|
+
|
|
+ switch (message.desiredConnectionType) {
|
|
+ case ConnectionType.ExtensionHost:
|
|
+ case ConnectionType.Management:
|
|
+ if (!this.connections.has(message.desiredConnectionType)) {
|
|
+ this.connections.set(message.desiredConnectionType, new Map());
|
|
+ }
|
|
+ const connections = this.connections.get(message.desiredConnectionType)!;
|
|
+
|
|
+ const ok = async () => {
|
|
+ return message.desiredConnectionType === ConnectionType.ExtensionHost
|
|
+ ? { debugPort: await this.getDebugPort() }
|
|
+ : { type: 'ok' };
|
|
+ };
|
|
+
|
|
+ const token = protocol.options.reconnectionToken;
|
|
+ if (protocol.options.reconnection && connections.has(token)) {
|
|
+ protocol.sendMessage(await ok());
|
|
+ const buffer = protocol.readEntireBuffer();
|
|
+ protocol.dispose();
|
|
+ return connections.get(token)!.reconnect(protocol.getSocket(), buffer);
|
|
+ } else if (protocol.options.reconnection || connections.has(token)) {
|
|
+ throw new Error(protocol.options.reconnection
|
|
+ ? 'Unrecognized reconnection token'
|
|
+ : 'Duplicate reconnection token'
|
|
+ );
|
|
+ }
|
|
+
|
|
+ protocol.sendMessage(await ok());
|
|
+
|
|
+ let connection: Connection;
|
|
+ if (message.desiredConnectionType === ConnectionType.Management) {
|
|
+ connection = new ManagementConnection(protocol, token);
|
|
+ this._onDidClientConnect.fire({
|
|
+ protocol, onDidClientDisconnect: connection.onClose,
|
|
+ });
|
|
+ // TODO: Need a way to match clients with a connection. For now
|
|
+ // dispose everything which only works because no extensions currently
|
|
+ // utilize long-running proxies.
|
|
+ (this.services.get(INodeProxyService) as NodeProxyService)._onUp.fire();
|
|
+ connection.onClose(() => (this.services.get(INodeProxyService) as NodeProxyService)._onDown.fire());
|
|
+ } else {
|
|
+ const buffer = protocol.readEntireBuffer();
|
|
+ connection = new ExtensionHostConnection(
|
|
+ message.args ? message.args.language : 'en',
|
|
+ protocol, buffer, token,
|
|
+ this.services.get(ILogService) as ILogService,
|
|
+ this.services.get(IEnvironmentService) as INativeEnvironmentService,
|
|
+ );
|
|
+ }
|
|
+ connections.set(token, connection);
|
|
+ connection.onClose(() => connections.delete(token));
|
|
+ this.disposeOldOfflineConnections(connections);
|
|
+ break;
|
|
+ case ConnectionType.Tunnel: return protocol.tunnel();
|
|
+ default: throw new Error('Unrecognized connection type');
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private disposeOldOfflineConnections(connections: Map<string, Connection>): void {
|
|
+ const offline = Array.from(connections.values())
|
|
+ .filter((connection) => typeof connection.offline !== 'undefined');
|
|
+ for (let i = 0, max = offline.length - this.maxExtraOfflineConnections; i < max; ++i) {
|
|
+ offline[i].dispose();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private async initializeServices(args: ParsedArgs): Promise<void> {
|
|
+ const environmentService = new EnvironmentService(args, process.execPath);
|
|
+ // https://github.com/cdr/code-server/issues/1693
|
|
+ fs.mkdirSync(environmentService.globalStorageHome.fsPath, { recursive: true });
|
|
+
|
|
+ const logService = new SpdLogService(RemoteExtensionLogFileName, environmentService.logsPath, getLogLevel(environmentService));
|
|
+ const fileService = new FileService(logService);
|
|
+ fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(logService));
|
|
+
|
|
+ const piiPaths = [
|
|
+ path.join(environmentService.userDataPath, 'clp'), // Language packs.
|
|
+ environmentService.appRoot,
|
|
+ environmentService.extensionsPath,
|
|
+ environmentService.builtinExtensionsPath,
|
|
+ ...environmentService.extraExtensionPaths,
|
|
+ ...environmentService.extraBuiltinExtensionPaths,
|
|
+ ];
|
|
+
|
|
+ this.ipc.registerChannel('logger', new LoggerChannel(logService));
|
|
+ this.ipc.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ExtensionHostDebugBroadcastChannel());
|
|
+
|
|
+ this.services.set(ILogService, logService);
|
|
+ this.services.set(IEnvironmentService, environmentService);
|
|
+
|
|
+ const configurationService = new ConfigurationService(environmentService.settingsResource, fileService);
|
|
+ await configurationService.initialize();
|
|
+ this.services.set(IConfigurationService, configurationService);
|
|
+
|
|
+ this.services.set(IRequestService, new SyncDescriptor(RequestService));
|
|
+ this.services.set(IFileService, fileService);
|
|
+ this.services.set(IProductService, { _serviceBrand: undefined, ...product });
|
|
+ this.services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService));
|
|
+ this.services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
|
|
+
|
|
+ if (!environmentService.disableTelemetry) {
|
|
+ this.services.set(ITelemetryService, new TelemetryService({
|
|
+ appender: combinedAppender(
|
|
+ new AppInsightsAppender('code-server', null, () => new TelemetryClient() as any, logService),
|
|
+ new LogAppender(logService),
|
|
+ ),
|
|
+ sendErrorTelemetry: true,
|
|
+ commonProperties: resolveCommonProperties(
|
|
+ product.commit, product.version, await getMachineId(),
|
|
+ [], environmentService.installSourcePath, 'code-server',
|
|
+ ),
|
|
+ piiPaths,
|
|
+ }, configurationService));
|
|
+ } else {
|
|
+ this.services.set(ITelemetryService, NullTelemetryService);
|
|
+ }
|
|
+
|
|
+ await new Promise((resolve) => {
|
|
+ const instantiationService = new InstantiationService(this.services);
|
|
+ this.services.set(ILocalizationsService, instantiationService.createInstance(LocalizationsService));
|
|
+ this.services.set(INodeProxyService, instantiationService.createInstance(NodeProxyService));
|
|
+
|
|
+ instantiationService.invokeFunction(() => {
|
|
+ instantiationService.createInstance(LogsDataCleaner);
|
|
+ const telemetryService = this.services.get(ITelemetryService) as ITelemetryService;
|
|
+ this.ipc.registerChannel('extensions', new ExtensionManagementChannel(
|
|
+ this.services.get(IExtensionManagementService) as IExtensionManagementService,
|
|
+ (context) => getUriTransformer(context.remoteAuthority),
|
|
+ ));
|
|
+ this.ipc.registerChannel('remoteextensionsenvironment', new ExtensionEnvironmentChannel(
|
|
+ environmentService, logService, telemetryService, '',
|
|
+ ));
|
|
+ this.ipc.registerChannel('request', new RequestChannel(this.services.get(IRequestService) as IRequestService));
|
|
+ this.ipc.registerChannel('telemetry', new TelemetryChannel(telemetryService));
|
|
+ this.ipc.registerChannel('nodeProxy', new NodeProxyChannel(this.services.get(INodeProxyService) as INodeProxyService));
|
|
+ this.ipc.registerChannel('localizations', <IServerChannel<any>>createChannelReceiver(this.services.get(ILocalizationsService) as ILocalizationsService));
|
|
+ this.ipc.registerChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME, new FileProviderChannel(environmentService, logService));
|
|
+ resolve(new ErrorTelemetry(telemetryService));
|
|
+ });
|
|
+ });
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * TODO: implement.
|
|
+ */
|
|
+ private async getDebugPort(): Promise<number | undefined> {
|
|
+ return undefined;
|
|
+ }
|
|
+}
|
|
diff --git a/src/vs/server/node/util.ts b/src/vs/server/node/util.ts
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..fa47e993b46802f1a26457649e9e8bc467a73bf2
|
|
--- /dev/null
|
|
+++ b/src/vs/server/node/util.ts
|
|
@@ -0,0 +1,13 @@
|
|
+import { URITransformer } from 'vs/base/common/uriIpc';
|
|
+
|
|
+export const getUriTransformer = (remoteAuthority: string): URITransformer => {
|
|
+ return new URITransformer(remoteAuthority);
|
|
+};
|
|
+
|
|
+/**
|
|
+ * Encode a path for opening via the folder or workspace query parameter. This
|
|
+ * preserves slashes so it can be edited by hand more easily.
|
|
+ */
|
|
+export const encodePath = (path: string): string => {
|
|
+ return path.split("/").map((p) => encodeURIComponent(p)).join("/");
|
|
+};
|
|
diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts
|
|
index 3d77009b908f61690a56dc589360627f6f5a3924..11deb1b99ac9d3baa4aa583d711a5e020b4379ec 100644
|
|
--- a/src/vs/workbench/api/browser/extensionHost.contribution.ts
|
|
+++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts
|
|
@@ -60,6 +60,7 @@ import './mainThreadComments';
|
|
import './mainThreadNotebook';
|
|
import './mainThreadTask';
|
|
import './mainThreadLabelService';
|
|
+import 'vs/server/browser/mainThreadNodeProxy';
|
|
import './mainThreadTunnelService';
|
|
import './mainThreadAuthentication';
|
|
import './mainThreadTimeline';
|
|
diff --git a/src/vs/workbench/api/browser/mainThreadStorage.ts b/src/vs/workbench/api/browser/mainThreadStorage.ts
|
|
index 7bc3904963bed2925f3640b6bd929347159dd3cf..c6db2368ae9eaca61889efcf3c49763c01ff7459 100644
|
|
--- a/src/vs/workbench/api/browser/mainThreadStorage.ts
|
|
+++ b/src/vs/workbench/api/browser/mainThreadStorage.ts
|
|
@@ -58,11 +58,11 @@ export class MainThreadStorage implements MainThreadStorageShape {
|
|
return JSON.parse(jsonValue);
|
|
}
|
|
|
|
- $setValue(shared: boolean, key: string, value: object): Promise<void> {
|
|
+ async $setValue(shared: boolean, key: string, value: object): Promise<void> {
|
|
let jsonValue: string;
|
|
try {
|
|
jsonValue = JSON.stringify(value);
|
|
- this._storageService.store(key, jsonValue, shared ? StorageScope.GLOBAL : StorageScope.WORKSPACE);
|
|
+ await this._storageService.store(key, jsonValue, shared ? StorageScope.GLOBAL : StorageScope.WORKSPACE);
|
|
} catch (err) {
|
|
return Promise.reject(err);
|
|
}
|
|
diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts
|
|
index 97793666ad8abf7d052ba96a88565042b21ebcec..13cd137db1e9435ef66ade3220774b1ddb608d91 100644
|
|
--- a/src/vs/workbench/api/common/extHost.api.impl.ts
|
|
+++ b/src/vs/workbench/api/common/extHost.api.impl.ts
|
|
@@ -68,6 +68,7 @@ import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransf
|
|
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
|
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
|
|
import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook';
|
|
+import { IExtHostNodeProxy } from 'vs/server/browser/extHostNodeProxy';
|
|
import { ExtHostTheming } from 'vs/workbench/api/common/extHostTheming';
|
|
import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService';
|
|
import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService';
|
|
@@ -97,6 +98,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
|
const extHostStorage = accessor.get(IExtHostStorage);
|
|
const extensionStoragePaths = accessor.get(IExtensionStoragePaths);
|
|
const extHostLogService = accessor.get(ILogService);
|
|
+ const extHostNodeProxy = accessor.get(IExtHostNodeProxy);
|
|
const extHostTunnelService = accessor.get(IExtHostTunnelService);
|
|
const extHostApiDeprecation = accessor.get(IExtHostApiDeprecationService);
|
|
const extHostWindow = accessor.get(IExtHostWindow);
|
|
@@ -107,6 +109,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
|
rpcProtocol.set(ExtHostContext.ExtHostConfiguration, extHostConfiguration);
|
|
rpcProtocol.set(ExtHostContext.ExtHostExtensionService, extensionService);
|
|
rpcProtocol.set(ExtHostContext.ExtHostStorage, extHostStorage);
|
|
+ rpcProtocol.set(ExtHostContext.ExtHostNodeProxy, extHostNodeProxy);
|
|
rpcProtocol.set(ExtHostContext.ExtHostTunnelService, extHostTunnelService);
|
|
rpcProtocol.set(ExtHostContext.ExtHostWindow, extHostWindow);
|
|
|
|
diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts
|
|
index eb5d8ea84551030a9d72918813fca7adb49a5cd8..da9eb521ca4c660de1bb23df52c18c0efe6f5979 100644
|
|
--- a/src/vs/workbench/api/common/extHost.protocol.ts
|
|
+++ b/src/vs/workbench/api/common/extHost.protocol.ts
|
|
@@ -769,6 +769,16 @@ export interface MainThreadLabelServiceShape extends IDisposable {
|
|
$unregisterResourceLabelFormatter(handle: number): void;
|
|
}
|
|
|
|
+export interface MainThreadNodeProxyShape extends IDisposable {
|
|
+ $send(message: string): void;
|
|
+}
|
|
+export interface ExtHostNodeProxyShape {
|
|
+ $onMessage(message: string): void;
|
|
+ $onClose(): void;
|
|
+ $onDown(): void;
|
|
+ $onUp(): void;
|
|
+}
|
|
+
|
|
export interface MainThreadSearchShape extends IDisposable {
|
|
$registerFileSearchProvider(handle: number, scheme: string): void;
|
|
$registerTextSearchProvider(handle: number, scheme: string): void;
|
|
@@ -1707,6 +1717,7 @@ export const MainContext = {
|
|
MainThreadWindow: createMainId<MainThreadWindowShape>('MainThreadWindow'),
|
|
MainThreadLabelService: createMainId<MainThreadLabelServiceShape>('MainThreadLabelService'),
|
|
MainThreadNotebook: createMainId<MainThreadNotebookShape>('MainThreadNotebook'),
|
|
+ MainThreadNodeProxy: createMainId<MainThreadNodeProxyShape>('MainThreadNodeProxy'),
|
|
MainThreadTheming: createMainId<MainThreadThemingShape>('MainThreadTheming'),
|
|
MainThreadTunnelService: createMainId<MainThreadTunnelServiceShape>('MainThreadTunnelService'),
|
|
MainThreadTimeline: createMainId<MainThreadTimelineShape>('MainThreadTimeline')
|
|
@@ -1745,6 +1756,7 @@ export const ExtHostContext = {
|
|
ExtHostOutputService: createMainId<ExtHostOutputServiceShape>('ExtHostOutputService'),
|
|
ExtHosLabelService: createMainId<ExtHostLabelServiceShape>('ExtHostLabelService'),
|
|
ExtHostNotebook: createMainId<ExtHostNotebookShape>('ExtHostNotebook'),
|
|
+ ExtHostNodeProxy: createMainId<ExtHostNodeProxyShape>('ExtHostNodeProxy'),
|
|
ExtHostTheming: createMainId<ExtHostThemingShape>('ExtHostTheming'),
|
|
ExtHostTunnelService: createMainId<ExtHostTunnelServiceShape>('ExtHostTunnelService'),
|
|
ExtHostAuthentication: createMainId<ExtHostAuthenticationShape>('ExtHostAuthentication'),
|
|
diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts
|
|
index 34639e18b6fb567feaf19cf20bd312b6b578723f..9c22fe6f090f3cfceea5c3f41695a1ab1d797a19 100644
|
|
--- a/src/vs/workbench/api/common/extHostExtensionService.ts
|
|
+++ b/src/vs/workbench/api/common/extHostExtensionService.ts
|
|
@@ -32,6 +32,7 @@ import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitData
|
|
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
|
|
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
|
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
|
+import { IExtHostNodeProxy } from 'vs/server/browser/extHostNodeProxy';
|
|
import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService';
|
|
import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService';
|
|
import { Emitter, Event } from 'vs/base/common/event';
|
|
@@ -82,6 +83,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
|
protected readonly _extHostWorkspace: ExtHostWorkspace;
|
|
protected readonly _extHostConfiguration: ExtHostConfiguration;
|
|
protected readonly _logService: ILogService;
|
|
+ protected readonly _nodeProxy: IExtHostNodeProxy;
|
|
protected readonly _extHostTunnelService: IExtHostTunnelService;
|
|
protected readonly _extHostTerminalService: IExtHostTerminalService;
|
|
|
|
@@ -114,6 +116,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
|
@ILogService logService: ILogService,
|
|
@IExtHostInitDataService initData: IExtHostInitDataService,
|
|
@IExtensionStoragePaths storagePath: IExtensionStoragePaths,
|
|
+ @IExtHostNodeProxy nodeProxy: IExtHostNodeProxy,
|
|
@IExtHostTunnelService extHostTunnelService: IExtHostTunnelService,
|
|
@IExtHostTerminalService extHostTerminalService: IExtHostTerminalService
|
|
) {
|
|
@@ -125,6 +128,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
|
this._extHostWorkspace = extHostWorkspace;
|
|
this._extHostConfiguration = extHostConfiguration;
|
|
this._logService = logService;
|
|
+ this._nodeProxy = nodeProxy;
|
|
this._extHostTunnelService = extHostTunnelService;
|
|
this._extHostTerminalService = extHostTerminalService;
|
|
this._disposables = new DisposableStore();
|
|
@@ -355,7 +359,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
|
|
|
const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup);
|
|
return Promise.all([
|
|
- this._loadCommonJSModule<IExtensionModule>(joinPath(extensionDescription.extensionLocation, entryPoint), activationTimesBuilder),
|
|
+ this._loadCommonJSModule<IExtensionModule>(joinPath(extensionDescription.extensionLocation, entryPoint), activationTimesBuilder, !extensionDescription.browser),
|
|
this._loadExtensionContext(extensionDescription)
|
|
]).then(values => {
|
|
return AbstractExtHostExtensionService._callActivate(this._logService, extensionDescription.identifier, values[0], values[1], activationTimesBuilder);
|
|
@@ -754,7 +758,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
|
|
|
protected abstract _beforeAlmostReadyToRunExtensions(): Promise<void>;
|
|
protected abstract _getEntryPoint(extensionDescription: IExtensionDescription): string | undefined;
|
|
- protected abstract _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T>;
|
|
+ protected abstract _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder, isRemote?: boolean): Promise<T>;
|
|
public abstract async $setRemoteEnvironment(env: { [key: string]: string | null }): Promise<void>;
|
|
}
|
|
|
|
diff --git a/src/vs/workbench/api/node/extHost.node.services.ts b/src/vs/workbench/api/node/extHost.node.services.ts
|
|
index b3c89e51cfc25a53293a352a2a8ad50d5f26d595..e21abe4e13bc25a5b72f556bbfb61085842faeb7 100644
|
|
--- a/src/vs/workbench/api/node/extHost.node.services.ts
|
|
+++ b/src/vs/workbench/api/node/extHost.node.services.ts
|
|
@@ -3,6 +3,8 @@
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
+import { IExtHostNodeProxy } from 'vs/server/browser/extHostNodeProxy';
|
|
+import { NotImplementedProxy } from 'vs/base/common/types';
|
|
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
|
import { ExtHostOutputService2 } from 'vs/workbench/api/node/extHostOutputService';
|
|
import { ExtHostTerminalService } from 'vs/workbench/api/node/extHostTerminalService';
|
|
@@ -36,3 +38,4 @@ registerSingleton(IExtHostSearch, NativeExtHostSearch);
|
|
registerSingleton(IExtHostTask, ExtHostTask);
|
|
registerSingleton(IExtHostTerminalService, ExtHostTerminalService);
|
|
registerSingleton(IExtHostTunnelService, ExtHostTunnelService);
|
|
+registerSingleton(IExtHostNodeProxy, class extends NotImplementedProxy<IExtHostNodeProxy>(String(IExtHostNodeProxy)) { whenReady = Promise.resolve(); });
|
|
diff --git a/src/vs/workbench/api/worker/extHost.worker.services.ts b/src/vs/workbench/api/worker/extHost.worker.services.ts
|
|
index 3843fdec386edc09a1d361b63de892a04e0070ed..8aac4df527857e964798362a69f5591bef07c165 100644
|
|
--- a/src/vs/workbench/api/worker/extHost.worker.services.ts
|
|
+++ b/src/vs/workbench/api/worker/extHost.worker.services.ts
|
|
@@ -8,6 +8,7 @@ import { ILogService } from 'vs/platform/log/common/log';
|
|
import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
|
|
import { ExtHostExtensionService } from 'vs/workbench/api/worker/extHostExtensionService';
|
|
import { ExtHostLogService } from 'vs/workbench/api/worker/extHostLogService';
|
|
+import { ExtHostNodeProxy, IExtHostNodeProxy } from 'vs/server/browser/extHostNodeProxy';
|
|
|
|
// #########################################################################
|
|
// ### ###
|
|
@@ -17,3 +18,4 @@ import { ExtHostLogService } from 'vs/workbench/api/worker/extHostLogService';
|
|
|
|
registerSingleton(IExtHostExtensionService, ExtHostExtensionService);
|
|
registerSingleton(ILogService, ExtHostLogService);
|
|
+registerSingleton(IExtHostNodeProxy, ExtHostNodeProxy);
|
|
diff --git a/src/vs/workbench/api/worker/extHostExtensionService.ts b/src/vs/workbench/api/worker/extHostExtensionService.ts
|
|
index c71ab1c7da462da8f4a12146d45e6cde7f06ad81..572b07ff2516154f49ab9e02bfcab2b4d8b3009f 100644
|
|
--- a/src/vs/workbench/api/worker/extHostExtensionService.ts
|
|
+++ b/src/vs/workbench/api/worker/extHostExtensionService.ts
|
|
@@ -9,6 +9,7 @@ import { AbstractExtHostExtensionService } from 'vs/workbench/api/common/extHost
|
|
import { URI } from 'vs/base/common/uri';
|
|
import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor';
|
|
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
|
+import { loadCommonJSModule } from 'vs/server/browser/worker';
|
|
|
|
class WorkerRequireInterceptor extends RequireInterceptor {
|
|
|
|
@@ -42,10 +43,15 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
|
}
|
|
|
|
protected _getEntryPoint(extensionDescription: IExtensionDescription): string | undefined {
|
|
- return extensionDescription.browser;
|
|
+ // NOTE@coder: We can support regular Node modules as well. These will just
|
|
+ // require the root of the extension.
|
|
+ return extensionDescription.browser || ".";
|
|
}
|
|
|
|
- protected async _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
|
|
+ protected async _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder, isRemote?: boolean): Promise<T> {
|
|
+ if (isRemote) {
|
|
+ return loadCommonJSModule(module, activationTimesBuilder, this._nodeProxy, this._logService, this._fakeModules!.getModule('vscode', module));
|
|
+ }
|
|
|
|
module = module.with({ path: ensureSuffix(module.path, '.js') });
|
|
const response = await fetch(module.toString(true));
|
|
diff --git a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css
|
|
index ced2d815834e40a1543e80516472799075980733..dfcae73e8a042307600c67f163aa00ba9e0762f4 100644
|
|
--- a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css
|
|
+++ b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css
|
|
@@ -55,6 +55,10 @@
|
|
align-items: center;
|
|
justify-content: center;
|
|
order: -1;
|
|
+
|
|
+ /* NOTE@coder: Hide since it doesn't seem to do anything when used with
|
|
+ code-server except open the VS Code repository. */
|
|
+ display: none !important;
|
|
}
|
|
|
|
.monaco-workbench .activitybar > .content > .home-bar > .home-bar-icon-badge {
|
|
diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts
|
|
index 0462617196b39111cb22e5abbf4b096406496bf8..11434d27af9ce3262c57918e0f5a44d4eebcf8ac 100644
|
|
--- a/src/vs/workbench/browser/web.main.ts
|
|
+++ b/src/vs/workbench/browser/web.main.ts
|
|
@@ -45,6 +45,7 @@ import { FileLogService } from 'vs/platform/log/common/fileLogService';
|
|
import { toLocalISOString } from 'vs/base/common/date';
|
|
import { isWorkspaceToOpen, isFolderToOpen } from 'vs/platform/windows/common/windows';
|
|
import { getWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces';
|
|
+import { initialize } from 'vs/server/browser/client';
|
|
import { coalesce } from 'vs/base/common/arrays';
|
|
import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider';
|
|
import { WebResourceIdentityService, IResourceIdentityService } from 'vs/platform/resource/common/resourceIdentityService';
|
|
@@ -84,6 +85,8 @@ class BrowserMain extends Disposable {
|
|
// Startup
|
|
const instantiationService = workbench.startup();
|
|
|
|
+ await initialize(services.serviceCollection);
|
|
+
|
|
// Return API Facade
|
|
return instantiationService.invokeFunction(accessor => {
|
|
const commandService = accessor.get(ICommandService);
|
|
diff --git a/src/vs/workbench/common/resources.ts b/src/vs/workbench/common/resources.ts
|
|
index 18ea0bfedb4492327429a38237b05915b29f6dd0..d59a17c17f4fffa23d786ce36b4ff624d5688a58 100644
|
|
--- a/src/vs/workbench/common/resources.ts
|
|
+++ b/src/vs/workbench/common/resources.ts
|
|
@@ -15,6 +15,7 @@ import { ParsedExpression, IExpression, parse } from 'vs/base/common/glob';
|
|
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
|
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
|
|
import { withNullAsUndefined } from 'vs/base/common/types';
|
|
+import { Schemas } from 'vs/base/common/network';
|
|
|
|
export class ResourceContextKey extends Disposable implements IContextKey<URI> {
|
|
|
|
@@ -68,7 +69,8 @@ export class ResourceContextKey extends Disposable implements IContextKey<URI> {
|
|
if (!ResourceContextKey._uriEquals(this._resourceKey.get(), value)) {
|
|
this._contextKeyService.bufferChangeEvents(() => {
|
|
this._resourceKey.set(value);
|
|
- this._schemeKey.set(value ? value.scheme : null);
|
|
+ // NOTE@coder: Fixes source control context menus (#1104).
|
|
+ this._schemeKey.set(value ? (value.scheme === Schemas.vscodeRemote ? Schemas.file : value.scheme) : null);
|
|
this._filenameKey.set(value ? basename(value) : null);
|
|
this._langIdKey.set(value ? this._modeService.getModeIdByFilepathOrFirstLine(value) : null);
|
|
this._extensionKey.set(value ? extname(value) : null);
|
|
diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css
|
|
index 9947f240bf20b42069bd3d50a96d7a783615f54b..bdba0a2fc64a2e6c2cd2644bcc6afc0d131501a7 100644
|
|
--- a/src/vs/workbench/contrib/scm/browser/media/scm.css
|
|
+++ b/src/vs/workbench/contrib/scm/browser/media/scm.css
|
|
@@ -138,9 +138,11 @@
|
|
margin-right: 8px;
|
|
}
|
|
|
|
-.scm-view .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions {
|
|
- flex-grow: 100;
|
|
-}
|
|
+/* NOTE@coder: Causes the label to shrink to zero width in Firefox due to
|
|
+ * overflow:hidden. This isn't right anyway, as far as I can tell. */
|
|
+/* .scm-view .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions { */
|
|
+/* flex-grow: 100; */
|
|
+/* } */
|
|
|
|
.scm-view .monaco-list .monaco-list-row .resource-group > .actions,
|
|
.scm-view .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions {
|
|
diff --git a/src/vs/workbench/services/dialogs/browser/dialogService.ts b/src/vs/workbench/services/dialogs/browser/dialogService.ts
|
|
index 6e3182a696dd3443e68ad9e92d029b6ec2d01677..7df85da165a3ba157629c6b9e92f08dd18c7511a 100644
|
|
--- a/src/vs/workbench/services/dialogs/browser/dialogService.ts
|
|
+++ b/src/vs/workbench/services/dialogs/browser/dialogService.ts
|
|
@@ -124,11 +124,12 @@ export class DialogService implements IDialogService {
|
|
async about(): Promise<void> {
|
|
const detailString = (useAgo: boolean): string => {
|
|
return nls.localize('aboutDetail',
|
|
- "Version: {0}\nCommit: {1}\nDate: {2}\nBrowser: {3}",
|
|
+ "code-server: v{4}\n VS Code: v{0}\nCommit: {1}\nDate: {2}\nBrowser: {3}",
|
|
this.productService.version || 'Unknown',
|
|
this.productService.commit || 'Unknown',
|
|
this.productService.date ? `${this.productService.date}${useAgo ? ' (' + fromNow(new Date(this.productService.date), true) + ')' : ''}` : 'Unknown',
|
|
- navigator.userAgent
|
|
+ navigator.userAgent,
|
|
+ this.productService.codeServerVersion || 'Unknown',
|
|
);
|
|
};
|
|
|
|
diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts
|
|
index ba2701ec54d1a70eaf66afacce585fe906644319..4d4aaa6958b636480178470570e856e62ab922ee 100644
|
|
--- a/src/vs/workbench/services/environment/browser/environmentService.ts
|
|
+++ b/src/vs/workbench/services/environment/browser/environmentService.ts
|
|
@@ -121,8 +121,18 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
|
|
@memoize
|
|
get logFile(): URI { return joinPath(this.options.logsPath, 'window.log'); }
|
|
|
|
+ // NOTE@coder: Use the regular path for extensions that write directly to disk
|
|
+ // instead of using the VS Code API.
|
|
@memoize
|
|
- get userRoamingDataHome(): URI { return URI.file('/User').with({ scheme: Schemas.userData }); }
|
|
+ get userRoamingDataHome(): URI { return URI.file(this.userDataPath).with({ scheme: Schemas.userData }); }
|
|
+ @memoize
|
|
+ get userDataPath(): string {
|
|
+ const dataPath = this.payload?.get("userDataPath");
|
|
+ if (!dataPath) {
|
|
+ throw new Error("userDataPath was not provided to environment service");
|
|
+ }
|
|
+ return dataPath;
|
|
+ }
|
|
|
|
@memoize
|
|
get settingsResource(): URI { return joinPath(this.userRoamingDataHome, 'settings.json'); }
|
|
@@ -284,7 +294,12 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
|
|
extensionHostDebugEnvironment.params.port = parseInt(value);
|
|
break;
|
|
case 'enableProposedApi':
|
|
- extensionHostDebugEnvironment.extensionEnabledProposedApi = [];
|
|
+ try {
|
|
+ extensionHostDebugEnvironment.extensionEnabledProposedApi = JSON.parse(value);
|
|
+ } catch (error) {
|
|
+ console.error(error);
|
|
+ extensionHostDebugEnvironment.extensionEnabledProposedApi = [];
|
|
+ }
|
|
break;
|
|
}
|
|
}
|
|
diff --git a/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts
|
|
index c28b14774005509f58dddd2dec25547bac85e09f..6090200d9c3671fc1239880dbd060a01a84db1fb 100644
|
|
--- a/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts
|
|
+++ b/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts
|
|
@@ -163,7 +163,7 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
|
|
}
|
|
}
|
|
}
|
|
- return true;
|
|
+ return false; // NOTE@coder: Don't disable anything by extensionKind.
|
|
}
|
|
return false;
|
|
}
|
|
diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts
|
|
index 33eb56db3c25a0dc028b0d54dfa102e5584441cf..e5167794c3f761b06c9745e12d49b4a5257b48ef 100644
|
|
--- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts
|
|
+++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts
|
|
@@ -236,6 +236,11 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
|
return this.extensionManagementServerService.webExtensionManagementServer.extensionManagementService.installFromGallery(gallery);
|
|
}
|
|
|
|
+ // NOTE@coder: Fall back to installing on the remote server.
|
|
+ if (this.extensionManagementServerService.remoteExtensionManagementServer) {
|
|
+ return this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.installFromGallery(gallery);
|
|
+ }
|
|
+
|
|
if (this.extensionManagementServerService.remoteExtensionManagementServer) {
|
|
const error = new Error(localize('cannot be installed', "Cannot install '{0}' because this extension has defined that it cannot run on the remote server.", gallery.displayName || gallery.name));
|
|
error.name = INSTALL_ERROR_NOT_SUPPORTED;
|
|
diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts
|
|
index d0710e77fa28aacf5b4dfe85efbf67a6a9ae78ab..ceb27174aee3c78ca5a086f05a6b1d3188888034 100644
|
|
--- a/src/vs/workbench/services/extensions/browser/extensionService.ts
|
|
+++ b/src/vs/workbench/services/extensions/browser/extensionService.ts
|
|
@@ -116,8 +116,10 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
|
this._remoteAgentService.getEnvironment(),
|
|
this._remoteAgentService.scanExtensions()
|
|
]);
|
|
- localExtensions = this._checkEnabledAndProposedAPI(localExtensions);
|
|
remoteExtensions = this._checkEnabledAndProposedAPI(remoteExtensions);
|
|
+ // NOTE@coder: Include remotely hosted extensions that should run locally.
|
|
+ localExtensions = this._checkEnabledAndProposedAPI(localExtensions)
|
|
+ .concat(remoteExtensions.filter(ext => ext.extensionKind && (ext.extensionKind === "web" || ext.extensionKind.includes("web"))));
|
|
|
|
const remoteAgentConnection = this._remoteAgentService.getConnection();
|
|
this._runningLocation = _determineRunningLocation(this._productService, this._configService, localExtensions, remoteExtensions, Boolean(remoteEnv && remoteAgentConnection));
|
|
diff --git a/src/vs/workbench/services/extensions/common/extensionsUtil.ts b/src/vs/workbench/services/extensions/common/extensionsUtil.ts
|
|
index 65e532ee58dfc06ed944846d01b885cb8f260ebc..0b6282fde7ad03c7ea9872a777cbf487253abed1 100644
|
|
--- a/src/vs/workbench/services/extensions/common/extensionsUtil.ts
|
|
+++ b/src/vs/workbench/services/extensions/common/extensionsUtil.ts
|
|
@@ -37,7 +37,8 @@ export function canExecuteOnWorkspace(manifest: IExtensionManifest, productServi
|
|
|
|
export function canExecuteOnWeb(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): boolean {
|
|
const extensionKind = getExtensionKind(manifest, productService, configurationService);
|
|
- return extensionKind.some(kind => kind === 'web');
|
|
+ // NOTE@coder: Hardcode vim for now.
|
|
+ return extensionKind.some(kind => kind === 'web') || manifest.name === 'vim';
|
|
}
|
|
|
|
export function getExtensionKind(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): ExtensionKind[] {
|
|
diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts
|
|
index 49542eda74c65e485272cd37d586911886aa3ad7..de0e2da0a4c2dca91dc7e0e48c28a8a75ca3e7d4 100644
|
|
--- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts
|
|
+++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts
|
|
@@ -16,7 +16,7 @@ import { IInitData } from 'vs/workbench/api/common/extHost.protocol';
|
|
import { MessageType, createMessageOfType, isMessageOfType, IExtHostSocketMessage, IExtHostReadyMessage, IExtHostReduceGraceTimeMessage } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
|
|
import { ExtensionHostMain, IExitFn } from 'vs/workbench/services/extensions/common/extensionHostMain';
|
|
import { VSBuffer } from 'vs/base/common/buffer';
|
|
-import { IURITransformer, URITransformer, IRawURITransformer } from 'vs/base/common/uriIpc';
|
|
+import { IURITransformer, URITransformer } from 'vs/base/common/uriIpc';
|
|
import { exists } from 'vs/base/node/pfs';
|
|
import { realpath } from 'vs/base/node/extpath';
|
|
import { IHostUtils } from 'vs/workbench/api/common/extHostExtensionService';
|
|
@@ -57,12 +57,13 @@ const args = minimist(process.argv.slice(2), {
|
|
const Module = require.__$__nodeRequire('module') as any;
|
|
const originalLoad = Module._load;
|
|
|
|
- Module._load = function (request: string) {
|
|
+ Module._load = function (request: string, parent: object, isMain: boolean) {
|
|
if (request === 'natives') {
|
|
throw new Error('Either the extension or a NPM dependency is using the "natives" node module which is unsupported as it can cause a crash of the extension host. Click [here](https://go.microsoft.com/fwlink/?linkid=871887) to find out more');
|
|
}
|
|
|
|
- return originalLoad.apply(this, arguments);
|
|
+ // NOTE@coder: Map node_module.asar requests to regular node_modules.
|
|
+ return originalLoad.apply(this, [request.replace(/node_modules\.asar(\.unpacked)?/, 'node_modules'), parent, isMain]);
|
|
};
|
|
})();
|
|
|
|
@@ -135,8 +136,11 @@ function _createExtHostProtocol(): Promise<PersistentProtocol> {
|
|
|
|
// Wait for rich client to reconnect
|
|
protocol.onSocketClose(() => {
|
|
- // The socket has closed, let's give the renderer a certain amount of time to reconnect
|
|
- disconnectRunner1.schedule();
|
|
+ // NOTE@coder: Inform the server so we can manage offline
|
|
+ // connections there instead. Our goal is to persist connections
|
|
+ // forever (to a reasonable point) to account for things like
|
|
+ // hibernating overnight.
|
|
+ process.send!({ type: 'VSCODE_EXTHOST_DISCONNECTED' });
|
|
});
|
|
}
|
|
}
|
|
@@ -313,11 +317,9 @@ export async function startExtensionHostProcess(): Promise<void> {
|
|
|
|
// Attempt to load uri transformer
|
|
let uriTransformer: IURITransformer | null = null;
|
|
- if (initData.remote.authority && args.uriTransformerPath) {
|
|
+ if (initData.remote.authority) {
|
|
try {
|
|
- const rawURITransformerFactory = <any>require.__$__nodeRequire(args.uriTransformerPath);
|
|
- const rawURITransformer = <IRawURITransformer>rawURITransformerFactory(initData.remote.authority);
|
|
- uriTransformer = new URITransformer(rawURITransformer);
|
|
+ uriTransformer = new URITransformer(initData.remote.authority);
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
diff --git a/src/vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts b/src/vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts
|
|
index 79455414c06b95612a0dce2cad01f2bb2f40ef49..a407593b4dc6053309ed560898918cf67470e836 100644
|
|
--- a/src/vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts
|
|
+++ b/src/vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts
|
|
@@ -14,7 +14,11 @@
|
|
|
|
require.config({
|
|
baseUrl: monacoBaseUrl,
|
|
- catchError: true
|
|
+ catchError: true,
|
|
+ paths: {
|
|
+ '@coder/node-browser': `../node_modules/@coder/node-browser/out/client/client.js`,
|
|
+ '@coder/requirefs': `../node_modules/@coder/requirefs/out/requirefs.js`,
|
|
+ }
|
|
});
|
|
|
|
require(['vs/workbench/services/extensions/worker/extensionHostWorker'], () => { }, err => console.error(err));
|
|
diff --git a/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts b/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts
|
|
index 44999bd842eae12b752b2e7e8c4904272b111dc1..601b1c5408835c743fe07e34da4d4534873bf832 100644
|
|
--- a/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts
|
|
+++ b/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts
|
|
@@ -5,17 +5,17 @@
|
|
|
|
import { createChannelSender } from 'vs/base/parts/ipc/common/ipc';
|
|
import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
|
|
-import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
|
|
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
|
+import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
|
|
|
export class LocalizationsService {
|
|
|
|
declare readonly _serviceBrand: undefined;
|
|
|
|
constructor(
|
|
- @ISharedProcessService sharedProcessService: ISharedProcessService,
|
|
+ @IRemoteAgentService remoteAgentService: IRemoteAgentService,
|
|
) {
|
|
- return createChannelSender<ILocalizationsService>(sharedProcessService.getChannel('localizations'));
|
|
+ return createChannelSender<ILocalizationsService>(remoteAgentService.getConnection()!.getChannel('localizations'));
|
|
}
|
|
}
|
|
|
|
diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts
|
|
index 0669178db4cf5efe28ffd2a8fe3301de47bcc545..28fafeb2de2efea5c6412853044ce84775f1e038 100644
|
|
--- a/src/vs/workbench/workbench.web.main.ts
|
|
+++ b/src/vs/workbench/workbench.web.main.ts
|
|
@@ -35,7 +35,8 @@ import 'vs/workbench/services/textfile/browser/browserTextFileService';
|
|
import 'vs/workbench/services/keybinding/browser/keymapService';
|
|
import 'vs/workbench/services/extensions/browser/extensionService';
|
|
import 'vs/workbench/services/extensionManagement/common/extensionManagementServerService';
|
|
-import 'vs/workbench/services/telemetry/browser/telemetryService';
|
|
+// NOTE@coder: We send it all to the server side to be processed there instead.
|
|
+// import 'vs/workbench/services/telemetry/browser/telemetryService';
|
|
import 'vs/workbench/services/configurationResolver/browser/configurationResolverService';
|
|
import 'vs/workbench/services/credentials/browser/credentialsService';
|
|
import 'vs/workbench/services/url/browser/urlService';
|
|
diff --git a/yarn.lock b/yarn.lock
|
|
index b2fbf543af319fcc3973248b4ed4981db1ca213a..f10dddd6594bed959e2caa69911f70a6ae8e0554 100644
|
|
--- a/yarn.lock
|
|
+++ b/yarn.lock
|
|
@@ -140,6 +140,23 @@
|
|
lodash "^4.17.13"
|
|
to-fast-properties "^2.0.0"
|
|
|
|
+"@coder/logger@^1.1.12":
|
|
+ version "1.1.12"
|
|
+ resolved "https://registry.yarnpkg.com/@coder/logger/-/logger-1.1.12.tgz#def113b7183abc35a8da2b57f0929f7e9626f4e0"
|
|
+ integrity sha512-oM0j3lTVPqApUm3e0bKKcXpfAiJEys31fgEfQlHmvEA13ujsC4zDuXnt0uzDtph48eMoNRLOF/EE4mNShVJKVw==
|
|
+
|
|
+"@coder/node-browser@^1.0.8":
|
|
+ version "1.0.8"
|
|
+ resolved "https://registry.yarnpkg.com/@coder/node-browser/-/node-browser-1.0.8.tgz#c22f581b089ad7d95ad1362fd351c57b7fbc6e70"
|
|
+ integrity sha512-NLF9sYMRCN9WK1C224pHax1Cay3qKypg25BhVg7VfNbo3Cpa3daata8RF/rT8JK3lPsu8PmFgDRQjzGC9X1Lrw==
|
|
+
|
|
+"@coder/requirefs@^1.1.5":
|
|
+ version "1.1.5"
|
|
+ resolved "https://registry.yarnpkg.com/@coder/requirefs/-/requirefs-1.1.5.tgz#259db370d563a79a96fb150bc9d69c7db6edc9fb"
|
|
+ integrity sha512-3jB47OFCql9+9FI6Vc4YX0cfFnG5rxBfrZUH45S4XYtYGOz+/Xl4h4d2iMk50b7veHkeSWGlB4VHC3UZ16zuYQ==
|
|
+ optionalDependencies:
|
|
+ jszip "2.6.0"
|
|
+
|
|
"@electron/get@^1.0.1":
|
|
version "1.7.2"
|
|
resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.7.2.tgz#286436a9fb56ff1a1fcdf0e80131fd65f4d1e0fd"
|
|
@@ -5421,6 +5438,13 @@ jsprim@^1.2.2:
|
|
json-schema "0.2.3"
|
|
verror "1.10.0"
|
|
|
|
+jszip@2.6.0:
|
|
+ version "2.6.0"
|
|
+ resolved "https://registry.yarnpkg.com/jszip/-/jszip-2.6.0.tgz#7fb3e9c2f11c8a9840612db5dabbc8cf3a7534b7"
|
|
+ integrity sha1-f7PpwvEciphAYS212rvIzzp1NLc=
|
|
+ dependencies:
|
|
+ pako "~1.0.0"
|
|
+
|
|
just-debounce@^1.0.0:
|
|
version "1.0.0"
|
|
resolved "https://registry.yarnpkg.com/just-debounce/-/just-debounce-1.0.0.tgz#87fccfaeffc0b68cd19d55f6722943f929ea35ea"
|
|
@@ -6008,26 +6032,11 @@ minimatch@0.3:
|
|
dependencies:
|
|
brace-expansion "^1.1.7"
|
|
|
|
-minimist@0.0.8:
|
|
- version "0.0.8"
|
|
- resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
|
- integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
|
|
-
|
|
-minimist@^1.2.0:
|
|
- version "1.2.0"
|
|
- resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
|
- integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
|
|
-
|
|
-minimist@^1.2.5:
|
|
+minimist@0.0.8, minimist@^1.2.0, minimist@^1.2.5, minimist@~0.0.1:
|
|
version "1.2.5"
|
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
|
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
|
|
|
-minimist@~0.0.1:
|
|
- version "0.0.10"
|
|
- resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
|
|
- integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=
|
|
-
|
|
minipass@^2.2.1, minipass@^2.3.3:
|
|
version "2.3.3"
|
|
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.3.tgz#a7dcc8b7b833f5d368759cce544dccb55f50f233"
|
|
@@ -6797,6 +6806,11 @@ p-try@^2.0.0:
|
|
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1"
|
|
integrity sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==
|
|
|
|
+pako@~1.0.0:
|
|
+ version "1.0.11"
|
|
+ resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
|
|
+ integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
|
|
+
|
|
pako@~1.0.5:
|
|
version "1.0.6"
|
|
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258"
|