diff --git a/README.md b/README.md
index 670195c3c92..b0a86b728ae 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
a code editor with what developers need for their core edit-build-debug cycle. Code
provides comprehensive editing and debugging support, an extensibility model, and lightweight integration with existing tools.
-VS Code is updated monthly with new features and bug fixes. You can download it for Windows, macOS, and Linux on [VS Code's website](https://code.visualstudio.com/Download). To get the latest releases everyday, you can install the [Insiders version of VS Code](https://code.visualstudio.com/insiders). This builds from the master branch and is updated at least daily.
+VS Code is updated monthly with new features and bug fixes. You can download it for Windows, macOS, and Linux on [VS Code's website](https://code.visualstudio.com/Download). To get the latest releases every day, you can install the [Insiders version of VS Code](https://code.visualstudio.com/insiders). This builds from the master branch and is updated at least daily.
diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js
index bfa2a0bd8a3..f1807d26252 100644
--- a/build/gulpfile.vscode.js
+++ b/build/gulpfile.vscode.js
@@ -43,7 +43,7 @@ const nodeModules = ['electron', 'original-fs']
// Build
const builtInExtensions = [
- { name: 'ms-vscode.node-debug', version: '1.17.4' },
+ { name: 'ms-vscode.node-debug', version: '1.17.5' },
{ name: 'ms-vscode.node-debug2', version: '1.17.1' }
];
diff --git a/extensions/coffeescript/test/colorize-results/test-regex_coffee.json b/extensions/coffeescript/test/colorize-results/test-regex_coffee.json
index 909a6112f35..445fab1f1c2 100644
--- a/extensions/coffeescript/test/colorize-results/test-regex_coffee.json
+++ b/extensions/coffeescript/test/colorize-results/test-regex_coffee.json
@@ -69,8 +69,8 @@
"c": "(",
"t": "source.coffee string.regexp.coffee meta.group.regexp punctuation.definition.group.regexp",
"r": {
- "dark_plus": "punctuation.definition.group.regexp: #D7BA7D",
- "light_plus": "punctuation.definition.group.regexp: #FF0000",
+ "dark_plus": "punctuation.definition.group.regexp: #CE9178",
+ "light_plus": "punctuation.definition.group.regexp: #D16969",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "string.regexp: #D16969"
@@ -80,8 +80,8 @@
"c": "\\d",
"t": "source.coffee string.regexp.coffee meta.group.regexp constant.character.character-class.regexp",
"r": {
- "dark_plus": "constant.character: #569CD6",
- "light_plus": "constant.character: #0000FF",
+ "dark_plus": "constant.character.character-class.regexp: #D16969",
+ "light_plus": "constant.character.character-class.regexp: #811F3F",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "constant.character: #569CD6"
@@ -91,8 +91,8 @@
"c": "+",
"t": "source.coffee string.regexp.coffee meta.group.regexp keyword.operator.quantifier.regexp",
"r": {
- "dark_plus": "keyword.operator.quantifier.regexp: #D4D4D4",
- "light_plus": "keyword.operator.quantifier.regexp: #0000FF",
+ "dark_plus": "keyword.operator.quantifier.regexp: #D7BA7D",
+ "light_plus": "keyword.operator.quantifier.regexp: #000000",
"dark_vs": "keyword.operator: #D4D4D4",
"light_vs": "keyword.operator: #000000",
"hc_black": "keyword.operator: #D4D4D4"
@@ -102,8 +102,8 @@
"c": ")",
"t": "source.coffee string.regexp.coffee meta.group.regexp punctuation.definition.group.regexp",
"r": {
- "dark_plus": "punctuation.definition.group.regexp: #D7BA7D",
- "light_plus": "punctuation.definition.group.regexp: #FF0000",
+ "dark_plus": "punctuation.definition.group.regexp: #CE9178",
+ "light_plus": "punctuation.definition.group.regexp: #D16969",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "string.regexp: #D16969"
diff --git a/extensions/coffeescript/test/colorize-results/test_coffee.json b/extensions/coffeescript/test/colorize-results/test_coffee.json
index 4320ae579e6..a36d10633bf 100644
--- a/extensions/coffeescript/test/colorize-results/test_coffee.json
+++ b/extensions/coffeescript/test/colorize-results/test_coffee.json
@@ -1378,8 +1378,8 @@
"c": "(",
"t": "source.coffee string.regexp.coffee meta.group.regexp punctuation.definition.group.regexp",
"r": {
- "dark_plus": "punctuation.definition.group.regexp: #D7BA7D",
- "light_plus": "punctuation.definition.group.regexp: #FF0000",
+ "dark_plus": "punctuation.definition.group.regexp: #CE9178",
+ "light_plus": "punctuation.definition.group.regexp: #D16969",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "string.regexp: #D16969"
@@ -1389,8 +1389,8 @@
"c": "\\d",
"t": "source.coffee string.regexp.coffee meta.group.regexp constant.character.character-class.regexp",
"r": {
- "dark_plus": "constant.character: #569CD6",
- "light_plus": "constant.character: #0000FF",
+ "dark_plus": "constant.character.character-class.regexp: #D16969",
+ "light_plus": "constant.character.character-class.regexp: #811F3F",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "constant.character: #569CD6"
@@ -1400,8 +1400,8 @@
"c": "+",
"t": "source.coffee string.regexp.coffee meta.group.regexp keyword.operator.quantifier.regexp",
"r": {
- "dark_plus": "keyword.operator.quantifier.regexp: #D4D4D4",
- "light_plus": "keyword.operator.quantifier.regexp: #0000FF",
+ "dark_plus": "keyword.operator.quantifier.regexp: #D7BA7D",
+ "light_plus": "keyword.operator.quantifier.regexp: #000000",
"dark_vs": "keyword.operator: #D4D4D4",
"light_vs": "keyword.operator: #000000",
"hc_black": "keyword.operator: #D4D4D4"
@@ -1411,8 +1411,8 @@
"c": ")",
"t": "source.coffee string.regexp.coffee meta.group.regexp punctuation.definition.group.regexp",
"r": {
- "dark_plus": "punctuation.definition.group.regexp: #D7BA7D",
- "light_plus": "punctuation.definition.group.regexp: #FF0000",
+ "dark_plus": "punctuation.definition.group.regexp: #CE9178",
+ "light_plus": "punctuation.definition.group.regexp: #D16969",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "string.regexp: #D16969"
@@ -1466,8 +1466,8 @@
"c": "(",
"t": "source.coffee string.regexp.coffee meta.group.regexp punctuation.definition.group.regexp",
"r": {
- "dark_plus": "punctuation.definition.group.regexp: #D7BA7D",
- "light_plus": "punctuation.definition.group.regexp: #FF0000",
+ "dark_plus": "punctuation.definition.group.regexp: #CE9178",
+ "light_plus": "punctuation.definition.group.regexp: #D16969",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "string.regexp: #D16969"
@@ -1477,8 +1477,8 @@
"c": "\\w",
"t": "source.coffee string.regexp.coffee meta.group.regexp constant.character.character-class.regexp",
"r": {
- "dark_plus": "constant.character: #569CD6",
- "light_plus": "constant.character: #0000FF",
+ "dark_plus": "constant.character.character-class.regexp: #D16969",
+ "light_plus": "constant.character.character-class.regexp: #811F3F",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "constant.character: #569CD6"
@@ -1488,8 +1488,8 @@
"c": "*",
"t": "source.coffee string.regexp.coffee meta.group.regexp keyword.operator.quantifier.regexp",
"r": {
- "dark_plus": "keyword.operator.quantifier.regexp: #D4D4D4",
- "light_plus": "keyword.operator.quantifier.regexp: #0000FF",
+ "dark_plus": "keyword.operator.quantifier.regexp: #D7BA7D",
+ "light_plus": "keyword.operator.quantifier.regexp: #000000",
"dark_vs": "keyword.operator: #D4D4D4",
"light_vs": "keyword.operator: #000000",
"hc_black": "keyword.operator: #D4D4D4"
@@ -1499,8 +1499,8 @@
"c": ")",
"t": "source.coffee string.regexp.coffee meta.group.regexp punctuation.definition.group.regexp",
"r": {
- "dark_plus": "punctuation.definition.group.regexp: #D7BA7D",
- "light_plus": "punctuation.definition.group.regexp: #FF0000",
+ "dark_plus": "punctuation.definition.group.regexp: #CE9178",
+ "light_plus": "punctuation.definition.group.regexp: #D16969",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "string.regexp: #D16969"
@@ -1554,8 +1554,8 @@
"c": "$",
"t": "source.coffee string.regexp.coffee keyword.control.anchor.regexp",
"r": {
- "dark_plus": "keyword.control.anchor.regexp: #C586C0",
- "light_plus": "keyword.control.anchor.regexp: #09885A",
+ "dark_plus": "keyword.control.anchor.regexp: #DCDCAA",
+ "light_plus": "keyword.control.anchor.regexp: #FF0000",
"dark_vs": "keyword.control: #569CD6",
"light_vs": "keyword.control: #0000FF",
"hc_black": "keyword.control: #C586C0"
diff --git a/extensions/git/src/askpass.ts b/extensions/git/src/askpass.ts
index 597cc043d4e..e241ced459a 100644
--- a/extensions/git/src/askpass.ts
+++ b/extensions/git/src/askpass.ts
@@ -27,10 +27,6 @@ function getIPCHandlePath(nonce: string): string {
return `\\\\.\\pipe\\vscode-git-askpass-${nonce}-sock`;
}
- if (process.env['XDG_RUNTIME_DIR']) {
- return path.join(process.env['XDG_RUNTIME_DIR'], `vscode-git-askpass-${nonce}.sock`);
- }
-
return path.join(os.tmpdir(), `vscode-git-askpass-${nonce}.sock`);
}
diff --git a/extensions/grunt/package.json b/extensions/grunt/package.json
index 0758d440b08..4c028b73312 100644
--- a/extensions/grunt/package.json
+++ b/extensions/grunt/package.json
@@ -31,6 +31,7 @@
"title": "Grunt",
"properties": {
"grunt.autoDetect": {
+ "scope": "resource",
"type": "string",
"enum": [
"off",
diff --git a/extensions/grunt/src/main.ts b/extensions/grunt/src/main.ts
index 554bb4963e6..a5e74585e8e 100644
--- a/extensions/grunt/src/main.ts
+++ b/extensions/grunt/src/main.ts
@@ -13,49 +13,6 @@ import * as nls from 'vscode-nls';
const localize = nls.config(process.env.VSCODE_NLS_CONFIG)();
type AutoDetect = 'on' | 'off';
-let taskProvider: vscode.Disposable | undefined;
-
-export function activate(_context: vscode.ExtensionContext): void {
- let workspaceRoot = vscode.workspace.rootPath;
- if (!workspaceRoot) {
- return;
- }
- let pattern = path.join(workspaceRoot, '[Gg]runtfile.js');
- let detectorPromise: Thenable | undefined = undefined;
- let fileWatcher = vscode.workspace.createFileSystemWatcher(pattern);
- fileWatcher.onDidChange(() => detectorPromise = undefined);
- fileWatcher.onDidCreate(() => detectorPromise = undefined);
- fileWatcher.onDidDelete(() => detectorPromise = undefined);
-
- function onConfigurationChanged() {
- let autoDetect = vscode.workspace.getConfiguration('grunt').get('autoDetect');
- if (taskProvider && autoDetect === 'off') {
- detectorPromise = undefined;
- taskProvider.dispose();
- taskProvider = undefined;
- } else if (!taskProvider && autoDetect === 'on') {
- taskProvider = vscode.workspace.registerTaskProvider('grunt', {
- provideTasks: () => {
- if (!detectorPromise) {
- detectorPromise = getGruntTasks();
- }
- return detectorPromise;
- },
- resolveTask(_task: vscode.Task): vscode.Task | undefined {
- return undefined;
- }
- });
- }
- }
- vscode.workspace.onDidChangeConfiguration(onConfigurationChanged);
- onConfigurationChanged();
-}
-
-export function deactivate(): void {
- if (taskProvider) {
- taskProvider.dispose();
- }
-}
function exists(file: string): Promise {
return new Promise((resolve, _reject) => {
@@ -76,19 +33,6 @@ function exec(command: string, options: cp.ExecOptions): Promise<{ stdout: strin
});
}
-let _channel: vscode.OutputChannel;
-function getOutputChannel(): vscode.OutputChannel {
- if (!_channel) {
- _channel = vscode.window.createOutputChannel('Grunt Auto Detection');
- }
- return _channel;
-}
-
-interface GruntTaskDefinition extends vscode.TaskDefinition {
- task: string;
- file?: string;
-}
-
const buildNames: string[] = ['build', 'compile', 'watch'];
function isBuildTask(name: string): boolean {
for (let buildName of buildNames) {
@@ -109,97 +53,287 @@ function isTestTask(name: string): boolean {
return false;
}
-async function getGruntTasks(): Promise {
- let workspaceRoot = vscode.workspace.rootPath;
- let emptyTasks: vscode.Task[] = [];
- if (!workspaceRoot) {
- return emptyTasks;
+let _channel: vscode.OutputChannel;
+function getOutputChannel(): vscode.OutputChannel {
+ if (!_channel) {
+ _channel = vscode.window.createOutputChannel('Gulp Auto Detection');
}
- if (!await exists(path.join(workspaceRoot, 'gruntfile.js')) && !await exists(path.join(workspaceRoot, 'Gruntfile.js'))) {
- return emptyTasks;
+ return _channel;
+}
+
+interface GruntTaskDefinition extends vscode.TaskDefinition {
+ task: string;
+ file?: string;
+}
+
+class FolderDetector {
+
+ private fileWatcher: vscode.FileSystemWatcher;
+ private promise: Thenable | undefined;
+
+ constructor(private _workspaceFolder: vscode.WorkspaceFolder) {
}
- let command: string;
- let platform = process.platform;
- if (platform === 'win32' && await exists(path.join(workspaceRoot!, 'node_modules', '.bin', 'grunt.cmd'))) {
- command = path.join('.', 'node_modules', '.bin', 'grunt.cmd');
- } else if ((platform === 'linux' || platform === 'darwin') && await exists(path.join(workspaceRoot!, 'node_modules', '.bin', 'grunt'))) {
- command = path.join('.', 'node_modules', '.bin', 'grunt');
- } else {
- command = 'grunt';
+ public get workspaceFolder(): vscode.WorkspaceFolder {
+ return this._workspaceFolder;
}
- let commandLine = `${command} --help --no-color`;
- try {
- let { stdout, stderr } = await exec(commandLine, { cwd: workspaceRoot });
- if (stderr) {
- getOutputChannel().appendLine(stderr);
- getOutputChannel().show(true);
+ public isEnabled(): boolean {
+ return vscode.workspace.getConfiguration('grunt', this._workspaceFolder.uri).get('autoDetect') === 'on';
+ }
+
+ public start(): void {
+ let pattern = path.join(this._workspaceFolder.uri.fsPath, '[Gg]runtfile.js');
+ this.fileWatcher = vscode.workspace.createFileSystemWatcher(pattern);
+ this.fileWatcher.onDidChange(() => this.promise = undefined);
+ this.fileWatcher.onDidCreate(() => this.promise = undefined);
+ this.fileWatcher.onDidDelete(() => this.promise = undefined);
+ }
+
+ public async getTasks(): Promise {
+ if (!this.promise) {
+ this.promise = this.computeTasks();
}
- let result: vscode.Task[] = [];
- if (stdout) {
- // grunt lists tasks as follows (description is wrapped into a new line if too long):
- // ...
- // Available tasks
- // uglify Minify files with UglifyJS. *
- // jshint Validate files with JSHint. *
- // test Alias for "jshint", "qunit" tasks.
- // default Alias for "jshint", "qunit", "concat", "uglify" tasks.
- // long Alias for "eslint", "qunit", "browserify", "sass",
- // "autoprefixer", "uglify", tasks.
- //
- // Tasks run in the order specified
+ return this.promise;
+ }
- let lines = stdout.split(/\r{0,1}\n/);
- let tasksStart = false;
- let tasksEnd = false;
- for (let line of lines) {
- if (line.length === 0) {
- continue;
- }
- if (!tasksStart && !tasksEnd) {
- if (line.indexOf('Available tasks') === 0) {
- tasksStart = true;
+ private async computeTasks(): Promise {
+ let rootPath = this._workspaceFolder.uri.scheme === 'file' ? this._workspaceFolder.uri.fsPath : undefined;
+ let emptyTasks: vscode.Task[] = [];
+ if (!rootPath) {
+ return emptyTasks;
+ }
+ if (!await exists(path.join(rootPath, 'gruntfile.js')) && !await exists(path.join(rootPath, 'Gruntfile.js'))) {
+ return emptyTasks;
+ }
+
+ let command: string;
+ let platform = process.platform;
+ if (platform === 'win32' && await exists(path.join(rootPath!, 'node_modules', '.bin', 'grunt.cmd'))) {
+ command = path.join('.', 'node_modules', '.bin', 'grunt.cmd');
+ } else if ((platform === 'linux' || platform === 'darwin') && await exists(path.join(rootPath!, 'node_modules', '.bin', 'grunt'))) {
+ command = path.join('.', 'node_modules', '.bin', 'grunt');
+ } else {
+ command = 'grunt';
+ }
+
+ let commandLine = `${command} --help --no-color`;
+ try {
+ let { stdout, stderr } = await exec(commandLine, { cwd: rootPath });
+ if (stderr) {
+ getOutputChannel().appendLine(stderr);
+ getOutputChannel().show(true);
+ }
+ let result: vscode.Task[] = [];
+ if (stdout) {
+ // grunt lists tasks as follows (description is wrapped into a new line if too long):
+ // ...
+ // Available tasks
+ // uglify Minify files with UglifyJS. *
+ // jshint Validate files with JSHint. *
+ // test Alias for "jshint", "qunit" tasks.
+ // default Alias for "jshint", "qunit", "concat", "uglify" tasks.
+ // long Alias for "eslint", "qunit", "browserify", "sass",
+ // "autoprefixer", "uglify", tasks.
+ //
+ // Tasks run in the order specified
+
+ let lines = stdout.split(/\r{0,1}\n/);
+ let tasksStart = false;
+ let tasksEnd = false;
+ for (let line of lines) {
+ if (line.length === 0) {
+ continue;
}
- } else if (tasksStart && !tasksEnd) {
- if (line.indexOf('Tasks run in the order specified') === 0) {
- tasksEnd = true;
- } else {
- let regExp = /^\s*(\S.*\S) \S/g;
- let matches = regExp.exec(line);
- if (matches && matches.length === 2) {
- let name = matches[1];
- let kind: GruntTaskDefinition = {
- type: 'grunt',
- task: name
- };
- let source = 'grunt';
- let task = name.indexOf(' ') === -1
- ? new vscode.Task(kind, name, source, new vscode.ShellExecution(`${command} ${name}`))
- : new vscode.Task(kind, name, source, new vscode.ShellExecution(`${command} "${name}"`));
- result.push(task);
- let lowerCaseTaskName = name.toLowerCase();
- if (isBuildTask(lowerCaseTaskName)) {
- task.group = vscode.TaskGroup.Build;
- } else if (isTestTask(lowerCaseTaskName)) {
- task.group = vscode.TaskGroup.Test;
+ if (!tasksStart && !tasksEnd) {
+ if (line.indexOf('Available tasks') === 0) {
+ tasksStart = true;
+ }
+ } else if (tasksStart && !tasksEnd) {
+ if (line.indexOf('Tasks run in the order specified') === 0) {
+ tasksEnd = true;
+ } else {
+ let regExp = /^\s*(\S.*\S) \S/g;
+ let matches = regExp.exec(line);
+ if (matches && matches.length === 2) {
+ let name = matches[1];
+ let kind: GruntTaskDefinition = {
+ type: 'grunt',
+ task: name
+ };
+ let source = 'grunt';
+ let options: vscode.ShellExecutionOptions = { cwd: this.workspaceFolder.uri.fsPath };
+ let task = name.indexOf(' ') === -1
+ ? new vscode.Task(kind, this.workspaceFolder, name, source, new vscode.ShellExecution(`${command} ${name}`, options))
+ : new vscode.Task(kind, this.workspaceFolder, name, source, new vscode.ShellExecution(`${command} "${name}"`, options));
+ result.push(task);
+ let lowerCaseTaskName = name.toLowerCase();
+ if (isBuildTask(lowerCaseTaskName)) {
+ task.group = vscode.TaskGroup.Build;
+ } else if (isTestTask(lowerCaseTaskName)) {
+ task.group = vscode.TaskGroup.Test;
+ }
}
}
}
}
}
+ return result;
+ } catch (err) {
+ let channel = getOutputChannel();
+ if (err.stderr) {
+ channel.appendLine(err.stderr);
+ }
+ if (err.stdout) {
+ channel.appendLine(err.stdout);
+ }
+ channel.appendLine(localize('execFailed', 'Auto detecting Grunt failed with error: {0}', err.error ? err.error.toString() : 'unknown'));
+ channel.show(true);
+ return emptyTasks;
}
- return result;
- } catch (err) {
- let channel = getOutputChannel();
- if (err.stderr) {
- channel.appendLine(err.stderr);
- }
- if (err.stdout) {
- channel.appendLine(err.stdout);
- }
- channel.appendLine(localize('execFailed', 'Auto detecting Grunt failed with error: {0}', err.error ? err.error.toString() : 'unknown'));
- channel.show(true);
- return emptyTasks;
}
+
+ public dispose() {
+ this.promise = undefined;
+ if (this.fileWatcher) {
+ this.fileWatcher.dispose();
+ }
+ }
+}
+
+class TaskDetector {
+
+ private taskProvider: vscode.Disposable | undefined;
+ private detectors: Map = new Map();
+ private promise: Promise | undefined;
+
+ constructor() {
+ }
+
+ public start(): void {
+ let folders = vscode.workspace.workspaceFolders;
+ if (folders) {
+ this.updateWorkspaceFolders(folders, []);
+ }
+ vscode.workspace.onDidChangeWorkspaceFolders((event) => this.updateWorkspaceFolders(event.added, event.removed));
+ vscode.workspace.onDidChangeConfiguration(this.updateConfiguration, this);
+ }
+
+ public dispose(): void {
+ if (this.taskProvider) {
+ this.taskProvider.dispose();
+ this.taskProvider = undefined;
+ }
+ this.detectors.clear();
+ this.promise = undefined;
+ }
+
+ private updateWorkspaceFolders(added: vscode.WorkspaceFolder[], removed: vscode.WorkspaceFolder[]): void {
+ let changed = false;
+ for (let remove of removed) {
+ let detector = this.detectors.get(remove.uri.toString());
+ if (detector) {
+ changed = true;
+ detector.dispose();
+ this.detectors.delete(remove.uri.toString());
+ }
+ }
+ for (let add of added) {
+ let detector = new FolderDetector(add);
+ if (detector.isEnabled()) {
+ changed = true;
+ this.detectors.set(add.uri.toString(), detector);
+ detector.start();
+ }
+ }
+ if (changed) {
+ this.promise = undefined;
+ }
+ this.updateProvider();
+ }
+
+ private updateConfiguration(): void {
+ let changed = false;
+ for (let detector of this.detectors.values()) {
+ if (!detector.isEnabled()) {
+ changed = true;
+ detector.dispose();
+ this.detectors.delete(detector.workspaceFolder.uri.toString());
+ }
+ }
+ let folders = vscode.workspace.workspaceFolders;
+ if (folders) {
+ for (let folder of folders) {
+ if (!this.detectors.has(folder.uri.toString())) {
+ let detector = new FolderDetector(folder);
+ if (detector.isEnabled()) {
+ changed = true;
+ this.detectors.set(folder.uri.toString(), detector);
+ detector.start();
+ }
+ }
+ }
+ }
+ if (changed) {
+ this.promise = undefined;
+ }
+ this.updateProvider();
+ }
+
+ private updateProvider(): void {
+ if (!this.taskProvider && this.detectors.size > 0) {
+ this.taskProvider = vscode.workspace.registerTaskProvider('gulp', {
+ provideTasks: () => {
+ return this.getTasks();
+ },
+ resolveTask(_task: vscode.Task): vscode.Task | undefined {
+ return undefined;
+ }
+ });
+ }
+ else if (this.taskProvider && this.detectors.size === 0) {
+ this.taskProvider.dispose();
+ this.taskProvider = undefined;
+ this.promise = undefined;
+ }
+ }
+
+ public getTasks(): Promise {
+ if (!this.promise) {
+ this.promise = this.computeTasks();
+ }
+ return this.promise;
+ }
+
+ private computeTasks(): Promise {
+ if (this.detectors.size === 0) {
+ return Promise.resolve([]);
+ } else if (this.detectors.size === 1) {
+ return this.detectors.values().next().value.getTasks();
+ } else {
+ let promises: Promise[] = [];
+ for (let detector of this.detectors.values()) {
+ promises.push(detector.getTasks().then((value) => value, () => []));
+ }
+ return Promise.all(promises).then((values) => {
+ let result: vscode.Task[] = [];
+ for (let tasks of values) {
+ if (tasks && tasks.length > 0) {
+ result.push(...tasks);
+ }
+ }
+ return result;
+ });
+ }
+ }
+}
+
+let detector: TaskDetector;
+export function activate(_context: vscode.ExtensionContext): void {
+ detector = new TaskDetector();
+ detector.start();
+}
+
+export function deactivate(): void {
+ detector.dispose();
}
\ No newline at end of file
diff --git a/extensions/gulp/package.json b/extensions/gulp/package.json
index 235f3e2d138..498384f3fa7 100644
--- a/extensions/gulp/package.json
+++ b/extensions/gulp/package.json
@@ -31,6 +31,7 @@
"title": "Gulp",
"properties": {
"gulp.autoDetect": {
+ "scope": "resource",
"type": "string",
"enum": [
"off",
diff --git a/extensions/gulp/src/main.ts b/extensions/gulp/src/main.ts
index 24d7370cf13..127e07afd64 100644
--- a/extensions/gulp/src/main.ts
+++ b/extensions/gulp/src/main.ts
@@ -13,49 +13,6 @@ import * as nls from 'vscode-nls';
const localize = nls.config(process.env.VSCODE_NLS_CONFIG)();
type AutoDetect = 'on' | 'off';
-let taskProvider: vscode.Disposable | undefined;
-
-export function activate(_context: vscode.ExtensionContext): void {
- let workspaceRoot = vscode.workspace.rootPath;
- if (!workspaceRoot) {
- return;
- }
- let pattern = path.join(workspaceRoot, 'gulpfile{.babel.js,.js}');
- let gulpPromise: Thenable | undefined = undefined;
- let fileWatcher = vscode.workspace.createFileSystemWatcher(pattern);
- fileWatcher.onDidChange(() => gulpPromise = undefined);
- fileWatcher.onDidCreate(() => gulpPromise = undefined);
- fileWatcher.onDidDelete(() => gulpPromise = undefined);
-
- function onConfigurationChanged() {
- let autoDetect = vscode.workspace.getConfiguration('gulp').get('autoDetect');
- if (taskProvider && autoDetect === 'off') {
- gulpPromise = undefined;
- taskProvider.dispose();
- taskProvider = undefined;
- } else if (!taskProvider && autoDetect === 'on') {
- taskProvider = vscode.workspace.registerTaskProvider('gulp', {
- provideTasks: () => {
- if (!gulpPromise) {
- gulpPromise = getGulpTasks();
- }
- return gulpPromise;
- },
- resolveTask(_task: vscode.Task): vscode.Task | undefined {
- return undefined;
- }
- });
- }
- }
- vscode.workspace.onDidChangeConfiguration(onConfigurationChanged);
- onConfigurationChanged();
-}
-
-export function deactivate(): void {
- if (taskProvider) {
- taskProvider.dispose();
- }
-}
function exists(file: string): Promise {
return new Promise((resolve, _reject) => {
@@ -76,19 +33,6 @@ function exec(command: string, options: cp.ExecOptions): Promise<{ stdout: strin
});
}
-let _channel: vscode.OutputChannel;
-function getOutputChannel(): vscode.OutputChannel {
- if (!_channel) {
- _channel = vscode.window.createOutputChannel('Gulp Auto Detection');
- }
- return _channel;
-}
-
-interface GulpTaskDefinition extends vscode.TaskDefinition {
- task: string;
- file?: string;
-}
-
const buildNames: string[] = ['build', 'compile', 'watch'];
function isBuildTask(name: string): boolean {
for (let buildName of buildNames) {
@@ -109,69 +53,259 @@ function isTestTask(name: string): boolean {
return false;
}
-async function getGulpTasks(): Promise {
- let workspaceRoot = vscode.workspace.rootPath;
- let emptyTasks: vscode.Task[] = [];
- if (!workspaceRoot) {
- return emptyTasks;
+let _channel: vscode.OutputChannel;
+function getOutputChannel(): vscode.OutputChannel {
+ if (!_channel) {
+ _channel = vscode.window.createOutputChannel('Gulp Auto Detection');
}
- let gulpfile = path.join(workspaceRoot, 'gulpfile.js');
- if (!await exists(gulpfile)) {
- gulpfile = path.join(workspaceRoot, 'gulpfile.babel.js');
- if (! await exists(gulpfile)) {
+ return _channel;
+}
+
+interface GulpTaskDefinition extends vscode.TaskDefinition {
+ task: string;
+ file?: string;
+}
+
+class FolderDetector {
+
+ private fileWatcher: vscode.FileSystemWatcher;
+ private promise: Thenable | undefined;
+
+ constructor(private _workspaceFolder: vscode.WorkspaceFolder) {
+ }
+
+ public get workspaceFolder(): vscode.WorkspaceFolder {
+ return this._workspaceFolder;
+ }
+
+ public isEnabled(): boolean {
+ return vscode.workspace.getConfiguration('gulp', this._workspaceFolder.uri).get('autoDetect') === 'on';
+ }
+
+ public start(): void {
+ let pattern = path.join(this._workspaceFolder.uri.fsPath, 'gulpfile{.babel.js,.js}');
+ this.fileWatcher = vscode.workspace.createFileSystemWatcher(pattern);
+ this.fileWatcher.onDidChange(() => this.promise = undefined);
+ this.fileWatcher.onDidCreate(() => this.promise = undefined);
+ this.fileWatcher.onDidDelete(() => this.promise = undefined);
+ }
+
+ public async getTasks(): Promise {
+ if (!this.promise) {
+ this.promise = this.computeTasks();
+ }
+ return this.promise;
+ }
+
+ private async computeTasks(): Promise {
+ let rootPath = this._workspaceFolder.uri.scheme === 'file' ? this._workspaceFolder.uri.fsPath : undefined;
+ let emptyTasks: vscode.Task[] = [];
+ if (!rootPath) {
+ return emptyTasks;
+ }
+ let gulpfile = path.join(rootPath, 'gulpfile.js');
+ if (!await exists(gulpfile)) {
+ gulpfile = path.join(rootPath, 'gulpfile.babel.js');
+ if (! await exists(gulpfile)) {
+ return emptyTasks;
+ }
+ }
+
+ let gulpCommand: string;
+ let platform = process.platform;
+ if (platform === 'win32' && await exists(path.join(rootPath!, 'node_modules', '.bin', 'gulp.cmd'))) {
+ gulpCommand = path.join('.', 'node_modules', '.bin', 'gulp.cmd');
+ } else if ((platform === 'linux' || platform === 'darwin') && await exists(path.join(rootPath!, 'node_modules', '.bin', 'gulp'))) {
+ gulpCommand = path.join('.', 'node_modules', '.bin', 'gulp');
+ } else {
+ gulpCommand = 'gulp';
+ }
+
+ let commandLine = `${gulpCommand} --tasks-simple --no-color`;
+ try {
+ let { stdout, stderr } = await exec(commandLine, { cwd: rootPath });
+ if (stderr && stderr.length > 0) {
+ getOutputChannel().appendLine(stderr);
+ getOutputChannel().show(true);
+ }
+ let result: vscode.Task[] = [];
+ if (stdout) {
+ let lines = stdout.split(/\r{0,1}\n/);
+ for (let line of lines) {
+ if (line.length === 0) {
+ continue;
+ }
+ let kind: GulpTaskDefinition = {
+ type: 'gulp',
+ task: line
+ };
+ let options: vscode.ShellExecutionOptions = { cwd: this.workspaceFolder.uri.fsPath };
+ let task = new vscode.Task(kind, this.workspaceFolder, line, 'gulp', new vscode.ShellExecution(`${gulpCommand} ${line}`, options));
+ result.push(task);
+ let lowerCaseLine = line.toLowerCase();
+ if (isBuildTask(lowerCaseLine)) {
+ task.group = vscode.TaskGroup.Build;
+ } else if (isTestTask(lowerCaseLine)) {
+ task.group = vscode.TaskGroup.Test;
+ }
+ }
+ }
+ return result;
+ } catch (err) {
+ let channel = getOutputChannel();
+ if (err.stderr) {
+ channel.appendLine(err.stderr);
+ }
+ if (err.stdout) {
+ channel.appendLine(err.stdout);
+ }
+ channel.appendLine(localize('execFailed', 'Auto detecting gulp failed with error: {0}', err.error ? err.error.toString() : 'unknown'));
+ channel.show(true);
return emptyTasks;
}
}
- let gulpCommand: string;
- let platform = process.platform;
- if (platform === 'win32' && await exists(path.join(workspaceRoot!, 'node_modules', '.bin', 'gulp.cmd'))) {
- gulpCommand = path.join('.', 'node_modules', '.bin', 'gulp.cmd');
- } else if ((platform === 'linux' || platform === 'darwin') && await exists(path.join(workspaceRoot!, 'node_modules', '.bin', 'gulp'))) {
- gulpCommand = path.join('.', 'node_modules', '.bin', 'gulp');
- } else {
- gulpCommand = 'gulp';
+ public dispose() {
+ this.promise = undefined;
+ if (this.fileWatcher) {
+ this.fileWatcher.dispose();
+ }
+ }
+}
+
+class TaskDetector {
+
+ private taskProvider: vscode.Disposable | undefined;
+ private detectors: Map = new Map();
+ private promise: Promise | undefined;
+
+ constructor() {
}
- let commandLine = `${gulpCommand} --tasks-simple --no-color`;
- try {
- let { stdout, stderr } = await exec(commandLine, { cwd: workspaceRoot });
- if (stderr && stderr.length > 0) {
- getOutputChannel().appendLine(stderr);
- getOutputChannel().show(true);
+ public start(): void {
+ let folders = vscode.workspace.workspaceFolders;
+ if (folders) {
+ this.updateWorkspaceFolders(folders, []);
}
- let result: vscode.Task[] = [];
- if (stdout) {
- let lines = stdout.split(/\r{0,1}\n/);
- for (let line of lines) {
- if (line.length === 0) {
- continue;
- }
- let kind: GulpTaskDefinition = {
- type: 'gulp',
- task: line
- };
- let task = new vscode.Task(kind, line, 'gulp', new vscode.ShellExecution(`${gulpCommand} ${line}`));
- result.push(task);
- let lowerCaseLine = line.toLowerCase();
- if (isBuildTask(lowerCaseLine)) {
- task.group = vscode.TaskGroup.Build;
- } else if (isTestTask(lowerCaseLine)) {
- task.group = vscode.TaskGroup.Test;
+ vscode.workspace.onDidChangeWorkspaceFolders((event) => this.updateWorkspaceFolders(event.added, event.removed));
+ vscode.workspace.onDidChangeConfiguration(this.updateConfiguration, this);
+ }
+
+ public dispose(): void {
+ if (this.taskProvider) {
+ this.taskProvider.dispose();
+ this.taskProvider = undefined;
+ }
+ this.detectors.clear();
+ this.promise = undefined;
+ }
+
+ private updateWorkspaceFolders(added: vscode.WorkspaceFolder[], removed: vscode.WorkspaceFolder[]): void {
+ let changed = false;
+ for (let remove of removed) {
+ let detector = this.detectors.get(remove.uri.toString());
+ if (detector) {
+ changed = true;
+ detector.dispose();
+ this.detectors.delete(remove.uri.toString());
+ }
+ }
+ for (let add of added) {
+ let detector = new FolderDetector(add);
+ if (detector.isEnabled()) {
+ changed = true;
+ this.detectors.set(add.uri.toString(), detector);
+ detector.start();
+ }
+ }
+ if (changed) {
+ this.promise = undefined;
+ }
+ this.updateProvider();
+ }
+
+ private updateConfiguration(): void {
+ let changed = false;
+ for (let detector of this.detectors.values()) {
+ if (!detector.isEnabled()) {
+ changed = true;
+ detector.dispose();
+ this.detectors.delete(detector.workspaceFolder.uri.toString());
+ }
+ }
+ let folders = vscode.workspace.workspaceFolders;
+ if (folders) {
+ for (let folder of folders) {
+ if (!this.detectors.has(folder.uri.toString())) {
+ let detector = new FolderDetector(folder);
+ if (detector.isEnabled()) {
+ changed = true;
+ this.detectors.set(folder.uri.toString(), detector);
+ detector.start();
+ }
}
}
}
- return result;
- } catch (err) {
- let channel = getOutputChannel();
- if (err.stderr) {
- channel.appendLine(err.stderr);
+ if (changed) {
+ this.promise = undefined;
}
- if (err.stdout) {
- channel.appendLine(err.stdout);
- }
- channel.appendLine(localize('execFailed', 'Auto detecting gulp failed with error: {0}', err.error ? err.error.toString() : 'unknown'));
- channel.show(true);
- return emptyTasks;
+ this.updateProvider();
}
+
+ private updateProvider(): void {
+ if (!this.taskProvider && this.detectors.size > 0) {
+ this.taskProvider = vscode.workspace.registerTaskProvider('gulp', {
+ provideTasks: () => {
+ return this.getTasks();
+ },
+ resolveTask(_task: vscode.Task): vscode.Task | undefined {
+ return undefined;
+ }
+ });
+ }
+ else if (this.taskProvider && this.detectors.size === 0) {
+ this.taskProvider.dispose();
+ this.taskProvider = undefined;
+ this.promise = undefined;
+ }
+ }
+
+ public getTasks(): Promise {
+ if (!this.promise) {
+ this.promise = this.computeTasks();
+ }
+ return this.promise;
+ }
+
+ private computeTasks(): Promise {
+ if (this.detectors.size === 0) {
+ return Promise.resolve([]);
+ } else if (this.detectors.size === 1) {
+ return this.detectors.values().next().value.getTasks();
+ } else {
+ let promises: Promise[] = [];
+ for (let detector of this.detectors.values()) {
+ promises.push(detector.getTasks().then((value) => value, () => []));
+ }
+ return Promise.all(promises).then((values) => {
+ let result: vscode.Task[] = [];
+ for (let tasks of values) {
+ if (tasks && tasks.length > 0) {
+ result.push(...tasks);
+ }
+ }
+ return result;
+ });
+ }
+ }
+}
+
+let detector: TaskDetector;
+export function activate(_context: vscode.ExtensionContext): void {
+ detector = new TaskDetector();
+ detector.start();
+}
+
+export function deactivate(): void {
+ detector.dispose();
}
\ No newline at end of file
diff --git a/extensions/json/test/colorize-results/test_json.json b/extensions/json/test/colorize-results/test_json.json
index facb8d52a57..f5295bcf70a 100644
--- a/extensions/json/test/colorize-results/test_json.json
+++ b/extensions/json/test/colorize-results/test_json.json
@@ -388,8 +388,8 @@
"c": "\\u0056",
"t": "source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json constant.character.escape.json",
"r": {
- "dark_plus": "constant.character.escape: #C586C0",
- "light_plus": "constant.character.escape: #FF0000",
+ "dark_plus": "constant.character.escape: #D7BA7D",
+ "light_plus": "constant.character.escape: #A31515",
"dark_vs": "string: #CE9178",
"light_vs": "string: #A31515",
"hc_black": "constant.character: #569CD6"
diff --git a/extensions/make/test/colorize-results/makefile.json b/extensions/make/test/colorize-results/makefile.json
index 1fb93bab87c..94d9b456703 100644
--- a/extensions/make/test/colorize-results/makefile.json
+++ b/extensions/make/test/colorize-results/makefile.json
@@ -135,8 +135,8 @@
"c": "\\",
"t": "source.makefile meta.scope.target.makefile meta.scope.prerequisites.makefile constant.character.escape.continuation.makefile",
"r": {
- "dark_plus": "constant.character.escape: #C586C0",
- "light_plus": "constant.character.escape: #FF0000",
+ "dark_plus": "constant.character.escape: #D7BA7D",
+ "light_plus": "constant.character.escape: #A31515",
"dark_vs": "default: #D4D4D4",
"light_vs": "default: #000000",
"hc_black": "constant.character: #569CD6"
@@ -278,8 +278,8 @@
"c": "\\",
"t": "source.makefile meta.scope.target.makefile meta.scope.prerequisites.makefile constant.character.escape.continuation.makefile",
"r": {
- "dark_plus": "constant.character.escape: #C586C0",
- "light_plus": "constant.character.escape: #FF0000",
+ "dark_plus": "constant.character.escape: #D7BA7D",
+ "light_plus": "constant.character.escape: #A31515",
"dark_vs": "default: #D4D4D4",
"light_vs": "default: #000000",
"hc_black": "constant.character: #569CD6"
@@ -322,8 +322,8 @@
"c": "\\",
"t": "source.makefile meta.scope.target.makefile meta.scope.prerequisites.makefile comment.line.number-sign.makefile constant.character.escape.continuation.makefile",
"r": {
- "dark_plus": "constant.character.escape: #C586C0",
- "light_plus": "constant.character.escape: #FF0000",
+ "dark_plus": "constant.character.escape: #D7BA7D",
+ "light_plus": "constant.character.escape: #A31515",
"dark_vs": "comment: #608B4E",
"light_vs": "comment: #008000",
"hc_black": "constant.character: #569CD6"
@@ -377,8 +377,8 @@
"c": "\\",
"t": "source.makefile comment.line.number-sign.makefile constant.character.escape.continuation.makefile",
"r": {
- "dark_plus": "constant.character.escape: #C586C0",
- "light_plus": "constant.character.escape: #FF0000",
+ "dark_plus": "constant.character.escape: #D7BA7D",
+ "light_plus": "constant.character.escape: #A31515",
"dark_vs": "comment: #608B4E",
"light_vs": "comment: #008000",
"hc_black": "constant.character: #569CD6"
@@ -553,8 +553,8 @@
"c": "\\",
"t": "source.makefile meta.scope.target.makefile meta.scope.prerequisites.makefile constant.character.escape.continuation.makefile",
"r": {
- "dark_plus": "constant.character.escape: #C586C0",
- "light_plus": "constant.character.escape: #FF0000",
+ "dark_plus": "constant.character.escape: #D7BA7D",
+ "light_plus": "constant.character.escape: #A31515",
"dark_vs": "default: #D4D4D4",
"light_vs": "default: #000000",
"hc_black": "constant.character: #569CD6"
diff --git a/extensions/perl/test/colorize-results/test2_pl.json b/extensions/perl/test/colorize-results/test2_pl.json
index af312ff8930..bf690458af4 100644
--- a/extensions/perl/test/colorize-results/test2_pl.json
+++ b/extensions/perl/test/colorize-results/test2_pl.json
@@ -1301,8 +1301,8 @@
"c": "\\d\\d\\d",
"t": "source.perl string.regexp.find.perl constant.character.escape.perl",
"r": {
- "dark_plus": "constant.character.escape: #C586C0",
- "light_plus": "constant.character.escape: #FF0000",
+ "dark_plus": "constant.character.escape: #D7BA7D",
+ "light_plus": "constant.character.escape: #A31515",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "constant.character: #569CD6"
@@ -1323,8 +1323,8 @@
"c": "\\d\\d",
"t": "source.perl string.regexp.find.perl constant.character.escape.perl",
"r": {
- "dark_plus": "constant.character.escape: #C586C0",
- "light_plus": "constant.character.escape: #FF0000",
+ "dark_plus": "constant.character.escape: #D7BA7D",
+ "light_plus": "constant.character.escape: #A31515",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "constant.character: #569CD6"
@@ -1345,8 +1345,8 @@
"c": "\\d\\d",
"t": "source.perl string.regexp.find.perl constant.character.escape.perl",
"r": {
- "dark_plus": "constant.character.escape: #C586C0",
- "light_plus": "constant.character.escape: #FF0000",
+ "dark_plus": "constant.character.escape: #D7BA7D",
+ "light_plus": "constant.character.escape: #A31515",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "constant.character: #569CD6"
@@ -1774,8 +1774,8 @@
"c": "\\d",
"t": "source.perl string.regexp.find.perl constant.character.escape.perl",
"r": {
- "dark_plus": "constant.character.escape: #C586C0",
- "light_plus": "constant.character.escape: #FF0000",
+ "dark_plus": "constant.character.escape: #D7BA7D",
+ "light_plus": "constant.character.escape: #A31515",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "constant.character: #569CD6"
diff --git a/extensions/perl/test/colorize-results/test_pl.json b/extensions/perl/test/colorize-results/test_pl.json
index 8062166dc5c..e6551c2c639 100644
--- a/extensions/perl/test/colorize-results/test_pl.json
+++ b/extensions/perl/test/colorize-results/test_pl.json
@@ -388,8 +388,8 @@
"c": "\\s",
"t": "source.perl string.regexp.find.perl constant.character.escape.perl",
"r": {
- "dark_plus": "constant.character.escape: #C586C0",
- "light_plus": "constant.character.escape: #FF0000",
+ "dark_plus": "constant.character.escape: #D7BA7D",
+ "light_plus": "constant.character.escape: #A31515",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "constant.character: #569CD6"
@@ -410,8 +410,8 @@
"c": "\\s",
"t": "source.perl string.regexp.find.perl constant.character.escape.perl",
"r": {
- "dark_plus": "constant.character.escape: #C586C0",
- "light_plus": "constant.character.escape: #FF0000",
+ "dark_plus": "constant.character.escape: #D7BA7D",
+ "light_plus": "constant.character.escape: #A31515",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "constant.character: #569CD6"
@@ -432,8 +432,8 @@
"c": "\\(",
"t": "source.perl string.regexp.find.perl constant.character.escape.perl",
"r": {
- "dark_plus": "constant.character.escape: #C586C0",
- "light_plus": "constant.character.escape: #FF0000",
+ "dark_plus": "constant.character.escape: #D7BA7D",
+ "light_plus": "constant.character.escape: #A31515",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "constant.character: #569CD6"
@@ -454,8 +454,8 @@
"c": "\\)",
"t": "source.perl string.regexp.find.perl constant.character.escape.perl",
"r": {
- "dark_plus": "constant.character.escape: #C586C0",
- "light_plus": "constant.character.escape: #FF0000",
+ "dark_plus": "constant.character.escape: #D7BA7D",
+ "light_plus": "constant.character.escape: #A31515",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "constant.character: #569CD6"
@@ -476,8 +476,8 @@
"c": "\\)",
"t": "source.perl string.regexp.find.perl constant.character.escape.perl",
"r": {
- "dark_plus": "constant.character.escape: #C586C0",
- "light_plus": "constant.character.escape: #FF0000",
+ "dark_plus": "constant.character.escape: #D7BA7D",
+ "light_plus": "constant.character.escape: #A31515",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "constant.character: #569CD6"
@@ -674,8 +674,8 @@
"c": "\\n",
"t": "source.perl string.quoted.double.perl constant.character.escape.perl",
"r": {
- "dark_plus": "constant.character.escape: #C586C0",
- "light_plus": "constant.character.escape: #FF0000",
+ "dark_plus": "constant.character.escape: #D7BA7D",
+ "light_plus": "constant.character.escape: #A31515",
"dark_vs": "string: #CE9178",
"light_vs": "string: #A31515",
"hc_black": "constant.character: #569CD6"
@@ -949,8 +949,8 @@
"c": "\\n",
"t": "source.perl string.quoted.double.perl constant.character.escape.perl",
"r": {
- "dark_plus": "constant.character.escape: #C586C0",
- "light_plus": "constant.character.escape: #FF0000",
+ "dark_plus": "constant.character.escape: #D7BA7D",
+ "light_plus": "constant.character.escape: #A31515",
"dark_vs": "string: #CE9178",
"light_vs": "string: #A31515",
"hc_black": "constant.character: #569CD6"
@@ -1444,8 +1444,8 @@
"c": "\\n",
"t": "source.perl string.quoted.double.perl constant.character.escape.perl",
"r": {
- "dark_plus": "constant.character.escape: #C586C0",
- "light_plus": "constant.character.escape: #FF0000",
+ "dark_plus": "constant.character.escape: #D7BA7D",
+ "light_plus": "constant.character.escape: #A31515",
"dark_vs": "string: #CE9178",
"light_vs": "string: #A31515",
"hc_black": "constant.character: #569CD6"
@@ -2280,8 +2280,8 @@
"c": "\\n",
"t": "source.perl string.quoted.double.perl constant.character.escape.perl",
"r": {
- "dark_plus": "constant.character.escape: #C586C0",
- "light_plus": "constant.character.escape: #FF0000",
+ "dark_plus": "constant.character.escape: #D7BA7D",
+ "light_plus": "constant.character.escape: #A31515",
"dark_vs": "string: #CE9178",
"light_vs": "string: #A31515",
"hc_black": "constant.character: #569CD6"
diff --git a/extensions/php/package.json b/extensions/php/package.json
index e3b313bb321..3f50c17669b 100644
--- a/extensions/php/package.json
+++ b/extensions/php/package.json
@@ -27,7 +27,7 @@
"PHP",
"php"
],
- "firstLine": "^#!/.*\\bphp\\b",
+ "firstLine": "^#!/.*\\bphp\\b",
"mimetypes": [
"application/x-php"
],
diff --git a/extensions/php/test/colorize-results/issue-28354_php.json b/extensions/php/test/colorize-results/issue-28354_php.json
index 5f1b87f3834..10314265464 100644
--- a/extensions/php/test/colorize-results/issue-28354_php.json
+++ b/extensions/php/test/colorize-results/issue-28354_php.json
@@ -278,8 +278,8 @@
"c": "\\'",
"t": "text.html.php meta.embedded.block.html source.js meta.embedded.block.php source.php string.quoted.single.php constant.character.escape.php",
"r": {
- "dark_plus": "constant.character.escape: #C586C0",
- "light_plus": "constant.character.escape: #FF0000",
+ "dark_plus": "constant.character.escape: #D7BA7D",
+ "light_plus": "constant.character.escape: #A31515",
"dark_vs": "string: #CE9178",
"light_vs": "string: #A31515",
"hc_black": "constant.character: #569CD6"
@@ -377,8 +377,8 @@
"c": "\\'",
"t": "text.html.php meta.embedded.block.html source.js meta.embedded.block.php source.php string.quoted.single.php constant.character.escape.php",
"r": {
- "dark_plus": "constant.character.escape: #C586C0",
- "light_plus": "constant.character.escape: #FF0000",
+ "dark_plus": "constant.character.escape: #D7BA7D",
+ "light_plus": "constant.character.escape: #A31515",
"dark_vs": "string: #CE9178",
"light_vs": "string: #A31515",
"hc_black": "constant.character: #569CD6"
diff --git a/extensions/python/test/colorize-results/test_py.json b/extensions/python/test/colorize-results/test_py.json
index ef8d364c52b..235958f078f 100644
--- a/extensions/python/test/colorize-results/test_py.json
+++ b/extensions/python/test/colorize-results/test_py.json
@@ -5294,8 +5294,8 @@
"c": "(",
"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.begin.regexp",
"r": {
- "dark_plus": "support.other.parenthesis.regexp: #D7BA7D",
- "light_plus": "support.other.parenthesis.regexp: #FF0000",
+ "dark_plus": "support.other.parenthesis.regexp: #CE9178",
+ "light_plus": "support.other.parenthesis.regexp: #D16969",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "string.regexp: #D16969"
@@ -5305,8 +5305,8 @@
"c": "[",
"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp punctuation.character.set.begin.regexp constant.other.set.regexp",
"r": {
- "dark_plus": "meta.character.set.regexp: #D7BA7D",
- "light_plus": "meta.character.set.regexp: #FF0000",
+ "dark_plus": "punctuation.character.set.begin.regexp: #CE9178",
+ "light_plus": "punctuation.character.set.begin.regexp: #D16969",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "string.regexp: #D16969"
@@ -5316,8 +5316,8 @@
"c": "0-9-",
"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp constant.character.set.regexp",
"r": {
- "dark_plus": "constant.character: #569CD6",
- "light_plus": "constant.character: #0000FF",
+ "dark_plus": "constant.character.set.regexp: #D16969",
+ "light_plus": "constant.character.set.regexp: #811F3F",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "constant.character: #569CD6"
@@ -5327,8 +5327,8 @@
"c": "]",
"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp punctuation.character.set.end.regexp constant.other.set.regexp",
"r": {
- "dark_plus": "meta.character.set.regexp: #D7BA7D",
- "light_plus": "meta.character.set.regexp: #FF0000",
+ "dark_plus": "punctuation.character.set.end.regexp: #CE9178",
+ "light_plus": "punctuation.character.set.end.regexp: #D16969",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "string.regexp: #D16969"
@@ -5338,8 +5338,8 @@
"c": "*",
"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python keyword.operator.quantifier.regexp",
"r": {
- "dark_plus": "keyword.operator.quantifier.regexp: #D4D4D4",
- "light_plus": "keyword.operator.quantifier.regexp: #0000FF",
+ "dark_plus": "keyword.operator.quantifier.regexp: #D7BA7D",
+ "light_plus": "keyword.operator.quantifier.regexp: #000000",
"dark_vs": "keyword.operator: #D4D4D4",
"light_vs": "keyword.operator: #000000",
"hc_black": "keyword.operator: #D4D4D4"
@@ -5349,8 +5349,8 @@
"c": ")",
"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.end.regexp",
"r": {
- "dark_plus": "support.other.parenthesis.regexp: #D7BA7D",
- "light_plus": "support.other.parenthesis.regexp: #FF0000",
+ "dark_plus": "support.other.parenthesis.regexp: #CE9178",
+ "light_plus": "support.other.parenthesis.regexp: #D16969",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "string.regexp: #D16969"
@@ -5371,8 +5371,8 @@
"c": "*",
"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python keyword.operator.quantifier.regexp",
"r": {
- "dark_plus": "keyword.operator.quantifier.regexp: #D4D4D4",
- "light_plus": "keyword.operator.quantifier.regexp: #0000FF",
+ "dark_plus": "keyword.operator.quantifier.regexp: #D7BA7D",
+ "light_plus": "keyword.operator.quantifier.regexp: #000000",
"dark_vs": "keyword.operator: #D4D4D4",
"light_vs": "keyword.operator: #000000",
"hc_black": "keyword.operator: #D4D4D4"
@@ -5382,8 +5382,8 @@
"c": "(",
"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.begin.regexp",
"r": {
- "dark_plus": "support.other.parenthesis.regexp: #D7BA7D",
- "light_plus": "support.other.parenthesis.regexp: #FF0000",
+ "dark_plus": "support.other.parenthesis.regexp: #CE9178",
+ "light_plus": "support.other.parenthesis.regexp: #D16969",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "string.regexp: #D16969"
@@ -5393,8 +5393,8 @@
"c": "[",
"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp punctuation.character.set.begin.regexp constant.other.set.regexp",
"r": {
- "dark_plus": "meta.character.set.regexp: #D7BA7D",
- "light_plus": "meta.character.set.regexp: #FF0000",
+ "dark_plus": "punctuation.character.set.begin.regexp: #CE9178",
+ "light_plus": "punctuation.character.set.begin.regexp: #D16969",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "string.regexp: #D16969"
@@ -5404,8 +5404,8 @@
"c": "A-Za-z",
"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp constant.character.set.regexp",
"r": {
- "dark_plus": "constant.character: #569CD6",
- "light_plus": "constant.character: #0000FF",
+ "dark_plus": "constant.character.set.regexp: #D16969",
+ "light_plus": "constant.character.set.regexp: #811F3F",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "constant.character: #569CD6"
@@ -5415,8 +5415,8 @@
"c": "]",
"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp punctuation.character.set.end.regexp constant.other.set.regexp",
"r": {
- "dark_plus": "meta.character.set.regexp: #D7BA7D",
- "light_plus": "meta.character.set.regexp: #FF0000",
+ "dark_plus": "punctuation.character.set.end.regexp: #CE9178",
+ "light_plus": "punctuation.character.set.end.regexp: #D16969",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "string.regexp: #D16969"
@@ -5426,8 +5426,8 @@
"c": "+",
"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python keyword.operator.quantifier.regexp",
"r": {
- "dark_plus": "keyword.operator.quantifier.regexp: #D4D4D4",
- "light_plus": "keyword.operator.quantifier.regexp: #0000FF",
+ "dark_plus": "keyword.operator.quantifier.regexp: #D7BA7D",
+ "light_plus": "keyword.operator.quantifier.regexp: #000000",
"dark_vs": "keyword.operator: #D4D4D4",
"light_vs": "keyword.operator: #000000",
"hc_black": "keyword.operator: #D4D4D4"
@@ -5437,8 +5437,8 @@
"c": ")",
"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.end.regexp",
"r": {
- "dark_plus": "support.other.parenthesis.regexp: #D7BA7D",
- "light_plus": "support.other.parenthesis.regexp: #FF0000",
+ "dark_plus": "support.other.parenthesis.regexp: #CE9178",
+ "light_plus": "support.other.parenthesis.regexp: #D16969",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "string.regexp: #D16969"
@@ -5470,8 +5470,8 @@
"c": "+",
"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python keyword.operator.quantifier.regexp",
"r": {
- "dark_plus": "keyword.operator.quantifier.regexp: #D4D4D4",
- "light_plus": "keyword.operator.quantifier.regexp: #0000FF",
+ "dark_plus": "keyword.operator.quantifier.regexp: #D7BA7D",
+ "light_plus": "keyword.operator.quantifier.regexp: #000000",
"dark_vs": "keyword.operator: #D4D4D4",
"light_vs": "keyword.operator: #000000",
"hc_black": "keyword.operator: #D4D4D4"
@@ -5481,8 +5481,8 @@
"c": "(",
"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.begin.regexp",
"r": {
- "dark_plus": "support.other.parenthesis.regexp: #D7BA7D",
- "light_plus": "support.other.parenthesis.regexp: #FF0000",
+ "dark_plus": "support.other.parenthesis.regexp: #CE9178",
+ "light_plus": "support.other.parenthesis.regexp: #D16969",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "string.regexp: #D16969"
@@ -5503,8 +5503,8 @@
"c": "*",
"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python keyword.operator.quantifier.regexp",
"r": {
- "dark_plus": "keyword.operator.quantifier.regexp: #D4D4D4",
- "light_plus": "keyword.operator.quantifier.regexp: #0000FF",
+ "dark_plus": "keyword.operator.quantifier.regexp: #D7BA7D",
+ "light_plus": "keyword.operator.quantifier.regexp: #000000",
"dark_vs": "keyword.operator: #D4D4D4",
"light_vs": "keyword.operator: #000000",
"hc_black": "keyword.operator: #D4D4D4"
@@ -5514,8 +5514,8 @@
"c": ")",
"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.end.regexp",
"r": {
- "dark_plus": "support.other.parenthesis.regexp: #D7BA7D",
- "light_plus": "support.other.parenthesis.regexp: #FF0000",
+ "dark_plus": "support.other.parenthesis.regexp: #CE9178",
+ "light_plus": "support.other.parenthesis.regexp: #D16969",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "string.regexp: #D16969"
@@ -6504,8 +6504,8 @@
"c": "[",
"t": "source.python string.regexp.quoted.multi.python meta.character.set.regexp punctuation.character.set.begin.regexp constant.other.set.regexp",
"r": {
- "dark_plus": "meta.character.set.regexp: #D7BA7D",
- "light_plus": "meta.character.set.regexp: #FF0000",
+ "dark_plus": "punctuation.character.set.begin.regexp: #CE9178",
+ "light_plus": "punctuation.character.set.begin.regexp: #D16969",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "string.regexp: #D16969"
@@ -6515,8 +6515,8 @@
"c": "1,2)`` leads to",
"t": "source.python string.regexp.quoted.multi.python meta.character.set.regexp constant.character.set.regexp",
"r": {
- "dark_plus": "constant.character: #569CD6",
- "light_plus": "constant.character: #0000FF",
+ "dark_plus": "constant.character.set.regexp: #D16969",
+ "light_plus": "constant.character.set.regexp: #811F3F",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "constant.character: #569CD6"
diff --git a/extensions/ruby/test/colorize-results/test_rb.json b/extensions/ruby/test/colorize-results/test_rb.json
index 005f04f37c3..996dbbf0192 100644
--- a/extensions/ruby/test/colorize-results/test_rb.json
+++ b/extensions/ruby/test/colorize-results/test_rb.json
@@ -2500,8 +2500,8 @@
"c": "\\d",
"t": "source.ruby string.regexp.classic.ruby meta.group.regexp.ruby constant.character.escape.ruby",
"r": {
- "dark_plus": "constant.character.escape: #C586C0",
- "light_plus": "constant.character.escape: #FF0000",
+ "dark_plus": "constant.character.escape: #D7BA7D",
+ "light_plus": "constant.character.escape: #A31515",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "constant.character: #569CD6"
@@ -2522,8 +2522,8 @@
"c": "\\.\\d",
"t": "source.ruby string.regexp.classic.ruby meta.group.regexp.ruby constant.character.escape.ruby",
"r": {
- "dark_plus": "constant.character.escape: #C586C0",
- "light_plus": "constant.character.escape: #FF0000",
+ "dark_plus": "constant.character.escape: #D7BA7D",
+ "light_plus": "constant.character.escape: #A31515",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "constant.character: #569CD6"
@@ -2544,8 +2544,8 @@
"c": "\\.\\d",
"t": "source.ruby string.regexp.classic.ruby meta.group.regexp.ruby constant.character.escape.ruby",
"r": {
- "dark_plus": "constant.character.escape: #C586C0",
- "light_plus": "constant.character.escape: #FF0000",
+ "dark_plus": "constant.character.escape: #D7BA7D",
+ "light_plus": "constant.character.escape: #A31515",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "constant.character: #569CD6"
@@ -2577,8 +2577,8 @@
"c": "\\.\\d",
"t": "source.ruby string.regexp.classic.ruby meta.group.regexp.ruby meta.group.regexp.ruby constant.character.escape.ruby",
"r": {
- "dark_plus": "constant.character.escape: #C586C0",
- "light_plus": "constant.character.escape: #FF0000",
+ "dark_plus": "constant.character.escape: #D7BA7D",
+ "light_plus": "constant.character.escape: #A31515",
"dark_vs": "string.regexp: #D16969",
"light_vs": "string.regexp: #811F3F",
"hc_black": "constant.character: #569CD6"
diff --git a/extensions/scss/test/colorize-results/test_scss.json b/extensions/scss/test/colorize-results/test_scss.json
index 301d6fc35fd..cc598d4c7a2 100644
--- a/extensions/scss/test/colorize-results/test_scss.json
+++ b/extensions/scss/test/colorize-results/test_scss.json
@@ -20716,8 +20716,8 @@
"c": "\\\\",
"t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.property-list.scss meta.property-value.scss string.quoted.single.scss constant.character.escape.scss",
"r": {
- "dark_plus": "constant.character.escape: #C586C0",
- "light_plus": "constant.character.escape: #FF0000",
+ "dark_plus": "constant.character.escape: #D7BA7D",
+ "light_plus": "constant.character.escape: #A31515",
"dark_vs": "string: #CE9178",
"light_vs": "string: #A31515",
"hc_black": "constant.character: #569CD6"
@@ -20837,8 +20837,8 @@
"c": "\\'",
"t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.property-value.scss string.quoted.single.scss constant.character.escape.scss",
"r": {
- "dark_plus": "constant.character.escape: #C586C0",
- "light_plus": "constant.character.escape: #FF0000",
+ "dark_plus": "constant.character.escape: #D7BA7D",
+ "light_plus": "constant.character.escape: #A31515",
"dark_vs": "string: #CE9178",
"light_vs": "string: #A31515",
"hc_black": "constant.character: #569CD6"
@@ -20914,8 +20914,8 @@
"c": "\\\"",
"t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.property-value.scss string.quoted.double.scss constant.character.escape.scss",
"r": {
- "dark_plus": "constant.character.escape: #C586C0",
- "light_plus": "constant.character.escape: #FF0000",
+ "dark_plus": "constant.character.escape: #D7BA7D",
+ "light_plus": "constant.character.escape: #A31515",
"dark_vs": "string: #CE9178",
"light_vs": "string: #A31515",
"hc_black": "constant.character: #569CD6"
diff --git a/extensions/theme-defaults/themes/dark_plus.json b/extensions/theme-defaults/themes/dark_plus.json
index 55052a69d66..6fdfb2263ce 100644
--- a/extensions/theme-defaults/themes/dark_plus.json
+++ b/extensions/theme-defaults/themes/dark_plus.json
@@ -103,28 +103,44 @@
}
},
{
+ "name": "Regular expression groups",
"scope": [
"punctuation.definition.group.regexp",
"punctuation.definition.group.assertion.regexp",
"punctuation.definition.character-class.regexp",
+ "punctuation.character.set.begin.regexp",
+ "punctuation.character.set.end.regexp",
"keyword.operator.negation.regexp",
- "support.other.parenthesis.regexp",
- "meta.character.set.regexp"
+ "support.other.parenthesis.regexp"
],
"settings": {
- "foreground": "#d7ba7d"
+ "foreground": "#CE9178"
+ }
+ },
+ {
+ "scope": [
+ "constant.character.character-class.regexp",
+ "constant.other.character-class.set.regexp",
+ "constant.other.character-class.regexp",
+ "constant.character.set.regexp"
+ ],
+ "settings": {
+ "foreground": "#d16969"
+ }
+ },
+ {
+ "scope": [
+ "keyword.operator.or.regexp",
+ "keyword.control.anchor.regexp"
+ ],
+ "settings": {
+ "foreground": "#DCDCAA"
}
},
{
"scope": "keyword.operator.quantifier.regexp",
"settings": {
- "foreground": "#d4d4d4"
- }
- },
- {
- "scope": "keyword.control.anchor.regexp",
- "settings": {
- "foreground": "#C586C0"
+ "foreground": "#d7ba7d"
}
},
{
@@ -136,9 +152,8 @@
{
"scope": "constant.character.escape",
"settings": {
- "foreground": "#C586C0"
+ "foreground": "#d7ba7d"
}
}
-
]
}
\ No newline at end of file
diff --git a/extensions/theme-defaults/themes/light_plus.json b/extensions/theme-defaults/themes/light_plus.json
index b533627c9f2..3d5775ecec5 100644
--- a/extensions/theme-defaults/themes/light_plus.json
+++ b/extensions/theme-defaults/themes/light_plus.json
@@ -103,28 +103,44 @@
}
},
{
+ "name": "Regular expression groups",
"scope": [
"punctuation.definition.group.regexp",
"punctuation.definition.group.assertion.regexp",
"punctuation.definition.character-class.regexp",
+ "punctuation.character.set.begin.regexp",
+ "punctuation.character.set.end.regexp",
"keyword.operator.negation.regexp",
- "support.other.parenthesis.regexp",
- "meta.character.set.regexp"
+ "support.other.parenthesis.regexp"
],
"settings": {
- "foreground": "#ff0000"
+ "foreground": "#d16969"
+ }
+ },
+ {
+ "scope": [
+ "constant.character.character-class.regexp",
+ "constant.other.character-class.set.regexp",
+ "constant.other.character-class.regexp",
+ "constant.character.set.regexp"
+ ],
+ "settings": {
+ "foreground": "#811f3f"
}
},
{
"scope": "keyword.operator.quantifier.regexp",
"settings": {
- "foreground": "#0000ff"
+ "foreground": "#000000"
}
},
{
- "scope": "keyword.control.anchor.regexp",
+ "scope": [
+ "keyword.operator.or.regexp",
+ "keyword.control.anchor.regexp"
+ ],
"settings": {
- "foreground": "#09885a"
+ "foreground": "#ff0000"
}
},
{
@@ -136,8 +152,9 @@
{
"scope": "constant.character.escape",
"settings": {
- "foreground": "#ff0000"
+ "foreground": "#a31515"
}
}
+
]
}
\ No newline at end of file
diff --git a/src/vs/base/common/uri.ts b/src/vs/base/common/uri.ts
index 043e9be8be9..4dfeff378ee 100644
--- a/src/vs/base/common/uri.ts
+++ b/src/vs/base/common/uri.ts
@@ -56,6 +56,7 @@ export default class URI {
private static _empty = '';
private static _slash = '/';
private static _regexp = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
+ private static _driveLetter = /^[a-zA-Z]:/;
private static _driveLetterPath = /^\/[a-zA-Z]:/;
private static _upperCaseDrive = /^(\/)?([A-Z]:)/;
@@ -212,19 +213,25 @@ export default class URI {
let idx = path.indexOf(URI._slash, 2);
if (idx === -1) {
authority = path.substring(2);
- path = URI._empty;
+ path = URI._slash;
} else {
authority = path.substring(2, idx);
path = path.substring(idx);
}
}
- // Ensure that path starts with a slash
- // or that it is at least a slash
- if (path[0] !== URI._slash) {
+ // absolute windows paths get another slash
+ if (URI._driveLetter.test(path)) {
path = URI._slash + path;
}
+ // path must be absolute because we cannot format them
+ // otherwise: `file:./foo` -> file://./foo -> `{ authority: '.', path: 'foo' }`
+ // https://tools.ietf.org/html/rfc8089#section-2
+ else if (path[0] !== URI._slash) {
+ throw new Error('[UriError]: relative paths are not supported.');
+ }
+
return new URI('file', authority, path, URI._empty, URI._empty);
}
@@ -301,8 +308,20 @@ export default class URI {
parts.push('//');
}
if (authority) {
+ let idx = authority.indexOf('@');
+ if (idx !== -1) {
+ const userinfo = authority.substr(0, idx);
+ authority = authority.substr(idx + 1);
+ idx = userinfo.indexOf(':');
+ if (idx === -1) {
+ parts.push(encoder(userinfo));
+ } else {
+ parts.push(encoder(userinfo.substr(0, idx)), ':', encoder(userinfo.substr(idx + 1)));
+ }
+ parts.push('@');
+ }
authority = authority.toLowerCase();
- let idx = authority.indexOf(':');
+ idx = authority.indexOf(':');
if (idx === -1) {
parts.push(encoder(authority));
} else {
diff --git a/src/vs/base/test/common/uri.test.ts b/src/vs/base/test/common/uri.test.ts
index 1e8340f98c7..df2d989d26c 100644
--- a/src/vs/base/test/common/uri.test.ts
+++ b/src/vs/base/test/common/uri.test.ts
@@ -38,13 +38,11 @@ suite('URI', () => {
assert.equal(URI.file('c:/win/path/').fsPath, 'c:\\win\\path\\');
assert.equal(URI.file('C:/win/path').fsPath, 'c:\\win\\path');
assert.equal(URI.file('/c:/win/path').fsPath, 'c:\\win\\path');
- assert.equal(URI.file('./c/win/path').fsPath, '\\.\\c\\win\\path');
} else {
assert.equal(URI.file('c:/win/path').fsPath, 'c:/win/path');
assert.equal(URI.file('c:/win/path/').fsPath, 'c:/win/path/');
assert.equal(URI.file('C:/win/path').fsPath, 'c:/win/path');
assert.equal(URI.file('/c:/win/path').fsPath, 'c:/win/path');
- assert.equal(URI.file('./c/win/path').fsPath, '/./c/win/path');
}
});
@@ -318,27 +316,12 @@ suite('URI', () => {
test('URI#file, no path-is-uri check', () => {
// we don't complain here
- let value = URI.file('file://path/to/file');
+ let value = URI.file('/file://path/to/file');
assert.equal(value.scheme, 'file');
assert.equal(value.authority, '');
assert.equal(value.path, '/file://path/to/file');
});
- test('URI#file, always slash', () => {
-
- var value = URI.file('a.file');
- assert.equal(value.scheme, 'file');
- assert.equal(value.authority, '');
- assert.equal(value.path, '/a.file');
- assert.equal(value.toString(), 'file:///a.file');
-
- value = URI.parse(value.toString());
- assert.equal(value.scheme, 'file');
- assert.equal(value.authority, '');
- assert.equal(value.path, '/a.file');
- assert.equal(value.toString(), 'file:///a.file');
- });
-
test('URI.toString, only scheme and query', () => {
var value = URI.parse('stuff:?qüery');
assert.equal(value.toString(), 'stuff:?q%C3%BCery');
@@ -368,6 +351,23 @@ suite('URI', () => {
assert.equal(value.toString(), 'http://l%C3%B6calhost:8080/far');
});
+ test('URI#toString, user information in authority', () => {
+ var value = URI.parse('http://foo:bar@localhost/far');
+ assert.equal(value.toString(), 'http://foo:bar@localhost/far');
+
+ value = URI.parse('http://foo@localhost/far');
+ assert.equal(value.toString(), 'http://foo@localhost/far');
+
+ value = URI.parse('http://foo:bAr@localhost:8080/far');
+ assert.equal(value.toString(), 'http://foo:bAr@localhost:8080/far');
+
+ value = URI.parse('http://foo@localhost:8080/far');
+ assert.equal(value.toString(), 'http://foo@localhost:8080/far');
+
+ value = URI.from({ scheme: 'http', authority: 'fƶƶ:bƶr@lƶcalhost:8080', path: '/far', query: undefined, fragment: undefined });
+ assert.equal(value.toString(), 'http://f%C3%B6%C3%B6:b%C3%B6r@l%C3%B6calhost:8080/far');
+ });
+
test('correctFileUriToFilePath2', () => {
var test = (input: string, expected: string) => {
@@ -410,13 +410,36 @@ suite('URI', () => {
assert.equal(uri.toString(true), 'https://twitter.com/search?src=typd&q=%23tag');
});
+ test('class URI cannot represent relative file paths, #34449', function () {
+ const uri = URI.parse('file:./relative/path');
+ assert.equal(uri.scheme, 'file');
+ assert.equal(uri.authority, '');
+ assert.equal(uri.path, './relative/path');
+
+ assert.equal(uri.toString(), 'file://./relative/path');
+
+ // this is asymetric to be spec-compliant
+ const uri2 = URI.parse(uri.toString());
+ assert.equal(uri2.authority, '.');
+ assert.equal(uri2.path, '/relative/path');
+ });
+
+ test('class URI cannot represent relative file paths, #34449', function () {
+
+ const path = '/foo/bar';
+ assert.equal(URI.file(path).path, path);
+
+ // no relative paths
+ assert.throws(() => URI.file('foo/bar'));
+ assert.throws(() => URI.file('./foo/bar'));
+ });
test('URI - (de)serialize', function () {
var values = [
URI.parse('http://localhost:8080/far'),
URI.file('c:\\test with %25\\c#code'),
- URI.file('\\\\shƤres\\path\\c#\\plugin.json'),
+ URI.file('//shƤres/path/c#/plugin.json'),
URI.parse('http://api/files/test.me?t=1234'),
URI.parse('http://api/files/test.me?t=1234#fff'),
URI.parse('http://api/files/test.me#fff'),
diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts
index e04278782bf..5c6464d7db1 100644
--- a/src/vs/code/electron-main/windows.ts
+++ b/src/vs/code/electron-main/windows.ts
@@ -5,7 +5,7 @@
'use strict';
-import * as path from 'path';
+import { basename, normalize, join, dirname } from 'path';
import * as fs from 'original-fs';
import { localize } from 'vs/nls';
import * as arrays from 'vs/base/common/arrays';
@@ -114,6 +114,7 @@ export class WindowsManager implements IWindowsMainService {
private lastClosedWindowState: IWindowState;
private fileDialog: FileDialog;
+ private workspacesManager: WorkspacesManager;
private _onWindowReady = new Emitter();
onWindowReady: CommonEvent = this._onWindowReady.event;
@@ -146,7 +147,9 @@ export class WindowsManager implements IWindowsMainService {
@IInstantiationService private instantiationService: IInstantiationService
) {
this.windowsState = this.storageService.getItem(WindowsManager.windowsStateStorageKey) || { openedWindows: [] };
+
this.fileDialog = new FileDialog(environmentService, telemetryService, storageService, this);
+ this.workspacesManager = new WorkspacesManager(workspacesService, lifecycleService, backupService, environmentService, this);
this.migrateLegacyWindowState();
}
@@ -403,7 +406,7 @@ export class WindowsManager implements IWindowsMainService {
workspacesToRestore.push(...this.workspacesService.getUntitledWorkspacesSync()); // collect from previous window session
emptyToRestore = this.backupService.getEmptyWindowBackupPaths();
- emptyToRestore.push(...pathsToOpen.filter(w => !w.workspace && !w.folderPath && w.backupPath).map(w => path.basename(w.backupPath))); // add empty windows with backupPath
+ emptyToRestore.push(...pathsToOpen.filter(w => !w.workspace && !w.folderPath && w.backupPath).map(w => basename(w.backupPath))); // add empty windows with backupPath
emptyToRestore = arrays.distinct(emptyToRestore); // prevent duplicates
}
@@ -932,7 +935,7 @@ export class WindowsManager implements IWindowsMainService {
anyPath = parsedPath.path;
}
- const candidate = path.normalize(anyPath);
+ const candidate = normalize(anyPath);
try {
const candidateStat = fs.statSync(candidate);
if (candidateStat) {
@@ -1051,7 +1054,7 @@ export class WindowsManager implements IWindowsMainService {
// For all other cases we first call into registerEmptyWindowBackupSync() to set it before
// loading the window.
if (options.emptyWindowBackupFolder) {
- configuration.backupPath = path.join(this.environmentService.backupHome, options.emptyWindowBackupFolder);
+ configuration.backupPath = join(this.environmentService.backupHome, options.emptyWindowBackupFolder);
}
let window: CodeWindow;
@@ -1291,93 +1294,19 @@ export class WindowsManager implements IWindowsMainService {
});
}
- public saveAndOpenWorkspace(window: CodeWindow, path: string): TPromise {
- if (!window || !window.win || window.readyState !== ReadyState.READY || !window.openedWorkspace || !path) {
- return TPromise.as(null); // return early if the window is not ready or disposed or does not have a workspace
- }
-
- return this.doSaveAndOpenWorkspace(window, window.openedWorkspace, path);
+ public saveAndOpenWorkspace(win: CodeWindow, path: string): TPromise {
+ return this.workspacesManager.saveAndOpenWorkspace(win, path);
}
- public createAndOpenWorkspace(window: CodeWindow, folders?: string[], path?: string): TPromise {
- if (!window || !window.win || window.readyState !== ReadyState.READY) {
- return TPromise.as(null); // return early if the window is not ready or disposed
- }
-
- return this.workspacesService.createWorkspace(folders).then(workspace => {
- return this.doSaveAndOpenWorkspace(window, workspace, path);
- });
+ public createAndOpenWorkspace(win: CodeWindow, folders?: string[], path?: string): TPromise {
+ return this.workspacesManager.createAndOpenWorkspace(win, folders, path);
}
- private doSaveAndOpenWorkspace(window: CodeWindow, workspace: IWorkspaceIdentifier, path?: string): TPromise {
- let savePromise: TPromise;
- if (path) {
- savePromise = this.workspacesService.saveWorkspace(workspace, path);
- } else {
- savePromise = TPromise.as(workspace);
- }
-
- return savePromise.then(workspace => {
- window.focus();
-
- // Only open workspace when the window has not vetoed this
- return this.lifecycleService.unload(window, UnloadReason.RELOAD, workspace).done(veto => {
- if (!veto) {
-
- // Register window for backups and migrate current backups over
- let backupPath: string;
- if (window.config && !window.config.extensionDevelopmentPath) {
- backupPath = this.backupService.registerWorkspaceBackupSync(workspace, window.config.backupPath);
- }
-
- // Craft a new window configuration to use for the transition
- const configuration: IWindowConfiguration = mixin({}, window.config);
- configuration.folderPath = void 0;
- configuration.workspace = workspace;
- configuration.backupPath = backupPath;
-
- // Reload
- window.reload(configuration);
- }
- });
- });
+ public openWorkspace(win?: CodeWindow): void {
+ this.workspacesManager.openWorkspace(win);
}
- public openWorkspace(window: CodeWindow = this.getLastActiveWindow()): void {
- let defaultPath: string;
- if (window && window.openedWorkspace && !this.workspacesService.isUntitledWorkspace(window.openedWorkspace)) {
- defaultPath = path.dirname(window.openedWorkspace.configPath);
- } else {
- defaultPath = this.getWorkspaceDialogDefaultPath(window ? (window.openedWorkspace || window.openedFolderPath) : void 0);
- }
- this.pickFileAndOpen({
- windowId: window ? window.id : void 0,
- dialogOptions: {
- buttonLabel: mnemonicButtonLabel(localize({ key: 'openWorkspace', comment: ['&& denotes a mnemonic'] }, "&&Open")),
- title: localize('openWorkspaceTitle', "Open Workspace"),
- filters: WORKSPACE_FILTER,
- properties: ['openFile'],
- defaultPath
- }
- });
- }
-
- private getWorkspaceDialogDefaultPath(workspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier): string {
- let defaultPath: string;
- if (workspace) {
- if (isSingleFolderWorkspaceIdentifier(workspace)) {
- defaultPath = path.dirname(workspace);
- } else {
- const resolvedWorkspace = this.workspacesService.resolveWorkspaceSync(workspace.configPath);
- if (resolvedWorkspace && resolvedWorkspace.folders.length > 0) {
- defaultPath = path.dirname(resolvedWorkspace.folders[0].path);
- }
- }
- }
-
- return defaultPath;
- }
private onBeforeWindowUnload(e: IWindowUnloadEvent): void {
const windowClosing = (e.reason === UnloadReason.CLOSE);
@@ -1399,74 +1328,8 @@ export class WindowsManager implements IWindowsMainService {
return; // Windows/Linux: quits when last window is closed, so do not ask then
}
- this.promptToSaveUntitledWorkspace(e, workspace);
- }
-
- private promptToSaveUntitledWorkspace(e: IWindowUnloadEvent, workspace: IWorkspaceIdentifier): void {
- enum ConfirmResult {
- SAVE,
- DONT_SAVE,
- CANCEL
- }
-
- const save = { label: mnemonicButtonLabel(localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save")), result: ConfirmResult.SAVE };
- const dontSave = { label: mnemonicButtonLabel(localize({ key: 'doNotSave', comment: ['&& denotes a mnemonic'] }, "Do&&n't Save")), result: ConfirmResult.DONT_SAVE };
- const cancel = { label: localize('cancel', "Cancel"), result: ConfirmResult.CANCEL };
-
- const buttons: { label: string; result: ConfirmResult; }[] = [];
- if (isWindows) {
- buttons.push(save, dontSave, cancel);
- } else if (isLinux) {
- buttons.push(dontSave, cancel, save);
- } else {
- buttons.push(save, cancel, dontSave);
- }
-
- const options: Electron.MessageBoxOptions = {
- title: this.environmentService.appNameLong,
- message: localize('saveWorkspaceMessage', "Do you want to save your workspace configuration as a file?"),
- detail: localize('saveWorkspaceDetail', "Save your workspace if you plan to open it again."),
- noLink: true,
- type: 'warning',
- buttons: buttons.map(button => button.label),
- cancelId: buttons.indexOf(cancel)
- };
-
- if (isLinux) {
- options.defaultId = 2;
- }
-
- const res = dialog.showMessageBox(e.window.win, options);
-
- switch (buttons[res].result) {
-
- // Cancel: veto unload
- case ConfirmResult.CANCEL:
- e.veto(true);
- break;
-
- // Don't Save: delete workspace
- case ConfirmResult.DONT_SAVE:
- this.workspacesService.deleteUntitledWorkspaceSync(workspace);
- e.veto(false);
- break;
-
- // Save: save workspace, but do not veto unload
- case ConfirmResult.SAVE: {
- const target = dialog.showSaveDialog(e.window.win, {
- buttonLabel: mnemonicButtonLabel(localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save")),
- title: localize('saveWorkspace', "Save Workspace"),
- filters: WORKSPACE_FILTER,
- defaultPath: this.getWorkspaceDialogDefaultPath(workspace)
- });
-
- if (target) {
- e.veto(this.workspacesService.saveWorkspace(workspace, target).then(() => false, () => false));
- } else {
- e.veto(true); // keep veto if no target was provided
- }
- }
- }
+ // Handle untitled workspaces with prompt as needed
+ this.workspacesManager.promptToSaveUntitledWorkspace(e, workspace);
}
public focusLastActive(cli: ParsedArgs, context: OpenContext): CodeWindow {
@@ -1754,7 +1617,7 @@ class FileDialog {
if (paths && paths.length > 0) {
// Remember path in storage for next time
- this.storageService.setItem(FileDialog.workingDirPickerStorageKey, path.dirname(paths[0]));
+ this.storageService.setItem(FileDialog.workingDirPickerStorageKey, dirname(paths[0]));
// Return
return clb(paths);
@@ -1763,4 +1626,204 @@ class FileDialog {
return clb(void (0));
});
}
+}
+
+class WorkspacesManager {
+
+ constructor(
+ private workspacesService: IWorkspacesMainService,
+ private lifecycleService: ILifecycleService,
+ private backupService: IBackupMainService,
+ private environmentService: IEnvironmentService,
+ private windowsMainService: IWindowsMainService
+ ) {
+ }
+
+ public saveAndOpenWorkspace(window: CodeWindow, path: string): TPromise {
+ if (!window || !window.win || window.readyState !== ReadyState.READY || !window.openedWorkspace || !path || !this.isValidTargetWorkspacePath(window, path)) {
+ return TPromise.as(null); // return early if the window is not ready or disposed or does not have a workspace
+ }
+
+ return this.doSaveAndOpenWorkspace(window, window.openedWorkspace, path);
+ }
+
+ public createAndOpenWorkspace(window: CodeWindow, folders?: string[], path?: string): TPromise {
+ if (!window || !window.win || window.readyState !== ReadyState.READY || !this.isValidTargetWorkspacePath(window, path)) {
+ return TPromise.as(null); // return early if the window is not ready or disposed
+ }
+
+ return this.workspacesService.createWorkspace(folders).then(workspace => {
+ return this.doSaveAndOpenWorkspace(window, workspace, path);
+ });
+ }
+
+ private isValidTargetWorkspacePath(window: CodeWindow, path?: string): boolean {
+ if (!path) {
+ return true;
+ }
+
+ if (window.openedWorkspace && window.openedWorkspace.configPath === path) {
+ return false; // window is already opened on a workspace with that path
+ }
+
+ // Prevent overwriting a workspace that is currently opened in another window
+ if (findWindowOnWorkspace(this.windowsMainService.getWindows(), { id: this.workspacesService.getWorkspaceId(path), configPath: path })) {
+ const options: Electron.MessageBoxOptions = {
+ title: product.nameLong,
+ type: 'info',
+ buttons: [localize('ok', "OK")],
+ message: localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", basename(path)),
+ detail: localize('workspaceOpenedDetail', "The workspace is already opened in another window. Please close that window first and then try again."),
+ noLink: true
+ };
+
+ const activeWindow = BrowserWindow.getFocusedWindow();
+ if (activeWindow) {
+ dialog.showMessageBox(activeWindow, options);
+ } else {
+ dialog.showMessageBox(options);
+ }
+
+ return false;
+ }
+
+ return true; // OK
+ }
+
+ private doSaveAndOpenWorkspace(window: CodeWindow, workspace: IWorkspaceIdentifier, path?: string): TPromise {
+ let savePromise: TPromise;
+ if (path) {
+ savePromise = this.workspacesService.saveWorkspace(workspace, path);
+ } else {
+ savePromise = TPromise.as(workspace);
+ }
+
+ return savePromise.then(workspace => {
+ window.focus();
+
+ // Only open workspace when the window has not vetoed this
+ return this.lifecycleService.unload(window, UnloadReason.RELOAD, workspace).done(veto => {
+ if (!veto) {
+
+ // Register window for backups and migrate current backups over
+ let backupPath: string;
+ if (window.config && !window.config.extensionDevelopmentPath) {
+ backupPath = this.backupService.registerWorkspaceBackupSync(workspace, window.config.backupPath);
+ }
+
+ // Craft a new window configuration to use for the transition
+ const configuration: IWindowConfiguration = mixin({}, window.config);
+ configuration.folderPath = void 0;
+ configuration.workspace = workspace;
+ configuration.backupPath = backupPath;
+
+ // Reload
+ window.reload(configuration);
+ }
+ });
+ });
+ }
+
+ public openWorkspace(window = this.windowsMainService.getLastActiveWindow()): void {
+ let defaultPath: string;
+ if (window && window.openedWorkspace && !this.workspacesService.isUntitledWorkspace(window.openedWorkspace)) {
+ defaultPath = dirname(window.openedWorkspace.configPath);
+ } else {
+ defaultPath = this.getWorkspaceDialogDefaultPath(window ? (window.openedWorkspace || window.openedFolderPath) : void 0);
+ }
+
+ this.windowsMainService.pickFileAndOpen({
+ windowId: window ? window.id : void 0,
+ dialogOptions: {
+ buttonLabel: mnemonicButtonLabel(localize({ key: 'openWorkspace', comment: ['&& denotes a mnemonic'] }, "&&Open")),
+ title: localize('openWorkspaceTitle', "Open Workspace"),
+ filters: WORKSPACE_FILTER,
+ properties: ['openFile'],
+ defaultPath
+ }
+ });
+ }
+
+ public promptToSaveUntitledWorkspace(e: IWindowUnloadEvent, workspace: IWorkspaceIdentifier): void {
+ enum ConfirmResult {
+ SAVE,
+ DONT_SAVE,
+ CANCEL
+ }
+
+ const save = { label: mnemonicButtonLabel(localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save")), result: ConfirmResult.SAVE };
+ const dontSave = { label: mnemonicButtonLabel(localize({ key: 'doNotSave', comment: ['&& denotes a mnemonic'] }, "Do&&n't Save")), result: ConfirmResult.DONT_SAVE };
+ const cancel = { label: localize('cancel', "Cancel"), result: ConfirmResult.CANCEL };
+
+ const buttons: { label: string; result: ConfirmResult; }[] = [];
+ if (isWindows) {
+ buttons.push(save, dontSave, cancel);
+ } else if (isLinux) {
+ buttons.push(dontSave, cancel, save);
+ } else {
+ buttons.push(save, cancel, dontSave);
+ }
+
+ const options: Electron.MessageBoxOptions = {
+ title: this.environmentService.appNameLong,
+ message: localize('saveWorkspaceMessage', "Do you want to save your workspace configuration as a file?"),
+ detail: localize('saveWorkspaceDetail', "Save your workspace if you plan to open it again."),
+ noLink: true,
+ type: 'warning',
+ buttons: buttons.map(button => button.label),
+ cancelId: buttons.indexOf(cancel)
+ };
+
+ if (isLinux) {
+ options.defaultId = 2;
+ }
+
+ const res = dialog.showMessageBox(e.window.win, options);
+
+ switch (buttons[res].result) {
+
+ // Cancel: veto unload
+ case ConfirmResult.CANCEL:
+ e.veto(true);
+ break;
+
+ // Don't Save: delete workspace
+ case ConfirmResult.DONT_SAVE:
+ this.workspacesService.deleteUntitledWorkspaceSync(workspace);
+ e.veto(false);
+ break;
+
+ // Save: save workspace, but do not veto unload
+ case ConfirmResult.SAVE: {
+ const target = dialog.showSaveDialog(e.window.win, {
+ buttonLabel: mnemonicButtonLabel(localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save")),
+ title: localize('saveWorkspace', "Save Workspace"),
+ filters: WORKSPACE_FILTER,
+ defaultPath: this.getWorkspaceDialogDefaultPath(workspace)
+ });
+
+ if (target) {
+ e.veto(this.workspacesService.saveWorkspace(workspace, target).then(() => false, () => false));
+ } else {
+ e.veto(true); // keep veto if no target was provided
+ }
+ }
+ }
+ }
+
+ private getWorkspaceDialogDefaultPath(workspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier): string {
+ let defaultPath: string;
+ if (workspace) {
+ if (isSingleFolderWorkspaceIdentifier(workspace)) {
+ defaultPath = dirname(workspace);
+ } else {
+ const resolvedWorkspace = this.workspacesService.resolveWorkspaceSync(workspace.configPath);
+ if (resolvedWorkspace && resolvedWorkspace.folders.length > 0) {
+ defaultPath = dirname(resolvedWorkspace.folders[0].path);
+ }
+ }
+ }
+
+ return defaultPath;
+ }
}
\ No newline at end of file
diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts
index b8c5900a2d2..79cf5f2c105 100644
--- a/src/vs/editor/browser/view/viewImpl.ts
+++ b/src/vs/editor/browser/view/viewImpl.ts
@@ -308,7 +308,8 @@ export class View extends ViewEventHandler {
}
private getEditorClassName() {
- return this._context.configuration.editor.editorClassName + ' ' + getThemeTypeSelector(this._context.theme.type);
+ let focused = this._textAreaHandler.isFocused() ? ' focused' : '';
+ return this._context.configuration.editor.editorClassName + ' ' + getThemeTypeSelector(this._context.theme.type) + focused;
}
// --- begin event handlers
@@ -323,7 +324,7 @@ export class View extends ViewEventHandler {
return false;
}
public onFocusChanged(e: viewEvents.ViewFocusChangedEvent): boolean {
- this.domNode.toggleClassName('focused', e.isFocused);
+ this.domNode.setClassName(this.getEditorClassName());
if (e.isFocused) {
this.outgoingEvents.emitViewFocusGained();
} else {
diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts
index fe646ba39e4..22efae7b65e 100644
--- a/src/vs/editor/browser/widget/codeEditorWidget.ts
+++ b/src/vs/editor/browser/widget/codeEditorWidget.ts
@@ -400,6 +400,7 @@ export abstract class CodeEditorWidget extends CommonCodeEditor implements edito
this._view.render(false, true);
this.hasView = true;
+ this._view.domNode.domNode.setAttribute('data-uri', model.uri.toString());
}
}
diff --git a/src/vs/platform/backup/electron-main/backupMainService.ts b/src/vs/platform/backup/electron-main/backupMainService.ts
index 94210b0572f..9ea43c880e6 100644
--- a/src/vs/platform/backup/electron-main/backupMainService.ts
+++ b/src/vs/platform/backup/electron-main/backupMainService.ts
@@ -88,14 +88,19 @@ export class BackupMainService implements IBackupMainService {
}
private moveBackupFolderSync(backupPath: string, moveFromPath: string): void {
- if (!fs.existsSync(moveFromPath)) {
- return;
+
+ // Target exists: make sure to convert existing backups to empty window backups
+ if (fs.existsSync(backupPath)) {
+ this.convertToEmptyWindowBackup(backupPath);
}
- try {
- fs.renameSync(moveFromPath, backupPath);
- } catch (ex) {
- this.logService.error(`Backup: Could not move backup folder to new location: ${ex.toString()}`);
+ // When we have data to migrate from, move it over to the target location
+ if (fs.existsSync(moveFromPath)) {
+ try {
+ fs.renameSync(moveFromPath, backupPath);
+ } catch (ex) {
+ this.logService.error(`Backup: Could not move backup folder to new location: ${ex.toString()}`);
+ }
}
}
@@ -231,16 +236,7 @@ export class BackupMainService implements IBackupMainService {
staleBackupWorkspaces.push({ workspaceIdentifier: workspaceId, backupPath, target: workspaceOrFolder.target });
if (missingWorkspace) {
- const identifier = this.getRandomEmptyWindowId();
- this.pushBackupPathsSync(identifier, this.backups.emptyWorkspaces);
- const newEmptyWindowBackupPath = path.join(this.backupHome, identifier);
- try {
- fs.renameSync(backupPath, newEmptyWindowBackupPath);
- } catch (ex) {
- this.logService.error(`Backup: Could not rename backup folder for missing workspace: ${ex.toString()}`);
-
- this.removeBackupPathSync(identifier, this.backups.emptyWorkspaces);
- }
+ this.convertToEmptyWindowBackup(backupPath);
}
}
});
@@ -267,6 +263,27 @@ export class BackupMainService implements IBackupMainService {
});
}
+ private convertToEmptyWindowBackup(backupPath: string): boolean {
+
+ // New empty window backup
+ const identifier = this.getRandomEmptyWindowId();
+ this.pushBackupPathsSync(identifier, this.backups.emptyWorkspaces);
+
+ // Rename backupPath to new empty window backup path
+ const newEmptyWindowBackupPath = path.join(this.backupHome, identifier);
+ try {
+ fs.renameSync(backupPath, newEmptyWindowBackupPath);
+ } catch (ex) {
+ this.logService.error(`Backup: Could not rename backup folder: ${ex.toString()}`);
+
+ this.removeBackupPathSync(identifier, this.backups.emptyWorkspaces);
+
+ return false;
+ }
+
+ return true;
+ }
+
private hasBackupsSync(backupPath: string): boolean {
try {
const backupSchemas = extfs.readdirSync(backupPath);
diff --git a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts
index 063ebe91c6a..7d5a37d4edb 100644
--- a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts
+++ b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts
@@ -208,6 +208,33 @@ suite('BackupMainService', () => {
assert.ok(fs.existsSync(path.join(workspaceBackupPath, 'backup.txt')));
assert.ok(!fs.existsSync(backupPathToMigrate));
+ const emptyBackups = service.getEmptyWindowBackupPaths();
+ assert.equal(0, emptyBackups.length);
+
+ done();
+ });
+
+ test('service backup migration makes sure to preserve existing backups', done => {
+ const backupPathToMigrate = service.toBackupPath(fooFile.fsPath);
+ fs.mkdirSync(backupPathToMigrate);
+ fs.writeFileSync(path.join(backupPathToMigrate, 'backup.txt'), 'Some Data');
+ service.registerFolderBackupSync(backupPathToMigrate);
+
+ const backupPathToPreserve = service.toBackupPath(barFile.fsPath);
+ fs.mkdirSync(backupPathToPreserve);
+ fs.writeFileSync(path.join(backupPathToPreserve, 'backup.txt'), 'Some Data');
+ service.registerFolderBackupSync(backupPathToPreserve);
+
+ const workspaceBackupPath = service.registerWorkspaceBackupSync(toWorkspace(barFile.fsPath), backupPathToMigrate);
+
+ assert.ok(fs.existsSync(workspaceBackupPath));
+ assert.ok(fs.existsSync(path.join(workspaceBackupPath, 'backup.txt')));
+ assert.ok(!fs.existsSync(backupPathToMigrate));
+
+ const emptyBackups = service.getEmptyWindowBackupPaths();
+ assert.equal(1, emptyBackups.length);
+ assert.equal(1, fs.readdirSync(path.join(backupHome, emptyBackups[0])).length);
+
done();
});
diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts
index 36a1ed42492..01222188e6a 100644
--- a/src/vs/platform/environment/node/environmentService.ts
+++ b/src/vs/platform/environment/node/environmentService.ts
@@ -31,14 +31,7 @@ function getUniqueUserId(): string {
return crypto.createHash('sha256').update(username).digest('hex').substr(0, 6);
}
-// Read this before there's any chance it is overwritten
-// Related to https://github.com/Microsoft/vscode/issues/30624
-const xdgRuntimeDir = process.env['XDG_RUNTIME_DIR'];
-
function getNixIPCHandle(userDataPath: string, type: string): string {
- if (xdgRuntimeDir) {
- return path.join(xdgRuntimeDir, `${pkg.name}-${pkg.version}-${type}.sock`);
- }
return path.join(userDataPath, `${pkg.version}-${type}.sock`);
}
diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts
index 4e8c9dab644..0c78c12054c 100644
--- a/src/vs/platform/files/common/files.ts
+++ b/src/vs/platform/files/common/files.ts
@@ -94,7 +94,7 @@ export interface IFileService {
*
* The optional parameter content can be used as value to fill into the new file.
*/
- createFile(resource: URI, content?: string): TPromise;
+ createFile(resource: URI, content?: string, options?: ICreateFileOptions): TPromise;
/**
* Creates a new folder with the given path. The returned promise
@@ -523,6 +523,15 @@ export interface IResolveFileOptions {
resolveSingleChildDescendants?: boolean;
}
+export interface ICreateFileOptions {
+
+ /**
+ * Overwrite the file to create if it already exists on disk. Otherwise
+ * an error will be thrown (FILE_MODIFIED_SINCE).
+ */
+ overwrite?: boolean;
+}
+
export interface IImportResult {
stat: IFileStat;
isNew: boolean;
diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts
index ac6500b1860..92310b76a73 100644
--- a/src/vs/vscode.d.ts
+++ b/src/vs/vscode.d.ts
@@ -3869,6 +3869,21 @@ declare module 'vscode' {
options?: ShellExecutionOptions;
}
+ /**
+ * The scope of a task.
+ */
+ export enum TaskScope {
+ /**
+ * The task is a global task
+ */
+ Global = 1,
+
+ /**
+ * The task is a workspace task
+ */
+ Workspace = 2
+ }
+
/**
* A task to execute
*/
@@ -3877,8 +3892,10 @@ declare module 'vscode' {
/**
* Creates a new task.
*
+ * @deprecated: Use the new constructors that allow specifying a target for the task.
+ *
* @param definition The task definition as defined in the taskDefinitions extension point.
- * @param name The task's name. Is presented in the user interface.
+ * @param scope The task's name. Is presented in the user interface.
* @param source The task's source (e.g. 'gulp', 'npm', ...). Is presented in the user interface.
* @param execution The process or shell execution.
* @param problemMatchers the names of problem matchers to use, like '$tsc'
@@ -3887,11 +3904,31 @@ declare module 'vscode' {
*/
constructor(taskDefinition: TaskDefinition, name: string, source: string, execution?: ProcessExecution | ShellExecution, problemMatchers?: string | string[]);
+ /**
+ * Creates a new task.
+ *
+ * @param definition The task definition as defined in the taskDefinitions extension point.
+ * @param scope Specifies the task's scope. It is either a global or a workspace task or a task for a specific workspace folder.
+ * @param workspaceFolder The workspace folder this task is created for.
+ * @param name The task's name. Is presented in the user interface.
+ * @param source The task's source (e.g. 'gulp', 'npm', ...). Is presented in the user interface.
+ * @param execution The process or shell execution.
+ * @param problemMatchers the names of problem matchers to use, like '$tsc'
+ * or '$eslint'. Problem matchers can be contributed by an extension using
+ * the `problemMatchers` extension point.
+ */
+ constructor(taskDefinition: TaskDefinition, target: TaskScope.Global | TaskScope.Workspace | WorkspaceFolder, name: string, source: string, execution?: ProcessExecution | ShellExecution, problemMatchers?: string | string[]);
+
/**
* The task's definition.
*/
definition: TaskDefinition;
+ /**
+ * The task's scope.
+ */
+ scope?: TaskScope.Global | TaskScope.Workspace | WorkspaceFolder;
+
/**
* The task's name
*/
diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts
index b1829b9387f..4c9ed5a511f 100644
--- a/src/vs/workbench/api/node/extHost.api.impl.ts
+++ b/src/vs/workbench/api/node/extHost.api.impl.ts
@@ -97,7 +97,7 @@ export function createApiFactory(
const extHostQuickOpen = threadService.set(ExtHostContext.ExtHostQuickOpen, new ExtHostQuickOpen(threadService));
const extHostTerminalService = threadService.set(ExtHostContext.ExtHostTerminalService, new ExtHostTerminalService(threadService));
const extHostSCM = threadService.set(ExtHostContext.ExtHostSCM, new ExtHostSCM(threadService, extHostCommands));
- const extHostTask = threadService.set(ExtHostContext.ExtHostTask, new ExtHostTask(threadService));
+ const extHostTask = threadService.set(ExtHostContext.ExtHostTask, new ExtHostTask(threadService, extHostWorkspace));
const extHostCredentials = threadService.set(ExtHostContext.ExtHostCredentials, new ExtHostCredentials(threadService));
const extHostWindow = threadService.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(threadService));
threadService.set(ExtHostContext.ExtHostExtensionService, extensionService);
@@ -599,6 +599,7 @@ export function createApiFactory(
TaskGroup: extHostTypes.TaskGroup,
ProcessExecution: extHostTypes.ProcessExecution,
ShellExecution: extHostTypes.ShellExecution,
+ TaskScope: extHostTypes.TaskScope,
Task: extHostTypes.Task,
ConfigurationTarget: extHostTypes.ConfigurationTarget
};
diff --git a/src/vs/workbench/api/node/extHostFileSystemEventService.ts b/src/vs/workbench/api/node/extHostFileSystemEventService.ts
index 822298414c3..6038446ffc4 100644
--- a/src/vs/workbench/api/node/extHostFileSystemEventService.ts
+++ b/src/vs/workbench/api/node/extHostFileSystemEventService.ts
@@ -6,7 +6,7 @@
import Event, { Emitter } from 'vs/base/common/event';
import { Disposable } from './extHostTypes';
-import { match } from 'vs/base/common/glob';
+import { parse } from 'vs/base/common/glob';
import { Uri, FileSystemWatcher as _FileSystemWatcher } from 'vscode';
import { FileSystemEvents, ExtHostFileSystemEventServiceShape } from './extHost.protocol';
@@ -43,24 +43,26 @@ class FileSystemWatcher implements _FileSystemWatcher {
this._config += 0b100;
}
+ const parsedPattern = parse(globPattern);
+
let subscription = dispatcher(events => {
if (!ignoreCreateEvents) {
for (let created of events.created) {
- if (match(globPattern, created.fsPath)) {
+ if (parsedPattern(created.fsPath)) {
this._onDidCreate.fire(created);
}
}
}
if (!ignoreChangeEvents) {
for (let changed of events.changed) {
- if (match(globPattern, changed.fsPath)) {
+ if (parsedPattern(changed.fsPath)) {
this._onDidChange.fire(changed);
}
}
}
if (!ignoreDeleteEvents) {
for (let deleted of events.deleted) {
- if (match(globPattern, deleted.fsPath)) {
+ if (parsedPattern(deleted.fsPath)) {
this._onDidDelete.fire(deleted);
}
}
diff --git a/src/vs/workbench/api/node/extHostTask.ts b/src/vs/workbench/api/node/extHostTask.ts
index a174c82f1b7..b644dfb2424 100644
--- a/src/vs/workbench/api/node/extHostTask.ts
+++ b/src/vs/workbench/api/node/extHostTask.ts
@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
+import URI from 'vs/base/common/uri';
import * as nls from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import * as Objects from 'vs/base/common/objects';
@@ -15,6 +16,7 @@ import * as TaskSystem from 'vs/workbench/parts/tasks/common/tasks';
import { MainContext, MainThreadTaskShape, ExtHostTaskShape, IMainContext } from 'vs/workbench/api/node/extHost.protocol';
import * as types from 'vs/workbench/api/node/extHostTypes';
+import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
import * as vscode from 'vscode';
interface StringMap {
@@ -295,13 +297,13 @@ namespace ShellConfiguration {
namespace Tasks {
- export function from(tasks: vscode.Task[], extension: IExtensionDescription): TaskSystem.Task[] {
+ export function from(tasks: vscode.Task[], rootFolder: vscode.WorkspaceFolder, extension: IExtensionDescription): TaskSystem.Task[] {
if (tasks === void 0 || tasks === null) {
return [];
}
let result: TaskSystem.Task[] = [];
for (let task of tasks) {
- let converted = fromSingle(task, extension);
+ let converted = fromSingle(task, rootFolder, extension);
if (converted) {
result.push(converted);
}
@@ -309,7 +311,7 @@ namespace Tasks {
return result;
}
- function fromSingle(task: vscode.Task, extension: IExtensionDescription): TaskSystem.ContributedTask {
+ function fromSingle(task: vscode.Task, rootFolder: vscode.WorkspaceFolder, extension: IExtensionDescription): TaskSystem.ContributedTask {
if (typeof task.name !== 'string') {
return undefined;
}
@@ -326,10 +328,27 @@ namespace Tasks {
return undefined;
}
command.presentation = PresentationOptions.from(task.presentationOptions);
- let source = {
+
+ let taskScope: types.TaskScope.Global | types.TaskScope.Workspace | vscode.WorkspaceFolder | undefined = task.scope;
+ let workspaceFolder: vscode.WorkspaceFolder | undefined;
+ let scope: TaskSystem.TaskScope;
+ // For backwards compatibility
+ if (taskScope === void 0) {
+ scope = TaskSystem.TaskScope.Folder;
+ workspaceFolder = rootFolder;
+ } else if (taskScope === types.TaskScope.Global) {
+ scope = TaskSystem.TaskScope.Global;
+ } else if (taskScope === types.TaskScope.Workspace) {
+ scope = TaskSystem.TaskScope.Workspace;
+ } else {
+ workspaceFolder = taskScope;
+ }
+ let source: TaskSystem.ExtensionTaskSource = {
kind: TaskSystem.TaskSourceKind.Extension,
label: typeof task.source === 'string' ? task.source : extension.name,
- extension: extension.id
+ extension: extension.id,
+ scope: scope,
+ workspaceFolder: workspaceFolder ? { uri: workspaceFolder.uri as URI } : undefined
};
let label = nls.localize('task.label', '{0}: {1}', source.label, task.name);
let key = (task as types.Task).definitionKey;
@@ -398,11 +417,13 @@ interface HandlerData {
export class ExtHostTask implements ExtHostTaskShape {
private _proxy: MainThreadTaskShape;
+ private _extHostWorkspace: ExtHostWorkspace;
private _handleCounter: number;
private _handlers: Map;
- constructor(mainContext: IMainContext) {
+ constructor(mainContext: IMainContext, extHostWorkspace: ExtHostWorkspace) {
this._proxy = mainContext.get(MainContext.MainThreadTask);
+ this._extHostWorkspace = extHostWorkspace;
this._handleCounter = 0;
this._handlers = new Map();
};
@@ -426,8 +447,9 @@ export class ExtHostTask implements ExtHostTaskShape {
return TPromise.wrapError(new Error('no handler found'));
}
return asWinJsPromise(token => handler.provider.provideTasks(token)).then(value => {
+ let workspaceFolders = this._extHostWorkspace.getWorkspaceFolders();
return {
- tasks: Tasks.from(value, handler.extension),
+ tasks: Tasks.from(value, workspaceFolders && workspaceFolders.length > 0 ? workspaceFolders[0] : undefined, handler.extension),
extension: handler.extension
};
});
diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts
index e1b60f1ba51..a9b213648b8 100644
--- a/src/vs/workbench/api/node/extHostTypes.ts
+++ b/src/vs/workbench/api/node/extHostTypes.ts
@@ -1214,10 +1214,16 @@ export class ShellExecution implements vscode.ShellExecution {
}
}
+export enum TaskScope {
+ Global = 1,
+ Workspace = 2
+}
+
export class Task implements vscode.Task {
private _definition: vscode.TaskDefinition;
private _definitionKey: string;
+ private _scope: vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder;
private _name: string;
private _execution: ProcessExecution | ShellExecution;
private _problemMatchers: string[];
@@ -1227,11 +1233,29 @@ export class Task implements vscode.Task {
private _group: TaskGroup;
private _presentationOptions: vscode.TaskPresentationOptions;
- constructor(definition: vscode.TaskDefinition, name: string, source: string, execution?: ProcessExecution | ShellExecution, problemMatchers?: string | string[]) {
+ constructor(definition: vscode.TaskDefinition, name: string, source: string, execution?: ProcessExecution | ShellExecution, problemMatchers?: string | string[]);
+ constructor(definition: vscode.TaskDefinition, scope: vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder, name: string, source: string, execution?: ProcessExecution | ShellExecution, problemMatchers?: string | string[]);
+ constructor(definition: vscode.TaskDefinition, arg2: string | (vscode.TaskScope.Global | vscode.TaskScope.Workspace) | vscode.WorkspaceFolder, arg3: any, arg4?: any, arg5?: any, arg6?: any) {
this.definition = definition;
- this.name = name;
- this.source = source;
- this.execution = execution;
+ let problemMatchers: string | string[];
+ if (typeof arg2 === 'string') {
+ this.name = arg2;
+ this.source = arg3;
+ this.execution = arg4;
+ problemMatchers = arg5;
+ } else if (arg2 === TaskScope.Global || arg2 === TaskScope.Workspace) {
+ this.target = arg2;
+ this.name = arg3;
+ this.source = arg4;
+ this.execution = arg5;
+ problemMatchers = arg6;
+ } else {
+ this.target = arg2;
+ this.name = arg3;
+ this.source = arg4;
+ this.execution = arg5;
+ problemMatchers = arg6;
+ }
if (typeof problemMatchers === 'string') {
this._problemMatchers = [problemMatchers];
this._hasDefinedMatchers = true;
@@ -1266,6 +1290,14 @@ export class Task implements vscode.Task {
return this._definitionKey;
}
+ get scope(): vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder {
+ return this._scope;
+ }
+
+ set target(value: vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder) {
+ this._scope = value;
+ }
+
get name(): string {
return this._name;
}
diff --git a/src/vs/workbench/parts/debug/browser/debugContentProvider.ts b/src/vs/workbench/parts/debug/browser/debugContentProvider.ts
index ad96768d0d5..ccf2795916f 100644
--- a/src/vs/workbench/parts/debug/browser/debugContentProvider.ts
+++ b/src/vs/workbench/parts/debug/browser/debugContentProvider.ts
@@ -32,13 +32,21 @@ export class DebugContentProvider implements IWorkbenchContribution, ITextModelC
public provideTextContent(resource: uri): TPromise {
let process: IProcess;
+ let sourceRef: number;
+
if (resource.query) {
const keyvalues = resource.query.split('&');
for (let keyvalue of keyvalues) {
const pair = keyvalue.split('=');
- if (pair.length === 2 && pair[0] === 'session') {
- process = this.debugService.findProcessByUUID(decodeURIComponent(pair[1]));
- break;
+ if (pair.length === 2) {
+ switch (pair[0]) {
+ case 'session':
+ process = this.debugService.findProcessByUUID(decodeURIComponent(pair[1]));
+ break;
+ case 'sourceRef':
+ sourceRef = parseInt(pair[1]);
+ break;
+ }
}
}
}
@@ -55,18 +63,26 @@ export class DebugContentProvider implements IWorkbenchContribution, ITextModelC
let rawSource: DebugProtocol.Source;
if (source) {
rawSource = source.raw;
+ if (!sourceRef) {
+ sourceRef = source.reference;
+ }
} else {
- // Remove debug: scheme
- rawSource = { path: resource.with({ scheme: '', query: '' }).toString(true) };
+ // create a Source
+ rawSource = {
+ path: resource.with({ scheme: '', query: '' }).toString(true), // Remove debug: scheme
+ sourceReference: sourceRef
+ };
}
- return process.session.source({ sourceReference: source ? source.reference : undefined, source: rawSource }).then(response => {
+ return process.session.source({ sourceReference: sourceRef, source: rawSource }).then(response => {
+
const mime = response.body.mimeType || guessMimeTypes(resource.toString())[0];
const modePromise = this.modeService.getOrCreateMode(mime);
const model = this.modelService.createModel(response.body.content, modePromise, resource);
return model;
}, (err: DebugProtocol.ErrorResponse) => {
+
this.debugService.sourceIsNotAvailable(resource);
const modePromise = this.modeService.getOrCreateMode(MIME_TEXT);
const model = this.modelService.createModel(err.message, modePromise, resource);
diff --git a/src/vs/workbench/parts/debug/browser/debugViewlet.ts b/src/vs/workbench/parts/debug/browser/debugViewlet.ts
index a97e3d17f85..d0ff9fd24e1 100644
--- a/src/vs/workbench/parts/debug/browser/debugViewlet.ts
+++ b/src/vs/workbench/parts/debug/browser/debugViewlet.ts
@@ -56,10 +56,6 @@ export class DebugViewlet extends PersistentViewsViewlet {
public focus(): void {
super.focus();
- if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
- this.views[0].focusBody();
- }
-
if (this.startDebugActionItem) {
this.startDebugActionItem.focus();
}
diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts
index cedea9bb663..6761e846ab6 100644
--- a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts
+++ b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts
@@ -26,7 +26,7 @@ import { IExtensionsConfiguration, ConfigurationKey } from 'vs/workbench/parts/e
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
-import * as cp from 'child_process';
+import * as fs from 'fs';
import { distinct } from 'vs/base/common/arrays';
interface IExtensionsContent {
@@ -68,7 +68,6 @@ export class ExtensionTipsService implements IExtensionTipsService {
this._suggestTips();
this._suggestWorkspaceRecommendations();
- this._suggestBasedOnExecutables();
}
getWorkspaceRecommendations(): TPromise {
@@ -92,7 +91,7 @@ export class ExtensionTipsService implements IExtensionTipsService {
const fileBased = Object.keys(this._fileBasedRecommendations)
.filter(recommendation => allRecomendations.indexOf(recommendation) !== -1);
- const exeBased = distinct(this._exeBasedRecommendations);
+ const exeBased = distinct(this._suggestBasedOnExecutables());
this.telemetryService.publicLog('extensionRecommendations:unfiltered', { fileBased, exeBased });
@@ -319,15 +318,42 @@ export class ExtensionTipsService implements IExtensionTipsService {
});
}
- private _suggestBasedOnExecutables() {
- const cmd = process.platform === 'win32' ? 'where' : 'which';
+ private _suggestBasedOnExecutables(): string[] {
+ if (!process.env.PATH || this._exeBasedRecommendations.length > 0) {
+ return this._exeBasedRecommendations;
+ }
+
+ let envpaths = process.env.PATH.split(process.platform === 'win32' ? ';' : ':');
+ let foundExecutables: Set = new Set();
+
+ // Loop through recommended extensions
forEach(product.exeBasedExtensionTips, entry => {
- cp.exec(`${cmd} ${entry.value.replace(/,/g, ' ')}`, (err, stdout, stderr) => {
- if (stdout) {
- this._exeBasedRecommendations.push(entry.key);
+ let executables = entry.value.split(',');
+
+ // Loop through executables that would result in recommending current extension
+ for (let i = 0; i < executables.length; i++) {
+ if (!foundExecutables.has(executables[i])) {
+
+ // Loop through paths in PATH to find current executable
+ for (let pathEntry of envpaths) {
+ let fullPath = paths.join(pathEntry, executables[i]);
+ if (process.platform === 'win32') {
+ fullPath += '.exe';
+ }
+ if (fs.existsSync(fullPath)) {
+ foundExecutables.add(executables[i]);
+ break;
+ }
+ }
}
- });
+ if (foundExecutables.has(executables[i])) {
+ this._exeBasedRecommendations.push(entry.key);
+ break;
+ }
+ }
});
+
+ return this._exeBasedRecommendations;
}
private setIgnoreRecommendationsConfig(configVal: boolean) {
diff --git a/src/vs/workbench/parts/tasks/browser/quickOpen.ts b/src/vs/workbench/parts/tasks/browser/quickOpen.ts
index 2f64e812655..32a4d495b83 100644
--- a/src/vs/workbench/parts/tasks/browser/quickOpen.ts
+++ b/src/vs/workbench/parts/tasks/browser/quickOpen.ts
@@ -21,7 +21,7 @@ import { ActionBarContributor, ContributableActionProvider } from 'vs/workbench/
export class TaskEntry extends Model.QuickOpenEntry {
- constructor(protected taskService: ITaskService, protected quickOpenService: IQuickOpenService, protected _task: CustomTask | ContributedTask, highlights: Model.IHighlight[] = []) {
+ constructor(protected quickOpenService: IQuickOpenService, protected taskService: ITaskService, protected _task: CustomTask | ContributedTask, highlights: Model.IHighlight[] = []) {
super(highlights);
}
@@ -29,6 +29,17 @@ export class TaskEntry extends Model.QuickOpenEntry {
return this.task._label;
}
+ public getDescription(): string {
+ if (!this.taskService.hasMultipleFolders()) {
+ return null;
+ }
+ let workspaceFolder = Task.getWorkspaceFolder(this.task);
+ if (!workspaceFolder) {
+ return null;
+ }
+ return workspaceFolder.uri.fsPath;
+ }
+
public getAriaLabel(): string {
return nls.localize('entryAriaLabel', "{0}, tasks", this.getLabel());
}
diff --git a/src/vs/workbench/parts/tasks/browser/taskQuickOpen.ts b/src/vs/workbench/parts/tasks/browser/taskQuickOpen.ts
index 46d3c023200..b7325389b2b 100644
--- a/src/vs/workbench/parts/tasks/browser/taskQuickOpen.ts
+++ b/src/vs/workbench/parts/tasks/browser/taskQuickOpen.ts
@@ -14,12 +14,11 @@ import { CustomTask, ContributedTask } from 'vs/workbench/parts/tasks/common/tas
import { ITaskService } from 'vs/workbench/parts/tasks/common/taskService';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
-
import * as base from './quickOpen';
class TaskEntry extends base.TaskEntry {
- constructor(taskService: ITaskService, quickOpenService: IQuickOpenService, task: CustomTask | ContributedTask, highlights: Model.IHighlight[] = []) {
- super(taskService, quickOpenService, task, highlights);
+ constructor(quickOpenService: IQuickOpenService, taskService: ITaskService, task: CustomTask | ContributedTask, highlights: Model.IHighlight[] = []) {
+ super(quickOpenService, taskService, task, highlights);
}
public run(mode: QuickOpen.Mode, context: Model.IContext): boolean {
@@ -36,8 +35,8 @@ export class QuickOpenHandler extends base.QuickOpenHandler {
constructor(
@IQuickOpenService quickOpenService: IQuickOpenService,
- @ITaskService taskService: ITaskService,
- @IExtensionService extensionService: IExtensionService
+ @IExtensionService extensionService: IExtensionService,
+ @ITaskService taskService: ITaskService
) {
super(quickOpenService, taskService);
this.activationPromise = extensionService.activateByEvent('onCommand:workbench.action.tasks.runTask');
@@ -54,7 +53,7 @@ export class QuickOpenHandler extends base.QuickOpenHandler {
}
protected createEntry(task: CustomTask | ContributedTask, highlights: Model.IHighlight[]): base.TaskEntry {
- return new TaskEntry(this.taskService, this.quickOpenService, task, highlights);
+ return new TaskEntry(this.quickOpenService, this.taskService, task, highlights);
}
public getEmptyLabel(searchString: string): string {
diff --git a/src/vs/workbench/parts/tasks/common/taskService.ts b/src/vs/workbench/parts/tasks/common/taskService.ts
index 7598b170398..d56ad59ce58 100644
--- a/src/vs/workbench/parts/tasks/common/taskService.ts
+++ b/src/vs/workbench/parts/tasks/common/taskService.ts
@@ -59,7 +59,8 @@ export interface ITaskService extends IEventEmitter {
getTasksForGroup(group: string): TPromise;
getRecentlyUsedTasks(): LinkedMap;
- canCustomize(): boolean;
+ hasMultipleFolders();
+ canCustomize(task: ContributedTask | CustomTask): boolean;
customize(task: ContributedTask | CustomTask, properties?: {}, openConfig?: boolean): TPromise;
openConfig(task: CustomTask): TPromise;
diff --git a/src/vs/workbench/parts/tasks/common/tasks.ts b/src/vs/workbench/parts/tasks/common/tasks.ts
index fd9b28ce2d5..9c15a51436b 100644
--- a/src/vs/workbench/parts/tasks/common/tasks.ts
+++ b/src/vs/workbench/parts/tasks/common/tasks.ts
@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
+import URI from 'vs/base/common/uri';
import * as Types from 'vs/base/common/types';
import { IJSONSchemaMap } from 'vs/base/common/jsonSchema';
@@ -215,34 +216,47 @@ export namespace TaskGroup {
export type TaskGroup = 'clean' | 'build' | 'rebuild' | 'test';
+export enum TaskScope {
+ Global = 1,
+ Workspace = 2,
+ Folder = 3
+}
+
export namespace TaskSourceKind {
export const Workspace: 'workspace' = 'workspace';
export const Extension: 'extension' = 'extension';
export const Composite: 'composite' = 'composite';
}
+export interface WorkspaceFolder {
+ uri: URI;
+}
+
export interface TaskSourceConfigElement {
+ workspaceFolder: WorkspaceFolder;
file: string;
index: number;
element: any;
}
export interface WorkspaceTaskSource {
- kind: 'workspace';
- label: string;
- config: TaskSourceConfigElement;
- customizes?: TaskIdentifier;
+ readonly kind: 'workspace';
+ readonly label: string;
+ readonly config: TaskSourceConfigElement;
+ readonly customizes?: TaskIdentifier;
}
export interface ExtensionTaskSource {
- kind: 'extension';
- label: string;
- extension: string;
+ readonly kind: 'extension';
+ readonly label: string;
+ readonly extension: string;
+ readonly scope: TaskScope;
+ readonly workspaceFolder: WorkspaceFolder | undefined;
}
export interface CompositeTaskSource {
- kind: 'composite';
- label: string;
+ readonly kind: 'composite';
+ readonly label: string;
}
export type TaskSource = WorkspaceTaskSource | ExtensionTaskSource | CompositeTaskSource;
@@ -411,6 +425,16 @@ export namespace Task {
}
}
+ export function getWorkspaceFolder(task: Task): WorkspaceFolder | undefined {
+ if (CustomTask.is(task)) {
+ return task._source.config.workspaceFolder;
+ } else if (ContributedTask.is(task)) {
+ return task._source.workspaceFolder;
+ } else {
+ return undefined;
+ }
+ }
+
export function getTelemetryKind(task: Task): string {
if (ContributedTask.is(task)) {
return 'extension';
diff --git a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts
index 5d30f4a319b..c02e59693d7 100644
--- a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts
+++ b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts
@@ -76,7 +76,7 @@ import { Scope, IActionBarRegistry, Extensions as ActionBarExtensions } from 'vs
import { ITerminalService } from 'vs/workbench/parts/terminal/common/terminal';
import { ITaskSystem, ITaskResolver, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, TaskSystemEvents, TaskTerminateResponse } from 'vs/workbench/parts/tasks/common/taskSystem';
-import { Task, CustomTask, ConfiguringTask, ContributedTask, CompositeTask, TaskSet, TaskGroup, ExecutionEngine, JsonSchemaVersion, TaskSourceKind, TaskIdentifier } from 'vs/workbench/parts/tasks/common/tasks';
+import { Task, CustomTask, ConfiguringTask, ContributedTask, CompositeTask, TaskSet, TaskGroup, ExecutionEngine, JsonSchemaVersion, TaskSourceKind, TaskIdentifier, WorkspaceFolder } from 'vs/workbench/parts/tasks/common/tasks';
import { ITaskService, TaskServiceEvents, ITaskProvider, TaskEvent, RunOptions, CustomizationProperties } from 'vs/workbench/parts/tasks/common/taskService';
import { templates as taskTemplates } from 'vs/workbench/parts/tasks/common/taskTemplates';
@@ -618,7 +618,12 @@ interface WorkspaceTaskResult {
hasErrors: boolean;
}
-interface WorkspaceConfigurationResult {
+interface WorkspaceFolderTaskResult extends WorkspaceTaskResult {
+ workspaceFolder: WorkspaceFolder;
+}
+
+interface WorkspaceFolderConfigurationResult {
+ workspaceFolder: WorkspaceFolder;
config: TaskConfig.ExternalTaskRunnerConfiguration;
hasErrors: boolean;
}
@@ -657,9 +662,12 @@ class TaskService extends EventEmitter implements ITaskService {
private quickOpenService: IQuickOpenService;
private _configHasErrors: boolean;
+ private _schemaVersion: JsonSchemaVersion;
+ private _executionEngine: ExecutionEngine;
+ private _workspaceFolders: WorkspaceFolder[];
private _providers: Map;
- private _workspaceTasksPromise: TPromise;
+ private _workspaceTasksPromise: TPromise