From fc0c204b532fc17fcbe04b55df87210aa525263d Mon Sep 17 00:00:00 2001 From: Ahmed Tarek Date: Thu, 8 Feb 2018 23:33:29 +0200 Subject: [PATCH 001/283] Fixed SCM Badge Issue #42940 I have tested this out on my machine and it appears to work fine. --- src/vs/base/browser/ui/countBadge/countBadge.css | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/base/browser/ui/countBadge/countBadge.css b/src/vs/base/browser/ui/countBadge/countBadge.css index e6f36db1adc..1fdcfafa053 100644 --- a/src/vs/base/browser/ui/countBadge/countBadge.css +++ b/src/vs/base/browser/ui/countBadge/countBadge.css @@ -4,10 +4,12 @@ *--------------------------------------------------------------------------------------------*/ .monaco-count-badge { - padding: 0.2em 0.5em; + padding: 0.2em; border-radius: 1em; font-size: 85%; + min-width: 1em; + line-height: 1em; font-weight: normal; text-align: center; - display: inline; -} \ No newline at end of file + display: inline-block; +} From 67c4072ff7586a3677df7ca129de2550cc5519bc Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Thu, 29 Mar 2018 21:33:20 +0200 Subject: [PATCH 002/283] Respect ownership on code command installation Fixes #46754 --- .../parts/cli/electron-browser/cli.contribution.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/parts/cli/electron-browser/cli.contribution.ts b/src/vs/workbench/parts/cli/electron-browser/cli.contribution.ts index 5e754fb953d..fb2293d8434 100644 --- a/src/vs/workbench/parts/cli/electron-browser/cli.contribution.ts +++ b/src/vs/workbench/parts/cli/electron-browser/cli.contribution.ts @@ -78,8 +78,7 @@ class InstallAction extends Action { return createSymlink().then(null, err => { if (err.code === 'EACCES' || err.code === 'ENOENT') { - return this.createBinFolder() - .then(() => createSymlink()); + return this.createBinFolderAndSymlink(); } return TPromise.wrapError(err); @@ -101,14 +100,14 @@ class InstallAction extends Action { .then(null, ignore('ENOENT', false)); } - private createBinFolder(): TPromise { + private createBinFolderAndSymlink(): TPromise { return new TPromise((c, e) => { const buttons = [nls.localize('ok', "OK"), nls.localize('cancel2', "Cancel")]; this.dialogService.show(Severity.Info, nls.localize('warnEscalation', "Code will now prompt with 'osascript' for Administrator privileges to install the shell command."), buttons, { cancelId: 1 }).then(choice => { switch (choice) { case 0 /* OK */: - const command = 'osascript -e "do shell script \\"mkdir -p /usr/local/bin && chown \\" & (do shell script (\\"whoami\\")) & \\" /usr/local/bin\\" with administrator privileges"'; + const command = 'osascript -e "do shell script \\"mkdir -p /usr/local/bin && ln -sf \'' + getSource() + '\' \'' + this.target + '\'\\" with administrator privileges"'; nfcall(cp.exec, command, {}) .then(null, _ => TPromise.wrapError(new Error(nls.localize('cantCreateBinFolder', "Unable to create '/usr/local/bin'.")))) From a8803b454362391e26a4450a3d8bb4db5b9c6583 Mon Sep 17 00:00:00 2001 From: Pradeep Murugesan Date: Thu, 5 Apr 2018 15:10:59 +0200 Subject: [PATCH 003/283] #47041 fixed the scrolling of the editor and cursor after the revert from inline diff viewer --- extensions/git/src/commands.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index e37383c4932..0d990d07102 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -766,10 +766,14 @@ export class CommandCenter { return; } + const selectionsBeforeRevert = textEditor.selections; + const visibleRangesBeforeRevert = textEditor.visibleRanges; const result = applyLineChanges(originalDocument, modifiedDocument, changes); const edit = new WorkspaceEdit(); edit.replace(modifiedUri, new Range(new Position(0, 0), modifiedDocument.lineAt(modifiedDocument.lineCount - 1).range.end), result); workspace.applyEdit(edit); + textEditor.selections = selectionsBeforeRevert; + textEditor.revealRange(visibleRangesBeforeRevert[0]); await modifiedDocument.save(); } From d562ebbf25599e4806224fef8ec26804d1f9d74f Mon Sep 17 00:00:00 2001 From: Ryuichi Inagaki Date: Sun, 8 Apr 2018 23:26:39 +1000 Subject: [PATCH 004/283] ref #44776 Wrote a test for Repository#getCommit --- extensions/git/src/test/git.test.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/extensions/git/src/test/git.test.ts b/extensions/git/src/test/git.test.ts index 09661eebc9c..39c9f0a7e9f 100644 --- a/extensions/git/src/test/git.test.ts +++ b/extensions/git/src/test/git.test.ts @@ -6,8 +6,9 @@ 'use strict'; import 'mocha'; -import { GitStatusParser, parseGitmodules } from '../git'; +import { Git, GitStatusParser, Repository, parseGitmodules } from '../git'; import * as assert from 'assert'; +import * as sinon from 'sinon'; suite('git', () => { suite('GitStatusParser', () => { @@ -175,4 +176,22 @@ suite('git', () => { ]); }); }); + + suite('Repository', () => { + test('get commit', async () => { + const spawnOption = {}; + const gitOutput = `52c293a05038d865604c2284aa8698bd087915a1 +This is a commit message.`; + const git = sinon.createStubInstance(Git); + git.exec = sinon.stub() + .withArgs('REPOSITORY_ROOT', ['show', '-s', '--format=%H\n%B', 'REF'], spawnOption) + .returns(Promise.resolve({stdout: gitOutput})); + const repository = new Repository(git, 'REPOSITORY_ROOT'); + + assert.deepEqual(await repository.getCommit('REF'), { + hash: '52c293a05038d865604c2284aa8698bd087915a1', + message: 'This is a commit message.' + }); + }); + }); }); \ No newline at end of file From 3fdf0bcc56dadadb82b50b8ef9632fd20379079f Mon Sep 17 00:00:00 2001 From: Ryuichi Inagaki Date: Sun, 8 Apr 2018 23:41:43 +1000 Subject: [PATCH 005/283] ref #44776 Get previous commits as part of commit info --- extensions/git/src/git.ts | 7 ++++--- extensions/git/src/test/git.test.ts | 8 +++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index dc4829808b4..b2c111a46fe 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -481,6 +481,7 @@ export class Git { export interface Commit { hash: string; message: string; + previousHashes: string[]; } export class GitStatusParser { @@ -1287,14 +1288,14 @@ export class Repository { } async getCommit(ref: string): Promise { - const result = await this.run(['show', '-s', '--format=%H\n%B', ref]); - const match = /^([0-9a-f]{40})\n([^]*)$/m.exec(result.stdout.trim()); + const result = await this.run(['show', '-s', '--format=%H\n%P\n%B', ref]); + const match = /^([0-9a-f]{40})\n(.*)\n([^]*)$/m.exec(result.stdout.trim()); if (!match) { return Promise.reject('bad commit format'); } - return { hash: match[1], message: match[2] }; + return { hash: match[1], message: match[3], previousHashes: [match[2]] }; } async updateSubmodules(paths: string[]): Promise { diff --git a/extensions/git/src/test/git.test.ts b/extensions/git/src/test/git.test.ts index 39c9f0a7e9f..af4a24c349b 100644 --- a/extensions/git/src/test/git.test.ts +++ b/extensions/git/src/test/git.test.ts @@ -181,16 +181,18 @@ suite('git', () => { test('get commit', async () => { const spawnOption = {}; const gitOutput = `52c293a05038d865604c2284aa8698bd087915a1 +8e5a374372b8393906c7e380dbb09349c5385554 This is a commit message.`; const git = sinon.createStubInstance(Git); git.exec = sinon.stub() - .withArgs('REPOSITORY_ROOT', ['show', '-s', '--format=%H\n%B', 'REF'], spawnOption) + .withArgs('REPOSITORY_ROOT', ['show', '-s', '--format=%H\n%P\n%B', 'REF_SINGLE_PARENT'], spawnOption) .returns(Promise.resolve({stdout: gitOutput})); const repository = new Repository(git, 'REPOSITORY_ROOT'); - assert.deepEqual(await repository.getCommit('REF'), { + assert.deepEqual(await repository.getCommit('REF_SINGLE_PARENT'), { hash: '52c293a05038d865604c2284aa8698bd087915a1', - message: 'This is a commit message.' + message: 'This is a commit message.', + previousHashes: ['8e5a374372b8393906c7e380dbb09349c5385554'] }); }); }); From ac63779dcaac39c2ab175988e71afaed31622d08 Mon Sep 17 00:00:00 2001 From: Ryuichi Inagaki Date: Sun, 8 Apr 2018 23:52:25 +1000 Subject: [PATCH 006/283] ref #44776 Cater for the cases of multiple/no previous hashes --- extensions/git/src/git.ts | 3 ++- extensions/git/src/test/git.test.ts | 40 +++++++++++++++++++++++------ 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index b2c111a46fe..3b22f956538 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -1295,7 +1295,8 @@ export class Repository { return Promise.reject('bad commit format'); } - return { hash: match[1], message: match[3], previousHashes: [match[2]] }; + const previousHashes = match[2] ? match[2].split(' ') : []; + return { hash: match[1], message: match[3], previousHashes }; } async updateSubmodules(paths: string[]): Promise { diff --git a/extensions/git/src/test/git.test.ts b/extensions/git/src/test/git.test.ts index af4a24c349b..aff9b1f353f 100644 --- a/extensions/git/src/test/git.test.ts +++ b/extensions/git/src/test/git.test.ts @@ -178,22 +178,46 @@ suite('git', () => { }); suite('Repository', () => { - test('get commit', async () => { - const spawnOption = {}; - const gitOutput = `52c293a05038d865604c2284aa8698bd087915a1 + const spawnOption = {}; + const GIT_OUTPUT_SINGLE_PARENT = `52c293a05038d865604c2284aa8698bd087915a1 8e5a374372b8393906c7e380dbb09349c5385554 This is a commit message.`; - const git = sinon.createStubInstance(Git); - git.exec = sinon.stub() - .withArgs('REPOSITORY_ROOT', ['show', '-s', '--format=%H\n%P\n%B', 'REF_SINGLE_PARENT'], spawnOption) - .returns(Promise.resolve({stdout: gitOutput})); - const repository = new Repository(git, 'REPOSITORY_ROOT'); + const GIT_OUTPUT_MULTIPLE_PARENTS = `52c293a05038d865604c2284aa8698bd087915a1 +8e5a374372b8393906c7e380dbb09349c5385554 df27d8c75b129ab9b178b386077da2822101b217 +This is a commit message.`; + const GIT_OUTPUT_NO_PARENTS = `52c293a05038d865604c2284aa8698bd087915a1 +This is a commit message.`; + + const git = sinon.createStubInstance(Git); + git.exec = sinon.stub(); + git.exec + .withArgs('REPOSITORY_ROOT', ['show', '-s', '--format=%H\n%P\n%B', 'REF_SINGLE_PARENT'], spawnOption) + .returns(Promise.resolve({stdout: GIT_OUTPUT_SINGLE_PARENT})); + git.exec + .withArgs('REPOSITORY_ROOT', ['show', '-s', '--format=%H\n%P\n%B', 'REF_MULTIPLE_PARENTS'], spawnOption) + .returns(Promise.resolve({stdout: GIT_OUTPUT_MULTIPLE_PARENTS})); + git.exec + .withArgs('REPOSITORY_ROOT', ['show', '-s', '--format=%H\n%P\n%B', 'REF_NO_PARENTS'], spawnOption) + .returns(Promise.resolve({stdout: GIT_OUTPUT_NO_PARENTS})); + const repository = new Repository(git, 'REPOSITORY_ROOT'); + + test('get commit', async () => { assert.deepEqual(await repository.getCommit('REF_SINGLE_PARENT'), { hash: '52c293a05038d865604c2284aa8698bd087915a1', message: 'This is a commit message.', previousHashes: ['8e5a374372b8393906c7e380dbb09349c5385554'] }); }); + + test('multiple previous commits', async () => { + const commit = await repository.getCommit('REF_MULTIPLE_PARENTS'); + assert.deepEqual(commit.previousHashes, ['8e5a374372b8393906c7e380dbb09349c5385554', 'df27d8c75b129ab9b178b386077da2822101b217']); + }); + + test('no previous commits', async () => { + const commit = await repository.getCommit('REF_NO_PARENTS'); + assert.deepEqual(commit.previousHashes, []); + }); }); }); \ No newline at end of file From 963d337689650ea8e8adc848103a5ab79fe32944 Mon Sep 17 00:00:00 2001 From: Ryuichi Inagaki Date: Mon, 9 Apr 2018 00:06:28 +1000 Subject: [PATCH 007/283] ref #44776 Refactoring test --- extensions/git/src/test/git.test.ts | 30 +++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/extensions/git/src/test/git.test.ts b/extensions/git/src/test/git.test.ts index aff9b1f353f..ceab42b490c 100644 --- a/extensions/git/src/test/git.test.ts +++ b/extensions/git/src/test/git.test.ts @@ -190,16 +190,18 @@ This is a commit message.`; This is a commit message.`; const git = sinon.createStubInstance(Git); - git.exec = sinon.stub(); - git.exec - .withArgs('REPOSITORY_ROOT', ['show', '-s', '--format=%H\n%P\n%B', 'REF_SINGLE_PARENT'], spawnOption) - .returns(Promise.resolve({stdout: GIT_OUTPUT_SINGLE_PARENT})); - git.exec - .withArgs('REPOSITORY_ROOT', ['show', '-s', '--format=%H\n%P\n%B', 'REF_MULTIPLE_PARENTS'], spawnOption) - .returns(Promise.resolve({stdout: GIT_OUTPUT_MULTIPLE_PARENTS})); - git.exec - .withArgs('REPOSITORY_ROOT', ['show', '-s', '--format=%H\n%P\n%B', 'REF_NO_PARENTS'], spawnOption) - .returns(Promise.resolve({stdout: GIT_OUTPUT_NO_PARENTS})); + git.exec = stub([ + { + withArgs: ['REPOSITORY_ROOT', ['show', '-s', '--format=%H\n%P\n%B', 'REF_SINGLE_PARENT'], spawnOption], + returns: GIT_OUTPUT_SINGLE_PARENT + }, { + withArgs: ['REPOSITORY_ROOT', ['show', '-s', '--format=%H\n%P\n%B', 'REF_MULTIPLE_PARENTS'], spawnOption], + returns: GIT_OUTPUT_MULTIPLE_PARENTS + }, { + withArgs: ['REPOSITORY_ROOT', ['show', '-s', '--format=%H\n%P\n%B', 'REF_NO_PARENTS'], spawnOption], + returns: GIT_OUTPUT_NO_PARENTS + } + ]); const repository = new Repository(git, 'REPOSITORY_ROOT'); test('get commit', async () => { @@ -219,5 +221,13 @@ This is a commit message.`; const commit = await repository.getCommit('REF_NO_PARENTS'); assert.deepEqual(commit.previousHashes, []); }); + + function stub(argOutputPairs: {withArgs: any[], returns: string}[]): sinon.SinonStub { + const stub = sinon.stub(); + argOutputPairs.forEach(({withArgs, returns}) => { + stub.withArgs(...withArgs).returns(Promise.resolve({stdout: returns})); + }); + return stub; + } }); }); \ No newline at end of file From 91414f62cfa5e260099a778b7a5c1097a562082f Mon Sep 17 00:00:00 2001 From: Ryuichi Inagaki Date: Mon, 9 Apr 2018 22:02:39 +1000 Subject: [PATCH 008/283] ref #44776 Introduced `deleteRef` method on git.Repository --- extensions/git/src/git.ts | 5 ++ extensions/git/src/test/git.test.ts | 92 +++++++++++++++++------------ 2 files changed, 58 insertions(+), 39 deletions(-) diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 3b22f956538..96c70dae28e 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -895,6 +895,11 @@ export class Repository { await this.run(args); } + async deleteRef(ref: string): Promise { + const args = ['update-ref', '-d', ref]; + await this.run(args); + } + async merge(ref: string): Promise { const args = ['merge', ref]; diff --git a/extensions/git/src/test/git.test.ts b/extensions/git/src/test/git.test.ts index ceab42b490c..375e787b823 100644 --- a/extensions/git/src/test/git.test.ts +++ b/extensions/git/src/test/git.test.ts @@ -179,55 +179,69 @@ suite('git', () => { suite('Repository', () => { const spawnOption = {}; - const GIT_OUTPUT_SINGLE_PARENT = `52c293a05038d865604c2284aa8698bd087915a1 + + suite('getCommit', () => { + const GIT_OUTPUT_SINGLE_PARENT = `52c293a05038d865604c2284aa8698bd087915a1 8e5a374372b8393906c7e380dbb09349c5385554 This is a commit message.`; - const GIT_OUTPUT_MULTIPLE_PARENTS = `52c293a05038d865604c2284aa8698bd087915a1 + const GIT_OUTPUT_MULTIPLE_PARENTS = `52c293a05038d865604c2284aa8698bd087915a1 8e5a374372b8393906c7e380dbb09349c5385554 df27d8c75b129ab9b178b386077da2822101b217 This is a commit message.`; - const GIT_OUTPUT_NO_PARENTS = `52c293a05038d865604c2284aa8698bd087915a1 + const GIT_OUTPUT_NO_PARENTS = `52c293a05038d865604c2284aa8698bd087915a1 This is a commit message.`; - const git = sinon.createStubInstance(Git); - git.exec = stub([ - { - withArgs: ['REPOSITORY_ROOT', ['show', '-s', '--format=%H\n%P\n%B', 'REF_SINGLE_PARENT'], spawnOption], - returns: GIT_OUTPUT_SINGLE_PARENT - }, { - withArgs: ['REPOSITORY_ROOT', ['show', '-s', '--format=%H\n%P\n%B', 'REF_MULTIPLE_PARENTS'], spawnOption], - returns: GIT_OUTPUT_MULTIPLE_PARENTS - }, { - withArgs: ['REPOSITORY_ROOT', ['show', '-s', '--format=%H\n%P\n%B', 'REF_NO_PARENTS'], spawnOption], - returns: GIT_OUTPUT_NO_PARENTS + const git = sinon.createStubInstance(Git); + git.exec = stub([ + { + withArgs: ['REPOSITORY_ROOT', ['show', '-s', '--format=%H\n%P\n%B', 'REF_SINGLE_PARENT'], spawnOption], + returns: GIT_OUTPUT_SINGLE_PARENT + }, { + withArgs: ['REPOSITORY_ROOT', ['show', '-s', '--format=%H\n%P\n%B', 'REF_MULTIPLE_PARENTS'], spawnOption], + returns: GIT_OUTPUT_MULTIPLE_PARENTS + }, { + withArgs: ['REPOSITORY_ROOT', ['show', '-s', '--format=%H\n%P\n%B', 'REF_NO_PARENTS'], spawnOption], + returns: GIT_OUTPUT_NO_PARENTS + } + ]); + const repository = new Repository(git, 'REPOSITORY_ROOT'); + + test('get commit', async () => { + assert.deepEqual(await repository.getCommit('REF_SINGLE_PARENT'), { + hash: '52c293a05038d865604c2284aa8698bd087915a1', + message: 'This is a commit message.', + previousHashes: ['8e5a374372b8393906c7e380dbb09349c5385554'] + }); + }); + + test('multiple previous commits', async () => { + const commit = await repository.getCommit('REF_MULTIPLE_PARENTS'); + assert.deepEqual(commit.previousHashes, ['8e5a374372b8393906c7e380dbb09349c5385554', 'df27d8c75b129ab9b178b386077da2822101b217']); + }); + + test('no previous commits', async () => { + const commit = await repository.getCommit('REF_NO_PARENTS'); + assert.deepEqual(commit.previousHashes, []); + }); + + function stub(argOutputPairs: {withArgs: any[], returns: string}[]): sinon.SinonStub { + const stub = sinon.stub(); + argOutputPairs.forEach(({withArgs, returns}) => { + stub.withArgs(...withArgs).returns(Promise.resolve({stdout: returns})); + }); + return stub; } - ]); - const repository = new Repository(git, 'REPOSITORY_ROOT'); + }); - test('get commit', async () => { - assert.deepEqual(await repository.getCommit('REF_SINGLE_PARENT'), { - hash: '52c293a05038d865604c2284aa8698bd087915a1', - message: 'This is a commit message.', - previousHashes: ['8e5a374372b8393906c7e380dbb09349c5385554'] + suite('deleteRef', () => { + const git = sinon.createStubInstance(Git); + git.exec = sinon.spy(); + const repository = new Repository(git, 'REPOSITORY_ROOT'); + + test('delete ref', async () => { + await repository.deleteRef('REF_TO_BE_DELETED'); + assert.deepEqual(git.exec.args, [['REPOSITORY_ROOT', ['update-ref', '-d', 'REF_TO_BE_DELETED'], spawnOption]]); }); }); - - test('multiple previous commits', async () => { - const commit = await repository.getCommit('REF_MULTIPLE_PARENTS'); - assert.deepEqual(commit.previousHashes, ['8e5a374372b8393906c7e380dbb09349c5385554', 'df27d8c75b129ab9b178b386077da2822101b217']); - }); - - test('no previous commits', async () => { - const commit = await repository.getCommit('REF_NO_PARENTS'); - assert.deepEqual(commit.previousHashes, []); - }); - - function stub(argOutputPairs: {withArgs: any[], returns: string}[]): sinon.SinonStub { - const stub = sinon.stub(); - argOutputPairs.forEach(({withArgs, returns}) => { - stub.withArgs(...withArgs).returns(Promise.resolve({stdout: returns})); - }); - return stub; - } }); }); \ No newline at end of file From 81b3b44413a09ed2c0103d1db34084850cd1134f Mon Sep 17 00:00:00 2001 From: Ryuichi Inagaki Date: Mon, 9 Apr 2018 22:06:07 +1000 Subject: [PATCH 009/283] ref #44776 Added DeleteRef operation --- extensions/git/src/repository.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index cca9f563060..b70df19cc79 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -292,6 +292,7 @@ export enum Operation { GetCommitTemplate = 'GetCommitTemplate', DeleteBranch = 'DeleteBranch', RenameBranch = 'RenameBranch', + DeleteRef = 'DeleteRef', Merge = 'Merge', Ignore = 'Ignore', Tag = 'Tag', @@ -722,6 +723,10 @@ export class Repository implements Disposable { await this.run(Operation.Reset, () => this.repository.reset(treeish, hard)); } + async deleteRef(ref: string): Promise { + await this.run(Operation.DeleteRef, () => this.repository.deleteRef(ref)); + } + @throttle async fetch(): Promise { await this.run(Operation.Fetch, () => this.repository.fetch()); From 278842c987856558adfd505e601cf761c261d4f7 Mon Sep 17 00:00:00 2001 From: Ryuichi Inagaki Date: Mon, 9 Apr 2018 22:07:59 +1000 Subject: [PATCH 010/283] ref #44776 deleteRef instead of reset if commit has no parents --- extensions/git/src/commands.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index e37383c4932..ccb441dded2 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -1123,7 +1123,12 @@ export class CommandCenter { } const commit = await repository.getCommit('HEAD'); - await repository.reset('HEAD~'); + if (commit.previousHashes.length > 0) { + await repository.reset('HEAD~'); + } else { + await repository.deleteRef('HEAD'); + await this.unstageAll(repository); + } repository.inputBox.value = commit.message; } From 6b7a644082f4aa17265558fc11c23261fbc4b89e Mon Sep 17 00:00:00 2001 From: Ryuichi Inagaki Date: Wed, 11 Apr 2018 20:02:20 +1000 Subject: [PATCH 011/283] ref #44776 Only test the logic of parsing `git show` output * Instead of testing it through `getCommit` as we don't use stub in test --- extensions/git/src/git.ts | 19 ++++---- extensions/git/src/test/git.test.ts | 72 +++++++++++------------------ 2 files changed, 39 insertions(+), 52 deletions(-) diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 96c70dae28e..c13078b4053 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -612,6 +612,16 @@ export function parseGitmodules(raw: string): Submodule[] { return result; } +export function parseGitCommit(raw: string): Commit | null { + const match = /^([0-9a-f]{40})\n(.*)\n([^]*)$/m.exec(raw.trim()); + if (!match) { + return null; + } + + const previousHashes = match[2] ? match[2].split(' ') : []; + return { hash: match[1], message: match[3], previousHashes }; +} + export interface DiffOptions { cached?: boolean; } @@ -1294,14 +1304,7 @@ export class Repository { async getCommit(ref: string): Promise { const result = await this.run(['show', '-s', '--format=%H\n%P\n%B', ref]); - const match = /^([0-9a-f]{40})\n(.*)\n([^]*)$/m.exec(result.stdout.trim()); - - if (!match) { - return Promise.reject('bad commit format'); - } - - const previousHashes = match[2] ? match[2].split(' ') : []; - return { hash: match[1], message: match[3], previousHashes }; + return parseGitCommit(result.stdout) || Promise.reject('bad commit format'); } async updateSubmodules(paths: string[]): Promise { diff --git a/extensions/git/src/test/git.test.ts b/extensions/git/src/test/git.test.ts index 375e787b823..1e28ac305f1 100644 --- a/extensions/git/src/test/git.test.ts +++ b/extensions/git/src/test/git.test.ts @@ -6,7 +6,7 @@ 'use strict'; import 'mocha'; -import { Git, GitStatusParser, Repository, parseGitmodules } from '../git'; +import { Git, GitStatusParser, Repository, parseGitCommit, parseGitmodules } from '../git'; import * as assert from 'assert'; import * as sinon from 'sinon'; @@ -177,63 +177,47 @@ suite('git', () => { }); }); - suite('Repository', () => { - const spawnOption = {}; - - suite('getCommit', () => { + suite('parseGitCommit', () => { + test('single previous commit', () => { const GIT_OUTPUT_SINGLE_PARENT = `52c293a05038d865604c2284aa8698bd087915a1 8e5a374372b8393906c7e380dbb09349c5385554 This is a commit message.`; + + assert.deepEqual(parseGitCommit(GIT_OUTPUT_SINGLE_PARENT), { + hash: '52c293a05038d865604c2284aa8698bd087915a1', + message: 'This is a commit message.', + previousHashes: ['8e5a374372b8393906c7e380dbb09349c5385554'] + }); + }); + + test('multiple previous commits', () => { const GIT_OUTPUT_MULTIPLE_PARENTS = `52c293a05038d865604c2284aa8698bd087915a1 8e5a374372b8393906c7e380dbb09349c5385554 df27d8c75b129ab9b178b386077da2822101b217 This is a commit message.`; + + assert.deepEqual(parseGitCommit(GIT_OUTPUT_MULTIPLE_PARENTS), { + hash: '52c293a05038d865604c2284aa8698bd087915a1', + message: 'This is a commit message.', + previousHashes: ['8e5a374372b8393906c7e380dbb09349c5385554', 'df27d8c75b129ab9b178b386077da2822101b217'] + }); + }); + + test('no previous commits', async () => { const GIT_OUTPUT_NO_PARENTS = `52c293a05038d865604c2284aa8698bd087915a1 This is a commit message.`; - const git = sinon.createStubInstance(Git); - git.exec = stub([ - { - withArgs: ['REPOSITORY_ROOT', ['show', '-s', '--format=%H\n%P\n%B', 'REF_SINGLE_PARENT'], spawnOption], - returns: GIT_OUTPUT_SINGLE_PARENT - }, { - withArgs: ['REPOSITORY_ROOT', ['show', '-s', '--format=%H\n%P\n%B', 'REF_MULTIPLE_PARENTS'], spawnOption], - returns: GIT_OUTPUT_MULTIPLE_PARENTS - }, { - withArgs: ['REPOSITORY_ROOT', ['show', '-s', '--format=%H\n%P\n%B', 'REF_NO_PARENTS'], spawnOption], - returns: GIT_OUTPUT_NO_PARENTS - } - ]); - const repository = new Repository(git, 'REPOSITORY_ROOT'); - - test('get commit', async () => { - assert.deepEqual(await repository.getCommit('REF_SINGLE_PARENT'), { - hash: '52c293a05038d865604c2284aa8698bd087915a1', - message: 'This is a commit message.', - previousHashes: ['8e5a374372b8393906c7e380dbb09349c5385554'] - }); + assert.deepEqual(parseGitCommit(GIT_OUTPUT_NO_PARENTS), { + hash: '52c293a05038d865604c2284aa8698bd087915a1', + message: 'This is a commit message.', + previousHashes: [] }); - - test('multiple previous commits', async () => { - const commit = await repository.getCommit('REF_MULTIPLE_PARENTS'); - assert.deepEqual(commit.previousHashes, ['8e5a374372b8393906c7e380dbb09349c5385554', 'df27d8c75b129ab9b178b386077da2822101b217']); - }); - - test('no previous commits', async () => { - const commit = await repository.getCommit('REF_NO_PARENTS'); - assert.deepEqual(commit.previousHashes, []); - }); - - function stub(argOutputPairs: {withArgs: any[], returns: string}[]): sinon.SinonStub { - const stub = sinon.stub(); - argOutputPairs.forEach(({withArgs, returns}) => { - stub.withArgs(...withArgs).returns(Promise.resolve({stdout: returns})); - }); - return stub; - } }); + }); + suite('Repository', () => { suite('deleteRef', () => { + const spawnOption = {}; const git = sinon.createStubInstance(Git); git.exec = sinon.spy(); const repository = new Repository(git, 'REPOSITORY_ROOT'); From a02823851b27227a3252498e278d1eb1a4a2171a Mon Sep 17 00:00:00 2001 From: Ryuichi Inagaki Date: Wed, 11 Apr 2018 20:03:39 +1000 Subject: [PATCH 012/283] ref #44776 Removed the test for deleteRef method * as we don't use stub in test --- extensions/git/src/test/git.test.ts | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/extensions/git/src/test/git.test.ts b/extensions/git/src/test/git.test.ts index 1e28ac305f1..a1c5e196cfa 100644 --- a/extensions/git/src/test/git.test.ts +++ b/extensions/git/src/test/git.test.ts @@ -6,9 +6,8 @@ 'use strict'; import 'mocha'; -import { Git, GitStatusParser, Repository, parseGitCommit, parseGitmodules } from '../git'; +import { GitStatusParser, parseGitCommit, parseGitmodules } from '../git'; import * as assert from 'assert'; -import * as sinon from 'sinon'; suite('git', () => { suite('GitStatusParser', () => { @@ -214,18 +213,4 @@ This is a commit message.`; }); }); }); - - suite('Repository', () => { - suite('deleteRef', () => { - const spawnOption = {}; - const git = sinon.createStubInstance(Git); - git.exec = sinon.spy(); - const repository = new Repository(git, 'REPOSITORY_ROOT'); - - test('delete ref', async () => { - await repository.deleteRef('REF_TO_BE_DELETED'); - assert.deepEqual(git.exec.args, [['REPOSITORY_ROOT', ['update-ref', '-d', 'REF_TO_BE_DELETED'], spawnOption]]); - }); - }); - }); }); \ No newline at end of file From ee748ab5c8757de6b586ad62ff551eae78f36e5a Mon Sep 17 00:00:00 2001 From: Ivor Huang Date: Thu, 12 Apr 2018 20:48:31 -0400 Subject: [PATCH 013/283] implement push success notification for issue #39039 --- extensions/git/package.json | 5 +++++ extensions/git/package.nls.json | 3 ++- extensions/git/src/commands.ts | 4 ++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index c4a03271ec2..fb5170b607d 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -966,6 +966,11 @@ "default": true, "description": "%config.showInlineOpenFileAction%" }, + "git.showPushSuccessNotification": { + "type": "boolean", + "description": "%config.showPushSuccessNotification%", + "default": false + }, "git.inputValidation": { "type": "string", "enum": [ diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 7f2c43dd233..23ef70fb461 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -68,6 +68,7 @@ "config.decorations.enabled": "Controls if Git contributes colors and badges to the explorer and the open editors view.", "config.promptToSaveFilesBeforeCommit": "Controls whether Git should check for unsaved files before committing.", "config.showInlineOpenFileAction": "Controls whether to show an inline Open File action in the Git changes view.", + "config.showPushSuccessNotification": "Controls whether to show a notification when a push is successful.", "config.inputValidation": "Controls when to show commit message input validation.", "config.detectSubmodules": "Controls whether to automatically detect git submodules.", "config.detectSubmodulesLimit": "Controls the limit of git submodules detected.", @@ -77,4 +78,4 @@ "colors.ignored": "Color for ignored resources.", "colors.conflict": "Color for resources with conflicts.", "colors.submodule": "Color for submodule resources." -} \ No newline at end of file +} diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index e37383c4932..5725f10c191 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -1376,6 +1376,10 @@ export class CommandCenter { try { await repository.push(repository.HEAD); + const gitConfig = workspace.getConfiguration('git'); + if (gitConfig.get('showPushSuccessNotification')) { + window.showInformationMessage(localize('push success', "Successfully pushed.")); + } } catch (err) { if (err.gitErrorCode !== GitErrorCodes.NoUpstreamBranch) { throw err; From f1a362b250a20a3fcbeb8fa4189cf50171db9d1b Mon Sep 17 00:00:00 2001 From: Pradeep Murugesan Date: Fri, 13 Apr 2018 11:28:35 +0200 Subject: [PATCH 014/283] Commit always using sign off flag setting 47395 --- extensions/git/package.json | 6 ++++++ extensions/git/package.nls.json | 1 + extensions/git/src/commands.ts | 4 ++++ 3 files changed, 11 insertions(+) diff --git a/extensions/git/package.json b/extensions/git/package.json index c4a03271ec2..6c097dd2e60 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -987,6 +987,12 @@ "scope": "resource", "default": 10, "description": "%config.detectSubmodulesLimit%" + }, + "git.alwaysSignOff": { + "type": "boolean", + "scope": "resource", + "default": false, + "description": "%config.alwaysSignOff%" } } }, diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 7f2c43dd233..8714430f2ed 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -71,6 +71,7 @@ "config.inputValidation": "Controls when to show commit message input validation.", "config.detectSubmodules": "Controls whether to automatically detect git submodules.", "config.detectSubmodulesLimit": "Controls the limit of git submodules detected.", + "config.alwaysSignOff": "Controls the signoff flag for all commits", "colors.modified": "Color for modified resources.", "colors.deleted": "Color for deleted resources.", "colors.untracked": "Color for untracked resources.", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index e37383c4932..043eec1269e 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -1017,6 +1017,10 @@ export class CommandCenter { // enable signing of commits if configurated opts.signCommit = enableCommitSigning; + if (config.get('alwaysSignOff')) { + opts.signoff = true; + } + if ( // no changes (noStagedChanges && noUnstagedChanges) From 59c06466c20fbe720a514419c4b208c0f48ef406 Mon Sep 17 00:00:00 2001 From: Adit Bhatt Date: Sat, 14 Apr 2018 20:45:50 -0400 Subject: [PATCH 015/283] feat: add support for ignored repositories list --- extensions/git/package.json | 11 ++++++++++- extensions/git/package.nls.json | 1 + extensions/git/src/model.ts | 5 +++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index c4a03271ec2..53af18525e4 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -987,6 +987,15 @@ "scope": "resource", "default": 10, "description": "%config.detectSubmodulesLimit%" + }, + "git.ignoreRepositories": { + "type": [ + "array", + "null" + ], + "default": null, + "scope": "resource", + "description": "%config.ignoreRepositories%" } } }, @@ -1126,4 +1135,4 @@ "@types/which": "^1.0.28", "mocha": "^3.2.0" } -} +} \ No newline at end of file diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 7f2c43dd233..30e22a309aa 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -71,6 +71,7 @@ "config.inputValidation": "Controls when to show commit message input validation.", "config.detectSubmodules": "Controls whether to automatically detect git submodules.", "config.detectSubmodulesLimit": "Controls the limit of git submodules detected.", + "config.ignoreRepositories": "List of git repositories to ignore", "colors.modified": "Color for modified resources.", "colors.deleted": "Color for deleted resources.", "colors.untracked": "Color for untracked resources.", diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 65036c93a7b..36c4f7b4ca4 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -190,6 +190,7 @@ export class Model { const config = workspace.getConfiguration('git', Uri.file(path)); const enabled = config.get('enabled') === true; + const ignoredRepos = new Set(config.get>('ignoreRepositories')); if (!enabled) { return; @@ -207,6 +208,10 @@ export class Model { return; } + if (ignoredRepos.has(rawRoot)) { + return; + } + const repository = new Repository(this.git.open(repositoryRoot), this.globalState); this.open(repository); From 2c2122ee5d0aec100765d0a1d6a8e3322a89d312 Mon Sep 17 00:00:00 2001 From: "wbarajas@umich.edu" Date: Mon, 16 Apr 2018 22:35:18 -0400 Subject: [PATCH 016/283] feature: add support to manually add repositories --- extensions/git/package.json | 18 ++++++++++++++++++ extensions/git/package.nls.json | 1 + extensions/git/src/commands.ts | 31 +++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/extensions/git/package.json b/extensions/git/package.json index c4a03271ec2..a4db8561692 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -38,6 +38,15 @@ "dark": "resources/icons/dark/git.svg" } }, + { + "command": "git.loadRepo", + "title": "%command.loadRepo%", + "category": "Git", + "icon": { + "light": "resources/icons/light/git.svg", + "dark": "resources/icons/dark/git.svg" + } + }, { "command": "git.close", "title": "%command.close%", @@ -336,6 +345,10 @@ "command": "git.init", "when": "config.git.enabled" }, + { + "command": "git.loadRepo", + "when": "config.git.enabled" + }, { "command": "git.close", "when": "config.git.enabled && gitOpenRepositoryCount != 0" @@ -535,6 +548,11 @@ "group": "navigation", "when": "config.git.enabled && !scmProvider && gitOpenRepositoryCount == 0 && workspaceFolderCount != 0" }, + { + "command": "git.loadRepo", + "group": "navigation", + "when": "config.git.enabled && !scmProvider && gitOpenRepositoryCount == 0 && workspaceFolderCount != 0" + }, { "command": "git.commit", "group": "navigation", diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 7f2c43dd233..1d4f3e43d66 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -3,6 +3,7 @@ "description": "Git SCM Integration", "command.clone": "Clone", "command.init": "Initialize Repository", + "command.loadRepo": "Load Repo", "command.close": "Close Repository", "command.refresh": "Refresh", "command.openChange": "Open Changes", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index e37383c4932..94d28692d94 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -477,6 +477,37 @@ export class CommandCenter { await this.model.tryOpenRepository(path); } + @command('git.loadRepo', { repository: false }) + async loadRepo(path?: string): Promise { + + if (!path) { + path = await window.showInputBox({ + prompt: localize('repopath', "Repository Path"), + ignoreFocusOut: true + }); + } + + if (path) { + + try { + + if (this.model.getRepository(path)) { + await this.model.tryOpenRepository(path); + } + + else { + window.showInformationMessage(localize('notfound', "Could not find a repository at this path")); + } + + return; + } + + catch (err) { + //If something went wrong, tryOpenRepository should have already given error + } + } + } + @command('git.close', { repository: true }) async close(repository: Repository): Promise { this.model.close(repository); From 375411e6311adb7b8eb78980ae85615a74856263 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 7 May 2018 11:37:17 -0700 Subject: [PATCH 017/283] Fixes #48714: Added conversion from local case of the file to git case. This does not fix the view in the Explorer, but this solution should be easy to apply to that issue too. --- extensions/git/src/git.ts | 21 +++++++++++++++++++++ extensions/git/src/repository.ts | 9 +++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index a04aba93531..93225dd33b5 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -738,6 +738,27 @@ export class Repository { return { mode, object, size: parseInt(size) }; } + async lstreeOutput(treeish: string, path: string): Promise { + if (!treeish) { // index + const { stdout } = await this.run(['ls-files', '--stage', '--', path]); + return stdout; + } + + const { stdout } = await this.run(['ls-tree', '-l', treeish, '--', path]); + return stdout; + } + + async relativePathToGitRelativePath(treeish: string, path: string): Promise { + let gitPath: string = path; + const pathPrefix = path.substring(0, path.lastIndexOf('/')); + const lstOutput = await this.lstreeOutput(treeish, pathPrefix + '/'); + const findResult = lstOutput.toUpperCase().indexOf(path.toUpperCase()); + if (findResult) { + gitPath = lstOutput.substr(findResult, path.length); + } + return gitPath; + } + async detectObjectType(object: string): Promise<{ mimetype: string, encoding?: string }> { const child = await this.stream(['show', object]); const buffer = await readBytes(child.stdout, 4100); diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index cca9f563060..6acb75432db 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -811,12 +811,17 @@ export class Repository implements Disposable { } async show(ref: string, filePath: string): Promise { - return this.run(Operation.Show, () => { - const relativePath = path.relative(this.repository.root, filePath).replace(/\\/g, '/'); + return this.run(Operation.Show, async () => { + let relativePath = path.relative(this.repository.root, filePath).replace(/\\/g, '/'); const configFiles = workspace.getConfiguration('files', Uri.file(filePath)); const defaultEncoding = configFiles.get('encoding'); const autoGuessEncoding = configFiles.get('autoGuessEncoding'); + if (ref === '') { + ref = 'HEAD'; + } + + relativePath = await this.repository.relativePathToGitRelativePath(ref, relativePath); return this.repository.bufferString(`${ref}:${relativePath}`, defaultEncoding, autoGuessEncoding); }); } From 325a919569af204f5b1d6ebe35e534a5aa14c2cd Mon Sep 17 00:00:00 2001 From: Zhuowei Zhang Date: Mon, 21 May 2018 21:47:45 -0400 Subject: [PATCH 018/283] git: Detect conflict markets in BOTH_ADDED files when staging The existing code only checked for conflict markers for files with BOTH_MODIFIED status; files with BOTH_ADDED status were always detected as conflicting even after conflicts are resolved. This fixes #44106. --- extensions/git/src/commands.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 1ca402bf14f..befe0dd07d1 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -624,12 +624,13 @@ export class CommandCenter { const selection = resourceStates.filter(s => s instanceof Resource) as Resource[]; const merge = selection.filter(s => s.resourceGroupType === ResourceGroupType.Merge); - const bothModified = merge.filter(s => s.type === Status.BOTH_MODIFIED); + const isBothAddedOrModified = (s: Resource) => s.type === Status.BOTH_MODIFIED || s.type === Status.BOTH_ADDED; + const bothModified = merge.filter(isBothAddedOrModified); const promises = bothModified.map(s => grep(s.resourceUri.fsPath, /^<{7}|^={7}|^>{7}/)); const unresolvedBothModified = await Promise.all(promises); const resolvedConflicts = bothModified.filter((s, i) => !unresolvedBothModified[i]); const unresolvedConflicts = [ - ...merge.filter(s => s.type !== Status.BOTH_MODIFIED), + ...merge.filter(s => !isBothAddedOrModified(s)), ...bothModified.filter((s, i) => unresolvedBothModified[i]) ]; From 67ad033d53289a13253b62173f63b2a69229556d Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 13 Jun 2018 16:00:03 +0200 Subject: [PATCH 019/283] hello breadcrumbs widget --- .../electron-browser/breadcrumbsWidget.css | 32 ++++ .../electron-browser/breadcrumbsWidget.ts | 171 ++++++++++++++++++ 2 files changed, 203 insertions(+) create mode 100644 src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget.css create mode 100644 src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget.ts diff --git a/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget.css b/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget.css new file mode 100644 index 00000000000..2901388a828 --- /dev/null +++ b/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget.css @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-breadcrumbs { + user-select: none; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: flex-start; + --item-hover-background: green; + --item-hover-color: inhert; +} + +.monaco-breadcrumbs .monaco-breadcrumb-item { + display: inline-block; + padding: 0 5px 0 5px; + flex: 0 1 auto; + white-space: nowrap; + cursor: pointer; +} + +.monaco-breadcrumbs .monaco-breadcrumb-item:hover { + color: var(--item-hover-color); + background-color: var(--item-hover-background); +} + +.monaco-breadcrumbs .monaco-breadcrumb-item-more::after { + content: '〉'; + padding-left: 5px; +} diff --git a/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget.ts b/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget.ts new file mode 100644 index 00000000000..3c23a030b22 --- /dev/null +++ b/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget.ts @@ -0,0 +1,171 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import 'vs/css!./breadcrumbsWidget'; +import * as dom from 'vs/base/browser/dom'; +import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; +import { ScrollbarVisibility } from 'vs/base/common/scrollable'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IMouseEvent } from 'vs/base/browser/mouseEvent'; +import { Event, Emitter } from 'vs/base/common/event'; + +export class BreadcrumbsItem { + + constructor( + readonly node: HTMLElement, + readonly more: boolean + ) { + + } + + dispose(): void { + // + } +} + +export class SimpleBreadcrumbsItem extends BreadcrumbsItem { + + constructor(text: string, title: string = text, more: boolean = true) { + super(document.createElement('span'), more); + this.node.innerText = text; + this.node.title = title; + } +} + +export class RenderedBreadcrumbsItem extends BreadcrumbsItem { + + + private _disposables: IDisposable[] = []; + + constructor(render: (element: E, container: HTMLSpanElement, bucket: IDisposable[]) => any, element: E, more: boolean) { + super(document.createElement('span'), more); + render(element, this.node, this._disposables); + } + + dispose() { + dispose(this._disposables); + super.dispose(); + } +} + +export class BreadcrumbsWidget { + + private readonly _disposables = new Array(); + private readonly _domNode: HTMLSpanElement; + private readonly _scrollable: DomScrollableElement; + private _cachedWidth: number; + + private readonly _onDidSelectItem = new Emitter(); + readonly onDidSelectItem: Event = this._onDidSelectItem.event; + + private readonly _items = new Array(); + private readonly _nodes = new Array(); + private readonly _freeNodes = new Array(); + private _activeItem: number; + + constructor( + container: HTMLElement + ) { + this._domNode = document.createElement('span'); + this._domNode.className = 'monaco-breadcrumbs'; + this._scrollable = new DomScrollableElement(this._domNode, { + vertical: ScrollbarVisibility.Hidden, + horizontal: ScrollbarVisibility.Auto, + horizontalScrollbarSize: 3, + useShadows: false + }); + this._disposables.push(this._scrollable); + this._disposables.push(dom.addStandardDisposableListener(this._domNode, 'click', e => this._onClick(e))); + container.appendChild(this._scrollable.getDomNode()); + } + + dispose(): void { + dispose(this._disposables); + this._domNode.remove(); + this._disposables.length = 0; + this._nodes.length = 0; + this._freeNodes.length = 0; + } + + layout(width: number = this._cachedWidth): void { + if (typeof width === 'number') { + this._cachedWidth = width; + this._domNode.style.width = `${this._cachedWidth}px`; + this._scrollable.scanDomNode(); + } + } + + focus(): void { + this._domNode.focus(); + } + + select(nth: number): void { + if (typeof this._activeItem === 'number') { + dom.removeClass(this._nodes[this._activeItem], 'active'); + } + if (nth >= this._nodes.length) { + this._activeItem = nth; + let node = this._nodes[this._activeItem]; + dom.addClass(node, 'active'); + this._scrollable.setScrollPosition({ scrollLeft: node.offsetLeft }); + } + } + + append(item: BreadcrumbsItem): void { + this._items.push(item); + this._render(this._items.length - 1); + } + + replace(existing: BreadcrumbsItem, newItems: BreadcrumbsItem[]): void { + let start = !existing ? 0 : this._items.indexOf(existing); + let removed = this._items.splice(start, this._items.length - start, ...newItems); + this._render(start); + dispose(removed); + } + + private _render(start: number): void { + for (; start < this._items.length && start < this._nodes.length; start++) { + let item = this._items[start]; + let node = this._nodes[start]; + this._renderItem(item, node); + } + // case a: more nodes -> remove them + for (; start < this._nodes.length; start++) { + this._nodes[start].remove(); + this._freeNodes.push(this._nodes[start]); + } + this._nodes.length = this._items.length; + + // case b: more items -> render them + for (; start < this._items.length; start++) { + let item = this._items[start]; + let node = this._freeNodes.length > 0 ? this._freeNodes.pop() : document.createElement('span'); + this._renderItem(item, node); + this._domNode.appendChild(node); + this._nodes[start] = node; + } + this.layout(); + this.select(this._nodes.length - 1); + } + + private _renderItem(item: BreadcrumbsItem, container: HTMLSpanElement): void { + dom.clearNode(container); + dom.append(container, item.node); + dom.addClass(container, 'monaco-breadcrumb-item'); + dom.toggleClass(container, 'monaco-breadcrumb-item-more', item.more); + } + + private _onClick(event: IMouseEvent): void { + for (let el = event.target; el; el = el.parentElement) { + let idx = this._nodes.indexOf(el as any); + if (idx >= 0) { + this._onDidSelectItem.fire(this._items[idx]); + break; + } + } + } +} From 83ca2647a0ff50d8abedf1f6789360b9e88b7d98 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 13 Jun 2018 16:00:25 +0200 Subject: [PATCH 020/283] breadcrumbs fake integration --- .../breadcrumbs.contribution.ts | 14 +++ .../breadcrumbsStatusbarItem.ts | 89 +++++++++++++++++++ src/vs/workbench/workbench.main.ts | 2 + 3 files changed, 105 insertions(+) create mode 100644 src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbs.contribution.ts create mode 100644 src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsStatusbarItem.ts diff --git a/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbs.contribution.ts b/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbs.contribution.ts new file mode 100644 index 00000000000..a6cd82ad1ba --- /dev/null +++ b/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbs.contribution.ts @@ -0,0 +1,14 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions, IStatusbarRegistry, StatusbarAlignment, StatusbarItemDescriptor } from 'vs/workbench/browser/parts/statusbar/statusbar'; +import { BreadcrumbsStatusbarItem } from 'vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsStatusbarItem'; + +Registry.as(Extensions.Statusbar).registerStatusbarItem( + new StatusbarItemDescriptor(BreadcrumbsStatusbarItem, StatusbarAlignment.LEFT) +); diff --git a/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsStatusbarItem.ts b/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsStatusbarItem.ts new file mode 100644 index 00000000000..6b8763df1f4 --- /dev/null +++ b/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsStatusbarItem.ts @@ -0,0 +1,89 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { BreadcrumbsWidget, RenderedBreadcrumbsItem, BreadcrumbsItem } from 'vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { posix } from 'path'; +import URI from 'vs/base/common/uri'; +import { getPathLabel, getBaseLabel } from 'vs/base/common/labels'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; + +export class BreadcrumbsStatusbarItem implements IStatusbarItem { + + private _widget: BreadcrumbsWidget; + private _disposables: IDisposable[] = []; + + constructor( + @IEditorService private readonly _editorService: IEditorService, + @IContextViewService private readonly _contextViewService: IContextViewService + ) { + } + + dispose(): void { + dispose(this._disposables); + } + + render(element: HTMLElement): IDisposable { + this._widget = new BreadcrumbsWidget(element); + this._widget.layout(300); + this._disposables.push(this._widget); + this._disposables.push(this._widget.onDidSelectItem(this._onDidSelectItem, this)); + this._disposables.push(this._editorService.onDidActiveEditorChange(this._onDidChangeActiveEditor, this)); + this._onDidChangeActiveEditor(); + return this; + } + + private _onDidChangeActiveEditor(): void { + let { activeEditor } = this._editorService; + if (!activeEditor) { + this._widget.replace(undefined, []); + return; + } + let resource = activeEditor.getResource(); + if (!resource) { + this._widget.replace(undefined, []); + return; + } + + interface Element { + name: string; + uri: URI; + } + + function render(element: Element, target: HTMLElement) { + target.innerText = getBaseLabel(element.uri); + target.title = getPathLabel(element.uri); + } + + let items: RenderedBreadcrumbsItem[] = []; + let path = resource.path; + while (path !== '/') { + let name = posix.basename(path); + let uri = resource.with({ path }); + path = posix.dirname(path); + items.unshift(new RenderedBreadcrumbsItem(render, { name, uri }, items.length !== 0)); + } + + this._widget.replace(undefined, items); + } + + private _onDidSelectItem(item: BreadcrumbsItem): void { + console.log(item, this._contextViewService._serviceBrand); + + // this._contextViewService.showContextView({ + // getAnchor() { + // return item.node; + // }, + // render(container) { + // container.innerText = JSON.stringify(item, undefined, 4); + // return null; + // } + // }); + } +} diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts index 4bb1eda080e..34cd6c0f235 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.main.ts @@ -94,6 +94,8 @@ import 'vs/workbench/electron-browser/workbench'; import 'vs/workbench/parts/relauncher/electron-browser/relauncher.contribution'; +import 'vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbs.contribution'; + import 'vs/workbench/parts/tasks/electron-browser/task.contribution'; import 'vs/workbench/parts/emmet/browser/emmet.browser.contribution'; From b7495d7c689f2808f3153a3f9bda8051af231358 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 14 Jun 2018 10:02:07 +0200 Subject: [PATCH 021/283] adopt getPathLabel changes --- .../breadcrumbs/electron-browser/breadcrumbsStatusbarItem.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsStatusbarItem.ts b/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsStatusbarItem.ts index 6b8763df1f4..6d4d9929320 100644 --- a/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsStatusbarItem.ts +++ b/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsStatusbarItem.ts @@ -58,7 +58,7 @@ export class BreadcrumbsStatusbarItem implements IStatusbarItem { function render(element: Element, target: HTMLElement) { target.innerText = getBaseLabel(element.uri); - target.title = getPathLabel(element.uri); + target.title = getPathLabel(element.uri, undefined, undefined); } let items: RenderedBreadcrumbsItem[] = []; From c2c9e8dddcd564903ea0c2ba2e0acfffb82a1658 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 14 Jun 2018 11:13:12 +0200 Subject: [PATCH 022/283] drop status bar playground, towards editor contrib --- .../breadcrumbs.contribution.ts | 26 +++- .../breadcrumbsStatusbarItem.ts | 89 ------------ .../electron-browser/breadcrumbsWidget.css | 9 +- .../electron-browser/breadcrumbsWidget.ts | 41 +++--- .../electron-browser/editorBreadcrumbs.ts | 128 ++++++++++++++++++ 5 files changed, 177 insertions(+), 116 deletions(-) delete mode 100644 src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsStatusbarItem.ts create mode 100644 src/vs/workbench/parts/breadcrumbs/electron-browser/editorBreadcrumbs.ts diff --git a/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbs.contribution.ts b/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbs.contribution.ts index a6cd82ad1ba..b0dc0ff39bb 100644 --- a/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbs.contribution.ts +++ b/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbs.contribution.ts @@ -5,10 +5,24 @@ 'use strict'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions, IStatusbarRegistry, StatusbarAlignment, StatusbarItemDescriptor } from 'vs/workbench/browser/parts/statusbar/statusbar'; -import { BreadcrumbsStatusbarItem } from 'vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsStatusbarItem'; +import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { registerEditorContribution, EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions'; +import { EditorBreadcrumbs } from 'vs/workbench/parts/breadcrumbs/electron-browser/editorBreadcrumbs'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -Registry.as(Extensions.Statusbar).registerStatusbarItem( - new StatusbarItemDescriptor(BreadcrumbsStatusbarItem, StatusbarAlignment.LEFT) -); +registerEditorContribution(EditorBreadcrumbs); + + +const BreadcrumbCommandCtor = EditorCommand.bindToContribution(EditorBreadcrumbs.get); + +registerEditorCommand(new BreadcrumbCommandCtor({ + id: 'breadcrumbs.focus', + precondition: undefined, + handler: x => x.focus(), + kbOpts: { + weight: KeybindingsRegistry.WEIGHT.editorContrib(50), + kbExpr: EditorContextKeys.focus, + primary: KeyMod.CtrlCmd | KeyCode.US_DOT + } +})); diff --git a/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsStatusbarItem.ts b/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsStatusbarItem.ts deleted file mode 100644 index 6d4d9929320..00000000000 --- a/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsStatusbarItem.ts +++ /dev/null @@ -1,89 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import { BreadcrumbsWidget, RenderedBreadcrumbsItem, BreadcrumbsItem } from 'vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { posix } from 'path'; -import URI from 'vs/base/common/uri'; -import { getPathLabel, getBaseLabel } from 'vs/base/common/labels'; -import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; - -export class BreadcrumbsStatusbarItem implements IStatusbarItem { - - private _widget: BreadcrumbsWidget; - private _disposables: IDisposable[] = []; - - constructor( - @IEditorService private readonly _editorService: IEditorService, - @IContextViewService private readonly _contextViewService: IContextViewService - ) { - } - - dispose(): void { - dispose(this._disposables); - } - - render(element: HTMLElement): IDisposable { - this._widget = new BreadcrumbsWidget(element); - this._widget.layout(300); - this._disposables.push(this._widget); - this._disposables.push(this._widget.onDidSelectItem(this._onDidSelectItem, this)); - this._disposables.push(this._editorService.onDidActiveEditorChange(this._onDidChangeActiveEditor, this)); - this._onDidChangeActiveEditor(); - return this; - } - - private _onDidChangeActiveEditor(): void { - let { activeEditor } = this._editorService; - if (!activeEditor) { - this._widget.replace(undefined, []); - return; - } - let resource = activeEditor.getResource(); - if (!resource) { - this._widget.replace(undefined, []); - return; - } - - interface Element { - name: string; - uri: URI; - } - - function render(element: Element, target: HTMLElement) { - target.innerText = getBaseLabel(element.uri); - target.title = getPathLabel(element.uri, undefined, undefined); - } - - let items: RenderedBreadcrumbsItem[] = []; - let path = resource.path; - while (path !== '/') { - let name = posix.basename(path); - let uri = resource.with({ path }); - path = posix.dirname(path); - items.unshift(new RenderedBreadcrumbsItem(render, { name, uri }, items.length !== 0)); - } - - this._widget.replace(undefined, items); - } - - private _onDidSelectItem(item: BreadcrumbsItem): void { - console.log(item, this._contextViewService._serviceBrand); - - // this._contextViewService.showContextView({ - // getAnchor() { - // return item.node; - // }, - // render(container) { - // container.innerText = JSON.stringify(item, undefined, 4); - // return null; - // } - // }); - } -} diff --git a/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget.css b/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget.css index 2901388a828..7a30f6791f4 100644 --- a/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget.css +++ b/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget.css @@ -21,12 +21,15 @@ cursor: pointer; } +.monaco-breadcrumbs:focus .monaco-breadcrumb-item.active { + color: pink; +} + .monaco-breadcrumbs .monaco-breadcrumb-item:hover { color: var(--item-hover-color); background-color: var(--item-hover-background); } -.monaco-breadcrumbs .monaco-breadcrumb-item-more::after { - content: '〉'; - padding-left: 5px; +.monaco-breadcrumbs .monaco-breadcrumb-item-more { + border-right: 1px solid; } diff --git a/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget.ts b/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget.ts index 3c23a030b22..e8ac7716b24 100644 --- a/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget.ts +++ b/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget.ts @@ -30,7 +30,7 @@ export class BreadcrumbsItem { export class SimpleBreadcrumbsItem extends BreadcrumbsItem { constructor(text: string, title: string = text, more: boolean = true) { - super(document.createElement('span'), more); + super(document.createElement('div'), more); this.node.innerText = text; this.node.title = title; } @@ -41,9 +41,9 @@ export class RenderedBreadcrumbsItem extends BreadcrumbsItem { private _disposables: IDisposable[] = []; - constructor(render: (element: E, container: HTMLSpanElement, bucket: IDisposable[]) => any, element: E, more: boolean) { - super(document.createElement('span'), more); - render(element, this.node, this._disposables); + constructor(render: (element: E, container: HTMLDivElement, bucket: IDisposable[]) => any, element: E, more: boolean) { + super(document.createElement('div'), more); + render(element, this.node as HTMLDivElement, this._disposables); } dispose() { @@ -55,7 +55,7 @@ export class RenderedBreadcrumbsItem extends BreadcrumbsItem { export class BreadcrumbsWidget { private readonly _disposables = new Array(); - private readonly _domNode: HTMLSpanElement; + private readonly _domNode: HTMLDivElement; private readonly _scrollable: DomScrollableElement; private _cachedWidth: number; @@ -63,15 +63,16 @@ export class BreadcrumbsWidget { readonly onDidSelectItem: Event = this._onDidSelectItem.event; private readonly _items = new Array(); - private readonly _nodes = new Array(); - private readonly _freeNodes = new Array(); - private _activeItem: number; + private readonly _nodes = new Array(); + private readonly _freeNodes = new Array(); + private _activeItem: number = -1; constructor( container: HTMLElement ) { - this._domNode = document.createElement('span'); + this._domNode = document.createElement('div'); this._domNode.className = 'monaco-breadcrumbs'; + this._domNode.tabIndex = -1; this._scrollable = new DomScrollableElement(this._domNode, { vertical: ScrollbarVisibility.Hidden, horizontal: ScrollbarVisibility.Auto, @@ -103,16 +104,19 @@ export class BreadcrumbsWidget { this._domNode.focus(); } - select(nth: number): void { - if (typeof this._activeItem === 'number') { + select(nth: number): boolean { + if (this._activeItem !== -1) { dom.removeClass(this._nodes[this._activeItem], 'active'); + this._activeItem = -1; } - if (nth >= this._nodes.length) { - this._activeItem = nth; - let node = this._nodes[this._activeItem]; - dom.addClass(node, 'active'); - this._scrollable.setScrollPosition({ scrollLeft: node.offsetLeft }); + if (nth < 0 || nth >= this._nodes.length) { + return false; } + this._activeItem = nth; + let node = this._nodes[this._activeItem]; + dom.addClass(node, 'active'); + this._scrollable.setScrollPosition({ scrollLeft: node.offsetLeft }); + return true; } append(item: BreadcrumbsItem): void { @@ -143,7 +147,7 @@ export class BreadcrumbsWidget { // case b: more items -> render them for (; start < this._items.length; start++) { let item = this._items[start]; - let node = this._freeNodes.length > 0 ? this._freeNodes.pop() : document.createElement('span'); + let node = this._freeNodes.length > 0 ? this._freeNodes.pop() : document.createElement('div'); this._renderItem(item, node); this._domNode.appendChild(node); this._nodes[start] = node; @@ -152,7 +156,7 @@ export class BreadcrumbsWidget { this.select(this._nodes.length - 1); } - private _renderItem(item: BreadcrumbsItem, container: HTMLSpanElement): void { + private _renderItem(item: BreadcrumbsItem, container: HTMLDivElement): void { dom.clearNode(container); dom.append(container, item.node); dom.addClass(container, 'monaco-breadcrumb-item'); @@ -163,6 +167,7 @@ export class BreadcrumbsWidget { for (let el = event.target; el; el = el.parentElement) { let idx = this._nodes.indexOf(el as any); if (idx >= 0) { + this.select(idx); this._onDidSelectItem.fire(this._items[idx]); break; } diff --git a/src/vs/workbench/parts/breadcrumbs/electron-browser/editorBreadcrumbs.ts b/src/vs/workbench/parts/breadcrumbs/electron-browser/editorBreadcrumbs.ts new file mode 100644 index 00000000000..198261cae8c --- /dev/null +++ b/src/vs/workbench/parts/breadcrumbs/electron-browser/editorBreadcrumbs.ts @@ -0,0 +1,128 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + + +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser'; +import { BreadcrumbsWidget, RenderedBreadcrumbsItem } from 'vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { Event, Emitter } from 'vs/base/common/event'; +import URI from 'vs/base/common/uri'; +import { posix } from 'path'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { FileKind } from 'vs/platform/files/common/files'; +import { FileLabel } from 'vs/workbench/browser/labels'; + +class Widget implements IOverlayWidget { + + breadcrumb: BreadcrumbsWidget; + ready: boolean; + getId(): string { + return 'EditorBreadcrumbs.Widget'; + } + + getDomNode(): HTMLElement { + let container = document.createElement('div'); + container.style.backgroundColor = 'white'; + let widget = new BreadcrumbsWidget(container); + this.breadcrumb = widget; + this.ready = true; + return container; + } + + getPosition(): IOverlayWidgetPosition { + return { + preference: OverlayWidgetPositionPreference.BOTTOM_RIGHT_CORNER + }; + } +} + +class BreadcrumbsUpdateEvent { + + private readonly _disposables = new Array(); + private readonly _emitter = new Emitter(); + + readonly event: Event = this._emitter.event; + + constructor(private readonly _editor: ICodeEditor) { + this._disposables.push(this._editor.onDidChangeModel(_ => this._emitter.fire())); + } +} + +export class EditorBreadcrumbs implements IEditorContribution { + + static get(editor: ICodeEditor): EditorBreadcrumbs { + return editor.getContribution('EditorBreadcrumbs'); + } + + private readonly _disposables = new Array(); + private readonly _widget: Widget; + private readonly _update: BreadcrumbsUpdateEvent; + + constructor( + private readonly _editor: ICodeEditor, + @IInstantiationService private readonly _instantiationService: IInstantiationService + ) { + this._widget = new Widget(); + this._update = new BreadcrumbsUpdateEvent(this._editor); + this._editor.addOverlayWidget(this._widget); + this._update.event(this._onUpdate, this, this._disposables); + } + + getId(): string { + return 'EditorBreadcrumbs'; + } + + dispose(): void { + this._editor.removeOverlayWidget(this._widget); + } + + private _onUpdate(): void { + if (!this._widget.ready || !this._editor.getModel()) { + return; + } + let { uri } = this._editor.getModel(); + + interface Element { + name: string; + uri: URI; + kind: FileKind; + } + + const render = (element: Element, target: HTMLElement, disposables: IDisposable[]) => { + let label = this._instantiationService.createInstance(FileLabel, target, {}); + label.setFile(element.uri, { fileKind: element.kind, hidePath: true }); + disposables.push(label); + }; + + let items: RenderedBreadcrumbsItem[] = []; + let path = uri.path; + while (path !== '/') { + let first = items.length === 0; + let name = posix.basename(path); + uri = uri.with({ path }); + path = posix.dirname(path); + items.unshift(new RenderedBreadcrumbsItem( + render, + { name, uri, kind: first ? FileKind.FILE : FileKind.FOLDER }, + !first + )); + } + + this._widget.breadcrumb.replace(undefined, items); + } + + focus(): void { + this._widget.breadcrumb.focus(); + } + + // saveViewState?() { + // throw new Error('Method not implemented.'); + // } + // restoreViewState?(state: any): void { + // throw new Error('Method not implemented.'); + // } +} From ecaae775e43aa1504c2c1805d2d1463011e58fdf Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 14 Jun 2018 11:51:14 +0200 Subject: [PATCH 023/283] add focus/next/prev/back commands --- .../breadcrumbs.contribution.ts | 48 +++++++++++++++- .../electron-browser/breadcrumbsWidget.ts | 43 +++++++++++--- .../electron-browser/editorBreadcrumbs.ts | 57 +++++++++++++++---- 3 files changed, 128 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbs.contribution.ts b/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbs.contribution.ts index b0dc0ff39bb..8757b516def 100644 --- a/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbs.contribution.ts +++ b/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbs.contribution.ts @@ -10,6 +10,7 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { registerEditorContribution, EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions'; import { EditorBreadcrumbs } from 'vs/workbench/parts/breadcrumbs/electron-browser/editorBreadcrumbs'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; registerEditorContribution(EditorBreadcrumbs); @@ -23,6 +24,51 @@ registerEditorCommand(new BreadcrumbCommandCtor({ kbOpts: { weight: KeybindingsRegistry.WEIGHT.editorContrib(50), kbExpr: EditorContextKeys.focus, - primary: KeyMod.CtrlCmd | KeyCode.US_DOT + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_DOT + } +})); + +registerEditorCommand(new BreadcrumbCommandCtor({ + id: 'breadcrumbs.focusNext', + precondition: undefined, + handler: x => x.focusNext(), + kbOpts: { + weight: KeybindingsRegistry.WEIGHT.editorContrib(50), + kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, EditorBreadcrumbs.CK_Focused), + primary: KeyCode.RightArrow + } +})); + +registerEditorCommand(new BreadcrumbCommandCtor({ + id: 'breadcrumbs.focusPrev', + precondition: undefined, + handler: x => x.focusPrev(), + kbOpts: { + weight: KeybindingsRegistry.WEIGHT.editorContrib(50), + kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, EditorBreadcrumbs.CK_Focused), + primary: KeyCode.LeftArrow + } +})); + +registerEditorCommand(new BreadcrumbCommandCtor({ + id: 'breadcrumbs.select', + precondition: undefined, + handler: x => x.select(), + kbOpts: { + weight: KeybindingsRegistry.WEIGHT.editorContrib(50), + kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, EditorBreadcrumbs.CK_Focused), + primary: KeyCode.Enter, + secondary: [KeyCode.UpArrow, KeyCode.Space] + } +})); + +registerEditorCommand(new BreadcrumbCommandCtor({ + id: 'breadcrumbs.focusEditor', + precondition: undefined, + handler: x => x.editor.focus(), + kbOpts: { + weight: KeybindingsRegistry.WEIGHT.editorContrib(50), + kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, EditorBreadcrumbs.CK_Focused), + primary: KeyCode.Escape } })); diff --git a/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget.ts b/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget.ts index e8ac7716b24..f11495d93a6 100644 --- a/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget.ts +++ b/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget.ts @@ -60,12 +60,15 @@ export class BreadcrumbsWidget { private _cachedWidth: number; private readonly _onDidSelectItem = new Emitter(); + private readonly _onDidChangeFocus = new Emitter(); + readonly onDidSelectItem: Event = this._onDidSelectItem.event; + readonly onDidChangeFocus: Event = this._onDidChangeFocus.event; private readonly _items = new Array(); private readonly _nodes = new Array(); private readonly _freeNodes = new Array(); - private _activeItem: number = -1; + private _focusedItemIdx: number = -1; constructor( container: HTMLElement @@ -82,6 +85,11 @@ export class BreadcrumbsWidget { this._disposables.push(this._scrollable); this._disposables.push(dom.addStandardDisposableListener(this._domNode, 'click', e => this._onClick(e))); container.appendChild(this._scrollable.getDomNode()); + + let focusTracker = dom.trackFocus(this._domNode); + this._disposables.push(focusTracker); + this._disposables.push(focusTracker.onDidBlur(_ => this._onDidChangeFocus.fire(false))); + this._disposables.push(focusTracker.onDidFocus(_ => this._onDidChangeFocus.fire(true))); } dispose(): void { @@ -104,21 +112,38 @@ export class BreadcrumbsWidget { this._domNode.focus(); } - select(nth: number): boolean { - if (this._activeItem !== -1) { - dom.removeClass(this._nodes[this._activeItem], 'active'); - this._activeItem = -1; + focusPrev(): any { + this._focus((this._focusedItemIdx - 1 + this._nodes.length) % this._nodes.length); + this._domNode.focus(); + } + + focusNext(): any { + this._focus((this._focusedItemIdx + 1) % this._nodes.length); + this._domNode.focus(); + } + + private _focus(nth: number): boolean { + if (this._focusedItemIdx !== -1) { + dom.removeClass(this._nodes[this._focusedItemIdx], 'active'); + this._focusedItemIdx = -1; } if (nth < 0 || nth >= this._nodes.length) { return false; } - this._activeItem = nth; - let node = this._nodes[this._activeItem]; + this._focusedItemIdx = nth; + let node = this._nodes[this._focusedItemIdx]; dom.addClass(node, 'active'); this._scrollable.setScrollPosition({ scrollLeft: node.offsetLeft }); return true; } + select(): void { + if (this._focusedItemIdx !== -1) { + let item = this._items[this._focusedItemIdx]; + this._onDidSelectItem.fire(item); + } + } + append(item: BreadcrumbsItem): void { this._items.push(item); this._render(this._items.length - 1); @@ -153,7 +178,7 @@ export class BreadcrumbsWidget { this._nodes[start] = node; } this.layout(); - this.select(this._nodes.length - 1); + this._focus(this._nodes.length - 1); } private _renderItem(item: BreadcrumbsItem, container: HTMLDivElement): void { @@ -167,7 +192,7 @@ export class BreadcrumbsWidget { for (let el = event.target; el; el = el.parentElement) { let idx = this._nodes.indexOf(el as any); if (idx >= 0) { - this.select(idx); + this._focus(idx); this._onDidSelectItem.fire(this._items[idx]); break; } diff --git a/src/vs/workbench/parts/breadcrumbs/electron-browser/editorBreadcrumbs.ts b/src/vs/workbench/parts/breadcrumbs/electron-browser/editorBreadcrumbs.ts index 198261cae8c..970656cab15 100644 --- a/src/vs/workbench/parts/breadcrumbs/electron-browser/editorBreadcrumbs.ts +++ b/src/vs/workbench/parts/breadcrumbs/electron-browser/editorBreadcrumbs.ts @@ -8,23 +8,30 @@ import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser'; import { BreadcrumbsWidget, RenderedBreadcrumbsItem } from 'vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import URI from 'vs/base/common/uri'; import { posix } from 'path'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { FileKind } from 'vs/platform/files/common/files'; import { FileLabel } from 'vs/workbench/browser/labels'; +import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; class Widget implements IOverlayWidget { breadcrumb: BreadcrumbsWidget; ready: boolean; + + constructor(private _onFirstRender: Function) { + + } + getId(): string { return 'EditorBreadcrumbs.Widget'; } getDomNode(): HTMLElement { + setTimeout(() => this._onFirstRender()); let container = document.createElement('div'); container.style.backgroundColor = 'white'; let widget = new BreadcrumbsWidget(container); @@ -52,8 +59,11 @@ class BreadcrumbsUpdateEvent { } } + export class EditorBreadcrumbs implements IEditorContribution { + static CK_Focused = new RawContextKey('breadcrumbFocused', false); + static get(editor: ICodeEditor): EditorBreadcrumbs { return editor.getContribution('EditorBreadcrumbs'); } @@ -62,14 +72,16 @@ export class EditorBreadcrumbs implements IEditorContribution { private readonly _widget: Widget; private readonly _update: BreadcrumbsUpdateEvent; + private _ckFocused: IContextKey; + constructor( - private readonly _editor: ICodeEditor, - @IInstantiationService private readonly _instantiationService: IInstantiationService + readonly editor: ICodeEditor, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, ) { - this._widget = new Widget(); - this._update = new BreadcrumbsUpdateEvent(this._editor); - this._editor.addOverlayWidget(this._widget); - this._update.event(this._onUpdate, this, this._disposables); + this._widget = new Widget(() => this._onWidgetReady()); + this._update = new BreadcrumbsUpdateEvent(this.editor); + this.editor.addOverlayWidget(this._widget); } getId(): string { @@ -77,14 +89,23 @@ export class EditorBreadcrumbs implements IEditorContribution { } dispose(): void { - this._editor.removeOverlayWidget(this._widget); + dispose(this._disposables); + this.editor.removeOverlayWidget(this._widget); + this._ckFocused.reset(); + } + + private _onWidgetReady(): void { + this._ckFocused = EditorBreadcrumbs.CK_Focused.bindTo(this._contextKeyService); + this._disposables.push(this._widget.breadcrumb.onDidChangeFocus(value => this._ckFocused.set(value))); + this._disposables.push(this._widget.breadcrumb.onDidSelectItem(this._onDidSelectItem, this)); + this._update.event(this._onUpdate, this, this._disposables); } private _onUpdate(): void { - if (!this._widget.ready || !this._editor.getModel()) { + if (!this._widget.ready || !this.editor.getModel()) { return; } - let { uri } = this._editor.getModel(); + let { uri } = this.editor.getModel(); interface Element { name: string; @@ -115,10 +136,26 @@ export class EditorBreadcrumbs implements IEditorContribution { this._widget.breadcrumb.replace(undefined, items); } + private _onDidSelectItem(item: RenderedBreadcrumbsItem): void { + console.log(item); + } + focus(): void { this._widget.breadcrumb.focus(); } + focusNext(): void { + this._widget.breadcrumb.focusNext(); + } + + focusPrev(): void { + this._widget.breadcrumb.focusPrev(); + } + + select(): void { + this._widget.breadcrumb.select(); + } + // saveViewState?() { // throw new Error('Method not implemented.'); // } From e35f72e5e9e55879331de69f55e4e1b2f5a27784 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 14 Jun 2018 16:31:26 +0200 Subject: [PATCH 024/283] move breadcrumbs into editor group view --- .../ui/breadcrumbs}/breadcrumbsWidget.css | 12 +- .../ui/breadcrumbs}/breadcrumbsWidget.ts | 20 +- .../workbench/browser/parts/editor/editor.ts | 3 +- .../browser/parts/editor/editorBreadcrumbs.ts | 352 ++++++++++++++++++ .../browser/parts/editor/editorGroupView.ts | 42 ++- .../parts/editor/media/editorbreadcrumbs.css | 12 + .../electron-browser/breadcrumbsPicker.ts | 144 +++++++ .../electron-browser/editorBreadcrumbs.ts | 49 ++- .../group/common/editorGroupsService.ts | 14 +- src/vs/workbench/workbench.main.ts | 2 - 10 files changed, 610 insertions(+), 40 deletions(-) rename src/vs/{workbench/parts/breadcrumbs/electron-browser => base/browser/ui/breadcrumbs}/breadcrumbsWidget.css (72%) rename src/vs/{workbench/parts/breadcrumbs/electron-browser => base/browser/ui/breadcrumbs}/breadcrumbsWidget.ts (94%) create mode 100644 src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts create mode 100644 src/vs/workbench/browser/parts/editor/media/editorbreadcrumbs.css create mode 100644 src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsPicker.ts diff --git a/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget.css b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css similarity index 72% rename from src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget.css rename to src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css index 7a30f6791f4..f1556526d68 100644 --- a/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget.css +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css @@ -9,8 +9,6 @@ flex-direction: row; flex-wrap: nowrap; justify-content: flex-start; - --item-hover-background: green; - --item-hover-color: inhert; } .monaco-breadcrumbs .monaco-breadcrumb-item { @@ -19,15 +17,7 @@ flex: 0 1 auto; white-space: nowrap; cursor: pointer; -} - -.monaco-breadcrumbs:focus .monaco-breadcrumb-item.active { - color: pink; -} - -.monaco-breadcrumbs .monaco-breadcrumb-item:hover { - color: var(--item-hover-color); - background-color: var(--item-hover-background); + align-self: center; } .monaco-breadcrumbs .monaco-breadcrumb-item-more { diff --git a/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts similarity index 94% rename from src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget.ts rename to src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index f11495d93a6..e83364656e6 100644 --- a/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -38,11 +38,12 @@ export class SimpleBreadcrumbsItem extends BreadcrumbsItem { export class RenderedBreadcrumbsItem extends BreadcrumbsItem { - + readonly element: E; private _disposables: IDisposable[] = []; constructor(render: (element: E, container: HTMLDivElement, bucket: IDisposable[]) => any, element: E, more: boolean) { super(document.createElement('div'), more); + this.element = element; render(element, this.node as HTMLDivElement, this._disposables); } @@ -57,7 +58,6 @@ export class BreadcrumbsWidget { private readonly _disposables = new Array(); private readonly _domNode: HTMLDivElement; private readonly _scrollable: DomScrollableElement; - private _cachedWidth: number; private readonly _onDidSelectItem = new Emitter(); private readonly _onDidChangeFocus = new Emitter(); @@ -100,10 +100,12 @@ export class BreadcrumbsWidget { this._freeNodes.length = 0; } - layout(width: number = this._cachedWidth): void { - if (typeof width === 'number') { - this._cachedWidth = width; - this._domNode.style.width = `${this._cachedWidth}px`; + layout(dim: dom.Dimension): void { + if (!dim) { + this._scrollable.scanDomNode(); + } else { + this._domNode.style.width = `${dim.width}px`; + this._domNode.style.height = `${dim.height}px`; this._scrollable.scanDomNode(); } } @@ -124,7 +126,7 @@ export class BreadcrumbsWidget { private _focus(nth: number): boolean { if (this._focusedItemIdx !== -1) { - dom.removeClass(this._nodes[this._focusedItemIdx], 'active'); + dom.removeClass(this._nodes[this._focusedItemIdx], 'focused'); this._focusedItemIdx = -1; } if (nth < 0 || nth >= this._nodes.length) { @@ -132,7 +134,7 @@ export class BreadcrumbsWidget { } this._focusedItemIdx = nth; let node = this._nodes[this._focusedItemIdx]; - dom.addClass(node, 'active'); + dom.addClass(node, 'focused'); this._scrollable.setScrollPosition({ scrollLeft: node.offsetLeft }); return true; } @@ -177,7 +179,7 @@ export class BreadcrumbsWidget { this._domNode.appendChild(node); this._nodes[start] = node; } - this.layout(); + this.layout(undefined); this._focus(this._nodes.length - 1); } diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index 74518dd76c1..f9b0c858a38 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -20,6 +20,7 @@ import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export const EDITOR_TITLE_HEIGHT = 35; +export const BREAD_CRUMPS_HEIGHT = 30; export const EDITOR_MIN_DIMENSIONS = new Dimension(220, 70); export const EDITOR_MAX_DIMENSIONS = new Dimension(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY); @@ -159,4 +160,4 @@ export interface EditorGroupsServiceImpl extends IEditorGroupsService { * A promise that resolves when groups have been restored. */ readonly whenRestored: TPromise; -} \ No newline at end of file +} diff --git a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts new file mode 100644 index 00000000000..72f5ee5c617 --- /dev/null +++ b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts @@ -0,0 +1,352 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import 'vs/css!./media/editorbreadcrumbs'; +import * as dom from 'vs/base/browser/dom'; +import { BreadcrumbsWidget, RenderedBreadcrumbsItem } from 'vs/base/browser/ui/breadcrumbs/breadcrumbsWidget'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import * as paths from 'vs/base/common/paths'; +import URI from 'vs/base/common/uri'; +import { IContextKey, IContextKeyService, RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { FileKind, IFileService, IFileStat } from 'vs/platform/files/common/files'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { FileLabel } from 'vs/workbench/browser/labels'; +import { EditorInput } from 'vs/workbench/common/editor'; +import { IEditorBreadcrumbs, IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; +import { ITreeConfiguration, IDataSource, ITree, IRenderer, ISelectionEvent } from 'vs/base/parts/tree/browser/tree'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { WorkbenchTree } from 'vs/platform/list/browser/listService'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { debounceEvent, Emitter, Event } from 'vs/base/common/event'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { isEqual } from 'vs/base/common/resources'; + +interface FileElement { + name: string; + uri: URI; + kind: FileKind; +} + +export class EditorBreadcrumbs implements IEditorBreadcrumbs { + + static CK_BreadcrumbsVisible = new RawContextKey('breadcrumbsVisible', false); + static CK_BreadcrumbsFocused = new RawContextKey('breadcrumbsFocused', false); + + private readonly _disposables = new Array(); + private readonly _domNode: HTMLDivElement; + private readonly _widget: BreadcrumbsWidget; + + private readonly _ckBreadcrumbsVisible: IContextKey; + private readonly _ckBreadcrumbsFocused: IContextKey; + + constructor( + container: HTMLElement, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IFileService private readonly _fileService: IFileService, + @IContextViewService private readonly _contextViewService: IContextViewService, + @IEditorService private readonly _editorService: IEditorService, + @IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + ) { + this._domNode = document.createElement('div'); + this._domNode.className = 'editor-breadcrumbs'; + this._widget = new BreadcrumbsWidget(this._domNode); + this._widget.onDidSelectItem(this._onDidSelectItem, this, this._disposables); + this._widget.onDidChangeFocus(val => this._ckBreadcrumbsFocused.set(val), undefined, this._disposables); + container.appendChild(this._domNode); + + this._ckBreadcrumbsVisible = EditorBreadcrumbs.CK_BreadcrumbsVisible.bindTo(this._contextKeyService); + this._ckBreadcrumbsFocused = EditorBreadcrumbs.CK_BreadcrumbsFocused.bindTo(this._contextKeyService); + } + + dispose(): void { + dispose(this._disposables); + this._widget.dispose(); + this._ckBreadcrumbsVisible.reset(); + } + + layout(dim: dom.Dimension): void { + this._domNode.style.width = `${dim.width}px`; + this._domNode.style.height = `${dim.height}px`; + this._widget.layout(dim); + } + + setActive(value: boolean): void { + dom.toggleClass(this._domNode, 'active', value); + } + + openEditor(input: EditorInput): void { + + let uri = input.getResource(); + if (!uri || !this._fileService.canHandleResource(uri)) { + return this.closeEditor(undefined); + } + + this._ckBreadcrumbsVisible.set(true); + dom.toggleClass(this._domNode, 'hidden', false); + + + const render = (element: FileElement, target: HTMLElement, disposables: IDisposable[]) => { + let label = this._instantiationService.createInstance(FileLabel, target, {}); + label.setFile(element.uri, { fileKind: element.kind, hidePath: true }); + disposables.push(label); + }; + + let items: RenderedBreadcrumbsItem[] = []; + let workspace = this._workspaceService.getWorkspaceFolder(uri); + let path = uri.path; + + while (true) { + if (workspace && isEqual(workspace.uri, uri, true) || path === '/') { + break; + } + + let first = items.length === 0; + let name = paths.basename(path); + uri = uri.with({ path }); + path = paths.dirname(path); + items.unshift(new RenderedBreadcrumbsItem( + render, + { name, uri, kind: first ? FileKind.FILE : FileKind.FOLDER }, + !first + )); + } + + this._widget.replace(undefined, items); + } + + closeEditor(input: EditorInput): void { + this._ckBreadcrumbsVisible.set(false); + dom.toggleClass(this._domNode, 'hidden', true); + } + + focus(): void { + this._widget.focus(); + } + + focusNext(): void { + this._widget.focusNext(); + } + + focusPrev(): void { + this._widget.focusPrev(); + } + + select(): void { + this._widget.select(); + } + + private _onDidSelectItem(item: RenderedBreadcrumbsItem): void { + this._contextViewService.showContextView({ + getAnchor() { + return item.node; + }, + render: (container: HTMLElement) => { + let res = this._instantiationService.createInstance(BreadcrumbsFilePicker, container); + res.layout({ width: 250, height: 300 }); + res.setInput(item.element.uri.with({ path: paths.dirname(item.element.uri.path) })); + res.onDidPickElement(data => { + this._contextViewService.hideContextView(); + if (!data) { + return; + } + if (URI.isUri(data)) { + this._editorService.openEditor({ resource: data }); + } + }); + return res; + }, + }); + } +} + +export abstract class BreadcrumbsPicker { + + readonly focus: dom.IFocusTracker; + + protected readonly _onDidPickElement = new Emitter(); + readonly onDidPickElement: Event = this._onDidPickElement.event; + + protected readonly _disposables = new Array(); + protected readonly _domNode: HTMLDivElement; + protected readonly _tree: WorkbenchTree; + + constructor( + container: HTMLElement, + @IInstantiationService protected readonly _instantiationService: IInstantiationService, + @IThemeService protected readonly _themeService: IThemeService, + ) { + this._domNode = document.createElement('div'); + // this._domNode.style.background = this._themeService.getTheme().getColor(colors.progressBarBackground).toString(); + container.appendChild(this._domNode); + + this._tree = this._instantiationService.createInstance(WorkbenchTree, this._domNode, this._completeTreeConfiguration({ dataSource: undefined }), {}); + debounceEvent(this._tree.onDidChangeSelection, (last, cur) => cur, 0)(this._onDidChangeSelection, this, this._disposables); + + this.focus = dom.trackFocus(this._domNode); + this.focus.onDidBlur(_ => this._onDidPickElement.fire(undefined), undefined, this._disposables); + } + + dispose(): void { + dispose(this._disposables); + this._onDidPickElement.dispose(); + this._tree.dispose(); + this.focus.dispose(); + } + + layout(dim: dom.Dimension) { + this._domNode.style.width = `${dim.width}px`; + this._domNode.style.height = `${dim.height}px`; + this._tree.layout(dim.height, dim.width); + } + + protected abstract _completeTreeConfiguration(config: ITreeConfiguration): ITreeConfiguration; + protected abstract _onDidChangeSelection(e: any): void; +} + +export class FileDataSource implements IDataSource { + + private readonly _parents = new WeakMap(); + + constructor( + @IFileService private readonly _fileService: IFileService, + ) { } + + getId(tree: ITree, element: IFileStat | URI): string { + return URI.isUri(element) ? element.toString() : element.resource.toString(); + } + + hasChildren(tree: ITree, element: IFileStat | URI): boolean { + return URI.isUri(element) || element.isDirectory; + } + + getChildren(tree: ITree, element: IFileStat | URI): TPromise { + return this._fileService.resolveFile( + URI.isUri(element) ? element : element.resource + ).then(stat => { + for (const child of stat.children) { + this._parents.set(child, stat); + } + return stat.children; + }); + } + + getParent(tree: ITree, element: IFileStat | URI): TPromise { + return TPromise.as(URI.isUri(element) ? undefined : this._parents.get(element)); + } +} + +export class FileRenderer implements IRenderer { + + constructor( + @IInstantiationService private readonly _instantiationService: IInstantiationService, + ) { } + + getHeight(tree: ITree, element: any): number { + return 22; + } + + getTemplateId(tree: ITree, element: any): string { + return 'FileStat'; + } + + renderTemplate(tree: ITree, templateId: string, container: HTMLElement) { + return this._instantiationService.createInstance(FileLabel, container, {}); + } + + renderElement(tree: ITree, element: IFileStat, templateId: string, templateData: FileLabel): void { + templateData.setFile(element.resource, { hidePath: true, fileKind: element.isDirectory ? FileKind.FOLDER : FileKind.FILE }); + } + + disposeTemplate(tree: ITree, templateId: string, templateData: FileLabel): void { + templateData.dispose(); + } +} + +export class BreadcrumbsFilePicker extends BreadcrumbsPicker { + + + protected _completeTreeConfiguration(config: ITreeConfiguration): ITreeConfiguration { + config.dataSource = this._instantiationService.createInstance(FileDataSource); + config.renderer = this._instantiationService.createInstance(FileRenderer); + return config; + } + + setInput(resource: URI): void { + this._tree.domFocus(); + this._tree.setInput(resource); + } + + protected _onDidChangeSelection(e: ISelectionEvent): void { + let [first] = e.selection; + let stat = first as IFileStat; + if (stat && !stat.isDirectory) { + this._onDidPickElement.fire(stat.resource); + } + } +} + + +//#region commands + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'breadcrumbs.focus', + weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_DOT, + when: EditorBreadcrumbs.CK_BreadcrumbsVisible, + handler(accessor) { + let groups = accessor.get(IEditorGroupsService); + groups.activeGroup.breadcrumbs.focus(); + } +}); +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'breadcrumbs.focusNext', + weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), + primary: KeyCode.RightArrow, + when: ContextKeyExpr.and(EditorBreadcrumbs.CK_BreadcrumbsVisible, EditorBreadcrumbs.CK_BreadcrumbsFocused), + handler(accessor) { + let groups = accessor.get(IEditorGroupsService); + groups.activeGroup.breadcrumbs.focusNext(); + } +}); +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'breadcrumbs.focusPrevious', + weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), + primary: KeyCode.LeftArrow, + when: ContextKeyExpr.and(EditorBreadcrumbs.CK_BreadcrumbsVisible, EditorBreadcrumbs.CK_BreadcrumbsFocused), + handler(accessor) { + let groups = accessor.get(IEditorGroupsService); + groups.activeGroup.breadcrumbs.focusPrev(); + } +}); +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'breadcrumbs.selectFocused', + weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), + primary: KeyCode.Enter, + secondary: [KeyCode.UpArrow, KeyCode.Space], + when: ContextKeyExpr.and(EditorBreadcrumbs.CK_BreadcrumbsVisible, EditorBreadcrumbs.CK_BreadcrumbsFocused), + handler(accessor) { + let groups = accessor.get(IEditorGroupsService); + groups.activeGroup.breadcrumbs.select(); + } +}); +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'breadcrumbs.selectEditor', + weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), + primary: KeyCode.Escape, + when: ContextKeyExpr.and(EditorBreadcrumbs.CK_BreadcrumbsVisible, EditorBreadcrumbs.CK_BreadcrumbsFocused), + handler(accessor) { + let groups = accessor.get(IEditorGroupsService); + groups.activeGroup.activeControl.focus(); + } +}); + +//#endregion diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 0e9f49bd151..97615f52698 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -19,7 +19,7 @@ import { attachProgressBarStyler } from 'vs/platform/theme/common/styler'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { editorBackground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { Themable, EDITOR_GROUP_HEADER_TABS_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND, EDITOR_GROUP_EMPTY_BACKGROUND, EDITOR_GROUP_FOCUSED_EMPTY_BORDER } from 'vs/workbench/common/theme'; -import { IMoveEditorOptions, ICopyEditorOptions, ICloseEditorsFilter, IGroupChangeEvent, GroupChangeKind, EditorsOrder, GroupsOrder } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IMoveEditorOptions, ICopyEditorOptions, ICloseEditorsFilter, IGroupChangeEvent, GroupChangeKind, EditorsOrder, GroupsOrder, IEditorBreadcrumbs } from 'vs/workbench/services/group/common/editorGroupsService'; import { TabsTitleControl } from 'vs/workbench/browser/parts/editor/tabsTitleControl'; import { EditorControl } from 'vs/workbench/browser/parts/editor/editorControl'; import { IProgressService } from 'vs/platform/progress/common/progress'; @@ -34,7 +34,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { RunOnceWorker } from 'vs/base/common/async'; import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch'; import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl'; -import { IEditorGroupsAccessor, IEditorGroupView, IEditorPartOptionsChangeEvent, EDITOR_TITLE_HEIGHT, EDITOR_MIN_DIMENSIONS, EDITOR_MAX_DIMENSIONS, getActiveTextEditorOptions, IEditorOpeningEvent } from 'vs/workbench/browser/parts/editor/editor'; +import { IEditorGroupsAccessor, IEditorGroupView, IEditorPartOptionsChangeEvent, EDITOR_TITLE_HEIGHT, EDITOR_MIN_DIMENSIONS, EDITOR_MAX_DIMENSIONS, getActiveTextEditorOptions, IEditorOpeningEvent, BREAD_CRUMPS_HEIGHT } from 'vs/workbench/browser/parts/editor/editor'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { join } from 'vs/base/common/paths'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -46,6 +46,7 @@ import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions' import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { fillInContextMenuActions } from 'vs/platform/actions/browser/menuItemActionItem'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { EditorBreadcrumbs } from 'vs/workbench/browser/parts/editor/editorBreadcrumbs'; export class EditorGroupView extends Themable implements IEditorGroupView { @@ -104,6 +105,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private titleContainer: HTMLElement; private titleAreaControl: TitleControl; + private breadcrumbsContainer: HTMLElement; + private breadcrumbsControl: EditorBreadcrumbs; + private progressBar: ProgressBar; private editorContainer: HTMLElement; @@ -190,6 +194,14 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Title control this.createTitleAreaControl(); + // Breadcrumbs container + this.breadcrumbsContainer = document.createElement('div'); + addClass(this.breadcrumbsContainer, 'editor-breadcrumbs'); + this.element.appendChild(this.breadcrumbsContainer); + + // Breadcrumbs control + this.createEditorBreadcrumbs(); + // Editor container this.editorContainer = document.createElement('div'); addClass(this.editorContainer, 'editor-container'); @@ -396,6 +408,16 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } } + private createEditorBreadcrumbs(): void { + + if (this.breadcrumbsControl) { + this.breadcrumbsControl.dispose(); + clearNode(this.breadcrumbsContainer); + } + + this.breadcrumbsControl = this.scopedInstantiationService.createInstance(EditorBreadcrumbs, this.breadcrumbsContainer); + } + private restoreEditors(from: IEditorGroupView | ISerializedEditorGroup): TPromise { if (this._group.count === 0) { return TPromise.as(void 0); // nothing to show @@ -592,6 +614,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return this._label; } + get breadcrumbs(): IEditorBreadcrumbs { + return this.breadcrumbsControl; + } + get disposed(): boolean { return this._disposed; } @@ -617,6 +643,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Update title control this.titleAreaControl.setActive(isActive); + this.breadcrumbsControl.setActive(isActive); + // Update styles this.updateStyles(); @@ -788,6 +816,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Show in title control after editor control because some actions depend on it this.titleAreaControl.openEditor(editor); + this.breadcrumbsControl.openEditor(editor); + return openEditorPromise; } @@ -955,8 +985,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.doCloseInactiveEditor(editor); } - // Forward to title control + // Forward to title control & breadcrumbs this.titleAreaControl.closeEditor(editor); + this.breadcrumbsControl.closeEditor(editor); } private doCloseActiveEditor(focusNext = this.accessor.activeGroup === this, fromError?: boolean): void { @@ -1342,7 +1373,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Forward to controls this.titleAreaControl.layout(new Dimension(this.dimension.width, EDITOR_TITLE_HEIGHT)); - this.editorControl.layout(new Dimension(this.dimension.width, this.dimension.height - EDITOR_TITLE_HEIGHT)); + this.breadcrumbsControl.layout(new Dimension(this.dimension.width, BREAD_CRUMPS_HEIGHT)); + this.editorControl.layout(new Dimension(this.dimension.width, this.dimension.height - (EDITOR_TITLE_HEIGHT + BREAD_CRUMPS_HEIGHT))); } toJSON(): ISerializedEditorGroup { @@ -1362,6 +1394,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.titleAreaControl.dispose(); + this.breadcrumbsControl.dispose(); + super.dispose(); } } diff --git a/src/vs/workbench/browser/parts/editor/media/editorbreadcrumbs.css b/src/vs/workbench/browser/parts/editor/media/editorbreadcrumbs.css new file mode 100644 index 00000000000..36815a8d225 --- /dev/null +++ b/src/vs/workbench/browser/parts/editor/media/editorbreadcrumbs.css @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-workbench>.part.editor>.content .editor-breadcrumbs .monaco-breadcrumbs { + --color-item-focused: pink; +} + +.monaco-workbench>.part.editor>.content .editor-breadcrumbs .monaco-breadcrumbs:focus .monaco-breadcrumb-item.focused { + color: var(--color-item-focused); +} diff --git a/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsPicker.ts b/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsPicker.ts new file mode 100644 index 00000000000..4deeeb91658 --- /dev/null +++ b/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsPicker.ts @@ -0,0 +1,144 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { Dimension, trackFocus, IFocusTracker } from 'vs/base/browser/dom'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { ITreeConfiguration, IDataSource, ITree, IRenderer, ISelectionEvent } from 'vs/base/parts/tree/browser/tree'; +import { WorkbenchTree } from 'vs/platform/list/browser/listService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { IFileService, IFileStat, FileKind } from 'vs/platform/files/common/files'; +import { FileLabel } from 'vs/workbench/browser/labels'; +import URI from 'vs/base/common/uri'; +import { Emitter, Event, debounceEvent } from 'vs/base/common/event'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; + +export abstract class BreadcrumbsPicker { + + readonly focus: IFocusTracker; + + protected readonly _onDidPickElement = new Emitter(); + readonly onDidPickElement: Event = this._onDidPickElement.event; + + protected readonly _disposables = new Array(); + protected readonly _domNode: HTMLDivElement; + protected readonly _tree: WorkbenchTree; + + constructor( + container: HTMLElement, + @IInstantiationService protected readonly _instantiationService: IInstantiationService, + @IThemeService protected readonly _themeService: IThemeService, + ) { + this._domNode = document.createElement('div'); + // this._domNode.style.background = this._themeService.getTheme().getColor(colors.progressBarBackground).toString(); + container.appendChild(this._domNode); + + this._tree = this._instantiationService.createInstance(WorkbenchTree, this._domNode, this._completeTreeConfiguration({ dataSource: undefined }), {}); + debounceEvent(this._tree.onDidChangeSelection, (last, cur) => cur, 0)(this._onDidChangeSelection, this, this._disposables); + + this.focus = trackFocus(this._domNode); + this.focus.onDidBlur(_ => this._onDidPickElement.fire(undefined), undefined, this._disposables); + } + + dispose(): void { + dispose(this._disposables); + this._onDidPickElement.dispose(); + this._tree.dispose(); + this.focus.dispose(); + } + + layout(dim: Dimension) { + this._domNode.style.width = `${dim.width}px`; + this._domNode.style.height = `${dim.height}px`; + this._tree.layout(dim.height, dim.width); + } + + protected abstract _completeTreeConfiguration(config: ITreeConfiguration): ITreeConfiguration; + protected abstract _onDidChangeSelection(e: any): void; +} + +export class FileDataSource implements IDataSource { + + private readonly _parents = new WeakMap(); + + constructor( + @IFileService private readonly _fileService: IFileService, + ) { } + + getId(tree: ITree, element: IFileStat | URI): string { + return URI.isUri(element) ? element.toString() : element.resource.toString(); + } + + hasChildren(tree: ITree, element: IFileStat | URI): boolean { + return URI.isUri(element) || element.isDirectory; + } + + getChildren(tree: ITree, element: IFileStat | URI): TPromise { + return this._fileService.resolveFile( + URI.isUri(element) ? element : element.resource + ).then(stat => { + for (const child of stat.children) { + this._parents.set(child, stat); + } + return stat.children; + }); + } + + getParent(tree: ITree, element: IFileStat | URI): TPromise { + return TPromise.as(URI.isUri(element) ? undefined : this._parents.get(element)); + } +} + +export class FileRenderer implements IRenderer { + + constructor( + @IInstantiationService private readonly _instantiationService: IInstantiationService, + ) { } + + getHeight(tree: ITree, element: any): number { + return 22; + } + + getTemplateId(tree: ITree, element: any): string { + return 'FileStat'; + } + + renderTemplate(tree: ITree, templateId: string, container: HTMLElement) { + return this._instantiationService.createInstance(FileLabel, container, {}); + } + + renderElement(tree: ITree, element: IFileStat, templateId: string, templateData: FileLabel): void { + templateData.setFile(element.resource, { hidePath: true, fileKind: element.isDirectory ? FileKind.FOLDER : FileKind.FILE }); + } + + disposeTemplate(tree: ITree, templateId: string, templateData: FileLabel): void { + templateData.dispose(); + } +} + +export class BreadcrumbsFilePicker extends BreadcrumbsPicker { + + + protected _completeTreeConfiguration(config: ITreeConfiguration): ITreeConfiguration { + config.dataSource = this._instantiationService.createInstance(FileDataSource); + config.renderer = this._instantiationService.createInstance(FileRenderer); + return config; + } + + setInput(resource: URI): void { + this._tree.domFocus(); + this._tree.setInput(resource); + } + + protected _onDidChangeSelection(e: ISelectionEvent): void { + let [first] = e.selection; + let stat = first as IFileStat; + if (stat && !stat.isDirectory) { + this._onDidPickElement.fire(stat.resource); + } + } +} diff --git a/src/vs/workbench/parts/breadcrumbs/electron-browser/editorBreadcrumbs.ts b/src/vs/workbench/parts/breadcrumbs/electron-browser/editorBreadcrumbs.ts index 970656cab15..a45d920e4a9 100644 --- a/src/vs/workbench/parts/breadcrumbs/electron-browser/editorBreadcrumbs.ts +++ b/src/vs/workbench/parts/breadcrumbs/electron-browser/editorBreadcrumbs.ts @@ -7,7 +7,7 @@ import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser'; -import { BreadcrumbsWidget, RenderedBreadcrumbsItem } from 'vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget'; +import { BreadcrumbsWidget, RenderedBreadcrumbsItem } from 'vs/base/browser/ui/breadcrumbs/breadcrumbsWidget'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import URI from 'vs/base/common/uri'; @@ -16,6 +16,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { FileKind } from 'vs/platform/files/common/files'; import { FileLabel } from 'vs/workbench/browser/labels'; import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { BreadcrumbsFilePicker } from 'vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsPicker'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; class Widget implements IOverlayWidget { @@ -59,6 +62,11 @@ class BreadcrumbsUpdateEvent { } } +interface FileElement { + name: string; + uri: URI; + kind: FileKind; +} export class EditorBreadcrumbs implements IEditorContribution { @@ -78,6 +86,8 @@ export class EditorBreadcrumbs implements IEditorContribution { readonly editor: ICodeEditor, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IContextViewService private readonly _contextViewService: IContextViewService, + @IEditorService private readonly _editorService: IEditorService, ) { this._widget = new Widget(() => this._onWidgetReady()); this._update = new BreadcrumbsUpdateEvent(this.editor); @@ -99,6 +109,7 @@ export class EditorBreadcrumbs implements IEditorContribution { this._disposables.push(this._widget.breadcrumb.onDidChangeFocus(value => this._ckFocused.set(value))); this._disposables.push(this._widget.breadcrumb.onDidSelectItem(this._onDidSelectItem, this)); this._update.event(this._onUpdate, this, this._disposables); + this._onUpdate(); } private _onUpdate(): void { @@ -107,26 +118,20 @@ export class EditorBreadcrumbs implements IEditorContribution { } let { uri } = this.editor.getModel(); - interface Element { - name: string; - uri: URI; - kind: FileKind; - } - - const render = (element: Element, target: HTMLElement, disposables: IDisposable[]) => { + const render = (element: FileElement, target: HTMLElement, disposables: IDisposable[]) => { let label = this._instantiationService.createInstance(FileLabel, target, {}); label.setFile(element.uri, { fileKind: element.kind, hidePath: true }); disposables.push(label); }; - let items: RenderedBreadcrumbsItem[] = []; + let items: RenderedBreadcrumbsItem[] = []; let path = uri.path; while (path !== '/') { let first = items.length === 0; let name = posix.basename(path); uri = uri.with({ path }); path = posix.dirname(path); - items.unshift(new RenderedBreadcrumbsItem( + items.unshift(new RenderedBreadcrumbsItem( render, { name, uri, kind: first ? FileKind.FILE : FileKind.FOLDER }, !first @@ -136,8 +141,28 @@ export class EditorBreadcrumbs implements IEditorContribution { this._widget.breadcrumb.replace(undefined, items); } - private _onDidSelectItem(item: RenderedBreadcrumbsItem): void { - console.log(item); + private _onDidSelectItem(item: RenderedBreadcrumbsItem): void { + + this._contextViewService.showContextView({ + getAnchor() { + return item.node; + }, + render: (container: HTMLElement) => { + let res = this._instantiationService.createInstance(BreadcrumbsFilePicker, container); + res.layout({ width: 300, height: 450 }); + res.setInput(item.element.uri.with({ path: posix.dirname(item.element.uri.path) })); + res.onDidPickElement(data => { + this._contextViewService.hideContextView(); + if (!data) { + return; + } + if (URI.isUri(data)) { + this._editorService.openEditor({ resource: data }); + } + }); + return res; + }, + }); } focus(): void { diff --git a/src/vs/workbench/services/group/common/editorGroupsService.ts b/src/vs/workbench/services/group/common/editorGroupsService.ts index 138849ffa8e..7df05d47a0a 100644 --- a/src/vs/workbench/services/group/common/editorGroupsService.ts +++ b/src/vs/workbench/services/group/common/editorGroupsService.ts @@ -312,6 +312,13 @@ export interface IGroupChangeEvent { editorIndex?: number; } +export interface IEditorBreadcrumbs { + focus(): void; + focusNext(): void; + focusPrev(): void; + select(): void; +} + export interface IEditorGroup { /** @@ -332,6 +339,11 @@ export interface IEditorGroup { */ readonly label: string; + /** + * + */ + readonly breadcrumbs: IEditorBreadcrumbs; + /** * The active control is the currently visible control of the group. */ @@ -476,4 +488,4 @@ export interface IEditorGroup { * Invoke a function in the context of the services of this group. */ invokeWithinContext(fn: (accessor: ServicesAccessor) => T): T; -} \ No newline at end of file +} diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts index 34cd6c0f235..4bb1eda080e 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.main.ts @@ -94,8 +94,6 @@ import 'vs/workbench/electron-browser/workbench'; import 'vs/workbench/parts/relauncher/electron-browser/relauncher.contribution'; -import 'vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbs.contribution'; - import 'vs/workbench/parts/tasks/electron-browser/task.contribution'; import 'vs/workbench/parts/emmet/browser/emmet.browser.contribution'; From 12a5327e67a5b0eec839e8142716b9dce47d271f Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 14 Jun 2018 16:32:30 +0200 Subject: [PATCH 025/283] remove parts/breadcrumbs --- .../breadcrumbs.contribution.ts | 74 ------- .../electron-browser/breadcrumbsPicker.ts | 144 ------------- .../electron-browser/editorBreadcrumbs.ts | 190 ------------------ 3 files changed, 408 deletions(-) delete mode 100644 src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbs.contribution.ts delete mode 100644 src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsPicker.ts delete mode 100644 src/vs/workbench/parts/breadcrumbs/electron-browser/editorBreadcrumbs.ts diff --git a/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbs.contribution.ts b/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbs.contribution.ts deleted file mode 100644 index 8757b516def..00000000000 --- a/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbs.contribution.ts +++ /dev/null @@ -1,74 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { registerEditorContribution, EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions'; -import { EditorBreadcrumbs } from 'vs/workbench/parts/breadcrumbs/electron-browser/editorBreadcrumbs'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; - -registerEditorContribution(EditorBreadcrumbs); - - -const BreadcrumbCommandCtor = EditorCommand.bindToContribution(EditorBreadcrumbs.get); - -registerEditorCommand(new BreadcrumbCommandCtor({ - id: 'breadcrumbs.focus', - precondition: undefined, - handler: x => x.focus(), - kbOpts: { - weight: KeybindingsRegistry.WEIGHT.editorContrib(50), - kbExpr: EditorContextKeys.focus, - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_DOT - } -})); - -registerEditorCommand(new BreadcrumbCommandCtor({ - id: 'breadcrumbs.focusNext', - precondition: undefined, - handler: x => x.focusNext(), - kbOpts: { - weight: KeybindingsRegistry.WEIGHT.editorContrib(50), - kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, EditorBreadcrumbs.CK_Focused), - primary: KeyCode.RightArrow - } -})); - -registerEditorCommand(new BreadcrumbCommandCtor({ - id: 'breadcrumbs.focusPrev', - precondition: undefined, - handler: x => x.focusPrev(), - kbOpts: { - weight: KeybindingsRegistry.WEIGHT.editorContrib(50), - kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, EditorBreadcrumbs.CK_Focused), - primary: KeyCode.LeftArrow - } -})); - -registerEditorCommand(new BreadcrumbCommandCtor({ - id: 'breadcrumbs.select', - precondition: undefined, - handler: x => x.select(), - kbOpts: { - weight: KeybindingsRegistry.WEIGHT.editorContrib(50), - kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, EditorBreadcrumbs.CK_Focused), - primary: KeyCode.Enter, - secondary: [KeyCode.UpArrow, KeyCode.Space] - } -})); - -registerEditorCommand(new BreadcrumbCommandCtor({ - id: 'breadcrumbs.focusEditor', - precondition: undefined, - handler: x => x.editor.focus(), - kbOpts: { - weight: KeybindingsRegistry.WEIGHT.editorContrib(50), - kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, EditorBreadcrumbs.CK_Focused), - primary: KeyCode.Escape - } -})); diff --git a/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsPicker.ts b/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsPicker.ts deleted file mode 100644 index 4deeeb91658..00000000000 --- a/src/vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsPicker.ts +++ /dev/null @@ -1,144 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import { Dimension, trackFocus, IFocusTracker } from 'vs/base/browser/dom'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { ITreeConfiguration, IDataSource, ITree, IRenderer, ISelectionEvent } from 'vs/base/parts/tree/browser/tree'; -import { WorkbenchTree } from 'vs/platform/list/browser/listService'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { TPromise } from 'vs/base/common/winjs.base'; -import { IFileService, IFileStat, FileKind } from 'vs/platform/files/common/files'; -import { FileLabel } from 'vs/workbench/browser/labels'; -import URI from 'vs/base/common/uri'; -import { Emitter, Event, debounceEvent } from 'vs/base/common/event'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; - -export abstract class BreadcrumbsPicker { - - readonly focus: IFocusTracker; - - protected readonly _onDidPickElement = new Emitter(); - readonly onDidPickElement: Event = this._onDidPickElement.event; - - protected readonly _disposables = new Array(); - protected readonly _domNode: HTMLDivElement; - protected readonly _tree: WorkbenchTree; - - constructor( - container: HTMLElement, - @IInstantiationService protected readonly _instantiationService: IInstantiationService, - @IThemeService protected readonly _themeService: IThemeService, - ) { - this._domNode = document.createElement('div'); - // this._domNode.style.background = this._themeService.getTheme().getColor(colors.progressBarBackground).toString(); - container.appendChild(this._domNode); - - this._tree = this._instantiationService.createInstance(WorkbenchTree, this._domNode, this._completeTreeConfiguration({ dataSource: undefined }), {}); - debounceEvent(this._tree.onDidChangeSelection, (last, cur) => cur, 0)(this._onDidChangeSelection, this, this._disposables); - - this.focus = trackFocus(this._domNode); - this.focus.onDidBlur(_ => this._onDidPickElement.fire(undefined), undefined, this._disposables); - } - - dispose(): void { - dispose(this._disposables); - this._onDidPickElement.dispose(); - this._tree.dispose(); - this.focus.dispose(); - } - - layout(dim: Dimension) { - this._domNode.style.width = `${dim.width}px`; - this._domNode.style.height = `${dim.height}px`; - this._tree.layout(dim.height, dim.width); - } - - protected abstract _completeTreeConfiguration(config: ITreeConfiguration): ITreeConfiguration; - protected abstract _onDidChangeSelection(e: any): void; -} - -export class FileDataSource implements IDataSource { - - private readonly _parents = new WeakMap(); - - constructor( - @IFileService private readonly _fileService: IFileService, - ) { } - - getId(tree: ITree, element: IFileStat | URI): string { - return URI.isUri(element) ? element.toString() : element.resource.toString(); - } - - hasChildren(tree: ITree, element: IFileStat | URI): boolean { - return URI.isUri(element) || element.isDirectory; - } - - getChildren(tree: ITree, element: IFileStat | URI): TPromise { - return this._fileService.resolveFile( - URI.isUri(element) ? element : element.resource - ).then(stat => { - for (const child of stat.children) { - this._parents.set(child, stat); - } - return stat.children; - }); - } - - getParent(tree: ITree, element: IFileStat | URI): TPromise { - return TPromise.as(URI.isUri(element) ? undefined : this._parents.get(element)); - } -} - -export class FileRenderer implements IRenderer { - - constructor( - @IInstantiationService private readonly _instantiationService: IInstantiationService, - ) { } - - getHeight(tree: ITree, element: any): number { - return 22; - } - - getTemplateId(tree: ITree, element: any): string { - return 'FileStat'; - } - - renderTemplate(tree: ITree, templateId: string, container: HTMLElement) { - return this._instantiationService.createInstance(FileLabel, container, {}); - } - - renderElement(tree: ITree, element: IFileStat, templateId: string, templateData: FileLabel): void { - templateData.setFile(element.resource, { hidePath: true, fileKind: element.isDirectory ? FileKind.FOLDER : FileKind.FILE }); - } - - disposeTemplate(tree: ITree, templateId: string, templateData: FileLabel): void { - templateData.dispose(); - } -} - -export class BreadcrumbsFilePicker extends BreadcrumbsPicker { - - - protected _completeTreeConfiguration(config: ITreeConfiguration): ITreeConfiguration { - config.dataSource = this._instantiationService.createInstance(FileDataSource); - config.renderer = this._instantiationService.createInstance(FileRenderer); - return config; - } - - setInput(resource: URI): void { - this._tree.domFocus(); - this._tree.setInput(resource); - } - - protected _onDidChangeSelection(e: ISelectionEvent): void { - let [first] = e.selection; - let stat = first as IFileStat; - if (stat && !stat.isDirectory) { - this._onDidPickElement.fire(stat.resource); - } - } -} diff --git a/src/vs/workbench/parts/breadcrumbs/electron-browser/editorBreadcrumbs.ts b/src/vs/workbench/parts/breadcrumbs/electron-browser/editorBreadcrumbs.ts deleted file mode 100644 index a45d920e4a9..00000000000 --- a/src/vs/workbench/parts/breadcrumbs/electron-browser/editorBreadcrumbs.ts +++ /dev/null @@ -1,190 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - - -import { IEditorContribution } from 'vs/editor/common/editorCommon'; -import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser'; -import { BreadcrumbsWidget, RenderedBreadcrumbsItem } from 'vs/base/browser/ui/breadcrumbs/breadcrumbsWidget'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { Event, Emitter } from 'vs/base/common/event'; -import URI from 'vs/base/common/uri'; -import { posix } from 'path'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { FileKind } from 'vs/platform/files/common/files'; -import { FileLabel } from 'vs/workbench/browser/labels'; -import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { BreadcrumbsFilePicker } from 'vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsPicker'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; - -class Widget implements IOverlayWidget { - - breadcrumb: BreadcrumbsWidget; - ready: boolean; - - constructor(private _onFirstRender: Function) { - - } - - getId(): string { - return 'EditorBreadcrumbs.Widget'; - } - - getDomNode(): HTMLElement { - setTimeout(() => this._onFirstRender()); - let container = document.createElement('div'); - container.style.backgroundColor = 'white'; - let widget = new BreadcrumbsWidget(container); - this.breadcrumb = widget; - this.ready = true; - return container; - } - - getPosition(): IOverlayWidgetPosition { - return { - preference: OverlayWidgetPositionPreference.BOTTOM_RIGHT_CORNER - }; - } -} - -class BreadcrumbsUpdateEvent { - - private readonly _disposables = new Array(); - private readonly _emitter = new Emitter(); - - readonly event: Event = this._emitter.event; - - constructor(private readonly _editor: ICodeEditor) { - this._disposables.push(this._editor.onDidChangeModel(_ => this._emitter.fire())); - } -} - -interface FileElement { - name: string; - uri: URI; - kind: FileKind; -} - -export class EditorBreadcrumbs implements IEditorContribution { - - static CK_Focused = new RawContextKey('breadcrumbFocused', false); - - static get(editor: ICodeEditor): EditorBreadcrumbs { - return editor.getContribution('EditorBreadcrumbs'); - } - - private readonly _disposables = new Array(); - private readonly _widget: Widget; - private readonly _update: BreadcrumbsUpdateEvent; - - private _ckFocused: IContextKey; - - constructor( - readonly editor: ICodeEditor, - @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IContextKeyService private readonly _contextKeyService: IContextKeyService, - @IContextViewService private readonly _contextViewService: IContextViewService, - @IEditorService private readonly _editorService: IEditorService, - ) { - this._widget = new Widget(() => this._onWidgetReady()); - this._update = new BreadcrumbsUpdateEvent(this.editor); - this.editor.addOverlayWidget(this._widget); - } - - getId(): string { - return 'EditorBreadcrumbs'; - } - - dispose(): void { - dispose(this._disposables); - this.editor.removeOverlayWidget(this._widget); - this._ckFocused.reset(); - } - - private _onWidgetReady(): void { - this._ckFocused = EditorBreadcrumbs.CK_Focused.bindTo(this._contextKeyService); - this._disposables.push(this._widget.breadcrumb.onDidChangeFocus(value => this._ckFocused.set(value))); - this._disposables.push(this._widget.breadcrumb.onDidSelectItem(this._onDidSelectItem, this)); - this._update.event(this._onUpdate, this, this._disposables); - this._onUpdate(); - } - - private _onUpdate(): void { - if (!this._widget.ready || !this.editor.getModel()) { - return; - } - let { uri } = this.editor.getModel(); - - const render = (element: FileElement, target: HTMLElement, disposables: IDisposable[]) => { - let label = this._instantiationService.createInstance(FileLabel, target, {}); - label.setFile(element.uri, { fileKind: element.kind, hidePath: true }); - disposables.push(label); - }; - - let items: RenderedBreadcrumbsItem[] = []; - let path = uri.path; - while (path !== '/') { - let first = items.length === 0; - let name = posix.basename(path); - uri = uri.with({ path }); - path = posix.dirname(path); - items.unshift(new RenderedBreadcrumbsItem( - render, - { name, uri, kind: first ? FileKind.FILE : FileKind.FOLDER }, - !first - )); - } - - this._widget.breadcrumb.replace(undefined, items); - } - - private _onDidSelectItem(item: RenderedBreadcrumbsItem): void { - - this._contextViewService.showContextView({ - getAnchor() { - return item.node; - }, - render: (container: HTMLElement) => { - let res = this._instantiationService.createInstance(BreadcrumbsFilePicker, container); - res.layout({ width: 300, height: 450 }); - res.setInput(item.element.uri.with({ path: posix.dirname(item.element.uri.path) })); - res.onDidPickElement(data => { - this._contextViewService.hideContextView(); - if (!data) { - return; - } - if (URI.isUri(data)) { - this._editorService.openEditor({ resource: data }); - } - }); - return res; - }, - }); - } - - focus(): void { - this._widget.breadcrumb.focus(); - } - - focusNext(): void { - this._widget.breadcrumb.focusNext(); - } - - focusPrev(): void { - this._widget.breadcrumb.focusPrev(); - } - - select(): void { - this._widget.breadcrumb.select(); - } - - // saveViewState?() { - // throw new Error('Method not implemented.'); - // } - // restoreViewState?(state: any): void { - // throw new Error('Method not implemented.'); - // } -} From 7b12b499055bcb078887cd5a1f71b10a94bf8869 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 14 Jun 2018 16:33:36 +0200 Subject: [PATCH 026/283] fix compile --- src/vs/workbench/test/workbenchTestServices.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index ce5053d10fe..6c4ba8d0cb5 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -67,7 +67,7 @@ import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensi import { IKeybindingService } from '../../platform/keybinding/common/keybinding'; import { IDecorationsService, IResourceDecorationChangeEvent, IDecoration, IDecorationData, IDecorationsProvider } from 'vs/workbench/services/decorations/browser/decorations'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IEditorGroupsService, IEditorGroup, GroupsOrder, GroupsArrangement, GroupDirection, IAddGroupOptions, IMergeGroupOptions, IMoveEditorOptions, ICopyEditorOptions, IEditorReplacement, IGroupChangeEvent, EditorsOrder, IFindGroupScope, EditorGroupLayout } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService, IEditorGroup, GroupsOrder, GroupsArrangement, GroupDirection, IAddGroupOptions, IMergeGroupOptions, IMoveEditorOptions, ICopyEditorOptions, IEditorReplacement, IGroupChangeEvent, EditorsOrder, IFindGroupScope, EditorGroupLayout, IEditorBreadcrumbs } from 'vs/workbench/services/group/common/editorGroupsService'; import { IEditorService, IOpenEditorOverrideHandler } from 'vs/workbench/services/editor/common/editorService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; @@ -577,6 +577,7 @@ export class TestEditorGroup implements IEditorGroupView { constructor(public id: number) { } group: EditorGroup = void 0; + breadcrumbs: IEditorBreadcrumbs; activeControl: IEditor; activeEditor: IEditorInput; previewEditor: IEditorInput; From a3660bf2d96bdd12a07ad1ac8700dea7f38995ec Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 14 Jun 2018 18:09:18 +0200 Subject: [PATCH 027/283] enable icons --- src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts index 72f5ee5c617..687de6d3daa 100644 --- a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts @@ -57,7 +57,7 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { this._domNode = document.createElement('div'); - this._domNode.className = 'editor-breadcrumbs'; + dom.addClasses(this._domNode, 'editor-breadcrumbs', 'show-file-icons'); this._widget = new BreadcrumbsWidget(this._domNode); this._widget.onDidSelectItem(this._onDidSelectItem, this, this._disposables); this._widget.onDidChangeFocus(val => this._ckBreadcrumbsFocused.set(val), undefined, this._disposables); @@ -150,6 +150,7 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { return item.node; }, render: (container: HTMLElement) => { + dom.addClasses(container, 'show-file-icons'); let res = this._instantiationService.createInstance(BreadcrumbsFilePicker, container); res.layout({ width: 250, height: 300 }); res.setInput(item.element.uri.with({ path: paths.dirname(item.element.uri.path) })); From ba50059fcace300396ba3b44c5774ce462b308ce Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 14 Jun 2018 18:09:28 +0200 Subject: [PATCH 028/283] remove separator --- src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css index f1556526d68..51dd57c694c 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css @@ -19,7 +19,3 @@ cursor: pointer; align-self: center; } - -.monaco-breadcrumbs .monaco-breadcrumb-item-more { - border-right: 1px solid; -} From 801ac81ce6d5bc1d0e1dbd89475cec34d4d90024 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 14 Jun 2018 18:13:08 +0200 Subject: [PATCH 029/283] fix focus issue --- src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index e83364656e6..ceb9c4c3438 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -125,7 +125,7 @@ export class BreadcrumbsWidget { } private _focus(nth: number): boolean { - if (this._focusedItemIdx !== -1) { + if (this._focusedItemIdx >= 0 && this._focusedItemIdx < this._nodes.length) { dom.removeClass(this._nodes[this._focusedItemIdx], 'focused'); this._focusedItemIdx = -1; } From f01aa6eb19450bd978d5aadb87d2661eb82157b0 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 14 Jun 2018 18:25:55 +0200 Subject: [PATCH 030/283] add picker brackground color --- src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts index 687de6d3daa..39f93bfd6c6 100644 --- a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts @@ -28,6 +28,7 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { isEqual } from 'vs/base/common/resources'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; interface FileElement { name: string; @@ -186,7 +187,7 @@ export abstract class BreadcrumbsPicker { @IThemeService protected readonly _themeService: IThemeService, ) { this._domNode = document.createElement('div'); - // this._domNode.style.background = this._themeService.getTheme().getColor(colors.progressBarBackground).toString(); + this._domNode.style.background = this._themeService.getTheme().getColor(SIDE_BAR_BACKGROUND).toString(); container.appendChild(this._domNode); this._tree = this._instantiationService.createInstance(WorkbenchTree, this._domNode, this._completeTreeConfiguration({ dataSource: undefined }), {}); From 6992f948e66fc87cd713b6d1c83b3cd0ebac73b1 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 14 Jun 2018 18:43:29 +0200 Subject: [PATCH 031/283] focus group when selecting breadcrumbs --- src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts | 8 +++++--- .../workbench/browser/parts/editor/editorBreadcrumbs.ts | 6 +++++- src/vs/workbench/browser/parts/editor/editorGroupView.ts | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index ceb9c4c3438..bbb9aa1abf2 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -60,9 +60,11 @@ export class BreadcrumbsWidget { private readonly _scrollable: DomScrollableElement; private readonly _onDidSelectItem = new Emitter(); + private readonly _onDidFocusItem = new Emitter(); private readonly _onDidChangeFocus = new Emitter(); readonly onDidSelectItem: Event = this._onDidSelectItem.event; + readonly onDidFocusItem: Event = this._onDidFocusItem.event; readonly onDidChangeFocus: Event = this._onDidChangeFocus.event; private readonly _items = new Array(); @@ -133,9 +135,9 @@ export class BreadcrumbsWidget { return false; } this._focusedItemIdx = nth; - let node = this._nodes[this._focusedItemIdx]; - dom.addClass(node, 'focused'); - this._scrollable.setScrollPosition({ scrollLeft: node.offsetLeft }); + dom.addClass(this._nodes[this._focusedItemIdx], 'focused'); + this._scrollable.setScrollPosition({ scrollLeft: this._nodes[this._focusedItemIdx].offsetLeft }); + this._onDidFocusItem.fire(this._items[this._focusedItemIdx]); return true; } diff --git a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts index 39f93bfd6c6..f3d1bea4c7c 100644 --- a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts @@ -18,7 +18,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { FileLabel } from 'vs/workbench/browser/labels'; import { EditorInput } from 'vs/workbench/common/editor'; -import { IEditorBreadcrumbs, IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorBreadcrumbs, IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService'; import { ITreeConfiguration, IDataSource, ITree, IRenderer, ISelectionEvent } from 'vs/base/parts/tree/browser/tree'; import { TPromise } from 'vs/base/common/winjs.base'; import { WorkbenchTree } from 'vs/platform/list/browser/listService'; @@ -50,6 +50,7 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { constructor( container: HTMLElement, + private readonly _editorGroup: IEditorGroup, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IFileService private readonly _fileService: IFileService, @IContextViewService private readonly _contextViewService: IContextViewService, @@ -146,6 +147,9 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { } private _onDidSelectItem(item: RenderedBreadcrumbsItem): void { + + this._editorGroup.focus(); + this._contextViewService.showContextView({ getAnchor() { return item.node; diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 97615f52698..ce9f5dbf209 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -415,7 +415,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { clearNode(this.breadcrumbsContainer); } - this.breadcrumbsControl = this.scopedInstantiationService.createInstance(EditorBreadcrumbs, this.breadcrumbsContainer); + this.breadcrumbsControl = this.scopedInstantiationService.createInstance(EditorBreadcrumbs, this.breadcrumbsContainer, this); } private restoreEditors(from: IEditorGroupView | ISerializedEditorGroup): TPromise { From d4af61a17773d0433777a5dce42d53b688f4cc5b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 15 Jun 2018 10:28:24 +0200 Subject: [PATCH 032/283] support styles in breadcrumb widget --- .../ui/breadcrumbs/breadcrumbsWidget.css | 10 ++- .../ui/breadcrumbs/breadcrumbsWidget.ts | 73 +++++++++++++++++-- src/vs/platform/theme/common/styler.ts | 32 +++++++- .../workbench/browser/parts/editor/editor.ts | 2 +- .../browser/parts/editor/editorBreadcrumbs.ts | 11 ++- .../browser/parts/editor/editorGroupView.ts | 6 +- .../parts/editor/media/editorbreadcrumbs.css | 8 +- 7 files changed, 122 insertions(+), 20 deletions(-) diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css index 51dd57c694c..be772fca1bb 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css @@ -12,10 +12,16 @@ } .monaco-breadcrumbs .monaco-breadcrumb-item { - display: inline-block; - padding: 0 5px 0 5px; + display: flex; + align-items: center; + padding: 3px; flex: 0 1 auto; white-space: nowrap; cursor: pointer; align-self: center; + height: 100%; +} + +.monaco-breadcrumbs .monaco-breadcrumb-item:nth-child(2) { /*first-child is the style-element*/ + padding-left: 8px; } diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index bbb9aa1abf2..564a4cd67bf 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -12,6 +12,7 @@ import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { Event, Emitter } from 'vs/base/common/event'; +import { Color } from 'vs/base/common/color'; export class BreadcrumbsItem { @@ -53,10 +54,23 @@ export class RenderedBreadcrumbsItem extends BreadcrumbsItem { } } +export interface IBreadcrumbsWidgetStyles { + breadcrumbsBackground?: Color; + breadcrumbsItemHoverBackground?: Color; + breadcrumbsItemHoverForeground?: Color; + breadcrumbsItemFocusBackground?: Color; + breadcrumbsItemFocusForeground?: Color; + breadcrumbsActiveItemSelectionBackground?: Color; + breadcrumbsActiveItemSelectionForeground?: Color; + breadcrumbsInactiveItemSelectionBackground?: Color; + breadcrumbsInactiveItemSelectionForeground?: Color; +} + export class BreadcrumbsWidget { private readonly _disposables = new Array(); private readonly _domNode: HTMLDivElement; + private readonly _styleElement: HTMLStyleElement; private readonly _scrollable: DomScrollableElement; private readonly _onDidSelectItem = new Emitter(); @@ -70,7 +84,9 @@ export class BreadcrumbsWidget { private readonly _items = new Array(); private readonly _nodes = new Array(); private readonly _freeNodes = new Array(); + private _focusedItemIdx: number = -1; + private _selectedItemIdx: number = -1; constructor( container: HTMLElement @@ -88,6 +104,8 @@ export class BreadcrumbsWidget { this._disposables.push(dom.addStandardDisposableListener(this._domNode, 'click', e => this._onClick(e))); container.appendChild(this._scrollable.getDomNode()); + this._styleElement = dom.createStyleSheet(this._domNode); + let focusTracker = dom.trackFocus(this._domNode); this._disposables.push(focusTracker); this._disposables.push(focusTracker.onDidBlur(_ => this._onDidChangeFocus.fire(false))); @@ -112,6 +130,37 @@ export class BreadcrumbsWidget { } } + style(style: IBreadcrumbsWidgetStyles): void { + let content = ''; + if (style.breadcrumbsItemFocusForeground) { + content += `.monaco-breadcrumbs:focus .monaco-breadcrumb-item.focused { color: ${style.breadcrumbsItemFocusForeground}}\n`; + } + if (style.breadcrumbsItemFocusBackground) { + content += `.monaco-breadcrumbs:focus .monaco-breadcrumb-item.focused { background-color: ${style.breadcrumbsItemFocusBackground}}\n`; + } + if (style.breadcrumbsItemFocusForeground) { + content += `.monaco-breadcrumbs .monaco-breadcrumb-item:hover { color: ${style.breadcrumbsItemHoverForeground}}\n`; + } + if (style.breadcrumbsItemFocusBackground) { + content += `.monaco-breadcrumbs .monaco-breadcrumb-item:hover { background-color: ${style.breadcrumbsItemHoverBackground}}\n`; + } + if (style.breadcrumbsActiveItemSelectionForeground) { + content += `.monaco-breadcrumbs:hover .monaco-breadcrumb-item.selected { color: ${style.breadcrumbsActiveItemSelectionForeground}}\n`; + } + if (style.breadcrumbsActiveItemSelectionBackground) { + content += `.monaco-breadcrumbs:hover .monaco-breadcrumb-item.selected { background-color: ${style.breadcrumbsActiveItemSelectionBackground}}\n`; + } + if (style.breadcrumbsInactiveItemSelectionForeground) { + content += `.monaco-breadcrumbs .monaco-breadcrumb-item.selected { color: ${style.breadcrumbsInactiveItemSelectionForeground}}\n`; + } + if (style.breadcrumbsInactiveItemSelectionBackground) { + content += `.monaco-breadcrumbs .monaco-breadcrumb-item.selected { background-color: ${style.breadcrumbsInactiveItemSelectionBackground}}\n`; + } + if (this._styleElement.innerHTML !== content) { + this._styleElement.innerHTML = content; + } + } + focus(): void { this._domNode.focus(); } @@ -141,11 +190,25 @@ export class BreadcrumbsWidget { return true; } - select(): void { - if (this._focusedItemIdx !== -1) { - let item = this._items[this._focusedItemIdx]; - this._onDidSelectItem.fire(item); + getFocusedItem(): BreadcrumbsItem { + return this._items[this._focusedItemIdx]; + } + + select(item: BreadcrumbsItem): void { + this._select(this._items.indexOf(item)); + } + + private _select(nth: number): void { + if (this._selectedItemIdx >= 0 && this._selectedItemIdx < this._nodes.length) { + dom.removeClass(this._nodes[this._selectedItemIdx], 'selected'); + this._selectedItemIdx = -1; } + if (nth < 0 || nth >= this._nodes.length) { + return; + } + this._selectedItemIdx = nth; + dom.addClass(this._nodes[this._selectedItemIdx], 'selected'); + this._onDidSelectItem.fire(this._items[this._selectedItemIdx]); } append(item: BreadcrumbsItem): void { @@ -197,7 +260,7 @@ export class BreadcrumbsWidget { let idx = this._nodes.indexOf(el as any); if (idx >= 0) { this._focus(idx); - this._onDidSelectItem.fire(this._items[idx]); + this._select(idx); break; } } diff --git a/src/vs/platform/theme/common/styler.ts b/src/vs/platform/theme/common/styler.ts index 07dfd1d9551..fe53923ce8e 100644 --- a/src/vs/platform/theme/common/styler.ts +++ b/src/vs/platform/theme/common/styler.ts @@ -261,4 +261,34 @@ export function attachProgressBarStyler(widget: IThemable, themeService: IThemeS export function attachStylerCallback(themeService: IThemeService, colors: { [name: string]: ColorIdentifier }, callback: styleFn): IDisposable { return attachStyler(themeService, colors, callback); -} \ No newline at end of file +} + +export interface IBreadcrumbsWidgetStyleOverrides extends IStyleOverrides { + breadcrumbsBackground?: ColorIdentifier; + breadcrumbsItemHoverBackground?: ColorIdentifier; + breadcrumbsItemHoverForeground?: ColorIdentifier; + breadcrumbsItemFocusBackground?: ColorIdentifier; + breadcrumbsItemFocusForeground?: ColorIdentifier; + breadcrumbsActiveItemSelectionBackground?: ColorIdentifier; + breadcrumbsActiveItemSelectionForeground?: ColorIdentifier; + breadcrumbsInactiveItemSelectionBackground?: ColorIdentifier; + breadcrumbsInactiveItemSelectionForeground?: ColorIdentifier; +} + +export const defaultBreadcrumbsStyles = { + breadcrumbsBackground: editorBackground, + breadcrumbsItemHoverBackground: listHoverBackground, + breadcrumbsItemHoverForeground: listHoverForeground, + breadcrumbsItemFocusBackground: listFocusBackground, + breadcrumbsItemFocusForeground: listFocusForeground, + breadcrumbsItemSelectionBackground: listActiveSelectionBackground, + breadcrumbsItemSelectionForeground: listActiveSelectionForeground, + breadcrumbsActiveItemSelectionBackground: listActiveSelectionBackground, + breadcrumbsActiveItemSelectionForeground: listActiveSelectionForeground, + breadcrumbsInactiveItemSelectionBackground: listInactiveSelectionBackground, + breadcrumbsInactiveItemSelectionForeground: listInactiveSelectionForeground, +}; + +export function attachBreadcrumbsStyler(widget: IThemable, themeService: IThemeService, style?: IBreadcrumbsWidgetStyleOverrides): IDisposable { + return attachStyler(themeService, { ...defaultBreadcrumbsStyles, ...style }, widget); +} diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index f9b0c858a38..78cb9a535ad 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -20,7 +20,7 @@ import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export const EDITOR_TITLE_HEIGHT = 35; -export const BREAD_CRUMPS_HEIGHT = 30; +export const BREAD_CRUMBS_HEIGHT = 25; export const EDITOR_MIN_DIMENSIONS = new Dimension(220, 70); export const EDITOR_MAX_DIMENSIONS = new Dimension(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY); diff --git a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts index f3d1bea4c7c..e364774c27f 100644 --- a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts @@ -29,6 +29,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { isEqual } from 'vs/base/common/resources'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { attachBreadcrumbsStyler } from 'vs/platform/theme/common/styler'; interface FileElement { name: string; @@ -57,13 +58,16 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { @IEditorService private readonly _editorService: IEditorService, @IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService, @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IThemeService private readonly _themeService: IThemeService, ) { this._domNode = document.createElement('div'); dom.addClasses(this._domNode, 'editor-breadcrumbs', 'show-file-icons'); + dom.append(container, this._domNode); + this._widget = new BreadcrumbsWidget(this._domNode); this._widget.onDidSelectItem(this._onDidSelectItem, this, this._disposables); this._widget.onDidChangeFocus(val => this._ckBreadcrumbsFocused.set(val), undefined, this._disposables); - container.appendChild(this._domNode); + this._disposables.push(attachBreadcrumbsStyler(this._widget, this._themeService)); this._ckBreadcrumbsVisible = EditorBreadcrumbs.CK_BreadcrumbsVisible.bindTo(this._contextKeyService); this._ckBreadcrumbsFocused = EditorBreadcrumbs.CK_BreadcrumbsFocused.bindTo(this._contextKeyService); @@ -143,7 +147,10 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { } select(): void { - this._widget.select(); + const item = this._widget.getFocusedItem(); + if (item) { + this._widget.select(item); + } } private _onDidSelectItem(item: RenderedBreadcrumbsItem): void { diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index ce9f5dbf209..4588fb166ab 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -34,7 +34,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { RunOnceWorker } from 'vs/base/common/async'; import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch'; import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl'; -import { IEditorGroupsAccessor, IEditorGroupView, IEditorPartOptionsChangeEvent, EDITOR_TITLE_HEIGHT, EDITOR_MIN_DIMENSIONS, EDITOR_MAX_DIMENSIONS, getActiveTextEditorOptions, IEditorOpeningEvent, BREAD_CRUMPS_HEIGHT } from 'vs/workbench/browser/parts/editor/editor'; +import { IEditorGroupsAccessor, IEditorGroupView, IEditorPartOptionsChangeEvent, EDITOR_TITLE_HEIGHT, EDITOR_MIN_DIMENSIONS, EDITOR_MAX_DIMENSIONS, getActiveTextEditorOptions, IEditorOpeningEvent, BREAD_CRUMBS_HEIGHT } from 'vs/workbench/browser/parts/editor/editor'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { join } from 'vs/base/common/paths'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -1373,8 +1373,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Forward to controls this.titleAreaControl.layout(new Dimension(this.dimension.width, EDITOR_TITLE_HEIGHT)); - this.breadcrumbsControl.layout(new Dimension(this.dimension.width, BREAD_CRUMPS_HEIGHT)); - this.editorControl.layout(new Dimension(this.dimension.width, this.dimension.height - (EDITOR_TITLE_HEIGHT + BREAD_CRUMPS_HEIGHT))); + this.breadcrumbsControl.layout(new Dimension(this.dimension.width, BREAD_CRUMBS_HEIGHT)); + this.editorControl.layout(new Dimension(this.dimension.width, this.dimension.height - (EDITOR_TITLE_HEIGHT + BREAD_CRUMBS_HEIGHT))); } toJSON(): ISerializedEditorGroup { diff --git a/src/vs/workbench/browser/parts/editor/media/editorbreadcrumbs.css b/src/vs/workbench/browser/parts/editor/media/editorbreadcrumbs.css index 36815a8d225..a141046f6bb 100644 --- a/src/vs/workbench/browser/parts/editor/media/editorbreadcrumbs.css +++ b/src/vs/workbench/browser/parts/editor/media/editorbreadcrumbs.css @@ -3,10 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench>.part.editor>.content .editor-breadcrumbs .monaco-breadcrumbs { - --color-item-focused: pink; -} - -.monaco-workbench>.part.editor>.content .editor-breadcrumbs .monaco-breadcrumbs:focus .monaco-breadcrumb-item.focused { - color: var(--color-item-focused); +.monaco-workbench>.part.editor>.content .editor-group-container:not(.active) .editor-breadcrumbs { + opacity: .8; } From d50438e6ed2ea2c6b1d0703e5690a35f5124c509 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 15 Jun 2018 10:38:45 +0200 Subject: [PATCH 033/283] tweak styles --- src/vs/platform/theme/common/styler.ts | 2 +- .../browser/parts/editor/editorBreadcrumbs.ts | 21 ++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/vs/platform/theme/common/styler.ts b/src/vs/platform/theme/common/styler.ts index fe53923ce8e..a14be6f3154 100644 --- a/src/vs/platform/theme/common/styler.ts +++ b/src/vs/platform/theme/common/styler.ts @@ -285,7 +285,7 @@ export const defaultBreadcrumbsStyles = { breadcrumbsItemSelectionForeground: listActiveSelectionForeground, breadcrumbsActiveItemSelectionBackground: listActiveSelectionBackground, breadcrumbsActiveItemSelectionForeground: listActiveSelectionForeground, - breadcrumbsInactiveItemSelectionBackground: listInactiveSelectionBackground, + breadcrumbsInactiveItemSelectionBackground: editorBackground, breadcrumbsInactiveItemSelectionForeground: listInactiveSelectionForeground, }; diff --git a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts index e364774c27f..b42df4763fe 100644 --- a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts @@ -102,7 +102,7 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { const render = (element: FileElement, target: HTMLElement, disposables: IDisposable[]) => { let label = this._instantiationService.createInstance(FileLabel, target, {}); - label.setFile(element.uri, { fileKind: element.kind, hidePath: true }); + label.setFile(element.uri, { fileKind: element.kind, hidePath: true, fileDecorations: { colors: false, badges: false } }); disposables.push(label); }; @@ -111,19 +111,21 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { let path = uri.path; while (true) { - if (workspace && isEqual(workspace.uri, uri, true) || path === '/') { - break; - } - let first = items.length === 0; let name = paths.basename(path); uri = uri.with({ path }); - path = paths.dirname(path); + if (workspace && isEqual(workspace.uri, uri, true)) { + break; + } items.unshift(new RenderedBreadcrumbsItem( render, { name, uri, kind: first ? FileKind.FILE : FileKind.FOLDER }, !first )); + path = paths.dirname(path); + if (path === '/') { + break; + } } this._widget.replace(undefined, items); @@ -168,6 +170,7 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { res.setInput(item.element.uri.with({ path: paths.dirname(item.element.uri.path) })); res.onDidPickElement(data => { this._contextViewService.hideContextView(); + this._widget.select(undefined); if (!data) { return; } @@ -276,7 +279,11 @@ export class FileRenderer implements IRenderer { } renderElement(tree: ITree, element: IFileStat, templateId: string, templateData: FileLabel): void { - templateData.setFile(element.resource, { hidePath: true, fileKind: element.isDirectory ? FileKind.FOLDER : FileKind.FILE }); + templateData.setFile(element.resource, { + hidePath: true, + fileKind: element.isDirectory ? FileKind.FOLDER : FileKind.FILE, + fileDecorations: { colors: true, badges: true } + }); } disposeTemplate(tree: ITree, templateId: string, templateData: FileLabel): void { From af12e2617fcfecfdac10aa38ef0fc8e53e43847a Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 15 Jun 2018 10:46:58 +0200 Subject: [PATCH 034/283] add simple sorter --- .../browser/parts/editor/editorBreadcrumbs.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts index b42df4763fe..8a4673d45c7 100644 --- a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts @@ -19,7 +19,7 @@ import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRe import { FileLabel } from 'vs/workbench/browser/labels'; import { EditorInput } from 'vs/workbench/common/editor'; import { IEditorBreadcrumbs, IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService'; -import { ITreeConfiguration, IDataSource, ITree, IRenderer, ISelectionEvent } from 'vs/base/parts/tree/browser/tree'; +import { ITreeConfiguration, IDataSource, ITree, IRenderer, ISelectionEvent, ISorter } from 'vs/base/parts/tree/browser/tree'; import { TPromise } from 'vs/base/common/winjs.base'; import { WorkbenchTree } from 'vs/platform/list/browser/listService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -30,6 +30,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { isEqual } from 'vs/base/common/resources'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { attachBreadcrumbsStyler } from 'vs/platform/theme/common/styler'; +import { compareFileNames } from 'vs/base/common/comparers'; interface FileElement { name: string; @@ -291,12 +292,27 @@ export class FileRenderer implements IRenderer { } } +export class FileSorter implements ISorter { + compare(tree: ITree, a: IFileStat, b: IFileStat): number { + if (a.isDirectory === b.isDirectory) { + // same type -> compare on names + return compareFileNames(a.name, b.name); + } else if (a.isDirectory) { + return -1; + } else { + return 1; + } + } +} + export class BreadcrumbsFilePicker extends BreadcrumbsPicker { protected _completeTreeConfiguration(config: ITreeConfiguration): ITreeConfiguration { + // todo@joh reuse explorer implementations? config.dataSource = this._instantiationService.createInstance(FileDataSource); config.renderer = this._instantiationService.createInstance(FileRenderer); + config.sorter = new FileSorter(); return config; } From b4325d00ebb31d6a28a0f488d1a256f0f5c261a0 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 15 Jun 2018 10:50:59 +0200 Subject: [PATCH 035/283] fix item height --- src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css index be772fca1bb..0a6f4ced2c5 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css @@ -14,7 +14,7 @@ .monaco-breadcrumbs .monaco-breadcrumb-item { display: flex; align-items: center; - padding: 3px; + padding: 0 5px 0 3px; flex: 0 1 auto; white-space: nowrap; cursor: pointer; From f8f26b3ae57052eda70285b52793377891a875a4 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 15 Jun 2018 11:02:48 +0200 Subject: [PATCH 036/283] fix - set missing background style --- src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index 564a4cd67bf..52fb96f0223 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -132,6 +132,9 @@ export class BreadcrumbsWidget { style(style: IBreadcrumbsWidgetStyles): void { let content = ''; + if (style.breadcrumbsBackground) { + content += `.monaco-breadcrumbs { background-color: ${style.breadcrumbsBackground}}`; + } if (style.breadcrumbsItemFocusForeground) { content += `.monaco-breadcrumbs:focus .monaco-breadcrumb-item.focused { color: ${style.breadcrumbsItemFocusForeground}}\n`; } From db1eedb9c7928fd2a157fb3efd86ab23b069db90 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 15 Jun 2018 14:59:57 +0200 Subject: [PATCH 037/283] splice and editor tracking experiements --- .../ui/breadcrumbs/breadcrumbsWidget.ts | 10 +- .../browser/parts/editor/editorBreadcrumbs.ts | 91 +++++++++++++++++-- 2 files changed, 86 insertions(+), 15 deletions(-) diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index 52fb96f0223..e8d2e242514 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -214,14 +214,12 @@ export class BreadcrumbsWidget { this._onDidSelectItem.fire(this._items[this._selectedItemIdx]); } - append(item: BreadcrumbsItem): void { - this._items.push(item); - this._render(this._items.length - 1); + items(): ReadonlyArray { + return this._items; } - replace(existing: BreadcrumbsItem, newItems: BreadcrumbsItem[]): void { - let start = !existing ? 0 : this._items.indexOf(existing); - let removed = this._items.splice(start, this._items.length - start, ...newItems); + splice(start: number, deleteCount: number, items: BreadcrumbsItem[]): void { + let removed = this._items.splice(start, deleteCount, ...items); this._render(start); dispose(removed); } diff --git a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts index 8a4673d45c7..a87c49e86cc 100644 --- a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts @@ -19,7 +19,7 @@ import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRe import { FileLabel } from 'vs/workbench/browser/labels'; import { EditorInput } from 'vs/workbench/common/editor'; import { IEditorBreadcrumbs, IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService'; -import { ITreeConfiguration, IDataSource, ITree, IRenderer, ISelectionEvent, ISorter } from 'vs/base/parts/tree/browser/tree'; +import { ITreeConfiguration, IDataSource, IRenderer, ISelectionEvent, ISorter, ITree } from 'vs/base/parts/tree/browser/tree'; import { TPromise } from 'vs/base/common/winjs.base'; import { WorkbenchTree } from 'vs/platform/list/browser/listService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -31,6 +31,8 @@ import { isEqual } from 'vs/base/common/resources'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { attachBreadcrumbsStyler } from 'vs/platform/theme/common/styler'; import { compareFileNames } from 'vs/base/common/comparers'; +import { isCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { onUnexpectedError } from 'vs/base/common/errors'; interface FileElement { name: string; @@ -43,12 +45,14 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { static CK_BreadcrumbsVisible = new RawContextKey('breadcrumbsVisible', false); static CK_BreadcrumbsFocused = new RawContextKey('breadcrumbsFocused', false); + private readonly _ckBreadcrumbsVisible: IContextKey; + private readonly _ckBreadcrumbsFocused: IContextKey; + private readonly _disposables = new Array(); private readonly _domNode: HTMLDivElement; private readonly _widget: BreadcrumbsWidget; - private readonly _ckBreadcrumbsVisible: IContextKey; - private readonly _ckBreadcrumbsFocused: IContextKey; + private _editorDisposables = new Array(); constructor( container: HTMLElement, @@ -92,6 +96,9 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { openEditor(input: EditorInput): void { + this._editorDisposables = dispose(this._editorDisposables); + + let uri = input.getResource(); if (!uri || !this._fileService.canHandleResource(uri)) { return this.closeEditor(undefined); @@ -100,25 +107,24 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { this._ckBreadcrumbsVisible.set(true); dom.toggleClass(this._domNode, 'hidden', false); - const render = (element: FileElement, target: HTMLElement, disposables: IDisposable[]) => { let label = this._instantiationService.createInstance(FileLabel, target, {}); label.setFile(element.uri, { fileKind: element.kind, hidePath: true, fileDecorations: { colors: false, badges: false } }); disposables.push(label); }; - let items: RenderedBreadcrumbsItem[] = []; + let fileItems: RenderedBreadcrumbsItem[] = []; let workspace = this._workspaceService.getWorkspaceFolder(uri); let path = uri.path; while (true) { - let first = items.length === 0; + let first = fileItems.length === 0; let name = paths.basename(path); uri = uri.with({ path }); if (workspace && isEqual(workspace.uri, uri, true)) { break; } - items.unshift(new RenderedBreadcrumbsItem( + fileItems.unshift(new RenderedBreadcrumbsItem( render, { name, uri, kind: first ? FileKind.FILE : FileKind.FOLDER }, !first @@ -129,7 +135,70 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { } } - this._widget.replace(undefined, items); + this._widget.splice(0, undefined, fileItems); + + let control = this._editorGroup.activeControl.getControl() as ICodeEditor; + if (!isCodeEditor(control)) { + return; + } + + let editorWait = new TPromise(resolve => { + if (control.getModel() && control.getModel().uri.toString() === input.getResource().toString()) { + return resolve(true); + } + let listener = control.onDidChangeModel(e => { + if (e.newModelUrl.toString() === input.getResource().toString()) { + resolve(true); + listener.dispose(); + } + }); + this._editorDisposables.push({ + dispose: () => { + resolve(false); + listener.dispose(); + } + }); + }); + + editorWait.then(success => { + console.log(success, control.getModel().uri.path); + if (!success) { + return undefined; + } + // let request = OutlineModel.create(control.getModel()); + // let { promise } = asDisposablePromise(request, undefined, this._editorDisposables); + // return promise; + + // }).then(model => { + // if (!model) { + // console.log('canceled', control.getModel().uri.path); + // return; // canceled + // } + + // let showOutlineForPosition = (position: IPosition) => { + // console.log('show', model.textModel.uri.path); + // let element: OutlineElement | OutlineGroup = model.getItemEnclosingPosition(position); + // let outlineItems: SimpleBreadcrumbsItem[] = []; + // while (element) { + // if (element instanceof OutlineElement) { + // outlineItems.push(new SimpleBreadcrumbsItem(element.symbol.name)); + // } else { + // outlineItems.push(new SimpleBreadcrumbsItem(element.providerIndex.toString())); + // } + // if (element.parent instanceof OutlineElement || element.parent instanceof OutlineGroup) { + // element = element.parent; + // } else { + // element = undefined; + // } + // } + // outlineItems.reverse(); + // this._widget.splice(fileItems.length, this._widget.items().length - fileItems.length, outlineItems); + // }; + + // showOutlineForPosition(control.getPosition()); + // debounceEvent(control.onDidChangeCursorPosition, (last, cur) => cur, 100)(_ => showOutlineForPosition(control.getPosition()), undefined, this._editorDisposables); + + }, onUnexpectedError); } closeEditor(input: EditorInput): void { @@ -158,6 +227,10 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { private _onDidSelectItem(item: RenderedBreadcrumbsItem): void { + if (!(item instanceof RenderedBreadcrumbsItem)) { + return; + } + this._editorGroup.focus(); this._contextViewService.showContextView({ @@ -206,7 +279,7 @@ export abstract class BreadcrumbsPicker { container.appendChild(this._domNode); this._tree = this._instantiationService.createInstance(WorkbenchTree, this._domNode, this._completeTreeConfiguration({ dataSource: undefined }), {}); - debounceEvent(this._tree.onDidChangeSelection, (last, cur) => cur, 0)(this._onDidChangeSelection, this, this._disposables); + debounceEvent(this._tree.onDidChangeSelection, (_last, cur) => cur, 0)(this._onDidChangeSelection, this, this._disposables); this.focus = dom.trackFocus(this._domNode); this.focus.onDidBlur(_ => this._onDidPickElement.fire(undefined), undefined, this._disposables); From 925faa2f5506f5503fa97fa73327355eab0a0d20 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 15 Jun 2018 16:04:58 +0200 Subject: [PATCH 038/283] fix update of file elements --- src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts index a87c49e86cc..e144273796d 100644 --- a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts @@ -98,7 +98,6 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { this._editorDisposables = dispose(this._editorDisposables); - let uri = input.getResource(); if (!uri || !this._fileService.canHandleResource(uri)) { return this.closeEditor(undefined); @@ -135,7 +134,7 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { } } - this._widget.splice(0, undefined, fileItems); + this._widget.splice(0, this._widget.items().length, fileItems); let control = this._editorGroup.activeControl.getControl() as ICodeEditor; if (!isCodeEditor(control)) { From 1c7032fa2c1448b9166e8181cc198d8d4822e38b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 15 Jun 2018 16:41:00 +0200 Subject: [PATCH 039/283] outline element ftw --- .../browser/parts/editor/editorBreadcrumbs.ts | 114 +++++++++--------- 1 file changed, 59 insertions(+), 55 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts index e144273796d..abe74857ea7 100644 --- a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts @@ -32,7 +32,11 @@ import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { attachBreadcrumbsStyler } from 'vs/platform/theme/common/styler'; import { compareFileNames } from 'vs/base/common/comparers'; import { isCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { onUnexpectedError } from 'vs/base/common/errors'; +import { OutlineModel, OutlineGroup, OutlineElement, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; +import { asDisposablePromise } from 'vs/base/common/async'; +import { IPosition } from 'vs/editor/common/core/position'; +import { first } from 'vs/base/common/collections'; +import { DocumentSymbolProviderRegistry } from 'vs/editor/common/modes'; interface FileElement { name: string; @@ -141,63 +145,63 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { return; } - let editorWait = new TPromise(resolve => { - if (control.getModel() && control.getModel().uri.toString() === input.getResource().toString()) { - return resolve(true); + + let oracle = new class extends Emitter { + + private readonly _listener: IDisposable[] = []; + + constructor() { + super(); + DocumentSymbolProviderRegistry.onDidChange(_ => this.fire()); + this._listener.push(control.onDidChangeModel(_ => this._checkModel())); + this._listener.push(control.onDidChangeModelLanguage(_ => this._checkModel())); + this._checkModel(); } - let listener = control.onDidChangeModel(e => { - if (e.newModelUrl.toString() === input.getResource().toString()) { - resolve(true); - listener.dispose(); + + private _checkModel() { + if (control.getModel() && isEqual(control.getModel().uri, input.getResource())) { + this.fire(); } - }); - this._editorDisposables.push({ - dispose: () => { - resolve(false); - listener.dispose(); + } + + dispose(): void { + dispose(this._listener); + super.dispose(); + } + }; + + this._editorDisposables.push(oracle); + + oracle.event(async _ => { + let model = await asDisposablePromise(OutlineModel.create(control.getModel()), undefined, this._editorDisposables).promise; + if (!model) { + return; + } + type OutlineItem = OutlineElement | OutlineGroup; + + let render = (element: OutlineItem, target: HTMLElement, disposables: IDisposable[]) => { + let label = this._instantiationService.createInstance(FileLabel, target, {}); + if (element instanceof OutlineElement) { + label.setLabel({ name: element.symbol.name }); + } else { + label.setLabel({ name: element.provider.displayName }); } - }); + disposables.push(label); + }; + + let showOutlineForPosition = (position: IPosition) => { + let element = model.getItemEnclosingPosition(position) as TreeElement; + let outlineItems: RenderedBreadcrumbsItem[] = []; + while (element instanceof OutlineGroup || element instanceof OutlineElement) { + outlineItems.unshift(new RenderedBreadcrumbsItem(render, element, !!first(element.children))); + element = element.parent; + } + this._widget.splice(fileItems.length, this._widget.items().length - fileItems.length, outlineItems); + }; + + showOutlineForPosition(control.getPosition()); + debounceEvent(control.onDidChangeCursorPosition, (last, cur) => cur, 100)(_ => showOutlineForPosition(control.getPosition()), undefined, this._editorDisposables); }); - - editorWait.then(success => { - console.log(success, control.getModel().uri.path); - if (!success) { - return undefined; - } - // let request = OutlineModel.create(control.getModel()); - // let { promise } = asDisposablePromise(request, undefined, this._editorDisposables); - // return promise; - - // }).then(model => { - // if (!model) { - // console.log('canceled', control.getModel().uri.path); - // return; // canceled - // } - - // let showOutlineForPosition = (position: IPosition) => { - // console.log('show', model.textModel.uri.path); - // let element: OutlineElement | OutlineGroup = model.getItemEnclosingPosition(position); - // let outlineItems: SimpleBreadcrumbsItem[] = []; - // while (element) { - // if (element instanceof OutlineElement) { - // outlineItems.push(new SimpleBreadcrumbsItem(element.symbol.name)); - // } else { - // outlineItems.push(new SimpleBreadcrumbsItem(element.providerIndex.toString())); - // } - // if (element.parent instanceof OutlineElement || element.parent instanceof OutlineGroup) { - // element = element.parent; - // } else { - // element = undefined; - // } - // } - // outlineItems.reverse(); - // this._widget.splice(fileItems.length, this._widget.items().length - fileItems.length, outlineItems); - // }; - - // showOutlineForPosition(control.getPosition()); - // debounceEvent(control.onDidChangeCursorPosition, (last, cur) => cur, 100)(_ => showOutlineForPosition(control.getPosition()), undefined, this._editorDisposables); - - }, onUnexpectedError); } closeEditor(input: EditorInput): void { @@ -226,7 +230,7 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { private _onDidSelectItem(item: RenderedBreadcrumbsItem): void { - if (!(item instanceof RenderedBreadcrumbsItem)) { + if (item.element instanceof TreeElement) { return; } From 4276839d0119a0fe6091787403e621bbe0cf9204 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 15 Jun 2018 16:47:56 +0200 Subject: [PATCH 040/283] leave todo-tag --- src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts index abe74857ea7..4ef921c25c6 100644 --- a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts @@ -196,6 +196,7 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { outlineItems.unshift(new RenderedBreadcrumbsItem(render, element, !!first(element.children))); element = element.parent; } + // todo@joh compare items for equality and only update changed... this._widget.splice(fileItems.length, this._widget.items().length - fileItems.length, outlineItems); }; From 71464af4a1e16ffb40c0f20471efa838e3fbfaa1 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 18 Jun 2018 15:25:47 +0200 Subject: [PATCH 041/283] show outline elements in picker --- .../browser/parts/editor/editorBreadcrumbs.ts | 89 ++++++++++++++----- 1 file changed, 67 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts index 4ef921c25c6..037240bb87e 100644 --- a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts @@ -14,7 +14,7 @@ import * as paths from 'vs/base/common/paths'; import URI from 'vs/base/common/uri'; import { IContextKey, IContextKeyService, RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { FileKind, IFileService, IFileStat } from 'vs/platform/files/common/files'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, IConstructorSignature2 } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { FileLabel } from 'vs/workbench/browser/labels'; import { EditorInput } from 'vs/workbench/common/editor'; @@ -27,23 +27,29 @@ import { debounceEvent, Emitter, Event } from 'vs/base/common/event'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { isEqual } from 'vs/base/common/resources'; +import { isEqual, dirname } from 'vs/base/common/resources'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { attachBreadcrumbsStyler } from 'vs/platform/theme/common/styler'; import { compareFileNames } from 'vs/base/common/comparers'; import { isCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { OutlineModel, OutlineGroup, OutlineElement, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; -import { asDisposablePromise } from 'vs/base/common/async'; +import { asDisposablePromise, setDisposableTimeout } from 'vs/base/common/async'; import { IPosition } from 'vs/editor/common/core/position'; import { first } from 'vs/base/common/collections'; import { DocumentSymbolProviderRegistry } from 'vs/editor/common/modes'; +import { Range } from 'vs/editor/common/core/range'; +import { OutlineDataSource, OutlineRenderer, OutlineItemComparator, OutlineController } from 'vs/editor/contrib/documentSymbols/outlineTree'; -interface FileElement { - name: string; - uri: URI; - kind: FileKind; +class FileElement { + constructor( + readonly name: string, + readonly kind: FileKind, + readonly uri: URI, + ) { } } +type BreadcrumbElement = FileElement | OutlineGroup | OutlineElement; + export class EditorBreadcrumbs implements IEditorBreadcrumbs { static CK_BreadcrumbsVisible = new RawContextKey('breadcrumbsVisible', false); @@ -129,7 +135,7 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { } fileItems.unshift(new RenderedBreadcrumbsItem( render, - { name, uri, kind: first ? FileKind.FILE : FileKind.FOLDER }, + new FileElement(name, first ? FileKind.FILE : FileKind.FOLDER, uri), !first )); path = paths.dirname(path); @@ -155,7 +161,7 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { DocumentSymbolProviderRegistry.onDidChange(_ => this.fire()); this._listener.push(control.onDidChangeModel(_ => this._checkModel())); this._listener.push(control.onDidChangeModelLanguage(_ => this._checkModel())); - this._checkModel(); + this._listener.push(setDisposableTimeout(_ => this._checkModel(), 0)); } private _checkModel() { @@ -229,23 +235,27 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { } } - private _onDidSelectItem(item: RenderedBreadcrumbsItem): void { - - if (item.element instanceof TreeElement) { - return; - } - + private _onDidSelectItem(item: RenderedBreadcrumbsItem): void { this._editorGroup.focus(); + let ctor: IConstructorSignature2; + let input: any; + if (item.element instanceof FileElement) { + ctor = BreadcrumbsFilePicker; + input = dirname(item.element.uri); + } else { + ctor = BreadcrumbsOutlinePicker; + input = item.element.parent; + } + this._contextViewService.showContextView({ getAnchor() { return item.node; }, render: (container: HTMLElement) => { dom.addClasses(container, 'show-file-icons'); - let res = this._instantiationService.createInstance(BreadcrumbsFilePicker, container); + let res = this._instantiationService.createInstance(ctor, container, input); res.layout({ width: 250, height: 300 }); - res.setInput(item.element.uri.with({ path: paths.dirname(item.element.uri.path) })); res.onDidPickElement(data => { this._contextViewService.hideContextView(); this._widget.select(undefined); @@ -253,7 +263,23 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { return; } if (URI.isUri(data)) { + // open new editor this._editorService.openEditor({ resource: data }); + + } else if (data instanceof OutlineElement) { + + let resource: URI; + let candidate = data.parent; + while (candidate) { + if (candidate instanceof OutlineModel) { + resource = candidate.textModel.uri; + break; + } + candidate = candidate.parent; + } + + this._editorService.openEditor({ resource, options: { selection: Range.collapseToStart(data.symbol.identifierRange) } }); + } }); return res; @@ -275,6 +301,7 @@ export abstract class BreadcrumbsPicker { constructor( container: HTMLElement, + input: any, @IInstantiationService protected readonly _instantiationService: IInstantiationService, @IThemeService protected readonly _themeService: IThemeService, ) { @@ -287,6 +314,9 @@ export abstract class BreadcrumbsPicker { this.focus = dom.trackFocus(this._domNode); this.focus.onDidBlur(_ => this._onDidPickElement.fire(undefined), undefined, this._disposables); + + this._tree.domFocus(); + this._tree.setInput(input); } dispose(): void { @@ -393,11 +423,6 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker { return config; } - setInput(resource: URI): void { - this._tree.domFocus(); - this._tree.setInput(resource); - } - protected _onDidChangeSelection(e: ISelectionEvent): void { let [first] = e.selection; let stat = first as IFileStat; @@ -407,6 +432,26 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker { } } +export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker { + + protected _completeTreeConfiguration(config: ITreeConfiguration): ITreeConfiguration { + config.dataSource = this._instantiationService.createInstance(OutlineDataSource); + config.renderer = this._instantiationService.createInstance(OutlineRenderer); + config.controller = this._instantiationService.createInstance(OutlineController, {}); + config.sorter = new OutlineItemComparator(); + return config; + } + + protected _onDidChangeSelection(e: ISelectionEvent): void { + if (e.payload && !e.payload.didClickElement) { + return; + } + let [first] = e.selection; + if (first instanceof OutlineElement) { + this._onDidPickElement.fire(first); + } + } +} //#region commands From c97de6d4cb9800c9554698805826369d7af9e49e Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 18 Jun 2018 15:26:18 +0200 Subject: [PATCH 042/283] outline - add twistie knowledge to event --- src/vs/editor/contrib/documentSymbols/outlineTree.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/documentSymbols/outlineTree.ts b/src/vs/editor/contrib/documentSymbols/outlineTree.ts index 078a79f4fa5..b34dcd8dfef 100644 --- a/src/vs/editor/contrib/documentSymbols/outlineTree.ts +++ b/src/vs/editor/contrib/documentSymbols/outlineTree.ts @@ -310,7 +310,7 @@ export class OutlineController extends WorkbenchTreeController { protected onLeftClick(tree: ITree, element: any, event: IMouseEvent, origin: string = 'mouse'): boolean { - const payload = { origin: origin, originalEvent: event }; + const payload = { origin: origin, originalEvent: event, didClickElement: false }; if (tree.getInput() === element) { tree.clearFocus(payload); @@ -322,13 +322,13 @@ export class OutlineController extends WorkbenchTreeController { } event.stopPropagation(); + payload.didClickElement = element instanceof OutlineElement && !this.isClickOnTwistie(event); + tree.domFocus(); tree.setSelection([element], payload); tree.setFocus(element, payload); - const didClickElement = element instanceof OutlineElement && !this.isClickOnTwistie(event); - - if (!didClickElement) { + if (!payload.didClickElement) { if (tree.isExpanded(element)) { tree.collapse(element).then(null, onUnexpectedError); } else { From 92693a1b268a37e1f4446115a171810715d5640d Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 25 Jun 2018 15:13:03 +0200 Subject: [PATCH 043/283] API: UriHandler, rename and docs --- extensions/git/src/protocolHandler.ts | 6 ++-- src/vs/vscode.proposed.d.ts | 32 +++++++++++++++++-- .../api/electron-browser/mainThreadUrls.ts | 4 +-- src/vs/workbench/api/node/extHost.api.impl.ts | 4 +-- src/vs/workbench/api/node/extHost.protocol.ts | 4 +-- src/vs/workbench/api/node/extHostUrls.ts | 8 ++--- 6 files changed, 42 insertions(+), 16 deletions(-) diff --git a/extensions/git/src/protocolHandler.ts b/extensions/git/src/protocolHandler.ts index b84d6620068..422ca3df739 100644 --- a/extensions/git/src/protocolHandler.ts +++ b/extensions/git/src/protocolHandler.ts @@ -5,16 +5,16 @@ 'use strict'; -import { ProtocolHandler, Uri, window, Disposable, commands } from 'vscode'; +import { UriHandler, Uri, window, Disposable, commands } from 'vscode'; import { dispose } from './util'; import * as querystring from 'querystring'; -export class GitProtocolHandler implements ProtocolHandler { +export class GitProtocolHandler implements UriHandler { private disposables: Disposable[] = []; constructor() { - this.disposables.push(window.registerProtocolHandler(this)); + this.disposables.push(window.registerUriHandler(this)); } handleUri(uri: Uri): void { diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 99ece63c7de..997cfb6570c 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -497,16 +497,42 @@ declare module 'vscode' { //#region URLs - export interface ProtocolHandler { + /** + * A Uri handler is responsible for handling system-wide [Uris](#Uri). + * + * @see [window.registerUriHandler](#window.registerUriHandler). + */ + export interface UriHandler { + + /** + * Handle the provided system-wide [Uri](#Uri). + * + * @see [window.registerUriHandler](#window.registerUriHandler). + */ handleUri(uri: Uri): void; } export namespace window { /** - * Registers a protocol handler capable of handling system-wide URIs. + * Registers a [Uri handler](#UriHandler) capable of handling system-wide [Uris](#Uri). + * A Uri handler is scoped to the extension it is contributed from: it will only + * be able to handle Uris which are directed to the extension itself. + * + * For example, if the `vscode.git` extension registers a Uri handler, it will only + * be allowed to handle Uris with the prefix `{scheme}://vscode.git`, in which `{scheme}` + * is either `vscode` or `vscode-insiders`. All the following Uris are examples: + * + * - `vscode://vscode.git` + * - `vscode://vscode.git/` + * - `vscode-insiders://vscode.git/status` + * - `vscode-insiders://vscode.git/clone?url=foobar` + * + * An extension can only register a single Uri handler in its entire activation lifetime. + * + * @param handler The Uri handler to register for this extension. */ - export function registerProtocolHandler(handler: ProtocolHandler): Disposable; + export function registerUriHandler(handler: UriHandler): Disposable; } //#endregion diff --git a/src/vs/workbench/api/electron-browser/mainThreadUrls.ts b/src/vs/workbench/api/electron-browser/mainThreadUrls.ts index 7ba2ed813b1..f85ebfdea27 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadUrls.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadUrls.ts @@ -42,7 +42,7 @@ export class MainThreadUrls implements MainThreadUrlsShape { this.proxy = context.getProxy(ExtHostContext.ExtHostUrls); } - $registerProtocolHandler(handle: number, extensionId: string): TPromise { + $registerUriHandler(handle: number, extensionId: string): TPromise { const handler = new ExtensionUrlHandler(this.proxy, handle, extensionId); const disposable = this.urlService.registerHandler(handler); @@ -52,7 +52,7 @@ export class MainThreadUrls implements MainThreadUrlsShape { return TPromise.as(null); } - $unregisterProtocolHandler(handle: number): TPromise { + $unregisterUriHandler(handle: number): TPromise { const tuple = this.handlers.get(handle); if (!tuple) { diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 609db622a2b..2b434b1fd03 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -452,8 +452,8 @@ export function createApiFactory( registerWebviewPanelSerializer: proposedApiFunction(extension, (viewType: string, serializer: vscode.WebviewPanelSerializer) => { return extHostWebviews.registerWebviewPanelSerializer(viewType, serializer); }), - registerProtocolHandler: proposedApiFunction(extension, (handler: vscode.ProtocolHandler) => { - return extHostUrls.registerProtocolHandler(extension.id, handler); + registerUriHandler: proposedApiFunction(extension, (handler: vscode.UriHandler) => { + return extHostUrls.registerUriHandler(extension.id, handler); }), get quickInputBackButton() { return proposedApiFunction(extension, (): vscode.QuickInputButton => { diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 6aab73ca6f7..20996b207a6 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -462,8 +462,8 @@ export interface ExtHostWebviewsShape { } export interface MainThreadUrlsShape extends IDisposable { - $registerProtocolHandler(handle: number, extensionId: string): TPromise; - $unregisterProtocolHandler(handle: number): TPromise; + $registerUriHandler(handle: number, extensionId: string): TPromise; + $unregisterUriHandler(handle: number): TPromise; } export interface ExtHostUrlsShape { diff --git a/src/vs/workbench/api/node/extHostUrls.ts b/src/vs/workbench/api/node/extHostUrls.ts index 534dfb4f02a..fd02738bdae 100644 --- a/src/vs/workbench/api/node/extHostUrls.ts +++ b/src/vs/workbench/api/node/extHostUrls.ts @@ -15,7 +15,7 @@ export class ExtHostUrls implements ExtHostUrlsShape { private readonly _proxy: MainThreadUrlsShape; private handles = new Set(); - private handlers = new Map(); + private handlers = new Map(); constructor( mainContext: IMainContext @@ -23,7 +23,7 @@ export class ExtHostUrls implements ExtHostUrlsShape { this._proxy = mainContext.getProxy(MainContext.MainThreadUrls); } - registerProtocolHandler(extensionId: string, handler: vscode.ProtocolHandler): vscode.Disposable { + registerUriHandler(extensionId: string, handler: vscode.UriHandler): vscode.Disposable { if (this.handles.has(extensionId)) { throw new Error(`Protocol handler already registered for extension ${extensionId}`); } @@ -31,12 +31,12 @@ export class ExtHostUrls implements ExtHostUrlsShape { const handle = ExtHostUrls.HandlePool++; this.handles.add(extensionId); this.handlers.set(handle, handler); - this._proxy.$registerProtocolHandler(handle, extensionId); + this._proxy.$registerUriHandler(handle, extensionId); return toDisposable(() => { this.handles.delete(extensionId); this.handlers.delete(handle); - this._proxy.$unregisterProtocolHandler(handle); + this._proxy.$unregisterUriHandler(handle); }); } From c7050d6c016587e9e50d3e1124bb2acad16b4fc6 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 25 Jun 2018 15:35:16 +0200 Subject: [PATCH 044/283] uri handler can return promise, we'll log any errors --- src/vs/vscode.proposed.d.ts | 2 +- src/vs/workbench/api/node/extHostUrls.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 997cfb6570c..4f56858bceb 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -509,7 +509,7 @@ declare module 'vscode' { * * @see [window.registerUriHandler](#window.registerUriHandler). */ - handleUri(uri: Uri): void; + handleUri(uri: Uri): ProviderResult; } export namespace window { diff --git a/src/vs/workbench/api/node/extHostUrls.ts b/src/vs/workbench/api/node/extHostUrls.ts index fd02738bdae..06f5b72ca62 100644 --- a/src/vs/workbench/api/node/extHostUrls.ts +++ b/src/vs/workbench/api/node/extHostUrls.ts @@ -8,6 +8,8 @@ import { MainContext, IMainContext, ExtHostUrlsShape, MainThreadUrlsShape } from import URI, { UriComponents } from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { toDisposable } from 'vs/base/common/lifecycle'; +import { asWinJsPromise } from 'vs/base/common/async'; +import { onUnexpectedError } from 'vs/base/common/errors'; export class ExtHostUrls implements ExtHostUrlsShape { @@ -47,7 +49,9 @@ export class ExtHostUrls implements ExtHostUrlsShape { return TPromise.as(null); } - handler.handleUri(URI.revive(uri)); + asWinJsPromise(_ => handler.handleUri(URI.revive(uri))) + .done(null, onUnexpectedError); + return TPromise.as(null); } } \ No newline at end of file From 87fa41ee006e75f6325729149a313f90bd6e61c5 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 25 Jun 2018 15:37:46 +0200 Subject: [PATCH 045/283] improve Url handler docs --- src/vs/vscode.proposed.d.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 4f56858bceb..0d387afcade 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -516,8 +516,10 @@ declare module 'vscode' { /** * Registers a [Uri handler](#UriHandler) capable of handling system-wide [Uris](#Uri). - * A Uri handler is scoped to the extension it is contributed from: it will only - * be able to handle Uris which are directed to the extension itself. + * In case there are multiple windows open, the topmost window will handle the Uri. + * A Uri handler is scoped to the extension it is contributed from; it will only + * be able to handle Uris which are directed to the extension itself. The Uri format + * is predetermined by the extension's identifier. * * For example, if the `vscode.git` extension registers a Uri handler, it will only * be allowed to handle Uris with the prefix `{scheme}://vscode.git`, in which `{scheme}` @@ -526,7 +528,7 @@ declare module 'vscode' { * - `vscode://vscode.git` * - `vscode://vscode.git/` * - `vscode-insiders://vscode.git/status` - * - `vscode-insiders://vscode.git/clone?url=foobar` + * - `vscode-insiders://vscode.git/clone?when=now` * * An extension can only register a single Uri handler in its entire activation lifetime. * From 0e8d72797107ece0d12267c423ba8c3f26b93235 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 25 Jun 2018 16:09:43 +0200 Subject: [PATCH 046/283] promote uri handler api to stable --- src/vs/vscode.d.ts | 37 ++++++++++++++++ src/vs/vscode.proposed.d.ts | 44 ------------------- src/vs/workbench/api/node/extHost.api.impl.ts | 4 +- 3 files changed, 39 insertions(+), 46 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 71329e06fea..826cdea5118 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -5686,6 +5686,21 @@ declare module 'vscode' { readonly focused: boolean; } + /** + * A Uri handler is responsible for handling system-wide [Uris](#Uri). + * + * @see [window.registerUriHandler](#window.registerUriHandler). + */ + export interface UriHandler { + + /** + * Handle the provided system-wide [Uri](#Uri). + * + * @see [window.registerUriHandler](#window.registerUriHandler). + */ + handleUri(uri: Uri): ProviderResult; + } + /** * Namespace for dealing with the current window of the editor. That is visible * and active editors, as well as, UI elements to show messages, selections, and @@ -6145,6 +6160,28 @@ declare module 'vscode' { * @returns a [TreeView](#TreeView). */ export function createTreeView(viewId: string, options: { treeDataProvider: TreeDataProvider }): TreeView; + + /** + * Registers a [Uri handler](#UriHandler) capable of handling system-wide [Uris](#Uri). + * In case there are multiple windows open, the topmost window will handle the Uri. + * A Uri handler is scoped to the extension it is contributed from; it will only + * be able to handle Uris which are directed to the extension itself. The Uri format + * is predetermined by the extension's identifier. + * + * For example, if the `vscode.git` extension registers a Uri handler, it will only + * be allowed to handle Uris with the prefix `{scheme}://vscode.git`, in which `{scheme}` + * is either `vscode` or `vscode-insiders`. All the following Uris are examples: + * + * - `vscode://vscode.git` + * - `vscode://vscode.git/` + * - `vscode-insiders://vscode.git/status` + * - `vscode-insiders://vscode.git/clone?when=now` + * + * An extension can only register a single Uri handler in its entire activation lifetime. + * + * @param handler The Uri handler to register for this extension. + */ + export function registerUriHandler(handler: UriHandler): Disposable; } /** diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 0d387afcade..10dea16487e 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -495,50 +495,6 @@ declare module 'vscode' { //#endregion - //#region URLs - - /** - * A Uri handler is responsible for handling system-wide [Uris](#Uri). - * - * @see [window.registerUriHandler](#window.registerUriHandler). - */ - export interface UriHandler { - - /** - * Handle the provided system-wide [Uri](#Uri). - * - * @see [window.registerUriHandler](#window.registerUriHandler). - */ - handleUri(uri: Uri): ProviderResult; - } - - export namespace window { - - /** - * Registers a [Uri handler](#UriHandler) capable of handling system-wide [Uris](#Uri). - * In case there are multiple windows open, the topmost window will handle the Uri. - * A Uri handler is scoped to the extension it is contributed from; it will only - * be able to handle Uris which are directed to the extension itself. The Uri format - * is predetermined by the extension's identifier. - * - * For example, if the `vscode.git` extension registers a Uri handler, it will only - * be allowed to handle Uris with the prefix `{scheme}://vscode.git`, in which `{scheme}` - * is either `vscode` or `vscode-insiders`. All the following Uris are examples: - * - * - `vscode://vscode.git` - * - `vscode://vscode.git/` - * - `vscode-insiders://vscode.git/status` - * - `vscode-insiders://vscode.git/clone?when=now` - * - * An extension can only register a single Uri handler in its entire activation lifetime. - * - * @param handler The Uri handler to register for this extension. - */ - export function registerUriHandler(handler: UriHandler): Disposable; - } - - //#endregion - //#region Joh -> exclusive document filters export interface DocumentFilter { diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 2b434b1fd03..a13e4ea75f0 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -452,9 +452,9 @@ export function createApiFactory( registerWebviewPanelSerializer: proposedApiFunction(extension, (viewType: string, serializer: vscode.WebviewPanelSerializer) => { return extHostWebviews.registerWebviewPanelSerializer(viewType, serializer); }), - registerUriHandler: proposedApiFunction(extension, (handler: vscode.UriHandler) => { + registerUriHandler(handler: vscode.UriHandler) { return extHostUrls.registerUriHandler(extension.id, handler); - }), + }, get quickInputBackButton() { return proposedApiFunction(extension, (): vscode.QuickInputButton => { return extHostQuickOpen.backButton; From 99ae54c030a5cec05436fa8158377b18072618c0 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 25 Jun 2018 16:16:38 +0200 Subject: [PATCH 047/283] document onUri activation event --- src/vs/vscode.d.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 826cdea5118..8268b0b1204 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -6179,6 +6179,9 @@ declare module 'vscode' { * * An extension can only register a single Uri handler in its entire activation lifetime. * + * * *Note:* There is an activation event `onUri` that fires when a Uri directed for + * the current extension is about to be handled. + * * @param handler The Uri handler to register for this extension. */ export function registerUriHandler(handler: UriHandler): Disposable; From 74df04928f030e796b3662e2e77e3319c427dad7 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 25 Jun 2018 17:06:35 +0200 Subject: [PATCH 048/283] uri handler api: improve docs --- src/vs/vscode.d.ts | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 8268b0b1204..fb96d7b0d88 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -5687,14 +5687,14 @@ declare module 'vscode' { } /** - * A Uri handler is responsible for handling system-wide [Uris](#Uri). + * A uri handler is responsible for handling system-wide [uris](#Uri). * * @see [window.registerUriHandler](#window.registerUriHandler). */ export interface UriHandler { /** - * Handle the provided system-wide [Uri](#Uri). + * Handle the provided system-wide [uri](#Uri). * * @see [window.registerUriHandler](#window.registerUriHandler). */ @@ -6162,27 +6162,31 @@ declare module 'vscode' { export function createTreeView(viewId: string, options: { treeDataProvider: TreeDataProvider }): TreeView; /** - * Registers a [Uri handler](#UriHandler) capable of handling system-wide [Uris](#Uri). - * In case there are multiple windows open, the topmost window will handle the Uri. - * A Uri handler is scoped to the extension it is contributed from; it will only - * be able to handle Uris which are directed to the extension itself. The Uri format - * is predetermined by the extension's identifier. + * Registers a [uri handler](#UriHandler) capable of handling system-wide [uris](#Uri). + * In case there are multiple windows open, the topmost window will handle the uri. + * A uri handler is scoped to the extension it is contributed from; it will only + * be able to handle uris which are directed to the extension itself. A uri must respect + * the following rules: * - * For example, if the `vscode.git` extension registers a Uri handler, it will only - * be allowed to handle Uris with the prefix `{scheme}://vscode.git`, in which `{scheme}` - * is either `vscode` or `vscode-insiders`. All the following Uris are examples: + * - The uri-scheme must be the product name (eg. `vscode`); + * - The uri-authority must be the extension id (eg. `vscode-git`); + * - The uri-path, -query and -fragment parts are arbitrary. + * + * For example, if the `vscode.git` extension registers a uri handler, it will only + * be allowed to handle uris with the prefix `{product}://vscode.git`. All the following + * uris are examples: * * - `vscode://vscode.git` * - `vscode://vscode.git/` - * - `vscode-insiders://vscode.git/status` - * - `vscode-insiders://vscode.git/clone?when=now` + * - `vscode://vscode.git/status` + * - `vscode://vscode.git/clone?when=now` * - * An extension can only register a single Uri handler in its entire activation lifetime. + * An extension can only register a single uri handler in its entire activation lifetime. * - * * *Note:* There is an activation event `onUri` that fires when a Uri directed for + * * *Note:* There is an activation event `onUri` that fires when a uri directed for * the current extension is about to be handled. * - * @param handler The Uri handler to register for this extension. + * @param handler The uri handler to register for this extension. */ export function registerUriHandler(handler: UriHandler): Disposable; } From 262787a4f29cae840a1f58d7ff26a41385658483 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 28 Jun 2018 06:36:02 -0700 Subject: [PATCH 049/283] Re-enable terminal menu Part of #53158 --- src/vs/code/electron-main/menus.ts | 98 ++++++++++-------------------- 1 file changed, 31 insertions(+), 67 deletions(-) diff --git a/src/vs/code/electron-main/menus.ts b/src/vs/code/electron-main/menus.ts index e37611cccd4..c8cbcb11fe1 100644 --- a/src/vs/code/electron-main/menus.ts +++ b/src/vs/code/electron-main/menus.ts @@ -240,9 +240,9 @@ export class CodeMenu { this.setGotoMenu(gotoMenu); // Terminal - // const terminalMenu = new Menu(); - // const terminalMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mTerminal', comment: ['&& denotes a mnemonic'] }, "&&Terminal")), submenu: terminalMenu }); - // this.setTerminalMenu(terminalMenu); + const terminalMenu = new Menu(); + const terminalMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mTerminal', comment: ['&& denotes a mnemonic'] }, "&&Terminal")), submenu: terminalMenu }); + this.setTerminalMenu(terminalMenu); // Debug const debugMenu = new Menu(); @@ -277,7 +277,7 @@ export class CodeMenu { menubar.append(selectionMenuItem); menubar.append(viewMenuItem); menubar.append(gotoMenuItem); - // menubar.append(terminalMenuItem); + menubar.append(terminalMenuItem); menubar.append(debugMenuItem); menubar.append(taskMenuItem); @@ -864,71 +864,35 @@ export class CodeMenu { ].forEach(item => gotoMenu.append(item)); } - // private setTerminalMenu(terminalMenu: Electron.Menu): void { - // const newTerminal = this.createMenuItem(nls.localize({ key: 'miNewTerminal', comment: ['&& denotes a mnemonic'] }, "&&New Terminal"), 'workbench.action.terminal.new'); - // const splitTerminal = this.createMenuItem(nls.localize({ key: 'miSplitTerminal', comment: ['&& denotes a mnemonic'] }, "&&Split Terminal"), 'workbench.action.terminal.split'); - // const killTerminal = this.createMenuItem(nls.localize({ key: 'miKillTerminal', comment: ['&& denotes a mnemonic'] }, "&&Kill Terminal"), 'workbench.action.terminal.kill'); + private setTerminalMenu(terminalMenu: Electron.Menu): void { + const newTerminal = this.createMenuItem(nls.localize({ key: 'miNewTerminal', comment: ['&& denotes a mnemonic'] }, "&&New Terminal"), 'workbench.action.terminal.new'); + const splitTerminal = this.createMenuItem(nls.localize({ key: 'miSplitTerminal', comment: ['&& denotes a mnemonic'] }, "&&Split Terminal"), 'workbench.action.terminal.split'); + const killTerminal = this.createMenuItem(nls.localize({ key: 'miKillTerminal', comment: ['&& denotes a mnemonic'] }, "&&Kill Terminal"), 'workbench.action.terminal.kill'); + const clear = this.createMenuItem(nls.localize({ key: 'miClear', comment: ['&& denotes a mnemonic'] }, "&&Clear"), 'workbench.action.terminal.clear'); + const runActiveFile = this.createMenuItem(nls.localize({ key: 'miRunActiveFile', comment: ['&& denotes a mnemonic'] }, "Run &&Active File"), 'workbench.action.terminal.runActiveFile'); + const runSelectedText = this.createMenuItem(nls.localize({ key: 'miRunSelectedText', comment: ['&& denotes a mnemonic'] }, "Run &&Selected Text"), 'workbench.action.terminal.runSelectedText'); + const scrollToPreviousCommand = this.createMenuItem(nls.localize({ key: 'miScrollToPreviousCommand', comment: ['&& denotes a mnemonic'] }, "Scroll To Previous Command"), 'workbench.action.terminal.scrollToPreviousCommand'); + const scrollToNextCommand = this.createMenuItem(nls.localize({ key: 'miScrollToNextCommand', comment: ['&& denotes a mnemonic'] }, "Scroll To Next Command"), 'workbench.action.terminal.scrollToNextCommand'); + const selectToPreviousCommand = this.createMenuItem(nls.localize({ key: 'miSelectToPreviousCommand', comment: ['&& denotes a mnemonic'] }, "Select To Previous Command"), 'workbench.action.terminal.selectToPreviousCommand'); + const selectToNextCommand = this.createMenuItem(nls.localize({ key: 'miSelectToNextCommand', comment: ['&& denotes a mnemonic'] }, "Select To Next Command"), 'workbench.action.terminal.selectToNextCommand'); - // const clear = this.createMenuItem(nls.localize({ key: 'miClear', comment: ['&& denotes a mnemonic'] }, "&&Clear"), 'workbench.action.terminal.clear'); - // // const deleteWordLeft = this.createMenuItem(nls.localize({ key: 'miDeleteWordLeft', comment: ['&& denotes a mnemonic'] }, "Delete Word To &&Left"), 'workbench.action.terminal.deleteWordLeft'); - // // const deleteWordRight = this.createMenuItem(nls.localize({ key: 'miDeleteWordRight', comment: ['&& denotes a mnemonic'] }, "Delete Word To &&Right"), 'workbench.action.terminal.deleteWordRight'); - // // const moveToLineStart = this.createMenuItem(nls.localize({ key: 'miMoveToLineStart', comment: ['&& denotes a mnemonic'] }, "Move to Line Start"), 'workbench.action.terminal.moveToLineStart'); - // // const moveToLineEnd = this.createMenuItem(nls.localize({ key: 'miMoveToLineEnd', comment: ['&& denotes a mnemonic'] }, "Move to Line &&End"), 'workbench.action.terminal.moveToLineEnd'); + const menuItems: MenuItem[] = [ + newTerminal, + splitTerminal, + killTerminal, + __separator__(), + clear, + runActiveFile, + runSelectedText, + __separator__(), + scrollToPreviousCommand, + scrollToNextCommand, + selectToPreviousCommand, + selectToNextCommand + ]; - // const runActiveFile = this.createMenuItem(nls.localize({ key: 'miRunActiveFile', comment: ['&& denotes a mnemonic'] }, "Run &&Active File"), 'workbench.action.terminal.runActiveFile'); - // const runSelectedText = this.createMenuItem(nls.localize({ key: 'miRunSelectedText', comment: ['&& denotes a mnemonic'] }, "Run &&Selected Text"), 'workbench.action.terminal.runSelectedText'); - - // // const scrollUp = this.createMenuItem(nls.localize({ key: 'miScrollUp', comment: ['&& denotes a mnemonic'] }, "Scroll Up"), 'workbench.action.terminal.scrollUp'); - // // const scrollDown = this.createMenuItem(nls.localize({ key: 'miScrollDown', comment: ['&& denotes a mnemonic'] }, "Scroll Down"), 'workbench.action.terminal.scrollDown'); - // // const scrollUpPage = this.createMenuItem(nls.localize({ key: 'miScrollUpPage', comment: ['&& denotes a mnemonic'] }, "Scroll Up Page"), 'workbench.action.terminal.scrollUpPage'); - // // const scrollDownPage = this.createMenuItem(nls.localize({ key: 'miScrollDownPage', comment: ['&& denotes a mnemonic'] }, "Scroll Down Page"), 'workbench.action.terminal.scrollDownPage'); - // // const scrollToTop = this.createMenuItem(nls.localize({ key: 'miScrollToTop', comment: ['&& denotes a mnemonic'] }, "Scroll To Top"), 'workbench.action.terminal.scrollToTop'); - // // const scrollToBottom = this.createMenuItem(nls.localize({ key: 'miScrollToBottom', comment: ['&& denotes a mnemonic'] }, "Scroll To Bottom"), 'workbench.action.terminal.scrollToBottom'); - // const scrollToPreviousCommand = this.createMenuItem(nls.localize({ key: 'miScrollToPreviousCommand', comment: ['&& denotes a mnemonic'] }, "Scroll To Previous Command"), 'workbench.action.terminal.scrollToPreviousCommand'); - // const scrollToNextCommand = this.createMenuItem(nls.localize({ key: 'miScrollToNextCommand', comment: ['&& denotes a mnemonic'] }, "Scroll To Next Command"), 'workbench.action.terminal.scrollToNextCommand'); - - // const selectToPreviousCommand = this.createMenuItem(nls.localize({ key: 'miSelectToPreviousCommand', comment: ['&& denotes a mnemonic'] }, "Select To Previous Command"), 'workbench.action.terminal.selectToPreviousCommand'); - // const selectToNextCommand = this.createMenuItem(nls.localize({ key: 'miSelectToNextCommand', comment: ['&& denotes a mnemonic'] }, "Select To Next Command"), 'workbench.action.terminal.selectToNextCommand'); - - // const menuItems: MenuItem[] = [ - // newTerminal, - // splitTerminal, - // killTerminal, - // __separator__(), - // clear, - // ]; - // // if (!isWindows) { - // // menuItems.push( - // // deleteWordLeft, - // // deleteWordRight, - // // ); - - // // } - // // if (isMacintosh) { - // // menuItems.push( - // // moveToLineStart, - // // moveToLineEnd - // // ); - // // } - // menuItems.push( - // runActiveFile, - // runSelectedText, - // __separator__(), - // // scrollUp, - // // scrollDown, - // // scrollUpPage, - // // scrollDownPage, - // // scrollToTop, - // // scrollToBottom, - // scrollToPreviousCommand, - // scrollToNextCommand, - // // __separator__(), - // selectToPreviousCommand, - // selectToNextCommand - // ); - - // menuItems.forEach(item => terminalMenu.append(item)); - // } + menuItems.forEach(item => terminalMenu.append(item)); + } private setDebugMenu(debugMenu: Electron.Menu): void { const start = this.createMenuItem(nls.localize({ key: 'miStartDebugging', comment: ['&& denotes a mnemonic'] }, "&&Start Debugging"), 'workbench.action.debug.start'); From a4e6bdc6fd61f0350fa5f47d0a291cb1c4a0c463 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 28 Jun 2018 06:37:40 -0700 Subject: [PATCH 050/283] Rename integrated terminal to terminal in view menu Fixes #53158 --- src/vs/code/electron-main/menus.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/code/electron-main/menus.ts b/src/vs/code/electron-main/menus.ts index c8cbcb11fe1..4a1ebf94aab 100644 --- a/src/vs/code/electron-main/menus.ts +++ b/src/vs/code/electron-main/menus.ts @@ -658,7 +658,7 @@ export class CodeMenu { // Panels const output = this.createMenuItem(nls.localize({ key: 'miToggleOutput', comment: ['&& denotes a mnemonic'] }, "&&Output"), 'workbench.action.output.toggleOutput'); const debugConsole = this.createMenuItem(nls.localize({ key: 'miToggleDebugConsole', comment: ['&& denotes a mnemonic'] }, "De&&bug Console"), 'workbench.debug.action.toggleRepl'); - const integratedTerminal = this.createMenuItem(nls.localize({ key: 'miToggleIntegratedTerminal', comment: ['&& denotes a mnemonic'] }, "&&Integrated Terminal"), 'workbench.action.terminal.toggleTerminal'); + const terminal = this.createMenuItem(nls.localize({ key: 'miToggleTerminal', comment: ['&& denotes a mnemonic'] }, "&&Terminal"), 'workbench.action.terminal.toggleTerminal'); const problems = this.createMenuItem(nls.localize({ key: 'miMarker', comment: ['&& denotes a mnemonic'] }, "&&Problems"), 'workbench.actions.view.problems'); const fullscreen = new MenuItem(this.withKeybinding('workbench.action.toggleFullScreen', { label: this.mnemonicLabel(nls.localize({ key: 'miToggleFullScreen', comment: ['&& denotes a mnemonic'] }, "Toggle &&Full Screen")), click: () => this.windowsMainService.getLastActiveWindow().toggleFullScreen(), enabled: this.windowsMainService.getWindowCount() > 0 })); @@ -759,7 +759,7 @@ export class CodeMenu { output, problems, debugConsole, - integratedTerminal, + terminal, __separator__(), fullscreen, toggleZenMode, From 162758bd115051ac95cd92b2f0df7e78445aa67c Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 29 Jun 2018 15:27:18 +0200 Subject: [PATCH 051/283] adopt api changes --- src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts index 037240bb87e..c5f086b0724 100644 --- a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts @@ -278,7 +278,7 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { candidate = candidate.parent; } - this._editorService.openEditor({ resource, options: { selection: Range.collapseToStart(data.symbol.identifierRange) } }); + this._editorService.openEditor({ resource, options: { selection: Range.collapseToStart(data.symbol.selectionRange) } }); } }); From aa75fbd691d7d538ce75c6f132febf23b691d9e7 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 2 Jul 2018 16:57:19 +0200 Subject: [PATCH 052/283] fix dirty diff provider order related to #53442 --- .../scm/electron-browser/dirtydiffDecorator.ts | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts b/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts index 973f975cb0e..0f7d7786288 100644 --- a/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts @@ -8,7 +8,7 @@ import * as nls from 'vs/nls'; import 'vs/css!./media/dirtydiffDecorator'; -import { ThrottledDelayer, always } from 'vs/base/common/async'; +import { ThrottledDelayer, always, first } from 'vs/base/common/async'; import { IDisposable, dispose, toDisposable, empty as EmptyDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { TPromise } from 'vs/base/common/winjs.base'; import { Event, Emitter, anyEvent as anyEvent, filterEvent, once } from 'vs/base/common/event'; @@ -1032,20 +1032,13 @@ export class DirtyDiffModel { }); } - private async getOriginalResource(): TPromise { + private getOriginalResource(): TPromise { if (!this._editorModel) { return null; } - for (const repository of this.scmService.repositories) { - const result = repository.provider.getOriginalResource(this._editorModel.uri); - - if (result) { - return result; - } - } - - return null; + const uri = this._editorModel.uri; + return first(this.scmService.repositories.map(r => () => r.provider.getOriginalResource(uri))); } findNextClosestChange(lineNumber: number, inclusive = true): number { From 0f965d5bd988ed34de478165a247b70c6f3160a7 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 2 Jul 2018 17:18:00 +0200 Subject: [PATCH 053/283] #53442 Avoid async and winjs promise --- .../preferences/browser/preferencesEditor.ts | 8 ++++---- .../parts/search/browser/searchActions.ts | 15 ++++++++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts b/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts index afaa9962fea..9b12f126db0 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts @@ -434,7 +434,7 @@ class PreferencesRenderersController extends Disposable { } } - private async _onEditableContentDidChange(): TPromise { + private async _onEditableContentDidChange(): Promise { await this.localFilterPreferences(this._lastQuery, true); await this.remoteSearchPreferences(this._lastQuery, true); } @@ -511,11 +511,11 @@ class PreferencesRenderersController extends Disposable { return TPromise.join(searchPs).then(() => { }); } - private searchSettingsTarget(query: string, provider: ISearchProvider, target: SettingsTarget, groupId: string, groupLabel: string, groupOrder: number): TPromise { + private searchSettingsTarget(query: string, provider: ISearchProvider, target: SettingsTarget, groupId: string, groupLabel: string, groupOrder: number): Promise { if (!query) { // Don't open the other settings targets when query is empty this._onDidFilterResultsCountChange.fire({ target, count: 0 }); - return TPromise.wrap(null); + return Promise.resolve(null); } return this.getPreferencesEditorModel(target).then(model => { @@ -532,7 +532,7 @@ class PreferencesRenderersController extends Disposable { }); } - private async getPreferencesEditorModel(target: SettingsTarget): TPromise { + private async getPreferencesEditorModel(target: SettingsTarget): Promise { const resource = target === ConfigurationTarget.USER ? this.preferencesService.userSettingsResource : target === ConfigurationTarget.WORKSPACE ? this.preferencesService.workspaceSettingsResource : target; diff --git a/src/vs/workbench/parts/search/browser/searchActions.ts b/src/vs/workbench/parts/search/browser/searchActions.ts index 0fb3e737976..bd2aa0463a9 100644 --- a/src/vs/workbench/parts/search/browser/searchActions.ts +++ b/src/vs/workbench/parts/search/browser/searchActions.ts @@ -492,14 +492,15 @@ export class ReplaceAllInFolderAction extends AbstractSearchAndReplaceAction { super(Constants.ReplaceAllInFolderActionId, appendKeyBindingLabel(ReplaceAllInFolderAction.LABEL, keyBindingService.lookupKeybinding(Constants.ReplaceAllInFolderActionId), keyBindingService), 'action-replace-all'); } - public async run(): TPromise { + public run(): TPromise { let nextFocusElement = this.getElementToFocusAfterRemoved(this.viewer, this.folderMatch); - await this.folderMatch.replaceAll(); - - if (nextFocusElement) { - this.viewer.setFocus(nextFocusElement); - } - this.viewer.domFocus(); + return this.folderMatch.replaceAll() + .then(() => { + if (nextFocusElement) { + this.viewer.setFocus(nextFocusElement); + } + this.viewer.domFocus(); + }); } } From 76d28c9194aec63218aed57e32b40810240b1b77 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 2 Jul 2018 17:30:59 +0200 Subject: [PATCH 054/283] cleanup more async related to #53442 --- .../browser/parts/views/panelViewlet.ts | 12 ++-- .../browser/parts/views/viewsViewlet.ts | 46 +++++++------- .../parts/scm/electron-browser/scmViewlet.ts | 61 +++++++++---------- 3 files changed, 59 insertions(+), 60 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/panelViewlet.ts b/src/vs/workbench/browser/parts/views/panelViewlet.ts index 13be029935a..8bfdfdae01c 100644 --- a/src/vs/workbench/browser/parts/views/panelViewlet.ts +++ b/src/vs/workbench/browser/parts/views/panelViewlet.ts @@ -207,12 +207,12 @@ export class PanelViewlet extends Viewlet { super(id, partService, telemetryService, themeService); } - async create(parent: HTMLElement): TPromise { - super.create(parent); - - this.panelview = this._register(new PanelView(parent, this.options)); - this._register(this.panelview.onDidDrop(({ from, to }) => this.movePanel(from as ViewletPanel, to as ViewletPanel))); - this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, (e: MouseEvent) => this.showContextMenu(new StandardMouseEvent(e)))); + create(parent: HTMLElement): TPromise { + return super.create(parent).then(() => { + this.panelview = this._register(new PanelView(parent, this.options)); + this._register(this.panelview.onDidDrop(({ from, to }) => this.movePanel(from as ViewletPanel, to as ViewletPanel))); + this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, (e: MouseEvent) => this.showContextMenu(new StandardMouseEvent(e)))); + }); } private showContextMenu(event: StandardMouseEvent): void { diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index e930b4be1fb..66b31e8c237 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -144,31 +144,31 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView this._register(toDisposable(() => this.viewDisposables = dispose(this.viewDisposables))); } - async create(parent: HTMLElement): TPromise { - await super.create(parent); - - this._register(this.onDidSashChange(() => this.saveViewSizes())); - this.viewsModel.onDidAdd(added => this.onDidAddViews(added)); - this.viewsModel.onDidRemove(removed => this.onDidRemoveViews(removed)); - const addedViews: IAddedViewDescriptorRef[] = this.viewsModel.visibleViewDescriptors.map((viewDescriptor, index) => { - const size = this.viewsModel.getSize(viewDescriptor.id); - const collapsed = this.viewsModel.isCollapsed(viewDescriptor.id); - return ({ viewDescriptor, index, size, collapsed }); - }); - if (addedViews.length) { - this.onDidAddViews(addedViews); - } - - // Update headers after and title contributed views after available, since we read from cache in the beginning to know if the viewlet has single view or not. Ref #29609 - this.extensionService.whenInstalledExtensionsRegistered().then(() => { - this.areExtensionsReady = true; - if (this.panels.length) { - this.updateTitleArea(); - this.updateViewHeaders(); + create(parent: HTMLElement): TPromise { + return super.create(parent).then(() => { + this._register(this.onDidSashChange(() => this.saveViewSizes())); + this.viewsModel.onDidAdd(added => this.onDidAddViews(added)); + this.viewsModel.onDidRemove(removed => this.onDidRemoveViews(removed)); + const addedViews: IAddedViewDescriptorRef[] = this.viewsModel.visibleViewDescriptors.map((viewDescriptor, index) => { + const size = this.viewsModel.getSize(viewDescriptor.id); + const collapsed = this.viewsModel.isCollapsed(viewDescriptor.id); + return ({ viewDescriptor, index, size, collapsed }); + }); + if (addedViews.length) { + this.onDidAddViews(addedViews); } - }); - this.focus(); + // Update headers after and title contributed views after available, since we read from cache in the beginning to know if the viewlet has single view or not. Ref #29609 + this.extensionService.whenInstalledExtensionsRegistered().then(() => { + this.areExtensionsReady = true; + if (this.panels.length) { + this.updateTitleArea(); + this.updateViewHeaders(); + } + }); + + this.focus(); + }); } getContextMenuActions(): IAction[] { diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index 3a2eda194f7..5e3a84b8862 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -811,12 +811,9 @@ export class RepositoryPanel extends ViewletPanel { this.inputBox.setPlaceHolder(placeholder); }; - const validationDelayer = new ThrottledDelayer(200); - + const validationDelayer = new ThrottledDelayer(200); const validate = () => { - validationDelayer.trigger(async (): TPromise => { - const result = await this.repository.input.validateInput(this.inputBox.value, this.inputBox.inputElement.selectionStart); - + return this.repository.input.validateInput(this.inputBox.value, this.inputBox.inputElement.selectionStart).then(result => { if (!result) { this.inputBox.inputElement.removeAttribute('aria-invalid'); this.inputBox.hideMessage(); @@ -827,15 +824,17 @@ export class RepositoryPanel extends ViewletPanel { }); }; + const triggerValidation = () => validationDelayer.trigger(validate); + this.inputBox = new InputBox(this.inputBoxContainer, this.contextViewService, { flexibleHeight: true }); this.disposables.push(attachInputBoxStyler(this.inputBox, this.themeService)); this.disposables.push(this.inputBox); - this.inputBox.onDidChange(validate, null, this.disposables); + this.inputBox.onDidChange(triggerValidation, null, this.disposables); const onKeyUp = domEvent(this.inputBox.inputElement, 'keyup'); const onMouseUp = domEvent(this.inputBox.inputElement, 'mouseup'); - anyEvent(onKeyUp, onMouseUp)(() => validate(), null, this.disposables); + anyEvent(onKeyUp, onMouseUp)(triggerValidation, null, this.disposables); this.inputBox.value = this.repository.input.value; this.inputBox.onDidChange(value => this.repository.input.value = value, null, this.disposables); @@ -1068,37 +1067,37 @@ export class SCMViewlet extends PanelViewlet implements IViewModel, IViewsViewle this.disposables.push(this.contributedViews); } - async create(parent: HTMLElement): TPromise { - await super.create(parent); + create(parent: HTMLElement): TPromise { + return super.create(parent).then(() => { + this.el = parent; + addClass(this.el, 'scm-viewlet'); + addClass(this.el, 'empty'); + append(parent, $('div.empty-message', null, localize('no open repo', "There are no active source control providers."))); - this.el = parent; - addClass(this.el, 'scm-viewlet'); - addClass(this.el, 'empty'); - append(parent, $('div.empty-message', null, localize('no open repo', "There are no active source control providers."))); + this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables); + this.scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.disposables); + this.scmService.repositories.forEach(r => this.onDidAddRepository(r)); - this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables); - this.scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.disposables); - this.scmService.repositories.forEach(r => this.onDidAddRepository(r)); + const onDidUpdateConfiguration = filterEvent(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.alwaysShowProviders')); + onDidUpdateConfiguration(this.onDidChangeRepositories, this, this.disposables); - const onDidUpdateConfiguration = filterEvent(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.alwaysShowProviders')); - onDidUpdateConfiguration(this.onDidChangeRepositories, this, this.disposables); + this.onDidChangeRepositories(); - this.onDidChangeRepositories(); + this.contributedViews.onDidAdd(this.onDidAddContributedViews, this, this.disposables); + this.contributedViews.onDidRemove(this.onDidRemoveContributedViews, this, this.disposables); - this.contributedViews.onDidAdd(this.onDidAddContributedViews, this, this.disposables); - this.contributedViews.onDidRemove(this.onDidRemoveContributedViews, this, this.disposables); + let index = this.getContributedViewsStartIndex(); + const contributedViews: IAddedViewDescriptorRef[] = this.contributedViews.visibleViewDescriptors.map(viewDescriptor => { + const size = this.contributedViews.getSize(viewDescriptor.id); + const collapsed = this.contributedViews.isCollapsed(viewDescriptor.id); + return { viewDescriptor, index: index++, size, collapsed }; + }); + if (contributedViews.length) { + this.onDidAddContributedViews(contributedViews); + } - let index = this.getContributedViewsStartIndex(); - const contributedViews: IAddedViewDescriptorRef[] = this.contributedViews.visibleViewDescriptors.map(viewDescriptor => { - const size = this.contributedViews.getSize(viewDescriptor.id); - const collapsed = this.contributedViews.isCollapsed(viewDescriptor.id); - return { viewDescriptor, index: index++, size, collapsed }; + this.onDidSashChange(this.saveContributedViewSizes, this, this.disposables); }); - if (contributedViews.length) { - this.onDidAddContributedViews(contributedViews); - } - - this.onDidSashChange(this.saveContributedViewSizes, this, this.disposables); } private onDidAddRepository(repository: ISCMRepository): void { From 12479612cc729bc5ae00efc877c3a423a00f214b Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 2 Jul 2018 17:37:41 +0200 Subject: [PATCH 055/283] cleanup more async/await related to #53442 --- src/vs/base/common/async.ts | 4 ++-- src/vs/platform/url/common/urlService.ts | 14 ++++---------- .../workbench/parts/debug/browser/debugViewlet.ts | 8 ++++---- .../files/electron-browser/explorerViewlet.ts | 8 ++++---- .../update/electron-browser/releaseNotesEditor.ts | 2 +- .../parts/url/electron-browser/url.contribution.ts | 10 +++++----- 6 files changed, 20 insertions(+), 26 deletions(-) diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 53a7e38ee3c..88d57b4307e 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -442,13 +442,13 @@ export function sequence(promiseFactories: ITask>[]): TPromise(promiseFactories: ITask>[], shouldStop: (t: T) => boolean = t => !!t): TPromise { +export function first(promiseFactories: ITask>[], shouldStop: (t: T) => boolean = t => !!t, defaultValue: T = null): TPromise { let index = 0; const len = promiseFactories.length; const loop: () => TPromise = () => { if (index >= len) { - return TPromise.as(null); + return TPromise.as(defaultValue); } const factory = promiseFactories[index++]; diff --git a/src/vs/platform/url/common/urlService.ts b/src/vs/platform/url/common/urlService.ts index f4fbab19d7d..2d7fda8b99f 100644 --- a/src/vs/platform/url/common/urlService.ts +++ b/src/vs/platform/url/common/urlService.ts @@ -9,6 +9,7 @@ import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; import URI from 'vs/base/common/uri'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { TPromise } from 'vs/base/common/winjs.base'; +import { first } from 'vs/base/common/async'; declare module Array { function from(set: Set): T[]; @@ -20,16 +21,9 @@ export class URLService implements IURLService { private handlers = new Set(); - async open(uri: URI): TPromise { + open(uri: URI): TPromise { const handlers = Array.from(this.handlers); - - for (const handler of handlers) { - if (await handler.handleURL(uri)) { - return true; - } - } - - return false; + return first(handlers.map(h => () => h.handleURL(uri)), undefined, false); } registerHandler(handler: IURLHandler): IDisposable { @@ -44,7 +38,7 @@ export class RelayURLService extends URLService implements IURLHandler { super(); } - async open(uri: URI): TPromise { + open(uri: URI): TPromise { return this.urlService.open(uri); } diff --git a/src/vs/workbench/parts/debug/browser/debugViewlet.ts b/src/vs/workbench/parts/debug/browser/debugViewlet.ts index 2cc3d77ddf9..30ffc30d939 100644 --- a/src/vs/workbench/parts/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/parts/debug/browser/debugViewlet.ts @@ -66,10 +66,10 @@ export class DebugViewlet extends ViewContainerViewlet { })); } - async create(parent: HTMLElement): TPromise { - await super.create(parent); - - DOM.addClass(parent, 'debug-viewlet'); + create(parent: HTMLElement): TPromise { + return super.create(parent).then(() => { + DOM.addClass(parent, 'debug-viewlet'); + }); } public focus(): void { diff --git a/src/vs/workbench/parts/files/electron-browser/explorerViewlet.ts b/src/vs/workbench/parts/files/electron-browser/explorerViewlet.ts index 57326f16477..257e5d35082 100644 --- a/src/vs/workbench/parts/files/electron-browser/explorerViewlet.ts +++ b/src/vs/workbench/parts/files/electron-browser/explorerViewlet.ts @@ -173,10 +173,10 @@ export class ExplorerViewlet extends ViewContainerViewlet implements IExplorerVi this._register(this.contextService.onDidChangeWorkspaceName(e => this.updateTitleArea())); } - async create(parent: HTMLElement): TPromise { - await super.create(parent); - - DOM.addClass(parent, 'explorer-viewlet'); + create(parent: HTMLElement): TPromise { + return super.create(parent).then(() => { + DOM.addClass(parent, 'explorer-viewlet'); + }); } protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewletPanel { diff --git a/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts b/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts index f1dcc32c086..c32bfc0d3c4 100644 --- a/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts +++ b/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts @@ -160,7 +160,7 @@ export class ReleaseNotesManager { return body; } - private async renderContent(text: string): TPromise { + private async renderContent(text: string): Promise { const renderer = await this.getRenderer(text); return marked(text, { renderer }); } diff --git a/src/vs/workbench/parts/url/electron-browser/url.contribution.ts b/src/vs/workbench/parts/url/electron-browser/url.contribution.ts index bc6c1f8faa3..375cfdad0a4 100644 --- a/src/vs/workbench/parts/url/electron-browser/url.contribution.ts +++ b/src/vs/workbench/parts/url/electron-browser/url.contribution.ts @@ -27,11 +27,11 @@ export class OpenUrlAction extends Action { super(id, label); } - async run(): TPromise { - const input = await this.quickInputService.input({ prompt: 'URL to open' }); - const uri = URI.parse(input); - - this.urlService.open(uri); + run(): TPromise { + return this.quickInputService.input({ prompt: 'URL to open' }).then(input => { + const uri = URI.parse(input); + this.urlService.open(uri); + }); } } From 02c99b75627703b34d358f5c23e8f8b298500604 Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Mon, 2 Jul 2018 17:36:23 +0200 Subject: [PATCH 056/283] Avoid aync and winjs-promise --- .../workbench/api/node/extHostDebugService.ts | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index 041ab383c13..17cb3c8254e 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -123,7 +123,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { } } - public async $runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise { + public $runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise { if (args.kind === 'integrated') { @@ -136,20 +136,31 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { }); } - let t = this._integratedTerminalInstance; + return new TPromise(resolve => { + if (this._integratedTerminalInstance) { + this._integratedTerminalInstance.processId.then(pid => { + resolve(hasChildprocesses(pid)); + }, err => { + resolve(true); + }); + } else { + resolve(true); + } + }).then(needNewTerminal => { - if ((t && hasChildprocesses(await t.processId)) || !t) { - t = this._terminalService.createTerminal(args.title || nls.localize('debug.terminal.title', "debuggee")); - this._integratedTerminalInstance = t; - } - t.show(); + if (needNewTerminal) { + this._integratedTerminalInstance = this._terminalService.createTerminal(args.title || nls.localize('debug.terminal.title', "debuggee")); + } - return new TPromise((resolve, error) => { - setTimeout(_ => { - const command = prepareCommand(args, config); - t.sendText(command, true); - resolve(void 0); - }, 500); + this._integratedTerminalInstance.show(); + + return new TPromise((resolve, error) => { + setTimeout(_ => { + const command = prepareCommand(args, config); + this._integratedTerminalInstance.sendText(command, true); + resolve(void 0); + }, 500); + }); }); } else if (args.kind === 'external') { From 42cd3f9db365e4bdec9b50d97ad125b0b7c0e481 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 2 Jul 2018 18:00:35 +0200 Subject: [PATCH 057/283] #53442 Avoid async and winjs --- .../electron-browser/extensionsViewlet.ts | 23 +++++++++--------- .../electron-browser/extensionsViews.ts | 24 +++++++++---------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts index 40be6b379cd..507bf50decd 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts @@ -301,7 +301,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio }, this, this.disposables); } - async create(parent: HTMLElement): TPromise { + create(parent: HTMLElement): TPromise { addClass(parent, 'extensions-viewlet'); this.root = parent; @@ -326,14 +326,14 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio this.onSearchChange = mapEvent(onSearchInput, e => e.target.value); - await super.create(this.extensionsBox); - - const installed = await this.extensionManagementService.getInstalled(LocalExtensionType.User); - - if (installed.length === 0) { - this.searchBox.value = '@sort:installs'; - this.searchExtensionsContextKey.set(true); - } + return super.create(this.extensionsBox) + .then(() => this.extensionManagementService.getInstalled(LocalExtensionType.User)) + .then(installed => { + if (installed.length === 0) { + this.searchBox.value = '@sort:installs'; + this.searchExtensionsContextKey.set(true); + } + }); } public updateStyles(): void { @@ -429,7 +429,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio .done(null, err => this.onError(err)); } - private async doSearch(): TPromise { + private doSearch(): TPromise { const value = this.searchBox.value || ''; this.searchExtensionsContextKey.set(!!value); this.searchInstalledExtensionsContextKey.set(InstalledExtensionsView.isInstalledExtensionsQuery(value)); @@ -439,8 +439,9 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio this.nonEmptyWorkspaceContextKey.set(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY); if (value) { - this.progress(TPromise.join(this.panels.map(view => (view).show(this.searchBox.value)))); + return this.progress(TPromise.join(this.panels.map(view => (view).show(this.searchBox.value)))); } + return TPromise.as(null); } protected onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPanel[] { diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts index e61c9d9603a..a03d325d251 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts @@ -107,7 +107,7 @@ export class ExtensionsListView extends ViewletPanel { this.list.layout(size); } - async show(query: string): TPromise> { + async show(query: string): Promise> { const model = await this.query(query); this.setModel(model); return model; @@ -141,7 +141,7 @@ export class ExtensionsListView extends ViewletPanel { return this.list.length; } - private async query(value: string): TPromise> { + private async query(value: string): Promise> { const query = Query.parse(value); let options: IQueryOptions = { @@ -652,7 +652,7 @@ export class InstalledExtensionsView extends ExtensionsListView { || ExtensionsListView.isEnabledExtensionsQuery(query); } - async show(query: string): TPromise> { + async show(query: string): Promise> { if (InstalledExtensionsView.isInstalledExtensionsQuery(query)) { return super.show(query); } @@ -664,7 +664,7 @@ export class InstalledExtensionsView extends ExtensionsListView { export class GroupByServerExtensionsView extends ExtensionsListView { - async show(query: string): TPromise> { + async show(query: string): Promise> { query = query.replace(/@group:server/g, '').trim(); query = query ? query : '@installed'; if (!InstalledExtensionsView.isInstalledExtensionsQuery(query) && !ExtensionsListView.isBuiltInExtensionsQuery(query)) { @@ -676,21 +676,21 @@ export class GroupByServerExtensionsView extends ExtensionsListView { export class EnabledExtensionsView extends ExtensionsListView { - async show(query: string): TPromise> { + async show(query: string): Promise> { return super.show('@enabled'); } } export class DisabledExtensionsView extends ExtensionsListView { - async show(query: string): TPromise> { + async show(query: string): Promise> { return super.show('@disabled'); } } export class BuiltInExtensionsView extends ExtensionsListView { - async show(query: string): TPromise> { + async show(query: string): Promise> { return super.show(query.replace('@builtin', '@builtin:features')); } @@ -698,14 +698,14 @@ export class BuiltInExtensionsView extends ExtensionsListView { export class BuiltInThemesExtensionsView extends ExtensionsListView { - async show(query: string): TPromise> { + async show(query: string): Promise> { return super.show(query.replace('@builtin', '@builtin:themes')); } } export class BuiltInBasicsExtensionsView extends ExtensionsListView { - async show(query: string): TPromise> { + async show(query: string): Promise> { return super.show(query.replace('@builtin', '@builtin:basics')); } } @@ -720,7 +720,7 @@ export class DefaultRecommendedExtensionsView extends ExtensionsListView { })); } - async show(query: string): TPromise> { + async show(query: string): Promise> { return super.show('@recommended:all'); } @@ -737,7 +737,7 @@ export class RecommendedExtensionsView extends ExtensionsListView { })); } - async show(query: string): TPromise> { + async show(query: string): Promise> { return super.show('@recommended'); } } @@ -777,7 +777,7 @@ export class WorkspaceRecommendedExtensionsView extends ExtensionsListView { this.disposables.push(...[this.installAllAction, configureWorkspaceFolderAction, actionbar]); } - async show(): TPromise> { + async show(): Promise> { let model = await super.show('@recommended:workspace'); this.setExpanded(model.length > 0); return model; From dd6dc96d2063a1806a59ecb1511b3e2a24a8009e Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 2 Jul 2018 17:38:52 +0200 Subject: [PATCH 058/283] clean more async related to #53442 --- src/vs/code/electron-main/app.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index edd6a1a8f68..b12acca0233 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -434,7 +434,7 @@ export class CodeApplication { const environmentService = accessor.get(IEnvironmentService); urlService.registerHandler({ - async handleURL(uri: URI): TPromise { + handleURL(uri: URI): TPromise { if (windowsMainService.getWindowCount() === 0) { const cli = { ...environmentService.args, goto: true }; const [window] = windowsMainService.open({ context: OpenContext.API, cli, forceEmpty: true }); @@ -442,7 +442,7 @@ export class CodeApplication { return window.ready().then(() => urlService.open(uri)); } - return false; + return TPromise.as(false); } }); } From 36022c7068549a19c1d919b2de6a44d01004330d Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 2 Jul 2018 18:26:34 +0200 Subject: [PATCH 059/283] remove more async TPromise related to #53442 --- .../driver/electron-browser/driver.ts | 59 ++++--- .../platform/driver/electron-main/driver.ts | 149 ++++++++++-------- src/vs/platform/driver/node/driver.ts | 3 +- 3 files changed, 115 insertions(+), 96 deletions(-) diff --git a/src/vs/platform/driver/electron-browser/driver.ts b/src/vs/platform/driver/electron-browser/driver.ts index 6c723a99181..ae94a0aa811 100644 --- a/src/vs/platform/driver/electron-browser/driver.ts +++ b/src/vs/platform/driver/electron-browser/driver.ts @@ -14,6 +14,7 @@ import { getTopLeftOffset, getClientArea } from 'vs/base/browser/dom'; import * as electron from 'electron'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { Terminal } from 'vscode-xterm'; +import { toWinJsPromise } from 'vs/base/common/async'; function serializeElement(element: Element, recursive: boolean): IElement { const attributes = Object.create(null); @@ -50,19 +51,19 @@ class WindowDriver implements IWindowDriver { @IWindowService private windowService: IWindowService ) { } - async click(selector: string, xoffset?: number, yoffset?: number): TPromise { - return this._click(selector, 1, xoffset, yoffset); + click(selector: string, xoffset?: number, yoffset?: number): TPromise { + return toWinJsPromise(this._click(selector, 1, xoffset, yoffset)); } doubleClick(selector: string): TPromise { - return this._click(selector, 2); + return toWinJsPromise(this._click(selector, 2)); } - private async _getElementXY(selector: string, xoffset?: number, yoffset?: number): TPromise<{ x: number; y: number; }> { + private async _getElementXY(selector: string, xoffset?: number, yoffset?: number): Promise<{ x: number; y: number; }> { const element = document.querySelector(selector); if (!element) { - throw new Error('Element not found'); + return TPromise.wrapError(new Error('Element not found')); } const { left, top } = getTopLeftOffset(element as HTMLElement); @@ -83,7 +84,7 @@ class WindowDriver implements IWindowDriver { return { x, y }; } - private async _click(selector: string, clickCount: number, xoffset?: number, yoffset?: number): TPromise { + private async _click(selector: string, clickCount: number, xoffset?: number, yoffset?: number): Promise { const { x, y } = await this._getElementXY(selector, xoffset, yoffset); const webContents = electron.remote.getCurrentWebContents(); @@ -94,11 +95,11 @@ class WindowDriver implements IWindowDriver { await TPromise.timeout(100); } - async setValue(selector: string, text: string): TPromise { + setValue(selector: string, text: string): TPromise { const element = document.querySelector(selector); if (!element) { - throw new Error('Element not found'); + return TPromise.wrapError(new Error('Element not found')); } const inputElement = element as HTMLInputElement; @@ -106,13 +107,15 @@ class WindowDriver implements IWindowDriver { const event = new Event('input', { bubbles: true, cancelable: true }); inputElement.dispatchEvent(event); + + return TPromise.as(null); } - async getTitle(): TPromise { - return document.title; + getTitle(): TPromise { + return TPromise.as(document.title); } - async isActiveElement(selector: string): TPromise { + isActiveElement(selector: string): TPromise { const element = document.querySelector(selector); if (element !== document.activeElement) { @@ -128,13 +131,13 @@ class WindowDriver implements IWindowDriver { el = el.parentElement; } - throw new Error(`Active element not found. Current active element is '${chain.join(' > ')}'`); + return TPromise.wrapError(new Error(`Active element not found. Current active element is '${chain.join(' > ')}'`)); } - return true; + return TPromise.as(true); } - async getElements(selector: string, recursive: boolean): TPromise { + getElements(selector: string, recursive: boolean): TPromise { const query = document.querySelectorAll(selector); const result: IElement[] = []; @@ -143,14 +146,14 @@ class WindowDriver implements IWindowDriver { result.push(serializeElement(element, recursive)); } - return result; + return TPromise.as(result); } - async typeInEditor(selector: string, text: string): TPromise { + typeInEditor(selector: string, text: string): TPromise { const element = document.querySelector(selector); if (!element) { - throw new Error('Editor not found: ' + selector); + return TPromise.wrapError(new Error('Editor not found: ' + selector)); } const textarea = element as HTMLTextAreaElement; @@ -164,19 +167,21 @@ class WindowDriver implements IWindowDriver { const event = new Event('input', { 'bubbles': true, 'cancelable': true }); textarea.dispatchEvent(event); + + return TPromise.as(null); } - async getTerminalBuffer(selector: string): TPromise { + getTerminalBuffer(selector: string): TPromise { const element = document.querySelector(selector); if (!element) { - throw new Error('Terminal not found: ' + selector); + return TPromise.wrapError(new Error('Terminal not found: ' + selector)); } const xterm: Terminal = (element as any).xterm; if (!xterm) { - throw new Error('Xterm not found: ' + selector); + return TPromise.wrapError(new Error('Xterm not found: ' + selector)); } const lines: string[] = []; @@ -185,27 +190,29 @@ class WindowDriver implements IWindowDriver { lines.push(xterm._core.buffer.translateBufferLineToString(i, true)); } - return lines; + return TPromise.as(lines); } - async writeInTerminal(selector: string, text: string): TPromise { + writeInTerminal(selector: string, text: string): TPromise { const element = document.querySelector(selector); if (!element) { - throw new Error('Element not found'); + return TPromise.wrapError(new Error('Element not found')); } const xterm: Terminal = (element as any).xterm; if (!xterm) { - throw new Error('Xterm not found'); + return TPromise.wrapError(new Error('Xterm not found')); } xterm._core.send(text); + + return TPromise.as(null); } - async openDevTools(): TPromise { - await this.windowService.openDevTools({ mode: 'detach' }); + openDevTools(): TPromise { + return this.windowService.openDevTools({ mode: 'detach' }); } } diff --git a/src/vs/platform/driver/electron-main/driver.ts b/src/vs/platform/driver/electron-main/driver.ts index 46efb22aed5..bd006e35558 100644 --- a/src/vs/platform/driver/electron-main/driver.ts +++ b/src/vs/platform/driver/electron-main/driver.ts @@ -21,7 +21,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' // TODO@joao: bad layering! import { KeybindingIO } from 'vs/workbench/services/keybinding/common/keybindingIO'; import { ScanCodeBinding } from 'vs/workbench/services/keybinding/common/scanCode'; -import { NativeImage } from 'electron'; +import { toWinJsPromise } from 'vs/base/common/async'; class WindowRouter implements IClientRouter { @@ -50,57 +50,57 @@ export class Driver implements IDriver, IWindowDriverRegistry { @IWindowsMainService private windowsService: IWindowsMainService ) { } - async registerWindowDriver(windowId: number): TPromise { + registerWindowDriver(windowId: number): TPromise { this.registeredWindowIds.add(windowId); this.reloadingWindowIds.delete(windowId); this.onDidReloadingChange.fire(); - return this.options; + return TPromise.as(this.options); } - async reloadWindowDriver(windowId: number): TPromise { + reloadWindowDriver(windowId: number): TPromise { this.reloadingWindowIds.add(windowId); + return TPromise.as(null); } - async getWindowIds(): TPromise { - return this.windowsService.getWindows() + getWindowIds(): TPromise { + return TPromise.as(this.windowsService.getWindows() .map(w => w.id) - .filter(id => this.registeredWindowIds.has(id) && !this.reloadingWindowIds.has(id)); + .filter(id => this.registeredWindowIds.has(id) && !this.reloadingWindowIds.has(id))); } - async capturePage(windowId: number): TPromise { - await this.whenUnfrozen(windowId); - - const window = this.windowsService.getWindowById(windowId); - const webContents = window.win.webContents; - const image = await new Promise(c => webContents.capturePage(c)); - const buffer = image.toPNG(); - - return buffer.toString('base64'); + capturePage(windowId: number): TPromise { + return this.whenUnfrozen(windowId).then(() => { + const window = this.windowsService.getWindowById(windowId); + const webContents = window.win.webContents; + return new TPromise(c => webContents.capturePage(image => c(image.toPNG().toString('base64')))); + }); } - async reloadWindow(windowId: number): TPromise { - await this.whenUnfrozen(windowId); - - const window = this.windowsService.getWindowById(windowId); - this.reloadingWindowIds.add(windowId); - this.windowsService.reload(window); + reloadWindow(windowId: number): TPromise { + return this.whenUnfrozen(windowId).then(() => { + const window = this.windowsService.getWindowById(windowId); + this.reloadingWindowIds.add(windowId); + this.windowsService.reload(window); + }); } - async dispatchKeybinding(windowId: number, keybinding: string): TPromise { - await this.whenUnfrozen(windowId); + dispatchKeybinding(windowId: number, keybinding: string): TPromise { + return this.whenUnfrozen(windowId).then(() => { + const [first, second] = KeybindingIO._readUserBinding(keybinding); - const [first, second] = KeybindingIO._readUserBinding(keybinding); - - await this._dispatchKeybinding(windowId, first); - - if (second) { - await this._dispatchKeybinding(windowId, second); - } + return this._dispatchKeybinding(windowId, first).then(() => { + if (second) { + return this._dispatchKeybinding(windowId, second); + } else { + return TPromise.as(null); + } + }); + }); } - private async _dispatchKeybinding(windowId: number, keybinding: SimpleKeybinding | ScanCodeBinding): TPromise { + private _dispatchKeybinding(windowId: number, keybinding: SimpleKeybinding | ScanCodeBinding): TPromise { if (keybinding instanceof ScanCodeBinding) { - throw new Error('ScanCodeBindings not supported'); + return TPromise.wrapError(new Error('ScanCodeBindings not supported')); } const window = this.windowsService.getWindowById(windowId); @@ -135,63 +135,76 @@ export class Driver implements IDriver, IWindowDriverRegistry { webContents.sendInputEvent({ type: 'keyUp', keyCode, modifiers } as any); - await TPromise.timeout(100); + return TPromise.timeout(100); } - async click(windowId: number, selector: string, xoffset?: number, yoffset?: number): TPromise { - const windowDriver = await this.getWindowDriver(windowId); - return windowDriver.click(selector, xoffset, yoffset); + click(windowId: number, selector: string, xoffset?: number, yoffset?: number): TPromise { + return this.getWindowDriver(windowId).then(windowDriver => { + return windowDriver.click(selector, xoffset, yoffset); + }); } - async doubleClick(windowId: number, selector: string): TPromise { - const windowDriver = await this.getWindowDriver(windowId); - return windowDriver.doubleClick(selector); + doubleClick(windowId: number, selector: string): TPromise { + return this.getWindowDriver(windowId).then(windowDriver => { + return windowDriver.doubleClick(selector); + }); } - async setValue(windowId: number, selector: string, text: string): TPromise { - const windowDriver = await this.getWindowDriver(windowId); - return windowDriver.setValue(selector, text); + setValue(windowId: number, selector: string, text: string): TPromise { + return this.getWindowDriver(windowId).then(windowDriver => { + return windowDriver.setValue(selector, text); + }); } - async getTitle(windowId: number): TPromise { - const windowDriver = await this.getWindowDriver(windowId); - return windowDriver.getTitle(); + getTitle(windowId: number): TPromise { + return this.getWindowDriver(windowId).then(windowDriver => { + return windowDriver.getTitle(); + }); } - async isActiveElement(windowId: number, selector: string): TPromise { - const windowDriver = await this.getWindowDriver(windowId); - return windowDriver.isActiveElement(selector); + isActiveElement(windowId: number, selector: string): TPromise { + return this.getWindowDriver(windowId).then(windowDriver => { + return windowDriver.isActiveElement(selector); + }); } - async getElements(windowId: number, selector: string, recursive: boolean): TPromise { - const windowDriver = await this.getWindowDriver(windowId); - return windowDriver.getElements(selector, recursive); + getElements(windowId: number, selector: string, recursive: boolean): TPromise { + return this.getWindowDriver(windowId).then(windowDriver => { + return windowDriver.getElements(selector, recursive); + }); } - async typeInEditor(windowId: number, selector: string, text: string): TPromise { - const windowDriver = await this.getWindowDriver(windowId); - return windowDriver.typeInEditor(selector, text); + typeInEditor(windowId: number, selector: string, text: string): TPromise { + return this.getWindowDriver(windowId).then(windowDriver => { + return windowDriver.typeInEditor(selector, text); + }); } - async getTerminalBuffer(windowId: number, selector: string): TPromise { - const windowDriver = await this.getWindowDriver(windowId); - return windowDriver.getTerminalBuffer(selector); + getTerminalBuffer(windowId: number, selector: string): TPromise { + return this.getWindowDriver(windowId).then(windowDriver => { + return windowDriver.getTerminalBuffer(selector); + }); } - async writeInTerminal(windowId: number, selector: string, text: string): TPromise { - const windowDriver = await this.getWindowDriver(windowId); - return windowDriver.writeInTerminal(selector, text); + writeInTerminal(windowId: number, selector: string, text: string): TPromise { + return this.getWindowDriver(windowId).then(windowDriver => { + return windowDriver.writeInTerminal(selector, text); + }); } - private async getWindowDriver(windowId: number): TPromise { - await this.whenUnfrozen(windowId); - - const router = new WindowRouter(windowId); - const windowDriverChannel = this.windowServer.getChannel('windowDriver', router); - return new WindowDriverChannelClient(windowDriverChannel); + private getWindowDriver(windowId: number): TPromise { + return this.whenUnfrozen(windowId).then(() => { + const router = new WindowRouter(windowId); + const windowDriverChannel = this.windowServer.getChannel('windowDriver', router); + return new WindowDriverChannelClient(windowDriverChannel); + }); } - private async whenUnfrozen(windowId: number): TPromise { + private whenUnfrozen(windowId: number): TPromise { + return toWinJsPromise(this._whenUnfrozen(windowId)); + } + + private async _whenUnfrozen(windowId: number): Promise { while (this.reloadingWindowIds.has(windowId)) { await toPromise(this.onDidReloadingChange.event); } diff --git a/src/vs/platform/driver/node/driver.ts b/src/vs/platform/driver/node/driver.ts index fb61d1d1fa6..1f5df95b0c7 100644 --- a/src/vs/platform/driver/node/driver.ts +++ b/src/vs/platform/driver/node/driver.ts @@ -5,11 +5,10 @@ 'use strict'; -import { TPromise } from 'vs/base/common/winjs.base'; import { IDriver, DriverChannelClient } from 'vs/platform/driver/common/driver'; import { connect as connectNet, Client } from 'vs/base/parts/ipc/node/ipc.net'; -export async function connect(handle: string): TPromise<{ client: Client, driver: IDriver }> { +export async function connect(handle: string): Promise<{ client: Client, driver: IDriver }> { const client = await connectNet(handle, 'driverClient'); const channel = client.getChannel('driver'); const driver = new DriverChannelClient(channel); From 8077d80adb530be82429849c886bd2119523d841 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 2 Jul 2018 09:58:38 -0700 Subject: [PATCH 060/283] Remove title attr from review-comment aria-label covers screen readers, title just shows the tooltip. Fixes Microsoft/vscode-pull-request-github#61 --- .../parts/comments/electron-browser/commentThreadWidget.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/parts/comments/electron-browser/commentThreadWidget.ts b/src/vs/workbench/parts/comments/electron-browser/commentThreadWidget.ts index 7ff7d99f135..f6983c6144d 100644 --- a/src/vs/workbench/parts/comments/electron-browser/commentThreadWidget.ts +++ b/src/vs/workbench/parts/comments/electron-browser/commentThreadWidget.ts @@ -64,7 +64,6 @@ export class CommentNode { this._domNode.setAttribute('aria-label', `${comment.userName}, ${comment.body.value}`); this._domNode.setAttribute('role', 'treeitem'); - this._domNode.title = `${comment.userName}, ${comment.body.value}`; this._clearTimeout = null; } From d028c0923201ed3cd8bf0fde22a4fb4a06c33a52 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 2 Jul 2018 09:58:40 -0700 Subject: [PATCH 061/283] Fix #53445 - only run getSettingsSearchBuildId on official builds --- build/gulpfile.vscode.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 526e5e56f5a..bedba703ace 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -268,10 +268,8 @@ function packageTask(platform, arch, opts) { const date = new Date().toISOString(); const productJsonUpdate = { commit, date, checksums }; - try { + if (shouldSetupSettingsSearch()) { productJsonUpdate.settingsSearchBuildId = getSettingsSearchBuildId(packageJson); - } catch (err) { - console.warn(err); } const productJsonStream = gulp.src(['product.json'], { base: '.' }) @@ -470,9 +468,9 @@ gulp.task('upload-vscode-sourcemaps', ['minify-vscode'], () => { const allConfigDetailsPath = path.join(os.tmpdir(), 'configuration.json'); gulp.task('upload-vscode-configuration', ['generate-vscode-configuration'], () => { - const branch = process.env.BUILD_SOURCEBRANCH; - if (!/\/master$/.test(branch) && branch.indexOf('/release/') < 0) { + if (!shouldSetupSettingsSearch()) { + const branch = process.env.BUILD_SOURCEBRANCH; console.log(`Only runs on master and release branches, not ${branch}`); return; } @@ -495,6 +493,11 @@ gulp.task('upload-vscode-configuration', ['generate-vscode-configuration'], () = })); }); +function shouldSetupSettingsSearch() { + const branch = process.env.BUILD_SOURCEBRANCH; + return typeof branch === 'string' && (/\/master$/.test(branch) || branch.indexOf('/release/') >= 0); +} + function getSettingsSearchBuildId(packageJson) { const previous = util.getPreviousVersion(packageJson.version); From 3c9cff88b73d5eb0659938a97a63eb6357311cab Mon Sep 17 00:00:00 2001 From: Gary Ewan Park Date: Mon, 2 Jul 2018 18:01:54 +0100 Subject: [PATCH 062/283] (doc) Corrected Stack Overflow tag name (#53431) --- .github/ISSUE_TEMPLATE/question.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index 2043e48c7aa..a0435cb1d3e 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -1,9 +1,9 @@ --- name: Question -about: The issue tracker is not for questions. Please ask questions on https://stackoverflow.com/questions/tagged/vscode. +about: The issue tracker is not for questions. Please ask questions on https://stackoverflow.com/questions/tagged/visual-studio-code. --- 🚨 The issue tracker is not for questions 🚨 -If you have a question, please ask it on https://stackoverflow.com/questions/tagged/vscode. +If you have a question, please ask it on https://stackoverflow.com/questions/tagged/visual-studio-code. From 1bf262e9d7f8dcf7d97830933c2793d67af5a95c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 2 Jul 2018 10:02:17 -0700 Subject: [PATCH 063/283] Update xterm.js Includes: - Column selection (changes force selection behavior) - Save attribute on save/restore cursor Fixes #53268 --- package.json | 2 +- .../parts/terminal/electron-browser/media/xterm.css | 7 ++++++- yarn.lock | 6 +++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 18ff8e046dd..8a2febaae1d 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "vscode-nsfw": "1.0.17", "vscode-ripgrep": "^1.0.1", "vscode-textmate": "^4.0.1", - "vscode-xterm": "3.5.0-beta17", + "vscode-xterm": "3.6.0-beta1", "yauzl": "^2.9.1" }, "devDependencies": { diff --git a/src/vs/workbench/parts/terminal/electron-browser/media/xterm.css b/src/vs/workbench/parts/terminal/electron-browser/media/xterm.css index 95d45b4670b..5665309c509 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/media/xterm.css +++ b/src/vs/workbench/parts/terminal/electron-browser/media/xterm.css @@ -162,4 +162,9 @@ .xterm-cursor-pointer { cursor: pointer !important; -} \ No newline at end of file +} + +.xterm.xterm-cursor-crosshair { + /* Column selection mode */ + cursor: crosshair; +} diff --git a/yarn.lock b/yarn.lock index b1404be9a6a..1ac2e5486b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6256,9 +6256,9 @@ vscode-textmate@^4.0.1: dependencies: oniguruma "^7.0.0" -vscode-xterm@3.5.0-beta17: - version "3.5.0-beta17" - resolved "https://registry.yarnpkg.com/vscode-xterm/-/vscode-xterm-3.5.0-beta17.tgz#9a7dd13cdd9a45257aacb815501f71ed788c2e48" +vscode-xterm@3.6.0-beta1: + version "3.6.0-beta1" + resolved "https://registry.yarnpkg.com/vscode-xterm/-/vscode-xterm-3.6.0-beta1.tgz#6ce8b79d33f7506ac3571d92c15f9d65e8b856ca" vso-node-api@^6.1.2-preview: version "6.1.2-preview" From d386cba3e47ef0483f252c3ebc402c7092d96535 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 2 Jul 2018 10:03:15 -0700 Subject: [PATCH 064/283] Update typings --- src/typings/vscode-xterm.d.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/typings/vscode-xterm.d.ts b/src/typings/vscode-xterm.d.ts index 8661eec2dd7..eb8c87e277a 100644 --- a/src/typings/vscode-xterm.d.ts +++ b/src/typings/vscode-xterm.d.ts @@ -124,6 +124,15 @@ declare module 'vscode-xterm' { */ macOptionIsMeta?: boolean; + /** + * Whether holding a modifier key will force normal selection behavior, + * regardless of whether the terminal is in mouse events mode. This will + * also prevent mouse events from being emitted by the terminal. For example, + * this allows you to use xterm.js' regular selection inside tmux with + * mouse mode enabled. + */ + macOptionClickForcesSelection?: boolean; + /** * (EXPERIMENTAL) The type of renderer to use, this allows using the * fallback DOM renderer when canvas is too slow for the environment. The From dcb35c91468f9bbbff8f487b7261e99f2c6736c6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 2 Jul 2018 10:06:26 -0700 Subject: [PATCH 065/283] Expose macOptionClickForcesSelection terminal setting --- src/vs/workbench/parts/terminal/common/terminal.ts | 1 + .../parts/terminal/electron-browser/terminal.contribution.ts | 5 +++++ .../parts/terminal/electron-browser/terminalInstance.ts | 2 ++ 3 files changed, 8 insertions(+) diff --git a/src/vs/workbench/parts/terminal/common/terminal.ts b/src/vs/workbench/parts/terminal/common/terminal.ts index bca473c50b7..81613ecef7b 100644 --- a/src/vs/workbench/parts/terminal/common/terminal.ts +++ b/src/vs/workbench/parts/terminal/common/terminal.ts @@ -70,6 +70,7 @@ export interface ITerminalConfiguration { windows: string[]; }; macOptionIsMeta: boolean; + macOptionClickForcesSelection: boolean; rendererType: 'auto' | 'canvas' | 'dom'; rightClickBehavior: 'default' | 'copyPaste' | 'selectWord'; cursorBlinking: boolean; diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts index d85a4c3c02a..97c1069a8c5 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts @@ -120,6 +120,11 @@ configurationRegistry.registerConfiguration({ 'type': 'boolean', 'default': false }, + 'terminal.integrated.macOptionClickForcesSelection': { + 'description': nls.localize('terminal.integrated.macOptionClickForcesSelection', "Whether to force selection when when using option+click on macOS, this will force a regular (line) selection and disallow the use of column selection mode. This enables copying and pasting using the regular terminal selection when in tmux mouse mode for example."), + 'type': 'boolean', + 'default': false + }, 'terminal.integrated.copyOnSelection': { 'description': nls.localize('terminal.integrated.copyOnSelection', "When set, text selected in the terminal will be copied to the clipboard."), 'type': 'boolean', diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 1a73d38dbd7..9a9bde9425b 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -283,6 +283,7 @@ export class TerminalInstance implements ITerminalInstance { bellStyle: config.enableBell ? 'sound' : 'none', screenReaderMode: accessibilitySupport === 'on', macOptionIsMeta: config.macOptionIsMeta, + macOptionClickForcesSelection: config.macOptionClickForcesSelection, rightClickSelectsWord: config.rightClickBehavior === 'selectWord', // TODO: Guess whether to use canvas or dom better rendererType: config.rendererType === 'auto' ? 'canvas' : config.rendererType, @@ -871,6 +872,7 @@ export class TerminalInstance implements ITerminalInstance { this._setEnableBell(config.enableBell); this._safeSetOption('scrollback', config.scrollback); this._safeSetOption('macOptionIsMeta', config.macOptionIsMeta); + this._safeSetOption('macOptionClickForcesSelection', config.macOptionClickForcesSelection); this._safeSetOption('rightClickSelectsWord', config.rightClickBehavior === 'selectWord'); } From 5f03849cb8ffc77c57b72f28b85bf316f6e60a60 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 2 Jul 2018 10:11:33 -0700 Subject: [PATCH 066/283] Force crosshair cursor in column selection mode --- .../workbench/parts/terminal/electron-browser/media/xterm.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/terminal/electron-browser/media/xterm.css b/src/vs/workbench/parts/terminal/electron-browser/media/xterm.css index 5665309c509..be7a3c7a177 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/media/xterm.css +++ b/src/vs/workbench/parts/terminal/electron-browser/media/xterm.css @@ -166,5 +166,5 @@ .xterm.xterm-cursor-crosshair { /* Column selection mode */ - cursor: crosshair; + cursor: crosshair !important; } From e2025178d9a2a61d633b509e9278b9355913f8a4 Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Mon, 2 Jul 2018 10:34:24 -0700 Subject: [PATCH 067/283] fixes #53309 (#53456) --- .../parts/files/electron-browser/views/explorerViewer.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts b/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts index 3b4181ae326..3f363cbdbd2 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts +++ b/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts @@ -56,6 +56,7 @@ import { IDialogService, IConfirmationResult, IConfirmation, getConfirmMessage } import { INotificationService } from 'vs/platform/notification/common/notification'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { fillInContextMenuActions } from 'vs/platform/actions/browser/menuItemActionItem'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; export class FileDataSource implements IDataSource { constructor( @@ -405,6 +406,7 @@ export class FileController extends WorkbenchTreeController implements IDisposab @IMenuService private menuService: IMenuService, @IContextKeyService contextKeyService: IContextKeyService, @IClipboardService private clipboardService: IClipboardService, + @IKeybindingService private keybindingService: IKeybindingService, @IConfigurationService configurationService: IConfigurationService ) { super({ clickBehavior: ClickBehavior.ON_MOUSE_UP /* do not change to not break DND */ }, configurationService); @@ -537,6 +539,9 @@ export class FileController extends WorkbenchTreeController implements IDisposab tree.domFocus(); } }, + getKeyBinding: (action) => { + return this.keybindingService.lookupKeybinding(action.id); + }, getActionsContext: () => selection && selection.indexOf(stat) >= 0 ? selection.map((fs: ExplorerItem) => fs.resource) : stat instanceof ExplorerItem ? [stat.resource] : [] From 832b07b5777c98828006831f10a2a3158398b660 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 2 Jul 2018 10:49:31 -0700 Subject: [PATCH 068/283] Fix terminal links not working after showing the terminal panel Fixes #37103 --- .../terminal/electron-browser/terminalInstance.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 9a9bde9425b..94bacf0b29d 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -971,12 +971,15 @@ export class TerminalInstance implements ITerminalInstance { this._xterm.resize(cols, rows); if (this._isVisible) { - // Force the renderer to unpause by simulating an IntersectionObserver event. This - // is to fix an issue where dragging the window to the top of the screen to maximize - // on Winodws/Linux would fire an event saying that the terminal was not visible. - // This should only force a refresh if one is needed. + // HACK: Force the renderer to unpause by simulating an IntersectionObserver event. + // This is to fix an issue where dragging the window to the top of the screen to + // maximize on Windows/Linux would fire an event saying that the terminal was not + // visible. if (this._xterm.getOption('rendererType') === 'canvas') { this._xterm._core.renderer.onIntersectionChange({ intersectionRatio: 1 }); + // HACK: Force a refresh of the screen to ensure links are refresh corrected. + // This can probably be removed when the above hack is fixed in Chromium. + this._xterm.refresh(0, this._xterm.rows - 1); } } } From a115c730307f12fc50972fa4f5d97b9574c14b65 Mon Sep 17 00:00:00 2001 From: Yu Zhang <583181285@qq.com> Date: Tue, 3 Jul 2018 02:16:20 +0800 Subject: [PATCH 069/283] Make titlebar more consistent with Windows 10 (#53211) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 💄 accurate window controls width * 💄 transition duration --- .../browser/parts/titlebar/media/titlebarpart.css | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 770803a9a34..09e63db3f9c 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -82,16 +82,15 @@ .monaco-workbench > .part.titlebar > .window-controls-container > .window-icon { display: inline-block; -webkit-app-region: no-drag; - -webkit-transition: background-color .2s; - transition: background-color .2s; + -webkit-transition: background-color .1s; + transition: background-color .1s; height: 100%; width: 33.34%; - background-size: 20%; + background-size: 21.74%; background-position: center center; background-repeat: no-repeat; } - .monaco-workbench > .part.titlebar > .window-controls-container > .window-icon svg { shape-rendering: crispEdges; text-align: center; From 1d1fe28b2749a3c62c21af608b2514a5def4ea08 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 2 Jul 2018 11:04:31 -0700 Subject: [PATCH 070/283] Fix #53455 - categories without children should not be shown --- src/vs/workbench/parts/preferences/browser/settingsTree.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/preferences/browser/settingsTree.ts b/src/vs/workbench/parts/preferences/browser/settingsTree.ts index e86b79e6e54..ac53257f46e 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsTree.ts @@ -243,7 +243,9 @@ function _resolveSettingsTree(tocData: ITOCEntry, allSettings: Set): I return { id: tocData.id, label: tocData.label, - children: tocData.children.map(child => _resolveSettingsTree(child, allSettings)) + children: tocData.children + .map(child => _resolveSettingsTree(child, allSettings)) + .filter(child => (child.children && child.children.length) || (child.settings && child.settings.length)) }; } From 7f6ea96c1642ec7d2c152c3ef0c1a924fc53a5d0 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 2 Jul 2018 11:16:55 -0700 Subject: [PATCH 071/283] Revert "Fix #53445 - only run getSettingsSearchBuildId on official builds" This reverts commit d028c0923201ed3cd8bf0fde22a4fb4a06c33a52. --- build/gulpfile.vscode.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index bedba703ace..526e5e56f5a 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -268,8 +268,10 @@ function packageTask(platform, arch, opts) { const date = new Date().toISOString(); const productJsonUpdate = { commit, date, checksums }; - if (shouldSetupSettingsSearch()) { + try { productJsonUpdate.settingsSearchBuildId = getSettingsSearchBuildId(packageJson); + } catch (err) { + console.warn(err); } const productJsonStream = gulp.src(['product.json'], { base: '.' }) @@ -468,9 +470,9 @@ gulp.task('upload-vscode-sourcemaps', ['minify-vscode'], () => { const allConfigDetailsPath = path.join(os.tmpdir(), 'configuration.json'); gulp.task('upload-vscode-configuration', ['generate-vscode-configuration'], () => { + const branch = process.env.BUILD_SOURCEBRANCH; - if (!shouldSetupSettingsSearch()) { - const branch = process.env.BUILD_SOURCEBRANCH; + if (!/\/master$/.test(branch) && branch.indexOf('/release/') < 0) { console.log(`Only runs on master and release branches, not ${branch}`); return; } @@ -493,11 +495,6 @@ gulp.task('upload-vscode-configuration', ['generate-vscode-configuration'], () = })); }); -function shouldSetupSettingsSearch() { - const branch = process.env.BUILD_SOURCEBRANCH; - return typeof branch === 'string' && (/\/master$/.test(branch) || branch.indexOf('/release/') >= 0); -} - function getSettingsSearchBuildId(packageJson) { const previous = util.getPreviousVersion(packageJson.version); From 1a4543e94036624a13d044021a4f930981ba2ab2 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Sun, 1 Jul 2018 23:38:29 -0700 Subject: [PATCH 072/283] Fix markdown code not closed --- src/vs/vscode.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 10e2b712d23..a341359fdeb 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -3948,7 +3948,7 @@ declare module 'vscode' { * * Diagnostics with this tag are rendered faded out. The amount of fading * is controlled by the `"editorUnnecessaryCode.opacity"` theme color. For - * example, `"editorUnnecessaryCode.opacity": "#000000c0" will render the + * example, `"editorUnnecessaryCode.opacity": "#000000c0"` will render the * code with 75% opacity. For high contrast themes, use the * `"editorUnnecessaryCode.border"` the color to underline unnecessary code * instead of fading it out. From 6adba50143e8f4f59485d676f721fc1f7f6878ff Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 2 Jul 2018 12:53:58 -0700 Subject: [PATCH 073/283] Fix spelling --- src/vs/vscode.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index a341359fdeb..78fc4ef3556 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -3950,7 +3950,7 @@ declare module 'vscode' { * is controlled by the `"editorUnnecessaryCode.opacity"` theme color. For * example, `"editorUnnecessaryCode.opacity": "#000000c0"` will render the * code with 75% opacity. For high contrast themes, use the - * `"editorUnnecessaryCode.border"` the color to underline unnecessary code + * `"editorUnnecessaryCode.border"` theme color to underline unnecessary code * instead of fading it out. */ Unnecessary = 1, From 7f02ffaab3f205e619844968f92cc127602bb5f2 Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Mon, 2 Jul 2018 12:55:02 -0700 Subject: [PATCH 074/283] remove auto save ipc --- src/vs/code/electron-main/menus.ts | 3 ++- src/vs/workbench/electron-browser/window.ts | 28 +-------------------- 2 files changed, 3 insertions(+), 28 deletions(-) diff --git a/src/vs/code/electron-main/menus.ts b/src/vs/code/electron-main/menus.ts index 50735d5f45a..bdac1abce4e 100644 --- a/src/vs/code/electron-main/menus.ts +++ b/src/vs/code/electron-main/menus.ts @@ -385,7 +385,8 @@ export class CodeMenu { const saveAllFiles = this.createMenuItem(nls.localize({ key: 'miSaveAll', comment: ['&& denotes a mnemonic'] }, "Save A&&ll"), 'workbench.action.files.saveAll'); const autoSaveEnabled = [AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE].some(s => this.currentAutoSaveSetting === s); - const autoSave = new MenuItem(this.likeAction('vscode.toggleAutoSave', { label: this.mnemonicLabel(nls.localize('miAutoSave', "Auto Save")), type: 'checkbox', checked: autoSaveEnabled, enabled: this.windowsMainService.getWindowCount() > 0, click: () => this.windowsMainService.sendToFocused('vscode.toggleAutoSave') }, false)); + + const autoSave = this.createMenuItem(this.mnemonicLabel(nls.localize('miAutoSave', "Auto Save")), 'workbench.action.toggleAutoSave', this.windowsMainService.getWindowCount() > 0, autoSaveEnabled); const preferences = this.getPreferencesMenu(); diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index ccde056f231..a87a8f73f31 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -8,14 +8,13 @@ import * as nls from 'vs/nls'; import URI from 'vs/base/common/uri'; import * as errors from 'vs/base/common/errors'; -import * as types from 'vs/base/common/types'; import { TPromise } from 'vs/base/common/winjs.base'; import * as arrays from 'vs/base/common/arrays'; import * as objects from 'vs/base/common/objects'; import * as DOM from 'vs/base/browser/dom'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IAction, Action } from 'vs/base/common/actions'; -import { AutoSaveConfiguration, IFileService } from 'vs/platform/files/common/files'; +import { IFileService } from 'vs/platform/files/common/files'; import { toResource, IUntitledResourceInput } from 'vs/workbench/common/editor'; import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -38,7 +37,6 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { fillInActionBarActions } from 'vs/platform/actions/browser/menuItemActionItem'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { LifecyclePhase, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces'; import { IIntegrityService } from 'vs/platform/integrity/common/integrity'; @@ -60,8 +58,6 @@ const TextInputActions: IAction[] = [ export class ElectronWindow extends Themable { - private static readonly AUTO_SAVE_SETTING = 'files.autoSave'; - private touchBarMenu: IMenu; private touchBarUpdater: RunOnceScheduler; private touchBarDisposables: IDisposable[]; @@ -181,11 +177,6 @@ export class ElectronWindow extends Themable { this.notificationService.info(message); }); - // Support toggling auto save - ipc.on('vscode.toggleAutoSave', () => { - this.toggleAutoSave(); - }); - // Fullscreen Events ipc.on('vscode:enterFullScreen', () => { this.lifecycleService.when(LifecyclePhase.Running).then(() => { @@ -507,23 +498,6 @@ export class ElectronWindow extends Themable { }); } - private toggleAutoSave(): void { - const setting = this.configurationService.inspect(ElectronWindow.AUTO_SAVE_SETTING); - let userAutoSaveConfig = setting.user; - if (types.isUndefinedOrNull(userAutoSaveConfig)) { - userAutoSaveConfig = setting.default; // use default if setting not defined - } - - let newAutoSaveValue: string; - if ([AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE].some(s => s === userAutoSaveConfig)) { - newAutoSaveValue = AutoSaveConfiguration.OFF; - } else { - newAutoSaveValue = AutoSaveConfiguration.AFTER_DELAY; - } - - this.configurationService.updateValue(ElectronWindow.AUTO_SAVE_SETTING, newAutoSaveValue, ConfigurationTarget.USER); - } - dispose(): void { this.touchBarDisposables = dispose(this.touchBarDisposables); From a07dc890ba6c0a650aac188eebbea7951d6f7469 Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Mon, 2 Jul 2018 13:20:12 -0700 Subject: [PATCH 075/283] remove query selector call refs #52884 --- .../browser/parts/titlebar/titlebarPart.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 923c1c25051..70cc8f3cec2 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -31,7 +31,7 @@ import { isMacintosh, isWindows, isLinux } from 'vs/base/common/platform'; import URI from 'vs/base/common/uri'; import { Color } from 'vs/base/common/color'; import { trim } from 'vs/base/common/strings'; -import { addDisposableListener, EventType, EventHelper, Dimension, addClass, removeClass } from 'vs/base/browser/dom'; +import { addDisposableListener, EventType, EventHelper, Dimension } from 'vs/base/browser/dom'; import { IPartService } from 'vs/workbench/services/part/common/partService'; export class TitlebarPart extends Part implements ITitleService { @@ -47,6 +47,7 @@ export class TitlebarPart extends Part implements ITitleService { private titleContainer: Builder; private title: Builder; private windowControls: Builder; + private maxRestoreControl: Builder; private appIcon: Builder; private pendingTitle: string; @@ -297,7 +298,7 @@ export class TitlebarPart extends Part implements ITitleService { }); // Restore - $(this.windowControls).div({ class: 'window-icon window-max-restore' }).on(EventType.CLICK, () => { + this.maxRestoreControl = $(this.windowControls).div({ class: 'window-icon window-max-restore' }).on(EventType.CLICK, () => { this.windowService.isMaximized().then((maximized) => { if (maximized) { return this.windowService.unmaximizeWindow(); @@ -332,17 +333,16 @@ export class TitlebarPart extends Part implements ITitleService { } private onDidChangeMaximized(maximized: boolean) { - const element = $(this.titleContainer).getHTMLElement().querySelector('.window-max-restore') as HTMLElement; - if (!element) { + if (!this.maxRestoreControl) { return; } if (maximized) { - removeClass(element, 'window-maximize'); - addClass(element, 'window-unmaximize'); + this.maxRestoreControl.removeClass('window-maximize'); + this.maxRestoreControl.addClass('window-unmaximize'); } else { - removeClass(element, 'window-unmaximize'); - addClass(element, 'window-maximize'); + this.maxRestoreControl.removeClass('window-unmaximize'); + this.maxRestoreControl.addClass('window-maximize'); } } From 3a862bae7a5819367663061656c1bf9569895c74 Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Mon, 2 Jul 2018 13:31:04 -0700 Subject: [PATCH 076/283] only use the builder pattern for menubarPart refs #52884 --- src/vs/base/browser/dom.ts | 1 + .../workbench/browser/parts/menubar/menubarPart.ts | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 6e7c479e58d..02b3d4115ea 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -819,6 +819,7 @@ export const EventType = { MOUSE_OVER: 'mouseover', MOUSE_MOVE: 'mousemove', MOUSE_OUT: 'mouseout', + MOUSE_ENTER: 'mouseenter', MOUSE_LEAVE: 'mouseleave', CONTEXT_MENU: 'contextmenu', WHEEL: 'wheel', diff --git a/src/vs/workbench/browser/parts/menubar/menubarPart.ts b/src/vs/workbench/browser/parts/menubar/menubarPart.ts index af50fa982b4..e5cd5b529af 100644 --- a/src/vs/workbench/browser/parts/menubar/menubarPart.ts +++ b/src/vs/workbench/browser/parts/menubar/menubarPart.ts @@ -489,7 +489,7 @@ export class MenubarPart extends Part { menu.onDidChange(() => updateActions(menu, this.customMenus[menuIndex].actions)); updateActions(menu, this.customMenus[menuIndex].actions); - this.customMenus[menuIndex].titleElement.on(EventType.CLICK, (event) => { + this.customMenus[menuIndex].titleElement.on(EventType.CLICK, () => { if (this._modifierKeyStatus && (this._modifierKeyStatus.shiftKey || this._modifierKeyStatus.ctrlKey)) { return; // supress keyboard shortcuts that shouldn't conflict } @@ -498,21 +498,21 @@ export class MenubarPart extends Part { this.isFocused = !this.isFocused; }); - this.customMenus[menuIndex].titleElement.getHTMLElement().onmouseenter = () => { + this.customMenus[menuIndex].titleElement.on(EventType.MOUSE_ENTER, () => { if (this.isFocused && !this.isCurrentMenu(menuIndex)) { this.toggleCustomMenu(menuIndex); } - }; + }); - this.customMenus[menuIndex].titleElement.getHTMLElement().onmouseleave = () => { + this.customMenus[menuIndex].titleElement.on(EventType.MOUSE_LEAVE, () => { if (!this.isFocused) { this.cleanupCustomMenu(); } - }; + }); - this.customMenus[menuIndex].titleElement.getHTMLElement().onblur = () => { + this.customMenus[menuIndex].titleElement.on(EventType.BLUR, () => { this.cleanupCustomMenu(); - }; + }); } this.container.off(EventType.KEY_DOWN); From 86c05da801cc99d5be9a52f116cad3149d743446 Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Mon, 2 Jul 2018 14:01:13 -0700 Subject: [PATCH 077/283] Fixes #51521, TypeError: Cannot read property 'close' of undefined --- src/vs/platform/issue/electron-main/issueService.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/issue/electron-main/issueService.ts b/src/vs/platform/issue/electron-main/issueService.ts index 05a43fe1992..cebb4e61237 100644 --- a/src/vs/platform/issue/electron-main/issueService.ts +++ b/src/vs/platform/issue/electron-main/issueService.ts @@ -78,8 +78,10 @@ export class IssueService implements IIssueService { this._issueWindow.on('close', () => this._issueWindow = null); this._issueParentWindow.on('closed', () => { - this._issueWindow.close(); - this._issueWindow = null; + if (this._issueWindow) { + this._issueWindow.close(); + this._issueWindow = null; + } }); } @@ -139,8 +141,10 @@ export class IssueService implements IIssueService { this._processExplorerWindow.on('close', () => this._processExplorerWindow = void 0); parentWindow.on('close', () => { - this._processExplorerWindow.close(); - this._processExplorerWindow = null; + if (this._processExplorerWindow) { + this._processExplorerWindow.close(); + this._processExplorerWindow = null; + } }); } From 68198cddecb257410465022117205b5078dbb5a0 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 2 Jul 2018 13:47:51 -0700 Subject: [PATCH 078/283] Don't try injecting vscode api into webviews that have scripts disabled Fixes #53463 --- src/vs/workbench/parts/webview/electron-browser/webview-pre.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/webview/electron-browser/webview-pre.js b/src/vs/workbench/parts/webview/electron-browser/webview-pre.js index 51a9d42aa32..116392df7e9 100644 --- a/src/vs/workbench/parts/webview/electron-browser/webview-pre.js +++ b/src/vs/workbench/parts/webview/electron-browser/webview-pre.js @@ -172,7 +172,7 @@ } // apply default script - if (enableWrappedPostMessage) { + if (enableWrappedPostMessage && options.allowScripts) { const defaultScript = newDocument.createElement('script'); defaultScript.textContent = ` const acquireVsCodeApi = (function() { From 090d912b0d9bdf0274bbbb86a73d4f30757098ad Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 2 Jul 2018 14:05:29 -0700 Subject: [PATCH 079/283] Adding workaround for release notes not having syntax highlighting on first load Fixes #53413 --- .../electron-browser/releaseNotesEditor.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts b/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts index c32bfc0d3c4..9eef2facfd9 100644 --- a/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts +++ b/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts @@ -51,6 +51,7 @@ export class ReleaseNotesManager { private _releaseNotesCache: { [version: string]: TPromise; } = Object.create(null); private _currentReleaseNotes: WebviewEditorInput | undefined = undefined; + private _lastText: string; public constructor( @IEnvironmentService private readonly _environmentService: IEnvironmentService, @@ -60,14 +61,25 @@ export class ReleaseNotesManager { @IRequestService private readonly _requestService: IRequestService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @IEditorService private readonly _editorService: IEditorService, - @IWebviewEditorService private readonly _webviewEditorService: IWebviewEditorService, - ) { } + @IWebviewEditorService private readonly _webviewEditorService: IWebviewEditorService + ) { + TokenizationRegistry.onDidChange(async () => { + if (!this._currentReleaseNotes || !this._lastText) { + return; + } + const html = await this.renderBody(this._lastText); + if (this._currentReleaseNotes) { + this._currentReleaseNotes.html = html; + } + }); + } public async show( accessor: ServicesAccessor, version: string ): TPromise { const releaseNoteText = await this.loadReleaseNotes(version); + this._lastText = releaseNoteText; const html = await this.renderBody(releaseNoteText); const title = nls.localize('releaseNotesInputName', "Release Notes: {0}", version); @@ -154,9 +166,10 @@ export class ReleaseNotesManager { } private async renderBody(text: string) { + const content = await this.renderContent(text); const colorMap = TokenizationRegistry.getColorMap(); const css = generateTokensCSSForColorMap(colorMap); - const body = renderBody(await this.renderContent(text), css); + const body = renderBody(content, css); return body; } From 7b474b7febe52b0280c4a0f4a1f67d9dcfc7e07d Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Mon, 2 Jul 2018 14:31:41 -0700 Subject: [PATCH 080/283] Show reply button on newly created comment thread --- .../parts/comments/electron-browser/commentThreadWidget.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/parts/comments/electron-browser/commentThreadWidget.ts b/src/vs/workbench/parts/comments/electron-browser/commentThreadWidget.ts index f6983c6144d..62589397bd0 100644 --- a/src/vs/workbench/parts/comments/electron-browser/commentThreadWidget.ts +++ b/src/vs/workbench/parts/comments/electron-browser/commentThreadWidget.ts @@ -349,7 +349,7 @@ export class ReviewZoneWidget extends ZoneWidget { this.setCommentEditorDecorations(); // Only add the additional step of clicking a reply button to expand the textarea when there are existing comments - this.createCommentButton(); + this.createReplyButton(); this._localToDispose.push(this._commentEditor.onKeyDown((ev: IKeyboardEvent) => { const hasExistingComments = this._commentThread.comments.length > 0; @@ -383,6 +383,8 @@ export class ReviewZoneWidget extends ZoneWidget { new Range(lineNumber, 1, lineNumber, 1), this._commentEditor.getValue() ); + + this.createReplyButton(); } this._commentEditor.setValue(''); @@ -414,7 +416,7 @@ export class ReviewZoneWidget extends ZoneWidget { } } - createCommentButton() { + createReplyButton() { const hasExistingComments = this._commentThread.comments.length > 0; if (hasExistingComments) { this._reviewThreadReplyButton = $('button.review-thread-reply-button').appendTo(this._commentForm).getHTMLElement(); From 5dcaea7f2ec8bb20b66747e9e2d63398f6c8f1db Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Mon, 2 Jul 2018 14:54:02 -0700 Subject: [PATCH 081/283] Focus on input for new comments, fixes Microsoft/vscode-pull-request-github/issues/14 --- .../parts/comments/electron-browser/commentThreadWidget.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/comments/electron-browser/commentThreadWidget.ts b/src/vs/workbench/parts/comments/electron-browser/commentThreadWidget.ts index 62589397bd0..e06e3c0ea21 100644 --- a/src/vs/workbench/parts/comments/electron-browser/commentThreadWidget.ts +++ b/src/vs/workbench/parts/comments/electron-browser/commentThreadWidget.ts @@ -436,7 +436,10 @@ export class ReviewZoneWidget extends ZoneWidget { } }); } else { - dom.addClass(this._commentForm, 'expand'); + if (!dom.hasClass(this._commentForm, 'expand')) { + dom.addClass(this._commentForm, 'expand'); + this._commentEditor.focus(); + } } } From a8c07148d8fb230a170640e7aba269c4cf2e0acf Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Mon, 2 Jul 2018 15:59:57 -0700 Subject: [PATCH 082/283] moving the resizer to be added last and fixing sizing --- .../workbench/browser/parts/titlebar/media/titlebarpart.css | 2 +- src/vs/workbench/browser/parts/titlebar/titlebarPart.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 09e63db3f9c..a8330e6fc88 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -45,7 +45,7 @@ position: absolute; top: 0; width: 100%; - height: 1px; + height: 20%; } .monaco-workbench.windows > .part.titlebar > .window-title, diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 70cc8f3cec2..d7112d84a45 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -259,9 +259,6 @@ export class TitlebarPart extends Part implements ITitleService { this.windowService.closeWindow().then(null, errors.onUnexpectedError); }); - - // Resizer - $(this.titleContainer).div({ class: 'resizer' }); } // Title @@ -316,6 +313,9 @@ export class TitlebarPart extends Part implements ITitleService { const isMaximized = this.windowService.getConfiguration().maximized ? true : false; this.onDidChangeMaximized(isMaximized); this.windowService.onDidChangeMaximize(this.onDidChangeMaximized, this); + + // Resizer + $(this.titleContainer).div({ class: 'resizer' }); } // Since the title area is used to drag the window, we do not want to steal focus from the From 74fcd9ae313957df6ed910966616225d67831a24 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 2 Jul 2018 18:06:44 -0700 Subject: [PATCH 083/283] Don't use tpromise + async in webview methods #53442 --- .../api/electron-browser/mainThreadWebview.ts | 4 ++-- .../webview/electron-browser/webviewEditor.ts | 20 ++++++++--------- .../electron-browser/webviewEditorService.ts | 22 ++++++++++--------- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/api/electron-browser/mainThreadWebview.ts b/src/vs/workbench/api/electron-browser/mainThreadWebview.ts index 4c2d53c73a3..8def9ec323a 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadWebview.ts @@ -117,7 +117,7 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv this._webviewService.revealWebview(webview, targetGroup || this._editorGroupService.activeGroup, preserveFocus); } - public async $postMessage(handle: WebviewPanelHandle, message: any): TPromise { + public $postMessage(handle: WebviewPanelHandle, message: any): TPromise { const webview = this.getWebview(handle); const editors = this._editorService.visibleControls .filter(e => e instanceof WebviewEditor) @@ -128,7 +128,7 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv editor.sendMessage(message); } - return (editors.length > 0); + return TPromise.as(editors.length > 0); } public $registerSerializer(viewType: string): void { diff --git a/src/vs/workbench/parts/webview/electron-browser/webviewEditor.ts b/src/vs/workbench/parts/webview/electron-browser/webviewEditor.ts index b4d98e02caf..b69210804bc 100644 --- a/src/vs/workbench/parts/webview/electron-browser/webviewEditor.ts +++ b/src/vs/workbench/parts/webview/electron-browser/webviewEditor.ts @@ -163,22 +163,22 @@ export class WebviewEditor extends BaseWebviewEditor { super.clearInput(); } - async setInput(input: WebviewEditorInput, options: EditorOptions, token: CancellationToken): TPromise { + setInput(input: WebviewEditorInput, options: EditorOptions, token: CancellationToken): Thenable { if (this.input) { (this.input as WebviewEditorInput).releaseWebview(this); this._webview = undefined; this._webviewContent = undefined; } - await super.setInput(input, options, token); + return super.setInput(input, options, token) + .then(() => input.resolve()) + .then(() => { + if (token.isCancellationRequested) { + return; + } - await input.resolve(); - - if (token.isCancellationRequested) { - return; - } - - input.updateGroup(this.group.id); - this.updateWebview(input); + input.updateGroup(this.group.id); + this.updateWebview(input); + }); } private updateWebview(input: WebviewEditorInput) { diff --git a/src/vs/workbench/parts/webview/electron-browser/webviewEditorService.ts b/src/vs/workbench/parts/webview/electron-browser/webviewEditorService.ts index 344a2153402..d5c548c85f4 100644 --- a/src/vs/workbench/parts/webview/electron-browser/webviewEditorService.ts +++ b/src/vs/workbench/parts/webview/electron-browser/webviewEditorService.ts @@ -134,16 +134,18 @@ export class WebviewEditorService implements IWebviewEditorService { canRevive: (_webview) => { return true; }, - reviveWebview: async (webview: WebviewEditorInput): TPromise => { - const didRevive = await this.tryRevive(webview); - if (didRevive) { - return; - } - // A reviver may not be registered yet. Put into queue and resolve promise when we can revive - let resolve: (value: void) => void; - const promise = new TPromise(r => { resolve = r; }); - this._awaitingRevival.push({ input: webview, resolve }); - return promise; + reviveWebview: (webview: WebviewEditorInput): TPromise => { + return this.tryRevive(webview).then(didRevive => { + if (didRevive) { + return TPromise.as(void 0); + } + + // A reviver may not be registered yet. Put into queue and resolve promise when we can revive + let resolve: (value: void) => void; + const promise = new TPromise(r => { resolve = r; }); + this._awaitingRevival.push({ input: webview, resolve }); + return promise; + }); } }); From ea5620dd8d768b287a7dda401afb23b1af14f81d Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 8 Jun 2018 17:30:59 -0700 Subject: [PATCH 084/283] SearchProvider invoked on every keypress --- extensions/search-rg/src/arrays.ts | 75 ++ .../search-rg/src/cachedSearchProvider.ts | 275 +++++++ extensions/search-rg/src/charCode.ts | 422 +++++++++++ extensions/search-rg/src/comparers.ts | 108 +++ extensions/search-rg/src/extension.ts | 10 +- extensions/search-rg/src/fileSearchScorer.ts | 618 ++++++++++++++++ extensions/search-rg/src/filters.ts | 224 ++++++ extensions/search-rg/src/ripgrepFileSearch.ts | 4 +- extensions/search-rg/src/strings.ts | 683 ++++++++++++++++++ src/vs/vscode.proposed.d.ts | 8 +- src/vs/workbench/api/node/extHostSearch.ts | 311 +------- 11 files changed, 2447 insertions(+), 291 deletions(-) create mode 100644 extensions/search-rg/src/arrays.ts create mode 100644 extensions/search-rg/src/cachedSearchProvider.ts create mode 100644 extensions/search-rg/src/charCode.ts create mode 100644 extensions/search-rg/src/comparers.ts create mode 100644 extensions/search-rg/src/fileSearchScorer.ts create mode 100644 extensions/search-rg/src/filters.ts create mode 100644 extensions/search-rg/src/strings.ts diff --git a/extensions/search-rg/src/arrays.ts b/extensions/search-rg/src/arrays.ts new file mode 100644 index 00000000000..145ea2fd2a4 --- /dev/null +++ b/extensions/search-rg/src/arrays.ts @@ -0,0 +1,75 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Asynchronous variant of `top()` allowing for splitting up work in batches between which the event loop can run. + * + * Returns the top N elements from the array. + * + * Faster than sorting the entire array when the array is a lot larger than N. + * + * @param array The unsorted array. + * @param compare A sort function for the elements. + * @param n The number of elements to return. + * @param batch The number of elements to examine before yielding to the event loop. + * @return The first n elemnts from array when sorted with compare. + */ +export function topAsync(array: T[], compare: (a: T, b: T) => number, n: number, batch: number): Promise { + // TODO@roblou cancellation + + if (n === 0) { + return Promise.resolve([]); + } + let canceled = false; + return new Promise((resolve, reject) => { + (async () => { + const o = array.length; + const result = array.slice(0, n).sort(compare); + for (let i = n, m = Math.min(n + batch, o); i < o; i = m, m = Math.min(m + batch, o)) { + if (i > n) { + await new Promise(resolve => setTimeout(resolve, 0)); // nextTick() would starve I/O. + } + if (canceled) { + throw new Error('canceled'); + } + topStep(array, compare, result, i, m); + } + return result; + })() + .then(resolve, reject); + }); +} + +function topStep(array: T[], compare: (a: T, b: T) => number, result: T[], i: number, m: number): void { + for (const n = result.length; i < m; i++) { + const element = array[i]; + if (compare(element, result[n - 1]) < 0) { + result.pop(); + const j = findFirstInSorted(result, e => compare(element, e) < 0); + result.splice(j, 0, element); + } + } +} + +/** + * Takes a sorted array and a function p. The array is sorted in such a way that all elements where p(x) is false + * are located before all elements where p(x) is true. + * @returns the least x for which p(x) is true or array.length if no element fullfills the given function. + */ +export function findFirstInSorted(array: T[], p: (x: T) => boolean): number { + let low = 0, high = array.length; + if (high === 0) { + return 0; // no children + } + while (low < high) { + let mid = Math.floor((low + high) / 2); + if (p(array[mid])) { + high = mid; + } else { + low = mid + 1; + } + } + return low; +} \ No newline at end of file diff --git a/extensions/search-rg/src/cachedSearchProvider.ts b/extensions/search-rg/src/cachedSearchProvider.ts new file mode 100644 index 00000000000..201a279123f --- /dev/null +++ b/extensions/search-rg/src/cachedSearchProvider.ts @@ -0,0 +1,275 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as path from 'path'; +import * as vscode from 'vscode'; +import * as arrays from './arrays'; +import { compareItemsByScore, IItemAccessor, prepareQuery, ScorerCache } from './fileSearchScorer'; +import * as strings from './strings'; + +interface IProviderArgs { + query: vscode.FileSearchQuery; + options: vscode.FileSearchOptions; + progress: vscode.Progress; + token: vscode.CancellationToken; +} + +export class CachedSearchProvider { + + private static readonly BATCH_SIZE = 512; + + private caches: { [cacheKey: string]: Cache; } = Object.create(null); + + constructor(private outputChannel: vscode.OutputChannel) { + } + + provideFileSearchResults(provider: vscode.SearchProvider, query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + const onResult = (result: IInternalFileMatch) => { + progress.report(result.relativePath); + }; + + const providerArgs = { + query, options, progress, token + }; + + let sortedSearch = this.trySortedSearchFromCache(providerArgs, onResult); + if (!sortedSearch) { + const engineOpts = options.maxResults ? + { + ...options, + ...{ maxResults: 1e9 } + } : + options; + providerArgs.options = engineOpts; + + sortedSearch = this.doSortedSearch(providerArgs, provider); + } + + return sortedSearch.then(rawMatches => { + rawMatches.forEach(onResult); + }); + } + + private doSortedSearch(args: IProviderArgs, provider: vscode.SearchProvider): Promise { + let searchPromise: Promise; + let allResultsPromise = new Promise((c, e) => { + let results: IInternalFileMatch[] = []; + + const onResult = (progress: OneOrMore) => { + if (Array.isArray(progress)) { + results.push(...progress); + } else { + results.push(progress); + } + }; + + searchPromise = this.doSearch(args, provider, onResult, CachedSearchProvider.BATCH_SIZE) + .then(() => { + c(results); + }, e); + }); + + let cache: Cache; + if (args.query.cacheKey) { + cache = this.getOrCreateCache(args.query.cacheKey); // TODO include folder in cache key + cache.resultsToSearchCache[args.query.pattern] = { finished: allResultsPromise }; + allResultsPromise.then(null, err => { + delete cache.resultsToSearchCache[args.query.pattern]; + }); + } + + return new Promise((c, e) => { + allResultsPromise.then(results => { + const scorerCache: ScorerCache = cache ? cache.scorerCache : Object.create(null); + return this.sortResults(args, results, scorerCache) + .then(c); + }, e); + }); + } + + private getOrCreateCache(cacheKey: string): Cache { + const existing = this.caches[cacheKey]; + if (existing) { + return existing; + } + return this.caches[cacheKey] = new Cache(); + } + + private trySortedSearchFromCache(args: IProviderArgs, onResult: (result: IInternalFileMatch) => void): Promise { + const cache = args.query.cacheKey && this.caches[args.query.cacheKey]; + if (!cache) { + return undefined; + } + + const cached = this.getResultsFromCache(cache, args.query.pattern, onResult); + if (cached) { + return cached.then(([results, cacheStats]) => this.sortResults(args, results, cache.scorerCache)); + } + + return undefined; + } + + private sortResults(args: IProviderArgs, results: IInternalFileMatch[], scorerCache: ScorerCache): Promise { + // we use the same compare function that is used later when showing the results using fuzzy scoring + // this is very important because we are also limiting the number of results by config.maxResults + // and as such we want the top items to be included in this result set if the number of items + // exceeds config.maxResults. + const preparedQuery = prepareQuery(args.query.pattern); + const compare = (matchA: IInternalFileMatch, matchB: IInternalFileMatch) => compareItemsByScore(matchA, matchB, preparedQuery, true, FileMatchItemAccessor, scorerCache); + + return arrays.topAsync(results, compare, args.options.maxResults, 10000); + } + + private getResultsFromCache(cache: Cache, searchValue: string, onResult: (results: IInternalFileMatch) => void): Promise<[IInternalFileMatch[], CacheStats]> { + if (path.isAbsolute(searchValue)) { + return null; // bypass cache if user looks up an absolute path where matching goes directly on disk + } + + // Find cache entries by prefix of search value + const hasPathSep = searchValue.indexOf(path.sep) >= 0; + let cached: CacheEntry; + let wasResolved: boolean; + for (let previousSearch in cache.resultsToSearchCache) { + // If we narrow down, we might be able to reuse the cached results + if (searchValue.startsWith(previousSearch)) { + if (hasPathSep && previousSearch.indexOf(path.sep) < 0) { + continue; // since a path character widens the search for potential more matches, require it in previous search too + } + + const c = cache.resultsToSearchCache[previousSearch]; + c.finished.then(() => { wasResolved = false; }); + cached = c; + wasResolved = true; + break; + } + } + + if (!cached) { + return null; + } + + return new Promise((c, e) => { + cached.finished.then(cachedEntries => { + const cacheFilterStartTime = Date.now(); + + // Pattern match on results + let results: IInternalFileMatch[] = []; + const normalizedSearchValueLowercase = strings.stripWildcards(searchValue).toLowerCase(); + for (let i = 0; i < cachedEntries.length; i++) { + let entry = cachedEntries[i]; + + // Check if this entry is a match for the search value + if (!strings.fuzzyContains(entry.relativePath, normalizedSearchValueLowercase)) { + continue; + } + + results.push(entry); + } + + c([results, { + cacheWasResolved: wasResolved, + cacheFilterStartTime: cacheFilterStartTime, + cacheFilterResultCount: cachedEntries.length + }]); + }, e); + }); + } + + private doSearch(args: IProviderArgs, provider: vscode.SearchProvider, onResult: (result: OneOrMore) => void, batchSize?: number): Promise { + return new Promise((c, e) => { + let batch: IInternalFileMatch[] = []; + const onProviderResult = (match: string) => { + if (match) { + const internalMatch: IInternalFileMatch = { + relativePath: match, + basename: path.basename(match) + }; + + if (batchSize) { + batch.push(internalMatch); + if (batchSize > 0 && batch.length >= batchSize) { + onResult(batch); + batch = []; + } + } else { + onResult(internalMatch); + } + } + }; + + provider.provideFileSearchResults(args.query, args.options, { report: onProviderResult }, args.token).then(() => { + if (batch.length) { + onResult(batch); + } + + c(); // TODO limitHit + }, error => { + if (batch.length) { + onResult(batch); + } + + e(error); + }); + }); + } + + public clearCache(cacheKey: string): Promise { + delete this.caches[cacheKey]; + return Promise.resolve(undefined); + } +} + +function joinPath(resource: vscode.Uri, pathFragment: string): vscode.Uri { + const joinedPath = path.join(resource.path || '/', pathFragment); + return resource.with({ + path: joinedPath + }); +} + +interface IInternalFileMatch { + relativePath?: string; // Not present for extraFiles or absolute path matches + basename: string; +} + +export interface IDisposable { + dispose(): void; +} + +export interface Event { + (listener: (e: T) => any): IDisposable; +} + +interface CacheEntry { + finished: Promise; + onResult?: Event; +} + +type OneOrMore = T | T[]; + +class Cache { + public resultsToSearchCache: { [searchValue: string]: CacheEntry } = Object.create(null); + public scorerCache: ScorerCache = Object.create(null); +} + +const FileMatchItemAccessor = new class implements IItemAccessor { + + public getItemLabel(match: IInternalFileMatch): string { + return match.basename; // e.g. myFile.txt + } + + public getItemDescription(match: IInternalFileMatch): string { + return match.relativePath.substr(0, match.relativePath.length - match.basename.length - 1); // e.g. some/path/to/file + } + + public getItemPath(match: IInternalFileMatch): string { + return match.relativePath; // e.g. some/path/to/file/myFile.txt + } +}; + +interface CacheStats { + cacheWasResolved: boolean; + cacheFilterStartTime: number; + cacheFilterResultCount: number; +} diff --git a/extensions/search-rg/src/charCode.ts b/extensions/search-rg/src/charCode.ts new file mode 100644 index 00000000000..dd1bc58f80b --- /dev/null +++ b/extensions/search-rg/src/charCode.ts @@ -0,0 +1,422 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +// Names from https://blog.codinghorror.com/ascii-pronunciation-rules-for-programmers/ + +/** + * An inlined enum containing useful character codes (to be used with String.charCodeAt). + * Please leave the const keyword such that it gets inlined when compiled to JavaScript! + */ +export const enum CharCode { + Null = 0, + /** + * The `\t` character. + */ + Tab = 9, + /** + * The `\n` character. + */ + LineFeed = 10, + /** + * The `\r` character. + */ + CarriageReturn = 13, + Space = 32, + /** + * The `!` character. + */ + ExclamationMark = 33, + /** + * The `"` character. + */ + DoubleQuote = 34, + /** + * The `#` character. + */ + Hash = 35, + /** + * The `$` character. + */ + DollarSign = 36, + /** + * The `%` character. + */ + PercentSign = 37, + /** + * The `&` character. + */ + Ampersand = 38, + /** + * The `'` character. + */ + SingleQuote = 39, + /** + * The `(` character. + */ + OpenParen = 40, + /** + * The `)` character. + */ + CloseParen = 41, + /** + * The `*` character. + */ + Asterisk = 42, + /** + * The `+` character. + */ + Plus = 43, + /** + * The `,` character. + */ + Comma = 44, + /** + * The `-` character. + */ + Dash = 45, + /** + * The `.` character. + */ + Period = 46, + /** + * The `/` character. + */ + Slash = 47, + + Digit0 = 48, + Digit1 = 49, + Digit2 = 50, + Digit3 = 51, + Digit4 = 52, + Digit5 = 53, + Digit6 = 54, + Digit7 = 55, + Digit8 = 56, + Digit9 = 57, + + /** + * The `:` character. + */ + Colon = 58, + /** + * The `;` character. + */ + Semicolon = 59, + /** + * The `<` character. + */ + LessThan = 60, + /** + * The `=` character. + */ + Equals = 61, + /** + * The `>` character. + */ + GreaterThan = 62, + /** + * The `?` character. + */ + QuestionMark = 63, + /** + * The `@` character. + */ + AtSign = 64, + + A = 65, + B = 66, + C = 67, + D = 68, + E = 69, + F = 70, + G = 71, + H = 72, + I = 73, + J = 74, + K = 75, + L = 76, + M = 77, + N = 78, + O = 79, + P = 80, + Q = 81, + R = 82, + S = 83, + T = 84, + U = 85, + V = 86, + W = 87, + X = 88, + Y = 89, + Z = 90, + + /** + * The `[` character. + */ + OpenSquareBracket = 91, + /** + * The `\` character. + */ + Backslash = 92, + /** + * The `]` character. + */ + CloseSquareBracket = 93, + /** + * The `^` character. + */ + Caret = 94, + /** + * The `_` character. + */ + Underline = 95, + /** + * The ``(`)`` character. + */ + BackTick = 96, + + a = 97, + b = 98, + c = 99, + d = 100, + e = 101, + f = 102, + g = 103, + h = 104, + i = 105, + j = 106, + k = 107, + l = 108, + m = 109, + n = 110, + o = 111, + p = 112, + q = 113, + r = 114, + s = 115, + t = 116, + u = 117, + v = 118, + w = 119, + x = 120, + y = 121, + z = 122, + + /** + * The `{` character. + */ + OpenCurlyBrace = 123, + /** + * The `|` character. + */ + Pipe = 124, + /** + * The `}` character. + */ + CloseCurlyBrace = 125, + /** + * The `~` character. + */ + Tilde = 126, + + U_Combining_Grave_Accent = 0x0300, // U+0300 Combining Grave Accent + U_Combining_Acute_Accent = 0x0301, // U+0301 Combining Acute Accent + U_Combining_Circumflex_Accent = 0x0302, // U+0302 Combining Circumflex Accent + U_Combining_Tilde = 0x0303, // U+0303 Combining Tilde + U_Combining_Macron = 0x0304, // U+0304 Combining Macron + U_Combining_Overline = 0x0305, // U+0305 Combining Overline + U_Combining_Breve = 0x0306, // U+0306 Combining Breve + U_Combining_Dot_Above = 0x0307, // U+0307 Combining Dot Above + U_Combining_Diaeresis = 0x0308, // U+0308 Combining Diaeresis + U_Combining_Hook_Above = 0x0309, // U+0309 Combining Hook Above + U_Combining_Ring_Above = 0x030A, // U+030A Combining Ring Above + U_Combining_Double_Acute_Accent = 0x030B, // U+030B Combining Double Acute Accent + U_Combining_Caron = 0x030C, // U+030C Combining Caron + U_Combining_Vertical_Line_Above = 0x030D, // U+030D Combining Vertical Line Above + U_Combining_Double_Vertical_Line_Above = 0x030E, // U+030E Combining Double Vertical Line Above + U_Combining_Double_Grave_Accent = 0x030F, // U+030F Combining Double Grave Accent + U_Combining_Candrabindu = 0x0310, // U+0310 Combining Candrabindu + U_Combining_Inverted_Breve = 0x0311, // U+0311 Combining Inverted Breve + U_Combining_Turned_Comma_Above = 0x0312, // U+0312 Combining Turned Comma Above + U_Combining_Comma_Above = 0x0313, // U+0313 Combining Comma Above + U_Combining_Reversed_Comma_Above = 0x0314, // U+0314 Combining Reversed Comma Above + U_Combining_Comma_Above_Right = 0x0315, // U+0315 Combining Comma Above Right + U_Combining_Grave_Accent_Below = 0x0316, // U+0316 Combining Grave Accent Below + U_Combining_Acute_Accent_Below = 0x0317, // U+0317 Combining Acute Accent Below + U_Combining_Left_Tack_Below = 0x0318, // U+0318 Combining Left Tack Below + U_Combining_Right_Tack_Below = 0x0319, // U+0319 Combining Right Tack Below + U_Combining_Left_Angle_Above = 0x031A, // U+031A Combining Left Angle Above + U_Combining_Horn = 0x031B, // U+031B Combining Horn + U_Combining_Left_Half_Ring_Below = 0x031C, // U+031C Combining Left Half Ring Below + U_Combining_Up_Tack_Below = 0x031D, // U+031D Combining Up Tack Below + U_Combining_Down_Tack_Below = 0x031E, // U+031E Combining Down Tack Below + U_Combining_Plus_Sign_Below = 0x031F, // U+031F Combining Plus Sign Below + U_Combining_Minus_Sign_Below = 0x0320, // U+0320 Combining Minus Sign Below + U_Combining_Palatalized_Hook_Below = 0x0321, // U+0321 Combining Palatalized Hook Below + U_Combining_Retroflex_Hook_Below = 0x0322, // U+0322 Combining Retroflex Hook Below + U_Combining_Dot_Below = 0x0323, // U+0323 Combining Dot Below + U_Combining_Diaeresis_Below = 0x0324, // U+0324 Combining Diaeresis Below + U_Combining_Ring_Below = 0x0325, // U+0325 Combining Ring Below + U_Combining_Comma_Below = 0x0326, // U+0326 Combining Comma Below + U_Combining_Cedilla = 0x0327, // U+0327 Combining Cedilla + U_Combining_Ogonek = 0x0328, // U+0328 Combining Ogonek + U_Combining_Vertical_Line_Below = 0x0329, // U+0329 Combining Vertical Line Below + U_Combining_Bridge_Below = 0x032A, // U+032A Combining Bridge Below + U_Combining_Inverted_Double_Arch_Below = 0x032B, // U+032B Combining Inverted Double Arch Below + U_Combining_Caron_Below = 0x032C, // U+032C Combining Caron Below + U_Combining_Circumflex_Accent_Below = 0x032D, // U+032D Combining Circumflex Accent Below + U_Combining_Breve_Below = 0x032E, // U+032E Combining Breve Below + U_Combining_Inverted_Breve_Below = 0x032F, // U+032F Combining Inverted Breve Below + U_Combining_Tilde_Below = 0x0330, // U+0330 Combining Tilde Below + U_Combining_Macron_Below = 0x0331, // U+0331 Combining Macron Below + U_Combining_Low_Line = 0x0332, // U+0332 Combining Low Line + U_Combining_Double_Low_Line = 0x0333, // U+0333 Combining Double Low Line + U_Combining_Tilde_Overlay = 0x0334, // U+0334 Combining Tilde Overlay + U_Combining_Short_Stroke_Overlay = 0x0335, // U+0335 Combining Short Stroke Overlay + U_Combining_Long_Stroke_Overlay = 0x0336, // U+0336 Combining Long Stroke Overlay + U_Combining_Short_Solidus_Overlay = 0x0337, // U+0337 Combining Short Solidus Overlay + U_Combining_Long_Solidus_Overlay = 0x0338, // U+0338 Combining Long Solidus Overlay + U_Combining_Right_Half_Ring_Below = 0x0339, // U+0339 Combining Right Half Ring Below + U_Combining_Inverted_Bridge_Below = 0x033A, // U+033A Combining Inverted Bridge Below + U_Combining_Square_Below = 0x033B, // U+033B Combining Square Below + U_Combining_Seagull_Below = 0x033C, // U+033C Combining Seagull Below + U_Combining_X_Above = 0x033D, // U+033D Combining X Above + U_Combining_Vertical_Tilde = 0x033E, // U+033E Combining Vertical Tilde + U_Combining_Double_Overline = 0x033F, // U+033F Combining Double Overline + U_Combining_Grave_Tone_Mark = 0x0340, // U+0340 Combining Grave Tone Mark + U_Combining_Acute_Tone_Mark = 0x0341, // U+0341 Combining Acute Tone Mark + U_Combining_Greek_Perispomeni = 0x0342, // U+0342 Combining Greek Perispomeni + U_Combining_Greek_Koronis = 0x0343, // U+0343 Combining Greek Koronis + U_Combining_Greek_Dialytika_Tonos = 0x0344, // U+0344 Combining Greek Dialytika Tonos + U_Combining_Greek_Ypogegrammeni = 0x0345, // U+0345 Combining Greek Ypogegrammeni + U_Combining_Bridge_Above = 0x0346, // U+0346 Combining Bridge Above + U_Combining_Equals_Sign_Below = 0x0347, // U+0347 Combining Equals Sign Below + U_Combining_Double_Vertical_Line_Below = 0x0348, // U+0348 Combining Double Vertical Line Below + U_Combining_Left_Angle_Below = 0x0349, // U+0349 Combining Left Angle Below + U_Combining_Not_Tilde_Above = 0x034A, // U+034A Combining Not Tilde Above + U_Combining_Homothetic_Above = 0x034B, // U+034B Combining Homothetic Above + U_Combining_Almost_Equal_To_Above = 0x034C, // U+034C Combining Almost Equal To Above + U_Combining_Left_Right_Arrow_Below = 0x034D, // U+034D Combining Left Right Arrow Below + U_Combining_Upwards_Arrow_Below = 0x034E, // U+034E Combining Upwards Arrow Below + U_Combining_Grapheme_Joiner = 0x034F, // U+034F Combining Grapheme Joiner + U_Combining_Right_Arrowhead_Above = 0x0350, // U+0350 Combining Right Arrowhead Above + U_Combining_Left_Half_Ring_Above = 0x0351, // U+0351 Combining Left Half Ring Above + U_Combining_Fermata = 0x0352, // U+0352 Combining Fermata + U_Combining_X_Below = 0x0353, // U+0353 Combining X Below + U_Combining_Left_Arrowhead_Below = 0x0354, // U+0354 Combining Left Arrowhead Below + U_Combining_Right_Arrowhead_Below = 0x0355, // U+0355 Combining Right Arrowhead Below + U_Combining_Right_Arrowhead_And_Up_Arrowhead_Below = 0x0356, // U+0356 Combining Right Arrowhead And Up Arrowhead Below + U_Combining_Right_Half_Ring_Above = 0x0357, // U+0357 Combining Right Half Ring Above + U_Combining_Dot_Above_Right = 0x0358, // U+0358 Combining Dot Above Right + U_Combining_Asterisk_Below = 0x0359, // U+0359 Combining Asterisk Below + U_Combining_Double_Ring_Below = 0x035A, // U+035A Combining Double Ring Below + U_Combining_Zigzag_Above = 0x035B, // U+035B Combining Zigzag Above + U_Combining_Double_Breve_Below = 0x035C, // U+035C Combining Double Breve Below + U_Combining_Double_Breve = 0x035D, // U+035D Combining Double Breve + U_Combining_Double_Macron = 0x035E, // U+035E Combining Double Macron + U_Combining_Double_Macron_Below = 0x035F, // U+035F Combining Double Macron Below + U_Combining_Double_Tilde = 0x0360, // U+0360 Combining Double Tilde + U_Combining_Double_Inverted_Breve = 0x0361, // U+0361 Combining Double Inverted Breve + U_Combining_Double_Rightwards_Arrow_Below = 0x0362, // U+0362 Combining Double Rightwards Arrow Below + U_Combining_Latin_Small_Letter_A = 0x0363, // U+0363 Combining Latin Small Letter A + U_Combining_Latin_Small_Letter_E = 0x0364, // U+0364 Combining Latin Small Letter E + U_Combining_Latin_Small_Letter_I = 0x0365, // U+0365 Combining Latin Small Letter I + U_Combining_Latin_Small_Letter_O = 0x0366, // U+0366 Combining Latin Small Letter O + U_Combining_Latin_Small_Letter_U = 0x0367, // U+0367 Combining Latin Small Letter U + U_Combining_Latin_Small_Letter_C = 0x0368, // U+0368 Combining Latin Small Letter C + U_Combining_Latin_Small_Letter_D = 0x0369, // U+0369 Combining Latin Small Letter D + U_Combining_Latin_Small_Letter_H = 0x036A, // U+036A Combining Latin Small Letter H + U_Combining_Latin_Small_Letter_M = 0x036B, // U+036B Combining Latin Small Letter M + U_Combining_Latin_Small_Letter_R = 0x036C, // U+036C Combining Latin Small Letter R + U_Combining_Latin_Small_Letter_T = 0x036D, // U+036D Combining Latin Small Letter T + U_Combining_Latin_Small_Letter_V = 0x036E, // U+036E Combining Latin Small Letter V + U_Combining_Latin_Small_Letter_X = 0x036F, // U+036F Combining Latin Small Letter X + + /** + * Unicode Character 'LINE SEPARATOR' (U+2028) + * http://www.fileformat.info/info/unicode/char/2028/index.htm + */ + LINE_SEPARATOR_2028 = 8232, + + // http://www.fileformat.info/info/unicode/category/Sk/list.htm + U_CIRCUMFLEX = 0x005E, // U+005E CIRCUMFLEX + U_GRAVE_ACCENT = 0x0060, // U+0060 GRAVE ACCENT + U_DIAERESIS = 0x00A8, // U+00A8 DIAERESIS + U_MACRON = 0x00AF, // U+00AF MACRON + U_ACUTE_ACCENT = 0x00B4, // U+00B4 ACUTE ACCENT + U_CEDILLA = 0x00B8, // U+00B8 CEDILLA + U_MODIFIER_LETTER_LEFT_ARROWHEAD = 0x02C2, // U+02C2 MODIFIER LETTER LEFT ARROWHEAD + U_MODIFIER_LETTER_RIGHT_ARROWHEAD = 0x02C3, // U+02C3 MODIFIER LETTER RIGHT ARROWHEAD + U_MODIFIER_LETTER_UP_ARROWHEAD = 0x02C4, // U+02C4 MODIFIER LETTER UP ARROWHEAD + U_MODIFIER_LETTER_DOWN_ARROWHEAD = 0x02C5, // U+02C5 MODIFIER LETTER DOWN ARROWHEAD + U_MODIFIER_LETTER_CENTRED_RIGHT_HALF_RING = 0x02D2, // U+02D2 MODIFIER LETTER CENTRED RIGHT HALF RING + U_MODIFIER_LETTER_CENTRED_LEFT_HALF_RING = 0x02D3, // U+02D3 MODIFIER LETTER CENTRED LEFT HALF RING + U_MODIFIER_LETTER_UP_TACK = 0x02D4, // U+02D4 MODIFIER LETTER UP TACK + U_MODIFIER_LETTER_DOWN_TACK = 0x02D5, // U+02D5 MODIFIER LETTER DOWN TACK + U_MODIFIER_LETTER_PLUS_SIGN = 0x02D6, // U+02D6 MODIFIER LETTER PLUS SIGN + U_MODIFIER_LETTER_MINUS_SIGN = 0x02D7, // U+02D7 MODIFIER LETTER MINUS SIGN + U_BREVE = 0x02D8, // U+02D8 BREVE + U_DOT_ABOVE = 0x02D9, // U+02D9 DOT ABOVE + U_RING_ABOVE = 0x02DA, // U+02DA RING ABOVE + U_OGONEK = 0x02DB, // U+02DB OGONEK + U_SMALL_TILDE = 0x02DC, // U+02DC SMALL TILDE + U_DOUBLE_ACUTE_ACCENT = 0x02DD, // U+02DD DOUBLE ACUTE ACCENT + U_MODIFIER_LETTER_RHOTIC_HOOK = 0x02DE, // U+02DE MODIFIER LETTER RHOTIC HOOK + U_MODIFIER_LETTER_CROSS_ACCENT = 0x02DF, // U+02DF MODIFIER LETTER CROSS ACCENT + U_MODIFIER_LETTER_EXTRA_HIGH_TONE_BAR = 0x02E5, // U+02E5 MODIFIER LETTER EXTRA-HIGH TONE BAR + U_MODIFIER_LETTER_HIGH_TONE_BAR = 0x02E6, // U+02E6 MODIFIER LETTER HIGH TONE BAR + U_MODIFIER_LETTER_MID_TONE_BAR = 0x02E7, // U+02E7 MODIFIER LETTER MID TONE BAR + U_MODIFIER_LETTER_LOW_TONE_BAR = 0x02E8, // U+02E8 MODIFIER LETTER LOW TONE BAR + U_MODIFIER_LETTER_EXTRA_LOW_TONE_BAR = 0x02E9, // U+02E9 MODIFIER LETTER EXTRA-LOW TONE BAR + U_MODIFIER_LETTER_YIN_DEPARTING_TONE_MARK = 0x02EA, // U+02EA MODIFIER LETTER YIN DEPARTING TONE MARK + U_MODIFIER_LETTER_YANG_DEPARTING_TONE_MARK = 0x02EB, // U+02EB MODIFIER LETTER YANG DEPARTING TONE MARK + U_MODIFIER_LETTER_UNASPIRATED = 0x02ED, // U+02ED MODIFIER LETTER UNASPIRATED + U_MODIFIER_LETTER_LOW_DOWN_ARROWHEAD = 0x02EF, // U+02EF MODIFIER LETTER LOW DOWN ARROWHEAD + U_MODIFIER_LETTER_LOW_UP_ARROWHEAD = 0x02F0, // U+02F0 MODIFIER LETTER LOW UP ARROWHEAD + U_MODIFIER_LETTER_LOW_LEFT_ARROWHEAD = 0x02F1, // U+02F1 MODIFIER LETTER LOW LEFT ARROWHEAD + U_MODIFIER_LETTER_LOW_RIGHT_ARROWHEAD = 0x02F2, // U+02F2 MODIFIER LETTER LOW RIGHT ARROWHEAD + U_MODIFIER_LETTER_LOW_RING = 0x02F3, // U+02F3 MODIFIER LETTER LOW RING + U_MODIFIER_LETTER_MIDDLE_GRAVE_ACCENT = 0x02F4, // U+02F4 MODIFIER LETTER MIDDLE GRAVE ACCENT + U_MODIFIER_LETTER_MIDDLE_DOUBLE_GRAVE_ACCENT = 0x02F5, // U+02F5 MODIFIER LETTER MIDDLE DOUBLE GRAVE ACCENT + U_MODIFIER_LETTER_MIDDLE_DOUBLE_ACUTE_ACCENT = 0x02F6, // U+02F6 MODIFIER LETTER MIDDLE DOUBLE ACUTE ACCENT + U_MODIFIER_LETTER_LOW_TILDE = 0x02F7, // U+02F7 MODIFIER LETTER LOW TILDE + U_MODIFIER_LETTER_RAISED_COLON = 0x02F8, // U+02F8 MODIFIER LETTER RAISED COLON + U_MODIFIER_LETTER_BEGIN_HIGH_TONE = 0x02F9, // U+02F9 MODIFIER LETTER BEGIN HIGH TONE + U_MODIFIER_LETTER_END_HIGH_TONE = 0x02FA, // U+02FA MODIFIER LETTER END HIGH TONE + U_MODIFIER_LETTER_BEGIN_LOW_TONE = 0x02FB, // U+02FB MODIFIER LETTER BEGIN LOW TONE + U_MODIFIER_LETTER_END_LOW_TONE = 0x02FC, // U+02FC MODIFIER LETTER END LOW TONE + U_MODIFIER_LETTER_SHELF = 0x02FD, // U+02FD MODIFIER LETTER SHELF + U_MODIFIER_LETTER_OPEN_SHELF = 0x02FE, // U+02FE MODIFIER LETTER OPEN SHELF + U_MODIFIER_LETTER_LOW_LEFT_ARROW = 0x02FF, // U+02FF MODIFIER LETTER LOW LEFT ARROW + U_GREEK_LOWER_NUMERAL_SIGN = 0x0375, // U+0375 GREEK LOWER NUMERAL SIGN + U_GREEK_TONOS = 0x0384, // U+0384 GREEK TONOS + U_GREEK_DIALYTIKA_TONOS = 0x0385, // U+0385 GREEK DIALYTIKA TONOS + U_GREEK_KORONIS = 0x1FBD, // U+1FBD GREEK KORONIS + U_GREEK_PSILI = 0x1FBF, // U+1FBF GREEK PSILI + U_GREEK_PERISPOMENI = 0x1FC0, // U+1FC0 GREEK PERISPOMENI + U_GREEK_DIALYTIKA_AND_PERISPOMENI = 0x1FC1, // U+1FC1 GREEK DIALYTIKA AND PERISPOMENI + U_GREEK_PSILI_AND_VARIA = 0x1FCD, // U+1FCD GREEK PSILI AND VARIA + U_GREEK_PSILI_AND_OXIA = 0x1FCE, // U+1FCE GREEK PSILI AND OXIA + U_GREEK_PSILI_AND_PERISPOMENI = 0x1FCF, // U+1FCF GREEK PSILI AND PERISPOMENI + U_GREEK_DASIA_AND_VARIA = 0x1FDD, // U+1FDD GREEK DASIA AND VARIA + U_GREEK_DASIA_AND_OXIA = 0x1FDE, // U+1FDE GREEK DASIA AND OXIA + U_GREEK_DASIA_AND_PERISPOMENI = 0x1FDF, // U+1FDF GREEK DASIA AND PERISPOMENI + U_GREEK_DIALYTIKA_AND_VARIA = 0x1FED, // U+1FED GREEK DIALYTIKA AND VARIA + U_GREEK_DIALYTIKA_AND_OXIA = 0x1FEE, // U+1FEE GREEK DIALYTIKA AND OXIA + U_GREEK_VARIA = 0x1FEF, // U+1FEF GREEK VARIA + U_GREEK_OXIA = 0x1FFD, // U+1FFD GREEK OXIA + U_GREEK_DASIA = 0x1FFE, // U+1FFE GREEK DASIA + + + U_OVERLINE = 0x203E, // Unicode Character 'OVERLINE' + + /** + * UTF-8 BOM + * Unicode Character 'ZERO WIDTH NO-BREAK SPACE' (U+FEFF) + * http://www.fileformat.info/info/unicode/char/feff/index.htm + */ + UTF8_BOM = 65279 +} diff --git a/extensions/search-rg/src/comparers.ts b/extensions/search-rg/src/comparers.ts new file mode 100644 index 00000000000..1eb9632131d --- /dev/null +++ b/extensions/search-rg/src/comparers.ts @@ -0,0 +1,108 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as strings from './strings'; + +let intlFileNameCollator: Intl.Collator; +let intlFileNameCollatorIsNumeric: boolean; + +export function compareFileNames(one: string, other: string, caseSensitive = false): number { + if (intlFileNameCollator) { + const a = one || ''; + const b = other || ''; + const result = intlFileNameCollator.compare(a, b); + + // Using the numeric option in the collator will + // make compare(`foo1`, `foo01`) === 0. We must disambiguate. + if (intlFileNameCollatorIsNumeric && result === 0 && a !== b) { + return a < b ? -1 : 1; + } + + return result; + } + + return noIntlCompareFileNames(one, other, caseSensitive); +} + +const FileNameMatch = /^(.*?)(\.([^.]*))?$/; + +export function noIntlCompareFileNames(one: string, other: string, caseSensitive = false): number { + if (!caseSensitive) { + one = one && one.toLowerCase(); + other = other && other.toLowerCase(); + } + + const [oneName, oneExtension] = extractNameAndExtension(one); + const [otherName, otherExtension] = extractNameAndExtension(other); + + if (oneName !== otherName) { + return oneName < otherName ? -1 : 1; + } + + if (oneExtension === otherExtension) { + return 0; + } + + return oneExtension < otherExtension ? -1 : 1; +} + +function extractNameAndExtension(str?: string): [string, string] { + const match = str ? FileNameMatch.exec(str) : [] as RegExpExecArray; + + return [(match && match[1]) || '', (match && match[3]) || '']; +} + +export function compareAnything(one: string, other: string, lookFor: string): number { + let elementAName = one.toLowerCase(); + let elementBName = other.toLowerCase(); + + // Sort prefix matches over non prefix matches + const prefixCompare = compareByPrefix(one, other, lookFor); + if (prefixCompare) { + return prefixCompare; + } + + // Sort suffix matches over non suffix matches + let elementASuffixMatch = strings.endsWith(elementAName, lookFor); + let elementBSuffixMatch = strings.endsWith(elementBName, lookFor); + if (elementASuffixMatch !== elementBSuffixMatch) { + return elementASuffixMatch ? -1 : 1; + } + + // Understand file names + let r = compareFileNames(elementAName, elementBName); + if (r !== 0) { + return r; + } + + // Compare by name + return elementAName.localeCompare(elementBName); +} + +export function compareByPrefix(one: string, other: string, lookFor: string): number { + let elementAName = one.toLowerCase(); + let elementBName = other.toLowerCase(); + + // Sort prefix matches over non prefix matches + let elementAPrefixMatch = strings.startsWith(elementAName, lookFor); + let elementBPrefixMatch = strings.startsWith(elementBName, lookFor); + if (elementAPrefixMatch !== elementBPrefixMatch) { + return elementAPrefixMatch ? -1 : 1; + } + + // Same prefix: Sort shorter matches to the top to have those on top that match more precisely + else if (elementAPrefixMatch && elementBPrefixMatch) { + if (elementAName.length < elementBName.length) { + return -1; + } + + if (elementAName.length > elementBName.length) { + return 1; + } + } + + return 0; +} diff --git a/extensions/search-rg/src/extension.ts b/extensions/search-rg/src/extension.ts index a2e4611ffef..0490b59d9d4 100644 --- a/extensions/search-rg/src/extension.ts +++ b/extensions/search-rg/src/extension.ts @@ -5,7 +5,8 @@ import * as vscode from 'vscode'; import { RipgrepTextSearchEngine } from './ripgrepTextSearch'; -import { RipgrepFileSearchEngine } from './ripgrepFileSearch'; +import { RipgrepFileSearch } from './ripgrepFileSearch'; +import { CachedSearchProvider } from './cachedSearchProvider'; export function activate(): void { if (vscode.workspace.getConfiguration('searchRipgrep').get('enable')) { @@ -24,8 +25,9 @@ class RipgrepSearchProvider implements vscode.SearchProvider { return engine.provideTextSearchResults(query, options, progress, token); } - provideFileSearchResults(options: vscode.SearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { - const engine = new RipgrepFileSearchEngine(this.outputChannel); - return engine.provideFileSearchResults(options, progress, token); + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.SearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + const cachedProvider = new CachedSearchProvider(this.outputChannel); + const engine = new RipgrepFileSearch(this.outputChannel); + return cachedProvider.provideFileSearchResults(engine, query, options, progress, token); } } \ No newline at end of file diff --git a/extensions/search-rg/src/fileSearchScorer.ts b/extensions/search-rg/src/fileSearchScorer.ts new file mode 100644 index 00000000000..c2c9d8b68d8 --- /dev/null +++ b/extensions/search-rg/src/fileSearchScorer.ts @@ -0,0 +1,618 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) Microsoft Corporation. All rights reserved. +* Licensed under the MIT License. See License.txt in the project root for license information. +*--------------------------------------------------------------------------------------------*/ +'use strict'; + +import { stripWildcards, equalsIgnoreCase } from './strings'; +import { matchesPrefix, matchesCamelCase, createMatches, IMatch, isUpper } from './filters'; +import { compareAnything } from './comparers'; +import { CharCode } from './charCode'; + +const isWindows = process.platform === 'win32'; +const isMacintosh = (process.platform === 'darwin'); +const isLinux = (process.platform === 'linux'); + +const nativeSep = isWindows ? '\\' : '/'; + +export type Score = [number /* score */, number[] /* match positions */]; +export type ScorerCache = { [key: string]: IItemScore }; + +const NO_MATCH = 0; +const NO_SCORE: Score = [NO_MATCH, []]; + +// const DEBUG = false; +// const DEBUG_MATRIX = false; + +export function score(target: string, query: string, queryLower: string, fuzzy: boolean): Score { + if (!target || !query) { + return NO_SCORE; // return early if target or query are undefined + } + + const targetLength = target.length; + const queryLength = query.length; + + if (targetLength < queryLength) { + return NO_SCORE; // impossible for query to be contained in target + } + + // if (DEBUG) { + // console.group(`Target: ${target}, Query: ${query}`); + // } + + const targetLower = target.toLowerCase(); + + // When not searching fuzzy, we require the query to be contained fully + // in the target string contiguously. + if (!fuzzy) { + const indexOfQueryInTarget = targetLower.indexOf(queryLower); + if (indexOfQueryInTarget === -1) { + // if (DEBUG) { + // console.log(`Characters not matching consecutively ${queryLower} within ${targetLower}`); + // } + + return NO_SCORE; + } + } + + const res = doScore(query, queryLower, queryLength, target, targetLower, targetLength); + + // if (DEBUG) { + // console.log(`%cFinal Score: ${res[0]}`, 'font-weight: bold'); + // console.groupEnd(); + // } + + return res; +} + +function doScore(query: string, queryLower: string, queryLength: number, target: string, targetLower: string, targetLength: number): [number, number[]] { + const scores = []; + const matches = []; + + // + // Build Scorer Matrix: + // + // The matrix is composed of query q and target t. For each index we score + // q[i] with t[i] and compare that with the previous score. If the score is + // equal or larger, we keep the match. In addition to the score, we also keep + // the length of the consecutive matches to use as boost for the score. + // + // t a r g e t + // q + // u + // e + // r + // y + // + for (let queryIndex = 0; queryIndex < queryLength; queryIndex++) { + for (let targetIndex = 0; targetIndex < targetLength; targetIndex++) { + const currentIndex = queryIndex * targetLength + targetIndex; + const leftIndex = currentIndex - 1; + const diagIndex = (queryIndex - 1) * targetLength + targetIndex - 1; + + const leftScore: number = targetIndex > 0 ? scores[leftIndex] : 0; + const diagScore: number = queryIndex > 0 && targetIndex > 0 ? scores[diagIndex] : 0; + + const matchesSequenceLength: number = queryIndex > 0 && targetIndex > 0 ? matches[diagIndex] : 0; + + // If we are not matching on the first query character any more, we only produce a + // score if we had a score previously for the last query index (by looking at the diagScore). + // This makes sure that the query always matches in sequence on the target. For example + // given a target of "ede" and a query of "de", we would otherwise produce a wrong high score + // for query[1] ("e") matching on target[0] ("e") because of the "beginning of word" boost. + let score: number; + if (!diagScore && queryIndex > 0) { + score = 0; + } else { + score = computeCharScore(query, queryLower, queryIndex, target, targetLower, targetIndex, matchesSequenceLength); + } + + // We have a score and its equal or larger than the left score + // Match: sequence continues growing from previous diag value + // Score: increases by diag score value + if (score && diagScore + score >= leftScore) { + matches[currentIndex] = matchesSequenceLength + 1; + scores[currentIndex] = diagScore + score; + } + + // We either have no score or the score is lower than the left score + // Match: reset to 0 + // Score: pick up from left hand side + else { + matches[currentIndex] = NO_MATCH; + scores[currentIndex] = leftScore; + } + } + } + + // Restore Positions (starting from bottom right of matrix) + const positions = []; + let queryIndex = queryLength - 1; + let targetIndex = targetLength - 1; + while (queryIndex >= 0 && targetIndex >= 0) { + const currentIndex = queryIndex * targetLength + targetIndex; + const match = matches[currentIndex]; + if (match === NO_MATCH) { + targetIndex--; // go left + } else { + positions.push(targetIndex); + + // go up and left + queryIndex--; + targetIndex--; + } + } + + // Print matrix + // if (DEBUG_MATRIX) { + // printMatrix(query, target, matches, scores); + // } + + return [scores[queryLength * targetLength - 1], positions.reverse()]; +} + +function computeCharScore(query: string, queryLower: string, queryIndex: number, target: string, targetLower: string, targetIndex: number, matchesSequenceLength: number): number { + let score = 0; + + if (queryLower[queryIndex] !== targetLower[targetIndex]) { + return score; // no match of characters + } + + // Character match bonus + score += 1; + + // if (DEBUG) { + // console.groupCollapsed(`%cCharacter match bonus: +1 (char: ${queryLower[queryIndex]} at index ${targetIndex}, total score: ${score})`, 'font-weight: normal'); + // } + + // Consecutive match bonus + if (matchesSequenceLength > 0) { + score += (matchesSequenceLength * 5); + + // if (DEBUG) { + // console.log('Consecutive match bonus: ' + (matchesSequenceLength * 5)); + // } + } + + // Same case bonus + if (query[queryIndex] === target[targetIndex]) { + score += 1; + + // if (DEBUG) { + // console.log('Same case bonus: +1'); + // } + } + + // Start of word bonus + if (targetIndex === 0) { + score += 8; + + // if (DEBUG) { + // console.log('Start of word bonus: +8'); + // } + } + + else { + + // After separator bonus + const separatorBonus = scoreSeparatorAtPos(target.charCodeAt(targetIndex - 1)); + if (separatorBonus) { + score += separatorBonus; + + // if (DEBUG) { + // console.log('After separtor bonus: +4'); + // } + } + + // Inside word upper case bonus (camel case) + else if (isUpper(target.charCodeAt(targetIndex))) { + score += 1; + + // if (DEBUG) { + // console.log('Inside word upper case bonus: +1'); + // } + } + } + + // if (DEBUG) { + // console.groupEnd(); + // } + + return score; +} + +function scoreSeparatorAtPos(charCode: number): number { + switch (charCode) { + case CharCode.Slash: + case CharCode.Backslash: + return 5; // prefer path separators... + case CharCode.Underline: + case CharCode.Dash: + case CharCode.Period: + case CharCode.Space: + case CharCode.SingleQuote: + case CharCode.DoubleQuote: + case CharCode.Colon: + return 4; // ...over other separators + default: + return 0; + } +} + +// function printMatrix(query: string, target: string, matches: number[], scores: number[]): void { +// console.log('\t' + target.split('').join('\t')); +// for (let queryIndex = 0; queryIndex < query.length; queryIndex++) { +// let line = query[queryIndex] + '\t'; +// for (let targetIndex = 0; targetIndex < target.length; targetIndex++) { +// const currentIndex = queryIndex * target.length + targetIndex; +// line = line + 'M' + matches[currentIndex] + '/' + 'S' + scores[currentIndex] + '\t'; +// } + +// console.log(line); +// } +// } + +/** + * Scoring on structural items that have a label and optional description. + */ +export interface IItemScore { + + /** + * Overall score. + */ + score: number; + + /** + * Matches within the label. + */ + labelMatch?: IMatch[]; + + /** + * Matches within the description. + */ + descriptionMatch?: IMatch[]; +} + +const NO_ITEM_SCORE: IItemScore = Object.freeze({ score: 0 }); + +export interface IItemAccessor { + + /** + * Just the label of the item to score on. + */ + getItemLabel(item: T): string; + + /** + * The optional description of the item to score on. Can be null. + */ + getItemDescription(item: T): string; + + /** + * If the item is a file, the path of the file to score on. Can be null. + */ + getItemPath(file: T): string; +} + +const PATH_IDENTITY_SCORE = 1 << 18; +const LABEL_PREFIX_SCORE = 1 << 17; +const LABEL_CAMELCASE_SCORE = 1 << 16; +const LABEL_SCORE_THRESHOLD = 1 << 15; + +export interface IPreparedQuery { + original: string; + value: string; + lowercase: string; + containsPathSeparator: boolean; +} + +/** + * Helper function to prepare a search value for scoring in quick open by removing unwanted characters. + */ +export function prepareQuery(original: string): IPreparedQuery { + let lowercase: string; + let containsPathSeparator: boolean; + let value: string; + + if (original) { + value = stripWildcards(original).replace(/\s/g, ''); // get rid of all wildcards and whitespace + if (isWindows) { + value = value.replace(/\//g, nativeSep); // Help Windows users to search for paths when using slash + } + + lowercase = value.toLowerCase(); + containsPathSeparator = value.indexOf(nativeSep) >= 0; + } + + return { original, value, lowercase, containsPathSeparator }; +} + +export function scoreItem(item: T, query: IPreparedQuery, fuzzy: boolean, accessor: IItemAccessor, cache: ScorerCache): IItemScore { + if (!item || !query.value) { + return NO_ITEM_SCORE; // we need an item and query to score on at least + } + + const label = accessor.getItemLabel(item); + if (!label) { + return NO_ITEM_SCORE; // we need a label at least + } + + const description = accessor.getItemDescription(item); + + let cacheHash: string; + if (description) { + cacheHash = `${label}${description}${query.value}${fuzzy}`; + } else { + cacheHash = `${label}${query.value}${fuzzy}`; + } + + const cached = cache[cacheHash]; + if (cached) { + return cached; + } + + const itemScore = doScoreItem(label, description, accessor.getItemPath(item), query, fuzzy); + cache[cacheHash] = itemScore; + + return itemScore; +} + +function doScoreItem(label: string, description: string, path: string, query: IPreparedQuery, fuzzy: boolean): IItemScore { + + // 1.) treat identity matches on full path highest + if (path && isLinux ? query.original === path : equalsIgnoreCase(query.original, path)) { + return { score: PATH_IDENTITY_SCORE, labelMatch: [{ start: 0, end: label.length }], descriptionMatch: description ? [{ start: 0, end: description.length }] : void 0 }; + } + + // We only consider label matches if the query is not including file path separators + const preferLabelMatches = !path || !query.containsPathSeparator; + if (preferLabelMatches) { + + // 2.) treat prefix matches on the label second highest + const prefixLabelMatch = matchesPrefix(query.value, label); + if (prefixLabelMatch) { + return { score: LABEL_PREFIX_SCORE, labelMatch: prefixLabelMatch }; + } + + // 3.) treat camelcase matches on the label third highest + const camelcaseLabelMatch = matchesCamelCase(query.value, label); + if (camelcaseLabelMatch) { + return { score: LABEL_CAMELCASE_SCORE, labelMatch: camelcaseLabelMatch }; + } + + // 4.) prefer scores on the label if any + const [labelScore, labelPositions] = score(label, query.value, query.lowercase, fuzzy); + if (labelScore) { + return { score: labelScore + LABEL_SCORE_THRESHOLD, labelMatch: createMatches(labelPositions) }; + } + } + + // 5.) finally compute description + label scores if we have a description + if (description) { + let descriptionPrefix = description; + if (!!path) { + descriptionPrefix = `${description}${nativeSep}`; // assume this is a file path + } + + const descriptionPrefixLength = descriptionPrefix.length; + const descriptionAndLabel = `${descriptionPrefix}${label}`; + + const [labelDescriptionScore, labelDescriptionPositions] = score(descriptionAndLabel, query.value, query.lowercase, fuzzy); + if (labelDescriptionScore) { + const labelDescriptionMatches = createMatches(labelDescriptionPositions); + const labelMatch: IMatch[] = []; + const descriptionMatch: IMatch[] = []; + + // We have to split the matches back onto the label and description portions + labelDescriptionMatches.forEach(h => { + + // Match overlaps label and description part, we need to split it up + if (h.start < descriptionPrefixLength && h.end > descriptionPrefixLength) { + labelMatch.push({ start: 0, end: h.end - descriptionPrefixLength }); + descriptionMatch.push({ start: h.start, end: descriptionPrefixLength }); + } + + // Match on label part + else if (h.start >= descriptionPrefixLength) { + labelMatch.push({ start: h.start - descriptionPrefixLength, end: h.end - descriptionPrefixLength }); + } + + // Match on description part + else { + descriptionMatch.push(h); + } + }); + + return { score: labelDescriptionScore, labelMatch, descriptionMatch }; + } + } + + return NO_ITEM_SCORE; +} + +export function compareItemsByScore(itemA: T, itemB: T, query: IPreparedQuery, fuzzy: boolean, accessor: IItemAccessor, cache: ScorerCache, fallbackComparer = fallbackCompare): number { + const itemScoreA = scoreItem(itemA, query, fuzzy, accessor, cache); + const itemScoreB = scoreItem(itemB, query, fuzzy, accessor, cache); + + const scoreA = itemScoreA.score; + const scoreB = itemScoreB.score; + + // 1.) prefer identity matches + if (scoreA === PATH_IDENTITY_SCORE || scoreB === PATH_IDENTITY_SCORE) { + if (scoreA !== scoreB) { + return scoreA === PATH_IDENTITY_SCORE ? -1 : 1; + } + } + + // 2.) prefer label prefix matches + if (scoreA === LABEL_PREFIX_SCORE || scoreB === LABEL_PREFIX_SCORE) { + if (scoreA !== scoreB) { + return scoreA === LABEL_PREFIX_SCORE ? -1 : 1; + } + + const labelA = accessor.getItemLabel(itemA); + const labelB = accessor.getItemLabel(itemB); + + // prefer shorter names when both match on label prefix + if (labelA.length !== labelB.length) { + return labelA.length - labelB.length; + } + } + + // 3.) prefer camelcase matches + if (scoreA === LABEL_CAMELCASE_SCORE || scoreB === LABEL_CAMELCASE_SCORE) { + if (scoreA !== scoreB) { + return scoreA === LABEL_CAMELCASE_SCORE ? -1 : 1; + } + + const labelA = accessor.getItemLabel(itemA); + const labelB = accessor.getItemLabel(itemB); + + // prefer more compact camel case matches over longer + const comparedByMatchLength = compareByMatchLength(itemScoreA.labelMatch, itemScoreB.labelMatch); + if (comparedByMatchLength !== 0) { + return comparedByMatchLength; + } + + // prefer shorter names when both match on label camelcase + if (labelA.length !== labelB.length) { + return labelA.length - labelB.length; + } + } + + // 4.) prefer label scores + if (scoreA > LABEL_SCORE_THRESHOLD || scoreB > LABEL_SCORE_THRESHOLD) { + if (scoreB < LABEL_SCORE_THRESHOLD) { + return -1; + } + + if (scoreA < LABEL_SCORE_THRESHOLD) { + return 1; + } + } + + // 5.) compare by score + if (scoreA !== scoreB) { + return scoreA > scoreB ? -1 : 1; + } + + // 6.) scores are identical, prefer more compact matches (label and description) + const itemAMatchDistance = computeLabelAndDescriptionMatchDistance(itemA, itemScoreA, accessor); + const itemBMatchDistance = computeLabelAndDescriptionMatchDistance(itemB, itemScoreB, accessor); + if (itemAMatchDistance && itemBMatchDistance && itemAMatchDistance !== itemBMatchDistance) { + return itemBMatchDistance > itemAMatchDistance ? -1 : 1; + } + + // 7.) at this point, scores are identical and match compactness as well + // for both items so we start to use the fallback compare + return fallbackComparer(itemA, itemB, query, accessor); +} + +function computeLabelAndDescriptionMatchDistance(item: T, score: IItemScore, accessor: IItemAccessor): number { + const hasLabelMatches = (score.labelMatch && score.labelMatch.length); + const hasDescriptionMatches = (score.descriptionMatch && score.descriptionMatch.length); + + let matchStart: number = -1; + let matchEnd: number = -1; + + // If we have description matches, the start is first of description match + if (hasDescriptionMatches) { + matchStart = score.descriptionMatch[0].start; + } + + // Otherwise, the start is the first label match + else if (hasLabelMatches) { + matchStart = score.labelMatch[0].start; + } + + // If we have label match, the end is the last label match + // If we had a description match, we add the length of the description + // as offset to the end to indicate this. + if (hasLabelMatches) { + matchEnd = score.labelMatch[score.labelMatch.length - 1].end; + if (hasDescriptionMatches) { + const itemDescription = accessor.getItemDescription(item); + if (itemDescription) { + matchEnd += itemDescription.length; + } + } + } + + // If we have just a description match, the end is the last description match + else if (hasDescriptionMatches) { + matchEnd = score.descriptionMatch[score.descriptionMatch.length - 1].end; + } + + return matchEnd - matchStart; +} + +function compareByMatchLength(matchesA?: IMatch[], matchesB?: IMatch[]): number { + if ((!matchesA && !matchesB) || (!matchesA.length && !matchesB.length)) { + return 0; // make sure to not cause bad comparing when matches are not provided + } + + if (!matchesB || !matchesB.length) { + return -1; + } + + if (!matchesA || !matchesA.length) { + return 1; + } + + // Compute match length of A (first to last match) + const matchStartA = matchesA[0].start; + const matchEndA = matchesA[matchesA.length - 1].end; + const matchLengthA = matchEndA - matchStartA; + + // Compute match length of B (first to last match) + const matchStartB = matchesB[0].start; + const matchEndB = matchesB[matchesB.length - 1].end; + const matchLengthB = matchEndB - matchStartB; + + // Prefer shorter match length + return matchLengthA === matchLengthB ? 0 : matchLengthB < matchLengthA ? 1 : -1; +} + +export function fallbackCompare(itemA: T, itemB: T, query: IPreparedQuery, accessor: IItemAccessor): number { + + // check for label + description length and prefer shorter + const labelA = accessor.getItemLabel(itemA); + const labelB = accessor.getItemLabel(itemB); + + const descriptionA = accessor.getItemDescription(itemA); + const descriptionB = accessor.getItemDescription(itemB); + + const labelDescriptionALength = labelA.length + (descriptionA ? descriptionA.length : 0); + const labelDescriptionBLength = labelB.length + (descriptionB ? descriptionB.length : 0); + + if (labelDescriptionALength !== labelDescriptionBLength) { + return labelDescriptionALength - labelDescriptionBLength; + } + + // check for path length and prefer shorter + const pathA = accessor.getItemPath(itemA); + const pathB = accessor.getItemPath(itemB); + + if (pathA && pathB && pathA.length !== pathB.length) { + return pathA.length - pathB.length; + } + + // 7.) finally we have equal scores and equal length, we fallback to comparer + + // compare by label + if (labelA !== labelB) { + return compareAnything(labelA, labelB, query.value); + } + + // compare by description + if (descriptionA && descriptionB && descriptionA !== descriptionB) { + return compareAnything(descriptionA, descriptionB, query.value); + } + + // compare by path + if (pathA && pathB && pathA !== pathB) { + return compareAnything(pathA, pathB, query.value); + } + + // equal + return 0; +} \ No newline at end of file diff --git a/extensions/search-rg/src/filters.ts b/extensions/search-rg/src/filters.ts new file mode 100644 index 00000000000..63d4dcaeac3 --- /dev/null +++ b/extensions/search-rg/src/filters.ts @@ -0,0 +1,224 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as strings from './strings'; +import { CharCode } from './charCode'; + +export interface IFilter { + // Returns null if word doesn't match. + (word: string, wordToMatchAgainst: string): IMatch[]; +} + +export interface IMatch { + start: number; + end: number; +} + +// Prefix + +export const matchesPrefix: IFilter = _matchesPrefix.bind(undefined, true); + +function _matchesPrefix(ignoreCase: boolean, word: string, wordToMatchAgainst: string): IMatch[] { + if (!wordToMatchAgainst || wordToMatchAgainst.length < word.length) { + return null; + } + + let matches: boolean; + if (ignoreCase) { + matches = strings.startsWithIgnoreCase(wordToMatchAgainst, word); + } else { + matches = wordToMatchAgainst.indexOf(word) === 0; + } + + if (!matches) { + return null; + } + + return word.length > 0 ? [{ start: 0, end: word.length }] : []; +} + +// CamelCase + +function isLower(code: number): boolean { + return CharCode.a <= code && code <= CharCode.z; +} + +export function isUpper(code: number): boolean { + return CharCode.A <= code && code <= CharCode.Z; +} + +function isNumber(code: number): boolean { + return CharCode.Digit0 <= code && code <= CharCode.Digit9; +} + +function isWhitespace(code: number): boolean { + return ( + code === CharCode.Space + || code === CharCode.Tab + || code === CharCode.LineFeed + || code === CharCode.CarriageReturn + ); +} + +function isAlphanumeric(code: number): boolean { + return isLower(code) || isUpper(code) || isNumber(code); +} + +function join(head: IMatch, tail: IMatch[]): IMatch[] { + if (tail.length === 0) { + tail = [head]; + } else if (head.end === tail[0].start) { + tail[0].start = head.start; + } else { + tail.unshift(head); + } + return tail; +} + +function nextAnchor(camelCaseWord: string, start: number): number { + for (let i = start; i < camelCaseWord.length; i++) { + let c = camelCaseWord.charCodeAt(i); + if (isUpper(c) || isNumber(c) || (i > 0 && !isAlphanumeric(camelCaseWord.charCodeAt(i - 1)))) { + return i; + } + } + return camelCaseWord.length; +} + +function _matchesCamelCase(word: string, camelCaseWord: string, i: number, j: number): IMatch[] { + if (i === word.length) { + return []; + } else if (j === camelCaseWord.length) { + return null; + } else if (word[i] !== camelCaseWord[j].toLowerCase()) { + return null; + } else { + let result: IMatch[] = null; + let nextUpperIndex = j + 1; + result = _matchesCamelCase(word, camelCaseWord, i + 1, j + 1); + while (!result && (nextUpperIndex = nextAnchor(camelCaseWord, nextUpperIndex)) < camelCaseWord.length) { + result = _matchesCamelCase(word, camelCaseWord, i + 1, nextUpperIndex); + nextUpperIndex++; + } + return result === null ? null : join({ start: j, end: j + 1 }, result); + } +} + +interface ICamelCaseAnalysis { + upperPercent: number; + lowerPercent: number; + alphaPercent: number; + numericPercent: number; +} + +// Heuristic to avoid computing camel case matcher for words that don't +// look like camelCaseWords. +function analyzeCamelCaseWord(word: string): ICamelCaseAnalysis { + let upper = 0, lower = 0, alpha = 0, numeric = 0, code = 0; + + for (let i = 0; i < word.length; i++) { + code = word.charCodeAt(i); + + if (isUpper(code)) { upper++; } + if (isLower(code)) { lower++; } + if (isAlphanumeric(code)) { alpha++; } + if (isNumber(code)) { numeric++; } + } + + let upperPercent = upper / word.length; + let lowerPercent = lower / word.length; + let alphaPercent = alpha / word.length; + let numericPercent = numeric / word.length; + + return { upperPercent, lowerPercent, alphaPercent, numericPercent }; +} + +function isUpperCaseWord(analysis: ICamelCaseAnalysis): boolean { + const { upperPercent, lowerPercent } = analysis; + return lowerPercent === 0 && upperPercent > 0.6; +} + +function isCamelCaseWord(analysis: ICamelCaseAnalysis): boolean { + const { upperPercent, lowerPercent, alphaPercent, numericPercent } = analysis; + return lowerPercent > 0.2 && upperPercent < 0.8 && alphaPercent > 0.6 && numericPercent < 0.2; +} + +// Heuristic to avoid computing camel case matcher for words that don't +// look like camel case patterns. +function isCamelCasePattern(word: string): boolean { + let upper = 0, lower = 0, code = 0, whitespace = 0; + + for (let i = 0; i < word.length; i++) { + code = word.charCodeAt(i); + + if (isUpper(code)) { upper++; } + if (isLower(code)) { lower++; } + if (isWhitespace(code)) { whitespace++; } + } + + if ((upper === 0 || lower === 0) && whitespace === 0) { + return word.length <= 30; + } else { + return upper <= 5; + } +} + +export function matchesCamelCase(word: string, camelCaseWord: string): IMatch[] { + if (!camelCaseWord) { + return null; + } + + camelCaseWord = camelCaseWord.trim(); + + if (camelCaseWord.length === 0) { + return null; + } + + if (!isCamelCasePattern(word)) { + return null; + } + + if (camelCaseWord.length > 60) { + return null; + } + + const analysis = analyzeCamelCaseWord(camelCaseWord); + + if (!isCamelCaseWord(analysis)) { + if (!isUpperCaseWord(analysis)) { + return null; + } + + camelCaseWord = camelCaseWord.toLowerCase(); + } + + let result: IMatch[] = null; + let i = 0; + + word = word.toLowerCase(); + while (i < camelCaseWord.length && (result = _matchesCamelCase(word, camelCaseWord, 0, i)) === null) { + i = nextAnchor(camelCaseWord, i + 1); + } + + return result; +} + +export function createMatches(position: number[]): IMatch[] { + let ret: IMatch[] = []; + if (!position) { + return ret; + } + let last: IMatch; + for (const pos of position) { + if (last && last.end === pos) { + last.end += 1; + } else { + last = { start: pos, end: pos + 1 }; + ret.push(last); + } + } + return ret; +} diff --git a/extensions/search-rg/src/ripgrepFileSearch.ts b/extensions/search-rg/src/ripgrepFileSearch.ts index eaa75d647ef..026238d1a93 100644 --- a/extensions/search-rg/src/ripgrepFileSearch.ts +++ b/extensions/search-rg/src/ripgrepFileSearch.ts @@ -17,7 +17,7 @@ const isMac = process.platform === 'darwin'; // If vscode-ripgrep is in an .asar file, then the binary is unpacked. const rgDiskPath = rgPath.replace(/\bnode_modules\.asar\b/, 'node_modules.asar.unpacked'); -export class RipgrepFileSearchEngine { +export class RipgrepFileSearch { private rgProc: cp.ChildProcess; private killRgProcFn: (code?: number) => void; @@ -30,7 +30,7 @@ export class RipgrepFileSearchEngine { process.removeListener('exit', this.killRgProcFn); } - provideFileSearchResults(options: vscode.SearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { this.outputChannel.appendLine(`provideFileSearchResults ${JSON.stringify({ ...options, ...{ diff --git a/extensions/search-rg/src/strings.ts b/extensions/search-rg/src/strings.ts new file mode 100644 index 00000000000..5b9198709eb --- /dev/null +++ b/extensions/search-rg/src/strings.ts @@ -0,0 +1,683 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import { CharCode } from './charCode'; + +/** + * The empty string. + */ +export const empty = ''; + +export function isFalsyOrWhitespace(str: string): boolean { + if (!str || typeof str !== 'string') { + return true; + } + return str.trim().length === 0; +} + +/** + * @returns the provided number with the given number of preceding zeros. + */ +export function pad(n: number, l: number, char: string = '0'): string { + let str = '' + n; + let r = [str]; + + for (let i = str.length; i < l; i++) { + r.push(char); + } + + return r.reverse().join(''); +} + +const _formatRegexp = /{(\d+)}/g; + +/** + * Helper to produce a string with a variable number of arguments. Insert variable segments + * into the string using the {n} notation where N is the index of the argument following the string. + * @param value string to which formatting is applied + * @param args replacements for {n}-entries + */ +export function format(value: string, ...args: any[]): string { + if (args.length === 0) { + return value; + } + return value.replace(_formatRegexp, function (match, group) { + let idx = parseInt(group, 10); + return isNaN(idx) || idx < 0 || idx >= args.length ? + match : + args[idx]; + }); +} + +/** + * Converts HTML characters inside the string to use entities instead. Makes the string safe from + * being used e.g. in HTMLElement.innerHTML. + */ +export function escape(html: string): string { + return html.replace(/[<|>|&]/g, function (match) { + switch (match) { + case '<': return '<'; + case '>': return '>'; + case '&': return '&'; + default: return match; + } + }); +} + +/** + * Escapes regular expression characters in a given string + */ +export function escapeRegExpCharacters(value: string): string { + return value.replace(/[\-\\\{\}\*\+\?\|\^\$\.\[\]\(\)\#]/g, '\\$&'); +} + +/** + * Removes all occurrences of needle from the beginning and end of haystack. + * @param haystack string to trim + * @param needle the thing to trim (default is a blank) + */ +export function trim(haystack: string, needle: string = ' '): string { + let trimmed = ltrim(haystack, needle); + return rtrim(trimmed, needle); +} + +/** + * Removes all occurrences of needle from the beginning of haystack. + * @param haystack string to trim + * @param needle the thing to trim + */ +export function ltrim(haystack?: string, needle?: string): string { + if (!haystack || !needle) { + return haystack; + } + + let needleLen = needle.length; + if (needleLen === 0 || haystack.length === 0) { + return haystack; + } + + let offset = 0, + idx = -1; + + while ((idx = haystack.indexOf(needle, offset)) === offset) { + offset = offset + needleLen; + } + return haystack.substring(offset); +} + +/** + * Removes all occurrences of needle from the end of haystack. + * @param haystack string to trim + * @param needle the thing to trim + */ +export function rtrim(haystack?: string, needle?: string): string { + if (!haystack || !needle) { + return haystack; + } + + let needleLen = needle.length, + haystackLen = haystack.length; + + if (needleLen === 0 || haystackLen === 0) { + return haystack; + } + + let offset = haystackLen, + idx = -1; + + while (true) { + idx = haystack.lastIndexOf(needle, offset - 1); + if (idx === -1 || idx + needleLen !== offset) { + break; + } + if (idx === 0) { + return ''; + } + offset = idx; + } + + return haystack.substring(0, offset); +} + +export function convertSimple2RegExpPattern(pattern: string): string { + return pattern.replace(/[\-\\\{\}\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&').replace(/[\*]/g, '.*'); +} + +export function stripWildcards(pattern: string): string { + return pattern.replace(/\*/g, ''); +} + +/** + * Determines if haystack starts with needle. + */ +export function startsWith(haystack: string, needle: string): boolean { + if (haystack.length < needle.length) { + return false; + } + + if (haystack === needle) { + return true; + } + + for (let i = 0; i < needle.length; i++) { + if (haystack[i] !== needle[i]) { + return false; + } + } + + return true; +} + +/** + * Determines if haystack ends with needle. + */ +export function endsWith(haystack: string, needle: string): boolean { + let diff = haystack.length - needle.length; + if (diff > 0) { + return haystack.indexOf(needle, diff) === diff; + } else if (diff === 0) { + return haystack === needle; + } else { + return false; + } +} + +export interface RegExpOptions { + matchCase?: boolean; + wholeWord?: boolean; + multiline?: boolean; + global?: boolean; +} + +export function createRegExp(searchString: string, isRegex: boolean, options: RegExpOptions = {}): RegExp { + if (!searchString) { + throw new Error('Cannot create regex from empty string'); + } + if (!isRegex) { + searchString = escapeRegExpCharacters(searchString); + } + if (options.wholeWord) { + if (!/\B/.test(searchString.charAt(0))) { + searchString = '\\b' + searchString; + } + if (!/\B/.test(searchString.charAt(searchString.length - 1))) { + searchString = searchString + '\\b'; + } + } + let modifiers = ''; + if (options.global) { + modifiers += 'g'; + } + if (!options.matchCase) { + modifiers += 'i'; + } + if (options.multiline) { + modifiers += 'm'; + } + + return new RegExp(searchString, modifiers); +} + +export function regExpLeadsToEndlessLoop(regexp: RegExp): boolean { + // Exit early if it's one of these special cases which are meant to match + // against an empty string + if (regexp.source === '^' || regexp.source === '^$' || regexp.source === '$' || regexp.source === '^\\s*$') { + return false; + } + + // We check against an empty string. If the regular expression doesn't advance + // (e.g. ends in an endless loop) it will match an empty string. + let match = regexp.exec(''); + return (match && regexp.lastIndex === 0); +} + +export function regExpContainsBackreference(regexpValue: string): boolean { + return !!regexpValue.match(/([^\\]|^)(\\\\)*\\\d+/); +} + +/** + * Returns first index of the string that is not whitespace. + * If string is empty or contains only whitespaces, returns -1 + */ +export function firstNonWhitespaceIndex(str: string): number { + for (let i = 0, len = str.length; i < len; i++) { + let chCode = str.charCodeAt(i); + if (chCode !== CharCode.Space && chCode !== CharCode.Tab) { + return i; + } + } + return -1; +} + +/** + * Returns the leading whitespace of the string. + * If the string contains only whitespaces, returns entire string + */ +export function getLeadingWhitespace(str: string, start: number = 0, end: number = str.length): string { + for (let i = start; i < end; i++) { + let chCode = str.charCodeAt(i); + if (chCode !== CharCode.Space && chCode !== CharCode.Tab) { + return str.substring(start, i); + } + } + return str.substring(start, end); +} + +/** + * Returns last index of the string that is not whitespace. + * If string is empty or contains only whitespaces, returns -1 + */ +export function lastNonWhitespaceIndex(str: string, startIndex: number = str.length - 1): number { + for (let i = startIndex; i >= 0; i--) { + let chCode = str.charCodeAt(i); + if (chCode !== CharCode.Space && chCode !== CharCode.Tab) { + return i; + } + } + return -1; +} + +export function compare(a: string, b: string): number { + if (a < b) { + return -1; + } else if (a > b) { + return 1; + } else { + return 0; + } +} + +export function compareIgnoreCase(a: string, b: string): number { + const len = Math.min(a.length, b.length); + for (let i = 0; i < len; i++) { + let codeA = a.charCodeAt(i); + let codeB = b.charCodeAt(i); + + if (codeA === codeB) { + // equal + continue; + } + + if (isUpperAsciiLetter(codeA)) { + codeA += 32; + } + + if (isUpperAsciiLetter(codeB)) { + codeB += 32; + } + + const diff = codeA - codeB; + + if (diff === 0) { + // equal -> ignoreCase + continue; + + } else if (isLowerAsciiLetter(codeA) && isLowerAsciiLetter(codeB)) { + // + return diff; + + } else { + return compare(a.toLowerCase(), b.toLowerCase()); + } + } + + if (a.length < b.length) { + return -1; + } else if (a.length > b.length) { + return 1; + } else { + return 0; + } +} + +function isLowerAsciiLetter(code: number): boolean { + return code >= CharCode.a && code <= CharCode.z; +} + +function isUpperAsciiLetter(code: number): boolean { + return code >= CharCode.A && code <= CharCode.Z; +} + +function isAsciiLetter(code: number): boolean { + return isLowerAsciiLetter(code) || isUpperAsciiLetter(code); +} + +export function equalsIgnoreCase(a: string, b: string): boolean { + const len1 = a ? a.length : 0; + const len2 = b ? b.length : 0; + + if (len1 !== len2) { + return false; + } + + return doEqualsIgnoreCase(a, b); +} + +function doEqualsIgnoreCase(a: string, b: string, stopAt = a.length): boolean { + if (typeof a !== 'string' || typeof b !== 'string') { + return false; + } + + for (let i = 0; i < stopAt; i++) { + const codeA = a.charCodeAt(i); + const codeB = b.charCodeAt(i); + + if (codeA === codeB) { + continue; + } + + // a-z A-Z + if (isAsciiLetter(codeA) && isAsciiLetter(codeB)) { + let diff = Math.abs(codeA - codeB); + if (diff !== 0 && diff !== 32) { + return false; + } + } + + // Any other charcode + else { + if (String.fromCharCode(codeA).toLowerCase() !== String.fromCharCode(codeB).toLowerCase()) { + return false; + } + } + } + + return true; +} + +export function startsWithIgnoreCase(str: string, candidate: string): boolean { + const candidateLength = candidate.length; + if (candidate.length > str.length) { + return false; + } + + return doEqualsIgnoreCase(str, candidate, candidateLength); +} + +/** + * @returns the length of the common prefix of the two strings. + */ +export function commonPrefixLength(a: string, b: string): number { + + let i: number, + len = Math.min(a.length, b.length); + + for (i = 0; i < len; i++) { + if (a.charCodeAt(i) !== b.charCodeAt(i)) { + return i; + } + } + + return len; +} + +/** + * @returns the length of the common suffix of the two strings. + */ +export function commonSuffixLength(a: string, b: string): number { + + let i: number, + len = Math.min(a.length, b.length); + + let aLastIndex = a.length - 1; + let bLastIndex = b.length - 1; + + for (i = 0; i < len; i++) { + if (a.charCodeAt(aLastIndex - i) !== b.charCodeAt(bLastIndex - i)) { + return i; + } + } + + return len; +} + +function substrEquals(a: string, aStart: number, aEnd: number, b: string, bStart: number, bEnd: number): boolean { + while (aStart < aEnd && bStart < bEnd) { + if (a[aStart] !== b[bStart]) { + return false; + } + aStart += 1; + bStart += 1; + } + return true; +} + +/** + * Return the overlap between the suffix of `a` and the prefix of `b`. + * For instance `overlap("foobar", "arr, I'm a pirate") === 2`. + */ +export function overlap(a: string, b: string): number { + let aEnd = a.length; + let bEnd = b.length; + let aStart = aEnd - bEnd; + + if (aStart === 0) { + return a === b ? aEnd : 0; + } else if (aStart < 0) { + bEnd += aStart; + aStart = 0; + } + + while (aStart < aEnd && bEnd > 0) { + if (substrEquals(a, aStart, aEnd, b, 0, bEnd)) { + return bEnd; + } + bEnd -= 1; + aStart += 1; + } + return 0; +} + +// --- unicode +// http://en.wikipedia.org/wiki/Surrogate_pair +// Returns the code point starting at a specified index in a string +// Code points U+0000 to U+D7FF and U+E000 to U+FFFF are represented on a single character +// Code points U+10000 to U+10FFFF are represented on two consecutive characters +//export function getUnicodePoint(str:string, index:number, len:number):number { +// let chrCode = str.charCodeAt(index); +// if (0xD800 <= chrCode && chrCode <= 0xDBFF && index + 1 < len) { +// let nextChrCode = str.charCodeAt(index + 1); +// if (0xDC00 <= nextChrCode && nextChrCode <= 0xDFFF) { +// return (chrCode - 0xD800) << 10 + (nextChrCode - 0xDC00) + 0x10000; +// } +// } +// return chrCode; +//} +export function isHighSurrogate(charCode: number): boolean { + return (0xD800 <= charCode && charCode <= 0xDBFF); +} + +export function isLowSurrogate(charCode: number): boolean { + return (0xDC00 <= charCode && charCode <= 0xDFFF); +} + +/** + * Generated using https://github.com/alexandrudima/unicode-utils/blob/master/generate-rtl-test.js + */ +const CONTAINS_RTL = /(?:[\u05BE\u05C0\u05C3\u05C6\u05D0-\u05F4\u0608\u060B\u060D\u061B-\u064A\u066D-\u066F\u0671-\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u0710\u0712-\u072F\u074D-\u07A5\u07B1-\u07EA\u07F4\u07F5\u07FA-\u0815\u081A\u0824\u0828\u0830-\u0858\u085E-\u08BD\u200F\uFB1D\uFB1F-\uFB28\uFB2A-\uFD3D\uFD50-\uFDFC\uFE70-\uFEFC]|\uD802[\uDC00-\uDD1B\uDD20-\uDE00\uDE10-\uDE33\uDE40-\uDEE4\uDEEB-\uDF35\uDF40-\uDFFF]|\uD803[\uDC00-\uDCFF]|\uD83A[\uDC00-\uDCCF\uDD00-\uDD43\uDD50-\uDFFF]|\uD83B[\uDC00-\uDEBB])/; + +/** + * Returns true if `str` contains any Unicode character that is classified as "R" or "AL". + */ +export function containsRTL(str: string): boolean { + return CONTAINS_RTL.test(str); +} + +/** + * Generated using https://github.com/alexandrudima/unicode-utils/blob/master/generate-emoji-test.js + */ +const CONTAINS_EMOJI = /(?:[\u231A\u231B\u23F0\u23F3\u2600-\u27BF\u2B50\u2B55]|\uD83C[\uDDE6-\uDDFF\uDF00-\uDFFF]|\uD83D[\uDC00-\uDE4F\uDE80-\uDEF8]|\uD83E[\uDD00-\uDDE6])/; + +export function containsEmoji(str: string): boolean { + return CONTAINS_EMOJI.test(str); +} + +const IS_BASIC_ASCII = /^[\t\n\r\x20-\x7E]*$/; +/** + * Returns true if `str` contains only basic ASCII characters in the range 32 - 126 (including 32 and 126) or \n, \r, \t + */ +export function isBasicASCII(str: string): boolean { + return IS_BASIC_ASCII.test(str); +} + +export function containsFullWidthCharacter(str: string): boolean { + for (let i = 0, len = str.length; i < len; i++) { + if (isFullWidthCharacter(str.charCodeAt(i))) { + return true; + } + } + return false; +} + +export function isFullWidthCharacter(charCode: number): boolean { + // Do a cheap trick to better support wrapping of wide characters, treat them as 2 columns + // http://jrgraphix.net/research/unicode_blocks.php + // 2E80 — 2EFF CJK Radicals Supplement + // 2F00 — 2FDF Kangxi Radicals + // 2FF0 — 2FFF Ideographic Description Characters + // 3000 — 303F CJK Symbols and Punctuation + // 3040 — 309F Hiragana + // 30A0 — 30FF Katakana + // 3100 — 312F Bopomofo + // 3130 — 318F Hangul Compatibility Jamo + // 3190 — 319F Kanbun + // 31A0 — 31BF Bopomofo Extended + // 31F0 — 31FF Katakana Phonetic Extensions + // 3200 — 32FF Enclosed CJK Letters and Months + // 3300 — 33FF CJK Compatibility + // 3400 — 4DBF CJK Unified Ideographs Extension A + // 4DC0 — 4DFF Yijing Hexagram Symbols + // 4E00 — 9FFF CJK Unified Ideographs + // A000 — A48F Yi Syllables + // A490 — A4CF Yi Radicals + // AC00 — D7AF Hangul Syllables + // [IGNORE] D800 — DB7F High Surrogates + // [IGNORE] DB80 — DBFF High Private Use Surrogates + // [IGNORE] DC00 — DFFF Low Surrogates + // [IGNORE] E000 — F8FF Private Use Area + // F900 — FAFF CJK Compatibility Ideographs + // [IGNORE] FB00 — FB4F Alphabetic Presentation Forms + // [IGNORE] FB50 — FDFF Arabic Presentation Forms-A + // [IGNORE] FE00 — FE0F Variation Selectors + // [IGNORE] FE20 — FE2F Combining Half Marks + // [IGNORE] FE30 — FE4F CJK Compatibility Forms + // [IGNORE] FE50 — FE6F Small Form Variants + // [IGNORE] FE70 — FEFF Arabic Presentation Forms-B + // FF00 — FFEF Halfwidth and Fullwidth Forms + // [https://en.wikipedia.org/wiki/Halfwidth_and_fullwidth_forms] + // of which FF01 - FF5E fullwidth ASCII of 21 to 7E + // [IGNORE] and FF65 - FFDC halfwidth of Katakana and Hangul + // [IGNORE] FFF0 — FFFF Specials + charCode = +charCode; // @perf + return ( + (charCode >= 0x2E80 && charCode <= 0xD7AF) + || (charCode >= 0xF900 && charCode <= 0xFAFF) + || (charCode >= 0xFF01 && charCode <= 0xFF5E) + ); +} + +/** + * Given a string and a max length returns a shorted version. Shorting + * happens at favorable positions - such as whitespace or punctuation characters. + */ +export function lcut(text: string, n: number) { + if (text.length < n) { + return text; + } + + const re = /\b/g; + let i = 0; + while (re.test(text)) { + if (text.length - re.lastIndex < n) { + break; + } + + i = re.lastIndex; + re.lastIndex += 1; + } + + return text.substring(i).replace(/^\s/, empty); +} + +// Escape codes +// http://en.wikipedia.org/wiki/ANSI_escape_code +const EL = /\x1B\x5B[12]?K/g; // Erase in line +const COLOR_START = /\x1b\[\d+m/g; // Color +const COLOR_END = /\x1b\[0?m/g; // Color + +export function removeAnsiEscapeCodes(str: string): string { + if (str) { + str = str.replace(EL, ''); + str = str.replace(COLOR_START, ''); + str = str.replace(COLOR_END, ''); + } + + return str; +} + +// -- UTF-8 BOM + +export const UTF8_BOM_CHARACTER = String.fromCharCode(CharCode.UTF8_BOM); + +export function startsWithUTF8BOM(str: string): boolean { + return (str && str.length > 0 && str.charCodeAt(0) === CharCode.UTF8_BOM); +} + +export function stripUTF8BOM(str: string): string { + return startsWithUTF8BOM(str) ? str.substr(1) : str; +} + +export function repeat(s: string, count: number): string { + let result = ''; + for (let i = 0; i < count; i++) { + result += s; + } + return result; +} + +/** + * Checks if the characters of the provided query string are included in the + * target string. The characters do not have to be contiguous within the string. + */ +export function fuzzyContains(target: string, query: string): boolean { + if (!target || !query) { + return false; // return early if target or query are undefined + } + + if (target.length < query.length) { + return false; // impossible for query to be contained in target + } + + const queryLen = query.length; + const targetLower = target.toLowerCase(); + + let index = 0; + let lastIndexOf = -1; + while (index < queryLen) { + let indexOf = targetLower.indexOf(query[index], lastIndexOf + 1); + if (indexOf < 0) { + return false; + } + + lastIndexOf = indexOf; + + index++; + } + + return true; +} + +export function containsUppercaseCharacter(target: string, ignoreEscapedChars = false): boolean { + if (!target) { + return false; + } + + if (ignoreEscapedChars) { + target = target.replace(/\\./g, ''); + } + + return target.toLowerCase() !== target; +} diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 73007519c97..ff7897f0cb4 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -28,6 +28,7 @@ declare module 'vscode' { excludes: string[]; useIgnoreFiles?: boolean; followSymlinks?: boolean; + maxResults: number; } export interface TextSearchOptions extends SearchOptions { @@ -36,6 +37,11 @@ declare module 'vscode' { encoding?: string; } + export interface FileSearchQuery { + pattern: string; + cacheKey?: string; + } + export interface FileSearchOptions extends SearchOptions { } export interface TextSearchResult { @@ -47,7 +53,7 @@ declare module 'vscode' { } export interface SearchProvider { - provideFileSearchResults?(options: FileSearchOptions, progress: Progress, token: CancellationToken): Thenable; + provideFileSearchResults?(query: FileSearchQuery, options: FileSearchOptions, progress: Progress, token: CancellationToken): Thenable; provideTextSearchResults?(query: TextSearchQuery, options: TextSearchOptions, progress: Progress, token: CancellationToken): Thenable; } diff --git a/src/vs/workbench/api/node/extHostSearch.ts b/src/vs/workbench/api/node/extHostSearch.ts index c4cf892bd17..9f2baf368a1 100644 --- a/src/vs/workbench/api/node/extHostSearch.ts +++ b/src/vs/workbench/api/node/extHostSearch.ts @@ -4,21 +4,19 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import * as pfs from 'vs/base/node/pfs'; -import * as extfs from 'vs/base/node/extfs'; import * as path from 'path'; -import * as arrays from 'vs/base/common/arrays'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import * as glob from 'vs/base/common/glob'; +import { joinPath } from 'vs/base/common/resources'; import * as strings from 'vs/base/common/strings'; import URI, { UriComponents } from 'vs/base/common/uri'; import { PPromise, TPromise } from 'vs/base/common/winjs.base'; -import { IItemAccessor, ScorerCache, compareItemsByScore, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer'; -import { ICachedSearchStats, IFileMatch, IFolderQuery, IPatternInfo, IRawSearchQuery, ISearchQuery, ISearchCompleteStats, IRawFileMatch2 } from 'vs/platform/search/common/search'; +import * as extfs from 'vs/base/node/extfs'; +import * as pfs from 'vs/base/node/pfs'; +import { IFileMatch, IFolderQuery, IPatternInfo, IRawFileMatch2, IRawSearchQuery, ISearchCompleteStats, ISearchQuery } from 'vs/platform/search/common/search'; import * as vscode from 'vscode'; import { ExtHostSearchShape, IMainContext, MainContext, MainThreadSearchShape } from './extHost.protocol'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { joinPath } from 'vs/base/common/resources'; type OneOrMore = T | T[]; @@ -36,9 +34,7 @@ export class ExtHostSearch implements ExtHostSearchShape { constructor(mainContext: IMainContext, private _schemeTransformer: ISchemeTransformer, private _extfs = extfs, private _pfs = pfs) { this._proxy = mainContext.getProxy(MainContext.MainThreadSearch); - this._fileSearchManager = new FileSearchManager( - (eventName: string, data: any) => this._proxy.$handleTelemetry(eventName, data), - this._pfs); + this._fileSearchManager = new FileSearchManager(this._pfs); } private _transformScheme(scheme: string): string { @@ -495,7 +491,8 @@ class TextSearchEngine { useIgnoreFiles: !this.config.disregardIgnoreFiles, followSymlinks: !this.config.ignoreSymlinks, encoding: this.config.fileEncoding, - maxFileSize: this.config.maxFileSize + maxFileSize: this.config.maxFileSize, + maxResults: this.config.maxResults }; } } @@ -646,7 +643,11 @@ class FileSearchEngine { new TPromise(resolve => process.nextTick(resolve)) .then(() => { this.activeCancellationTokens.add(cancellation); - return this.provider.provideFileSearchResults(options, { report: onProviderResult }, cancellation.token); + return this.provider.provideFileSearchResults( + { cacheKey: this.config.cacheKey, pattern: this.config.filePattern }, + options, + { report: onProviderResult }, + cancellation.token); }) .then(() => { this.activeCancellationTokens.delete(cancellation); @@ -692,7 +693,8 @@ class FileSearchEngine { excludes, includes, useIgnoreFiles: !this.config.disregardIgnoreFiles, - followSymlinks: !this.config.ignoreSymlinks + followSymlinks: !this.config.ignoreSymlinks, + maxResults: this.config.maxResults }; } @@ -855,51 +857,23 @@ class FileSearchManager { private static readonly BATCH_SIZE = 512; - private caches: { [cacheKey: string]: Cache; } = Object.create(null); - - constructor(private telemetryCallback: (eventName: string, data: any) => void, private _pfs: typeof pfs) { } + constructor(private _pfs: typeof pfs) { } public fileSearch(config: ISearchQuery, provider: vscode.SearchProvider): PPromise> { - if (config.sortByScore) { - let sortedSearch = this.trySortedSearchFromCache(config); - if (!sortedSearch) { - const engineConfig = config.maxResults ? - { - ...config, - ...{ maxResults: null } - } : - config; - - const engine = new FileSearchEngine(engineConfig, provider, this._pfs); - sortedSearch = this.doSortedSearch(engine, provider, config); - } - - return new PPromise>((c, e, p) => { - process.nextTick(() => { // allow caller to register progress callback first - sortedSearch.then(([result, rawMatches]) => { - const serializedMatches = rawMatches.map(rawMatch => this.rawMatchToSearchItem(rawMatch)); - this.sendProgress(serializedMatches, p, FileSearchManager.BATCH_SIZE); - c(result); - }, e, p); - }); - }, () => { - sortedSearch.cancel(); - }); - } - - let searchPromise: PPromise>; + let searchP: PPromise; return new PPromise>((c, e, p) => { const engine = new FileSearchEngine(config, provider, this._pfs); - searchPromise = this.doSearch(engine, provider, FileSearchManager.BATCH_SIZE) - .then(c, e, progress => { - if (Array.isArray(progress)) { - p(progress.map(m => this.rawMatchToSearchItem(m))); - } else if ((progress).relativePath) { - p(this.rawMatchToSearchItem(progress)); - } - }); + searchP = this.doSearch(engine, provider, FileSearchManager.BATCH_SIZE).then(c, e, progress => { + if (Array.isArray(progress)) { + p(progress.map(m => this.rawMatchToSearchItem(m))); + } else if ((progress).relativePath) { + p(this.rawMatchToSearchItem(progress)); + } + }); }, () => { - searchPromise.cancel(); + if (searchP) { + searchP.cancel(); + } }); } @@ -909,193 +883,6 @@ class FileSearchManager { }; } - private doSortedSearch(engine: FileSearchEngine, provider: vscode.SearchProvider, config: IRawSearchQuery): PPromise<[ISearchCompleteStats, IInternalFileMatch[]]> { - let searchPromise: PPromise>; - let allResultsPromise = new PPromise<[ISearchCompleteStats, IInternalFileMatch[]], OneOrMore>((c, e, p) => { - let results: IInternalFileMatch[] = []; - searchPromise = this.doSearch(engine, provider, -1) - .then(result => { - c([result, results]); - this.telemetryCallback('fileSearch', null); - }, e, progress => { - if (Array.isArray(progress)) { - results = progress; - } else { - p(progress); - } - }); - }, () => { - searchPromise.cancel(); - }); - - let cache: Cache; - if (config.cacheKey) { - cache = this.getOrCreateCache(config.cacheKey); - cache.resultsToSearchCache[config.filePattern] = allResultsPromise; - allResultsPromise.then(null, err => { - delete cache.resultsToSearchCache[config.filePattern]; - }); - allResultsPromise = this.preventCancellation(allResultsPromise); - } - - let chained: TPromise; - return new PPromise<[ISearchCompleteStats, IInternalFileMatch[]]>((c, e, p) => { - chained = allResultsPromise.then(([result, results]) => { - const scorerCache: ScorerCache = cache ? cache.scorerCache : Object.create(null); - const unsortedResultTime = Date.now(); - return this.sortResults(config, results, scorerCache) - .then(sortedResults => { - const sortedResultTime = Date.now(); - - c([{ - stats: { - ...result.stats, - ...{ unsortedResultTime, sortedResultTime } - }, - limitHit: result.limitHit || typeof config.maxResults === 'number' && results.length > config.maxResults - }, sortedResults]); - }); - }, e, p); - }, () => { - chained.cancel(); - }); - } - - private getOrCreateCache(cacheKey: string): Cache { - const existing = this.caches[cacheKey]; - if (existing) { - return existing; - } - return this.caches[cacheKey] = new Cache(); - } - - private trySortedSearchFromCache(config: IRawSearchQuery): TPromise<[ISearchCompleteStats, IInternalFileMatch[]]> { - const cache = config.cacheKey && this.caches[config.cacheKey]; - if (!cache) { - return undefined; - } - - const cacheLookupStartTime = Date.now(); - const cached = this.getResultsFromCache(cache, config.filePattern); - if (cached) { - let chained: TPromise; - return new TPromise<[ISearchCompleteStats, IInternalFileMatch[]]>((c, e) => { - chained = cached.then(([result, results, cacheStats]) => { - const cacheLookupResultTime = Date.now(); - return this.sortResults(config, results, cache.scorerCache) - .then(sortedResults => { - const sortedResultTime = Date.now(); - - const stats: ICachedSearchStats = { - fromCache: true, - cacheLookupStartTime: cacheLookupStartTime, - cacheFilterStartTime: cacheStats.cacheFilterStartTime, - cacheLookupResultTime: cacheLookupResultTime, - cacheEntryCount: cacheStats.cacheFilterResultCount, - resultCount: results.length - }; - if (config.sortByScore) { - stats.unsortedResultTime = cacheLookupResultTime; - stats.sortedResultTime = sortedResultTime; - } - if (!cacheStats.cacheWasResolved) { - stats.joined = result.stats; - } - c([ - { - limitHit: result.limitHit || typeof config.maxResults === 'number' && results.length > config.maxResults, - stats: stats - }, - sortedResults - ]); - }); - }, e); - }, () => { - chained.cancel(); - }); - } - return undefined; - } - - private sortResults(config: IRawSearchQuery, results: IInternalFileMatch[], scorerCache: ScorerCache): TPromise { - // we use the same compare function that is used later when showing the results using fuzzy scoring - // this is very important because we are also limiting the number of results by config.maxResults - // and as such we want the top items to be included in this result set if the number of items - // exceeds config.maxResults. - const query = prepareQuery(config.filePattern); - const compare = (matchA: IInternalFileMatch, matchB: IInternalFileMatch) => compareItemsByScore(matchA, matchB, query, true, FileMatchItemAccessor, scorerCache); - - return arrays.topAsync(results, compare, config.maxResults, 10000); - } - - private sendProgress(results: IFileMatch[], progressCb: (batch: IFileMatch[]) => void, batchSize: number) { - if (batchSize && batchSize > 0) { - for (let i = 0; i < results.length; i += batchSize) { - progressCb(results.slice(i, i + batchSize)); - } - } else { - progressCb(results); - } - } - - private getResultsFromCache(cache: Cache, searchValue: string): PPromise<[ISearchCompleteStats, IInternalFileMatch[], CacheStats]> { - if (path.isAbsolute(searchValue)) { - return null; // bypass cache if user looks up an absolute path where matching goes directly on disk - } - - // Find cache entries by prefix of search value - const hasPathSep = searchValue.indexOf(path.sep) >= 0; - let cached: PPromise<[ISearchCompleteStats, IInternalFileMatch[]], OneOrMore>; - let wasResolved: boolean; - for (let previousSearch in cache.resultsToSearchCache) { - - // If we narrow down, we might be able to reuse the cached results - if (strings.startsWith(searchValue, previousSearch)) { - if (hasPathSep && previousSearch.indexOf(path.sep) < 0) { - continue; // since a path character widens the search for potential more matches, require it in previous search too - } - - const c = cache.resultsToSearchCache[previousSearch]; - c.then(() => { wasResolved = false; }); - wasResolved = true; - cached = this.preventCancellation(c); - break; - } - } - - if (!cached) { - return null; - } - - return new PPromise<[ISearchCompleteStats, IInternalFileMatch[], CacheStats]>((c, e, p) => { - cached.then(([complete, cachedEntries]) => { - const cacheFilterStartTime = Date.now(); - - // Pattern match on results - let results: IInternalFileMatch[] = []; - const normalizedSearchValueLowercase = strings.stripWildcards(searchValue).toLowerCase(); - for (let i = 0; i < cachedEntries.length; i++) { - let entry = cachedEntries[i]; - - // Check if this entry is a match for the search value - if (!strings.fuzzyContains(entry.relativePath, normalizedSearchValueLowercase)) { - continue; - } - - results.push(entry); - } - - c([complete, results, { - cacheWasResolved: wasResolved, - cacheFilterStartTime: cacheFilterStartTime, - cacheFilterResultCount: cachedEntries.length - }]); - }, e, p); - }, () => { - cached.cancel(); - }); - } - private doSearch(engine: FileSearchEngine, provider: vscode.SearchProvider, batchSize?: number): PPromise> { return new PPromise>((c, e, p) => { let batch: IInternalFileMatch[] = []; @@ -1131,48 +918,4 @@ class FileSearchManager { engine.cancel(); }); } - - public clearCache(cacheKey: string): TPromise { - delete this.caches[cacheKey]; - return TPromise.as(undefined); - } - - private preventCancellation(promise: PPromise): PPromise { - return new PPromise((c, e, p) => { - // Allow for piled up cancellations to come through first. - process.nextTick(() => { - promise.then(c, e, p); - }); - }, () => { - // Do not propagate. - }); - } -} - -class Cache { - - public resultsToSearchCache: { [searchValue: string]: PPromise<[ISearchCompleteStats, IInternalFileMatch[]], OneOrMore>; } = Object.create(null); - - public scorerCache: ScorerCache = Object.create(null); -} - -const FileMatchItemAccessor = new class implements IItemAccessor { - - public getItemLabel(match: IInternalFileMatch): string { - return match.basename; // e.g. myFile.txt - } - - public getItemDescription(match: IInternalFileMatch): string { - return match.relativePath.substr(0, match.relativePath.length - match.basename.length - 1); // e.g. some/path/to/file - } - - public getItemPath(match: IInternalFileMatch): string { - return match.relativePath; // e.g. some/path/to/file/myFile.txt - } -}; - -interface CacheStats { - cacheWasResolved: boolean; - cacheFilterStartTime: number; - cacheFilterResultCount: number; } From dc1c80e6ffaefbd35dbc51245e391fd9101d9648 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 8 Jun 2018 17:49:08 -0700 Subject: [PATCH 085/283] Clean up strings.ts --- extensions/search-rg/src/strings.ts | 607 ---------------------------- 1 file changed, 607 deletions(-) diff --git a/extensions/search-rg/src/strings.ts b/extensions/search-rg/src/strings.ts index 5b9198709eb..5f2b5495348 100644 --- a/extensions/search-rg/src/strings.ts +++ b/extensions/search-rg/src/strings.ts @@ -4,148 +4,6 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { CharCode } from './charCode'; - -/** - * The empty string. - */ -export const empty = ''; - -export function isFalsyOrWhitespace(str: string): boolean { - if (!str || typeof str !== 'string') { - return true; - } - return str.trim().length === 0; -} - -/** - * @returns the provided number with the given number of preceding zeros. - */ -export function pad(n: number, l: number, char: string = '0'): string { - let str = '' + n; - let r = [str]; - - for (let i = str.length; i < l; i++) { - r.push(char); - } - - return r.reverse().join(''); -} - -const _formatRegexp = /{(\d+)}/g; - -/** - * Helper to produce a string with a variable number of arguments. Insert variable segments - * into the string using the {n} notation where N is the index of the argument following the string. - * @param value string to which formatting is applied - * @param args replacements for {n}-entries - */ -export function format(value: string, ...args: any[]): string { - if (args.length === 0) { - return value; - } - return value.replace(_formatRegexp, function (match, group) { - let idx = parseInt(group, 10); - return isNaN(idx) || idx < 0 || idx >= args.length ? - match : - args[idx]; - }); -} - -/** - * Converts HTML characters inside the string to use entities instead. Makes the string safe from - * being used e.g. in HTMLElement.innerHTML. - */ -export function escape(html: string): string { - return html.replace(/[<|>|&]/g, function (match) { - switch (match) { - case '<': return '<'; - case '>': return '>'; - case '&': return '&'; - default: return match; - } - }); -} - -/** - * Escapes regular expression characters in a given string - */ -export function escapeRegExpCharacters(value: string): string { - return value.replace(/[\-\\\{\}\*\+\?\|\^\$\.\[\]\(\)\#]/g, '\\$&'); -} - -/** - * Removes all occurrences of needle from the beginning and end of haystack. - * @param haystack string to trim - * @param needle the thing to trim (default is a blank) - */ -export function trim(haystack: string, needle: string = ' '): string { - let trimmed = ltrim(haystack, needle); - return rtrim(trimmed, needle); -} - -/** - * Removes all occurrences of needle from the beginning of haystack. - * @param haystack string to trim - * @param needle the thing to trim - */ -export function ltrim(haystack?: string, needle?: string): string { - if (!haystack || !needle) { - return haystack; - } - - let needleLen = needle.length; - if (needleLen === 0 || haystack.length === 0) { - return haystack; - } - - let offset = 0, - idx = -1; - - while ((idx = haystack.indexOf(needle, offset)) === offset) { - offset = offset + needleLen; - } - return haystack.substring(offset); -} - -/** - * Removes all occurrences of needle from the end of haystack. - * @param haystack string to trim - * @param needle the thing to trim - */ -export function rtrim(haystack?: string, needle?: string): string { - if (!haystack || !needle) { - return haystack; - } - - let needleLen = needle.length, - haystackLen = haystack.length; - - if (needleLen === 0 || haystackLen === 0) { - return haystack; - } - - let offset = haystackLen, - idx = -1; - - while (true) { - idx = haystack.lastIndexOf(needle, offset - 1); - if (idx === -1 || idx + needleLen !== offset) { - break; - } - if (idx === 0) { - return ''; - } - offset = idx; - } - - return haystack.substring(0, offset); -} - -export function convertSimple2RegExpPattern(pattern: string): string { - return pattern.replace(/[\-\\\{\}\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&').replace(/[\*]/g, '.*'); -} - export function stripWildcards(pattern: string): string { return pattern.replace(/\*/g, ''); } @@ -185,459 +43,6 @@ export function endsWith(haystack: string, needle: string): boolean { } } -export interface RegExpOptions { - matchCase?: boolean; - wholeWord?: boolean; - multiline?: boolean; - global?: boolean; -} - -export function createRegExp(searchString: string, isRegex: boolean, options: RegExpOptions = {}): RegExp { - if (!searchString) { - throw new Error('Cannot create regex from empty string'); - } - if (!isRegex) { - searchString = escapeRegExpCharacters(searchString); - } - if (options.wholeWord) { - if (!/\B/.test(searchString.charAt(0))) { - searchString = '\\b' + searchString; - } - if (!/\B/.test(searchString.charAt(searchString.length - 1))) { - searchString = searchString + '\\b'; - } - } - let modifiers = ''; - if (options.global) { - modifiers += 'g'; - } - if (!options.matchCase) { - modifiers += 'i'; - } - if (options.multiline) { - modifiers += 'm'; - } - - return new RegExp(searchString, modifiers); -} - -export function regExpLeadsToEndlessLoop(regexp: RegExp): boolean { - // Exit early if it's one of these special cases which are meant to match - // against an empty string - if (regexp.source === '^' || regexp.source === '^$' || regexp.source === '$' || regexp.source === '^\\s*$') { - return false; - } - - // We check against an empty string. If the regular expression doesn't advance - // (e.g. ends in an endless loop) it will match an empty string. - let match = regexp.exec(''); - return (match && regexp.lastIndex === 0); -} - -export function regExpContainsBackreference(regexpValue: string): boolean { - return !!regexpValue.match(/([^\\]|^)(\\\\)*\\\d+/); -} - -/** - * Returns first index of the string that is not whitespace. - * If string is empty or contains only whitespaces, returns -1 - */ -export function firstNonWhitespaceIndex(str: string): number { - for (let i = 0, len = str.length; i < len; i++) { - let chCode = str.charCodeAt(i); - if (chCode !== CharCode.Space && chCode !== CharCode.Tab) { - return i; - } - } - return -1; -} - -/** - * Returns the leading whitespace of the string. - * If the string contains only whitespaces, returns entire string - */ -export function getLeadingWhitespace(str: string, start: number = 0, end: number = str.length): string { - for (let i = start; i < end; i++) { - let chCode = str.charCodeAt(i); - if (chCode !== CharCode.Space && chCode !== CharCode.Tab) { - return str.substring(start, i); - } - } - return str.substring(start, end); -} - -/** - * Returns last index of the string that is not whitespace. - * If string is empty or contains only whitespaces, returns -1 - */ -export function lastNonWhitespaceIndex(str: string, startIndex: number = str.length - 1): number { - for (let i = startIndex; i >= 0; i--) { - let chCode = str.charCodeAt(i); - if (chCode !== CharCode.Space && chCode !== CharCode.Tab) { - return i; - } - } - return -1; -} - -export function compare(a: string, b: string): number { - if (a < b) { - return -1; - } else if (a > b) { - return 1; - } else { - return 0; - } -} - -export function compareIgnoreCase(a: string, b: string): number { - const len = Math.min(a.length, b.length); - for (let i = 0; i < len; i++) { - let codeA = a.charCodeAt(i); - let codeB = b.charCodeAt(i); - - if (codeA === codeB) { - // equal - continue; - } - - if (isUpperAsciiLetter(codeA)) { - codeA += 32; - } - - if (isUpperAsciiLetter(codeB)) { - codeB += 32; - } - - const diff = codeA - codeB; - - if (diff === 0) { - // equal -> ignoreCase - continue; - - } else if (isLowerAsciiLetter(codeA) && isLowerAsciiLetter(codeB)) { - // - return diff; - - } else { - return compare(a.toLowerCase(), b.toLowerCase()); - } - } - - if (a.length < b.length) { - return -1; - } else if (a.length > b.length) { - return 1; - } else { - return 0; - } -} - -function isLowerAsciiLetter(code: number): boolean { - return code >= CharCode.a && code <= CharCode.z; -} - -function isUpperAsciiLetter(code: number): boolean { - return code >= CharCode.A && code <= CharCode.Z; -} - -function isAsciiLetter(code: number): boolean { - return isLowerAsciiLetter(code) || isUpperAsciiLetter(code); -} - -export function equalsIgnoreCase(a: string, b: string): boolean { - const len1 = a ? a.length : 0; - const len2 = b ? b.length : 0; - - if (len1 !== len2) { - return false; - } - - return doEqualsIgnoreCase(a, b); -} - -function doEqualsIgnoreCase(a: string, b: string, stopAt = a.length): boolean { - if (typeof a !== 'string' || typeof b !== 'string') { - return false; - } - - for (let i = 0; i < stopAt; i++) { - const codeA = a.charCodeAt(i); - const codeB = b.charCodeAt(i); - - if (codeA === codeB) { - continue; - } - - // a-z A-Z - if (isAsciiLetter(codeA) && isAsciiLetter(codeB)) { - let diff = Math.abs(codeA - codeB); - if (diff !== 0 && diff !== 32) { - return false; - } - } - - // Any other charcode - else { - if (String.fromCharCode(codeA).toLowerCase() !== String.fromCharCode(codeB).toLowerCase()) { - return false; - } - } - } - - return true; -} - -export function startsWithIgnoreCase(str: string, candidate: string): boolean { - const candidateLength = candidate.length; - if (candidate.length > str.length) { - return false; - } - - return doEqualsIgnoreCase(str, candidate, candidateLength); -} - -/** - * @returns the length of the common prefix of the two strings. - */ -export function commonPrefixLength(a: string, b: string): number { - - let i: number, - len = Math.min(a.length, b.length); - - for (i = 0; i < len; i++) { - if (a.charCodeAt(i) !== b.charCodeAt(i)) { - return i; - } - } - - return len; -} - -/** - * @returns the length of the common suffix of the two strings. - */ -export function commonSuffixLength(a: string, b: string): number { - - let i: number, - len = Math.min(a.length, b.length); - - let aLastIndex = a.length - 1; - let bLastIndex = b.length - 1; - - for (i = 0; i < len; i++) { - if (a.charCodeAt(aLastIndex - i) !== b.charCodeAt(bLastIndex - i)) { - return i; - } - } - - return len; -} - -function substrEquals(a: string, aStart: number, aEnd: number, b: string, bStart: number, bEnd: number): boolean { - while (aStart < aEnd && bStart < bEnd) { - if (a[aStart] !== b[bStart]) { - return false; - } - aStart += 1; - bStart += 1; - } - return true; -} - -/** - * Return the overlap between the suffix of `a` and the prefix of `b`. - * For instance `overlap("foobar", "arr, I'm a pirate") === 2`. - */ -export function overlap(a: string, b: string): number { - let aEnd = a.length; - let bEnd = b.length; - let aStart = aEnd - bEnd; - - if (aStart === 0) { - return a === b ? aEnd : 0; - } else if (aStart < 0) { - bEnd += aStart; - aStart = 0; - } - - while (aStart < aEnd && bEnd > 0) { - if (substrEquals(a, aStart, aEnd, b, 0, bEnd)) { - return bEnd; - } - bEnd -= 1; - aStart += 1; - } - return 0; -} - -// --- unicode -// http://en.wikipedia.org/wiki/Surrogate_pair -// Returns the code point starting at a specified index in a string -// Code points U+0000 to U+D7FF and U+E000 to U+FFFF are represented on a single character -// Code points U+10000 to U+10FFFF are represented on two consecutive characters -//export function getUnicodePoint(str:string, index:number, len:number):number { -// let chrCode = str.charCodeAt(index); -// if (0xD800 <= chrCode && chrCode <= 0xDBFF && index + 1 < len) { -// let nextChrCode = str.charCodeAt(index + 1); -// if (0xDC00 <= nextChrCode && nextChrCode <= 0xDFFF) { -// return (chrCode - 0xD800) << 10 + (nextChrCode - 0xDC00) + 0x10000; -// } -// } -// return chrCode; -//} -export function isHighSurrogate(charCode: number): boolean { - return (0xD800 <= charCode && charCode <= 0xDBFF); -} - -export function isLowSurrogate(charCode: number): boolean { - return (0xDC00 <= charCode && charCode <= 0xDFFF); -} - -/** - * Generated using https://github.com/alexandrudima/unicode-utils/blob/master/generate-rtl-test.js - */ -const CONTAINS_RTL = /(?:[\u05BE\u05C0\u05C3\u05C6\u05D0-\u05F4\u0608\u060B\u060D\u061B-\u064A\u066D-\u066F\u0671-\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u0710\u0712-\u072F\u074D-\u07A5\u07B1-\u07EA\u07F4\u07F5\u07FA-\u0815\u081A\u0824\u0828\u0830-\u0858\u085E-\u08BD\u200F\uFB1D\uFB1F-\uFB28\uFB2A-\uFD3D\uFD50-\uFDFC\uFE70-\uFEFC]|\uD802[\uDC00-\uDD1B\uDD20-\uDE00\uDE10-\uDE33\uDE40-\uDEE4\uDEEB-\uDF35\uDF40-\uDFFF]|\uD803[\uDC00-\uDCFF]|\uD83A[\uDC00-\uDCCF\uDD00-\uDD43\uDD50-\uDFFF]|\uD83B[\uDC00-\uDEBB])/; - -/** - * Returns true if `str` contains any Unicode character that is classified as "R" or "AL". - */ -export function containsRTL(str: string): boolean { - return CONTAINS_RTL.test(str); -} - -/** - * Generated using https://github.com/alexandrudima/unicode-utils/blob/master/generate-emoji-test.js - */ -const CONTAINS_EMOJI = /(?:[\u231A\u231B\u23F0\u23F3\u2600-\u27BF\u2B50\u2B55]|\uD83C[\uDDE6-\uDDFF\uDF00-\uDFFF]|\uD83D[\uDC00-\uDE4F\uDE80-\uDEF8]|\uD83E[\uDD00-\uDDE6])/; - -export function containsEmoji(str: string): boolean { - return CONTAINS_EMOJI.test(str); -} - -const IS_BASIC_ASCII = /^[\t\n\r\x20-\x7E]*$/; -/** - * Returns true if `str` contains only basic ASCII characters in the range 32 - 126 (including 32 and 126) or \n, \r, \t - */ -export function isBasicASCII(str: string): boolean { - return IS_BASIC_ASCII.test(str); -} - -export function containsFullWidthCharacter(str: string): boolean { - for (let i = 0, len = str.length; i < len; i++) { - if (isFullWidthCharacter(str.charCodeAt(i))) { - return true; - } - } - return false; -} - -export function isFullWidthCharacter(charCode: number): boolean { - // Do a cheap trick to better support wrapping of wide characters, treat them as 2 columns - // http://jrgraphix.net/research/unicode_blocks.php - // 2E80 — 2EFF CJK Radicals Supplement - // 2F00 — 2FDF Kangxi Radicals - // 2FF0 — 2FFF Ideographic Description Characters - // 3000 — 303F CJK Symbols and Punctuation - // 3040 — 309F Hiragana - // 30A0 — 30FF Katakana - // 3100 — 312F Bopomofo - // 3130 — 318F Hangul Compatibility Jamo - // 3190 — 319F Kanbun - // 31A0 — 31BF Bopomofo Extended - // 31F0 — 31FF Katakana Phonetic Extensions - // 3200 — 32FF Enclosed CJK Letters and Months - // 3300 — 33FF CJK Compatibility - // 3400 — 4DBF CJK Unified Ideographs Extension A - // 4DC0 — 4DFF Yijing Hexagram Symbols - // 4E00 — 9FFF CJK Unified Ideographs - // A000 — A48F Yi Syllables - // A490 — A4CF Yi Radicals - // AC00 — D7AF Hangul Syllables - // [IGNORE] D800 — DB7F High Surrogates - // [IGNORE] DB80 — DBFF High Private Use Surrogates - // [IGNORE] DC00 — DFFF Low Surrogates - // [IGNORE] E000 — F8FF Private Use Area - // F900 — FAFF CJK Compatibility Ideographs - // [IGNORE] FB00 — FB4F Alphabetic Presentation Forms - // [IGNORE] FB50 — FDFF Arabic Presentation Forms-A - // [IGNORE] FE00 — FE0F Variation Selectors - // [IGNORE] FE20 — FE2F Combining Half Marks - // [IGNORE] FE30 — FE4F CJK Compatibility Forms - // [IGNORE] FE50 — FE6F Small Form Variants - // [IGNORE] FE70 — FEFF Arabic Presentation Forms-B - // FF00 — FFEF Halfwidth and Fullwidth Forms - // [https://en.wikipedia.org/wiki/Halfwidth_and_fullwidth_forms] - // of which FF01 - FF5E fullwidth ASCII of 21 to 7E - // [IGNORE] and FF65 - FFDC halfwidth of Katakana and Hangul - // [IGNORE] FFF0 — FFFF Specials - charCode = +charCode; // @perf - return ( - (charCode >= 0x2E80 && charCode <= 0xD7AF) - || (charCode >= 0xF900 && charCode <= 0xFAFF) - || (charCode >= 0xFF01 && charCode <= 0xFF5E) - ); -} - -/** - * Given a string and a max length returns a shorted version. Shorting - * happens at favorable positions - such as whitespace or punctuation characters. - */ -export function lcut(text: string, n: number) { - if (text.length < n) { - return text; - } - - const re = /\b/g; - let i = 0; - while (re.test(text)) { - if (text.length - re.lastIndex < n) { - break; - } - - i = re.lastIndex; - re.lastIndex += 1; - } - - return text.substring(i).replace(/^\s/, empty); -} - -// Escape codes -// http://en.wikipedia.org/wiki/ANSI_escape_code -const EL = /\x1B\x5B[12]?K/g; // Erase in line -const COLOR_START = /\x1b\[\d+m/g; // Color -const COLOR_END = /\x1b\[0?m/g; // Color - -export function removeAnsiEscapeCodes(str: string): string { - if (str) { - str = str.replace(EL, ''); - str = str.replace(COLOR_START, ''); - str = str.replace(COLOR_END, ''); - } - - return str; -} - -// -- UTF-8 BOM - -export const UTF8_BOM_CHARACTER = String.fromCharCode(CharCode.UTF8_BOM); - -export function startsWithUTF8BOM(str: string): boolean { - return (str && str.length > 0 && str.charCodeAt(0) === CharCode.UTF8_BOM); -} - -export function stripUTF8BOM(str: string): string { - return startsWithUTF8BOM(str) ? str.substr(1) : str; -} - -export function repeat(s: string, count: number): string { - let result = ''; - for (let i = 0; i < count; i++) { - result += s; - } - return result; -} - /** * Checks if the characters of the provided query string are included in the * target string. The characters do not have to be contiguous within the string. @@ -669,15 +74,3 @@ export function fuzzyContains(target: string, query: string): boolean { return true; } - -export function containsUppercaseCharacter(target: string, ignoreEscapedChars = false): boolean { - if (!target) { - return false; - } - - if (ignoreEscapedChars) { - target = target.replace(/\\./g, ''); - } - - return target.toLowerCase() !== target; -} From 995ef296433b9165cc63ef190f76531ed6bc2fb7 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 8 Jun 2018 18:19:48 -0700 Subject: [PATCH 086/283] Add missing methods to strings.ts --- extensions/search-rg/src/strings.ts | 75 +++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 4 deletions(-) diff --git a/extensions/search-rg/src/strings.ts b/extensions/search-rg/src/strings.ts index 5f2b5495348..a72fd6f4c19 100644 --- a/extensions/search-rg/src/strings.ts +++ b/extensions/search-rg/src/strings.ts @@ -1,9 +1,12 @@ /*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +* Copyright (c) Microsoft Corporation. All rights reserved. +* Licensed under the MIT License. See License.txt in the project root for license information. +*--------------------------------------------------------------------------------------------*/ + 'use strict'; +import { CharCode } from './charCode'; + export function stripWildcards(pattern: string): string { return pattern.replace(/\*/g, ''); } @@ -29,6 +32,15 @@ export function startsWith(haystack: string, needle: string): boolean { return true; } +export function startsWithIgnoreCase(str: string, candidate: string): boolean { + const candidateLength = candidate.length; + if (candidate.length > str.length) { + return false; + } + + return doEqualsIgnoreCase(str, candidate, candidateLength); +} + /** * Determines if haystack ends with needle. */ @@ -43,6 +55,61 @@ export function endsWith(haystack: string, needle: string): boolean { } } +function isLowerAsciiLetter(code: number): boolean { + return code >= CharCode.a && code <= CharCode.z; +} + +function isUpperAsciiLetter(code: number): boolean { + return code >= CharCode.A && code <= CharCode.Z; +} + +function isAsciiLetter(code: number): boolean { + return isLowerAsciiLetter(code) || isUpperAsciiLetter(code); +} + +export function equalsIgnoreCase(a: string, b: string): boolean { + const len1 = a ? a.length : 0; + const len2 = b ? b.length : 0; + + if (len1 !== len2) { + return false; + } + + return doEqualsIgnoreCase(a, b); +} + +function doEqualsIgnoreCase(a: string, b: string, stopAt = a.length): boolean { + if (typeof a !== 'string' || typeof b !== 'string') { + return false; + } + + for (let i = 0; i < stopAt; i++) { + const codeA = a.charCodeAt(i); + const codeB = b.charCodeAt(i); + + if (codeA === codeB) { + continue; + } + + // a-z A-Z + if (isAsciiLetter(codeA) && isAsciiLetter(codeB)) { + let diff = Math.abs(codeA - codeB); + if (diff !== 0 && diff !== 32) { + return false; + } + } + + // Any other charcode + else { + if (String.fromCharCode(codeA).toLowerCase() !== String.fromCharCode(codeB).toLowerCase()) { + return false; + } + } + } + + return true; +} + /** * Checks if the characters of the provided query string are included in the * target string. The characters do not have to be contiguous within the string. @@ -73,4 +140,4 @@ export function fuzzyContains(target: string, query: string): boolean { } return true; -} +} \ No newline at end of file From e0e9c3d455d5c32b98914d7f42eb2827f0cc9372 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 1 Jul 2018 17:09:36 -0700 Subject: [PATCH 087/283] Search provider - some clean up --- .../search-rg/src/cachedSearchProvider.ts | 10 +-- .../search-rg/src/{ => common}/arrays.ts | 2 +- .../search-rg/src/{ => common}/charCode.ts | 0 .../search-rg/src/{ => common}/comparers.ts | 7 ++ .../src/{ => common}/fileSearchScorer.ts | 7 +- .../search-rg/src/{ => common}/filters.ts | 0 .../src/{ => common}/normalization.ts | 0 .../search-rg/src/{ => common}/strings.ts | 6 +- extensions/search-rg/src/extension.ts | 6 +- extensions/search-rg/src/ripgrepFileSearch.ts | 2 +- src/vs/vscode.proposed.d.ts | 2 +- .../api/electron-browser/mainThreadSearch.ts | 26 ++++--- src/vs/workbench/api/node/extHost.protocol.ts | 3 +- src/vs/workbench/api/node/extHostSearch.ts | 71 ++++--------------- .../api/extHostSearch.test.ts | 32 ++++----- 15 files changed, 74 insertions(+), 100 deletions(-) rename extensions/search-rg/src/{ => common}/arrays.ts (99%) rename extensions/search-rg/src/{ => common}/charCode.ts (100%) rename extensions/search-rg/src/{ => common}/comparers.ts (92%) rename extensions/search-rg/src/{ => common}/fileSearchScorer.ts (98%) rename extensions/search-rg/src/{ => common}/filters.ts (100%) rename extensions/search-rg/src/{ => common}/normalization.ts (100%) rename extensions/search-rg/src/{ => common}/strings.ts (92%) diff --git a/extensions/search-rg/src/cachedSearchProvider.ts b/extensions/search-rg/src/cachedSearchProvider.ts index 201a279123f..78932b5deb4 100644 --- a/extensions/search-rg/src/cachedSearchProvider.ts +++ b/extensions/search-rg/src/cachedSearchProvider.ts @@ -5,9 +5,9 @@ import * as path from 'path'; import * as vscode from 'vscode'; -import * as arrays from './arrays'; -import { compareItemsByScore, IItemAccessor, prepareQuery, ScorerCache } from './fileSearchScorer'; -import * as strings from './strings'; +import * as arrays from './common/arrays'; +import { compareItemsByScore, IItemAccessor, prepareQuery, ScorerCache } from './common/fileSearchScorer'; +import * as strings from './common/strings'; interface IProviderArgs { query: vscode.FileSearchQuery; @@ -119,7 +119,7 @@ export class CachedSearchProvider { const preparedQuery = prepareQuery(args.query.pattern); const compare = (matchA: IInternalFileMatch, matchB: IInternalFileMatch) => compareItemsByScore(matchA, matchB, preparedQuery, true, FileMatchItemAccessor, scorerCache); - return arrays.topAsync(results, compare, args.options.maxResults, 10000); + return arrays.topAsync(results, compare, args.options.maxResults || 10000, 10000); } private getResultsFromCache(cache: Cache, searchValue: string, onResult: (results: IInternalFileMatch) => void): Promise<[IInternalFileMatch[], CacheStats]> { @@ -199,7 +199,7 @@ export class CachedSearchProvider { } }; - provider.provideFileSearchResults(args.query, args.options, { report: onProviderResult }, args.token).then(() => { + provider.provideFileSearchResults(args.query, args.options, { report: onProviderResult }, args.token).then(() => { if (batch.length) { onResult(batch); } diff --git a/extensions/search-rg/src/arrays.ts b/extensions/search-rg/src/common/arrays.ts similarity index 99% rename from extensions/search-rg/src/arrays.ts rename to extensions/search-rg/src/common/arrays.ts index 145ea2fd2a4..d06d185dfa5 100644 --- a/extensions/search-rg/src/arrays.ts +++ b/extensions/search-rg/src/common/arrays.ts @@ -72,4 +72,4 @@ export function findFirstInSorted(array: T[], p: (x: T) => boolean): number { } } return low; -} \ No newline at end of file +} diff --git a/extensions/search-rg/src/charCode.ts b/extensions/search-rg/src/common/charCode.ts similarity index 100% rename from extensions/search-rg/src/charCode.ts rename to extensions/search-rg/src/common/charCode.ts diff --git a/extensions/search-rg/src/comparers.ts b/extensions/search-rg/src/common/comparers.ts similarity index 92% rename from extensions/search-rg/src/comparers.ts rename to extensions/search-rg/src/common/comparers.ts index 1eb9632131d..fc6022526db 100644 --- a/extensions/search-rg/src/comparers.ts +++ b/extensions/search-rg/src/common/comparers.ts @@ -9,6 +9,13 @@ import * as strings from './strings'; let intlFileNameCollator: Intl.Collator; let intlFileNameCollatorIsNumeric: boolean; +setFileNameComparer(new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' })); + +export function setFileNameComparer(collator: Intl.Collator): void { + intlFileNameCollator = collator; + intlFileNameCollatorIsNumeric = collator.resolvedOptions().numeric; +} + export function compareFileNames(one: string, other: string, caseSensitive = false): number { if (intlFileNameCollator) { const a = one || ''; diff --git a/extensions/search-rg/src/fileSearchScorer.ts b/extensions/search-rg/src/common/fileSearchScorer.ts similarity index 98% rename from extensions/search-rg/src/fileSearchScorer.ts rename to extensions/search-rg/src/common/fileSearchScorer.ts index c2c9d8b68d8..d73d7c77f41 100644 --- a/extensions/search-rg/src/fileSearchScorer.ts +++ b/extensions/search-rg/src/common/fileSearchScorer.ts @@ -1,7 +1,8 @@ /*--------------------------------------------------------------------------------------------- -* Copyright (c) Microsoft Corporation. All rights reserved. -* Licensed under the MIT License. See License.txt in the project root for license information. -*--------------------------------------------------------------------------------------------*/ + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + 'use strict'; import { stripWildcards, equalsIgnoreCase } from './strings'; diff --git a/extensions/search-rg/src/filters.ts b/extensions/search-rg/src/common/filters.ts similarity index 100% rename from extensions/search-rg/src/filters.ts rename to extensions/search-rg/src/common/filters.ts diff --git a/extensions/search-rg/src/normalization.ts b/extensions/search-rg/src/common/normalization.ts similarity index 100% rename from extensions/search-rg/src/normalization.ts rename to extensions/search-rg/src/common/normalization.ts diff --git a/extensions/search-rg/src/strings.ts b/extensions/search-rg/src/common/strings.ts similarity index 92% rename from extensions/search-rg/src/strings.ts rename to extensions/search-rg/src/common/strings.ts index a72fd6f4c19..2678aff1e0f 100644 --- a/extensions/search-rg/src/strings.ts +++ b/extensions/search-rg/src/common/strings.ts @@ -1,7 +1,7 @@ /*--------------------------------------------------------------------------------------------- -* Copyright (c) Microsoft Corporation. All rights reserved. -* Licensed under the MIT License. See License.txt in the project root for license information. -*--------------------------------------------------------------------------------------------*/ + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ 'use strict'; diff --git a/extensions/search-rg/src/extension.ts b/extensions/search-rg/src/extension.ts index 0490b59d9d4..079ae5f1215 100644 --- a/extensions/search-rg/src/extension.ts +++ b/extensions/search-rg/src/extension.ts @@ -17,7 +17,10 @@ export function activate(): void { } class RipgrepSearchProvider implements vscode.SearchProvider { + private cachedProvider: CachedSearchProvider; + constructor(private outputChannel: vscode.OutputChannel) { + this.cachedProvider = new CachedSearchProvider(this.outputChannel); } provideTextSearchResults(query: vscode.TextSearchQuery, options: vscode.TextSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { @@ -26,8 +29,7 @@ class RipgrepSearchProvider implements vscode.SearchProvider { } provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.SearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { - const cachedProvider = new CachedSearchProvider(this.outputChannel); const engine = new RipgrepFileSearch(this.outputChannel); - return cachedProvider.provideFileSearchResults(engine, query, options, progress, token); + return this.cachedProvider.provideFileSearchResults(engine, query, options, progress, token); } } \ No newline at end of file diff --git a/extensions/search-rg/src/ripgrepFileSearch.ts b/extensions/search-rg/src/ripgrepFileSearch.ts index 026238d1a93..dd0efaa0ed6 100644 --- a/extensions/search-rg/src/ripgrepFileSearch.ts +++ b/extensions/search-rg/src/ripgrepFileSearch.ts @@ -7,7 +7,7 @@ import * as cp from 'child_process'; import { Readable } from 'stream'; import { NodeStringDecoder, StringDecoder } from 'string_decoder'; import * as vscode from 'vscode'; -import { normalizeNFC, normalizeNFD } from './normalization'; +import { normalizeNFC, normalizeNFD } from './common/normalization'; import { rgPath } from './ripgrep'; import { anchorGlob } from './ripgrepHelpers'; import { rgErrorMsgForDisplay } from './ripgrepTextSearch'; diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index ff7897f0cb4..e299a3abf1c 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -28,7 +28,7 @@ declare module 'vscode' { excludes: string[]; useIgnoreFiles?: boolean; followSymlinks?: boolean; - maxResults: number; + maxResults?: number; } export interface TextSearchOptions extends SearchOptions { diff --git a/src/vs/workbench/api/electron-browser/mainThreadSearch.ts b/src/vs/workbench/api/electron-browser/mainThreadSearch.ts index 6e692f064b7..08e831aa481 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadSearch.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadSearch.ts @@ -42,7 +42,11 @@ export class MainThreadSearch implements MainThreadSearchShape { this._searchProvider.delete(handle); } - $handleFindMatch(handle: number, session, data: UriComponents | IRawFileMatch2[]): void { + $handleFileMatch(handle: number, session, data: UriComponents[]): void { + this._searchProvider.get(handle).handleFindMatch(session, data); + } + + $handleTextMatch(handle: number, session, data: IRawFileMatch2[]): void { this._searchProvider.get(handle).handleFindMatch(session, data); } @@ -134,22 +138,24 @@ class RemoteSearchProvider implements ISearchResultProvider { }); } - handleFindMatch(session: number, dataOrUri: UriComponents | IRawFileMatch2[]): void { + handleFindMatch(session: number, dataOrUri: (UriComponents | IRawFileMatch2)[]): void { if (!this._searches.has(session)) { // ignore... return; } const searchOp = this._searches.get(session); - if (Array.isArray(dataOrUri)) { - dataOrUri.forEach(m => { + dataOrUri.forEach(result => { + if ((result).lineMatches) { searchOp.addMatch({ - resource: URI.revive(m.resource), - lineMatches: m.lineMatches + resource: URI.revive((result).resource), + lineMatches: (result).lineMatches }); - }); - } else { - searchOp.addMatch({ resource: URI.revive(dataOrUri) }); - } + } else { + searchOp.addMatch({ + resource: URI.revive(result) + }); + } + }); } } diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 21850c7ed3a..980fbcefb9a 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -483,7 +483,8 @@ export interface MainThreadFileSystemShape extends IDisposable { export interface MainThreadSearchShape extends IDisposable { $registerSearchProvider(handle: number, scheme: string): void; $unregisterProvider(handle: number): void; - $handleFindMatch(handle: number, session: number, data: UriComponents | IRawFileMatch2[]): void; + $handleFileMatch(handle: number, session: number, data: UriComponents | UriComponents[]): void; + $handleTextMatch(handle: number, session: number, data: IRawFileMatch2[]): void; $handleTelemetry(eventName: string, data: any): void; } diff --git a/src/vs/workbench/api/node/extHostSearch.ts b/src/vs/workbench/api/node/extHostSearch.ts index 9f2baf368a1..57f5e8362ca 100644 --- a/src/vs/workbench/api/node/extHostSearch.ts +++ b/src/vs/workbench/api/node/extHostSearch.ts @@ -18,8 +18,6 @@ import { IFileMatch, IFolderQuery, IPatternInfo, IRawFileMatch2, IRawSearchQuery import * as vscode from 'vscode'; import { ExtHostSearchShape, IMainContext, MainContext, MainThreadSearchShape } from './extHost.protocol'; -type OneOrMore = T | T[]; - export interface ISchemeTransformer { transformOutgoing(scheme: string): string; } @@ -67,13 +65,7 @@ export class ExtHostSearch implements ExtHostSearchShape { null, null, progress => { - if (Array.isArray(progress)) { - progress.forEach(p => { - this._proxy.$handleFindMatch(handle, session, p.resource); - }); - } else { - this._proxy.$handleFindMatch(handle, session, progress.resource); - } + this._proxy.$handleFileMatch(handle, session, progress.map(p => p.resource)); }); } @@ -89,7 +81,7 @@ export class ExtHostSearch implements ExtHostSearchShape { null, null, progress => { - this._proxy.$handleFindMatch(handle, session, progress); + this._proxy.$handleTextMatch(handle, session, progress); }); } } @@ -512,16 +504,12 @@ class FileSearchEngine { private includePattern: glob.ParsedExpression; private maxResults: number; private exists: boolean; - // private maxFilesize: number; private isLimitHit: boolean; private resultCount: number; private isCanceled: boolean; private activeCancellationTokens: Set; - // private filesWalked: number; - // private directoriesWalked: number; - private globalExcludePattern: glob.ParsedExpression; constructor(private config: ISearchQuery, private provider: vscode.SearchProvider, private _pfs: typeof pfs) { @@ -529,14 +517,10 @@ class FileSearchEngine { this.includePattern = config.includePattern && glob.parse(config.includePattern); this.maxResults = config.maxResults || null; this.exists = config.exists; - // this.maxFilesize = config.maxFileSize || null; this.resultCount = 0; this.isLimitHit = false; this.activeCancellationTokens = new Set(); - // this.filesWalked = 0; - // this.directoriesWalked = 0; - if (this.filePattern) { this.normalizedFilePatternLowercase = strings.stripWildcards(this.filePattern).toLowerCase(); } @@ -644,7 +628,7 @@ class FileSearchEngine { .then(() => { this.activeCancellationTokens.add(cancellation); return this.provider.provideFileSearchResults( - { cacheKey: this.config.cacheKey, pattern: this.config.filePattern }, + { cacheKey: this.config.cacheKey, pattern: this.config.filePattern || '' }, options, { report: onProviderResult }, cancellation.token); @@ -736,7 +720,6 @@ class FileSearchEngine { const self = this; const filePattern = this.filePattern; function matchDirectory(entries: IDirectoryEntry[]) { - // self.directoriesWalked++; for (let i = 0, n = entries.length; i < n; i++) { const entry = entries[i]; const { relativePath, basename } = entry; @@ -753,7 +736,6 @@ class FileSearchEngine { if (sub) { matchDirectory(sub); } else { - // self.filesWalked++; if (relativePath === filePattern) { continue; // ignore file if its path matches with the file pattern because that is already matched above } @@ -769,25 +751,9 @@ class FileSearchEngine { matchDirectory(rootEntries); } - public getStats(): any { - return null; - // return { - // fromCache: false, - // traversal: Traversal[this.traversal], - // errors: this.errors, - // fileWalkStartTime: this.fileWalkStartTime, - // fileWalkResultTime: Date.now(), - // directoriesWalked: this.directoriesWalked, - // filesWalked: this.filesWalked, - // resultCount: this.resultCount, - // cmdForkResultTime: this.cmdForkResultTime, - // cmdResultCount: this.cmdResultCount - // }; - } - /** * Return whether the file pattern is an absolute path to a file that exists. - * TODO@roblou should use FS provider? + * TODO@roblou delete to match fileSearch.ts */ private checkFilePatternAbsoluteMatch(): TPromise<{ exists: boolean, size?: number }> { if (!this.filePattern || !path.isAbsolute(this.filePattern)) { @@ -859,16 +825,12 @@ class FileSearchManager { constructor(private _pfs: typeof pfs) { } - public fileSearch(config: ISearchQuery, provider: vscode.SearchProvider): PPromise> { + public fileSearch(config: ISearchQuery, provider: vscode.SearchProvider): PPromise { let searchP: PPromise; - return new PPromise>((c, e, p) => { + return new PPromise((c, e, p) => { const engine = new FileSearchEngine(config, provider, this._pfs); searchP = this.doSearch(engine, provider, FileSearchManager.BATCH_SIZE).then(c, e, progress => { - if (Array.isArray(progress)) { - p(progress.map(m => this.rawMatchToSearchItem(m))); - } else if ((progress).relativePath) { - p(this.rawMatchToSearchItem(progress)); - } + p(progress.map(m => this.rawMatchToSearchItem(m))); }); }, () => { if (searchP) { @@ -883,8 +845,8 @@ class FileSearchManager { }; } - private doSearch(engine: FileSearchEngine, provider: vscode.SearchProvider, batchSize?: number): PPromise> { - return new PPromise>((c, e, p) => { + private doSearch(engine: FileSearchEngine, provider: vscode.SearchProvider, batchSize: number): PPromise { + return new PPromise((c, e, p) => { let batch: IInternalFileMatch[] = []; engine.search().then(result => { if (batch.length) { @@ -892,8 +854,7 @@ class FileSearchManager { } c({ - limitHit: result.isLimitHit, - stats: engine.getStats() // TODO@roblou + limitHit: result.isLimitHit }); }, error => { if (batch.length) { @@ -903,14 +864,10 @@ class FileSearchManager { e(error); }, match => { if (match) { - if (batchSize) { - batch.push(match); - if (batchSize > 0 && batch.length >= batchSize) { - p(batch); - batch = []; - } - } else { - p(match); + batch.push(match); + if (batchSize > 0 && batch.length >= batchSize) { + p(batch); + batch = []; } } }); diff --git a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts index 7cb103589ca..5ace3f232cd 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts @@ -155,7 +155,7 @@ suite('ExtHostSearch', () => { test('no results', async () => { await registerTestSearchProvider({ - provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { return TPromise.wrap(null); } }); @@ -173,7 +173,7 @@ suite('ExtHostSearch', () => { ]; await registerTestSearchProvider({ - provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { reportedResults.forEach(r => progress.report(path.basename(r.fsPath))); return TPromise.wrap(null); } @@ -188,7 +188,7 @@ suite('ExtHostSearch', () => { test('Search canceled', async () => { let cancelRequested = false; await registerTestSearchProvider({ - provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { return new TPromise((resolve, reject) => { token.onCancellationRequested(() => { cancelRequested = true; @@ -213,7 +213,7 @@ suite('ExtHostSearch', () => { ]; await registerTestSearchProvider({ - provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { reportedResults.forEach(r => progress.report(r)); throw new Error('I broke'); } @@ -229,7 +229,7 @@ suite('ExtHostSearch', () => { test('provider returns null', async () => { await registerTestSearchProvider({ - provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { return null; } }); @@ -244,7 +244,7 @@ suite('ExtHostSearch', () => { test('all provider calls get global include/excludes', async () => { await registerTestSearchProvider({ - provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { assert(options.excludes.length === 2 && options.includes.length === 2, 'Missing global include/excludes'); return TPromise.wrap(null); } @@ -273,7 +273,7 @@ suite('ExtHostSearch', () => { test('global/local include/excludes combined', async () => { await registerTestSearchProvider({ - provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { if (options.folder.toString() === rootFolderA.toString()) { assert.deepEqual(options.includes.sort(), ['*.ts', 'foo']); assert.deepEqual(options.excludes.sort(), ['*.js', 'bar']); @@ -315,7 +315,7 @@ suite('ExtHostSearch', () => { test('include/excludes resolved correctly', async () => { await registerTestSearchProvider({ - provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { assert.deepEqual(options.includes.sort(), ['*.jsx', '*.ts']); assert.deepEqual(options.excludes.sort(), []); @@ -358,7 +358,7 @@ suite('ExtHostSearch', () => { ]; await registerTestSearchProvider({ - provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { reportedResults.forEach(r => progress.report(r)); return TPromise.wrap(null); } @@ -389,7 +389,7 @@ suite('ExtHostSearch', () => { test('multiroot sibling exclude clause', async () => { await registerTestSearchProvider({ - provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { let reportedResults; if (options.folder.fsPath === rootFolderA.fsPath) { reportedResults = [ @@ -460,7 +460,7 @@ suite('ExtHostSearch', () => { let wasCanceled = false; await registerTestSearchProvider({ - provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { reportedResults.forEach(r => progress.report(path.basename(r.fsPath))); token.onCancellationRequested(() => wasCanceled = true); @@ -497,7 +497,7 @@ suite('ExtHostSearch', () => { let wasCanceled = false; await registerTestSearchProvider({ - provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { reportedResults.forEach(r => progress.report(path.basename(r.fsPath))); token.onCancellationRequested(() => wasCanceled = true); @@ -533,7 +533,7 @@ suite('ExtHostSearch', () => { let wasCanceled = false; await registerTestSearchProvider({ - provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { reportedResults.forEach(r => progress.report(path.basename(r.fsPath))); token.onCancellationRequested(() => wasCanceled = true); @@ -564,7 +564,7 @@ suite('ExtHostSearch', () => { test('multiroot max results', async () => { let cancels = 0; await registerTestSearchProvider({ - provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { token.onCancellationRequested(() => cancels++); // Provice results async so it has a chance to invoke every provider @@ -610,7 +610,7 @@ suite('ExtHostSearch', () => { ]; await registerTestSearchProvider({ - provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { reportedResults.forEach(r => progress.report(path.basename(r.fsPath))); return TPromise.wrap(null); } @@ -642,7 +642,7 @@ suite('ExtHostSearch', () => { ]; await registerTestSearchProvider({ - provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { reportedResults.forEach(r => progress.report(path.basename(r.fsPath))); return TPromise.wrap(null); } From 0ef1c31b0679a7d48e0a88f933e3d323db248fd5 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 2 Jul 2018 19:23:45 -0700 Subject: [PATCH 088/283] Change SearchProvider to be URI-based, not string-based. #50788 --- .../search-rg/src/cachedSearchProvider.ts | 70 +++----- extensions/search-rg/src/extension.ts | 2 +- extensions/search-rg/src/ripgrepFileSearch.ts | 5 +- extensions/search-rg/src/ripgrepHelpers.ts | 10 +- extensions/search-rg/src/ripgrepTextSearch.ts | 3 +- src/vs/vscode.proposed.d.ts | 4 +- src/vs/workbench/api/node/extHost.protocol.ts | 2 +- src/vs/workbench/api/node/extHostSearch.ts | 77 ++++----- src/vs/workbench/api/node/extHostWorkspace.ts | 2 +- .../api/extHostSearch.test.ts | 151 +++++++++--------- 10 files changed, 154 insertions(+), 172 deletions(-) diff --git a/extensions/search-rg/src/cachedSearchProvider.ts b/extensions/search-rg/src/cachedSearchProvider.ts index 78932b5deb4..a17e6de102f 100644 --- a/extensions/search-rg/src/cachedSearchProvider.ts +++ b/extensions/search-rg/src/cachedSearchProvider.ts @@ -8,14 +8,19 @@ import * as vscode from 'vscode'; import * as arrays from './common/arrays'; import { compareItemsByScore, IItemAccessor, prepareQuery, ScorerCache } from './common/fileSearchScorer'; import * as strings from './common/strings'; +import { joinPath } from './ripgrepHelpers'; interface IProviderArgs { query: vscode.FileSearchQuery; options: vscode.FileSearchOptions; - progress: vscode.Progress; + progress: vscode.Progress; token: vscode.CancellationToken; } +export interface IInternalFileSearchProvider { + provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable; +} + export class CachedSearchProvider { private static readonly BATCH_SIZE = 512; @@ -25,12 +30,12 @@ export class CachedSearchProvider { constructor(private outputChannel: vscode.OutputChannel) { } - provideFileSearchResults(provider: vscode.SearchProvider, query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(provider: IInternalFileSearchProvider, query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { const onResult = (result: IInternalFileMatch) => { - progress.report(result.relativePath); + progress.report(joinPath(options.folder, result.relativePath)); }; - const providerArgs = { + const providerArgs: IProviderArgs = { query, options, progress, token }; @@ -52,23 +57,14 @@ export class CachedSearchProvider { }); } - private doSortedSearch(args: IProviderArgs, provider: vscode.SearchProvider): Promise { - let searchPromise: Promise; + private doSortedSearch(args: IProviderArgs, provider: IInternalFileSearchProvider): Promise { let allResultsPromise = new Promise((c, e) => { - let results: IInternalFileMatch[] = []; + const results: IInternalFileMatch[] = []; + const onResult = (progress: IInternalFileMatch[]) => results.push(...progress); - const onResult = (progress: OneOrMore) => { - if (Array.isArray(progress)) { - results.push(...progress); - } else { - results.push(progress); - } - }; - - searchPromise = this.doSearch(args, provider, onResult, CachedSearchProvider.BATCH_SIZE) - .then(() => { - c(results); - }, e); + // set maxResult = null + this.doSearch(args, provider, onResult, CachedSearchProvider.BATCH_SIZE) + .then(() => c(results), e); }); let cache: Cache; @@ -80,12 +76,9 @@ export class CachedSearchProvider { }); } - return new Promise((c, e) => { - allResultsPromise.then(results => { - const scorerCache: ScorerCache = cache ? cache.scorerCache : Object.create(null); - return this.sortResults(args, results, scorerCache) - .then(c); - }, e); + return allResultsPromise.then(results => { + const scorerCache: ScorerCache = cache ? cache.scorerCache : Object.create(null); + return this.sortResults(args, results, scorerCache); }); } @@ -119,7 +112,7 @@ export class CachedSearchProvider { const preparedQuery = prepareQuery(args.query.pattern); const compare = (matchA: IInternalFileMatch, matchB: IInternalFileMatch) => compareItemsByScore(matchA, matchB, preparedQuery, true, FileMatchItemAccessor, scorerCache); - return arrays.topAsync(results, compare, args.options.maxResults || 10000, 10000); + return arrays.topAsync(results, compare, args.options.maxResults || 0, 10000); } private getResultsFromCache(cache: Cache, searchValue: string, onResult: (results: IInternalFileMatch) => void): Promise<[IInternalFileMatch[], CacheStats]> { @@ -177,7 +170,7 @@ export class CachedSearchProvider { }); } - private doSearch(args: IProviderArgs, provider: vscode.SearchProvider, onResult: (result: OneOrMore) => void, batchSize?: number): Promise { + private doSearch(args: IProviderArgs, provider: IInternalFileSearchProvider, onResult: (result: IInternalFileMatch[]) => void, batchSize: number): Promise { return new Promise((c, e) => { let batch: IInternalFileMatch[] = []; const onProviderResult = (match: string) => { @@ -187,19 +180,15 @@ export class CachedSearchProvider { basename: path.basename(match) }; - if (batchSize) { - batch.push(internalMatch); - if (batchSize > 0 && batch.length >= batchSize) { - onResult(batch); - batch = []; - } - } else { - onResult(internalMatch); + batch.push(internalMatch); + if (batchSize > 0 && batch.length >= batchSize) { + onResult(batch); + batch = []; } } }; - provider.provideFileSearchResults(args.query, args.options, { report: onProviderResult }, args.token).then(() => { + provider.provideFileSearchResults(args.options, { report: onProviderResult }, args.token).then(() => { if (batch.length) { onResult(batch); } @@ -221,13 +210,6 @@ export class CachedSearchProvider { } } -function joinPath(resource: vscode.Uri, pathFragment: string): vscode.Uri { - const joinedPath = path.join(resource.path || '/', pathFragment); - return resource.with({ - path: joinedPath - }); -} - interface IInternalFileMatch { relativePath?: string; // Not present for extraFiles or absolute path matches basename: string; @@ -246,8 +228,6 @@ interface CacheEntry { onResult?: Event; } -type OneOrMore = T | T[]; - class Cache { public resultsToSearchCache: { [searchValue: string]: CacheEntry } = Object.create(null); public scorerCache: ScorerCache = Object.create(null); diff --git a/extensions/search-rg/src/extension.ts b/extensions/search-rg/src/extension.ts index 079ae5f1215..042d2269a80 100644 --- a/extensions/search-rg/src/extension.ts +++ b/extensions/search-rg/src/extension.ts @@ -28,7 +28,7 @@ class RipgrepSearchProvider implements vscode.SearchProvider { return engine.provideTextSearchResults(query, options, progress, token); } - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.SearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.SearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { const engine = new RipgrepFileSearch(this.outputChannel); return this.cachedProvider.provideFileSearchResults(engine, query, options, progress, token); } diff --git a/extensions/search-rg/src/ripgrepFileSearch.ts b/extensions/search-rg/src/ripgrepFileSearch.ts index dd0efaa0ed6..f6765d970bf 100644 --- a/extensions/search-rg/src/ripgrepFileSearch.ts +++ b/extensions/search-rg/src/ripgrepFileSearch.ts @@ -11,13 +11,14 @@ import { normalizeNFC, normalizeNFD } from './common/normalization'; import { rgPath } from './ripgrep'; import { anchorGlob } from './ripgrepHelpers'; import { rgErrorMsgForDisplay } from './ripgrepTextSearch'; +import { IInternalFileSearchProvider } from './cachedSearchProvider'; const isMac = process.platform === 'darwin'; // If vscode-ripgrep is in an .asar file, then the binary is unpacked. const rgDiskPath = rgPath.replace(/\bnode_modules\.asar\b/, 'node_modules.asar.unpacked'); -export class RipgrepFileSearch { +export class RipgrepFileSearch implements IInternalFileSearchProvider { private rgProc: cp.ChildProcess; private killRgProcFn: (code?: number) => void; @@ -30,7 +31,7 @@ export class RipgrepFileSearch { process.removeListener('exit', this.killRgProcFn); } - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { this.outputChannel.appendLine(`provideFileSearchResults ${JSON.stringify({ ...options, ...{ diff --git a/extensions/search-rg/src/ripgrepHelpers.ts b/extensions/search-rg/src/ripgrepHelpers.ts index 5487be750a0..fd083fe075f 100644 --- a/extensions/search-rg/src/ripgrepHelpers.ts +++ b/extensions/search-rg/src/ripgrepHelpers.ts @@ -5,9 +5,8 @@ 'use strict'; -import * as vscode from 'vscode'; - import * as path from 'path'; +import * as vscode from 'vscode'; export function fixDriveC(_path: string): string { const root = path.parse(_path).root; @@ -19,3 +18,10 @@ export function fixDriveC(_path: string): string { export function anchorGlob(glob: string): string { return glob.startsWith('**') || glob.startsWith('/') ? glob : `/${glob}`; } + +export function joinPath(resource: vscode.Uri, pathFragment: string): vscode.Uri { + const joinedPath = path.join(resource.path || '/', pathFragment); + return resource.with({ + path: joinedPath + }); +} diff --git a/extensions/search-rg/src/ripgrepTextSearch.ts b/extensions/search-rg/src/ripgrepTextSearch.ts index b03ee60c754..87aed94c6e3 100644 --- a/extensions/search-rg/src/ripgrepTextSearch.ts +++ b/extensions/search-rg/src/ripgrepTextSearch.ts @@ -7,6 +7,7 @@ import * as cp from 'child_process'; import { EventEmitter } from 'events'; +import * as path from 'path'; import { NodeStringDecoder, StringDecoder } from 'string_decoder'; import * as vscode from 'vscode'; import { rgPath } from './ripgrep'; @@ -291,7 +292,7 @@ export class RipgrepParser extends EventEmitter { lineMatches .map(range => { return { - path: this.currentFile, + uri: vscode.Uri.file(path.join(this.rootFolder, this.currentFile)), range, preview: { text: preview, diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index e299a3abf1c..3cc94a1cefe 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -45,7 +45,7 @@ declare module 'vscode' { export interface FileSearchOptions extends SearchOptions { } export interface TextSearchResult { - path: string; + uri: Uri; range: Range; // For now, preview must be a single line of text @@ -53,7 +53,7 @@ declare module 'vscode' { } export interface SearchProvider { - provideFileSearchResults?(query: FileSearchQuery, options: FileSearchOptions, progress: Progress, token: CancellationToken): Thenable; + provideFileSearchResults?(query: FileSearchQuery, options: FileSearchOptions, progress: Progress, token: CancellationToken): Thenable; provideTextSearchResults?(query: TextSearchQuery, options: TextSearchOptions, progress: Progress, token: CancellationToken): Thenable; } diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 980fbcefb9a..923fb9b16df 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -483,7 +483,7 @@ export interface MainThreadFileSystemShape extends IDisposable { export interface MainThreadSearchShape extends IDisposable { $registerSearchProvider(handle: number, scheme: string): void; $unregisterProvider(handle: number): void; - $handleFileMatch(handle: number, session: number, data: UriComponents | UriComponents[]): void; + $handleFileMatch(handle: number, session: number, data: UriComponents[]): void; $handleTextMatch(handle: number, session: number, data: IRawFileMatch2[]): void; $handleTelemetry(eventName: string, data: any): void; } diff --git a/src/vs/workbench/api/node/extHostSearch.ts b/src/vs/workbench/api/node/extHostSearch.ts index 57f5e8362ca..aeba2749c5f 100644 --- a/src/vs/workbench/api/node/extHostSearch.ts +++ b/src/vs/workbench/api/node/extHostSearch.ts @@ -8,13 +8,13 @@ import * as path from 'path'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import * as glob from 'vs/base/common/glob'; -import { joinPath } from 'vs/base/common/resources'; +import * as resources from 'vs/base/common/resources'; import * as strings from 'vs/base/common/strings'; import URI, { UriComponents } from 'vs/base/common/uri'; import { PPromise, TPromise } from 'vs/base/common/winjs.base'; import * as extfs from 'vs/base/node/extfs'; import * as pfs from 'vs/base/node/pfs'; -import { IFileMatch, IFolderQuery, IPatternInfo, IRawFileMatch2, IRawSearchQuery, ISearchCompleteStats, ISearchQuery } from 'vs/platform/search/common/search'; +import { IFileMatch, IFolderQuery, IPatternInfo, IRawSearchQuery, ISearchCompleteStats, ISearchQuery } from 'vs/platform/search/common/search'; import * as vscode from 'vscode'; import { ExtHostSearchShape, IMainContext, MainContext, MainThreadSearchShape } from './extHost.protocol'; @@ -120,29 +120,28 @@ function reviveFolderQuery(rawFolderQuery: IFolderQuery): IFolder } class TextSearchResultsCollector { - private _batchedCollector: BatchedCollector; + private _batchedCollector: BatchedCollector; private _currentFolderIdx: number; - private _currentRelativePath: string; - private _currentFileMatch: IRawFileMatch2; + private _currentUri: URI; + private _currentFileMatch: IFileMatch; - constructor(private folderQueries: IFolderQuery[], private _onResult: (result: IRawFileMatch2[]) => void) { - this._batchedCollector = new BatchedCollector(512, items => this.sendItems(items)); + constructor(private _onResult: (result: IFileMatch[]) => void) { + this._batchedCollector = new BatchedCollector(512, items => this.sendItems(items)); } add(data: vscode.TextSearchResult, folderIdx: number): void { // Collects TextSearchResults into IInternalFileMatches and collates using BatchedCollector. // This is efficient for ripgrep which sends results back one file at a time. It wouldn't be efficient for other search // providers that send results in random order. We could do this step afterwards instead. - if (this._currentFileMatch && (this._currentFolderIdx !== folderIdx || this._currentRelativePath !== data.path)) { + if (this._currentFileMatch && (this._currentFolderIdx !== folderIdx || resources.isEqual(this._currentUri, data.uri))) { this.pushToCollector(); this._currentFileMatch = null; } if (!this._currentFileMatch) { - const resource = joinPath(this.folderQueries[folderIdx].folder, data.path); this._currentFileMatch = { - resource, + resource: data.uri, lineMatches: [] }; } @@ -168,8 +167,8 @@ class TextSearchResultsCollector { this._batchedCollector.flush(); } - private sendItems(items: IRawFileMatch2 | IRawFileMatch2[]): void { - this._onResult(Array.isArray(items) ? items : [items]); + private sendItems(items: IFileMatch[]): void { + this._onResult(items); } } @@ -190,7 +189,7 @@ class BatchedCollector { private batchSize = 0; private timeoutHandle: number; - constructor(private maxBatchSize: number, private cb: (items: T | T[]) => void) { + constructor(private maxBatchSize: number, private cb: (items: T[]) => void) { } addItem(item: T, size: number): void { @@ -198,11 +197,7 @@ class BatchedCollector { return; } - if (this.maxBatchSize > 0) { - this.addItemToBatch(item, size); - } else { - this.cb(item); - } + this.addItemToBatch(item, size); } addItems(items: T[], size: number): void { @@ -378,11 +373,11 @@ class TextSearchEngine { this.activeCancellationTokens = new Set(); } - public search(): PPromise<{ limitHit: boolean }, IRawFileMatch2[]> { + public search(): PPromise<{ limitHit: boolean }, IFileMatch[]> { const folderQueries = this.config.folderQueries; - return new PPromise<{ limitHit: boolean }, IRawFileMatch2[]>((resolve, reject, _onResult) => { - this.collector = new TextSearchResultsCollector(this.config.folderQueries, _onResult); + return new PPromise<{ limitHit: boolean }, IFileMatch[]>((resolve, reject, _onResult) => { + this.collector = new TextSearchResultsCollector(_onResult); const onResult = (match: vscode.TextSearchResult, folderIdx: number) => { if (this.isCanceled) { @@ -425,11 +420,12 @@ class TextSearchEngine { const progress = { report: (result: vscode.TextSearchResult) => { const siblingFn = folderQuery.folder.scheme === 'file' && (() => { - return this.readdir(path.dirname(path.join(folderQuery.folder.fsPath, result.path))); + return this.readdir(path.dirname(result.uri.fsPath)); }); + const relativePath = path.relative(folderQuery.folder.fsPath, result.uri.fsPath); testingPs.push( - queryTester.includedInQuery(result.path, path.basename(result.path), siblingFn) + queryTester.includedInQuery(relativePath, path.basename(relativePath), siblingFn) .then(included => { if (included) { onResult(result); @@ -598,23 +594,20 @@ class FileSearchEngine { let cancellation = new CancellationTokenSource(); return new PPromise((resolve, reject, onResult) => { const options = this.getSearchOptionsForFolder(fq); - let filePatternSeen = false; const tree = this.initDirectoryTree(); const queryTester = new QueryGlobTester(this.config, fq); const noSiblingsClauses = !queryTester.hasSiblingExcludeClauses(); - const onProviderResult = (relativePath: string) => { + const onProviderResult = (result: URI) => { if (this.isCanceled) { return; } - if (noSiblingsClauses) { - if (relativePath === this.filePattern) { - filePatternSeen = true; - } + const relativePath = path.relative(fq.folder.fsPath, result.fsPath); - const basename = path.basename(relativePath); + if (noSiblingsClauses) { + const basename = path.basename(result.fsPath); this.matchFile(onResult, { base: fq.folder, relativePath, basename }); return; @@ -640,18 +633,16 @@ class FileSearchEngine { } if (noSiblingsClauses && this.isLimitHit) { - if (!filePatternSeen) { - // If the limit was hit, check whether filePattern is an exact relative match because it must be included - return this.checkFilePatternRelativeMatch(fq.folder).then(({ exists, size }) => { - if (exists) { - onResult({ - base: fq.folder, - relativePath: this.filePattern, - basename: path.basename(this.filePattern), - }); - } - }); - } + // If the limit was hit, check whether filePattern is an exact relative match because it must be included + return this.checkFilePatternRelativeMatch(fq.folder).then(({ exists, size }) => { + if (exists) { + onResult({ + base: fq.folder, + relativePath: this.filePattern, + basename: path.basename(this.filePattern), + }); + } + }); } this.matchDirectoryTree(tree, queryTester, onResult); @@ -841,7 +832,7 @@ class FileSearchManager { private rawMatchToSearchItem(match: IInternalFileMatch): IFileMatch { return { - resource: joinPath(match.base, match.relativePath) + resource: resources.joinPath(match.base, match.relativePath) }; } diff --git a/src/vs/workbench/api/node/extHostWorkspace.ts b/src/vs/workbench/api/node/extHostWorkspace.ts index 89dcbf56605..4ab4525526d 100644 --- a/src/vs/workbench/api/node/extHostWorkspace.ts +++ b/src/vs/workbench/api/node/extHostWorkspace.ts @@ -399,7 +399,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { lineMatch.offsetAndLengths.forEach(offsetAndLength => { const range = new Range(lineMatch.lineNumber, offsetAndLength[0], lineMatch.lineNumber, offsetAndLength[0] + offsetAndLength[1]); callback({ - path: URI.revive(p.resource).fsPath, + uri: URI.revive(p.resource), preview: { text: lineMatch.preview, match: range }, range }); diff --git a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts index 5ace3f232cd..3c5cceb4ce7 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts @@ -5,7 +5,6 @@ 'use strict'; import * as assert from 'assert'; -import * as path from 'path'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { dispose } from 'vs/base/common/lifecycle'; import { joinPath } from 'vs/base/common/resources'; @@ -36,12 +35,12 @@ class MockMainThreadSearch implements MainThreadSearchShape { $unregisterProvider(handle: number): void { } - $handleFindMatch(handle: number, session: number, data: UriComponents | IRawFileMatch2[]): void { - if (Array.isArray(data)) { - this.results.push(...data); - } else { - this.results.push(data); - } + $handleFileMatch(handle: number, session: number, data: UriComponents[]): void { + this.results.push(...data); + } + + $handleTextMatch(handle: number, session: number, data: IRawFileMatch2[]): void { + this.results.push(...data); } $handleTelemetry(eventName: string, data: any): void { @@ -155,7 +154,7 @@ suite('ExtHostSearch', () => { test('no results', async () => { await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { return TPromise.wrap(null); } }); @@ -173,8 +172,8 @@ suite('ExtHostSearch', () => { ]; await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { - reportedResults.forEach(r => progress.report(path.basename(r.fsPath))); + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + reportedResults.forEach(r => progress.report(r)); return TPromise.wrap(null); } }); @@ -188,11 +187,11 @@ suite('ExtHostSearch', () => { test('Search canceled', async () => { let cancelRequested = false; await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { return new TPromise((resolve, reject) => { token.onCancellationRequested(() => { cancelRequested = true; - progress.report('file1.ts'); + progress.report(joinPath(options.folder, 'file1.ts')); resolve(null); // or reject or nothing? }); @@ -213,8 +212,11 @@ suite('ExtHostSearch', () => { ]; await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { - reportedResults.forEach(r => progress.report(r)); + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + reportedResults + .map(relativePath => joinPath(options.folder, relativePath)) + .forEach(r => progress.report(r)); + throw new Error('I broke'); } }); @@ -229,7 +231,7 @@ suite('ExtHostSearch', () => { test('provider returns null', async () => { await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { return null; } }); @@ -244,7 +246,7 @@ suite('ExtHostSearch', () => { test('all provider calls get global include/excludes', async () => { await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { assert(options.excludes.length === 2 && options.includes.length === 2, 'Missing global include/excludes'); return TPromise.wrap(null); } @@ -273,7 +275,7 @@ suite('ExtHostSearch', () => { test('global/local include/excludes combined', async () => { await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { if (options.folder.toString() === rootFolderA.toString()) { assert.deepEqual(options.includes.sort(), ['*.ts', 'foo']); assert.deepEqual(options.excludes.sort(), ['*.js', 'bar']); @@ -315,7 +317,7 @@ suite('ExtHostSearch', () => { test('include/excludes resolved correctly', async () => { await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { assert.deepEqual(options.includes.sort(), ['*.jsx', '*.ts']); assert.deepEqual(options.excludes.sort(), []); @@ -358,8 +360,10 @@ suite('ExtHostSearch', () => { ]; await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { - reportedResults.forEach(r => progress.report(r)); + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + reportedResults + .map(relativePath => joinPath(options.folder, relativePath)) + .forEach(r => progress.report(r)); return TPromise.wrap(null); } }); @@ -389,20 +393,20 @@ suite('ExtHostSearch', () => { test('multiroot sibling exclude clause', async () => { await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { - let reportedResults; + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + let reportedResults: URI[]; if (options.folder.fsPath === rootFolderA.fsPath) { reportedResults = [ 'folder/fileA.scss', 'folder/fileA.css', 'folder/file2.css' - ]; + ].map(relativePath => joinPath(rootFolderA, relativePath)); } else { reportedResults = [ 'fileB.ts', 'fileB.js', 'file3.js' - ]; + ].map(relativePath => joinPath(rootFolderB, relativePath)); } reportedResults.forEach(r => progress.report(r)); @@ -460,8 +464,9 @@ suite('ExtHostSearch', () => { let wasCanceled = false; await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { - reportedResults.forEach(r => progress.report(path.basename(r.fsPath))); + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + reportedResults + .forEach(r => progress.report(r)); token.onCancellationRequested(() => wasCanceled = true); return TPromise.wrap(null); @@ -497,8 +502,8 @@ suite('ExtHostSearch', () => { let wasCanceled = false; await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { - reportedResults.forEach(r => progress.report(path.basename(r.fsPath))); + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + reportedResults.forEach(r => progress.report(r)); token.onCancellationRequested(() => wasCanceled = true); return TPromise.wrap(null); @@ -533,8 +538,8 @@ suite('ExtHostSearch', () => { let wasCanceled = false; await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { - reportedResults.forEach(r => progress.report(path.basename(r.fsPath))); + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + reportedResults.forEach(r => progress.report(r)); token.onCancellationRequested(() => wasCanceled = true); return TPromise.wrap(null); @@ -564,7 +569,7 @@ suite('ExtHostSearch', () => { test('multiroot max results', async () => { let cancels = 0; await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { token.onCancellationRequested(() => cancels++); // Provice results async so it has a chance to invoke every provider @@ -574,9 +579,8 @@ suite('ExtHostSearch', () => { 'file1.ts', 'file2.ts', 'file3.ts', - ].forEach(f => { - progress.report(f); - }); + ].map(relativePath => joinPath(options.folder, relativePath)) + .forEach(r => progress.report(r)); }); } }); @@ -610,8 +614,8 @@ suite('ExtHostSearch', () => { ]; await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { - reportedResults.forEach(r => progress.report(path.basename(r.fsPath))); + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + reportedResults.forEach(r => progress.report(r)); return TPromise.wrap(null); } }); @@ -642,8 +646,8 @@ suite('ExtHostSearch', () => { ]; await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { - reportedResults.forEach(r => progress.report(path.basename(r.fsPath))); + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + reportedResults.forEach(r => progress.report(r)); return TPromise.wrap(null); } }, fancyScheme); @@ -672,7 +676,7 @@ suite('ExtHostSearch', () => { // ]; // await registerTestSearchProvider({ - // provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + // provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { // reportedResults.forEach(r => progress.report(r)); // return TPromise.wrap(null); // } @@ -694,11 +698,11 @@ suite('ExtHostSearch', () => { }; } - function makeTextResult(relativePath: string): vscode.TextSearchResult { + function makeTextResult(baseFolder: URI, relativePath: string): vscode.TextSearchResult { return { preview: makePreview('foo'), range: new Range(0, 0, 0, 3), - path: relativePath + uri: joinPath(baseFolder, relativePath) }; } @@ -718,17 +722,16 @@ suite('ExtHostSearch', () => { }; } - function assertResults(actual: IFileMatch[], expected: vscode.TextSearchResult[], folder = rootFolderA) { + function assertResults(actual: IFileMatch[], expected: vscode.TextSearchResult[]) { const actualTextSearchResults: vscode.TextSearchResult[] = []; for (let fileMatch of actual) { // Make relative - const relativePath = fileMatch.resource.toString().substr(folder.toString().length + 1); for (let lineMatch of fileMatch.lineMatches) { for (let [offset, length] of lineMatch.offsetAndLengths) { actualTextSearchResults.push({ preview: { text: lineMatch.preview, match: null }, range: new Range(lineMatch.lineNumber, offset, lineMatch.lineNumber, length + offset), - path: relativePath + uri: fileMatch.resource }); } } @@ -741,7 +744,7 @@ suite('ExtHostSearch', () => { .map(r => ({ ...r, ...{ - uri: r.path.toString(), + uri: r.uri.toString(), range: rangeToString(r.range), preview: { text: r.preview.text, @@ -769,8 +772,8 @@ suite('ExtHostSearch', () => { test('basic results', async () => { const providedResults: vscode.TextSearchResult[] = [ - makeTextResult('file1.ts'), - makeTextResult('file2.ts') + makeTextResult(rootFolderA, 'file1.ts'), + makeTextResult(rootFolderA, 'file2.ts') ]; await registerTestSearchProvider({ @@ -920,8 +923,8 @@ suite('ExtHostSearch', () => { }; const providedResults: vscode.TextSearchResult[] = [ - makeTextResult('file1.js'), - makeTextResult('file1.ts') + makeTextResult(rootFolderA, 'file1.js'), + makeTextResult(rootFolderA, 'file1.ts') ]; await registerTestSearchProvider({ @@ -973,15 +976,15 @@ suite('ExtHostSearch', () => { let reportedResults; if (options.folder.fsPath === rootFolderA.fsPath) { reportedResults = [ - makeTextResult('folder/fileA.scss'), - makeTextResult('folder/fileA.css'), - makeTextResult('folder/file2.css') + makeTextResult(rootFolderA, 'folder/fileA.scss'), + makeTextResult(rootFolderA, 'folder/fileA.css'), + makeTextResult(rootFolderA, 'folder/file2.css') ]; } else { reportedResults = [ - makeTextResult('fileB.ts'), - makeTextResult('fileB.js'), - makeTextResult('file3.js') + makeTextResult(rootFolderB, 'fileB.ts'), + makeTextResult(rootFolderB, 'fileB.js'), + makeTextResult(rootFolderB, 'file3.js') ]; } @@ -1019,17 +1022,17 @@ suite('ExtHostSearch', () => { const { results } = await runTextSearch(getPattern('foo'), query); assertResults(results, [ - makeTextResult('folder/fileA.scss'), - makeTextResult('folder/file2.css'), - makeTextResult('fileB.ts'), - makeTextResult('fileB.js'), - makeTextResult('file3.js')]); + makeTextResult(rootFolderA, 'folder/fileA.scss'), + makeTextResult(rootFolderA, 'folder/file2.css'), + makeTextResult(rootFolderB, 'fileB.ts'), + makeTextResult(rootFolderB, 'fileB.js'), + makeTextResult(rootFolderB, 'file3.js')]); }); test('include pattern applied', async () => { const providedResults: vscode.TextSearchResult[] = [ - makeTextResult('file1.js'), - makeTextResult('file1.ts') + makeTextResult(rootFolderA, 'file1.js'), + makeTextResult(rootFolderA, 'file1.ts') ]; await registerTestSearchProvider({ @@ -1057,8 +1060,8 @@ suite('ExtHostSearch', () => { test('max results = 1', async () => { const providedResults: vscode.TextSearchResult[] = [ - makeTextResult('file1.ts'), - makeTextResult('file2.ts') + makeTextResult(rootFolderA, 'file1.ts'), + makeTextResult(rootFolderA, 'file2.ts') ]; let wasCanceled = false; @@ -1088,9 +1091,9 @@ suite('ExtHostSearch', () => { test('max results = 2', async () => { const providedResults: vscode.TextSearchResult[] = [ - makeTextResult('file1.ts'), - makeTextResult('file2.ts'), - makeTextResult('file3.ts') + makeTextResult(rootFolderA, 'file1.ts'), + makeTextResult(rootFolderA, 'file2.ts'), + makeTextResult(rootFolderA, 'file3.ts') ]; let wasCanceled = false; @@ -1120,8 +1123,8 @@ suite('ExtHostSearch', () => { test('provider returns maxResults exactly', async () => { const providedResults: vscode.TextSearchResult[] = [ - makeTextResult('file1.ts'), - makeTextResult('file2.ts') + makeTextResult(rootFolderA, 'file1.ts'), + makeTextResult(rootFolderA, 'file2.ts') ]; let wasCanceled = false; @@ -1160,7 +1163,7 @@ suite('ExtHostSearch', () => { 'file1.ts', 'file2.ts', 'file3.ts', - ].forEach(f => progress.report(makeTextResult(f))); + ].forEach(f => progress.report(makeTextResult(options.folder, f))); }); } }); @@ -1183,9 +1186,9 @@ suite('ExtHostSearch', () => { test('works with non-file schemes', async () => { const providedResults: vscode.TextSearchResult[] = [ - makeTextResult('file1.ts'), - makeTextResult('file2.ts'), - makeTextResult('file3.ts') + makeTextResult(fancySchemeFolderA, 'file1.ts'), + makeTextResult(fancySchemeFolderA, 'file2.ts'), + makeTextResult(fancySchemeFolderA, 'file3.ts') ]; await registerTestSearchProvider({ @@ -1204,7 +1207,7 @@ suite('ExtHostSearch', () => { }; const { results } = await runTextSearch(getPattern('foo'), query); - assertResults(results, providedResults, fancySchemeFolderA); + assertResults(results, providedResults); }); }); }); From 5a35534152b66c9798d1de1ed04b66f12273f484 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 2 Jul 2018 21:56:04 -0700 Subject: [PATCH 089/283] Remove unused import --- src/vs/workbench/parts/webview/electron-browser/webviewEditor.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/parts/webview/electron-browser/webviewEditor.ts b/src/vs/workbench/parts/webview/electron-browser/webviewEditor.ts index b69210804bc..82252859c77 100644 --- a/src/vs/workbench/parts/webview/electron-browser/webviewEditor.ts +++ b/src/vs/workbench/parts/webview/electron-browser/webviewEditor.ts @@ -8,7 +8,6 @@ import { domEvent } from 'vs/base/browser/event'; import { Emitter, Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; import URI from 'vs/base/common/uri'; -import { TPromise } from 'vs/base/common/winjs.base'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; From 7ea9f254a163d4d7093c4dc3e964690e2ad3959e Mon Sep 17 00:00:00 2001 From: Erich Gamma Date: Tue, 3 Jul 2018 08:53:21 +0200 Subject: [PATCH 090/283] update distroy commit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8a2febaae1d..54df1ab121c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.26.0", - "distro": "a1c7a95e4b1313b9d6a98aa45b7c406731404e31", + "distro": "7ee5cda9f4f44b2ba1f1ab290e48f86416914794", "author": { "name": "Microsoft Corporation" }, From 727c6f3537d732269db30cbb9efd496073351586 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 3 Jul 2018 08:47:25 +0200 Subject: [PATCH 091/283] debt - combine native promise and cancellation token source in 'CancelablePromise' type --- src/vs/base/common/async.ts | 46 ++++++++++++++++++- src/vs/base/test/common/async.test.ts | 39 ++++++++++++++++ src/vs/editor/contrib/links/getLinks.ts | 9 ++-- src/vs/editor/contrib/links/links.ts | 19 +++++--- .../api/extHostLanguageFeatures.test.ts | 5 +- 5 files changed, 103 insertions(+), 15 deletions(-) diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 88d57b4307e..b1f1fbb14eb 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -32,6 +32,40 @@ export function toWinJsPromise(arg: Thenable | TPromise): TPromise { return new TPromise((resolve, reject) => arg.then(resolve, reject)); } +export interface CancelablePromise extends Promise { + cancel(): void; +} + +export function createCancelablePromise(callback: (token: CancellationToken) => Thenable): CancelablePromise { + const source = new CancellationTokenSource(); + + const thenable = callback(source.token); + const promise = new Promise((resolve, reject) => { + source.token.onCancellationRequested(() => { + reject(errors.canceled()); + }); + Promise.resolve(thenable).then(value => { + source.dispose(); + resolve(value); + }, err => { + source.dispose(); + reject(err); + }); + }); + + return new class implements CancelablePromise { + cancel() { + source.cancel(); + } + then(resolve, reject) { + return promise.then(resolve, reject); + } + catch(reject) { + return this.then(undefined, reject); + } + }; +} + export function asWinJsPromise(callback: (token: CancellationToken) => T | TPromise | Thenable): TPromise { let source = new CancellationTokenSource(); return new TPromise((resolve, reject, progress) => { @@ -364,8 +398,16 @@ export class ShallowCancelThenPromise extends TPromise { /** * Replacement for `WinJS.TPromise.timeout`. */ -export function timeout(n: number): Thenable { - return new TPromise(resolve => setTimeout(resolve, n)); +export function timeout(n: number): CancelablePromise { + return createCancelablePromise(token => { + return new Promise((resolve, reject) => { + const handle = setTimeout(resolve, n); + token.onCancellationRequested(_ => { + clearTimeout(handle); + reject(errors.canceled()); + }); + }); + }); } function isWinJSPromise(candidate: any): candidate is TPromise { diff --git a/src/vs/base/test/common/async.test.ts b/src/vs/base/test/common/async.test.ts index d1ef408cb3f..a8914ebefdc 100644 --- a/src/vs/base/test/common/async.test.ts +++ b/src/vs/base/test/common/async.test.ts @@ -8,9 +8,48 @@ import * as assert from 'assert'; import { TPromise } from 'vs/base/common/winjs.base'; import * as Async from 'vs/base/common/async'; import URI from 'vs/base/common/uri'; +import { isPromiseCanceledError } from 'vs/base/common/errors'; suite('Async', () => { + test('cancelablePromise - set token, don\'t wait for inner promise', function () { + let canceled = 0; + let promise = Async.createCancelablePromise(token => { + token.onCancellationRequested(_ => { canceled += 1; }); + return new Promise(resolve => { /*never*/ }); + }); + let result = promise.then(_ => assert.ok(false), err => { + assert.equal(canceled, 1); + assert.ok(isPromiseCanceledError(err)); + }); + promise.cancel(); + promise.cancel(); // cancel only once + return result; + }); + + test('cancelablePromise - cancel despite inner promise being resolved', function () { + let canceled = 0; + let promise = Async.createCancelablePromise(token => { + token.onCancellationRequested(_ => { canceled += 1; }); + return Promise.resolve(1234); + }); + let result = promise.then(_ => assert.ok(false), err => { + assert.equal(canceled, 1); + assert.ok(isPromiseCanceledError(err)); + }); + promise.cancel(); + return result; + }); + + test('cancelablePromise - get inner result', async function () { + let promise = Async.createCancelablePromise(token => { + return Async.timeout(12).then(_ => 1234); + }); + + let result = await promise; + assert.equal(result, 1234); + }); + test('asDisposablePromise', async function () { let value = await Async.asDisposablePromise(TPromise.as(1)).promise; assert.equal(value, 1); diff --git a/src/vs/editor/contrib/links/getLinks.ts b/src/vs/editor/contrib/links/getLinks.ts index bbeda1aedd4..fb5f845e883 100644 --- a/src/vs/editor/contrib/links/getLinks.ts +++ b/src/vs/editor/contrib/links/getLinks.ts @@ -14,6 +14,7 @@ import { ILink, LinkProvider, LinkProviderRegistry } from 'vs/editor/common/mode import { asWinJsPromise } from 'vs/base/common/async'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IModelService } from 'vs/editor/common/services/modelService'; +import { CancellationToken } from 'vs/base/common/cancellation'; export class Link implements ILink { @@ -65,13 +66,13 @@ export class Link implements ILink { } } -export function getLinks(model: ITextModel): TPromise { +export function getLinks(model: ITextModel, token: CancellationToken): Promise { let links: Link[] = []; // ask all providers for links in parallel const promises = LinkProviderRegistry.ordered(model).reverse().map(provider => { - return asWinJsPromise(token => provider.provideLinks(model, token)).then(result => { + return Promise.resolve(provider.provideLinks(model, token)).then(result => { if (Array.isArray(result)) { const newLinks = result.map(link => new Link(link, provider)); links = union(links, newLinks); @@ -79,7 +80,7 @@ export function getLinks(model: ITextModel): TPromise { }, onUnexpectedExternalError); }); - return TPromise.join(promises).then(() => { + return Promise.all(promises).then(() => { return links; }); } @@ -137,5 +138,5 @@ CommandsRegistry.registerCommand('_executeLinkProvider', (accessor, ...args) => return undefined; } - return getLinks(model); + return getLinks(model, CancellationToken.None); }); diff --git a/src/vs/editor/contrib/links/links.ts b/src/vs/editor/contrib/links/links.ts index cea850c2a20..87fe09b073e 100644 --- a/src/vs/editor/contrib/links/links.ts +++ b/src/vs/editor/contrib/links/links.ts @@ -9,7 +9,6 @@ import 'vs/css!./links'; import * as nls from 'vs/nls'; import { onUnexpectedError } from 'vs/base/common/errors'; import * as platform from 'vs/base/common/platform'; -import { TPromise } from 'vs/base/common/winjs.base'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction } from 'vs/editor/browser/editorExtensions'; @@ -25,6 +24,7 @@ import { ClickLinkGesture, ClickLinkMouseEvent, ClickLinkKeyboardEvent } from 'v import { MarkdownString } from 'vs/base/common/htmlContent'; import { TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationsChangeAccessor } from 'vs/editor/common/model'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import * as async from 'vs/base/common/async'; const HOVER_MESSAGE_GENERAL_META = new MarkdownString().appendText( platform.isMacintosh @@ -149,8 +149,8 @@ class LinkDetector implements editorCommon.IEditorContribution { private editor: ICodeEditor; private enabled: boolean; private listenersToRemove: IDisposable[]; - private timeoutPromise: TPromise; - private computePromise: TPromise; + private timeoutPromise: async.CancelablePromise; + private computePromise: async.CancelablePromise; private activeLinkDecorationId: string; private openerService: IOpenerService; private notificationService: INotificationService; @@ -226,7 +226,7 @@ class LinkDetector implements editorCommon.IEditorContribution { private onChange(): void { if (!this.timeoutPromise) { - this.timeoutPromise = TPromise.timeout(LinkDetector.RECOMPUTE_TIME); + this.timeoutPromise = async.timeout(LinkDetector.RECOMPUTE_TIME); this.timeoutPromise.then(() => { this.timeoutPromise = null; this.beginCompute(); @@ -234,7 +234,7 @@ class LinkDetector implements editorCommon.IEditorContribution { } } - private beginCompute(): void { + private async beginCompute(): Promise { if (!this.editor.getModel() || !this.enabled) { return; } @@ -243,10 +243,15 @@ class LinkDetector implements editorCommon.IEditorContribution { return; } - this.computePromise = getLinks(this.editor.getModel()).then(links => { + this.computePromise = async.createCancelablePromise(token => getLinks(this.editor.getModel(), token)); + try { + const links = await this.computePromise; this.updateDecorations(links); + } catch (err) { + onUnexpectedError(err); + } finally { this.computePromise = null; - }); + } } private updateDecorations(links: Link[]): void { diff --git a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts index f5479d67f9e..d1cf84d2c74 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts @@ -46,6 +46,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { NullLogService } from 'vs/platform/log/common/log'; import { ITextModel, EndOfLineSequence } from 'vs/editor/common/model'; import { getColors } from 'vs/editor/contrib/colorPicker/color'; +import { CancellationToken } from 'vs/base/common/cancellation'; const defaultSelector = { scheme: 'far' }; const model: ITextModel = EditorModel.createFromString( @@ -1151,7 +1152,7 @@ suite('ExtHostLanguageFeatures', function () { })); return rpcProtocol.sync().then(() => { - return getLinks(model).then(value => { + return getLinks(model, CancellationToken.None).then(value => { assert.equal(value.length, 1); let [first] = value; @@ -1176,7 +1177,7 @@ suite('ExtHostLanguageFeatures', function () { })); return rpcProtocol.sync().then(() => { - return getLinks(model).then(value => { + return getLinks(model, CancellationToken.None).then(value => { assert.equal(value.length, 1); let [first] = value; From 51afe35d0227698c8e097d5ebcd52edbb6891521 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 3 Jul 2018 09:04:59 +0200 Subject: [PATCH 092/283] cleanup more async --- .../inactiveExtensionUrlHandler.ts | 73 ++++++++++--------- .../windows/electron-main/windowsService.ts | 10 +-- .../api/electron-browser/mainThreadSCM.ts | 20 ++--- src/vs/workbench/api/node/extHostSCM.ts | 28 +++---- .../node/extensionsWorkbenchService.ts | 6 +- 5 files changed, 69 insertions(+), 68 deletions(-) diff --git a/src/vs/platform/url/electron-browser/inactiveExtensionUrlHandler.ts b/src/vs/platform/url/electron-browser/inactiveExtensionUrlHandler.ts index f8c42720026..ffc232d2f09 100644 --- a/src/vs/platform/url/electron-browser/inactiveExtensionUrlHandler.ts +++ b/src/vs/platform/url/electron-browser/inactiveExtensionUrlHandler.ts @@ -57,56 +57,57 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { ]); } - async handleURL(uri: URI): TPromise { + handleURL(uri: URI): TPromise { if (!isExtensionId(uri.authority)) { - return false; + return TPromise.as(false); } const extensionId = uri.authority; const wasHandlerAvailable = this.extensionHandlers.has(extensionId); - const extensions = await this.extensionService.getExtensions(); - const extension = extensions.filter(e => e.id === extensionId)[0]; + return this.extensionService.getExtensions().then(extensions => { + const extension = extensions.filter(e => e.id === extensionId)[0]; - if (!extension) { - return false; - } - - const result = await this.dialogService.confirm({ - message: localize('confirmUrl', "Allow an extension to open this URL?", extensionId), - detail: `${extension.displayName || extension.name} (${extensionId}) wants to open a URL:\n\n${uri.toString()}` - }); - - if (!result.confirmed) { - return true; - } - - const handler = this.extensionHandlers.get(extensionId); - if (handler) { - if (!wasHandlerAvailable) { - // forward it directly - return handler.handleURL(uri); + if (!extension) { + return TPromise.as(false); } - // let the ExtensionUrlHandler instance handle this - return TPromise.as(false); - } + return this.dialogService.confirm({ + message: localize('confirmUrl', "Allow an extension to open this URL?", extensionId), + detail: `${extension.displayName || extension.name} (${extensionId}) wants to open a URL:\n\n${uri.toString()}` + }).then(result => { - // collect URI for eventual extension activation - const timestamp = new Date().getTime(); - let uris = this.uriBuffer.get(extensionId); + if (!result.confirmed) { + return TPromise.as(true); + } - if (!uris) { - uris = []; - this.uriBuffer.set(extensionId, uris); - } + const handler = this.extensionHandlers.get(extensionId); + if (handler) { + if (!wasHandlerAvailable) { + // forward it directly + return handler.handleURL(uri); + } - uris.push({ timestamp, uri }); + // let the ExtensionUrlHandler instance handle this + return TPromise.as(false); + } - // activate the extension - await this.extensionService.activateByEvent(`onUri:${extensionId}`); + // collect URI for eventual extension activation + const timestamp = new Date().getTime(); + let uris = this.uriBuffer.get(extensionId); - return true; + if (!uris) { + uris = []; + this.uriBuffer.set(extensionId, uris); + } + + uris.push({ timestamp, uri }); + + // activate the extension + return this.extensionService.activateByEvent(`onUri:${extensionId}`) + .then(() => true); + }); + }); } registerExtensionHandler(extensionId: string, handler: IURLHandler): void { diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index d9bbd5c7c07..b922375cb9e 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -530,21 +530,21 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable return TPromise.as(null); } - async handleURL(uri: URI): TPromise { + handleURL(uri: URI): TPromise { // Catch file URLs if (uri.authority === Schemas.file && !!uri.path) { - return this.openFileForURI(URI.file(uri.fsPath)); + this.openFileForURI(URI.file(uri.fsPath)); + return TPromise.as(true); } - return false; + return TPromise.as(false); } - private async openFileForURI(uri: URI): TPromise { + private openFileForURI(uri: URI): void { const cli = assign(Object.create(null), this.environmentService.args, { goto: true }); const pathsToOpen = [uri.fsPath]; this.windowsMainService.open({ context: OpenContext.API, cli, pathsToOpen }); - return true; } dispose(): void { diff --git a/src/vs/workbench/api/electron-browser/mainThreadSCM.ts b/src/vs/workbench/api/electron-browser/mainThreadSCM.ts index 6bd43da77dc..8ee8daaee7a 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadSCM.ts @@ -401,17 +401,17 @@ export class MainThreadSCM implements MainThreadSCMShape { } if (enabled) { - repository.input.validateInput = async (value, pos): TPromise => { - const result = await this._proxy.$validateInput(sourceControlHandle, value, pos); + repository.input.validateInput = (value, pos): TPromise => { + return this._proxy.$validateInput(sourceControlHandle, value, pos).then(result => { + if (!result) { + return undefined; + } - if (!result) { - return undefined; - } - - return { - message: result[0], - type: result[1] - }; + return { + message: result[0], + type: result[1] + }; + }); }; } else { repository.input.validateInput = () => TPromise.as(undefined); diff --git a/src/vs/workbench/api/node/extHostSCM.ts b/src/vs/workbench/api/node/extHostSCM.ts index b008b72b086..eeafdc4a416 100644 --- a/src/vs/workbench/api/node/extHostSCM.ts +++ b/src/vs/workbench/api/node/extHostSCM.ts @@ -237,14 +237,14 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG return this._resourceStatesMap.get(handle); } - async $executeResourceCommand(handle: number): TPromise { + $executeResourceCommand(handle: number): TPromise { const command = this._resourceStatesCommandsMap.get(handle); if (!command) { - return; + return TPromise.as(null); } - await this._commands.executeCommand(command.command, ...command.arguments); + return asWinJsPromise(_ => this._commands.executeCommand(command.command, ...command.arguments)); } _takeResourceStateSnapshot(): SCMRawResourceSplice[] { @@ -568,25 +568,25 @@ export class ExtHostSCM implements ExtHostSCMShape { return TPromise.as(null); } - async $executeResourceCommand(sourceControlHandle: number, groupHandle: number, handle: number): TPromise { + $executeResourceCommand(sourceControlHandle: number, groupHandle: number, handle: number): TPromise { this.logService.trace('ExtHostSCM#$executeResourceCommand', sourceControlHandle, groupHandle, handle); const sourceControl = this._sourceControls.get(sourceControlHandle); if (!sourceControl) { - return; + return TPromise.as(null); } const group = sourceControl.getResourceGroup(groupHandle); if (!group) { - return; + return TPromise.as(null); } - await group.$executeResourceCommand(handle); + return group.$executeResourceCommand(handle); } - async $validateInput(sourceControlHandle: number, value: string, cursorPosition: number): TPromise<[string, number] | undefined> { + $validateInput(sourceControlHandle: number, value: string, cursorPosition: number): TPromise<[string, number] | undefined> { this.logService.trace('ExtHostSCM#$validateInput', sourceControlHandle); const sourceControl = this._sourceControls.get(sourceControlHandle); @@ -599,12 +599,12 @@ export class ExtHostSCM implements ExtHostSCMShape { return TPromise.as(undefined); } - const result = await sourceControl.inputBox.validateInput(value, cursorPosition); + return asWinJsPromise(_ => Promise.resolve(sourceControl.inputBox.validateInput(value, cursorPosition))).then(result => { + if (!result) { + return TPromise.as(undefined); + } - if (!result) { - return TPromise.as(undefined); - } - - return [result.message, result.type]; + return TPromise.as<[string, number]>([result.message, result.type]); + }); } } diff --git a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts index b09e7e2b83e..7fee4c8e629 100644 --- a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts @@ -964,13 +964,13 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, this.notificationService.error(err); } - async handleURL(uri: URI): TPromise { + handleURL(uri: URI): TPromise { if (!/^extension/.test(uri.path)) { - return false; + return TPromise.as(false); } this.onOpenExtensionUrl(uri); - return true; + return TPromise.as(true); } private onOpenExtensionUrl(uri: URI): void { From d8a39866447931c8ab8ac311c5349d43ba3ef15e Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 3 Jul 2018 09:02:41 +0200 Subject: [PATCH 093/283] avoid async and winjs.promise, #53442 --- .../platform/windows/electron-main/windowsService.ts | 5 +++-- .../electron-browser/runtimeExtensionsEditor.ts | 6 +++++- .../terminal/electron-browser/terminalInstance.ts | 4 ++-- .../extensions/electron-browser/extensionService.ts | 10 +++++----- .../services/extensions/node/extensionPoints.ts | 2 +- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index b922375cb9e..2987443c204 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -537,14 +537,15 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable return TPromise.as(true); } - return TPromise.as(false); + return TPromise.wrap(false); } - private openFileForURI(uri: URI): void { + private openFileForURI(uri: URI): TPromise { const cli = assign(Object.create(null), this.environmentService.args, { goto: true }); const pathsToOpen = [uri.fsPath]; this.windowsMainService.open({ context: OpenContext.API, cli, pathsToOpen }); + return TPromise.wrap(true); } dispose(): void { diff --git a/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts index ae90082e58a..eafae3d229d 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -573,7 +573,11 @@ class SaveExtensionHostProfileAction extends Action { }); } - async run(): TPromise { + run(): TPromise { + return TPromise.wrap(this._asyncRun()); + } + + private async _asyncRun(): Promise { let picked = await this._windowService.showSaveDialog({ title: 'Save Extension Host Profile', buttonLabel: 'Save', diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 94bacf0b29d..8bafe8eaaa7 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -65,7 +65,7 @@ export class TerminalInstance implements ITerminalInstance { private _rows: number; private _dimensionsOverride: ITerminalDimensions; private _windowsShellHelper: WindowsShellHelper; - private _xtermReadyPromise: TPromise; + private _xtermReadyPromise: Promise; private _disposables: lifecycle.IDisposable[]; private _messageTitleDisposable: lifecycle.IDisposable; @@ -255,7 +255,7 @@ export class TerminalInstance implements ITerminalInstance { /** * Create xterm.js instance and attach data listeners. */ - protected async _createXterm(): TPromise { + protected async _createXterm(): Promise { if (!Terminal) { Terminal = (await import('vscode-xterm')).Terminal; // Enable xterm.js addons diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index c1b98e04a52..c2f0c78bfe2 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -649,7 +649,7 @@ export class ExtensionService extends Disposable implements IExtensionService { } } - private static async _validateExtensionsCache(windowService: IWindowService, notificationService: INotificationService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput): TPromise { + private static async _validateExtensionsCache(windowService: IWindowService, notificationService: INotificationService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput): Promise { const cacheFolder = path.join(environmentService.userDataPath, MANIFEST_CACHE_FOLDER); const cacheFile = path.join(cacheFolder, cacheKey); @@ -684,7 +684,7 @@ export class ExtensionService extends Disposable implements IExtensionService { ); } - private static async _readExtensionCache(environmentService: IEnvironmentService, cacheKey: string): TPromise { + private static async _readExtensionCache(environmentService: IEnvironmentService, cacheKey: string): Promise { const cacheFolder = path.join(environmentService.userDataPath, MANIFEST_CACHE_FOLDER); const cacheFile = path.join(cacheFolder, cacheKey); @@ -698,7 +698,7 @@ export class ExtensionService extends Disposable implements IExtensionService { return null; } - private static async _writeExtensionCache(environmentService: IEnvironmentService, cacheKey: string, cacheContents: IExtensionCacheData): TPromise { + private static async _writeExtensionCache(environmentService: IEnvironmentService, cacheKey: string, cacheContents: IExtensionCacheData): Promise { const cacheFolder = path.join(environmentService.userDataPath, MANIFEST_CACHE_FOLDER); const cacheFile = path.join(cacheFolder, cacheKey); @@ -715,7 +715,7 @@ export class ExtensionService extends Disposable implements IExtensionService { } } - private static async _scanExtensionsWithCache(windowService: IWindowService, notificationService: INotificationService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput, log: ILog): TPromise { + private static async _scanExtensionsWithCache(windowService: IWindowService, notificationService: INotificationService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput, log: ILog): Promise { if (input.devMode) { // Do not cache when running out of sources... return ExtensionScanner.scanExtensions(input, log); @@ -788,7 +788,7 @@ export class ExtensionService extends Disposable implements IExtensionService { log ); - let finalBuiltinExtensions: TPromise = builtinExtensions; + let finalBuiltinExtensions: TPromise = TPromise.wrap(builtinExtensions); if (devMode) { const builtInExtensionsFilePath = path.normalize(path.join(URI.parse(require.toUrl('')).fsPath, '..', 'build', 'builtInExtensions.json')); diff --git a/src/vs/workbench/services/extensions/node/extensionPoints.ts b/src/vs/workbench/services/extensions/node/extensionPoints.ts index 8de75c2f9a6..1d00242b57a 100644 --- a/src/vs/workbench/services/extensions/node/extensionPoints.ts +++ b/src/vs/workbench/services/extensions/node/extensionPoints.ts @@ -524,7 +524,7 @@ export class ExtensionScanner { /** * Scan a list of extensions defined in `absoluteFolderPath` */ - public static async scanExtensions(input: ExtensionScannerInput, log: ILog, resolver: IExtensionResolver = null): TPromise { + public static async scanExtensions(input: ExtensionScannerInput, log: ILog, resolver: IExtensionResolver = null): Promise { const absoluteFolderPath = input.absoluteFolderPath; const isBuiltin = input.isBuiltin; const isUnderDevelopment = input.isUnderDevelopment; From c9e6b4bbcec3f2805594de718edc0e10c6581dcc Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 3 Jul 2018 09:10:41 +0200 Subject: [PATCH 094/283] avoid async and winjs.promise, #53442 --- src/vs/workbench/node/extensionHostMain.ts | 4 ++-- .../parts/comments/electron-browser/commentService.ts | 2 +- .../parts/debug/electron-browser/terminalSupport.ts | 2 +- .../preferences/electron-browser/preferencesSearch.ts | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/node/extensionHostMain.ts b/src/vs/workbench/node/extensionHostMain.ts index 4aa8811e6f3..6bf7a628602 100644 --- a/src/vs/workbench/node/extensionHostMain.ts +++ b/src/vs/workbench/node/extensionHostMain.ts @@ -234,7 +234,7 @@ export class ExtensionHostMain { return TPromise.join([fileNamePromise, globPatternPromise]).then(() => { }); } - private async activateIfFileName(extensionId: string, fileName: string): TPromise { + private async activateIfFileName(extensionId: string, fileName: string): Promise { // find exact path for (const { uri } of this._workspace.folders) { @@ -250,7 +250,7 @@ export class ExtensionHostMain { return undefined; } - private async activateIfGlobPatterns(extensionId: string, globPatterns: string[]): TPromise { + private async activateIfGlobPatterns(extensionId: string, globPatterns: string[]): Promise { this._extHostLogService.trace(`extensionHostMain#activateIfGlobPatterns: fileSearch, extension: ${extensionId}, entryPoint: workspaceContains`); if (globPatterns.length === 0) { diff --git a/src/vs/workbench/parts/comments/electron-browser/commentService.ts b/src/vs/workbench/parts/comments/electron-browser/commentService.ts index 80aa7b9359e..ff0fe6655d1 100644 --- a/src/vs/workbench/parts/comments/electron-browser/commentService.ts +++ b/src/vs/workbench/parts/comments/electron-browser/commentService.ts @@ -115,7 +115,7 @@ export class CommentService extends Disposable implements ICommentService { return null; } - async getComments(resource: URI): TPromise { + getComments(resource: URI): TPromise { const result = []; for (const handle of keys(this._commentProviders)) { const provider = this._commentProviders.get(handle); diff --git a/src/vs/workbench/parts/debug/electron-browser/terminalSupport.ts b/src/vs/workbench/parts/debug/electron-browser/terminalSupport.ts index f05ab8af4a4..91fc2c53456 100644 --- a/src/vs/workbench/parts/debug/electron-browser/terminalSupport.ts +++ b/src/vs/workbench/parts/debug/electron-browser/terminalSupport.ts @@ -22,7 +22,7 @@ export class TerminalLauncher implements ITerminalLauncher { ) { } - async runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise { + runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise { if (args.kind === 'external') { return this.nativeTerminalService.runInTerminal(args.title, args.cwd, args.args, args.env || {}); diff --git a/src/vs/workbench/parts/preferences/electron-browser/preferencesSearch.ts b/src/vs/workbench/parts/preferences/electron-browser/preferencesSearch.ts index 3b40c62f656..f0037613a93 100644 --- a/src/vs/workbench/parts/preferences/electron-browser/preferencesSearch.ts +++ b/src/vs/workbench/parts/preferences/electron-browser/preferencesSearch.ts @@ -155,7 +155,7 @@ class RemoteSearchProvider implements ISearchProvider { @ILogService private logService: ILogService ) { this._remoteSearchP = this.options.filter ? - this.getSettingsForFilter(this.options.filter) : + TPromise.wrap(this.getSettingsForFilter(this.options.filter)) : TPromise.wrap(null); } @@ -196,7 +196,7 @@ class RemoteSearchProvider implements ISearchProvider { }); } - private async getSettingsForFilter(filter: string): TPromise { + private async getSettingsForFilter(filter: string): Promise { const allRequestDetails: IBingRequestDetails[] = []; // Only send MAX_REQUESTS requests in total just to keep it sane @@ -308,7 +308,7 @@ class RemoteSearchProvider implements ISearchProvider { }; } - private async prepareRequest(query: string, filterPage = 0): TPromise { + private async prepareRequest(query: string, filterPage = 0): Promise { const verbatimQuery = query; query = escapeSpecialChars(query); const boost = 10; @@ -526,4 +526,4 @@ class SettingMatches { endColumn: setting.valueRange.startColumn + match.end + 1 }; } -} \ No newline at end of file +} From 35f7a7b8177f3547ffb3f509d96c6566fb1fcfed Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 3 Jul 2018 09:22:10 +0200 Subject: [PATCH 095/283] avoid async and winjs.promise, #53442 --- src/vs/code/electron-main/logUploader.ts | 6 +++--- src/vs/platform/driver/electron-browser/driver.ts | 4 ++-- src/vs/platform/driver/electron-main/driver.ts | 4 ++-- .../parts/update/electron-browser/releaseNotesEditor.ts | 2 +- src/vs/workbench/parts/update/electron-browser/update.ts | 6 +++--- .../parts/webview/electron-browser/webviewEditorService.ts | 6 +++--- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/vs/code/electron-main/logUploader.ts b/src/vs/code/electron-main/logUploader.ts index cf76bbbcd1b..7142aa19a4c 100644 --- a/src/vs/code/electron-main/logUploader.ts +++ b/src/vs/code/electron-main/logUploader.ts @@ -37,7 +37,7 @@ export async function uploadLogs( channel: ILaunchChannel, requestService: IRequestService, environmentService: IEnvironmentService -): TPromise { +): Promise { const endpoint = Endpoint.getFromProduct(); if (!endpoint) { console.error(localize('invalidEndpoint', 'Invalid log uploader endpoint')); @@ -75,7 +75,7 @@ async function postLogs( endpoint: Endpoint, outZip: string, requestService: IRequestService -): TPromise { +): Promise { const dotter = setInterval(() => console.log('.'), 5000); let result: IRequestContext; try { @@ -152,4 +152,4 @@ function doZip( default: return cp.execFile('zip', ['-r', outZip, '.'], { cwd: logsPath }, callback); } -} \ No newline at end of file +} diff --git a/src/vs/platform/driver/electron-browser/driver.ts b/src/vs/platform/driver/electron-browser/driver.ts index ae94a0aa811..06590836265 100644 --- a/src/vs/platform/driver/electron-browser/driver.ts +++ b/src/vs/platform/driver/electron-browser/driver.ts @@ -220,7 +220,7 @@ export async function registerWindowDriver( client: IPCClient, windowId: number, instantiationService: IInstantiationService -): TPromise { +): Promise { const windowDriver = instantiationService.createInstance(WindowDriver); const windowDriverChannel = new WindowDriverChannel(windowDriver); client.registerChannel('windowDriver', windowDriverChannel); @@ -236,4 +236,4 @@ export async function registerWindowDriver( const disposable = toDisposable(() => windowDriverRegistry.reloadWindowDriver(windowId)); return combinedDisposable([disposable, client]); -} \ No newline at end of file +} diff --git a/src/vs/platform/driver/electron-main/driver.ts b/src/vs/platform/driver/electron-main/driver.ts index bd006e35558..1da60a761a6 100644 --- a/src/vs/platform/driver/electron-main/driver.ts +++ b/src/vs/platform/driver/electron-main/driver.ts @@ -216,7 +216,7 @@ export async function serve( handle: string, environmentService: IEnvironmentService, instantiationService: IInstantiationService -): TPromise { +): Promise { const verbose = environmentService.driverVerbose; const driver = instantiationService.createInstance(Driver, windowServer, { verbose }); @@ -228,4 +228,4 @@ export async function serve( server.registerChannel('driver', channel); return combinedDisposable([server, windowServer]); -} \ No newline at end of file +} diff --git a/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts b/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts index 9eef2facfd9..b50ecda1765 100644 --- a/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts +++ b/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts @@ -77,7 +77,7 @@ export class ReleaseNotesManager { public async show( accessor: ServicesAccessor, version: string - ): TPromise { + ): Promise { const releaseNoteText = await this.loadReleaseNotes(version); this._lastText = releaseNoteText; const html = await this.renderBody(releaseNoteText); diff --git a/src/vs/workbench/parts/update/electron-browser/update.ts b/src/vs/workbench/parts/update/electron-browser/update.ts index 2ae85c38570..49cd65734ba 100644 --- a/src/vs/workbench/parts/update/electron-browser/update.ts +++ b/src/vs/workbench/parts/update/electron-browser/update.ts @@ -73,11 +73,11 @@ export abstract class AbstractShowReleaseNotesAction extends Action { this.enabled = false; - return showReleaseNotes(this.instantiationService, this.version) + return TPromise.wrap(showReleaseNotes(this.instantiationService, this.version) .then(null, () => { const action = this.instantiationService.createInstance(OpenLatestReleaseNotesInBrowserAction); return action.run().then(() => false); - }); + })); } } @@ -507,4 +507,4 @@ export class UpdateContribution implements IGlobalActivity { dispose(): void { this.disposables = dispose(this.disposables); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/parts/webview/electron-browser/webviewEditorService.ts b/src/vs/workbench/parts/webview/electron-browser/webviewEditorService.ts index d5c548c85f4..3c4228078b6 100644 --- a/src/vs/workbench/parts/webview/electron-browser/webviewEditorService.ts +++ b/src/vs/workbench/parts/webview/electron-browser/webviewEditorService.ts @@ -135,7 +135,7 @@ export class WebviewEditorService implements IWebviewEditorService { return true; }, reviveWebview: (webview: WebviewEditorInput): TPromise => { - return this.tryRevive(webview).then(didRevive => { + return TPromise.wrap(this.tryRevive(webview)).then(didRevive => { if (didRevive) { return TPromise.as(void 0); } @@ -185,7 +185,7 @@ export class WebviewEditorService implements IWebviewEditorService { private async tryRevive( webview: WebviewEditorInput - ): TPromise { + ): Promise { const revivers = this._revivers.get(webview.viewType); if (!revivers) { return false; @@ -199,4 +199,4 @@ export class WebviewEditorService implements IWebviewEditorService { } return false; } -} \ No newline at end of file +} From b0fd77e9ff3c310b41f898a6bbccfc56710ead81 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 3 Jul 2018 09:34:45 +0200 Subject: [PATCH 096/283] fix compile/api issue when using es6 (which is actually correct) --- src/vs/workbench/api/node/extHostDebugService.ts | 6 +++--- src/vs/workbench/api/node/extHostWorkspace.ts | 4 ++-- .../test/electron-browser/api/extHostWorkspace.test.ts | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index 17cb3c8254e..192fc8c86da 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -426,9 +426,9 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { private fireBreakpointChanges(added: vscode.Breakpoint[], removed: vscode.Breakpoint[], changed: vscode.Breakpoint[]) { if (added.length > 0 || removed.length > 0 || changed.length > 0) { this._onDidChangeBreakpoints.fire(Object.freeze({ - added: Object.freeze(added), - removed: Object.freeze(removed), - changed: Object.freeze(changed) + added, + removed, + changed, })); } } diff --git a/src/vs/workbench/api/node/extHostWorkspace.ts b/src/vs/workbench/api/node/extHostWorkspace.ts index 4ab4525526d..536b4e58f9e 100644 --- a/src/vs/workbench/api/node/extHostWorkspace.ts +++ b/src/vs/workbench/api/node/extHostWorkspace.ts @@ -329,8 +329,8 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { // Events this._onDidChangeWorkspace.fire(Object.freeze({ - added: Object.freeze(added), - removed: Object.freeze(removed) + added, + removed, })); } diff --git a/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts b/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts index 5b03bd47a60..4568ce2703c 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts @@ -528,9 +528,9 @@ suite('ExtHostWorkspace', function () { assert.throws(() => { (e).added = []; }); - assert.throws(() => { - (e.added)[0] = null; - }); + // assert.throws(() => { + // (e.added)[0] = null; + // }); } catch (error) { finish(error); } From ecda8b719441832720c43a67b04667813f294b7e Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 3 Jul 2018 09:40:19 +0200 Subject: [PATCH 097/283] use HKLM for environment changes fixes #53438 --- build/win32/code.iss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/win32/code.iss b/build/win32/code.iss index 80fcbf70820..d180ca4ae9b 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -946,7 +946,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\Drive\shell\{#RegValu #define Uninstall64RootKey "HKCU64" #define Uninstall32RootKey "HKCU32" #else -#define EnvironmentRootKey "HKCR" +#define EnvironmentRootKey "HKLM" #define EnvironmentKey "System\CurrentControlSet\Control\Session Manager\Environment" #define Uninstall64RootKey "HKLM64" #define Uninstall32RootKey "HKLM32" From 284c2c0474c7aaa83bdcd7039a8f53f7244b9d06 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 3 Jul 2018 11:27:57 +0200 Subject: [PATCH 098/283] Fix tests --- .../test/electron-browser/extensionsActions.test.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts index 8308b8fc640..926f041931e 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts @@ -14,7 +14,7 @@ import * as ExtensionsActions from 'vs/workbench/parts/extensions/electron-brows import { ExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/node/extensionsWorkbenchService'; import { IExtensionManagementService, IExtensionGalleryService, IExtensionEnablementService, IExtensionTipsService, ILocalExtension, LocalExtensionType, IGalleryExtension, - DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, EnablementState, InstallOperation, IExtensionManagementServerService + DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, EnablementState, InstallOperation, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/platform/extensionManagement/common/extensionManagement'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionManagementService, getLocalExtensionIdFromGallery, getLocalExtensionIdFromManifest } from 'vs/platform/extensionManagement/node/extensionManagementService'; @@ -35,7 +35,8 @@ import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { URLService } from 'vs/platform/url/common/urlService'; import URI from 'vs/base/common/uri'; -import { ExtensionManagementServerService } from 'vs/workbench/services/extensions/node/extensionManagementServerService'; +import { SingleServerExtensionManagementServerService } from 'vs/workbench/services/extensions/node/extensionManagementServerService'; +import { Schemas } from 'vs/base/common/network'; suite('ExtensionsActions Test', () => { @@ -70,7 +71,7 @@ suite('ExtensionsActions Test', () => { instantiationService.stub(IExtensionManagementService, 'onUninstallExtension', uninstallEvent.event); instantiationService.stub(IExtensionManagementService, 'onDidUninstallExtension', didUninstallEvent.event); - instantiationService.stub(IExtensionManagementServerService, instantiationService.createInstance(ExtensionManagementServerService, instantiationService.get(IExtensionManagementService))); + instantiationService.stub(IExtensionManagementServerService, instantiationService.createInstance(SingleServerExtensionManagementServerService, { location: URI.from({ scheme: Schemas.file }), extensionManagementService: instantiationService.get(IExtensionManagementService) })); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); From 9ab41a4464e62739fa8850d2aa312de2f72eb20d Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 3 Jul 2018 11:38:45 +0200 Subject: [PATCH 099/283] :lipstick: --- src/vs/base/common/async.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index b1f1fbb14eb..aa4da71edc6 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -57,10 +57,10 @@ export function createCancelablePromise(callback: (token: CancellationToken) cancel() { source.cancel(); } - then(resolve, reject) { + then(resolve?: ((value: T) => TResult1 | Thenable) | undefined | null, reject?: ((reason: any) => TResult2 | Thenable) | undefined | null): Promise { return promise.then(resolve, reject); } - catch(reject) { + catch(reject?: ((reason: any) => TResult | Thenable) | undefined | null): Promise { return this.then(undefined, reject); } }; From 08388a54f1f80e6c8b929af0b1c57c8f5336e0a3 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 3 Jul 2018 12:11:25 +0200 Subject: [PATCH 100/283] :lipstick: --- src/vs/base/test/common/async.test.ts | 66 +++++++++++++-------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/vs/base/test/common/async.test.ts b/src/vs/base/test/common/async.test.ts index a8914ebefdc..9cab77a9881 100644 --- a/src/vs/base/test/common/async.test.ts +++ b/src/vs/base/test/common/async.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { TPromise } from 'vs/base/common/winjs.base'; -import * as Async from 'vs/base/common/async'; +import * as async from 'vs/base/common/async'; import URI from 'vs/base/common/uri'; import { isPromiseCanceledError } from 'vs/base/common/errors'; @@ -14,7 +14,7 @@ suite('Async', () => { test('cancelablePromise - set token, don\'t wait for inner promise', function () { let canceled = 0; - let promise = Async.createCancelablePromise(token => { + let promise = async.createCancelablePromise(token => { token.onCancellationRequested(_ => { canceled += 1; }); return new Promise(resolve => { /*never*/ }); }); @@ -29,7 +29,7 @@ suite('Async', () => { test('cancelablePromise - cancel despite inner promise being resolved', function () { let canceled = 0; - let promise = Async.createCancelablePromise(token => { + let promise = async.createCancelablePromise(token => { token.onCancellationRequested(_ => { canceled += 1; }); return Promise.resolve(1234); }); @@ -42,8 +42,8 @@ suite('Async', () => { }); test('cancelablePromise - get inner result', async function () { - let promise = Async.createCancelablePromise(token => { - return Async.timeout(12).then(_ => 1234); + let promise = async.createCancelablePromise(token => { + return async.timeout(12).then(_ => 1234); }); let result = await promise; @@ -51,10 +51,10 @@ suite('Async', () => { }); test('asDisposablePromise', async function () { - let value = await Async.asDisposablePromise(TPromise.as(1)).promise; + let value = await async.asDisposablePromise(TPromise.as(1)).promise; assert.equal(value, 1); - let disposablePromise = Async.asDisposablePromise(TPromise.timeout(1000).then(_ => 1), 2); + let disposablePromise = async.asDisposablePromise(TPromise.timeout(1000).then(_ => 1), 2); disposablePromise.dispose(); value = await disposablePromise.promise; assert.equal(value, 2); @@ -66,7 +66,7 @@ suite('Async', () => { return TPromise.as(++count); }; - let throttler = new Async.Throttler(); + let throttler = new async.Throttler(); return TPromise.join([ throttler.queue(factory).then((result) => { assert.equal(result, 1); }), @@ -85,7 +85,7 @@ suite('Async', () => { }); }; - let throttler = new Async.Throttler(); + let throttler = new async.Throttler(); return TPromise.join([ throttler.queue(factory).then((result) => { assert.equal(result, 1); }), @@ -112,7 +112,7 @@ suite('Async', () => { }); }; - let throttler = new Async.Throttler(); + let throttler = new async.Throttler(); let p1: TPromise; const p = TPromise.join([ @@ -135,7 +135,7 @@ suite('Async', () => { }); }; - let throttler = new Async.Throttler(); + let throttler = new async.Throttler(); let p2: TPromise; const p = TPromise.join([ @@ -158,7 +158,7 @@ suite('Async', () => { }); }; - let throttler = new Async.Throttler(); + let throttler = new async.Throttler(); let p3: TPromise; const p = TPromise.join([ @@ -178,7 +178,7 @@ suite('Async', () => { return TPromise.timeout(0).then(() => n); }; - let throttler = new Async.Throttler(); + let throttler = new async.Throttler(); let promises: TPromise[] = []; @@ -198,7 +198,7 @@ suite('Async', () => { }); }); - let throttler = new Async.Throttler(); + let throttler = new async.Throttler(); let promises: TPromise[] = []; let progresses: any[][] = [[], [], []]; @@ -219,7 +219,7 @@ suite('Async', () => { return TPromise.as(++count); }; - let delayer = new Async.Delayer(0); + let delayer = new async.Delayer(0); let promises: TPromise[] = []; assert(!delayer.isTriggered()); @@ -244,7 +244,7 @@ suite('Async', () => { return TPromise.as(++count); }; - let delayer = new Async.Delayer(0); + let delayer = new async.Delayer(0); assert(!delayer.isTriggered()); @@ -267,7 +267,7 @@ suite('Async', () => { return TPromise.as(++count); }; - let delayer = new Async.Delayer(0); + let delayer = new async.Delayer(0); let promises: TPromise[] = []; assert(!delayer.isTriggered()); @@ -294,7 +294,7 @@ suite('Async', () => { return TPromise.as(++count); }; - let delayer = new Async.Delayer(0); + let delayer = new async.Delayer(0); let promises: TPromise[] = []; assert(!delayer.isTriggered()); @@ -346,7 +346,7 @@ suite('Async', () => { return TPromise.as(n); }; - let delayer = new Async.Delayer(0); + let delayer = new async.Delayer(0); let promises: TPromise[] = []; assert(!delayer.isTriggered()); @@ -373,7 +373,7 @@ suite('Async', () => { }); }); - let delayer = new Async.Delayer(0); + let delayer = new async.Delayer(0); let promises: TPromise[] = []; let progresses: any[][] = [[], [], []]; @@ -397,7 +397,7 @@ suite('Async', () => { }); }); - let delayer = new Async.ThrottledDelayer(0); + let delayer = new async.ThrottledDelayer(0); let promises: TPromise[] = []; let progresses: any[][] = [[], [], []]; @@ -417,7 +417,7 @@ suite('Async', () => { return TPromise.as(n); }; - return Async.sequence([ + return async.sequence([ factoryFactory(1), factoryFactory(2), factoryFactory(3), @@ -438,7 +438,7 @@ suite('Async', () => { return TPromise.as(n); }; - let limiter = new Async.Limiter(1); + let limiter = new async.Limiter(1); let promises: TPromise[] = []; [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(n => promises.push(limiter.queue(factoryFactory(n)))); @@ -446,7 +446,7 @@ suite('Async', () => { return TPromise.join(promises).then((res) => { assert.equal(10, res.length); - limiter = new Async.Limiter(100); + limiter = new async.Limiter(100); promises = []; [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(n => promises.push(limiter.queue(factoryFactory(n)))); @@ -462,14 +462,14 @@ suite('Async', () => { return TPromise.timeout(0).then(() => n); }; - let limiter = new Async.Limiter(1); + let limiter = new async.Limiter(1); let promises: TPromise[] = []; [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(n => promises.push(limiter.queue(factoryFactory(n)))); return TPromise.join(promises).then((res) => { assert.equal(10, res.length); - limiter = new Async.Limiter(100); + limiter = new async.Limiter(100); promises = []; [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(n => promises.push(limiter.queue(factoryFactory(n)))); @@ -488,7 +488,7 @@ suite('Async', () => { return TPromise.timeout(0).then(() => { activePromises--; return n; }); }; - let limiter = new Async.Limiter(5); + let limiter = new async.Limiter(5); let promises: TPromise[] = []; [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(n => promises.push(limiter.queue(factoryFactory(n)))); @@ -500,7 +500,7 @@ suite('Async', () => { }); test('Queue - simple', function () { - let queue = new Async.Queue(); + let queue = new async.Queue(); let syncPromise = false; let f1 = () => TPromise.as(true).then(() => syncPromise = true); @@ -523,7 +523,7 @@ suite('Async', () => { }); test('Queue - order is kept', function () { - let queue = new Async.Queue(); + let queue = new async.Queue(); let res: number[] = []; @@ -547,7 +547,7 @@ suite('Async', () => { }); test('Queue - errors bubble individually but not cause stop', function () { - let queue = new Async.Queue(); + let queue = new async.Queue(); let res: number[] = []; let error = false; @@ -572,7 +572,7 @@ suite('Async', () => { }); test('Queue - order is kept (chained)', function () { - let queue = new Async.Queue(); + let queue = new async.Queue(); let res: number[] = []; @@ -600,7 +600,7 @@ suite('Async', () => { }); test('Queue - events', function (done) { - let queue = new Async.Queue(); + let queue = new async.Queue(); let finished = false; queue.onFinished(() => { @@ -626,7 +626,7 @@ suite('Async', () => { }); test('ResourceQueue - simple', function () { - let queue = new Async.ResourceQueue(); + let queue = new async.ResourceQueue(); const r1Queue = queue.queueFor(URI.file('/some/path')); const r2Queue = queue.queueFor(URI.file('/some/other/path')); From e79e446e90a8699ef1a7978d968fd37cf8829d61 Mon Sep 17 00:00:00 2001 From: Dirk Baeumer Date: Tue, 3 Jul 2018 11:37:32 +0200 Subject: [PATCH 101/283] Exclude all build folders to make the TS file watcher calm --- src/tsconfig.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/tsconfig.json b/src/tsconfig.json index d2b9e229c4a..408cad4917b 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -18,5 +18,10 @@ "typeRoots": [ "typings" ] - } + }, + "exclude": [ + "../out", + "../out-build", + "../out-vscode" + ] } From 3c0abb8d8aeb6b334e52b766ca3f81f3db88fd6b Mon Sep 17 00:00:00 2001 From: Dirk Baeumer Date: Tue, 3 Jul 2018 11:39:30 +0200 Subject: [PATCH 102/283] Add support to regenerate nsl cache if corrupted. --- src/bootstrap-amd.js | 13 +- src/main.js | 128 +++++++++++------- .../electron-browser/issue/issueReporter.js | 13 +- .../processExplorer/processExplorer.js | 13 +- .../sharedProcess/sharedProcess.js | 13 +- .../electron-browser/bootstrap/index.js | 13 +- 6 files changed, 136 insertions(+), 57 deletions(-) diff --git a/src/bootstrap-amd.js b/src/bootstrap-amd.js index f0015d389d3..98bc02c22cd 100644 --- a/src/bootstrap-amd.js +++ b/src/bootstrap-amd.js @@ -29,6 +29,8 @@ function readFile(file) { }); } +const writeFile = (file, content) => new Promise((c, e) => fs.writeFile(file, content, 'utf8', err => err ? e(err) : c())); + var rawNlsConfig = process.env['VSCODE_NLS_CONFIG']; var nlsConfig = rawNlsConfig ? JSON.parse(rawNlsConfig) : { availableLanguages: {} }; @@ -46,8 +48,15 @@ if (nlsConfig._resolvedLanguagePackCoreLocation) { let json = JSON.parse(content); bundles[bundle] = json; cb(undefined, json); - }) - .catch(cb); + }).catch((error) => { + try { + if (nlsConfig._corruptedFile) { + writeFile(nlsConfig._corruptedFile, 'corrupted').catch(function (error) { console.error(error); }); + } + } finally { + cb(error, undefined); + } + }); }; } diff --git a/src/main.js b/src/main.js index 624bf52f0c1..5d30c39f692 100644 --- a/src/main.js +++ b/src/main.js @@ -123,6 +123,10 @@ const exists = file => new Promise(c => fs.exists(file, c)); const readFile = file => new Promise((c, e) => fs.readFile(file, 'utf8', (err, data) => err ? e(err) : c(data))); const writeFile = (file, content) => new Promise((c, e) => fs.writeFile(file, content, 'utf8', err => err ? e(err) : c())); const touch = file => new Promise((c, e) => { const d = new Date(); fs.utimes(file, d, d, err => err ? e(err) : c()); }); +const lstat = file => new Promise((c, e) => fs.lstat(file, (err, stats) => err ? e(err) : c(stats))); +const readdir = dir => new Promise((c, e) => fs.readdir(dir, (err, files) => err ? e(err) : c(files))); +const rmdir = dir => new Promise((c, e) => fs.rmdir(dir, err => err ? e(err) : c(undefined))); +const unlink = file => new Promise((c, e) => fs.unlink(file, err => err ? e(err) : c(undefined))); function mkdirp(dir) { return mkdir(dir).then(null, err => { @@ -138,6 +142,23 @@ function mkdirp(dir) { }); } +function rimraf(location) { + return lstat(location).then(stat => { + if (stat.isDirectory() && !stat.isSymbolicLink()) { + return readdir(location) + .then(children => Promise.all(children.map(child => rimraf(path.join(location, child))))) + .then(() => rmdir(location)); + } else { + return unlink(location); + } + }, (err) => { + if (err.code === 'ENOENT') { + return void 0; + } + throw err; + }); +} + function resolveJSFlags(...jsFlags) { if (args['js-flags']) { @@ -267,62 +288,75 @@ function getNLSConfiguration(locale) { let cacheRoot = path.join(userData, 'clp', packId); let coreLocation = path.join(cacheRoot, commit); let translationsConfigFile = path.join(cacheRoot, 'tcf.json'); + let corruptedFile = path.join(cacheRoot, 'corrupted.info'); let result = { locale: initialLocale, availableLanguages: { '*': locale }, _languagePackId: packId, _translationsConfigFile: translationsConfigFile, _cacheRoot: cacheRoot, - _resolvedLanguagePackCoreLocation: coreLocation + _resolvedLanguagePackCoreLocation: coreLocation, + _corruptedFile: corruptedFile }; - return exists(coreLocation).then((fileExists) => { - if (fileExists) { - // We don't wait for this. No big harm if we can't touch - touch(coreLocation).catch(() => { }); - perf.mark('nlsGeneration:end'); - return result; + return exists(corruptedFile).then((corrupted) => { + // The nls cache directory is corrupted. + let toDelete; + if (corrupted) { + toDelete = rimraf(cacheRoot); + } else { + toDelete = Promise.resolve(undefined); } - return mkdirp(coreLocation).then(() => { - return Promise.all([readFile(path.join(__dirname, 'nls.metadata.json')), readFile(mainPack)]); - }).then((values) => { - let metadata = JSON.parse(values[0]); - let packData = JSON.parse(values[1]).contents; - let bundles = Object.keys(metadata.bundles); - let writes = []; - for (let bundle of bundles) { - let modules = metadata.bundles[bundle]; - let target = Object.create(null); - for (let module of modules) { - let keys = metadata.keys[module]; - let defaultMessages = metadata.messages[module]; - let translations = packData[module]; - let targetStrings; - if (translations) { - targetStrings = []; - for (let i = 0; i < keys.length; i++) { - let elem = keys[i]; - let key = typeof elem === 'string' ? elem : elem.key; - let translatedMessage = translations[key]; - if (translatedMessage === undefined) { - translatedMessage = defaultMessages[i]; - } - targetStrings.push(translatedMessage); - } - } else { - targetStrings = defaultMessages; - } - target[module] = targetStrings; + return toDelete.then(() => { + return exists(coreLocation).then((fileExists) => { + if (fileExists) { + // We don't wait for this. No big harm if we can't touch + touch(coreLocation).catch(() => { }); + perf.mark('nlsGeneration:end'); + return result; } - writes.push(writeFile(path.join(coreLocation, bundle.replace(/\//g, '!') + '.nls.json'), JSON.stringify(target))); - } - writes.push(writeFile(translationsConfigFile, JSON.stringify(packConfig.translations))); - return Promise.all(writes); - }).then(() => { - perf.mark('nlsGeneration:end'); - return result; - }).catch((err) => { - console.error('Generating translation files failed.', err); - return defaultResult(locale); + return mkdirp(coreLocation).then(() => { + return Promise.all([readFile(path.join(__dirname, 'nls.metadata.json')), readFile(mainPack)]); + }).then((values) => { + let metadata = JSON.parse(values[0]); + let packData = JSON.parse(values[1]).contents; + let bundles = Object.keys(metadata.bundles); + let writes = []; + for (let bundle of bundles) { + let modules = metadata.bundles[bundle]; + let target = Object.create(null); + for (let module of modules) { + let keys = metadata.keys[module]; + let defaultMessages = metadata.messages[module]; + let translations = packData[module]; + let targetStrings; + if (translations) { + targetStrings = []; + for (let i = 0; i < keys.length; i++) { + let elem = keys[i]; + let key = typeof elem === 'string' ? elem : elem.key; + let translatedMessage = translations[key]; + if (translatedMessage === undefined) { + translatedMessage = defaultMessages[i]; + } + targetStrings.push(translatedMessage); + } + } else { + targetStrings = defaultMessages; + } + target[module] = targetStrings; + } + writes.push(writeFile(path.join(coreLocation, bundle.replace(/\//g, '!') + '.nls.json'), JSON.stringify(target))); + } + writes.push(writeFile(translationsConfigFile, JSON.stringify(packConfig.translations))); + return Promise.all(writes); + }).then(() => { + perf.mark('nlsGeneration:end'); + return result; + }).catch((err) => { + console.error('Generating translation files failed.', err); + return defaultResult(locale); + }); + }); }); }); }); diff --git a/src/vs/code/electron-browser/issue/issueReporter.js b/src/vs/code/electron-browser/issue/issueReporter.js index 5b246f9491b..53abd748d7b 100644 --- a/src/vs/code/electron-browser/issue/issueReporter.js +++ b/src/vs/code/electron-browser/issue/issueReporter.js @@ -45,6 +45,8 @@ function readFile(file) { }); } +const writeFile = (file, content) => new Promise((c, e) => fs.writeFile(file, content, 'utf8', err => err ? e(err) : c())); + function main() { const args = parseURLQueryArgs(); const configuration = JSON.parse(args['config'] || '{}') || {}; @@ -127,8 +129,15 @@ function main() { let json = JSON.parse(content); bundles[bundle] = json; cb(undefined, json); - }) - .catch(cb); + }).catch((error) => { + try { + if (nlsConfig._corruptedFile) { + writeFile(nlsConfig._corruptedFile, 'corrupted').catch(function (error) { console.error(error); }); + } + } finally { + cb(error, undefined); + } + }); }; } diff --git a/src/vs/code/electron-browser/processExplorer/processExplorer.js b/src/vs/code/electron-browser/processExplorer/processExplorer.js index 1fdb4bb8296..0a798fb876d 100644 --- a/src/vs/code/electron-browser/processExplorer/processExplorer.js +++ b/src/vs/code/electron-browser/processExplorer/processExplorer.js @@ -45,6 +45,8 @@ function readFile(file) { }); } +const writeFile = (file, content) => new Promise((c, e) => fs.writeFile(file, content, 'utf8', err => err ? e(err) : c())); + function main() { const args = parseURLQueryArgs(); const configuration = JSON.parse(args['config'] || '{}') || {}; @@ -102,8 +104,15 @@ function main() { let json = JSON.parse(content); bundles[bundle] = json; cb(undefined, json); - }) - .catch(cb); + }).catch((error) => { + try { + if (nlsConfig._corruptedFile) { + writeFile(nlsConfig._corruptedFile, 'corrupted').catch(function (error) { console.error(error); }); + } + } finally { + cb(error, undefined); + } + }); }; } diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcess.js b/src/vs/code/electron-browser/sharedProcess/sharedProcess.js index 98d6e852940..d7dd08b4e3d 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcess.js +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcess.js @@ -53,6 +53,8 @@ function readFile(file) { }); } +const writeFile = (file, content) => new Promise((c, e) => fs.writeFile(file, content, 'utf8', err => err ? e(err) : c())); + function main() { const args = parseURLQueryArgs(); const configuration = JSON.parse(args['config'] || '{}') || {}; @@ -111,8 +113,15 @@ function main() { let json = JSON.parse(content); bundles[bundle] = json; cb(undefined, json); - }) - .catch(cb); + }).catch((error) => { + try { + if (nlsConfig._corruptedFile) { + writeFile(nlsConfig._corruptedFile, 'corrupted').catch(function (error) { console.error(error); }); + } + } finally { + cb(error, undefined); + } + }); }; } diff --git a/src/vs/workbench/electron-browser/bootstrap/index.js b/src/vs/workbench/electron-browser/bootstrap/index.js index 75f6dc4d7f6..692122631e7 100644 --- a/src/vs/workbench/electron-browser/bootstrap/index.js +++ b/src/vs/workbench/electron-browser/bootstrap/index.js @@ -81,6 +81,8 @@ function readFile(file) { }); } +const writeFile = (file, content) => new Promise((c, e) => fs.writeFile(file, content, 'utf8', err => err ? e(err) : c())); + function registerListeners(enableDeveloperTools) { // Devtools & reload support @@ -180,8 +182,15 @@ function main() { let json = JSON.parse(content); bundles[bundle] = json; cb(undefined, json); - }) - .catch(cb); + }).catch((error) => { + try { + if (nlsConfig._corruptedFile) { + writeFile(nlsConfig._corruptedFile, 'corrupted').catch(function (error) { console.error(error); }); + } + } finally { + cb(error, undefined); + } + }); }; } From a6e28b0be061f27a5ea179aa675e7d8e23d6cfd3 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 3 Jul 2018 12:42:54 +0200 Subject: [PATCH 103/283] empty -> Disposable.Empty --- src/vs/base/browser/ui/grid/gridview.ts | 24 +++++++++---------- src/vs/base/browser/ui/tree/tree.ts | 4 ++-- src/vs/base/common/event.ts | 4 ++-- src/vs/base/common/lifecycle.ts | 6 ++--- .../editor/contrib/hover/modesContentHover.ts | 8 +++---- .../browser/standaloneCodeEditor.ts | 4 ++-- .../parts/compositebar/compositeBarActions.ts | 6 ++--- .../browser/parts/views/customView.ts | 6 ++--- .../electron-browser/views/explorerViewer.ts | 6 ++--- .../html/electron-browser/htmlPreviewPart.ts | 6 ++--- .../electron-browser/dirtydiffDecorator.ts | 6 ++--- .../parts/scm/electron-browser/scmActivity.ts | 10 ++++---- .../parts/scm/electron-browser/scmViewlet.ts | 14 +++++------ .../parts/update/electron-browser/update.ts | 4 ++-- 14 files changed, 53 insertions(+), 55 deletions(-) diff --git a/src/vs/base/browser/ui/grid/gridview.ts b/src/vs/base/browser/ui/grid/gridview.ts index 12619fee81c..98a9d0ef79c 100644 --- a/src/vs/base/browser/ui/grid/gridview.ts +++ b/src/vs/base/browser/ui/grid/gridview.ts @@ -9,7 +9,7 @@ import 'vs/css!./gridview'; import { Event, anyEvent, Emitter, mapEvent, Relay } from 'vs/base/common/event'; import { Orientation, Sash } from 'vs/base/browser/ui/sash/sash'; import { SplitView, IView as ISplitView, Sizing, ISplitViewStyles } from 'vs/base/browser/ui/splitview/splitview'; -import { empty as EmptyDisposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { $ } from 'vs/base/browser/dom'; import { tail2 as tail } from 'vs/base/common/arrays'; import { Color } from 'vs/base/common/color'; @@ -122,12 +122,12 @@ class BranchNode implements ISplitView, IDisposable { private _onDidChange = new Emitter(); readonly onDidChange: Event = this._onDidChange.event; - private childrenChangeDisposable: IDisposable = EmptyDisposable; + private childrenChangeDisposable: IDisposable = Disposable.None; private _onDidSashReset = new Emitter(); readonly onDidSashReset: Event = this._onDidSashReset.event; - private splitviewSashResetDisposable: IDisposable = EmptyDisposable; - private childrenSashResetDisposable: IDisposable = EmptyDisposable; + private splitviewSashResetDisposable: IDisposable = Disposable.None; + private childrenSashResetDisposable: IDisposable = Disposable.None; get orthogonalStartSash(): Sash | undefined { return this.splitview.orthogonalStartSash; } set orthogonalStartSash(sash: Sash | undefined) { this.splitview.orthogonalStartSash = sash; } @@ -280,22 +280,22 @@ class BranchNode implements ISplitView, IDisposable { trySet2x2(other: BranchNode): IDisposable { if (this.children.length !== 2 || other.children.length !== 2) { - return EmptyDisposable; + return Disposable.None; } if (this.getChildSize(0) !== other.getChildSize(0)) { - return EmptyDisposable; + return Disposable.None; } const [firstChild, secondChild] = this.children; const [otherFirstChild, otherSecondChild] = other.children; if (!(firstChild instanceof LeafNode) || !(secondChild instanceof LeafNode)) { - return EmptyDisposable; + return Disposable.None; } if (!(otherFirstChild instanceof LeafNode) || !(otherSecondChild instanceof LeafNode)) { - return EmptyDisposable; + return Disposable.None; } if (this.orientation === Orientation.VERTICAL) { @@ -485,7 +485,7 @@ export class GridView implements IDisposable { private onDidSashResetRelay = new Relay(); readonly onDidSashReset: Event = this.onDidSashResetRelay.event; - private disposable2x2: IDisposable = EmptyDisposable; + private disposable2x2: IDisposable = Disposable.None; private get root(): BranchNode { return this._root; @@ -550,7 +550,7 @@ export class GridView implements IDisposable { addView(view: IView, size: number | Sizing, location: number[]): void { this.disposable2x2.dispose(); - this.disposable2x2 = EmptyDisposable; + this.disposable2x2 = Disposable.None; const [rest, index] = tail(location); const [pathToParent, parent] = this.getNode(rest); @@ -582,7 +582,7 @@ export class GridView implements IDisposable { removeView(location: number[], sizing?: Sizing): IView { this.disposable2x2.dispose(); - this.disposable2x2 = EmptyDisposable; + this.disposable2x2 = Disposable.None; const [rest, index] = tail(location); const [pathToParent, parent] = this.getNode(rest); @@ -783,7 +783,7 @@ export class GridView implements IDisposable { trySet2x2(): void { this.disposable2x2.dispose(); - this.disposable2x2 = EmptyDisposable; + this.disposable2x2 = Disposable.None; if (this.root.children.length !== 2) { return; diff --git a/src/vs/base/browser/ui/tree/tree.ts b/src/vs/base/browser/ui/tree/tree.ts index 9d98c12dfb7..6cd72d3311f 100644 --- a/src/vs/base/browser/ui/tree/tree.ts +++ b/src/vs/base/browser/ui/tree/tree.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./tree'; -import { IDisposable, dispose, empty as EmptyDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IListOptions, List, IIdentityProvider, IMultipleSelectionController } from 'vs/base/browser/ui/list/listWidget'; import { TreeModel, ITreeNode, ITreeElement } from 'vs/base/browser/ui/tree/treeModel'; import { IIterator, empty } from 'vs/base/common/iterator'; @@ -83,7 +83,7 @@ class TreeRenderer implements IRenderer, ITreeLis const contents = append(el, $('.tl-contents')); const templateData = this.renderer.renderTemplate(contents); - return { twistie, elementDisposable: EmptyDisposable, templateData }; + return { twistie, elementDisposable: Disposable.None, templateData }; } renderElement(node: ITreeNode, index: number, templateData: ITreeListTemplateData): void { diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index 99b3fed4001..2f88230192f 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -6,7 +6,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { once as onceFn } from 'vs/base/common/functional'; -import { combinedDisposable, empty as EmptyDisposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { combinedDisposable, Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { LinkedList } from 'vs/base/common/linkedList'; import { TPromise } from 'vs/base/common/winjs.base'; @@ -559,7 +559,7 @@ export class Relay implements IDisposable { private emitter = new Emitter(); readonly event: Event = this.emitter.event; - private disposable: IDisposable = EmptyDisposable; + private disposable: IDisposable = Disposable.None; set input(event: Event) { this.disposable.dispose(); diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index befc15c2676..fdb344181c6 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -7,10 +7,6 @@ import { once } from 'vs/base/common/functional'; -export const empty: IDisposable = Object.freeze({ - dispose() { } -}); - export interface IDisposable { dispose(): void; } @@ -57,6 +53,8 @@ export function toDisposable(...fns: (() => void)[]): IDisposable { export abstract class Disposable implements IDisposable { + static None = Object.freeze({ dispose() { } }); + protected _toDispose: IDisposable[] = []; protected get toDispose(): IDisposable[] { return this._toDispose; } diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index 9a3665d60d8..2713b5d2f4d 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -21,7 +21,7 @@ import { ColorPickerModel } from 'vs/editor/contrib/colorPicker/colorPickerModel import { ColorPickerWidget } from 'vs/editor/contrib/colorPicker/colorPickerWidget'; import { ColorDetector } from 'vs/editor/contrib/colorPicker/colorDetector'; import { Color, RGBA } from 'vs/base/common/color'; -import { IDisposable, empty as EmptyDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { getColorPresentations } from 'vs/editor/contrib/colorPicker/color'; import { IThemeService } from 'vs/platform/theme/common/themeService'; const $ = dom.$; @@ -167,7 +167,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { private _shouldFocus: boolean; private _colorPicker: ColorPickerWidget; - private renderDisposable: IDisposable = EmptyDisposable; + private renderDisposable: IDisposable = Disposable.None; constructor( editor: ICodeEditor, @@ -205,7 +205,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { dispose(): void { this.renderDisposable.dispose(); - this.renderDisposable = EmptyDisposable; + this.renderDisposable = Disposable.None; this._hoverOperation.cancel(); super.dispose(); } @@ -274,7 +274,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { this._highlightDecorations = this._editor.deltaDecorations(this._highlightDecorations, []); this._isChangingDecorations = false; this.renderDisposable.dispose(); - this.renderDisposable = EmptyDisposable; + this.renderDisposable = Disposable.None; this._colorPicker = null; } diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index 22b23704de2..3a1fabd9093 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -5,7 +5,7 @@ 'use strict'; -import { empty as emptyDisposable, IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { TPromise } from 'vs/base/common/winjs.base'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -206,7 +206,7 @@ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandalon } if (!this._standaloneKeybindingService) { console.warn('Cannot add keybinding because the editor is configured with an unrecognized KeybindingService'); - return emptyDisposable; + return Disposable.None; } // Read descriptor options diff --git a/src/vs/workbench/browser/parts/compositebar/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositebar/compositeBarActions.ts index d06fa2b65a6..6d6647e8c5a 100644 --- a/src/vs/workbench/browser/parts/compositebar/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositebar/compositeBarActions.ts @@ -12,7 +12,7 @@ import * as dom from 'vs/base/browser/dom'; import { Builder, $ } from 'vs/base/browser/builder'; import { BaseActionItem, IBaseActionItemOptions, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { dispose, IDisposable, empty, toDisposable } from 'vs/base/common/lifecycle'; +import { dispose, IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; import { TextBadge, NumberBadge, IBadge, IconBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity'; @@ -130,7 +130,7 @@ export class ActivityActionItem extends BaseActionItem { protected options: IActivityActionItemOptions; private $badgeContent: Builder; - private badgeDisposable: IDisposable = empty; + private badgeDisposable: IDisposable = Disposable.None; private mouseUpTimeout: number; constructor( @@ -230,7 +230,7 @@ export class ActivityActionItem extends BaseActionItem { const clazz = action.getClass(); this.badgeDisposable.dispose(); - this.badgeDisposable = empty; + this.badgeDisposable = Disposable.None; this.$badgeContent.empty(); this.$badge.hide(); diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index cbde117567a..54306015001 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/views'; import { Event, Emitter } from 'vs/base/common/event'; -import { IDisposable, dispose, empty as EmptyDisposable, toDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TPromise } from 'vs/base/common/winjs.base'; import { IAction, IActionItem, ActionRunner } from 'vs/base/common/actions'; @@ -109,7 +109,7 @@ export class CustomTreeViewPanel extends ViewletPanel { class TitleMenus implements IDisposable { private disposables: IDisposable[] = []; - private titleDisposable: IDisposable = EmptyDisposable; + private titleDisposable: IDisposable = Disposable.None; private titleActions: IAction[] = []; private titleSecondaryActions: IAction[] = []; @@ -123,7 +123,7 @@ class TitleMenus implements IDisposable { ) { if (this.titleDisposable) { this.titleDisposable.dispose(); - this.titleDisposable = EmptyDisposable; + this.titleDisposable = Disposable.None; } const _contextKeyService = this.contextKeyService.createScoped(); diff --git a/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts b/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts index 3f363cbdbd2..09615b2bae6 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts +++ b/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts @@ -20,7 +20,7 @@ import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; import { isMacintosh, isLinux } from 'vs/base/common/platform'; import * as glob from 'vs/base/common/glob'; import { FileLabel, IFileLabelOptions } from 'vs/workbench/browser/labels'; -import { IDisposable, dispose, empty as EmptyDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { IFilesConfiguration, SortOrder } from 'vs/workbench/parts/files/common/files'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { FileOperationError, FileOperationResult, IFileService, FileKind } from 'vs/platform/files/common/files'; @@ -233,7 +233,7 @@ export class FileRenderer implements IRenderer { } public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): IFileTemplateData { - const elementDisposable = EmptyDisposable; + const elementDisposable = Disposable.None; const label = this.instantiationService.createInstance(FileLabel, container, void 0); return { elementDisposable, label, container }; @@ -264,7 +264,7 @@ export class FileRenderer implements IRenderer { else { templateData.label.element.style.display = 'none'; this.renderInputBox(templateData.container, tree, stat, editableData); - templateData.elementDisposable = EmptyDisposable; + templateData.elementDisposable = Disposable.None; } } diff --git a/src/vs/workbench/parts/html/electron-browser/htmlPreviewPart.ts b/src/vs/workbench/parts/html/electron-browser/htmlPreviewPart.ts index 122d5047ffd..1729ad9165f 100644 --- a/src/vs/workbench/parts/html/electron-browser/htmlPreviewPart.ts +++ b/src/vs/workbench/parts/html/electron-browser/htmlPreviewPart.ts @@ -8,7 +8,7 @@ import { localize } from 'vs/nls'; import { TPromise } from 'vs/base/common/winjs.base'; import { ITextModel } from 'vs/editor/common/model'; -import { empty as EmptyDisposable, IDisposable, dispose, IReference } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, dispose, IReference } from 'vs/base/common/lifecycle'; import { EditorOptions, EditorInput, IEditorMemento } from 'vs/workbench/common/editor'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; @@ -42,8 +42,8 @@ export class HtmlPreviewPart extends BaseWebviewEditor { private _modelRef: IReference; public get model(): ITextModel { return this._modelRef && this._modelRef.object.textEditorModel; } - private _modelChangeSubscription = EmptyDisposable; - private _themeChangeSubscription = EmptyDisposable; + private _modelChangeSubscription = Disposable.None; + private _themeChangeSubscription = Disposable.None; private _content: HTMLElement; private _scrollYPercentage: number = 0; diff --git a/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts b/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts index 0f7d7786288..c86f7733749 100644 --- a/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts @@ -9,7 +9,7 @@ import * as nls from 'vs/nls'; import 'vs/css!./media/dirtydiffDecorator'; import { ThrottledDelayer, always, first } from 'vs/base/common/async'; -import { IDisposable, dispose, toDisposable, empty as EmptyDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, toDisposable, Disposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { TPromise } from 'vs/base/common/winjs.base'; import { Event, Emitter, anyEvent as anyEvent, filterEvent, once } from 'vs/base/common/event'; import * as ext from 'vs/workbench/common/contributions'; @@ -553,7 +553,7 @@ export class DirtyDiffController implements IEditorContribution { private widget: DirtyDiffWidget | null = null; private currentIndex: number = -1; private readonly isDirtyDiffVisible: IContextKey; - private session: IDisposable = EmptyDisposable; + private session: IDisposable = Disposable.None; private mouseDownInfo: { lineNumber: number } | null = null; private enabled = false; private disposables: IDisposable[] = []; @@ -611,7 +611,7 @@ export class DirtyDiffController implements IEditorContribution { close(): void { this.session.dispose(); - this.session = EmptyDisposable; + this.session = Disposable.None; } private assertWidget(): boolean { diff --git a/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts b/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts index 9464225a74d..c0c4f11d15e 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts @@ -7,7 +7,7 @@ import { localize } from 'vs/nls'; import { basename } from 'vs/base/common/paths'; -import { IDisposable, dispose, empty as EmptyDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, Disposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { filterEvent, anyEvent as anyEvent } from 'vs/base/common/event'; import { VIEWLET_ID } from 'vs/workbench/parts/scm/common/scm'; import { ISCMService, ISCMRepository } from 'vs/workbench/services/scm/common/scm'; @@ -18,7 +18,7 @@ import { IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment } export class StatusUpdater implements IWorkbenchContribution { - private badgeDisposable: IDisposable = EmptyDisposable; + private badgeDisposable: IDisposable = Disposable.None; private disposables: IDisposable[] = []; constructor( @@ -60,7 +60,7 @@ export class StatusUpdater implements IWorkbenchContribution { const badge = new NumberBadge(count, num => localize('scmPendingChangesBadge', '{0} pending changes', num)); this.badgeDisposable = this.activityService.showActivity(VIEWLET_ID, badge, 'scm-viewlet-label'); } else { - this.badgeDisposable = EmptyDisposable; + this.badgeDisposable = Disposable.None; } } @@ -72,8 +72,8 @@ export class StatusUpdater implements IWorkbenchContribution { export class StatusBarController implements IWorkbenchContribution { - private statusBarDisposable: IDisposable = EmptyDisposable; - private focusDisposable: IDisposable = EmptyDisposable; + private statusBarDisposable: IDisposable = Disposable.None; + private focusDisposable: IDisposable = Disposable.None; private focusedRepository: ISCMRepository | undefined = undefined; private focusedProviderContextKey: IContextKey; private disposables: IDisposable[] = []; diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index 5e3a84b8862..b369614adb0 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -12,7 +12,7 @@ import { Event, Emitter, chain, mapEvent, anyEvent, filterEvent, latch } from 'v import { domEvent, stop } from 'vs/base/browser/event'; import { basename } from 'vs/base/common/paths'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { IDisposable, dispose, combinedDisposable, empty as EmptyDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, combinedDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { PanelViewlet, ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; import { append, $, addClass, toggleClass, trackFocus, Dimension, addDisposableListener } from 'vs/base/browser/dom'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -142,7 +142,7 @@ class ProviderRenderer implements IRenderer new StatusBarActionItem(a as StatusBarAction) }); - const disposable = EmptyDisposable; + const disposable = Disposable.None; const templateDisposable = combinedDisposable([actionBar, badgeStyler]); return { title, type, countContainer, count, actionBar, disposable, templateDisposable }; @@ -389,7 +389,7 @@ class ResourceGroupRenderer implements IRenderer { @@ -501,7 +501,7 @@ class ResourceRenderer implements IRenderer { const decorationIcon = append(element, $('.decoration-icon')); return { - element, name, fileLabel, decorationIcon, actionBar, elementDisposable: EmptyDisposable, dispose: () => { + element, name, fileLabel, decorationIcon, actionBar, elementDisposable: Disposable.None, dispose: () => { actionBar.dispose(); fileLabel.dispose(); } @@ -1019,10 +1019,10 @@ export class SCMViewlet extends PanelViewlet implements IViewModel, IViewsViewle private menus: SCMMenus; private mainPanel: MainPanel | null = null; private cachedMainPanelHeight: number | undefined; - private mainPanelDisposable: IDisposable = EmptyDisposable; + private mainPanelDisposable: IDisposable = Disposable.None; private _repositories: ISCMRepository[] = []; private repositoryPanels: RepositoryPanel[] = []; - private singlePanelTitleActionsDisposable: IDisposable = EmptyDisposable; + private singlePanelTitleActionsDisposable: IDisposable = Disposable.None; private disposables: IDisposable[] = []; private lastFocusedRepository: ISCMRepository | undefined; @@ -1152,7 +1152,7 @@ export class SCMViewlet extends PanelViewlet implements IViewModel, IViewsViewle }); } else { this.mainPanelDisposable.dispose(); - this.mainPanelDisposable = EmptyDisposable; + this.mainPanelDisposable = Disposable.None; this.mainPanel = null; } } diff --git a/src/vs/workbench/parts/update/electron-browser/update.ts b/src/vs/workbench/parts/update/electron-browser/update.ts index 49cd65734ba..dd4c68f0af4 100644 --- a/src/vs/workbench/parts/update/electron-browser/update.ts +++ b/src/vs/workbench/parts/update/electron-browser/update.ts @@ -9,7 +9,7 @@ import * as nls from 'vs/nls'; import severity from 'vs/base/common/severity'; import { TPromise } from 'vs/base/common/winjs.base'; import { IAction, Action } from 'vs/base/common/actions'; -import { IDisposable, dispose, empty as EmptyDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import pkg from 'vs/platform/node/package'; import product from 'vs/platform/node/product'; @@ -235,7 +235,7 @@ export class UpdateContribution implements IGlobalActivity { get cssClass() { return 'update-activity'; } private state: UpdateState; - private badgeDisposable: IDisposable = EmptyDisposable; + private badgeDisposable: IDisposable = Disposable.None; private disposables: IDisposable[] = []; constructor( From 9b0d484e1a35fee8c78f2d1d96c98804c8ea9cbd Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 3 Jul 2018 12:49:48 +0200 Subject: [PATCH 104/283] update inno_updater --- build/win32/OSSREADME.json | 60 +++++++++++++++++------------------ build/win32/inno_updater.exe | Bin 412672 -> 410624 bytes 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/build/win32/OSSREADME.json b/build/win32/OSSREADME.json index e9c1e331135..6e9bb4b251a 100755 --- a/build/win32/OSSREADME.json +++ b/build/win32/OSSREADME.json @@ -545,33 +545,6 @@ ], "isProd": true }, - { - "name": "retep998/winapi-rs", - "version": "0.4.0", - "repositoryUrl": "https://github.com/retep998/winapi-rs", - "licenseDetail": [ - "Copyright (c) 2015 The winapi-rs Developers", - "", - "Permission is hereby granted, free of charge, to any person obtaining a copy", - "of this software and associated documentation files (the \"Software\"), to deal", - "in the Software without restriction, including without limitation the rights", - "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", - "copies of the Software, and to permit persons to whom the Software is", - "furnished to do so, subject to the following conditions:", - "", - "The above copyright notice and this permission notice shall be included in all", - "copies or substantial portions of the Software.", - "", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", - "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", - "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", - "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", - "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", - "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", - "SOFTWARE." - ], - "isProd": true - }, { "name": "retep998/winapi-rs", "version": "0.2.8", @@ -601,7 +574,7 @@ }, { "name": "retep998/winapi-rs", - "version": "0.2.2", + "version": "0.1.1", "repositoryUrl": "https://github.com/retep998/winapi-rs", "licenseDetail": [ "Copyright (c) 2015 The winapi-rs Developers", @@ -655,7 +628,34 @@ }, { "name": "retep998/winapi-rs", - "version": "0.1.1", + "version": "0.4.0", + "repositoryUrl": "https://github.com/retep998/winapi-rs", + "licenseDetail": [ + "Copyright (c) 2015 The winapi-rs Developers", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy", + "of this software and associated documentation files (the \"Software\"), to deal", + "in the Software without restriction, including without limitation the rights", + "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", + "copies of the Software, and to permit persons to whom the Software is", + "furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in all", + "copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", + "SOFTWARE." + ], + "isProd": true + }, + { + "name": "retep998/winapi-rs", + "version": "0.2.2", "repositoryUrl": "https://github.com/retep998/winapi-rs", "licenseDetail": [ "Copyright (c) 2015 The winapi-rs Developers", @@ -1791,4 +1791,4 @@ ], "isProd": true } -] +] \ No newline at end of file diff --git a/build/win32/inno_updater.exe b/build/win32/inno_updater.exe index 31e6e85ffe481c60d7c15d8bca264723694d1e2e..d2715b864f566481e12c10ecca4d13f22d68afb5 100755 GIT binary patch delta 140710 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 Njc%2!g&m;F{x1_ytE~V4 delta 143023 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
>Ft?5 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 Date: Tue, 3 Jul 2018 12:59:40 +0200 Subject: [PATCH 105/283] cleanup more disposable usage --- src/vs/base/common/lifecycle.ts | 11 +- .../parts/debug/browser/debugStatus.ts | 2 +- .../electron-browser/webviewElement.ts | 110 +++++++++--------- .../workbench/test/workbenchTestServices.ts | 4 +- 4 files changed, 59 insertions(+), 68 deletions(-) diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index fdb344181c6..f2a5cf2da1d 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -20,7 +20,6 @@ export function dispose(disposable: T): T; export function dispose(...disposables: T[]): T[]; export function dispose(disposables: T[]): T[]; export function dispose(first: T | T[], ...rest: T[]): T | T[] { - if (Array.isArray(first)) { first.forEach(d => d && d.dispose()); return []; @@ -41,14 +40,8 @@ export function combinedDisposable(disposables: IDisposable[]): IDisposable { return { dispose: () => dispose(disposables) }; } -export function toDisposable(...fns: (() => void)[]): IDisposable { - return { - dispose() { - for (const fn of fns) { - fn(); - } - } - }; +export function toDisposable(fn: () => void): IDisposable { + return { dispose() { fn(); } }; } export abstract class Disposable implements IDisposable { diff --git a/src/vs/workbench/parts/debug/browser/debugStatus.ts b/src/vs/workbench/parts/debug/browser/debugStatus.ts index 970cce4f2cd..149c9bd8eb8 100644 --- a/src/vs/workbench/parts/debug/browser/debugStatus.ts +++ b/src/vs/workbench/parts/debug/browser/debugStatus.ts @@ -76,7 +76,7 @@ export class DebugStatus extends Themable implements IStatusbarItem { private doRender(): void { if (!this.statusBarItem && this.container) { this.statusBarItem = dom.append(this.container, $('.debug-statusbar-item')); - this.toDispose.push(dom.addDisposableListener(this.statusBarItem, 'click', () => { + this._register(dom.addDisposableListener(this.statusBarItem, 'click', () => { this.quickOpenService.show('debug ').done(undefined, errors.onUnexpectedError); })); this.statusBarItem.title = nls.localize('selectAndStartDebug', "Select and start debug configuration"); diff --git a/src/vs/workbench/parts/webview/electron-browser/webviewElement.ts b/src/vs/workbench/parts/webview/electron-browser/webviewElement.ts index d5ee079299b..a97e8f8fd1c 100644 --- a/src/vs/workbench/parts/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/parts/webview/electron-browser/webviewElement.ts @@ -131,71 +131,69 @@ export class WebviewElement extends Disposable { })); } - this.toDispose.push( - addDisposableListener(this._webview, 'console-message', function (e: { level: number; message: string; line: number; sourceId: string; }) { - console.log(`[Embedded Page] ${e.message}`); - }), - addDisposableListener(this._webview, 'dom-ready', () => { - this.layout(); - }), - addDisposableListener(this._webview, 'crashed', () => { - console.error('embedded page crashed'); - }), - addDisposableListener(this._webview, 'ipc-message', (event) => { - switch (event.channel) { - case 'onmessage': - if (this._options.enableWrappedPostMessage && event.args && event.args.length) { - this._onMessage.fire(event.args[0]); - } - return; + this._register(addDisposableListener(this._webview, 'console-message', function (e: { level: number; message: string; line: number; sourceId: string; }) { + console.log(`[Embedded Page] ${e.message}`); + })); + this._register(addDisposableListener(this._webview, 'dom-ready', () => { + this.layout(); + })); + this._register(addDisposableListener(this._webview, 'crashed', () => { + console.error('embedded page crashed'); + })); + this._register(addDisposableListener(this._webview, 'ipc-message', (event) => { + switch (event.channel) { + case 'onmessage': + if (this._options.enableWrappedPostMessage && event.args && event.args.length) { + this._onMessage.fire(event.args[0]); + } + return; - case 'did-click-link': - let [uri] = event.args; - this._onDidClickLink.fire(URI.parse(uri)); - return; + case 'did-click-link': + let [uri] = event.args; + this._onDidClickLink.fire(URI.parse(uri)); + return; - case 'did-set-content': - this._webview.style.flex = ''; - this._webview.style.width = '100%'; - this._webview.style.height = '100%'; - this.layout(); - return; + case 'did-set-content': + this._webview.style.flex = ''; + this._webview.style.width = '100%'; + this._webview.style.height = '100%'; + this.layout(); + return; - case 'did-scroll': - if (event.args && typeof event.args[0] === 'number') { - this._onDidScroll.fire({ scrollYPercentage: event.args[0] }); - } - return; + case 'did-scroll': + if (event.args && typeof event.args[0] === 'number') { + this._onDidScroll.fire({ scrollYPercentage: event.args[0] }); + } + return; - case 'do-reload': - this.reload(); - return; + case 'do-reload': + this.reload(); + return; - case 'do-update-state': - this._state = event.args[0]; - this._onDidUpdateState.fire(this._state); - return; - } - }), - addDisposableListener(this._webview, 'focus', () => { - if (this._contextKey) { - this._contextKey.set(true); - } - }), - addDisposableListener(this._webview, 'blur', () => { - if (this._contextKey) { - this._contextKey.reset(); - } - }), - addDisposableListener(this._webview, 'devtools-opened', () => { - this._send('devtools-opened'); - }), - ); + case 'do-update-state': + this._state = event.args[0]; + this._onDidUpdateState.fire(this._state); + return; + } + })); + this._register(addDisposableListener(this._webview, 'focus', () => { + if (this._contextKey) { + this._contextKey.set(true); + } + })); + this._register(addDisposableListener(this._webview, 'blur', () => { + if (this._contextKey) { + this._contextKey.reset(); + } + })); + this._register(addDisposableListener(this._webview, 'devtools-opened', () => { + this._send('devtools-opened'); + })); this._webviewFindWidget = this._register(instantiationService.createInstance(WebviewFindWidget, this)); this.style(this._themeService.getTheme()); - this._themeService.onThemeChange(this.style, this, this.toDispose); + this._register(this._themeService.onThemeChange(this.style, this)); } public mountTo(parent: HTMLElement) { diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 954b62cfee8..9c15b408467 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -66,7 +66,7 @@ import { IExtensionService, ProfileSession, IExtensionsStatus, ExtensionPointCon import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IDecorationsService, IResourceDecorationChangeEvent, IDecoration, IDecorationData, IDecorationsProvider } from 'vs/workbench/services/decorations/browser/decorations'; -import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable, Disposable } from 'vs/base/common/lifecycle'; import { IEditorGroupsService, IEditorGroup, GroupsOrder, GroupsArrangement, GroupDirection, IAddGroupOptions, IMergeGroupOptions, IMoveEditorOptions, ICopyEditorOptions, IEditorReplacement, IGroupChangeEvent, EditorsOrder, IFindGroupScope, EditorGroupLayout } from 'vs/workbench/services/group/common/editorGroupsService'; import { IEditorService, IOpenEditorOverrideHandler } from 'vs/workbench/services/editor/common/editorService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; @@ -286,7 +286,7 @@ export function workbenchInstantiationService(): IInstantiationService { export class TestDecorationsService implements IDecorationsService { _serviceBrand: any; onDidChangeDecorations: Event = Event.None; - registerDecorationsProvider(provider: IDecorationsProvider): IDisposable { return toDisposable(); } + registerDecorationsProvider(provider: IDecorationsProvider): IDisposable { return Disposable.None; } getDecoration(uri: URI, includeChildren: boolean, overwrite?: IDecorationData): IDecoration { return void 0; } } From 3bf2a074fb23f399d4a27041dab2d016825458b1 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Tue, 3 Jul 2018 13:45:47 +0200 Subject: [PATCH 106/283] Reduce usage of winjs promise progress --- src/vs/base/common/async.ts | 2 +- src/vs/base/common/worker/simpleWorker.ts | 8 ++++---- src/vs/base/node/processes.ts | 4 ++-- src/vs/base/test/common/utils.ts | 12 +++--------- src/vs/base/test/common/winjs.promise.test.ts | 4 ++-- src/vs/editor/common/model/textModel.ts | 2 +- src/vs/editor/standalone/browser/colorizer.ts | 4 ++-- .../platform/integrity/node/integrityServiceImpl.ts | 2 +- .../services/extensions/node/extensionPoints.ts | 4 ++-- 9 files changed, 18 insertions(+), 24 deletions(-) diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index aa4da71edc6..985c5185c3a 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -352,7 +352,7 @@ export class Barrier { constructor() { this._isOpen = false; - this._promise = new TPromise((c, e, p) => { + this._promise = new TPromise((c, e) => { this._completePromise = c; }, () => { console.warn('You should really not try to cancel this ready promise!'); diff --git a/src/vs/base/common/worker/simpleWorker.ts b/src/vs/base/common/worker/simpleWorker.ts index ceaaa0ff541..8b0862a816d 100644 --- a/src/vs/base/common/worker/simpleWorker.ts +++ b/src/vs/base/common/worker/simpleWorker.ts @@ -91,7 +91,7 @@ class SimpleWorkerProtocol { c: null, e: null }; - let result = new TPromise((c, e, p) => { + let result = new TPromise((c, e) => { reply.c = c; reply.e = e; }, () => { @@ -232,7 +232,7 @@ export class SimpleWorkerClient extends Disposable { loaderConfiguration = (self).requirejs.s.contexts._.config; } - this._lazyProxy = new TPromise((c, e, p) => { + this._lazyProxy = new TPromise((c, e) => { lazyProxyFulfill = c; lazyProxyReject = e; }, () => { /* no cancel */ }); @@ -273,7 +273,7 @@ export class SimpleWorkerClient extends Disposable { } private _request(method: string, args: any[]): TPromise { - return new TPromise((c, e, p) => { + return new TPromise((c, e) => { this._onModuleLoaded.then(() => { this._protocol.sendMessage(method, args).then(c, e); }, e); @@ -363,7 +363,7 @@ export class SimpleWorkerServer { let cc: ValueCallback; let ee: ErrorCallback; - let r = new TPromise((c, e, p) => { + let r = new TPromise((c, e) => { cc = c; ee = e; }); diff --git a/src/vs/base/node/processes.ts b/src/vs/base/node/processes.ts index ea32b4dce2f..eb01c197f74 100644 --- a/src/vs/base/node/processes.ts +++ b/src/vs/base/node/processes.ts @@ -234,7 +234,7 @@ export abstract class AbstractProcess { if (this.cmd) { childProcess = cp.spawn(this.cmd, this.args, this.options); } else if (this.module) { - this.childProcessPromise = new TPromise((c, e, p) => { + this.childProcessPromise = new TPromise((c, e) => { fork(this.module, this.args, this.options, (error: any, childProcess: cp.ChildProcess) => { if (error) { e(error); @@ -323,7 +323,7 @@ export abstract class AbstractProcess { } private useExec(): TPromise { - return new TPromise((c, e, p) => { + return new TPromise((c, e) => { if (!this.shell || !Platform.isWindows) { c(false); } diff --git a/src/vs/base/test/common/utils.ts b/src/vs/base/test/common/utils.ts index 1875a6f6450..f0e948976fa 100644 --- a/src/vs/base/test/common/utils.ts +++ b/src/vs/base/test/common/utils.ts @@ -8,7 +8,7 @@ import * as errors from 'vs/base/common/errors'; import * as paths from 'vs/base/common/paths'; import URI from 'vs/base/common/uri'; -import { PPromise, ProgressCallback, TProgressCallback, TPromise, TValueCallback } from 'vs/base/common/winjs.base'; +import { PPromise, TProgressCallback, TPromise, TValueCallback } from 'vs/base/common/winjs.base'; export class DeferredTPromise extends TPromise { @@ -16,17 +16,15 @@ export class DeferredTPromise extends TPromise { private completeCallback: TValueCallback; private errorCallback: (err: any) => void; - private progressCallback: ProgressCallback; constructor() { let captured: any; - super((c, e, p) => { - captured = { c, e, p }; + super((c, e) => { + captured = { c, e }; }, () => this.oncancel()); this.canceled = false; this.completeCallback = captured.c; this.errorCallback = captured.e; - this.progressCallback = captured.p; } public complete(value: T) { @@ -37,10 +35,6 @@ export class DeferredTPromise extends TPromise { this.errorCallback(err); } - public progress(p: any) { - this.progressCallback(p); - } - private oncancel(): void { this.canceled = true; } diff --git a/src/vs/base/test/common/winjs.promise.test.ts b/src/vs/base/test/common/winjs.promise.test.ts index a9b3038b457..c8f0070d124 100644 --- a/src/vs/base/test/common/winjs.promise.test.ts +++ b/src/vs/base/test/common/winjs.promise.test.ts @@ -11,7 +11,7 @@ suite('WinJS and ES6 Promises', function () { test('Promise.resolve', function () { let resolveTPromise; - const tPromise = new winjs.Promise(function (c, e, p) { + const tPromise = new winjs.Promise((c, e) => { resolveTPromise = c; }); @@ -28,7 +28,7 @@ suite('WinJS and ES6 Promises', function () { test('new Promise', function () { let resolveTPromise; - const tPromise = new winjs.Promise(function (c, e, p) { + const tPromise = new winjs.Promise((c, e) => { resolveTPromise = c; }); diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 53dc47f82e1..1a4005d6a1b 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -46,7 +46,7 @@ export function createTextBufferFactory(text: string): model.ITextBufferFactory } export function createTextBufferFactoryFromStream(stream: IStringStream, filter?: (chunk: string) => string): TPromise { - return new TPromise((c, e, p) => { + return new TPromise((c, e) => { let done = false; let builder = createTextBufferBuilder(); diff --git a/src/vs/editor/standalone/browser/colorizer.ts b/src/vs/editor/standalone/browser/colorizer.ts index 298a4d7a0d5..1f5595800c8 100644 --- a/src/vs/editor/standalone/browser/colorizer.ts +++ b/src/vs/editor/standalone/browser/colorizer.ts @@ -42,7 +42,7 @@ export class Colorizer { let render = (str: string) => { domNode.innerHTML = str; }; - return this.colorize(modeService, text, mimeType, options).then(render, (err) => console.error(err), render); + return this.colorize(modeService, text, mimeType, options).then(render, (err) => console.error(err)); } private static _tokenizationSupportChangedPromise(language: string): TPromise { @@ -54,7 +54,7 @@ export class Colorizer { } }; - return new TPromise((c, e, p) => { + return new TPromise((c, e) => { listener = TokenizationRegistry.onDidChange((e) => { if (e.changedLanguages.indexOf(language) >= 0) { stopListening(); diff --git a/src/vs/platform/integrity/node/integrityServiceImpl.ts b/src/vs/platform/integrity/node/integrityServiceImpl.ts index 5a45065e013..0113966b1cc 100644 --- a/src/vs/platform/integrity/node/integrityServiceImpl.ts +++ b/src/vs/platform/integrity/node/integrityServiceImpl.ts @@ -133,7 +133,7 @@ export class IntegrityServiceImpl implements IIntegrityService { private _resolve(filename: string, expected: string): TPromise { let fileUri = URI.parse(require.toUrl(filename)); - return new TPromise((c, e, p) => { + return new TPromise((c, e) => { fs.readFile(fileUri.fsPath, (err, buff) => { if (err) { return e(err); diff --git a/src/vs/workbench/services/extensions/node/extensionPoints.ts b/src/vs/workbench/services/extensions/node/extensionPoints.ts index 1d00242b57a..90cfe42920d 100644 --- a/src/vs/workbench/services/extensions/node/extensionPoints.ts +++ b/src/vs/workbench/services/extensions/node/extensionPoints.ts @@ -208,7 +208,7 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler { * Parses original message bundle, returns null if the original message bundle is null. */ private static resolveOriginalMessageBundle(originalMessageBundle: string, errors: json.ParseError[]) { - return new TPromise<{ [key: string]: string; }>((c, e, p) => { + return new TPromise<{ [key: string]: string; }>((c, e) => { if (originalMessageBundle) { pfs.readFile(originalMessageBundle).then(originalBundleContent => { c(json.parse(originalBundleContent.toString(), errors)); @@ -226,7 +226,7 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler { * If the localized file is not present, returns null for the original and marks original as localized. */ private static findMessageBundles(nlsConfig: NlsConfiguration, basename: string): TPromise<{ localized: string, original: string }> { - return new TPromise<{ localized: string, original: string }>((c, e, p) => { + return new TPromise<{ localized: string, original: string }>((c, e) => { function loop(basename: string, locale: string): void { let toCheck = `${basename}.nls.${locale}.json`; pfs.fileExists(toCheck).then(exists => { From b778e7f53862ced7d70af70ee0d34fe5bf441966 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 3 Jul 2018 15:18:06 +0200 Subject: [PATCH 107/283] debt - add switch to append the ellased-time to a file and then shutdown --- .../environment/common/environment.ts | 1 + .../performance.contribution.ts | 1 + .../startupTimingsAppender.ts | 49 +++++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 src/vs/workbench/parts/performance/electron-browser/startupTimingsAppender.ts diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 06f80764726..99792a19f00 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -25,6 +25,7 @@ export interface ParsedArgs { performance?: boolean; 'prof-startup'?: string; 'prof-startup-prefix'?: string; + 'prof-append-timers'?: string; verbose?: boolean; log?: string; logExtensionHostCommunication?: boolean; diff --git a/src/vs/workbench/parts/performance/electron-browser/performance.contribution.ts b/src/vs/workbench/parts/performance/electron-browser/performance.contribution.ts index 771485b80f1..8c55379ff49 100644 --- a/src/vs/workbench/parts/performance/electron-browser/performance.contribution.ts +++ b/src/vs/workbench/parts/performance/electron-browser/performance.contribution.ts @@ -7,4 +7,5 @@ import './startupProfiler'; import './startupTimings'; +import './startupTimingsAppender'; import './stats'; diff --git a/src/vs/workbench/parts/performance/electron-browser/startupTimingsAppender.ts b/src/vs/workbench/parts/performance/electron-browser/startupTimingsAppender.ts new file mode 100644 index 00000000000..04ed3bb7d25 --- /dev/null +++ b/src/vs/workbench/parts/performance/electron-browser/startupTimingsAppender.ts @@ -0,0 +1,49 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { IWorkbenchContributionsRegistry, IWorkbenchContribution, Extensions } from 'vs/workbench/common/contributions'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { ITimerService } from 'vs/workbench/services/timer/common/timerService'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { nfcall } from 'vs/base/common/async'; +import { appendFile } from 'fs'; + +class StartupTimingsAppender implements IWorkbenchContribution { + + constructor( + @ITimerService timerService: ITimerService, + @IWindowsService windowsService: IWindowsService, + @ILifecycleService lifecycleService: ILifecycleService, + @IExtensionService extensionService: IExtensionService, + @IEnvironmentService environmentService: IEnvironmentService, + ) { + + let appendTo = environmentService.args['prof-append-timers']; + if (!appendTo) { + return; + } + + Promise.all([ + lifecycleService.when(LifecyclePhase.Eventually), + extensionService.whenInstalledExtensionsRegistered() + ]).then(() => { + const { startupMetrics } = timerService; + return nfcall(appendFile, appendTo, `${Date.now()}\t${startupMetrics.ellapsed}\n`); + }).then(() => { + windowsService.quit(); + }).catch(err => { + console.error(err); + windowsService.quit(); + }); + } +} + +const registry = Registry.as(Extensions.Workbench); +registry.registerWorkbenchContribution(StartupTimingsAppender, LifecyclePhase.Running); From 560c87ffd391dcdf52a24e00ea4262fccff74955 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Tue, 3 Jul 2018 15:52:10 +0200 Subject: [PATCH 108/283] Fixes #53152 --- src/vs/editor/common/config/editorOptions.ts | 2 +- src/vs/editor/contrib/contextmenu/contextmenu.ts | 4 +++- src/vs/monaco.d.ts | 2 +- .../electron-browser/debugEditorContribution.ts | 13 ++++++++++--- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 2189eaf348e..2ea6bc0e971 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -397,7 +397,7 @@ export interface IEditorOptions { /** * Configure the editor's hover. */ - hover?: boolean | IEditorHoverOptions; + hover?: IEditorHoverOptions; /** * Enable detecting links and making them clickable. * Defaults to true. diff --git a/src/vs/editor/contrib/contextmenu/contextmenu.ts b/src/vs/editor/contrib/contextmenu/contextmenu.ts index abdc0f2abd0..a3467f32a75 100644 --- a/src/vs/editor/contrib/contextmenu/contextmenu.ts +++ b/src/vs/editor/contrib/contextmenu/contextmenu.ts @@ -142,7 +142,9 @@ export class ContextMenuController implements IEditorContribution { // Disable hover const oldHoverSetting = this._editor.getConfiguration().contribInfo.hover; this._editor.updateOptions({ - hover: false + hover: { + enabled: false + } }); let menuPosition = forcedPosition; diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index cb68bdb514e..f46a6a4bbd2 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2736,7 +2736,7 @@ declare namespace monaco.editor { /** * Configure the editor's hover. */ - hover?: boolean | IEditorHoverOptions; + hover?: IEditorHoverOptions; /** * Enable detecting links and making them clickable. * Defaults to true. diff --git a/src/vs/workbench/parts/debug/electron-browser/debugEditorContribution.ts b/src/vs/workbench/parts/debug/electron-browser/debugEditorContribution.ts index 60d0dda6202..992050fd338 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugEditorContribution.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugEditorContribution.ts @@ -46,6 +46,7 @@ import { ContextSubMenu } from 'vs/base/browser/contextmenu'; import { memoize } from 'vs/base/common/decorators'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { getHover } from 'vs/editor/contrib/hover/getHover'; +import { IEditorHoverOptions } from 'vs/editor/common/config/editorOptions'; const HOVER_DELAY = 300; const LAUNCH_JSON_REGEX = /launch\.json$/; @@ -281,7 +282,9 @@ export class DebugEditorContribution implements IDebugEditorContribution { private _applyHoverConfiguration(model: ITextModel, stackFrame: IStackFrame): void { if (stackFrame && model && model.uri.toString() === stackFrame.source.uri.toString()) { this.editor.updateOptions({ - hover: !stackFrame || !model || model.uri.toString() !== stackFrame.source.uri.toString() + hover: { + enabled: !stackFrame || !model || model.uri.toString() !== stackFrame.source.uri.toString() + } }); } else { let overrides: IConfigurationOverrides; @@ -291,9 +294,13 @@ export class DebugEditorContribution implements IDebugEditorContribution { overrideIdentifier: model.getLanguageIdentifier().language }; } - const defaultConfiguration = this.configurationService.getValue('editor.hover', overrides); + const defaultConfiguration = this.configurationService.getValue('editor.hover', overrides); this.editor.updateOptions({ - hover: defaultConfiguration + hover: { + enabled: defaultConfiguration.enabled, + delay: defaultConfiguration.delay, + sticky: defaultConfiguration.sticky + } }); } } From 21991b49126c795a07c7c105d94a4c02bdc9098f Mon Sep 17 00:00:00 2001 From: Dirk Baeumer Date: Tue, 3 Jul 2018 16:12:20 +0200 Subject: [PATCH 109/283] Move to latest vscode-nls for corrupted cache support --- extensions/configuration-editing/package.json | 2 +- extensions/configuration-editing/yarn.lock | 6 +++--- extensions/css-language-features/package.json | 2 +- extensions/css-language-features/yarn.lock | 6 +++--- extensions/emmet/package.json | 2 +- extensions/emmet/yarn.lock | 6 +++--- extensions/extension-editing/package.json | 2 +- extensions/extension-editing/yarn.lock | 6 +++--- extensions/git/package.json | 2 +- extensions/git/yarn.lock | 6 +++--- extensions/grunt/package.json | 2 +- extensions/grunt/yarn.lock | 6 +++--- extensions/gulp/package.json | 2 +- extensions/gulp/yarn.lock | 6 +++--- extensions/html-language-features/package.json | 2 +- extensions/html-language-features/server/package.json | 2 +- extensions/html-language-features/server/yarn.lock | 4 ++++ extensions/html-language-features/yarn.lock | 6 +++--- extensions/jake/package.json | 2 +- extensions/jake/yarn.lock | 6 +++--- extensions/json-language-features/package.json | 2 +- extensions/json-language-features/server/package.json | 2 +- extensions/json-language-features/server/yarn.lock | 4 ++++ extensions/json-language-features/yarn.lock | 6 +++--- extensions/markdown-language-features/package.json | 2 +- extensions/markdown-language-features/yarn.lock | 6 +++--- extensions/merge-conflict/package.json | 2 +- extensions/merge-conflict/yarn.lock | 6 +++--- extensions/npm/package.json | 2 +- extensions/npm/yarn.lock | 6 +++--- extensions/php-language-features/package.json | 2 +- extensions/php-language-features/yarn.lock | 6 +++--- extensions/search-rg/package.json | 2 +- extensions/search-rg/yarn.lock | 6 +++--- extensions/typescript-language-features/package.json | 2 +- extensions/typescript-language-features/yarn.lock | 6 +++--- 36 files changed, 74 insertions(+), 66 deletions(-) diff --git a/extensions/configuration-editing/package.json b/extensions/configuration-editing/package.json index e66a313c9f6..f63d8c84087 100644 --- a/extensions/configuration-editing/package.json +++ b/extensions/configuration-editing/package.json @@ -18,7 +18,7 @@ }, "dependencies": { "jsonc-parser": "^1.0.0", - "vscode-nls": "^3.2.1" + "vscode-nls": "^3.2.4" }, "contributes": { "languages": [ diff --git a/extensions/configuration-editing/yarn.lock b/extensions/configuration-editing/yarn.lock index 29d3d43ae80..bcf4ddb12d8 100644 --- a/extensions/configuration-editing/yarn.lock +++ b/extensions/configuration-editing/yarn.lock @@ -10,6 +10,6 @@ jsonc-parser@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-1.0.0.tgz#ddcc864ae708e60a7a6dd36daea00172fa8d9272" -vscode-nls@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.1.tgz#b1f3e04e8a94a715d5a7bcbc8339c51e6d74ca51" +vscode-nls@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398" diff --git a/extensions/css-language-features/package.json b/extensions/css-language-features/package.json index 9bfc56b2e3f..dc78673bf07 100644 --- a/extensions/css-language-features/package.json +++ b/extensions/css-language-features/package.json @@ -709,7 +709,7 @@ "dependencies": { "vscode-languageclient": "^4.1.4", "vscode-languageserver-protocol-foldingprovider": "^2.0.1", - "vscode-nls": "^3.2.2" + "vscode-nls": "^3.2.4" }, "devDependencies": { "@types/node": "7.0.43", diff --git a/extensions/css-language-features/yarn.lock b/extensions/css-language-features/yarn.lock index 840422a34db..ac76cfede80 100644 --- a/extensions/css-language-features/yarn.lock +++ b/extensions/css-language-features/yarn.lock @@ -161,9 +161,9 @@ vscode-languageserver-types@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.7.2.tgz#aad8846f8e3e27962648554de5a8417e358f34eb" -vscode-nls@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.2.tgz#3817eca5b985c2393de325197cf4e15eb2aa5350" +vscode-nls@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398" wrappy@1: version "1.0.2" diff --git a/extensions/emmet/package.json b/extensions/emmet/package.json index 397a5a35a09..406b25c97d2 100644 --- a/extensions/emmet/package.json +++ b/extensions/emmet/package.json @@ -449,6 +449,6 @@ "image-size": "^0.5.2", "vscode-emmet-helper": "^1.2.10", "vscode-languageserver-types": "^3.5.0", - "vscode-nls": "3.2.1" + "vscode-nls": "3.2.4" } } diff --git a/extensions/emmet/yarn.lock b/extensions/emmet/yarn.lock index 35b7ef8973f..040417ab779 100644 --- a/extensions/emmet/yarn.lock +++ b/extensions/emmet/yarn.lock @@ -2131,9 +2131,9 @@ vscode-languageserver-types@^3.6.0-next.1: version "3.6.0-next.1" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.6.0-next.1.tgz#98e488d3f87b666b4ee1a3d89f0023e246d358f3" -vscode-nls@3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.1.tgz#b1f3e04e8a94a715d5a7bcbc8339c51e6d74ca51" +vscode-nls@3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398" vscode@1.0.1: version "1.0.1" diff --git a/extensions/extension-editing/package.json b/extensions/extension-editing/package.json index e5c502426c0..88b339023b2 100644 --- a/extensions/extension-editing/package.json +++ b/extensions/extension-editing/package.json @@ -21,7 +21,7 @@ "jsonc-parser": "^1.0.0", "markdown-it": "^8.3.1", "parse5": "^3.0.2", - "vscode-nls": "^3.2.1" + "vscode-nls": "^3.2.4" }, "contributes": { "jsonValidation": [ diff --git a/extensions/extension-editing/yarn.lock b/extensions/extension-editing/yarn.lock index 350fdcb5fe3..cdc02d1e035 100644 --- a/extensions/extension-editing/yarn.lock +++ b/extensions/extension-editing/yarn.lock @@ -58,6 +58,6 @@ uc.micro@^1.0.1, uc.micro@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.3.tgz#7ed50d5e0f9a9fb0a573379259f2a77458d50192" -vscode-nls@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.1.tgz#b1f3e04e8a94a715d5a7bcbc8339c51e6d74ca51" +vscode-nls@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398" diff --git a/extensions/git/package.json b/extensions/git/package.json index d4a9ebb0d58..9a723045468 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1140,7 +1140,7 @@ "iconv-lite": "0.4.19", "jschardet": "^1.6.0", "vscode-extension-telemetry": "0.0.17", - "vscode-nls": "^3.2.1", + "vscode-nls": "^3.2.4", "which": "^1.3.0" }, "devDependencies": { diff --git a/extensions/git/yarn.lock b/extensions/git/yarn.lock index 3fb8a6f087a..d8a4d64e743 100644 --- a/extensions/git/yarn.lock +++ b/extensions/git/yarn.lock @@ -263,9 +263,9 @@ vscode-extension-telemetry@0.0.17: dependencies: applicationinsights "1.0.1" -vscode-nls@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.1.tgz#b1f3e04e8a94a715d5a7bcbc8339c51e6d74ca51" +vscode-nls@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398" which@^1.3.0: version "1.3.0" diff --git a/extensions/grunt/package.json b/extensions/grunt/package.json index cfc88d5dea3..18c5c21e0cb 100644 --- a/extensions/grunt/package.json +++ b/extensions/grunt/package.json @@ -16,7 +16,7 @@ "watch": "gulp watch-extension:grunt" }, "dependencies": { - "vscode-nls": "^3.2.1" + "vscode-nls": "^3.2.4" }, "devDependencies": { "@types/node": "7.0.43" diff --git a/extensions/grunt/yarn.lock b/extensions/grunt/yarn.lock index 112e5f2ac8d..573098d7d26 100644 --- a/extensions/grunt/yarn.lock +++ b/extensions/grunt/yarn.lock @@ -6,6 +6,6 @@ version "7.0.43" resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.43.tgz#a187e08495a075f200ca946079c914e1a5fe962c" -vscode-nls@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.1.tgz#b1f3e04e8a94a715d5a7bcbc8339c51e6d74ca51" +vscode-nls@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398" diff --git a/extensions/gulp/package.json b/extensions/gulp/package.json index bc53fe127f7..65d9197a0d9 100644 --- a/extensions/gulp/package.json +++ b/extensions/gulp/package.json @@ -16,7 +16,7 @@ "watch": "gulp watch-extension:gulp" }, "dependencies": { - "vscode-nls": "^3.2.1" + "vscode-nls": "^3.2.4" }, "devDependencies": { "@types/node": "7.0.43" diff --git a/extensions/gulp/yarn.lock b/extensions/gulp/yarn.lock index 112e5f2ac8d..573098d7d26 100644 --- a/extensions/gulp/yarn.lock +++ b/extensions/gulp/yarn.lock @@ -6,6 +6,6 @@ version "7.0.43" resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.43.tgz#a187e08495a075f200ca946079c914e1a5fe962c" -vscode-nls@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.1.tgz#b1f3e04e8a94a715d5a7bcbc8339c51e6d74ca51" +vscode-nls@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398" diff --git a/extensions/html-language-features/package.json b/extensions/html-language-features/package.json index 5781a2d2faa..9ac18e38fbb 100644 --- a/extensions/html-language-features/package.json +++ b/extensions/html-language-features/package.json @@ -176,7 +176,7 @@ "vscode-extension-telemetry": "0.0.17", "vscode-languageclient": "^4.1.4", "vscode-languageserver-protocol-foldingprovider": "^2.0.1", - "vscode-nls": "^3.2.2" + "vscode-nls": "^3.2.4" }, "devDependencies": { "@types/node": "7.0.43" diff --git a/extensions/html-language-features/server/package.json b/extensions/html-language-features/server/package.json index 3ef45e9eff9..e9586c7ad34 100644 --- a/extensions/html-language-features/server/package.json +++ b/extensions/html-language-features/server/package.json @@ -13,7 +13,7 @@ "vscode-languageserver": "^4.1.3", "vscode-languageserver-protocol-foldingprovider": "^2.0.1", "vscode-languageserver-types": "^3.7.2", - "vscode-nls": "^3.2.2", + "vscode-nls": "^3.2.4", "vscode-uri": "^1.0.3" }, "devDependencies": { diff --git a/extensions/html-language-features/server/yarn.lock b/extensions/html-language-features/server/yarn.lock index 25d446165f1..32e736c53c2 100644 --- a/extensions/html-language-features/server/yarn.lock +++ b/extensions/html-language-features/server/yarn.lock @@ -242,6 +242,10 @@ vscode-nls@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.2.tgz#3817eca5b985c2393de325197cf4e15eb2aa5350" +vscode-nls@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398" + vscode-uri@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-1.0.1.tgz#11a86befeac3c4aa3ec08623651a3c81a6d0bbc8" diff --git a/extensions/html-language-features/yarn.lock b/extensions/html-language-features/yarn.lock index 36c0c89cd28..1e7214749f2 100644 --- a/extensions/html-language-features/yarn.lock +++ b/extensions/html-language-features/yarn.lock @@ -62,9 +62,9 @@ vscode-languageserver-types@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.7.2.tgz#aad8846f8e3e27962648554de5a8417e358f34eb" -vscode-nls@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.2.tgz#3817eca5b985c2393de325197cf4e15eb2aa5350" +vscode-nls@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398" zone.js@0.7.6: version "0.7.6" diff --git a/extensions/jake/package.json b/extensions/jake/package.json index 2330f353026..6befa9fbd9e 100644 --- a/extensions/jake/package.json +++ b/extensions/jake/package.json @@ -16,7 +16,7 @@ "watch": "gulp watch-extension:jake" }, "dependencies": { - "vscode-nls": "^3.2.1" + "vscode-nls": "^3.2.4" }, "devDependencies": { "@types/node": "7.0.43" diff --git a/extensions/jake/yarn.lock b/extensions/jake/yarn.lock index 112e5f2ac8d..573098d7d26 100644 --- a/extensions/jake/yarn.lock +++ b/extensions/jake/yarn.lock @@ -6,6 +6,6 @@ version "7.0.43" resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.43.tgz#a187e08495a075f200ca946079c914e1a5fe962c" -vscode-nls@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.1.tgz#b1f3e04e8a94a715d5a7bcbc8339c51e6d74ca51" +vscode-nls@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398" diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index 2e245fdafd8..8ea4b2f3d21 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -103,7 +103,7 @@ "vscode-extension-telemetry": "0.0.17", "vscode-languageclient": "^4.1.4", "vscode-languageserver-protocol-foldingprovider": "^2.0.1", - "vscode-nls": "^3.2.2" + "vscode-nls": "^3.2.4" }, "devDependencies": { "@types/node": "7.0.43" diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index 532345f51ee..2f67cd91401 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -16,7 +16,7 @@ "vscode-json-languageservice": "^3.1.2", "vscode-languageserver": "^4.1.3", "vscode-languageserver-protocol-foldingprovider": "^2.0.1", - "vscode-nls": "^3.2.2", + "vscode-nls": "^3.2.4", "vscode-uri": "^1.0.3" }, "devDependencies": { diff --git a/extensions/json-language-features/server/yarn.lock b/extensions/json-language-features/server/yarn.lock index babef1f7177..d9b5875b9bc 100644 --- a/extensions/json-language-features/server/yarn.lock +++ b/extensions/json-language-features/server/yarn.lock @@ -118,6 +118,10 @@ vscode-nls@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.2.tgz#3817eca5b985c2393de325197cf4e15eb2aa5350" +vscode-nls@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398" + vscode-uri@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-1.0.1.tgz#11a86befeac3c4aa3ec08623651a3c81a6d0bbc8" diff --git a/extensions/json-language-features/yarn.lock b/extensions/json-language-features/yarn.lock index 36c0c89cd28..1e7214749f2 100644 --- a/extensions/json-language-features/yarn.lock +++ b/extensions/json-language-features/yarn.lock @@ -62,9 +62,9 @@ vscode-languageserver-types@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.7.2.tgz#aad8846f8e3e27962648554de5a8417e358f34eb" -vscode-nls@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.2.tgz#3817eca5b985c2393de325197cf4e15eb2aa5350" +vscode-nls@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398" zone.js@0.7.6: version "0.7.6" diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index f7115786331..984967f637f 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -296,7 +296,7 @@ "markdown-it": "^8.4.1", "markdown-it-named-headers": "0.0.4", "vscode-extension-telemetry": "0.0.17", - "vscode-nls": "^3.2.1" + "vscode-nls": "^3.2.4" }, "devDependencies": { "@types/highlight.js": "9.1.10", diff --git a/extensions/markdown-language-features/yarn.lock b/extensions/markdown-language-features/yarn.lock index 18ac5da44ed..c44b9715579 100644 --- a/extensions/markdown-language-features/yarn.lock +++ b/extensions/markdown-language-features/yarn.lock @@ -5462,9 +5462,9 @@ vscode-extension-telemetry@0.0.17: dependencies: applicationinsights "1.0.1" -vscode-nls@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.1.tgz#b1f3e04e8a94a715d5a7bcbc8339c51e6d74ca51" +vscode-nls@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398" vscode@^1.1.10: version "1.1.10" diff --git a/extensions/merge-conflict/package.json b/extensions/merge-conflict/package.json index f58bb6f2a90..81b65c21ac6 100644 --- a/extensions/merge-conflict/package.json +++ b/extensions/merge-conflict/package.json @@ -100,7 +100,7 @@ } }, "dependencies": { - "vscode-nls": "^3.2.1" + "vscode-nls": "^3.2.4" }, "devDependencies": { "@types/node": "8.0.33" diff --git a/extensions/merge-conflict/yarn.lock b/extensions/merge-conflict/yarn.lock index 355d778c63c..5b1dccc447c 100644 --- a/extensions/merge-conflict/yarn.lock +++ b/extensions/merge-conflict/yarn.lock @@ -6,6 +6,6 @@ version "8.0.33" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.33.tgz#1126e94374014e54478092830704f6ea89df04cd" -vscode-nls@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.1.tgz#b1f3e04e8a94a715d5a7bcbc8339c51e6d74ca51" +vscode-nls@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398" diff --git a/extensions/npm/package.json b/extensions/npm/package.json index f45037ec8e7..df9c903fcb4 100644 --- a/extensions/npm/package.json +++ b/extensions/npm/package.json @@ -20,7 +20,7 @@ "jsonc-parser": "^1.0.0", "minimatch": "^3.0.4", "request-light": "^0.2.2", - "vscode-nls": "^3.2.1" + "vscode-nls": "^3.2.4" }, "devDependencies": { "@types/minimatch": "^3.0.3", diff --git a/extensions/npm/yarn.lock b/extensions/npm/yarn.lock index 741a6fe9a22..9822a92a784 100644 --- a/extensions/npm/yarn.lock +++ b/extensions/npm/yarn.lock @@ -93,6 +93,6 @@ vscode-nls@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-2.0.2.tgz#808522380844b8ad153499af5c3b03921aea02da" -vscode-nls@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.1.tgz#b1f3e04e8a94a715d5a7bcbc8339c51e6d74ca51" +vscode-nls@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398" diff --git a/extensions/php-language-features/package.json b/extensions/php-language-features/package.json index dc2332a328f..e39073271f3 100644 --- a/extensions/php-language-features/package.json +++ b/extensions/php-language-features/package.json @@ -77,7 +77,7 @@ "watch": "gulp watch-extension:php" }, "dependencies": { - "vscode-nls": "^3.2.1" + "vscode-nls": "^3.2.4" }, "devDependencies": { "@types/node": "7.0.43" diff --git a/extensions/php-language-features/yarn.lock b/extensions/php-language-features/yarn.lock index 112e5f2ac8d..573098d7d26 100644 --- a/extensions/php-language-features/yarn.lock +++ b/extensions/php-language-features/yarn.lock @@ -6,6 +6,6 @@ version "7.0.43" resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.43.tgz#a187e08495a075f200ca946079c914e1a5fe962c" -vscode-nls@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.1.tgz#b1f3e04e8a94a715d5a7bcbc8339c51e6d74ca51" +vscode-nls@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398" diff --git a/extensions/search-rg/package.json b/extensions/search-rg/package.json index ce6a1668677..b149088fcf2 100644 --- a/extensions/search-rg/package.json +++ b/extensions/search-rg/package.json @@ -14,7 +14,7 @@ "categories": [], "dependencies": { "vscode-extension-telemetry": "0.0.15", - "vscode-nls": "^3.2.1", + "vscode-nls": "^3.2.4", "vscode-ripgrep": "^1.0.1" }, "devDependencies": { diff --git a/extensions/search-rg/yarn.lock b/extensions/search-rg/yarn.lock index 4eaab0b7bb3..99b491f01ff 100644 --- a/extensions/search-rg/yarn.lock +++ b/extensions/search-rg/yarn.lock @@ -1545,9 +1545,9 @@ vscode-extension-telemetry@0.0.15: dependencies: applicationinsights "1.0.1" -vscode-nls@^3.2.1: - version "3.2.2" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.2.tgz#3817eca5b985c2393de325197cf4e15eb2aa5350" +vscode-nls@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398" vscode-ripgrep@^1.0.1: version "1.0.1" diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index d94a54d8837..841024fd02b 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -18,7 +18,7 @@ "dependencies": { "semver": "4.3.6", "vscode-extension-telemetry": "0.0.17", - "vscode-nls": "^3.2.1" + "vscode-nls": "^3.2.4" }, "devDependencies": { "@types/node": "8.0.33", diff --git a/extensions/typescript-language-features/yarn.lock b/extensions/typescript-language-features/yarn.lock index b9c4b1eece7..39422f56063 100644 --- a/extensions/typescript-language-features/yarn.lock +++ b/extensions/typescript-language-features/yarn.lock @@ -1548,9 +1548,9 @@ vscode-extension-telemetry@0.0.17: dependencies: applicationinsights "1.0.1" -vscode-nls@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.1.tgz#b1f3e04e8a94a715d5a7bcbc8339c51e6d74ca51" +vscode-nls@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398" vscode@^1.1.10: version "1.1.17" From 1e25b497f7f451f3839ba4bb12af230471353319 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 3 Jul 2018 16:19:17 +0200 Subject: [PATCH 110/283] debt - use cancellation token for outline --- .../contrib/documentSymbols/outlineModel.ts | 35 ++++++++++--------- .../documentSymbols/test/outlineModel.test.ts | 17 +++++---- .../outline/electron-browser/outlinePanel.ts | 18 ++++++++-- 3 files changed, 44 insertions(+), 26 deletions(-) diff --git a/src/vs/editor/contrib/documentSymbols/outlineModel.ts b/src/vs/editor/contrib/documentSymbols/outlineModel.ts index f448469a22f..a2a8a2447f8 100644 --- a/src/vs/editor/contrib/documentSymbols/outlineModel.ts +++ b/src/vs/editor/contrib/documentSymbols/outlineModel.ts @@ -6,8 +6,6 @@ import { DocumentSymbolProviderRegistry, DocumentSymbolProvider, DocumentSymbol } from 'vs/editor/common/modes'; import { ITextModel } from 'vs/editor/common/model'; -import { asWinJsPromise } from 'vs/base/common/async'; -import { TPromise } from 'vs/base/common/winjs.base'; import { fuzzyScore, FuzzyScore } from 'vs/base/common/filters'; import { IPosition } from 'vs/editor/common/core/position'; import { Range, IRange } from 'vs/editor/common/core/range'; @@ -17,6 +15,7 @@ import { commonPrefixLength } from 'vs/base/common/strings'; import { IMarker, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { onUnexpectedExternalError } from 'vs/base/common/errors'; import { LRUCache } from 'vs/base/common/map'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; export abstract class TreeElement { abstract id: string; @@ -196,7 +195,7 @@ export class OutlineGroup extends TreeElement { export class OutlineModel extends TreeElement { - private static readonly _requests = new LRUCache, model: OutlineModel }>(9, .75); + private static readonly _requests = new LRUCache, model: OutlineModel }>(9, .75); private static readonly _keys = new class { private _counter = 1; @@ -221,15 +220,17 @@ export class OutlineModel extends TreeElement { }; - static create(textModel: ITextModel): TPromise { + static create(textModel: ITextModel, token: CancellationToken): Promise { let key = this._keys.for(textModel); let data = OutlineModel._requests.get(key); if (!data) { + let source = new CancellationTokenSource(); data = { promiseCnt: 0, - promise: OutlineModel._create(textModel), + source, + promise: OutlineModel._create(textModel, source.token), model: undefined, }; OutlineModel._requests.set(key, data); @@ -237,13 +238,21 @@ export class OutlineModel extends TreeElement { if (data.model) { // resolved -> return data - return TPromise.as(data.model); + return Promise.resolve(data.model); } // increase usage counter data.promiseCnt += 1; - return new TPromise((resolve, reject) => { + token.onCancellationRequested(() => { + // last -> cancel provider request, remove cached promise + if (--data.promiseCnt === 0) { + data.source.cancel(); + OutlineModel._requests.delete(key); + } + }); + + return new Promise((resolve, reject) => { data.promise.then(model => { data.model = model; resolve(model); @@ -251,16 +260,10 @@ export class OutlineModel extends TreeElement { OutlineModel._requests.delete(key); reject(err); }); - }, () => { - // last -> cancel provider request, remove cached promise - if (--data.promiseCnt === 0) { - data.promise.cancel(); - OutlineModel._requests.delete(key); - } }); } - static _create(textModel: ITextModel): TPromise { + static _create(textModel: ITextModel, token: CancellationToken): Promise { let result = new OutlineModel(textModel); let promises = DocumentSymbolProviderRegistry.ordered(textModel).map((provider, index) => { @@ -268,7 +271,7 @@ export class OutlineModel extends TreeElement { let id = TreeElement.findId(`provider_${index}`, result); let group = new OutlineGroup(id, result, provider, index); - return asWinJsPromise(token => provider.provideDocumentSymbols(result.textModel, token)).then(result => { + return Promise.resolve(provider.provideDocumentSymbols(result.textModel, token)).then(result => { if (!isFalsyOrEmpty(result)) { for (const info of result) { OutlineModel._makeOutlineElement(info, group); @@ -283,7 +286,7 @@ export class OutlineModel extends TreeElement { }); }); - return TPromise.join(promises).then(() => { + return Promise.all(promises).then(() => { let count = 0; for (const key in result._groups) { diff --git a/src/vs/editor/contrib/documentSymbols/test/outlineModel.test.ts b/src/vs/editor/contrib/documentSymbols/test/outlineModel.test.ts index bd34ffd3550..c07576219f8 100644 --- a/src/vs/editor/contrib/documentSymbols/test/outlineModel.test.ts +++ b/src/vs/editor/contrib/documentSymbols/test/outlineModel.test.ts @@ -12,6 +12,7 @@ import { Range } from 'vs/editor/common/core/range'; import { IMarker, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { TextModel } from 'vs/editor/common/model/textModel'; import URI from 'vs/base/common/uri'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; suite('OutlineModel', function () { @@ -26,16 +27,16 @@ suite('OutlineModel', function () { } }); - await OutlineModel.create(model); + await OutlineModel.create(model, CancellationToken.None); assert.equal(count, 1); // cached - await OutlineModel.create(model); + await OutlineModel.create(model, CancellationToken.None); assert.equal(count, 1); // new version model.applyEdits([{ text: 'XXX', range: new Range(1, 1, 1, 1) }]); - await OutlineModel.create(model); + await OutlineModel.create(model, CancellationToken.None); assert.equal(count, 2); reg.dispose(); @@ -58,13 +59,15 @@ suite('OutlineModel', function () { }); assert.equal(isCancelled, false); - let p1 = OutlineModel.create(model); - let p2 = OutlineModel.create(model); + let s1 = new CancellationTokenSource(); + OutlineModel.create(model, s1.token); + let s2 = new CancellationTokenSource(); + OutlineModel.create(model, s2.token); - p1.cancel(); + s1.cancel(); assert.equal(isCancelled, false); - p2.cancel(); + s2.cancel(); assert.equal(isCancelled, true); reg.dispose(); diff --git a/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts b/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts index fa4a2ed4c07..83d1491b256 100644 --- a/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts +++ b/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts @@ -13,8 +13,8 @@ import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { Action, IAction, RadioGroup } from 'vs/base/common/actions'; import { firstIndex } from 'vs/base/common/arrays'; -import { asDisposablePromise, setDisposableTimeout } from 'vs/base/common/async'; -import { onUnexpectedError } from 'vs/base/common/errors'; +import { asDisposablePromise, setDisposableTimeout, createCancelablePromise } from 'vs/base/common/async'; +import { onUnexpectedError, isPromiseCanceledError } from 'vs/base/common/errors'; import { Emitter } from 'vs/base/common/event'; import { defaultGenerator } from 'vs/base/common/idGenerator'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -54,6 +54,7 @@ import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron import { OutlineConfigKeys, OutlineViewFiltered, OutlineViewFocused, OutlineViewId } from './outline'; import { OutlineController, OutlineDataSource, OutlineItemComparator, OutlineItemCompareType, OutlineItemFilter, OutlineRenderer, OutlineTreeState } from '../../../../editor/contrib/documentSymbols/outlineTree'; import { IResourceInput } from 'vs/platform/editor/common/editor'; +import { ITextModel } from 'vs/editor/common/model'; class RequestState { @@ -440,6 +441,17 @@ export class OutlinePanel extends ViewletPanel { this._message.innerText = escape(message); } + private static _createOutlineModel(model: ITextModel, disposables: IDisposable[]): Promise { + let promise = createCancelablePromise(token => OutlineModel.create(model, token)); + disposables.push({ dispose() { promise.cancel(); } }); + return promise.catch(err => { + if (!isPromiseCanceledError(err)) { + throw err; + } + return undefined; + }); + } + private async _doUpdate(editor: ICodeEditor, event: IModelContentChangedEvent): Promise { dispose(this._editorDisposables); @@ -464,7 +476,7 @@ export class OutlinePanel extends ViewletPanel { ); } - let model = await asDisposablePromise(OutlineModel.create(textModel), undefined, this._editorDisposables).promise; + let model = await OutlinePanel._createOutlineModel(textModel, this._editorDisposables); dispose(loadingMessage); if (!model) { return; From 92f232a8d136b104d2808283b12cc679ed2679b3 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 3 Jul 2018 15:46:36 +0200 Subject: [PATCH 111/283] #53130 Do not use new chinese locale id everywhere --- .../localizations.contribution.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts b/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts index 2944b605a8c..489108b53d3 100644 --- a/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts +++ b/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts @@ -141,9 +141,9 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo return; } - const currentLocale = this.getPossibleChineseMapping(locale); - const ceintlExtensionSearch = this.galleryService.query({ names: [`MS-CEINTL.vscode-language-pack-${currentLocale}`], pageSize: 1 }); - const tagSearch = this.galleryService.query({ text: `tag:lp-${currentLocale}`, pageSize: 1 }); + const extensionIdPrefix = this.getPossibleChineseMapping(locale); + const ceintlExtensionSearch = this.galleryService.query({ names: [`MS-CEINTL.vscode-language-pack-${extensionIdPrefix}`], pageSize: 1 }); + const tagSearch = this.galleryService.query({ text: `tag:lp-${locale}`, pageSize: 1 }); TPromise.join([ceintlExtensionSearch, tagSearch]).then(([ceintlResult, tagResult]) => { if (ceintlResult.total === 0 && tagResult.total === 0) { @@ -157,11 +157,11 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo return; } - TPromise.join([this.galleryService.getManifest(extensionToFetchTranslationsFrom), this.galleryService.getCoreTranslation(extensionToFetchTranslationsFrom, currentLocale)]) + TPromise.join([this.galleryService.getManifest(extensionToFetchTranslationsFrom), this.galleryService.getCoreTranslation(extensionToFetchTranslationsFrom, locale)]) .then(([manifest, translation]) => { - const loc = manifest && manifest.contributes && manifest.contributes.localizations && manifest.contributes.localizations.filter(x => this.getPossibleChineseMapping(x.languageId) === currentLocale)[0]; - const languageName = loc ? (loc.languageName || currentLocale) : currentLocale; - const languageDisplayName = loc ? (loc.localizedLanguageName || loc.languageName || currentLocale) : currentLocale; + const loc = manifest && manifest.contributes && manifest.contributes.localizations && manifest.contributes.localizations.filter(x => x.languageId.toLowerCase() === locale)[0]; + const languageName = loc ? (loc.languageName || locale) : locale; + const languageDisplayName = loc ? (loc.localizedLanguageName || loc.languageName || locale) : locale; const translationsFromPack = translation && translation.contents ? translation.contents['vs/platform/node/minimalTranslations'] : {}; const promptMessageKey = extensionToInstall ? 'installAndRestartMessage' : 'showLanguagePackExtensions'; const useEnglish = !translationsFromPack[promptMessageKey]; @@ -192,7 +192,7 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo this.viewletService.openViewlet(EXTENSIONS_VIEWLET_ID, true) .then(viewlet => viewlet as IExtensionsViewlet) .then(viewlet => { - viewlet.search(`tag:lp-${currentLocale}`); + viewlet.search(`tag:lp-${locale}`); viewlet.focus(); }); } From cab872154e202c4ffbd8025dfe5fe0d84d469445 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 3 Jul 2018 15:53:04 +0200 Subject: [PATCH 112/283] #53130 :lipstick: --- .../electron-browser/localizations.contribution.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts b/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts index 489108b53d3..340f821e0f4 100644 --- a/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts +++ b/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts @@ -141,8 +141,8 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo return; } - const extensionIdPrefix = this.getPossibleChineseMapping(locale); - const ceintlExtensionSearch = this.galleryService.query({ names: [`MS-CEINTL.vscode-language-pack-${extensionIdPrefix}`], pageSize: 1 }); + const extensionIdPostfix = this.getPossibleChineseMapping(locale); + const ceintlExtensionSearch = this.galleryService.query({ names: [`MS-CEINTL.vscode-language-pack-${extensionIdPostfix}`], pageSize: 1 }); const tagSearch = this.galleryService.query({ text: `tag:lp-${locale}`, pageSize: 1 }); TPromise.join([ceintlExtensionSearch, tagSearch]).then(([ceintlResult, tagResult]) => { From a2d1ee7f6cce5b133245d45c2e603a4c9023eb9b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 3 Jul 2018 16:16:41 +0200 Subject: [PATCH 113/283] Fix #53426 - Check server locations instead of extension locations --- .../extensions/electron-browser/extensionsActions.ts | 5 ++++- .../parts/extensions/electron-browser/extensionsList.ts | 8 +++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts index 087b2553530..8af34dabf3b 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts @@ -1290,6 +1290,7 @@ export class ReloadAction extends Action { @IWindowService private windowService: IWindowService, @IExtensionService private extensionService: IExtensionService, @IExtensionEnablementService private extensionEnablementService: IExtensionEnablementService, + @IExtensionManagementServerService private extensionManagementServerService: IExtensionManagementServerService ) { super('extensions.reload', localize('reloadAction', "Reload"), ReloadAction.DisabledClass, false); this.throttler = new Throttler(); @@ -1325,7 +1326,9 @@ export class ReloadAction extends Action { if (installed && installed.local) { if (runningExtension) { - const isSameLocation = runningExtension.extensionLocation.toString() === installed.local.location.toString(); + const runningExtensionServer = this.extensionManagementServerService.getExtensionManagementServer(runningExtension.extensionLocation); + const installedExtensionServer = this.extensionManagementServerService.getExtensionManagementServer(installed.local.location); + const isSameLocation = runningExtensionServer.location.toString() === installedExtensionServer.location.toString(); if (isSameLocation) { const isDifferentVersionRunning = this.extension.version !== runningExtension.version; if (isDifferentVersionRunning && !isDisabled) { diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts index f90c3f99801..1f032611899 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts @@ -154,7 +154,13 @@ export class Renderer implements IPagedRenderer { const installed = this.extensionsWorkbenchService.local.filter(e => e.id === extension.id)[0]; this.extensionService.getExtensions().then(runningExtensions => { - toggleClass(data.root, 'disabled', installed && installed.local ? runningExtensions.every(e => !(installed.local.location.toString() === e.extensionLocation.toString() && areSameExtensions(e, extension))) : false); + if (installed && installed.local) { + const installedExtensionServer = this.extensionManagementServerService.getExtensionManagementServer(installed.local.location); + const isSameExtensionRunning = runningExtensions.some(e => areSameExtensions(e, extension) && installedExtensionServer.location.toString() === this.extensionManagementServerService.getExtensionManagementServer(e.extensionLocation).location.toString()); + toggleClass(data.root, 'disabled', !isSameExtensionRunning); + } else { + removeClass(data.root, 'disabled'); + } }); const onError = once(domEvent(data.icon, 'error')); From 918a23f2f284350fa8d19457c7f0eb02c01f9bd3 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 3 Jul 2018 16:30:41 +0200 Subject: [PATCH 114/283] debt - avoid forceOpen if possible and rename to forceReload --- src/vs/base/parts/tree/browser/treeDefaults.ts | 6 +++--- src/vs/platform/editor/common/editor.ts | 6 +++--- .../workbench/browser/parts/editor/editorControl.ts | 4 ++-- src/vs/workbench/common/editor.ts | 12 ++++++------ .../electron-browser/debugConfigurationManager.ts | 1 - .../extensions/electron-browser/extensionsActions.ts | 2 -- .../parts/files/browser/editors/fileEditorTracker.ts | 2 +- .../electron-browser/localizationsActions.ts | 5 +---- .../parts/outline/electron-browser/outlinePanel.ts | 3 +-- .../tasks/electron-browser/task.contribution.ts | 3 --- .../editor/test/browser/editorService.test.ts | 2 +- .../test/common/editor/editorOptions.test.ts | 10 +++++----- 12 files changed, 23 insertions(+), 33 deletions(-) diff --git a/src/vs/base/parts/tree/browser/treeDefaults.ts b/src/vs/base/parts/tree/browser/treeDefaults.ts index b5c4ca60e62..032e70e54b7 100644 --- a/src/vs/base/parts/tree/browser/treeDefaults.ts +++ b/src/vs/base/parts/tree/browser/treeDefaults.ts @@ -175,9 +175,9 @@ export class DefaultController implements _.IController { tree.clearFocus(payload); tree.clearSelection(payload); } else { - const isMouseDown = eventish && event.browserEvent && event.browserEvent.type === 'mousedown'; - if (!isMouseDown) { - eventish.preventDefault(); // we cannot preventDefault onMouseDown because this would break DND otherwise + const isSingleMouseDown = eventish && event.browserEvent && event.browserEvent.type === 'mousedown' && event.browserEvent.detail === 1; + if (!isSingleMouseDown) { + eventish.preventDefault(); // we cannot preventDefault onMouseDown with single click because this would break DND otherwise } eventish.stopPropagation(); diff --git a/src/vs/platform/editor/common/editor.ts b/src/vs/platform/editor/common/editor.ts index d35396a181a..1f110f99f40 100644 --- a/src/vs/platform/editor/common/editor.ts +++ b/src/vs/platform/editor/common/editor.ts @@ -66,11 +66,11 @@ export interface IEditorOptions { readonly preserveFocus?: boolean; /** - * Tells the editor to replace the editor input in the editor even if it is identical to the one - * already showing. By default, the editor will not replace the input if it is identical to the + * Tells the editor to reload the editor input in the editor even if it is identical to the one + * already showing. By default, the editor will not reload the input if it is identical to the * one showing. */ - readonly forceOpen?: boolean; + readonly forceReload?: boolean; /** * Will reveal the editor if it is already opened and visible in any of the opened editor groups. Note diff --git a/src/vs/workbench/browser/parts/editor/editorControl.ts b/src/vs/workbench/browser/parts/editor/editorControl.ts index 039e66bd4fd..4d99ce5d284 100644 --- a/src/vs/workbench/browser/parts/editor/editorControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorControl.ts @@ -153,9 +153,9 @@ export class EditorControl extends Disposable { // If the input did not change, return early and only apply the options // unless the options instruct us to force open it even if it is the same - const forceOpen = options && options.forceOpen; + const forceReload = options && options.forceReload; const inputMatches = control.input && control.input.matches(editor); - if (inputMatches && !forceOpen) { + if (inputMatches && !forceReload) { // Forward options control.setOptions(options); diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 2a9380282e9..97889553c0b 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -677,7 +677,7 @@ export class EditorOptions implements IEditorOptions { const options = new EditorOptions(); options.preserveFocus = settings.preserveFocus; - options.forceOpen = settings.forceOpen; + options.forceReload = settings.forceReload; options.revealIfVisible = settings.revealIfVisible; options.revealIfOpened = settings.revealIfOpened; options.pinned = settings.pinned; @@ -694,11 +694,11 @@ export class EditorOptions implements IEditorOptions { preserveFocus: boolean; /** - * Tells the editor to replace the editor input in the editor even if it is identical to the one - * already showing. By default, the editor will not replace the input if it is identical to the + * Tells the editor to reload the editor input in the editor even if it is identical to the one + * already showing. By default, the editor will not reload the input if it is identical to the * one showing. */ - forceOpen: boolean; + forceReload: boolean; /** * Will reveal the editor if it is already opened and visible in any of the opened editor groups. @@ -763,8 +763,8 @@ export class TextEditorOptions extends EditorOptions { textEditorOptions.editorViewState = options.viewState as IEditorViewState; } - if (options.forceOpen) { - textEditorOptions.forceOpen = true; + if (options.forceReload) { + textEditorOptions.forceReload = true; } if (options.revealIfVisible) { diff --git a/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts b/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts index 40826f4af81..3daf1bd37ca 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts @@ -486,7 +486,6 @@ class Launch implements ILaunch { return this.editorService.openEditor({ resource: resource, options: { - forceOpen: true, selection, pinned: created, revealIfVisible: true diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts index 8af34dabf3b..68b2f5565c5 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts @@ -2044,7 +2044,6 @@ export abstract class AbstractConfigureRecommendedExtensionsAction extends Actio .then(selection => this.editorService.openEditor({ resource: extensionsFileResource, options: { - forceOpen: true, pinned: created, selection } @@ -2058,7 +2057,6 @@ export abstract class AbstractConfigureRecommendedExtensionsAction extends Actio .then(selection => this.editorService.openEditor({ resource: workspaceConfigurationFile, options: { - forceOpen: true, selection } })); diff --git a/src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts b/src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts index df5857ced33..4a3e6340596 100644 --- a/src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts +++ b/src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts @@ -308,7 +308,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut // Binary editor that should reload from event if (resource && isBinaryEditor && (e.contains(resource, FileChangeType.UPDATED) || e.contains(resource, FileChangeType.ADDED))) { - this.editorService.openEditor(editor.input, { forceOpen: true, preserveFocus: true }, editor.group); + this.editorService.openEditor(editor.input, { forceReload: true, preserveFocus: true }, editor.group); } }); } diff --git a/src/vs/workbench/parts/localizations/electron-browser/localizationsActions.ts b/src/vs/workbench/parts/localizations/electron-browser/localizationsActions.ts index d74ddb90984..7d22c8fc3a6 100644 --- a/src/vs/workbench/parts/localizations/electron-browser/localizationsActions.ts +++ b/src/vs/workbench/parts/localizations/electron-browser/localizationsActions.ts @@ -47,10 +47,7 @@ export class ConfigureLocaleAction extends Action { return undefined; } return this.editorService.openEditor({ - resource: stat.resource, - options: { - forceOpen: true - } + resource: stat.resource }); }, (error) => { throw new Error(localize('fail.createSettings', "Unable to create '{0}' ({1}).", getPathLabel(file, this.environmentService, this.contextService), error)); diff --git a/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts b/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts index 83d1491b256..bf5660f37fd 100644 --- a/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts +++ b/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts @@ -661,8 +661,7 @@ export class OutlinePanel extends ViewletPanel { options: { preserveFocus: !focus, selection: Range.collapseToStart(element.symbol.selectionRange), - revealInCenterIfOutsideViewport: true, - forceOpen: true + revealInCenterIfOutsideViewport: true } } as IResourceInput, aside ? SIDE_GROUP : ACTIVE_GROUP); } 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 37c67511039..6a058dff857 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts @@ -1062,7 +1062,6 @@ class TaskService implements ITaskService { this.editorService.openEditor({ resource, options: { - forceOpen: true, pinned: false } }); @@ -1085,7 +1084,6 @@ class TaskService implements ITaskService { return this.editorService.openEditor({ resource, options: { - forceOpen: true, pinned: false } }).then(() => undefined); @@ -2229,7 +2227,6 @@ class TaskService implements ITaskService { this.editorService.openEditor({ resource, options: { - forceOpen: true, pinned: configFileCreated // pin only if config file is created #8727 } }); diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index f5c38e6dd49..41138949711 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -494,7 +494,7 @@ suite('Editor service', () => { assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); - editor = await service.openEditor(input, { forceOpen: true }); + editor = await service.openEditor(input, { forceReload: true }); assertActiveEditorChangedEvent(false); assertVisibleEditorsChangedEvent(false); diff --git a/src/vs/workbench/test/common/editor/editorOptions.test.ts b/src/vs/workbench/test/common/editor/editorOptions.test.ts index f5dd20c857f..345b58f4833 100644 --- a/src/vs/workbench/test/common/editor/editorOptions.test.ts +++ b/src/vs/workbench/test/common/editor/editorOptions.test.ts @@ -16,12 +16,12 @@ suite('Workbench editor options', () => { assert(!options.preserveFocus); options.preserveFocus = true; assert(options.preserveFocus); - assert(!options.forceOpen); - options.forceOpen = true; - assert(options.forceOpen); + assert(!options.forceReload); + options.forceReload = true; + assert(options.forceReload); options = new EditorOptions(); - options.forceOpen = true; + options.forceReload = true; }); test('TextEditorOptions', function () { @@ -35,7 +35,7 @@ suite('Workbench editor options', () => { otherOptions.selection(1, 1, 2, 2); options = new TextEditorOptions(); - options.forceOpen = true; + options.forceReload = true; options.selection(1, 1, 2, 2); }); }); \ No newline at end of file From 7452fe03a0d11d319d91170f2e30a55401cf4012 Mon Sep 17 00:00:00 2001 From: Erich Gamma Date: Tue, 3 Jul 2018 16:55:48 +0200 Subject: [PATCH 115/283] Generalize the debug argument matching --- extensions/npm/src/npmView.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/extensions/npm/src/npmView.ts b/extensions/npm/src/npmView.ts index 3bf86c5b1e2..d4d5279e9a4 100644 --- a/extensions/npm/src/npmView.ts +++ b/extensions/npm/src/npmView.ts @@ -164,10 +164,14 @@ export class NpmScriptsTreeDataProvider implements TreeDataProvider { private extractDebugArg(scripts: any, task: Task): [string, number] | undefined { let script: string = scripts[task.name]; - let match = script.match(/--(inspect|debug)(-brk)?(=(\d*))?/); + // matches --debug, --debug=1234, --debug-brk, debug-brk=1234, --inspect, + // --inspect=1234, --inspect-brk, --inspect-brk=1234, + // --inspect=localhost:1245, --inspect=127.0.0.1:1234, --inspect=[aa:1:0:0:0]:1234, --inspect=:1234 + let match = script.match(/--(inspect|debug)(-brk)?(=((\[[0-9a-fA-F:]*\]|[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+|[a-zA-Z0-9\.]*):)?(\d+))?/); + if (match) { - if (match[4]) { - return [match[1], parseInt(match[4])]; + if (match[6]) { + return [match[1], parseInt(match[6])]; } if (match[1] === 'inspect') { return [match[1], 9229]; From df322e682164257c2a970531edba695a784f7f1a Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 3 Jul 2018 17:05:19 +0200 Subject: [PATCH 116/283] debt - less winjs.promise --- .../contrib/inPlaceReplace/inPlaceReplace.ts | 52 +++++++++---------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/src/vs/editor/contrib/inPlaceReplace/inPlaceReplace.ts b/src/vs/editor/contrib/inPlaceReplace/inPlaceReplace.ts index 4d7b492685d..3127c005bfe 100644 --- a/src/vs/editor/contrib/inPlaceReplace/inPlaceReplace.ts +++ b/src/vs/editor/contrib/inPlaceReplace/inPlaceReplace.ts @@ -20,6 +20,8 @@ import { registerThemingParticipant } from 'vs/platform/theme/common/themeServic import { editorBracketMatchBorder } from 'vs/editor/common/view/editorColorRegistry'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { CancelablePromise, createCancelablePromise, timeout } from 'vs/base/common/async'; +import { onUnexpectedError } from 'vs/base/common/errors'; class InPlaceReplaceController implements IEditorContribution { @@ -33,11 +35,11 @@ class InPlaceReplaceController implements IEditorContribution { className: 'valueSetReplacement' }); - private editor: ICodeEditor; - private currentRequest: TPromise; - private decorationRemover: TPromise; - private decorationIds: string[]; - private editorWorkerService: IEditorWorkerService; + private readonly editor: ICodeEditor; + private readonly editorWorkerService: IEditorWorkerService; + private decorationIds: string[] = []; + private currentRequest: CancelablePromise; + private decorationRemover: CancelablePromise; constructor( editor: ICodeEditor, @@ -45,9 +47,6 @@ class InPlaceReplaceController implements IEditorContribution { ) { this.editor = editor; this.editorWorkerService = editorWorkerService; - this.currentRequest = TPromise.as(null); - this.decorationRemover = TPromise.as(null); - this.decorationIds = []; } public dispose(): void { @@ -57,10 +56,12 @@ class InPlaceReplaceController implements IEditorContribution { return InPlaceReplaceController.ID; } - public run(source: string, up: boolean): TPromise { + public run(source: string, up: boolean): Thenable { // cancel any pending request - this.currentRequest.cancel(); + if (this.currentRequest) { + this.currentRequest.cancel(); + } let selection = this.editor.getSelection(); const model = this.editor.getModel(); @@ -74,18 +75,12 @@ class InPlaceReplaceController implements IEditorContribution { const state = new EditorState(this.editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position); if (!this.editorWorkerService.canNavigateValueSet(modelURI)) { - this.currentRequest = TPromise.as(null); - } else { - this.currentRequest = this.editorWorkerService.navigateValueSet(modelURI, selection, up); - this.currentRequest = this.currentRequest.then((basicResult) => { - if (basicResult && basicResult.range && basicResult.value) { - return basicResult; - } - return null; - }); + return undefined; } - return this.currentRequest.then((result: IInplaceReplaceSupportResult) => { + this.currentRequest = createCancelablePromise(token => this.editorWorkerService.navigateValueSet(modelURI, selection, up)); + + return this.currentRequest.then(result => { if (!result || !result.range || !result.value) { // No proper result @@ -127,12 +122,13 @@ class InPlaceReplaceController implements IEditorContribution { }]); // remove decoration after delay - this.decorationRemover.cancel(); - this.decorationRemover = TPromise.timeout(350); - this.decorationRemover.then(() => { - this.decorationIds = this.editor.deltaDecorations(this.decorationIds, []); - }); - }); + if (this.decorationRemover) { + this.decorationRemover.cancel(); + } + this.decorationRemover = timeout(350); + this.decorationRemover.then(() => this.decorationIds = this.editor.deltaDecorations(this.decorationIds, [])).catch(onUnexpectedError); + + }).catch(onUnexpectedError); } } @@ -156,7 +152,7 @@ class InPlaceReplaceUp extends EditorAction { if (!controller) { return undefined; } - return controller.run(this.id, true); + return TPromise.wrap(controller.run(this.id, true)); } } @@ -180,7 +176,7 @@ class InPlaceReplaceDown extends EditorAction { if (!controller) { return undefined; } - return controller.run(this.id, false); + return TPromise.wrap(controller.run(this.id, false)); } } From 63102e94c3e05d34e205f1b56496bb253c8b2ce4 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 3 Jul 2018 17:36:10 +0200 Subject: [PATCH 117/283] add product name and commit to timings logs --- .../performance/electron-browser/startupTimingsAppender.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/performance/electron-browser/startupTimingsAppender.ts b/src/vs/workbench/parts/performance/electron-browser/startupTimingsAppender.ts index 04ed3bb7d25..2822fbb1890 100644 --- a/src/vs/workbench/parts/performance/electron-browser/startupTimingsAppender.ts +++ b/src/vs/workbench/parts/performance/electron-browser/startupTimingsAppender.ts @@ -14,6 +14,7 @@ import { ITimerService } from 'vs/workbench/services/timer/common/timerService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { nfcall } from 'vs/base/common/async'; import { appendFile } from 'fs'; +import product from 'vs/platform/node/product'; class StartupTimingsAppender implements IWorkbenchContribution { @@ -35,7 +36,7 @@ class StartupTimingsAppender implements IWorkbenchContribution { extensionService.whenInstalledExtensionsRegistered() ]).then(() => { const { startupMetrics } = timerService; - return nfcall(appendFile, appendTo, `${Date.now()}\t${startupMetrics.ellapsed}\n`); + return nfcall(appendFile, appendTo, `${product.nameShort}\t${product.commit || '0000000'}\t${Date.now()}\t${startupMetrics.ellapsed}\n`); }).then(() => { windowsService.quit(); }).catch(err => { From 319efad16b634f1d50d4cb75508722085601be65 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 3 Jul 2018 09:43:39 -0700 Subject: [PATCH 118/283] Search provider - add docs to proposed API --- src/vs/vscode.proposed.d.ts | 147 +++++++++++++++++++++++++++++++++--- 1 file changed, 136 insertions(+), 11 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 3cc94a1cefe..bbefeaa34fb 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -13,48 +13,173 @@ declare module 'vscode' { export function sampleFunction(): Thenable; } - //#region Joh: remote, search provider + //#region Rob: search provider + /** + * The parameters of a query for text search. + */ export interface TextSearchQuery { + /** + * The text pattern to search for. + */ pattern: string; + + /** + * Whether or not `pattern` should be interpreted as a regular expression. + */ isRegExp?: boolean; + + /** + * Whether or not the search should be case-sensitive. + */ isCaseSensitive?: boolean; + + /** + * Whether or not to search for whole word matches only. + */ isWordMatch?: boolean; } + /** + * A file glob pattern to match file paths against. + * TODO@roblou - merge this with the GlobPattern docs/definition in vscode.d.ts. + * @see [GlobPattern](#GlobPattern) + */ + export type GlobString = string; + + /** + * Options common to file and text search + */ export interface SearchOptions { + /** + * The root folder to search within. + */ folder: Uri; - includes: string[]; // paths relative to folder - excludes: string[]; + + /** + * Files that match an `includes` glob pattern should be included in the search. + */ + includes: GlobString[]; + + /** + * Files that match an `excludes` glob pattern should be excluded from the search. + */ + excludes: GlobString[]; + + /** + * Whether external files that exclude files, like .gitignore, should be respected. + * See the vscode setting `"search.useIgnoreFiles"`. + */ useIgnoreFiles?: boolean; + + /** + * Whether symlinks should be followed while searching. + * See the vscode setting `"search.followSymlinks"`. + */ followSymlinks?: boolean; + + /** + * The maximum number of results to be returned. + */ maxResults?: number; } + /** + * Options that apply to text search. + */ export interface TextSearchOptions extends SearchOptions { - previewOptions?: any; // total length? # of context lines? leading and trailing # of chars? + /** + * TODO@roblou - total length? # of context lines? leading and trailing # of chars? + */ + previewOptions?: any; + + /** + * Exclude files larger than `maxFileSize` in bytes. + */ maxFileSize?: number; + + /** + * Interpret files using this encoding. + * See the vscode setting `"files.encoding"` + */ encoding?: string; } + /** + * The parameters of a query for file search. + */ export interface FileSearchQuery { + /** + * The search pattern to match against file paths. + */ pattern: string; + + /** + * `cacheKey` has the same value when `provideFileSearchResults` is invoked multiple times during a single quickopen session. + * Providers can optionally use this to cache results at the beginning of a quickopen session and filter results as the user types. + */ cacheKey?: string; } + /** + * Options that apply to file search. + */ export interface FileSearchOptions extends SearchOptions { } - export interface TextSearchResult { - uri: Uri; - range: Range; + export interface TextSearchResultPreview { + /** + * The matching line of text, or a portion of the matching line that contains the match. + * For now, this can only be a single line. + */ + text: string; - // For now, preview must be a single line of text - preview: { text: string, match: Range }; + /** + * The Range within `text` corresponding to the text of the match. + */ + match: Range; } + /** + * A match from a text search + */ + export interface TextSearchResult { + /** + * The uri for the matching document. + */ + uri: Uri; + + /** + * The range of the match within the document. + */ + range: Range; + + /** + * A preview of the matching line + */ + preview: TextSearchResultPreview; + } + + /** + * A SearchProvider provides search results for files or text in files. It can be invoked by quickopen, the search viewlet, and other extensions. + */ export interface SearchProvider { - provideFileSearchResults?(query: FileSearchQuery, options: FileSearchOptions, progress: Progress, token: CancellationToken): Thenable; - provideTextSearchResults?(query: TextSearchQuery, options: TextSearchOptions, progress: Progress, token: CancellationToken): Thenable; + /** + * Provide the set of files that match a certain file path pattern. + * @param query The parameters for this query. + * @param options A set of options to consider while searching files. + * @param progress A progress callback that must be invoked for all results. + * @param token A cancellation token. + */ + provideFileSearchResults(query: FileSearchQuery, options: FileSearchOptions, progress: Progress, token: CancellationToken): Thenable; + + /** + * Provide results that match the given text pattern. + * @param query The parameters for this query. + * @param options A set of options to consider while searching. + * @param progress A progress callback that must be invoked for all results. + * @param token A cancellation token. + */ + provideTextSearchResults(query: TextSearchQuery, options: TextSearchOptions, progress: Progress, token: CancellationToken): Thenable; } export interface FindTextInFilesOptions { From e15f6352ea2c6a62f09391f8b54745bbea92deb2 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 3 Jul 2018 10:02:16 -0700 Subject: [PATCH 119/283] Search provider - Make provider methods optional again --- src/vs/vscode.proposed.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index bbefeaa34fb..abdd4552dc0 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -170,7 +170,7 @@ declare module 'vscode' { * @param progress A progress callback that must be invoked for all results. * @param token A cancellation token. */ - provideFileSearchResults(query: FileSearchQuery, options: FileSearchOptions, progress: Progress, token: CancellationToken): Thenable; + provideFileSearchResults?(query: FileSearchQuery, options: FileSearchOptions, progress: Progress, token: CancellationToken): Thenable; /** * Provide results that match the given text pattern. @@ -179,7 +179,7 @@ declare module 'vscode' { * @param progress A progress callback that must be invoked for all results. * @param token A cancellation token. */ - provideTextSearchResults(query: TextSearchQuery, options: TextSearchOptions, progress: Progress, token: CancellationToken): Thenable; + provideTextSearchResults?(query: TextSearchQuery, options: TextSearchOptions, progress: Progress, token: CancellationToken): Thenable; } export interface FindTextInFilesOptions { From 4de490f435701dd937f5356e25029e9276fb0603 Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Tue, 3 Jul 2018 11:14:33 -0700 Subject: [PATCH 120/283] fix #53459 --- src/vs/workbench/electron-browser/workbench.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index 6c3e8ab23bd..4dc1424ef1c 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -1205,7 +1205,7 @@ export class Workbench extends Disposable implements IPartService { getTitleBarOffset(): number { let offset = 0; if (this.isVisible(Parts.TITLEBAR_PART)) { - offset = 22 / browser.getZoomFactor(); // adjust the position based on title bar size and zoom factor + offset = this.getContainer(Parts.TITLEBAR_PART).getBoundingClientRect().height; } return offset; From 39612184c380c079c3670e222ac16ae100ec1448 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 3 Jul 2018 22:49:16 +0200 Subject: [PATCH 121/283] remove progress usage from theme picker --- src/vs/platform/quickOpen/common/quickOpen.ts | 16 +++++++++---- .../parts/quickopen/quickOpenController.ts | 24 +++++++++++++------ .../electron-browser/themes.contribution.ts | 10 ++++---- 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/vs/platform/quickOpen/common/quickOpen.ts b/src/vs/platform/quickOpen/common/quickOpen.ts index e5e1bffd9d4..6a9b847fc09 100644 --- a/src/vs/platform/quickOpen/common/quickOpen.ts +++ b/src/vs/platform/quickOpen/common/quickOpen.ts @@ -86,6 +86,14 @@ export interface IPickOptions { contextKey?: string; } +export interface IStringPickOptions extends IPickOptions { + onDidFocus?: (item: string) => void; +} + +export interface ITypedPickOptions extends IPickOptions { + onDidFocus?: (entry: T) => void; +} + export interface IShowOptions { quickNavigateConfiguration?: IQuickNavigateConfiguration; inputSelection?: { start: number; end: number; }; @@ -114,10 +122,10 @@ export interface IQuickOpenService { * Passing in a promise will allow you to resolve the elements in the background while quick open will show a * progress bar spinning. */ - pick(picks: TPromise, options?: IPickOptions, token?: CancellationToken): TPromise; - pick(picks: TPromise, options?: IPickOptions, token?: CancellationToken): TPromise; - pick(picks: string[], options?: IPickOptions, token?: CancellationToken): TPromise; - pick(picks: T[], options?: IPickOptions, token?: CancellationToken): TPromise; + pick(picks: TPromise, options?: IStringPickOptions, token?: CancellationToken): TPromise; + pick(picks: TPromise, options?: ITypedPickOptions, token?: CancellationToken): TPromise; + pick(picks: string[], options?: IStringPickOptions, token?: CancellationToken): TPromise; + pick(picks: T[], options?: ITypedPickOptions, token?: CancellationToken): TPromise; /** * Allows to navigate from the outside in an opened picker. diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts index 5bc53c73ece..178903e64f2 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts @@ -34,7 +34,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { IPartService } from 'vs/workbench/services/part/common/partService'; import { QuickOpenHandler, QuickOpenHandlerDescriptor, IQuickOpenRegistry, Extensions, EditorQuickOpenEntry, CLOSE_ON_FOCUS_LOST_CONFIG } from 'vs/workbench/browser/quickopen'; import * as errors from 'vs/base/common/errors'; -import { IPickOpenEntry, IFilePickOpenEntry, IQuickOpenService, IPickOptions, IShowOptions, IPickOpenItem } from 'vs/platform/quickOpen/common/quickOpen'; +import { IPickOpenEntry, IFilePickOpenEntry, IQuickOpenService, IShowOptions, IPickOpenItem, IStringPickOptions, ITypedPickOptions } from 'vs/platform/quickOpen/common/quickOpen'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -73,6 +73,7 @@ interface IInternalPickOptions { ignoreFocusLost?: boolean; quickNavigateConfiguration?: IQuickNavigateConfiguration; onDidType?: (value: string) => any; + onDidFocus?: (item: any) => void; } export class QuickOpenController extends Component implements IQuickOpenService { @@ -145,11 +146,11 @@ export class QuickOpenController extends Component implements IQuickOpenService } } - pick(picks: TPromise, options?: IPickOptions, token?: CancellationToken): TPromise; - pick(picks: TPromise, options?: IPickOptions, token?: CancellationToken): TPromise; - pick(picks: string[], options?: IPickOptions, token?: CancellationToken): TPromise; - pick(picks: T[], options?: IPickOptions, token?: CancellationToken): TPromise; - pick(arg1: string[] | TPromise | IPickOpenEntry[] | TPromise, options?: IPickOptions, token?: CancellationToken): TPromise { + pick(picks: TPromise, options?: IStringPickOptions, token?: CancellationToken): TPromise; + pick(picks: TPromise, options?: ITypedPickOptions, token?: CancellationToken): TPromise; + pick(picks: string[], options?: IStringPickOptions, token?: CancellationToken): TPromise; + pick(picks: T[], options?: ITypedPickOptions, token?: CancellationToken): TPromise; + pick(arg1: string[] | TPromise | IPickOpenEntry[] | TPromise, options?: IStringPickOptions | ITypedPickOptions, token?: CancellationToken): TPromise { if (!options) { options = Object.create(null); } @@ -279,7 +280,16 @@ export class QuickOpenController extends Component implements IQuickOpenService // Model const model = new QuickOpenModel([], new PickOpenActionProvider()); - const entries = picks.map((e, index) => this.instantiationService.createInstance(PickOpenEntry, e, index, () => progress(e), () => this.pickOpenWidget.refresh())); + const entries = picks.map((e, index) => { + const onPreview = () => { + if (options.onDidFocus) { + options.onDidFocus(e); + } + + progress(e); + }; + return this.instantiationService.createInstance(PickOpenEntry, e, index, onPreview, () => this.pickOpenWidget.refresh()); + }); if (picks.length === 0) { entries.push(this.instantiationService.createInstance(PickOpenEntry, { label: nls.localize('emptyPicks', "There are no entries to pick from") }, 0, null, null)); } diff --git a/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts b/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts index bde131d66ed..4dea03c0c20 100644 --- a/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts +++ b/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts @@ -77,13 +77,11 @@ export class SelectColorThemeAction extends Action { const placeHolder = localize('themes.selectTheme', "Select Color Theme (Up/Down Keys to Preview)"); const autoFocusIndex = firstIndex(picks, p => p.id === currentTheme.id); const delayer = new Delayer(100); + const chooseTheme = theme => delayer.trigger(() => selectTheme(theme || currentTheme, true), 0); + const tryTheme = theme => delayer.trigger(() => selectTheme(theme, false)); - return this.quickOpenService.pick(picks, { placeHolder, autoFocus: { autoFocusIndex } }) - .then( - theme => delayer.trigger(() => selectTheme(theme || currentTheme, true), 0), - null, - theme => delayer.trigger(() => selectTheme(theme, false)) - ); + return this.quickOpenService.pick(picks, { placeHolder, autoFocus: { autoFocusIndex }, onDidFocus: tryTheme }) + .then(chooseTheme); }); } } From 7021188827d38002d00ce03d9ac5c99835c15801 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 3 Jul 2018 14:17:48 -0700 Subject: [PATCH 122/283] Bump node2 --- build/builtInExtensions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index ac3e6988afe..2d53c46e935 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -6,7 +6,7 @@ }, { "name": "ms-vscode.node-debug2", - "version": "1.25.5", + "version": "1.26.0", "repo": "https://github.com/Microsoft/vscode-node-debug2" } ] From 21082bf9ef3aedbdbf8ecfd6c30d7b4c5a6eed28 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 3 Jul 2018 14:46:41 -0700 Subject: [PATCH 123/283] Search provider - give different cacheKeys for different folders --- src/vs/workbench/api/node/extHostSearch.ts | 5 ++- .../api/extHostSearch.test.ts | 44 +++++++++++-------- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/api/node/extHostSearch.ts b/src/vs/workbench/api/node/extHostSearch.ts index aeba2749c5f..4f9c48ed0eb 100644 --- a/src/vs/workbench/api/node/extHostSearch.ts +++ b/src/vs/workbench/api/node/extHostSearch.ts @@ -621,7 +621,10 @@ class FileSearchEngine { .then(() => { this.activeCancellationTokens.add(cancellation); return this.provider.provideFileSearchResults( - { cacheKey: this.config.cacheKey, pattern: this.config.filePattern || '' }, + { + pattern: this.config.filePattern || '', + cacheKey: this.config.cacheKey + '_' + fq.folder.fsPath + }, options, { report: onProviderResult }, cancellation.token); diff --git a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts index 3c5cceb4ce7..49d33da63b2 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts @@ -666,27 +666,33 @@ suite('ExtHostSearch', () => { compareURIs(results, reportedResults); }); - // Mock fs? - // test('Returns result for absolute path', async () => { - // const queriedFile = makeFileResult(rootFolderA, 'file2.ts'); - // const reportedResults = [ - // makeFileResult(rootFolderA, 'file1.ts'), - // queriedFile, - // makeFileResult(rootFolderA, 'file3.ts'), - // ]; + test('uses different cache keys for different folders', async () => { + const cacheKeys: string[] = []; + await registerTestSearchProvider({ + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + cacheKeys.push(query.cacheKey); + return TPromise.wrap(null); + } + }); - // await registerTestSearchProvider({ - // provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { - // reportedResults.forEach(r => progress.report(r)); - // return TPromise.wrap(null); - // } - // }); + const query: ISearchQuery = { + type: QueryType.File, + filePattern: '', + cacheKey: 'cacheKey', + folderQueries: [ + { + folder: rootFolderA + }, + { + folder: rootFolderB + } + ] + }; - // const queriedFilePath = queriedFile.fsPath; - // const { results } = await runFileSearch(getSimpleQuery(queriedFilePath)); - // assert.equal(results.length, 1); - // compareURIs(results, [queriedFile]); - // }); + await runFileSearch(query); + assert.equal(cacheKeys.length, 2); + assert.notEqual(cacheKeys[0], cacheKeys[1]); + }); }); suite('Text:', () => { From fdf27274f0b575e91aed21fe513491214ae40085 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 3 Jul 2018 15:59:49 -0700 Subject: [PATCH 124/283] Search provider - fix cancellation in extension code --- .../search-rg/src/cachedSearchProvider.ts | 20 +++-------- extensions/search-rg/src/extension.ts | 25 +++++++++++--- extensions/search-rg/src/ripgrepFileSearch.ts | 34 +++++++------------ extensions/search-rg/src/ripgrepTextSearch.ts | 26 +++++++------- .../src/{ripgrepHelpers.ts => utils.ts} | 0 5 files changed, 50 insertions(+), 55 deletions(-) rename extensions/search-rg/src/{ripgrepHelpers.ts => utils.ts} (100%) diff --git a/extensions/search-rg/src/cachedSearchProvider.ts b/extensions/search-rg/src/cachedSearchProvider.ts index a17e6de102f..a1a924d56a0 100644 --- a/extensions/search-rg/src/cachedSearchProvider.ts +++ b/extensions/search-rg/src/cachedSearchProvider.ts @@ -8,7 +8,7 @@ import * as vscode from 'vscode'; import * as arrays from './common/arrays'; import { compareItemsByScore, IItemAccessor, prepareQuery, ScorerCache } from './common/fileSearchScorer'; import * as strings from './common/strings'; -import { joinPath } from './ripgrepHelpers'; +import { joinPath } from './utils'; interface IProviderArgs { query: vscode.FileSearchQuery; @@ -27,9 +27,6 @@ export class CachedSearchProvider { private caches: { [cacheKey: string]: Cache; } = Object.create(null); - constructor(private outputChannel: vscode.OutputChannel) { - } - provideFileSearchResults(provider: IInternalFileSearchProvider, query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { const onResult = (result: IInternalFileMatch) => { progress.report(joinPath(options.folder, result.relativePath)); @@ -58,11 +55,11 @@ export class CachedSearchProvider { } private doSortedSearch(args: IProviderArgs, provider: IInternalFileSearchProvider): Promise { - let allResultsPromise = new Promise((c, e) => { + const allResultsPromise = new Promise((c, e) => { const results: IInternalFileMatch[] = []; const onResult = (progress: IInternalFileMatch[]) => results.push(...progress); - // set maxResult = null + // TODO@roblou set maxResult = null this.doSearch(args, provider, onResult, CachedSearchProvider.BATCH_SIZE) .then(() => c(results), e); }); @@ -193,7 +190,7 @@ export class CachedSearchProvider { onResult(batch); } - c(); // TODO limitHit + c(); }, error => { if (batch.length) { onResult(batch); @@ -215,17 +212,8 @@ interface IInternalFileMatch { basename: string; } -export interface IDisposable { - dispose(): void; -} - -export interface Event { - (listener: (e: T) => any): IDisposable; -} - interface CacheEntry { finished: Promise; - onResult?: Event; } class Cache { diff --git a/extensions/search-rg/src/extension.ts b/extensions/search-rg/src/extension.ts index 042d2269a80..f5300c2d2bc 100644 --- a/extensions/search-rg/src/extension.ts +++ b/extensions/search-rg/src/extension.ts @@ -5,7 +5,7 @@ import * as vscode from 'vscode'; import { RipgrepTextSearchEngine } from './ripgrepTextSearch'; -import { RipgrepFileSearch } from './ripgrepFileSearch'; +import { RipgrepFileSearchEngine } from './ripgrepFileSearch'; import { CachedSearchProvider } from './cachedSearchProvider'; export function activate(): void { @@ -16,20 +16,35 @@ export function activate(): void { } } +type SearchEngine = RipgrepFileSearchEngine | RipgrepTextSearchEngine; + class RipgrepSearchProvider implements vscode.SearchProvider { private cachedProvider: CachedSearchProvider; + private inProgress: Set = new Set(); constructor(private outputChannel: vscode.OutputChannel) { - this.cachedProvider = new CachedSearchProvider(this.outputChannel); + this.cachedProvider = new CachedSearchProvider(); + process.once('exit', () => this.dispose()); } provideTextSearchResults(query: vscode.TextSearchQuery, options: vscode.TextSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { const engine = new RipgrepTextSearchEngine(this.outputChannel); - return engine.provideTextSearchResults(query, options, progress, token); + return this.withEngine(engine, () => engine.provideTextSearchResults(query, options, progress, token)); } provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.SearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { - const engine = new RipgrepFileSearch(this.outputChannel); - return this.cachedProvider.provideFileSearchResults(engine, query, options, progress, token); + const engine = new RipgrepFileSearchEngine(this.outputChannel); + return this.withEngine(engine, () => this.cachedProvider.provideFileSearchResults(engine, query, options, progress, token)); + } + + private withEngine(engine: SearchEngine, fn: () => Thenable): Thenable { + this.inProgress.add(engine); + return fn().then(() => { + this.inProgress.delete(engine); + }); + } + + private dispose() { + this.inProgress.forEach(engine => engine.cancel()); } } \ No newline at end of file diff --git a/extensions/search-rg/src/ripgrepFileSearch.ts b/extensions/search-rg/src/ripgrepFileSearch.ts index f6765d970bf..7edf4c4f4b9 100644 --- a/extensions/search-rg/src/ripgrepFileSearch.ts +++ b/extensions/search-rg/src/ripgrepFileSearch.ts @@ -9,7 +9,7 @@ import { NodeStringDecoder, StringDecoder } from 'string_decoder'; import * as vscode from 'vscode'; import { normalizeNFC, normalizeNFD } from './common/normalization'; import { rgPath } from './ripgrep'; -import { anchorGlob } from './ripgrepHelpers'; +import { anchorGlob } from './utils'; import { rgErrorMsgForDisplay } from './ripgrepTextSearch'; import { IInternalFileSearchProvider } from './cachedSearchProvider'; @@ -18,17 +18,17 @@ const isMac = process.platform === 'darwin'; // If vscode-ripgrep is in an .asar file, then the binary is unpacked. const rgDiskPath = rgPath.replace(/\bnode_modules\.asar\b/, 'node_modules.asar.unpacked'); -export class RipgrepFileSearch implements IInternalFileSearchProvider { +export class RipgrepFileSearchEngine implements IInternalFileSearchProvider { private rgProc: cp.ChildProcess; - private killRgProcFn: (code?: number) => void; + private isDone: boolean; - constructor(private outputChannel: vscode.OutputChannel) { - this.killRgProcFn = () => this.rgProc && this.rgProc.kill(); - process.once('exit', this.killRgProcFn); - } + constructor(private outputChannel: vscode.OutputChannel) { } - private dispose() { - process.removeListener('exit', this.killRgProcFn); + cancel() { + this.isDone = true; + if (this.rgProc) { + this.rgProc.kill(); + } } provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { @@ -40,12 +40,7 @@ export class RipgrepFileSearch implements IInternalFileSearchProvider { })}`); return new Promise((resolve, reject) => { - let isDone = false; - const cancel = () => { - isDone = true; - this.rgProc.kill(); - }; - token.onCancellationRequested(cancel); + token.onCancellationRequested(() => this.cancel()); const rgArgs = getRgArgs(options); @@ -94,7 +89,7 @@ export class RipgrepFileSearch implements IInternalFileSearchProvider { }); if (last) { - if (isDone) { + if (this.isDone) { resolve(); } else { // Trigger last result @@ -107,12 +102,7 @@ export class RipgrepFileSearch implements IInternalFileSearchProvider { } } }); - }).then( - () => this.dispose(), - err => { - this.dispose(); - return Promise.reject(err); - }); + }); } private collectStdout(cmd: cp.ChildProcess, cb: (err: Error, stdout?: string, last?: boolean) => void): void { diff --git a/extensions/search-rg/src/ripgrepTextSearch.ts b/extensions/search-rg/src/ripgrepTextSearch.ts index 87aed94c6e3..711f985a8db 100644 --- a/extensions/search-rg/src/ripgrepTextSearch.ts +++ b/extensions/search-rg/src/ripgrepTextSearch.ts @@ -11,7 +11,7 @@ import * as path from 'path'; import { NodeStringDecoder, StringDecoder } from 'string_decoder'; import * as vscode from 'vscode'; import { rgPath } from './ripgrep'; -import { anchorGlob } from './ripgrepHelpers'; +import { anchorGlob } from './utils'; // If vscode-ripgrep is in an .asar file, then the binary is unpacked. const rgDiskPath = rgPath.replace(/\bnode_modules\.asar\b/, 'node_modules.asar.unpacked'); @@ -22,12 +22,21 @@ const MAX_TEXT_RESULTS = 10000; export class RipgrepTextSearchEngine { private isDone = false; private rgProc: cp.ChildProcess; - private killRgProcFn: (code?: number) => void; private ripgrepParser: RipgrepParser; - constructor(private outputChannel: vscode.OutputChannel) { - this.killRgProcFn = () => this.rgProc && this.rgProc.kill(); + constructor(private outputChannel: vscode.OutputChannel) { } + + cancel() { + this.isDone = true; + + if (this.rgProc) { + this.rgProc.kill(); + } + + if (this.ripgrepParser) { + this.ripgrepParser.cancel(); + } } provideTextSearchResults(query: vscode.TextSearchQuery, options: vscode.TextSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { @@ -39,12 +48,7 @@ export class RipgrepTextSearchEngine { })}`); return new Promise((resolve, reject) => { - const cancel = () => { - this.isDone = true; - this.ripgrepParser.cancel(); - this.rgProc.kill(); - }; - token.onCancellationRequested(cancel); + token.onCancellationRequested(() => this.cancel()); const rgArgs = getRgArgs(query, options); @@ -56,7 +60,6 @@ export class RipgrepTextSearchEngine { this.outputChannel.appendLine(`rg ${escapedArgs}\n - cwd: ${cwd}`); this.rgProc = cp.spawn(rgDiskPath, rgArgs, { cwd }); - process.once('exit', this.killRgProcFn); this.rgProc.on('error', e => { console.error(e); this.outputChannel.append('Error: ' + (e && e.message)); @@ -92,7 +95,6 @@ export class RipgrepTextSearchEngine { this.outputChannel.appendLine(gotData ? 'Got data from stdout' : 'No data from stdout'); this.outputChannel.appendLine(gotResult ? 'Got result from parser' : 'No result from parser'); this.outputChannel.appendLine(''); - process.removeListener('exit', this.killRgProcFn); if (this.isDone) { resolve(); } else { diff --git a/extensions/search-rg/src/ripgrepHelpers.ts b/extensions/search-rg/src/utils.ts similarity index 100% rename from extensions/search-rg/src/ripgrepHelpers.ts rename to extensions/search-rg/src/utils.ts From 824a3e9298e4bb73f3fe4e2fa7c6446573026050 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 3 Jul 2018 16:03:43 -0700 Subject: [PATCH 125/283] Search provider - remove absolute path checks from extHostSearch to match fileSearch.ts --- .../search-rg/src/cachedSearchProvider.ts | 4 - src/vs/workbench/api/node/extHostSearch.ts | 90 ++++++------------- 2 files changed, 26 insertions(+), 68 deletions(-) diff --git a/extensions/search-rg/src/cachedSearchProvider.ts b/extensions/search-rg/src/cachedSearchProvider.ts index a1a924d56a0..85012b70552 100644 --- a/extensions/search-rg/src/cachedSearchProvider.ts +++ b/extensions/search-rg/src/cachedSearchProvider.ts @@ -113,10 +113,6 @@ export class CachedSearchProvider { } private getResultsFromCache(cache: Cache, searchValue: string, onResult: (results: IInternalFileMatch) => void): Promise<[IInternalFileMatch[], CacheStats]> { - if (path.isAbsolute(searchValue)) { - return null; // bypass cache if user looks up an absolute path where matching goes directly on disk - } - // Find cache entries by prefix of search value const hasPathSep = searchValue.indexOf(path.sep) >= 0; let cached: CacheEntry; diff --git a/src/vs/workbench/api/node/extHostSearch.ts b/src/vs/workbench/api/node/extHostSearch.ts index 4f9c48ed0eb..7fe3e6eca15 100644 --- a/src/vs/workbench/api/node/extHostSearch.ts +++ b/src/vs/workbench/api/node/extHostSearch.ts @@ -540,52 +540,36 @@ class FileSearchEngine { }; // Support that the file pattern is a full path to a file that exists - this.checkFilePatternAbsoluteMatch().then(({ exists, size }) => { - if (this.isCanceled) { - return resolve({ isLimitHit: this.isLimitHit }); - } + if (this.isCanceled) { + return resolve({ isLimitHit: this.isLimitHit }); + } - // Report result from file pattern if matching - if (exists) { - onResult({ - base: URI.file(this.filePattern), - basename: path.basename(this.filePattern), - size + // For each extra file + if (this.config.extraFileResources) { + this.config.extraFileResources + .forEach(extraFile => { + const extraFileStr = extraFile.toString(); // ? + const basename = path.basename(extraFileStr); + if (this.globalExcludePattern && this.globalExcludePattern(extraFileStr, basename)) { + return; // excluded + } + + // File: Check for match on file pattern and include pattern + this.matchFile(onResult, { base: extraFile, basename }); }); + } - // Optimization: a match on an absolute path is a good result and we do not - // continue walking the entire root paths array for other matches because - // it is very unlikely that another file would match on the full absolute path - return resolve({ isLimitHit: this.isLimitHit }); - } + // For each root folder + PPromise.join(folderQueries.map(fq => { + return this.searchInFolder(fq).then(null, null, onResult); + })).then(() => { + resolve({ isLimitHit: this.isLimitHit }); + }, (errs: Error[]) => { + const errMsg = errs + .map(err => toErrorMessage(err)) + .filter(msg => !!msg)[0]; - // For each extra file - if (this.config.extraFileResources) { - this.config.extraFileResources - .forEach(extraFile => { - const extraFileStr = extraFile.toString(); // ? - const basename = path.basename(extraFileStr); - if (this.globalExcludePattern && this.globalExcludePattern(extraFileStr, basename)) { - return; // excluded - } - - // File: Check for match on file pattern and include pattern - this.matchFile(onResult, { base: extraFile, basename }); - }); - } - - // For each root folder - PPromise.join(folderQueries.map(fq => { - return this.searchInFolder(fq).then(null, null, onResult); - })).then(() => { - resolve({ isLimitHit: this.isLimitHit }); - }, (errs: Error[]) => { - const errMsg = errs - .map(err => toErrorMessage(err)) - .filter(msg => !!msg)[0]; - - reject(new Error(errMsg)); - }); + reject(new Error(errMsg)); }); }); } @@ -745,28 +729,6 @@ class FileSearchEngine { matchDirectory(rootEntries); } - /** - * Return whether the file pattern is an absolute path to a file that exists. - * TODO@roblou delete to match fileSearch.ts - */ - private checkFilePatternAbsoluteMatch(): TPromise<{ exists: boolean, size?: number }> { - if (!this.filePattern || !path.isAbsolute(this.filePattern)) { - return TPromise.wrap({ exists: false }); - } - - return this._pfs.stat(this.filePattern) - .then(stat => { - return { - exists: !stat.isDirectory(), - size: stat.size - }; - }, err => { - return { - exists: false - }; - }); - } - private checkFilePatternRelativeMatch(base: URI): TPromise<{ exists: boolean, size?: number }> { if (!this.filePattern || path.isAbsolute(this.filePattern) || base.scheme !== 'file') { return TPromise.wrap({ exists: false }); From ca60914d78e03f54083900fee404e9838ece657f Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 3 Jul 2018 16:49:32 -0700 Subject: [PATCH 126/283] Test for joinPath --- src/vs/base/test/common/resources.test.ts | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/vs/base/test/common/resources.test.ts b/src/vs/base/test/common/resources.test.ts index 2c1e18bf6ef..6a613730db3 100644 --- a/src/vs/base/test/common/resources.test.ts +++ b/src/vs/base/test/common/resources.test.ts @@ -5,9 +5,9 @@ 'use strict'; import * as assert from 'assert'; -import URI from 'vs/base/common/uri'; -import { distinctParents, dirname } from 'vs/base/common/resources'; import { normalize } from 'vs/base/common/paths'; +import { dirname, distinctParents, joinPath } from 'vs/base/common/resources'; +import URI from 'vs/base/common/uri'; suite('Resources', () => { @@ -50,4 +50,22 @@ suite('Resources', () => { // does not explode (https://github.com/Microsoft/vscode/issues/41987) dirname(URI.from({ scheme: 'file', authority: '/users/someone/portal.h' })); }); + + test('joinPath', () => { + assert.equal( + joinPath(URI.file('/foo/bar'), '/file.js').toString(), + 'file:///foo/bar/file.js'); + + assert.equal( + joinPath(URI.file('/foo/bar/'), '/file.js').toString(), + 'file:///foo/bar/file.js'); + + assert.equal( + joinPath(URI.file('/'), '/file.js').toString(), + 'file:///file.js'); + + assert.equal( + joinPath(URI.from({ scheme: 'myScheme', authority: 'authority', path: '/path', query: 'query', fragment: 'fragment' }), '/file.js').toString(), + 'myScheme://authority/path/file.js?query#fragment'); + }); }); \ No newline at end of file From 9f8491190a37f10b9b1cd0a4b4bd0703aad41191 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 3 Jul 2018 15:44:33 -0700 Subject: [PATCH 127/283] Update js/ts grammars --- .../javascript/syntaxes/JavaScript.tmLanguage.json | 14 +++++++------- .../syntaxes/JavaScriptReact.tmLanguage.json | 14 +++++++------- .../syntaxes/TypeScript.tmLanguage.json | 6 +++--- .../syntaxes/TypeScriptReact.tmLanguage.json | 14 +++++++------- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json b/extensions/javascript/syntaxes/JavaScript.tmLanguage.json index 30c1ca68b1c..8fd13accda3 100644 --- a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json +++ b/extensions/javascript/syntaxes/JavaScript.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/7bf8960f7042474b10b519f39339fc527907ce16", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/88217b1c7d36ed5d35adc3099ba7978aabe2531b", "name": "JavaScript (with React support)", "scopeName": "source.js", "patterns": [ @@ -4115,7 +4115,7 @@ }, "directives": { "name": "comment.line.triple-slash.directive.js", - "begin": "^(///)\\s*(?=<(reference|amd-dependency|amd-module)(\\s+(path|types|no-default-lib|name)\\s*=\\s*((\\'([^\\'\\\\]|\\\\\\'|\\\\)*\\')|(\\\"([^\\\"\\\\]|\\\\\\\"|\\\\)*\\\")))+\\s*/>\\s*$)", + "begin": "^(///)\\s*(?=<(reference|amd-dependency|amd-module)(\\s+(path|types|no-default-lib|lib|name)\\s*=\\s*((\\'([^\\'\\\\]|\\\\\\'|\\\\)*\\')|(\\\"([^\\\"\\\\]|\\\\\\\"|\\\\)*\\\")))+\\s*/>\\s*$)", "beginCaptures": { "1": { "name": "punctuation.definition.comment.js" @@ -4143,7 +4143,7 @@ "patterns": [ { "name": "entity.other.attribute-name.directive.js", - "match": "path|types|no-default-lib|name" + "match": "path|types|no-default-lib|lib|name" }, { "name": "keyword.operator.assignment.js", @@ -4664,8 +4664,8 @@ ] }, "jsx-tag-in-expression": { - "begin": "(?x)\n (?:*]|&&|\\|\\||\\?|^return|[^\\._$[:alnum:]]return|^default|[^\\._$[:alnum:]]default|^)\\s*\n (?!<\\s*[_$[:alpha:]][_$[:alnum:]]*((\\s+extends\\s+[^=>])|,)) # look ahead is not type parameter of arrow\n (?=(<)\\s*(?:([_$a-zA-Z][-$\\w.]*)(?\\,\\.\\[=]|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[=]|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?!=)\\>)*(?!=)>\\s*)|(\\s+))(?!\\?)|\\/?>))", - "end": "(?!(<)\\s*(?:([_$a-zA-Z][-$\\w.]*)(?\\,\\.\\[=]|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[=]|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?!=)\\>)*(?!=)>\\s*)|(\\s+))(?!\\?)|\\/?>))", + "begin": "(?x)\n (?:*]|&&|\\|\\||\\?|^return|[^\\._$[:alnum:]]return|^default|[^\\._$[:alnum:]]default|^)\\s*\n (?!<\\s*[_$[:alpha:]][_$[:alnum:]]*((\\s+extends\\s+[^=>])|,)) # look ahead is not type parameter of arrow\n (?=(<)\\s*(?:([_$a-zA-Z][-$\\w.]*)(?))", + "end": "(?!(<)\\s*(?:([_$a-zA-Z][-$\\w.]*)(?))", "patterns": [ { "include": "#jsx-tag" @@ -4674,7 +4674,7 @@ }, "jsx-tag": { "name": "meta.tag.js", - "begin": "(?=(<)\\s*(?:([_$a-zA-Z][-$\\w.]*)(?\\,\\.\\[=]|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[=]|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?!=)\\>)*(?!=)>\\s*)|(\\s+))(?!\\?)|\\/?>))", + "begin": "(?=(<)\\s*(?:([_$a-zA-Z][-$\\w.]*)(?))", "end": "(/>)|(?:())", "endCaptures": { "1": { @@ -4701,7 +4701,7 @@ }, "patterns": [ { - "begin": "(<)\\s*(?:([_$a-zA-Z][-$\\w.]*)(?\\,\\.\\[=]|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[=]|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?!=)\\>)*(?!=)>\\s*)|(\\s+))(?!\\?)|\\/?>)", + "begin": "(<)\\s*(?:([_$a-zA-Z][-$\\w.]*)(?)", "beginCaptures": { "1": { "name": "punctuation.definition.tag.begin.js" diff --git a/extensions/javascript/syntaxes/JavaScriptReact.tmLanguage.json b/extensions/javascript/syntaxes/JavaScriptReact.tmLanguage.json index 2327bd28a91..a7ceac55af3 100644 --- a/extensions/javascript/syntaxes/JavaScriptReact.tmLanguage.json +++ b/extensions/javascript/syntaxes/JavaScriptReact.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/7bf8960f7042474b10b519f39339fc527907ce16", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/88217b1c7d36ed5d35adc3099ba7978aabe2531b", "name": "JavaScript (with React support)", "scopeName": "source.js.jsx", "patterns": [ @@ -4115,7 +4115,7 @@ }, "directives": { "name": "comment.line.triple-slash.directive.js.jsx", - "begin": "^(///)\\s*(?=<(reference|amd-dependency|amd-module)(\\s+(path|types|no-default-lib|name)\\s*=\\s*((\\'([^\\'\\\\]|\\\\\\'|\\\\)*\\')|(\\\"([^\\\"\\\\]|\\\\\\\"|\\\\)*\\\")))+\\s*/>\\s*$)", + "begin": "^(///)\\s*(?=<(reference|amd-dependency|amd-module)(\\s+(path|types|no-default-lib|lib|name)\\s*=\\s*((\\'([^\\'\\\\]|\\\\\\'|\\\\)*\\')|(\\\"([^\\\"\\\\]|\\\\\\\"|\\\\)*\\\")))+\\s*/>\\s*$)", "beginCaptures": { "1": { "name": "punctuation.definition.comment.js.jsx" @@ -4143,7 +4143,7 @@ "patterns": [ { "name": "entity.other.attribute-name.directive.js.jsx", - "match": "path|types|no-default-lib|name" + "match": "path|types|no-default-lib|lib|name" }, { "name": "keyword.operator.assignment.js.jsx", @@ -4664,8 +4664,8 @@ ] }, "jsx-tag-in-expression": { - "begin": "(?x)\n (?:*]|&&|\\|\\||\\?|^return|[^\\._$[:alnum:]]return|^default|[^\\._$[:alnum:]]default|^)\\s*\n (?!<\\s*[_$[:alpha:]][_$[:alnum:]]*((\\s+extends\\s+[^=>])|,)) # look ahead is not type parameter of arrow\n (?=(<)\\s*(?:([_$a-zA-Z][-$\\w.]*)(?\\,\\.\\[=]|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[=]|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?!=)\\>)*(?!=)>\\s*)|(\\s+))(?!\\?)|\\/?>))", - "end": "(?!(<)\\s*(?:([_$a-zA-Z][-$\\w.]*)(?\\,\\.\\[=]|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[=]|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?!=)\\>)*(?!=)>\\s*)|(\\s+))(?!\\?)|\\/?>))", + "begin": "(?x)\n (?:*]|&&|\\|\\||\\?|^return|[^\\._$[:alnum:]]return|^default|[^\\._$[:alnum:]]default|^)\\s*\n (?!<\\s*[_$[:alpha:]][_$[:alnum:]]*((\\s+extends\\s+[^=>])|,)) # look ahead is not type parameter of arrow\n (?=(<)\\s*(?:([_$a-zA-Z][-$\\w.]*)(?))", + "end": "(?!(<)\\s*(?:([_$a-zA-Z][-$\\w.]*)(?))", "patterns": [ { "include": "#jsx-tag" @@ -4674,7 +4674,7 @@ }, "jsx-tag": { "name": "meta.tag.js.jsx", - "begin": "(?=(<)\\s*(?:([_$a-zA-Z][-$\\w.]*)(?\\,\\.\\[=]|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[=]|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?!=)\\>)*(?!=)>\\s*)|(\\s+))(?!\\?)|\\/?>))", + "begin": "(?=(<)\\s*(?:([_$a-zA-Z][-$\\w.]*)(?))", "end": "(/>)|(?:())", "endCaptures": { "1": { @@ -4701,7 +4701,7 @@ }, "patterns": [ { - "begin": "(<)\\s*(?:([_$a-zA-Z][-$\\w.]*)(?\\,\\.\\[=]|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[=]|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?!=)\\>)*(?!=)>\\s*)|(\\s+))(?!\\?)|\\/?>)", + "begin": "(<)\\s*(?:([_$a-zA-Z][-$\\w.]*)(?)", "beginCaptures": { "1": { "name": "punctuation.definition.tag.begin.js.jsx" diff --git a/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json b/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json index c56e185f844..bee4c021973 100644 --- a/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json +++ b/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/7bf8960f7042474b10b519f39339fc527907ce16", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/40288b872220e5c0b844b1de507f1749ed14589b", "name": "TypeScript", "scopeName": "source.ts", "patterns": [ @@ -4149,7 +4149,7 @@ }, "directives": { "name": "comment.line.triple-slash.directive.ts", - "begin": "^(///)\\s*(?=<(reference|amd-dependency|amd-module)(\\s+(path|types|no-default-lib|name)\\s*=\\s*((\\'([^\\'\\\\]|\\\\\\'|\\\\)*\\')|(\\\"([^\\\"\\\\]|\\\\\\\"|\\\\)*\\\")))+\\s*/>\\s*$)", + "begin": "^(///)\\s*(?=<(reference|amd-dependency|amd-module)(\\s+(path|types|no-default-lib|lib|name)\\s*=\\s*((\\'([^\\'\\\\]|\\\\\\'|\\\\)*\\')|(\\\"([^\\\"\\\\]|\\\\\\\"|\\\\)*\\\")))+\\s*/>\\s*$)", "beginCaptures": { "1": { "name": "punctuation.definition.comment.ts" @@ -4177,7 +4177,7 @@ "patterns": [ { "name": "entity.other.attribute-name.directive.ts", - "match": "path|types|no-default-lib|name" + "match": "path|types|no-default-lib|lib|name" }, { "name": "keyword.operator.assignment.ts", diff --git a/extensions/typescript-basics/syntaxes/TypeScriptReact.tmLanguage.json b/extensions/typescript-basics/syntaxes/TypeScriptReact.tmLanguage.json index 8ef6980ec42..441f07d6abd 100644 --- a/extensions/typescript-basics/syntaxes/TypeScriptReact.tmLanguage.json +++ b/extensions/typescript-basics/syntaxes/TypeScriptReact.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/7bf8960f7042474b10b519f39339fc527907ce16", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/88217b1c7d36ed5d35adc3099ba7978aabe2531b", "name": "TypeScriptReact", "scopeName": "source.tsx", "patterns": [ @@ -4115,7 +4115,7 @@ }, "directives": { "name": "comment.line.triple-slash.directive.tsx", - "begin": "^(///)\\s*(?=<(reference|amd-dependency|amd-module)(\\s+(path|types|no-default-lib|name)\\s*=\\s*((\\'([^\\'\\\\]|\\\\\\'|\\\\)*\\')|(\\\"([^\\\"\\\\]|\\\\\\\"|\\\\)*\\\")))+\\s*/>\\s*$)", + "begin": "^(///)\\s*(?=<(reference|amd-dependency|amd-module)(\\s+(path|types|no-default-lib|lib|name)\\s*=\\s*((\\'([^\\'\\\\]|\\\\\\'|\\\\)*\\')|(\\\"([^\\\"\\\\]|\\\\\\\"|\\\\)*\\\")))+\\s*/>\\s*$)", "beginCaptures": { "1": { "name": "punctuation.definition.comment.tsx" @@ -4143,7 +4143,7 @@ "patterns": [ { "name": "entity.other.attribute-name.directive.tsx", - "match": "path|types|no-default-lib|name" + "match": "path|types|no-default-lib|lib|name" }, { "name": "keyword.operator.assignment.tsx", @@ -4664,8 +4664,8 @@ ] }, "jsx-tag-in-expression": { - "begin": "(?x)\n (?:*]|&&|\\|\\||\\?|^return|[^\\._$[:alnum:]]return|^default|[^\\._$[:alnum:]]default|^)\\s*\n (?!<\\s*[_$[:alpha:]][_$[:alnum:]]*((\\s+extends\\s+[^=>])|,)) # look ahead is not type parameter of arrow\n (?=(<)\\s*(?:([_$a-zA-Z][-$\\w.]*)(?\\,\\.\\[=]|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[=]|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?!=)\\>)*(?!=)>\\s*)|(\\s+))(?!\\?)|\\/?>))", - "end": "(?!(<)\\s*(?:([_$a-zA-Z][-$\\w.]*)(?\\,\\.\\[=]|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[=]|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?!=)\\>)*(?!=)>\\s*)|(\\s+))(?!\\?)|\\/?>))", + "begin": "(?x)\n (?:*]|&&|\\|\\||\\?|^return|[^\\._$[:alnum:]]return|^default|[^\\._$[:alnum:]]default|^)\\s*\n (?!<\\s*[_$[:alpha:]][_$[:alnum:]]*((\\s+extends\\s+[^=>])|,)) # look ahead is not type parameter of arrow\n (?=(<)\\s*(?:([_$a-zA-Z][-$\\w.]*)(?))", + "end": "(?!(<)\\s*(?:([_$a-zA-Z][-$\\w.]*)(?))", "patterns": [ { "include": "#jsx-tag" @@ -4674,7 +4674,7 @@ }, "jsx-tag": { "name": "meta.tag.tsx", - "begin": "(?=(<)\\s*(?:([_$a-zA-Z][-$\\w.]*)(?\\,\\.\\[=]|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[=]|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?!=)\\>)*(?!=)>\\s*)|(\\s+))(?!\\?)|\\/?>))", + "begin": "(?=(<)\\s*(?:([_$a-zA-Z][-$\\w.]*)(?))", "end": "(/>)|(?:())", "endCaptures": { "1": { @@ -4701,7 +4701,7 @@ }, "patterns": [ { - "begin": "(<)\\s*(?:([_$a-zA-Z][-$\\w.]*)(?\\,\\.\\[=]|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[=]|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?!=)\\>)*(?!=)>\\s*)|(\\s+))(?!\\?)|\\/?>)", + "begin": "(<)\\s*(?:([_$a-zA-Z][-$\\w.]*)(?)", "beginCaptures": { "1": { "name": "punctuation.definition.tag.begin.tsx" From eb6ef5239829e3bee700f7eb6893e9fc9f723f91 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 3 Jul 2018 16:21:10 -0700 Subject: [PATCH 128/283] Clean up file - remove use strict - Use property shorthand - Prefix unused with _ --- src/vs/base/browser/ui/octiconLabel/octiconLabel.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/vs/base/browser/ui/octiconLabel/octiconLabel.ts b/src/vs/base/browser/ui/octiconLabel/octiconLabel.ts index e55a3c61e92..0b1154a98fd 100644 --- a/src/vs/base/browser/ui/octiconLabel/octiconLabel.ts +++ b/src/vs/base/browser/ui/octiconLabel/octiconLabel.ts @@ -2,14 +2,13 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; import 'vs/css!./octicons/octicons'; import 'vs/css!./octicons/octicons-animations'; import { escape } from 'vs/base/common/strings'; function expand(text: string): string { - return text.replace(/\$\(((.+?)(~(.*?))?)\)/g, (match, g1, name, g3, animation) => { + return text.replace(/\$\(((.+?)(~(.*?))?)\)/g, (_match, _g1, name, _g3, animation) => { return ``; }); } @@ -20,11 +19,9 @@ export function renderOcticons(label: string): string { export class OcticonLabel { - private readonly _container: HTMLElement; - - constructor(container: HTMLElement) { - this._container = container; - } + constructor( + private readonly _container: HTMLElement + ) { } set text(text: string) { this._container.innerHTML = renderOcticons(text || ''); From 15f51b3c42564affc82c502a276b62d5bc233876 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 3 Jul 2018 17:18:46 -0700 Subject: [PATCH 129/283] Include organize imports actions in vscode.executeCodeActionProvider results Fixes 53512 --- src/vs/editor/contrib/codeAction/codeAction.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/codeAction/codeAction.ts b/src/vs/editor/contrib/codeAction/codeAction.ts index c13ef19a29d..af07775c903 100644 --- a/src/vs/editor/contrib/codeAction/codeAction.ts +++ b/src/vs/editor/contrib/codeAction/codeAction.ts @@ -88,5 +88,5 @@ registerLanguageCommand('_executeCodeActionProvider', function (accessor, args) throw illegalArgument(); } - return getCodeActions(model, model.validateRange(range), undefined); + return getCodeActions(model, model.validateRange(range), { type: CodeActionTrigger.Manual, filter: { includeSourceActions: true } }); }); From 750558c96aaa513dc463475512d12f9827ec285d Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 3 Jul 2018 17:32:39 -0700 Subject: [PATCH 130/283] Use correct type --- src/vs/editor/contrib/codeAction/codeAction.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/codeAction/codeAction.ts b/src/vs/editor/contrib/codeAction/codeAction.ts index af07775c903..983a88f179a 100644 --- a/src/vs/editor/contrib/codeAction/codeAction.ts +++ b/src/vs/editor/contrib/codeAction/codeAction.ts @@ -88,5 +88,5 @@ registerLanguageCommand('_executeCodeActionProvider', function (accessor, args) throw illegalArgument(); } - return getCodeActions(model, model.validateRange(range), { type: CodeActionTrigger.Manual, filter: { includeSourceActions: true } }); + return getCodeActions(model, model.validateRange(range), { type: 'manual', filter: { includeSourceActions: true } }); }); From 69df745be9e4d30da7dc05dc1edf44ee3ae58bcc Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 3 Jul 2018 18:45:12 -0700 Subject: [PATCH 131/283] Fix build --- extensions/search-rg/src/ripgrepTextSearch.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/search-rg/src/ripgrepTextSearch.ts b/extensions/search-rg/src/ripgrepTextSearch.ts index 711f985a8db..728365bff17 100644 --- a/extensions/search-rg/src/ripgrepTextSearch.ts +++ b/extensions/search-rg/src/ripgrepTextSearch.ts @@ -74,7 +74,7 @@ export class RipgrepTextSearchEngine { }); this.ripgrepParser.on('hitLimit', () => { - cancel(); + this.cancel(); }); this.rgProc.stdout.on('data', data => { From 3a0ae3683ba94c6888a8c102390753637baa48e9 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 3 Jul 2018 18:53:43 -0700 Subject: [PATCH 132/283] Search Provider - docs for registerSearchProvider --- src/vs/vscode.proposed.d.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index abdd4552dc0..52a4abab7d6 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -192,7 +192,17 @@ declare module 'vscode' { } export namespace workspace { + /** + * Register a search provider. + * + * Only one provider can be registered per scheme. + * + * @param scheme The provider will be invoked for workspace folders that have this file scheme. + * @param provider The provider. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. + */ export function registerSearchProvider(scheme: string, provider: SearchProvider): Disposable; + export function findTextInFiles(query: TextSearchQuery, options: FindTextInFilesOptions, callback: (result: TextSearchResult) => void, token?: CancellationToken): Thenable; } From 0215f30d9bea77c631370d4407fe9e5deaf731b8 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 3 Jul 2018 20:08:53 -0700 Subject: [PATCH 133/283] Search Provider - implement clearCache --- extensions/search-rg/src/extension.ts | 4 + src/vs/platform/search/common/search.ts | 1 + src/vs/vscode.proposed.d.ts | 7 ++ .../api/electron-browser/mainThreadSearch.ts | 8 +- src/vs/workbench/api/node/extHost.protocol.ts | 1 + src/vs/workbench/api/node/extHostSearch.ts | 76 ++++++++++++++----- .../services/search/node/searchService.ts | 7 +- 7 files changed, 83 insertions(+), 21 deletions(-) diff --git a/extensions/search-rg/src/extension.ts b/extensions/search-rg/src/extension.ts index f5300c2d2bc..f999cb0cb6c 100644 --- a/extensions/search-rg/src/extension.ts +++ b/extensions/search-rg/src/extension.ts @@ -37,6 +37,10 @@ class RipgrepSearchProvider implements vscode.SearchProvider { return this.withEngine(engine, () => this.cachedProvider.provideFileSearchResults(engine, query, options, progress, token)); } + clearCache(cacheKey: string): void { + this.cachedProvider.clearCache(cacheKey); + } + private withEngine(engine: SearchEngine, fn: () => Thenable): Thenable { this.inProgress.add(engine); return fn().then(() => { diff --git a/src/vs/platform/search/common/search.ts b/src/vs/platform/search/common/search.ts index 2e1e6dcab87..91ad26c83c2 100644 --- a/src/vs/platform/search/common/search.ts +++ b/src/vs/platform/search/common/search.ts @@ -47,6 +47,7 @@ export interface ISearchHistoryService { export interface ISearchResultProvider { search(query: ISearchQuery): PPromise; + clearCache(cacheKey: string): TPromise; } export interface IFolderQuery { diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 52a4abab7d6..21c791c6d07 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -117,6 +117,7 @@ declare module 'vscode' { /** * `cacheKey` has the same value when `provideFileSearchResults` is invoked multiple times during a single quickopen session. * Providers can optionally use this to cache results at the beginning of a quickopen session and filter results as the user types. + * It will have a different value for each folder searched. */ cacheKey?: string; } @@ -172,6 +173,12 @@ declare module 'vscode' { */ provideFileSearchResults?(query: FileSearchQuery, options: FileSearchOptions, progress: Progress, token: CancellationToken): Thenable; + /** + * Optional - if the provider makes use of `query.cacheKey`, it can implement this method which is invoked when the cache can be cleared. + * @param cacheKey The same key that was passed as `query.cacheKey`. + */ + clearCache?(cacheKey: string): void; + /** * Provide results that match the given text pattern. * @param query The parameters for this query. diff --git a/src/vs/workbench/api/electron-browser/mainThreadSearch.ts b/src/vs/workbench/api/electron-browser/mainThreadSearch.ts index 08e831aa481..7e3224c1de2 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadSearch.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadSearch.ts @@ -29,7 +29,7 @@ export class MainThreadSearch implements MainThreadSearchShape { } dispose(): void { - this._searchProvider.forEach(value => dispose()); + this._searchProvider.forEach(value => value.dispose()); this._searchProvider.clear(); } @@ -79,7 +79,7 @@ class SearchOperation { } } -class RemoteSearchProvider implements ISearchResultProvider { +class RemoteSearchProvider implements ISearchResultProvider, IDisposable { private readonly _registrations: IDisposable[]; private readonly _searches = new Map(); @@ -138,6 +138,10 @@ class RemoteSearchProvider implements ISearchResultProvider { }); } + clearCache(cacheKey: string): TPromise { + return this._proxy.$clearCache(this._handle, cacheKey); + } + handleFindMatch(session: number, dataOrUri: (UriComponents | IRawFileMatch2)[]): void { if (!this._searches.has(session)) { // ignore... diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 923fb9b16df..053a2e768b6 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -682,6 +682,7 @@ export interface ExtHostFileSystemShape { export interface ExtHostSearchShape { $provideFileSearchResults(handle: number, session: number, query: IRawSearchQuery): TPromise; + $clearCache(handle: number, cacheKey: string): TPromise; $provideTextSearchResults(handle: number, session: number, pattern: IPatternInfo, query: IRawSearchQuery): TPromise; } diff --git a/src/vs/workbench/api/node/extHostSearch.ts b/src/vs/workbench/api/node/extHostSearch.ts index 7fe3e6eca15..d35c2401007 100644 --- a/src/vs/workbench/api/node/extHostSearch.ts +++ b/src/vs/workbench/api/node/extHostSearch.ts @@ -69,6 +69,16 @@ export class ExtHostSearch implements ExtHostSearchShape { }); } + $clearCache(handle: number, cacheKey: string): TPromise { + const provider = this._searchProvider.get(handle); + if (!provider.clearCache) { + return TPromise.as(undefined); + } + + return TPromise.as( + this._fileSearchManager.clearCache(cacheKey, provider)); + } + $provideTextSearchResults(handle: number, session: number, pattern: IPatternInfo, rawQuery: IRawSearchQuery): TPromise { const provider = this._searchProvider.get(handle); if (!provider.provideTextSearchResults) { @@ -530,10 +540,10 @@ class FileSearchEngine { this.activeCancellationTokens = new Set(); } - public search(): PPromise<{ isLimitHit: boolean }, IInternalFileMatch> { + public search(): PPromise { const folderQueries = this.config.folderQueries; - return new PPromise<{ isLimitHit: boolean }, IInternalFileMatch>((resolve, reject, _onResult) => { + return new PPromise((resolve, reject, _onResult) => { const onResult = (match: IInternalFileMatch) => { this.resultCount++; _onResult(match); @@ -541,7 +551,7 @@ class FileSearchEngine { // Support that the file pattern is a full path to a file that exists if (this.isCanceled) { - return resolve({ isLimitHit: this.isLimitHit }); + return resolve({ limitHit: this.isLimitHit, cacheKeys: [] }); } // For each extra file @@ -562,8 +572,8 @@ class FileSearchEngine { // For each root folder PPromise.join(folderQueries.map(fq => { return this.searchInFolder(fq).then(null, null, onResult); - })).then(() => { - resolve({ isLimitHit: this.isLimitHit }); + })).then(cacheKeys => { + resolve({ limitHit: this.isLimitHit, cacheKeys }); }, (errs: Error[]) => { const errMsg = errs .map(err => toErrorMessage(err)) @@ -574,7 +584,7 @@ class FileSearchEngine { }); } - private searchInFolder(fq: IFolderQuery): PPromise { + private searchInFolder(fq: IFolderQuery): PPromise { let cancellation = new CancellationTokenSource(); return new PPromise((resolve, reject, onResult) => { const options = this.getSearchOptionsForFolder(fq); @@ -601,13 +611,17 @@ class FileSearchEngine { this.addDirectoryEntries(tree, fq.folder, relativePath, onResult); }; - new TPromise(resolve => process.nextTick(resolve)) + let folderCacheKey: string; + new TPromise(_resolve => process.nextTick(_resolve)) .then(() => { this.activeCancellationTokens.add(cancellation); + + folderCacheKey = this.config.cacheKey && (this.config.cacheKey + '_' + fq.folder.fsPath); + return this.provider.provideFileSearchResults( { pattern: this.config.filePattern || '', - cacheKey: this.config.cacheKey + '_' + fq.folder.fsPath + cacheKey: folderCacheKey }, options, { report: onProviderResult }, @@ -637,7 +651,7 @@ class FileSearchEngine { }).then( () => { cancellation.dispose(); - resolve(undefined); + resolve(folderCacheKey); }, err => { cancellation.dispose(); @@ -775,19 +789,38 @@ class FileSearchEngine { } } +interface IInternalSearchComplete { + limitHit: boolean; + cacheKeys: string[]; +} + class FileSearchManager { private static readonly BATCH_SIZE = 512; + private readonly expandedCacheKeys = new Map(); + constructor(private _pfs: typeof pfs) { } - public fileSearch(config: ISearchQuery, provider: vscode.SearchProvider): PPromise { + fileSearch(config: ISearchQuery, provider: vscode.SearchProvider): PPromise { let searchP: PPromise; return new PPromise((c, e, p) => { const engine = new FileSearchEngine(config, provider, this._pfs); - searchP = this.doSearch(engine, provider, FileSearchManager.BATCH_SIZE).then(c, e, progress => { - p(progress.map(m => this.rawMatchToSearchItem(m))); - }); + + searchP = this.doSearch(engine, FileSearchManager.BATCH_SIZE).then( + result => { + if (config.cacheKey) { + this.expandedCacheKeys.set(config.cacheKey, result.cacheKeys); + } + + c({ + limitHit: result.limitHit + }); + }, + e, + progress => { + p(progress.map(m => this.rawMatchToSearchItem(m))); + }); }, () => { if (searchP) { searchP.cancel(); @@ -795,23 +828,30 @@ class FileSearchManager { }); } + clearCache(cacheKey: string, provider: vscode.SearchProvider): void { + if (!this.expandedCacheKeys.has(cacheKey)) { + return; + } + + this.expandedCacheKeys.get(cacheKey).forEach(key => provider.clearCache(key)); + this.expandedCacheKeys.delete(cacheKey); + } + private rawMatchToSearchItem(match: IInternalFileMatch): IFileMatch { return { resource: resources.joinPath(match.base, match.relativePath) }; } - private doSearch(engine: FileSearchEngine, provider: vscode.SearchProvider, batchSize: number): PPromise { - return new PPromise((c, e, p) => { + private doSearch(engine: FileSearchEngine, batchSize: number): PPromise { + return new PPromise((c, e, p) => { let batch: IInternalFileMatch[] = []; engine.search().then(result => { if (batch.length) { p(batch); } - c({ - limitHit: result.isLimitHit - }); + c(result); }, error => { if (batch.length) { p(batch); diff --git a/src/vs/workbench/services/search/node/searchService.ts b/src/vs/workbench/services/search/node/searchService.ts index e7159cee743..cc292c17701 100644 --- a/src/vs/workbench/services/search/node/searchService.ts +++ b/src/vs/workbench/services/search/node/searchService.ts @@ -258,7 +258,12 @@ export class SearchService implements ISearchService { } public clearCache(cacheKey: string): TPromise { - return this.diskSearch.clearCache(cacheKey); + return TPromise.join([ + ...this.searchProviders, + this.fileSearchProvider, + this.diskSearch + ].map(provider => provider && provider.clearCache(cacheKey))) + .then(() => { }); } private forwardTelemetry() { From 2315156bf3f605b3ead55eeb003a8fad567369db Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 3 Jul 2018 20:11:35 -0700 Subject: [PATCH 134/283] Search Provider - delete unused search stats --- extensions/search-rg/src/cachedSearchProvider.ts | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/extensions/search-rg/src/cachedSearchProvider.ts b/extensions/search-rg/src/cachedSearchProvider.ts index 85012b70552..8dba32717e1 100644 --- a/extensions/search-rg/src/cachedSearchProvider.ts +++ b/extensions/search-rg/src/cachedSearchProvider.ts @@ -95,7 +95,7 @@ export class CachedSearchProvider { const cached = this.getResultsFromCache(cache, args.query.pattern, onResult); if (cached) { - return cached.then(([results, cacheStats]) => this.sortResults(args, results, cache.scorerCache)); + return cached.then((results) => this.sortResults(args, results, cache.scorerCache)); } return undefined; @@ -112,7 +112,7 @@ export class CachedSearchProvider { return arrays.topAsync(results, compare, args.options.maxResults || 0, 10000); } - private getResultsFromCache(cache: Cache, searchValue: string, onResult: (results: IInternalFileMatch) => void): Promise<[IInternalFileMatch[], CacheStats]> { + private getResultsFromCache(cache: Cache, searchValue: string, onResult: (results: IInternalFileMatch) => void): Promise { // Find cache entries by prefix of search value const hasPathSep = searchValue.indexOf(path.sep) >= 0; let cached: CacheEntry; @@ -154,11 +154,7 @@ export class CachedSearchProvider { results.push(entry); } - c([results, { - cacheWasResolved: wasResolved, - cacheFilterStartTime: cacheFilterStartTime, - cacheFilterResultCount: cachedEntries.length - }]); + c(results); }, e); }); } @@ -231,9 +227,3 @@ const FileMatchItemAccessor = new class implements IItemAccessor Date: Tue, 3 Jul 2018 20:46:32 -0700 Subject: [PATCH 135/283] findTextInFiles - change includes/excludes from array to single object to match findFiles --- src/vs/vscode.proposed.d.ts | 4 ++-- src/vs/workbench/api/node/extHostWorkspace.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 21c791c6d07..d378593816e 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -190,8 +190,8 @@ declare module 'vscode' { } export interface FindTextInFilesOptions { - includes?: GlobPattern[]; - excludes?: GlobPattern[]; + include?: GlobPattern; + exclude?: GlobPattern; maxResults?: number; useIgnoreFiles?: boolean; followSymlinks?: boolean; diff --git a/src/vs/workbench/api/node/extHostWorkspace.ts b/src/vs/workbench/api/node/extHostWorkspace.ts index 536b4e58f9e..2afd565ed8a 100644 --- a/src/vs/workbench/api/node/extHostWorkspace.ts +++ b/src/vs/workbench/api/node/extHostWorkspace.ts @@ -386,12 +386,12 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { const queryOptions: IQueryOptions = { ignoreSymlinks: typeof options.followSymlinks === 'boolean' ? !options.followSymlinks : undefined, disregardIgnoreFiles: typeof options.useIgnoreFiles === 'boolean' ? !options.useIgnoreFiles : undefined, - disregardExcludeSettings: options.excludes === null, + disregardExcludeSettings: options.exclude === null, fileEncoding: options.encoding, maxResults: options.maxResults, - includePattern: options.includes && options.includes.map(include => globPatternToString(include)).join(', '), - excludePattern: options.excludes && options.excludes.map(exclude => globPatternToString(exclude)).join(', ') + includePattern: options.include && globPatternToString(options.include), + excludePattern: options.exclude && globPatternToString(options.exclude) }; this._activeSearchCallbacks[requestId] = p => { From 185278a3fa02f08d2b2b089786186e9e4e8a167f Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 3 Jul 2018 20:46:54 -0700 Subject: [PATCH 136/283] findTextInFiles vscode-api-tests test --- .../src/singlefolder-tests/workspace.test.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index 1f42a34e597..9d50cafbb17 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -511,6 +511,16 @@ suite('workspace-namespace', () => { // }); // }); + test('findTextInFiles', async () => { + const results: vscode.TextSearchResult[] = []; + await vscode.workspace.findTextInFiles({ pattern: 'foo' }, { include: '*.ts' }, result => { + results.push(result); + }); + + assert.equal(results.length, 1); + assert.equal(vscode.workspace.asRelativePath(results[0].uri), '10linefile.ts'); + }); + test('applyEdit', () => { return vscode.workspace.openTextDocument(vscode.Uri.parse('untitled:' + join(vscode.workspace.rootPath || '', './new2.txt'))).then(doc => { From 6a40aa296e62142ee2c0d3df1772650069d899c3 Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Tue, 3 Jul 2018 23:25:22 -0700 Subject: [PATCH 137/283] register disposables for menubarPart (#53467) * register disposables * adding missing disposables --- src/vs/base/browser/ui/menu/menu.ts | 9 ++++ .../browser/parts/menubar/menubarPart.ts | 52 +++++++++---------- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index 5234c457c62..fe7be1d3a35 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -260,4 +260,13 @@ class SubmenuActionItem extends MenuActionItem { this.mysubmenu = this.parentData.submenu; } } + + public dispose() { + super.dispose(); + + if (this.mysubmenu) { + this.mysubmenu.dispose(); + this.mysubmenu = null; + } + } } \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/menubar/menubarPart.ts b/src/vs/workbench/browser/parts/menubar/menubarPart.ts index e5cd5b529af..31c84306e65 100644 --- a/src/vs/workbench/browser/parts/menubar/menubarPart.ts +++ b/src/vs/workbench/browser/parts/menubar/menubarPart.ts @@ -120,33 +120,33 @@ export class MenubarPart extends Part { super(id, { hasTitle: false }, themeService); this.topLevelMenus = { - 'File': this.menuService.createMenu(MenuId.MenubarFileMenu, this.contextKeyService), - 'Edit': this.menuService.createMenu(MenuId.MenubarEditMenu, this.contextKeyService), - 'Selection': this.menuService.createMenu(MenuId.MenubarSelectionMenu, this.contextKeyService), - 'View': this.menuService.createMenu(MenuId.MenubarViewMenu, this.contextKeyService), - 'Go': this.menuService.createMenu(MenuId.MenubarGoMenu, this.contextKeyService), - 'Terminal': this.menuService.createMenu(MenuId.MenubarTerminalMenu, this.contextKeyService), - 'Debug': this.menuService.createMenu(MenuId.MenubarDebugMenu, this.contextKeyService), - 'Tasks': this.menuService.createMenu(MenuId.MenubarTasksMenu, this.contextKeyService), - 'Help': this.menuService.createMenu(MenuId.MenubarHelpMenu, this.contextKeyService) + 'File': this._register(this.menuService.createMenu(MenuId.MenubarFileMenu, this.contextKeyService)), + 'Edit': this._register(this.menuService.createMenu(MenuId.MenubarEditMenu, this.contextKeyService)), + 'Selection': this._register(this.menuService.createMenu(MenuId.MenubarSelectionMenu, this.contextKeyService)), + 'View': this._register(this.menuService.createMenu(MenuId.MenubarViewMenu, this.contextKeyService)), + 'Go': this._register(this.menuService.createMenu(MenuId.MenubarGoMenu, this.contextKeyService)), + 'Terminal': this._register(this.menuService.createMenu(MenuId.MenubarTerminalMenu, this.contextKeyService)), + 'Debug': this._register(this.menuService.createMenu(MenuId.MenubarDebugMenu, this.contextKeyService)), + 'Tasks': this._register(this.menuService.createMenu(MenuId.MenubarTasksMenu, this.contextKeyService)), + 'Help': this._register(this.menuService.createMenu(MenuId.MenubarHelpMenu, this.contextKeyService)) }; if (isMacintosh) { - this.topLevelMenus['Window'] = this.menuService.createMenu(MenuId.MenubarWindowMenu, this.contextKeyService); + this.topLevelMenus['Window'] = this._register(this.menuService.createMenu(MenuId.MenubarWindowMenu, this.contextKeyService)); } - this.actionRunner = new ActionRunner(); - this.actionRunner.onDidBeforeRun(() => { + this.actionRunner = this._register(new ActionRunner()); + this._register(this.actionRunner.onDidBeforeRun(() => { if (this.focusedMenu && this.focusedMenu.holder) { this.focusedMenu.holder.hide(); } - }); + })); - this._onVisibilityChange = new Emitter(); + this._onVisibilityChange = this._register(new Emitter()); if (isMacintosh || this.currentTitlebarStyleSetting !== 'custom') { for (let topLevelMenuName of Object.keys(this.topLevelMenus)) { - this.topLevelMenus[topLevelMenuName].onDidChange(() => this.setupMenubar()); + this._register(this.topLevelMenus[topLevelMenuName].onDidChange(() => this.setupMenubar())); } this.setupMenubar(); } @@ -274,21 +274,21 @@ export class MenubarPart extends Part { } private registerListeners(): void { - browser.onDidChangeFullscreen(() => this.onDidChangeFullscreen()); + this._register(browser.onDidChangeFullscreen(() => this.onDidChangeFullscreen())); // Update when config changes - this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e)); + this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e))); // Listen to update service // this.updateService.onStateChange(() => this.setupMenubar()); // Listen for changes in recently opened menu - this.windowsService.onRecentlyOpenedChange(() => { this.onRecentlyOpenedChange(); }); + this._register(this.windowsService.onRecentlyOpenedChange(() => { this.onRecentlyOpenedChange(); })); // Listen to keybindings change - this.keybindingService.onDidUpdateKeybindings(() => this.setupMenubar()); + this._register(this.keybindingService.onDidUpdateKeybindings(() => this.setupMenubar())); - ModifierKeyEmitter.getInstance().event(this.onModifierKeyToggled, this); + this._register(ModifierKeyEmitter.getInstance().event(this.onModifierKeyToggled, this)); } private setupMenubar(): void { @@ -486,7 +486,7 @@ export class MenubarPart extends Part { }; this.customMenus[menuIndex].actions = []; - menu.onDidChange(() => updateActions(menu, this.customMenus[menuIndex].actions)); + this._register(menu.onDidChange(() => updateActions(menu, this.customMenus[menuIndex].actions))); updateActions(menu, this.customMenus[menuIndex].actions); this.customMenus[menuIndex].titleElement.on(EventType.CLICK, () => { @@ -662,19 +662,19 @@ export class MenubarPart extends Part { // actionItemProvider: (action) => { return this._getActionItem(action); } }; - let menuWidget = new Menu(menuHolder.getHTMLElement(), customMenu.actions, menuOptions); + let menuWidget = this._register(new Menu(menuHolder.getHTMLElement(), customMenu.actions, menuOptions)); - menuWidget.onDidCancel(() => { + this._register(menuWidget.onDidCancel(() => { this.cleanupCustomMenu(); this.isFocused = false; - }); + })); - menuWidget.onDidBlur(() => { + this._register(menuWidget.onDidBlur(() => { setTimeout(() => { this.cleanupCustomMenu(); this.isFocused = false; }, 100); - }); + })); menuWidget.focus(); From 7d277787b4c58bcbe69befef2dda0055198c3289 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 3 Jul 2018 18:06:06 +0200 Subject: [PATCH 138/283] fix bad smoketest launcher --- test/smoke/test/index.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/smoke/test/index.js b/test/smoke/test/index.js index 69bb655de6e..71023e7e91f 100644 --- a/test/smoke/test/index.js +++ b/test/smoke/test/index.js @@ -5,13 +5,22 @@ const path = require('path'); const Mocha = require('mocha'); +const minimist = require('minimist'); const suite = 'Smoke Tests'; +const [, , ...args] = process.argv; +const opts = minimist(args, { + string: [ + 'f' + ] +}); + const options = { useColors: true, timeout: 60000, - slow: 30000 + slow: 30000, + grep: opts['f'] }; if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) { From dedf22a55c71b1fafab4d45f8ff30e9741cafb63 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 4 Jul 2018 08:35:25 +0200 Subject: [PATCH 139/283] fix NPE --- .../workbench/parts/scm/electron-browser/dirtydiffDecorator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts b/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts index c86f7733749..90a6893cdf4 100644 --- a/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts @@ -1034,7 +1034,7 @@ export class DirtyDiffModel { private getOriginalResource(): TPromise { if (!this._editorModel) { - return null; + return TPromise.as(null); } const uri = this._editorModel.uri; From 0f851d0fd509eb77511c3b00bb195c198bd01432 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 4 Jul 2018 08:47:53 +0200 Subject: [PATCH 140/283] ipc events should not use TPromise --- src/vs/base/parts/ipc/common/ipc.ts | 219 +++++++++++------- .../base/parts/ipc/test/node/testService.ts | 21 +- .../sharedProcess/sharedProcessMain.ts | 12 +- src/vs/code/electron-main/app.ts | 3 +- src/vs/code/electron-main/launch.ts | 5 + src/vs/platform/dialogs/common/dialogIpc.ts | 6 +- src/vs/platform/driver/common/driver.ts | 13 ++ .../driver/electron-browser/driver.ts | 27 +-- .../platform/driver/electron-main/driver.ts | 6 +- .../common/extensionManagementIpc.ts | 43 ++-- src/vs/platform/issue/common/issueIpc.ts | 5 + .../localizations/common/localizationsIpc.ts | 18 +- src/vs/platform/log/common/logIpc.ts | 21 +- src/vs/platform/menubar/common/menubarIpc.ts | 5 + .../platform/telemetry/common/telemetryIpc.ts | 5 + src/vs/platform/update/common/updateIpc.ts | 19 +- src/vs/platform/url/common/urlIpc.ts | 9 + src/vs/platform/windows/common/windowsIpc.ts | 56 ++--- .../workspaces/common/workspacesIpc.ts | 5 + .../files/node/watcher/nsfw/watcherIpc.ts | 5 + .../files/node/watcher/unix/watcherIpc.ts | 5 + .../services/search/node/searchIpc.ts | 5 + .../search/node/worker/searchWorkerIpc.ts | 5 + 23 files changed, 343 insertions(+), 175 deletions(-) diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index a3985361b63..de387bb039b 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -6,17 +6,21 @@ 'use strict'; import { Promise, TPromise } from 'vs/base/common/winjs.base'; -import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { Event, Emitter, once, filterEvent } from 'vs/base/common/event'; +import { IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Event, Emitter, once, filterEvent, toPromise, Relay } from 'vs/base/common/event'; enum MessageType { - RequestCommon, - RequestCancel, + RequestPromise, + RequestPromiseCancel, ResponseInitialize, - ResponseSuccess, - ResponseProgress, - ResponseError, - ResponseErrorObj + ResponsePromiseSuccess, + ResponsePromiseProgress, + ResponsePromiseError, + ResponsePromiseErrorObj, + + RequestEventListen, + RequestEventDispose, + ResponseEventFire, } function isResponse(messageType: MessageType): boolean { @@ -36,7 +40,6 @@ interface IRawRequest extends IRawMessage { interface IRequest { raw: IRawRequest; - emitter?: Emitter; flush?: () => void; } @@ -65,7 +68,8 @@ enum State { * with at most one single return value. */ export interface IChannel { - call(command: string, arg?: any): TPromise; + call(command: string, arg?: any): TPromise; + listen(event: string, arg?: any): Event; } /** @@ -90,7 +94,8 @@ export interface IChannelClient { * channels (each from a separate client) to pick from. */ export interface IClientRouter { - route(command: string, arg: any): string; + routeCall(command: string, arg: any): string; + routeEvent(event: string, arg: any): string; } /** @@ -104,6 +109,8 @@ export interface IRoutingChannelClient { getChannel(channelName: string, router: IClientRouter): T; } +// TODO@joao cleanup this mess! + export class ChannelServer implements IChannelServer, IDisposable { private channels: { [name: string]: IChannel } = Object.create(null); @@ -121,17 +128,22 @@ export class ChannelServer implements IChannelServer, IDisposable { private onMessage(request: IRawRequest): void { switch (request.type) { - case MessageType.RequestCommon: - this.onCommonRequest(request); + case MessageType.RequestPromise: + this.onPromise(request); break; - case MessageType.RequestCancel: - this.onCancelRequest(request); + case MessageType.RequestEventListen: + this.onEventListen(request); + break; + + case MessageType.RequestPromiseCancel: + case MessageType.RequestEventDispose: + this.disposeActiveRequest(request); break; } } - private onCommonRequest(request: IRawRequest): void { + private onPromise(request: IRawRequest): void { const channel = this.channels[request.channelName]; let promise: Promise; @@ -144,7 +156,7 @@ export class ChannelServer implements IChannelServer, IDisposable { const id = request.id; const requestPromise = promise.then(data => { - this.protocol.send({ id, data, type: MessageType.ResponseSuccess }); + this.protocol.send({ id, data, type: MessageType.ResponsePromiseSuccess }); delete this.activeRequests[request.id]; }, data => { if (data instanceof Error) { @@ -153,21 +165,31 @@ export class ChannelServer implements IChannelServer, IDisposable { message: data.message, name: data.name, stack: data.stack ? (data.stack.split ? data.stack.split('\n') : data.stack) : void 0 - }, type: MessageType.ResponseError + }, type: MessageType.ResponsePromiseError }); } else { - this.protocol.send({ id, data, type: MessageType.ResponseErrorObj }); + this.protocol.send({ id, data, type: MessageType.ResponsePromiseErrorObj }); } delete this.activeRequests[request.id]; }, data => { - this.protocol.send({ id, data, type: MessageType.ResponseProgress }); + this.protocol.send({ id, data, type: MessageType.ResponsePromiseProgress }); }); this.activeRequests[request.id] = toDisposable(() => requestPromise.cancel()); } - private onCancelRequest(request: IRawRequest): void { + private onEventListen(request: IRawRequest): void { + const channel = this.channels[request.channelName]; + + const id = request.id; + const event = channel.listen(request.name, request.arg); + const disposable = event(data => this.protocol.send({ id, data, type: MessageType.ResponseEventFire })); + + this.activeRequests[request.id] = disposable; + } + + private disposeActiveRequest(request: IRawRequest): void { const disposable = this.activeRequests[request.id]; if (disposable) { @@ -190,63 +212,85 @@ export class ChannelServer implements IChannelServer, IDisposable { export class ChannelClient implements IChannelClient, IDisposable { - private state: State; - private activeRequests: Promise[]; - private bufferedRequests: IRequest[]; - private handlers: { [id: number]: IHandler; }; - private lastRequestId: number; + private state: State = State.Uninitialized; + private activeRequests: IDisposable[] = []; + private bufferedRequests: IRequest[] = []; + private handlers: { [id: number]: IHandler; } = Object.create(null); + private lastRequestId: number = 0; private protocolListener: IDisposable; + private _onDidInitialize = new Emitter(); + readonly onDidInitialize = this._onDidInitialize.event; + constructor(private protocol: IMessagePassingProtocol) { - this.state = State.Uninitialized; - this.activeRequests = []; - this.bufferedRequests = []; - this.handlers = Object.create(null); - this.lastRequestId = 0; this.protocolListener = this.protocol.onMessage(r => this.onMessage(r)); } getChannel(channelName: string): T { - const call = (command: string, arg: any) => this.request(channelName, command, arg); - return { call } as T; + const call = (command: string, arg: any) => this.requestPromise(channelName, command, arg); + const listen = (event: string, arg: any) => this.requestEvent(channelName, event, arg); + + return { call, listen } as T; } - private request(channelName: string, name: string, arg: any): Promise { - const request = { - raw: { - id: this.lastRequestId++, - type: MessageType.RequestCommon, - channelName, - name, - arg - } - }; + private requestPromise(channelName: string, name: string, arg: any): TPromise { + const id = this.lastRequestId++; + const type = MessageType.RequestPromise; + const request = { raw: { id, type, channelName, name, arg } }; const activeRequest = this.state === State.Uninitialized ? this.bufferRequest(request) : this.doRequest(request); - this.activeRequests.push(activeRequest); + const disposable = toDisposable(() => activeRequest.cancel()); + this.activeRequests.push(disposable); activeRequest .then(null, _ => null) - .done(() => this.activeRequests = this.activeRequests.filter(i => i !== activeRequest)); + .done(() => this.activeRequests = this.activeRequests.filter(el => el !== disposable)); return activeRequest; } + private requestEvent(channelName: string, name: string, arg: any): Event { + const id = this.lastRequestId++; + const type = MessageType.RequestEventListen; + const request = { raw: { id, type, channelName, name, arg } }; + + let uninitializedPromise: TPromise | null = null; + const emitter = new Emitter({ + onFirstListenerAdd: () => { + uninitializedPromise = this.whenInitialized().then(() => { + uninitializedPromise = null; + this.send(request.raw); + }); + }, + onLastListenerRemove: () => { + if (uninitializedPromise) { + uninitializedPromise.cancel(); + uninitializedPromise = null; + } else { + this.send({ id, type: MessageType.RequestEventDispose }); + } + } + }); + + this.handlers[id] = response => emitter.fire(response.data); + return emitter.event; + } + private doRequest(request: IRequest): Promise { const id = request.raw.id; return new TPromise((c, e, p) => { this.handlers[id] = response => { switch (response.type) { - case MessageType.ResponseSuccess: + case MessageType.ResponsePromiseSuccess: delete this.handlers[id]; c(response.data); break; - case MessageType.ResponseError: + case MessageType.ResponsePromiseError: delete this.handlers[id]; const error = new Error(response.data.message); (error).stack = response.data.stack; @@ -254,12 +298,12 @@ export class ChannelClient implements IChannelClient, IDisposable { e(error); break; - case MessageType.ResponseErrorObj: + case MessageType.ResponsePromiseErrorObj: delete this.handlers[id]; e(response.data); break; - case MessageType.ResponseProgress: + case MessageType.ResponsePromiseProgress: p(response.data); break; } @@ -267,7 +311,7 @@ export class ChannelClient implements IChannelClient, IDisposable { this.send(request.raw); }, - () => this.send({ id, type: MessageType.RequestCancel })); + () => this.send({ id, type: MessageType.RequestPromiseCancel })); } private bufferRequest(request: IRequest): Promise { @@ -309,6 +353,7 @@ export class ChannelClient implements IChannelClient, IDisposable { if (this.state === State.Uninitialized && response.type === MessageType.ResponseInitialize) { this.state = State.Idle; + this._onDidInitialize.fire(); this.bufferedRequests.forEach(r => r.flush && r.flush()); this.bufferedRequests = null; return; @@ -328,12 +373,19 @@ export class ChannelClient implements IChannelClient, IDisposable { } } + private whenInitialized(): TPromise { + if (this.state === State.Idle) { + return TPromise.as(null); + } else { + return toPromise(this.onDidInitialize); + } + } + dispose(): void { this.protocolListener.dispose(); this.protocolListener = null; - this.activeRequests.forEach(r => r.cancel()); - this.activeRequests = []; + this.activeRequests = dispose(this.activeRequests); } } @@ -381,16 +433,28 @@ export class IPCServer implements IChannelServer, IRoutingChannelClient, IDispos getChannel(channelName: string, router: IClientRouter): T { const call = (command: string, arg: any) => { - const id = router.route(command, arg); + const id = router.routeCall(command, arg); if (!id) { return TPromise.wrapError(new Error('Client id should be provided')); } - return this.getClient(id).then(client => client.getChannel(channelName).call(command, arg)); + return getDelayedChannel(this.getClient(id).then(client => client.getChannel(channelName))) + .call(command, arg); }; - return { call } as T; + const listen = (event: string, arg: any) => { + const id = router.routeEvent(event, arg); + + if (!id) { + return TPromise.wrapError(new Error('Client id should be provided')); + } + + return getDelayedChannel(this.getClient(id).then(client => client.getChannel(channelName))) + .listen(event, arg); + }; + + return { call, listen } as T; } registerChannel(channelName: string, channel: IChannel): void { @@ -453,7 +517,13 @@ export class IPCClient implements IChannelClient, IChannelServer, IDisposable { export function getDelayedChannel(promise: TPromise): T { const call = (command: string, arg: any) => promise.then(c => c.call(command, arg)); - return { call } as T; + const listen = (event: string, arg: any) => { + const relay = new Relay(); + promise.then(c => relay.input = c.listen(event, arg)); + return relay.event; + }; + + return { call, listen } as T; } export function getNextTickChannel(channel: T): T { @@ -469,34 +539,15 @@ export function getNextTickChannel(channel: T): T { .then(() => channel.call(command, arg)); }; - return { call } as T; -} - -export type Serializer = (obj: T) => R; -export type Deserializer = (raw: R) => T; - -export function eventToCall(event: Event, serializer: Serializer = t => t): TPromise { - let disposable: IDisposable; - - return new TPromise( - (c, e, p) => disposable = event(t => p(serializer(t))), - () => disposable.dispose() - ); -} - -export function eventFromCall(channel: IChannel, name: string, arg: any = null, deserializer: Deserializer = t => t): Event { - let promise: Promise; - - const emitter = new Emitter({ - onFirstListenerAdd: () => { - promise = channel.call(name, arg) - .then(null, err => null, e => emitter.fire(deserializer(e))); - }, - onLastListenerRemove: () => { - promise.cancel(); - promise = null; + const listen = (event: string, arg: any) => { + if (didTick) { + return channel.listen(event, arg); } - }); - return emitter.event; + return TPromise.timeout(0) + .then(() => didTick = true) + .then(() => channel.listen(event, arg)); + }; + + return { call, listen } as T; } diff --git a/src/vs/base/parts/ipc/test/node/testService.ts b/src/vs/base/parts/ipc/test/node/testService.ts index 453ea9c85e0..a53ff5ed37b 100644 --- a/src/vs/base/parts/ipc/test/node/testService.ts +++ b/src/vs/base/parts/ipc/test/node/testService.ts @@ -5,7 +5,7 @@ 'use strict'; import { TPromise, PPromise } from 'vs/base/common/winjs.base'; -import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc'; +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event, Emitter } from 'vs/base/common/event'; export interface IMarcoPoloEvent { @@ -68,6 +68,9 @@ export class TestService implements ITestService { } export interface ITestChannel extends IChannel { + listen(event: 'marco'): Event; + listen(event: string, arg?: any): Event; + call(command: 'marco'): TPromise; call(command: 'pong', ping: string): TPromise; call(command: 'cancelMe'): TPromise; @@ -79,12 +82,19 @@ export class TestChannel implements ITestChannel { constructor(private testService: ITestService) { } + listen(event: string, arg?: any): Event { + switch (event) { + case 'marco': return this.testService.onMarco; + } + + throw new Error('Event not found'); + } + call(command: string, ...args: any[]): TPromise { switch (command) { case 'pong': return this.testService.pong(args[0]); case 'cancelMe': return this.testService.cancelMe(); case 'marco': return this.testService.marco(); - case 'event:marco': return eventToCall(this.testService.onMarco); case 'batchPerf': return this.testService.batchPerf(args[0].batches, args[0].size, args[0].dataSize); default: return TPromise.wrapError(new Error('command not found')); } @@ -93,12 +103,9 @@ export class TestChannel implements ITestChannel { export class TestServiceClient implements ITestService { - private _onMarco: Event; - get onMarco(): Event { return this._onMarco; } + get onMarco(): Event { return this.channel.listen('marco'); } - constructor(private channel: ITestChannel) { - this._onMarco = eventFromCall(channel, 'event:marco'); - } + constructor(private channel: ITestChannel) { } marco(): TPromise { return this.channel.call('marco'); diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 50027d9528e..97f712452c9 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -66,7 +66,7 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I process.once('exit', () => dispose(disposables)); const environmentService = new EnvironmentService(initData.args, process.execPath); - const logLevelClient = new LogLevelSetterChannelClient(server.getChannel('loglevel', { route: () => 'main' })); + const logLevelClient = new LogLevelSetterChannelClient(server.getChannel('loglevel', { routeCall: () => 'main', routeEvent: () => 'main' })); const logService = new FollowerLogService(logLevelClient, createSpdLogService('sharedprocess', initData.logLevel, environmentService.logsPath)); disposables.push(logService); @@ -77,14 +77,18 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I services.set(IConfigurationService, new SyncDescriptor(ConfigurationService)); services.set(IRequestService, new SyncDescriptor(RequestService)); - const windowsChannel = server.getChannel('windows', { route: () => 'main' }); + const windowsChannel = server.getChannel('windows', { routeCall: () => 'main', routeEvent: () => 'main' }); const windowsService = new WindowsChannelClient(windowsChannel); services.set(IWindowsService, windowsService); const activeWindowManager = new ActiveWindowManager(windowsService); const dialogChannel = server.getChannel('dialog', { - route: () => { - logService.info('Routing dialog request to the client', activeWindowManager.activeClientId); + routeCall: () => { + logService.info('Routing dialog call request to the client', activeWindowManager.activeClientId); + return activeWindowManager.activeClientId; + }, + routeEvent: () => { + logService.info('Routing dialog listen request to the client', activeWindowManager.activeClientId); return activeWindowManager.activeClientId; } }); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index b12acca0233..d48e917621e 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -425,7 +425,8 @@ export class CodeApplication { // Create a URL handler which forwards to the last active window const activeWindowManager = new ActiveWindowManager(windowsService); - const urlHandlerChannel = this.electronIpcServer.getChannel('urlHandler', { route: () => activeWindowManager.activeClientId }); + const route = () => activeWindowManager.activeClientId; + const urlHandlerChannel = this.electronIpcServer.getChannel('urlHandler', { routeCall: route, routeEvent: route }); const multiplexURLHandler = new URLHandlerChannelClient(urlHandlerChannel); // On Mac, Code can be running without any open windows, so we must create a window to handle urls, diff --git a/src/vs/code/electron-main/launch.ts b/src/vs/code/electron-main/launch.ts index 7da8d2edbd2..d1781a1a89b 100644 --- a/src/vs/code/electron-main/launch.ts +++ b/src/vs/code/electron-main/launch.ts @@ -20,6 +20,7 @@ import { Schemas } from 'vs/base/common/network'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import URI from 'vs/base/common/uri'; import { BrowserWindow } from 'electron'; +import { Event } from 'vs/base/common/event'; export const ID = 'launchService'; export const ILaunchService = createDecorator(ID); @@ -79,6 +80,10 @@ export class LaunchChannel implements ILaunchChannel { constructor(private service: ILaunchService) { } + listen(event: string): Event { + throw new Error('No event found'); + } + call(command: string, arg: any): TPromise { switch (command) { case 'start': diff --git a/src/vs/platform/dialogs/common/dialogIpc.ts b/src/vs/platform/dialogs/common/dialogIpc.ts index 7d260b4e8f8..0a73419d531 100644 --- a/src/vs/platform/dialogs/common/dialogIpc.ts +++ b/src/vs/platform/dialogs/common/dialogIpc.ts @@ -9,6 +9,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IDialogService, IConfirmation, IConfirmationResult } from 'vs/platform/dialogs/common/dialogs'; import Severity from 'vs/base/common/severity'; +import { Event } from 'vs/base/common/event'; export interface IDialogChannel extends IChannel { call(command: 'show'): TPromise; @@ -18,7 +19,10 @@ export interface IDialogChannel extends IChannel { export class DialogChannel implements IDialogChannel { - constructor(@IDialogService private dialogService: IDialogService) { + constructor(@IDialogService private dialogService: IDialogService) { } + + listen(event: string): Event { + throw new Error('No event found'); } call(command: string, args?: any[]): TPromise { diff --git a/src/vs/platform/driver/common/driver.ts b/src/vs/platform/driver/common/driver.ts index a069d1c087d..82e907f2e25 100644 --- a/src/vs/platform/driver/common/driver.ts +++ b/src/vs/platform/driver/common/driver.ts @@ -8,6 +8,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; +import { Event } from 'vs/base/common/event'; export const ID = 'driverService'; export const IDriver = createDecorator(ID); @@ -65,6 +66,10 @@ export class DriverChannel implements IDriverChannel { constructor(private driver: IDriver) { } + listen(event: string): Event { + throw new Error('No event found'); + } + call(command: string, arg?: any): TPromise { switch (command) { case 'getWindowIds': return this.driver.getWindowIds(); @@ -164,6 +169,10 @@ export class WindowDriverRegistryChannel implements IWindowDriverRegistryChannel constructor(private registry: IWindowDriverRegistry) { } + listen(event: string): Event { + throw new Error('No event found'); + } + call(command: string, arg?: any): TPromise { switch (command) { case 'registerWindowDriver': return this.registry.registerWindowDriver(arg); @@ -218,6 +227,10 @@ export class WindowDriverChannel implements IWindowDriverChannel { constructor(private driver: IWindowDriver) { } + listen(event: string): Event { + throw new Error('No event found'); + } + call(command: string, arg?: any): TPromise { switch (command) { case 'click': return this.driver.click(arg[0], arg[1], arg[2]); diff --git a/src/vs/platform/driver/electron-browser/driver.ts b/src/vs/platform/driver/electron-browser/driver.ts index 06590836265..4dbaaff6c95 100644 --- a/src/vs/platform/driver/electron-browser/driver.ts +++ b/src/vs/platform/driver/electron-browser/driver.ts @@ -14,7 +14,6 @@ import { getTopLeftOffset, getClientArea } from 'vs/base/browser/dom'; import * as electron from 'electron'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { Terminal } from 'vscode-xterm'; -import { toWinJsPromise } from 'vs/base/common/async'; function serializeElement(element: Element, recursive: boolean): IElement { const attributes = Object.create(null); @@ -52,14 +51,14 @@ class WindowDriver implements IWindowDriver { ) { } click(selector: string, xoffset?: number, yoffset?: number): TPromise { - return toWinJsPromise(this._click(selector, 1, xoffset, yoffset)); + return this._click(selector, 1, xoffset, yoffset); } doubleClick(selector: string): TPromise { - return toWinJsPromise(this._click(selector, 2)); + return this._click(selector, 2); } - private async _getElementXY(selector: string, xoffset?: number, yoffset?: number): Promise<{ x: number; y: number; }> { + private _getElementXY(selector: string, xoffset?: number, yoffset?: number): TPromise<{ x: number; y: number; }> { const element = document.querySelector(selector); if (!element) { @@ -81,18 +80,20 @@ class WindowDriver implements IWindowDriver { x = Math.round(x); y = Math.round(y); - return { x, y }; + return TPromise.as({ x, y }); } - private async _click(selector: string, clickCount: number, xoffset?: number, yoffset?: number): Promise { - const { x, y } = await this._getElementXY(selector, xoffset, yoffset); - const webContents = electron.remote.getCurrentWebContents(); + private _click(selector: string, clickCount: number, xoffset?: number, yoffset?: number): TPromise { + return this._getElementXY(selector, xoffset, yoffset).then(({ x, y }) => { - webContents.sendInputEvent({ type: 'mouseDown', x, y, button: 'left', clickCount } as any); - await TPromise.timeout(10); - webContents.sendInputEvent({ type: 'mouseUp', x, y, button: 'left', clickCount } as any); + const webContents = electron.remote.getCurrentWebContents(); + webContents.sendInputEvent({ type: 'mouseDown', x, y, button: 'left', clickCount } as any); - await TPromise.timeout(100); + return TPromise.timeout(10).then(() => { + webContents.sendInputEvent({ type: 'mouseUp', x, y, button: 'left', clickCount } as any); + return TPromise.timeout(100); + }); + }); } setValue(selector: string, text: string): TPromise { @@ -231,7 +232,7 @@ export async function registerWindowDriver( const options = await windowDriverRegistry.registerWindowDriver(windowId); if (options.verbose) { - // windowDriver.openDevTools(); + windowDriver.openDevTools(); } const disposable = toDisposable(() => windowDriverRegistry.reloadWindowDriver(windowId)); diff --git a/src/vs/platform/driver/electron-main/driver.ts b/src/vs/platform/driver/electron-main/driver.ts index 1da60a761a6..ba2b3c673a2 100644 --- a/src/vs/platform/driver/electron-main/driver.ts +++ b/src/vs/platform/driver/electron-main/driver.ts @@ -27,7 +27,11 @@ class WindowRouter implements IClientRouter { constructor(private windowId: number) { } - route(command: string, arg: any): string { + routeCall(): string { + return `window:${this.windowId}`; + } + + routeEvent(): string { return `window:${this.windowId}`; } } diff --git a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts index 882efb29b22..1de227bfe06 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts @@ -6,17 +6,17 @@ 'use strict'; import { TPromise } from 'vs/base/common/winjs.base'; -import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc'; +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, LocalExtensionType, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension } from './extensionManagement'; import { Event, buffer, mapEvent } from 'vs/base/common/event'; import URI from 'vs/base/common/uri'; import { IURITransformer } from 'vs/base/common/uriIpc'; export interface IExtensionManagementChannel extends IChannel { - call(command: 'event:onInstallExtension'): TPromise; - call(command: 'event:onDidInstallExtension'): TPromise; - call(command: 'event:onUninstallExtension'): TPromise; - call(command: 'event:onDidUninstallExtension'): TPromise; + listen(event: 'onInstallExtension'): Event; + listen(event: 'onDidInstallExtension'): Event; + listen(event: 'onUninstallExtension'): Event; + listen(event: 'onDidUninstallExtension'): Event; call(command: 'install', args: [string]): TPromise; call(command: 'installFromGallery', args: [IGalleryExtension]): TPromise; call(command: 'uninstall', args: [ILocalExtension, boolean]): TPromise; @@ -40,12 +40,19 @@ export class ExtensionManagementChannel implements IExtensionManagementChannel { this.onDidUninstallExtension = buffer(service.onDidUninstallExtension, true); } + listen(event: string): Event { + switch (event) { + case 'onInstallExtension': return this.onInstallExtension; + case 'onDidInstallExtension': return this.onDidInstallExtension; + case 'onUninstallExtension': return this.onUninstallExtension; + case 'onDidUninstallExtension': return this.onDidUninstallExtension; + } + + throw new Error('Invalid listen'); + } + call(command: string, args?: any): TPromise { switch (command) { - case 'event:onInstallExtension': return eventToCall(this.onInstallExtension); - case 'event:onDidInstallExtension': return eventToCall(this.onDidInstallExtension); - case 'event:onUninstallExtension': return eventToCall(this.onUninstallExtension); - case 'event:onDidUninstallExtension': return eventToCall(this.onDidUninstallExtension); case 'install': return this.service.install(args[0]); case 'installFromGallery': return this.service.installFromGallery(args[0]); case 'uninstall': return this.service.uninstall(args[0], args[1]); @@ -54,7 +61,8 @@ export class ExtensionManagementChannel implements IExtensionManagementChannel { case 'updateMetadata': return this.service.updateMetadata(args[0], args[1]); case 'getExtensionsReport': return this.service.getExtensionsReport(); } - return undefined; + + throw new Error('Invalid call'); } } @@ -64,17 +72,10 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer constructor(private channel: IExtensionManagementChannel, private uriTransformer: IURITransformer) { } - private _onInstallExtension = eventFromCall(this.channel, 'event:onInstallExtension'); - get onInstallExtension(): Event { return this._onInstallExtension; } - - private _onDidInstallExtension = mapEvent(eventFromCall(this.channel, 'event:onDidInstallExtension'), i => ({ ...i, local: this._transform(i.local) })); - get onDidInstallExtension(): Event { return this._onDidInstallExtension; } - - private _onUninstallExtension = eventFromCall(this.channel, 'event:onUninstallExtension'); - get onUninstallExtension(): Event { return this._onUninstallExtension; } - - private _onDidUninstallExtension = eventFromCall(this.channel, 'event:onDidUninstallExtension'); - get onDidUninstallExtension(): Event { return this._onDidUninstallExtension; } + get onInstallExtension(): Event { return this.channel.listen('onInstallExtension'); } + get onDidInstallExtension(): Event { return mapEvent(this.channel.listen('onDidInstallExtension'), i => ({ ...i, local: this._transform(i.local) })); } + get onUninstallExtension(): Event { return this.channel.listen('onUninstallExtension'); } + get onDidUninstallExtension(): Event { return this.channel.listen('onDidUninstallExtension'); } install(zipPath: string): TPromise { return this.channel.call('install', [zipPath]) diff --git a/src/vs/platform/issue/common/issueIpc.ts b/src/vs/platform/issue/common/issueIpc.ts index d804c6579fe..d45c1d870ee 100644 --- a/src/vs/platform/issue/common/issueIpc.ts +++ b/src/vs/platform/issue/common/issueIpc.ts @@ -8,6 +8,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IIssueService, IssueReporterData, ProcessExplorerData } from './issue'; +import { Event } from 'vs/base/common/event'; export interface IIssueChannel extends IChannel { call(command: 'openIssueReporter', arg: IssueReporterData): TPromise; @@ -19,6 +20,10 @@ export class IssueChannel implements IIssueChannel { constructor(private service: IIssueService) { } + listen(event: string): Event { + throw new Error('No event found'); + } + call(command: string, arg?: any): TPromise { switch (command) { case 'openIssueReporter': diff --git a/src/vs/platform/localizations/common/localizationsIpc.ts b/src/vs/platform/localizations/common/localizationsIpc.ts index 51b6fb34147..2f06fb819ac 100644 --- a/src/vs/platform/localizations/common/localizationsIpc.ts +++ b/src/vs/platform/localizations/common/localizationsIpc.ts @@ -6,12 +6,14 @@ 'use strict'; import { TPromise } from 'vs/base/common/winjs.base'; -import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc'; +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event, buffer } from 'vs/base/common/event'; import { ILocalizationsService, LanguageType } from 'vs/platform/localizations/common/localizations'; export interface ILocalizationsChannel extends IChannel { - call(command: 'event:onDidLanguagesChange'): TPromise; + listen(event: 'onDidLanguagesChange'): Event; + listen(event: string, arg?: any): Event; + call(command: 'getLanguageIds'): TPromise; call(command: string, arg?: any): TPromise; } @@ -24,9 +26,16 @@ export class LocalizationsChannel implements ILocalizationsChannel { this.onDidLanguagesChange = buffer(service.onDidLanguagesChange, true); } + listen(event: string): Event { + switch (event) { + case 'onDidLanguagesChange': return this.onDidLanguagesChange; + } + + throw new Error('No event found'); + } + call(command: string, arg?: any): TPromise { switch (command) { - case 'event:onDidLanguagesChange': return eventToCall(this.onDidLanguagesChange); case 'getLanguageIds': return this.service.getLanguageIds(arg); } return undefined; @@ -39,8 +48,7 @@ export class LocalizationsChannelClient implements ILocalizationsService { constructor(private channel: ILocalizationsChannel) { } - private _onDidLanguagesChange = eventFromCall(this.channel, 'event:onDidLanguagesChange'); - get onDidLanguagesChange(): Event { return this._onDidLanguagesChange; } + get onDidLanguagesChange(): Event { return this.channel.listen('onDidLanguagesChange'); } getLanguageIds(type?: LanguageType): TPromise { return this.channel.call('getLanguageIds', type); diff --git a/src/vs/platform/log/common/logIpc.ts b/src/vs/platform/log/common/logIpc.ts index d39072ea674..24bccff9463 100644 --- a/src/vs/platform/log/common/logIpc.ts +++ b/src/vs/platform/log/common/logIpc.ts @@ -3,14 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc'; +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { TPromise } from 'vs/base/common/winjs.base'; import { LogLevel, ILogService, DelegatedLogService } from 'vs/platform/log/common/log'; import { Event, buffer } from 'vs/base/common/event'; export interface ILogLevelSetterChannel extends IChannel { - call(command: 'event:onDidChangeLogLevel'): TPromise; + listen(event: 'onDidChangeLogLevel'): Event; + listen(event: string, arg?: any): Event; + call(command: 'setLevel', logLevel: LogLevel): TPromise; + call(command: string, arg?: any): TPromise; } export class LogLevelSetterChannel implements ILogLevelSetterChannel { @@ -21,9 +24,16 @@ export class LogLevelSetterChannel implements ILogLevelSetterChannel { this.onDidChangeLogLevel = buffer(service.onDidChangeLogLevel, true); } + listen(event: string): Event { + switch (event) { + case 'onDidChangeLogLevel': return this.onDidChangeLogLevel; + } + + throw new Error('No event found'); + } + call(command: string, arg?: any): TPromise { switch (command) { - case 'event:onDidChangeLogLevel': return eventToCall(this.onDidChangeLogLevel); case 'setLevel': this.service.setLevel(arg); return TPromise.as(null); } return undefined; @@ -34,8 +44,9 @@ export class LogLevelSetterChannelClient { constructor(private channel: ILogLevelSetterChannel) { } - private _onDidChangeLogLevel = eventFromCall(this.channel, 'event:onDidChangeLogLevel'); - get onDidChangeLogLevel(): Event { return this._onDidChangeLogLevel; } + get onDidChangeLogLevel(): Event { + return this.channel.listen('onDidChangeLogLevel'); + } setLevel(level: LogLevel): TPromise { return this.channel.call('setLevel', level); diff --git a/src/vs/platform/menubar/common/menubarIpc.ts b/src/vs/platform/menubar/common/menubarIpc.ts index 52b2841d3e8..13699176ef5 100644 --- a/src/vs/platform/menubar/common/menubarIpc.ts +++ b/src/vs/platform/menubar/common/menubarIpc.ts @@ -7,6 +7,7 @@ import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { TPromise } from 'vs/base/common/winjs.base'; import { IMenubarService, IMenubarData } from 'vs/platform/menubar/common/menubar'; +import { Event } from 'vs/base/common/event'; export interface IMenubarChannel extends IChannel { call(command: 'updateMenubar', arg: [number, IMenubarData]): TPromise; @@ -17,6 +18,10 @@ export class MenubarChannel implements IMenubarChannel { constructor(private service: IMenubarService) { } + listen(event: string, arg?: any): Event { + throw new Error('No events'); + } + call(command: string, arg?: any): TPromise { switch (command) { case 'updateMenubar': return this.service.updateMenubar(arg[0], arg[1]); diff --git a/src/vs/platform/telemetry/common/telemetryIpc.ts b/src/vs/platform/telemetry/common/telemetryIpc.ts index df3b884b3ed..f08e0c0969c 100644 --- a/src/vs/platform/telemetry/common/telemetryIpc.ts +++ b/src/vs/platform/telemetry/common/telemetryIpc.ts @@ -8,6 +8,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { ITelemetryAppender } from 'vs/platform/telemetry/common/telemetryUtils'; +import { Event } from 'vs/base/common/event'; export interface ITelemetryLog { eventName: string; @@ -23,6 +24,10 @@ export class TelemetryAppenderChannel implements ITelemetryAppenderChannel { constructor(private appender: ITelemetryAppender) { } + listen(event: string, arg?: any): Event { + throw new Error('No events'); + } + call(command: string, { eventName, data }: ITelemetryLog): TPromise { this.appender.log(eventName, data); return TPromise.as(null); diff --git a/src/vs/platform/update/common/updateIpc.ts b/src/vs/platform/update/common/updateIpc.ts index 5138e17702c..aa2e8fd304e 100644 --- a/src/vs/platform/update/common/updateIpc.ts +++ b/src/vs/platform/update/common/updateIpc.ts @@ -6,12 +6,15 @@ 'use strict'; import { TPromise } from 'vs/base/common/winjs.base'; -import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc'; +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event, Emitter } from 'vs/base/common/event'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IUpdateService, State } from './update'; export interface IUpdateChannel extends IChannel { + listen(event: 'onStateChange'): Event; + listen(command: string, arg?: any): Event; + call(command: 'checkForUpdates', arg: any): TPromise; call(command: 'downloadUpdate'): TPromise; call(command: 'applyUpdate'): TPromise; @@ -25,9 +28,16 @@ export class UpdateChannel implements IUpdateChannel { constructor(private service: IUpdateService) { } + listen(event: string, arg?: any): Event { + switch (event) { + case 'onStateChange': return this.service.onStateChange; + } + + throw new Error('No event found'); + } + call(command: string, arg?: any): TPromise { switch (command) { - case 'event:onStateChange': return eventToCall(this.service.onStateChange); case 'checkForUpdates': return this.service.checkForUpdates(arg); case 'downloadUpdate': return this.service.downloadUpdate(); case 'applyUpdate': return this.service.applyUpdate(); @@ -43,8 +53,6 @@ export class UpdateChannelClient implements IUpdateService { _serviceBrand: any; - private _onRemoteStateChange = eventFromCall(this.channel, 'event:onStateChange'); - private _onStateChange = new Emitter(); get onStateChange(): Event { return this._onStateChange.event; } @@ -60,7 +68,8 @@ export class UpdateChannelClient implements IUpdateService { this._onStateChange.fire(state); // fire subsequent states as they come in from remote - this._onRemoteStateChange(state => this._onStateChange.fire(state)); + + this.channel.listen('onStateChange')(state => this._onStateChange.fire(state)); }, onUnexpectedError); } diff --git a/src/vs/platform/url/common/urlIpc.ts b/src/vs/platform/url/common/urlIpc.ts index 0cc9ec695ad..84fdc5c2e65 100644 --- a/src/vs/platform/url/common/urlIpc.ts +++ b/src/vs/platform/url/common/urlIpc.ts @@ -10,6 +10,7 @@ import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IURLHandler, IURLService } from './url'; import URI from 'vs/base/common/uri'; import { IDisposable } from 'vs/base/common/lifecycle'; +import { Event } from 'vs/base/common/event'; export interface IURLServiceChannel extends IChannel { call(command: 'open', url: string): TPromise; @@ -20,6 +21,10 @@ export class URLServiceChannel implements IURLServiceChannel { constructor(private service: IURLService) { } + listen(event: string, arg?: any): Event { + throw new Error('No events'); + } + call(command: string, arg?: any): TPromise { switch (command) { case 'open': return this.service.open(URI.revive(arg)); @@ -52,6 +57,10 @@ export class URLHandlerChannel implements IURLHandlerChannel { constructor(private handler: IURLHandler) { } + listen(event: string, arg?: any): Event { + throw new Error('No events'); + } + call(command: string, arg?: any): TPromise { switch (command) { case 'handleURL': return this.handler.handleURL(URI.revive(arg)); diff --git a/src/vs/platform/windows/common/windowsIpc.ts b/src/vs/platform/windows/common/windowsIpc.ts index 8875c105621..0adca2f73b4 100644 --- a/src/vs/platform/windows/common/windowsIpc.ts +++ b/src/vs/platform/windows/common/windowsIpc.ts @@ -7,7 +7,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { Event, buffer } from 'vs/base/common/event'; -import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc'; +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IWindowsService, INativeOpenDialogOptions, IEnterWorkspaceResult, CrashReporterStartOptions, IMessageBoxResult, MessageBoxOptions, SaveDialogOptions, OpenDialogOptions, IDevToolsOptions } from 'vs/platform/windows/common/windows'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces'; import { IRecentlyOpened } from 'vs/platform/history/common/history'; @@ -16,10 +16,14 @@ import URI from 'vs/base/common/uri'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; export interface IWindowsChannel extends IChannel { - call(command: 'event:onWindowOpen'): TPromise; - call(command: 'event:onWindowFocus'): TPromise; - call(command: 'event:onWindowBlur'): TPromise; - call(command: 'event:onRecentlyOpenedChange'): TPromise; + listen(event: 'onWindowOpen'): Event; + listen(event: 'onWindowFocus'): Event; + listen(event: 'onWindowBlur'): Event; + listen(event: 'onWindowMaximize'): Event; + listen(event: 'onWindowUnmaximize'): Event; + listen(event: 'onRecentlyOpenedChange'): Event; + listen(event: string, arg?: any): Event; + call(command: 'pickFileFolderAndOpen', arg: INativeOpenDialogOptions): TPromise; call(command: 'pickFileAndOpen', arg: INativeOpenDialogOptions): TPromise; call(command: 'pickFolderAndOpen', arg: INativeOpenDialogOptions): TPromise; @@ -89,14 +93,21 @@ export class WindowsChannel implements IWindowsChannel { this.onRecentlyOpenedChange = buffer(service.onRecentlyOpenedChange, true); } + listen(event: string, arg?: any): Event { + switch (event) { + case 'onWindowOpen': return this.onWindowOpen; + case 'onWindowFocus': return this.onWindowFocus; + case 'onWindowBlur': return this.onWindowBlur; + case 'onWindowMaximize': return this.onWindowMaximize; + case 'onWindowUnmaximize': return this.onWindowUnmaximize; + case 'onRecentlyOpenedChange': return this.onRecentlyOpenedChange; + } + + throw new Error('No event found'); + } + call(command: string, arg?: any): TPromise { switch (command) { - case 'event:onWindowOpen': return eventToCall(this.onWindowOpen); - case 'event:onWindowFocus': return eventToCall(this.onWindowFocus); - case 'event:onWindowBlur': return eventToCall(this.onWindowBlur); - case 'event:onWindowMaximize': return eventToCall(this.onWindowMaximize); - case 'event:onWindowUnmaximize': return eventToCall(this.onWindowUnmaximize); - case 'event:onRecentlyOpenedChange': return eventToCall(this.onRecentlyOpenedChange); case 'pickFileFolderAndOpen': return this.service.pickFileFolderAndOpen(arg); case 'pickFileAndOpen': return this.service.pickFileAndOpen(arg); case 'pickFolderAndOpen': return this.service.pickFolderAndOpen(arg); @@ -170,23 +181,12 @@ export class WindowsChannelClient implements IWindowsService { constructor(private channel: IWindowsChannel) { } - private _onWindowOpen: Event = eventFromCall(this.channel, 'event:onWindowOpen'); - get onWindowOpen(): Event { return this._onWindowOpen; } - - private _onWindowFocus: Event = eventFromCall(this.channel, 'event:onWindowFocus'); - get onWindowFocus(): Event { return this._onWindowFocus; } - - private _onWindowBlur: Event = eventFromCall(this.channel, 'event:onWindowBlur'); - get onWindowBlur(): Event { return this._onWindowBlur; } - - private _onWindowMaximize: Event = eventFromCall(this.channel, 'event:onWindowMaximize'); - get onWindowMaximize(): Event { return this._onWindowMaximize; } - - private _onWindowUnmaximize: Event = eventFromCall(this.channel, 'event:onWindowUnmaximize'); - get onWindowUnmaximize(): Event { return this._onWindowUnmaximize; } - - private _onRecentlyOpenedChange: Event = eventFromCall(this.channel, 'event:onRecentlyOpenedChange'); - get onRecentlyOpenedChange(): Event { return this._onRecentlyOpenedChange; } + get onWindowOpen(): Event { return this.channel.listen('onWindowOpen'); } + get onWindowFocus(): Event { return this.channel.listen('onWindowFocus'); } + get onWindowBlur(): Event { return this.channel.listen('onWindowBlur'); } + get onWindowMaximize(): Event { return this.channel.listen('onWindowMaximize'); } + get onWindowUnmaximize(): Event { return this.channel.listen('onWindowUnmaximize'); } + get onRecentlyOpenedChange(): Event { return this.channel.listen('onRecentlyOpenedChange'); } pickFileFolderAndOpen(options: INativeOpenDialogOptions): TPromise { return this.channel.call('pickFileFolderAndOpen', options); diff --git a/src/vs/platform/workspaces/common/workspacesIpc.ts b/src/vs/platform/workspaces/common/workspacesIpc.ts index 898f9d43a29..e123da0b4c7 100644 --- a/src/vs/platform/workspaces/common/workspacesIpc.ts +++ b/src/vs/platform/workspaces/common/workspacesIpc.ts @@ -9,6 +9,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IWorkspacesService, IWorkspaceIdentifier, IWorkspaceFolderCreationData, IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces'; import URI from 'vs/base/common/uri'; +import { Event } from 'vs/base/common/event'; export interface IWorkspacesChannel extends IChannel { call(command: 'createWorkspace', arg: [IWorkspaceFolderCreationData[]]): TPromise; @@ -19,6 +20,10 @@ export class WorkspacesChannel implements IWorkspacesChannel { constructor(private service: IWorkspacesMainService) { } + listen(event: string, arg?: any): Event { + throw new Error('No events'); + } + call(command: string, arg?: any): TPromise { switch (command) { case 'createWorkspace': { diff --git a/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts b/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts index 88ebc3d19da..849173e7c70 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts @@ -8,6 +8,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IWatcherRequest, IWatcherService } from './watcher'; +import { Event } from 'vs/base/common/event'; export interface IWatcherChannel extends IChannel { call(command: 'initialize', verboseLogging: boolean): TPromise; @@ -19,6 +20,10 @@ export class WatcherChannel implements IWatcherChannel { constructor(private service: IWatcherService) { } + listen(event: string, arg?: any): Event { + throw new Error('No events'); + } + call(command: string, arg: any): TPromise { switch (command) { case 'initialize': return this.service.initialize(arg); diff --git a/src/vs/workbench/services/files/node/watcher/unix/watcherIpc.ts b/src/vs/workbench/services/files/node/watcher/unix/watcherIpc.ts index bf2fba4664b..8b24dc59c28 100644 --- a/src/vs/workbench/services/files/node/watcher/unix/watcherIpc.ts +++ b/src/vs/workbench/services/files/node/watcher/unix/watcherIpc.ts @@ -8,6 +8,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IWatcherRequest, IWatcherService, IWatcherOptions } from 'vs/workbench/services/files/node/watcher/unix/watcher'; +import { Event } from 'vs/base/common/event'; export interface IWatcherChannel extends IChannel { call(command: 'initialize', options: IWatcherOptions): TPromise; @@ -19,6 +20,10 @@ export class WatcherChannel implements IWatcherChannel { constructor(private service: IWatcherService) { } + listen(event: string, arg?: any): Event { + throw new Error('No events'); + } + call(command: string, arg: any): TPromise { switch (command) { case 'initialize': return this.service.initialize(arg); diff --git a/src/vs/workbench/services/search/node/searchIpc.ts b/src/vs/workbench/services/search/node/searchIpc.ts index 4e9575e0d55..ffa1d267bb9 100644 --- a/src/vs/workbench/services/search/node/searchIpc.ts +++ b/src/vs/workbench/services/search/node/searchIpc.ts @@ -8,6 +8,7 @@ import { PPromise, TPromise } from 'vs/base/common/winjs.base'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IRawSearchService, IRawSearch, ISerializedSearchComplete, ISerializedSearchProgressItem, ITelemetryEvent } from './search'; +import { Event } from 'vs/base/common/event'; export interface ISearchChannel extends IChannel { call(command: 'fileSearch', search: IRawSearch): PPromise; @@ -21,6 +22,10 @@ export class SearchChannel implements ISearchChannel { constructor(private service: IRawSearchService) { } + listen(event: string, arg?: any): Event { + throw new Error('No events'); + } + call(command: string, arg?: any): TPromise { switch (command) { case 'fileSearch': return this.service.fileSearch(arg); diff --git a/src/vs/workbench/services/search/node/worker/searchWorkerIpc.ts b/src/vs/workbench/services/search/node/worker/searchWorkerIpc.ts index 5260dc0b80d..70aade1c801 100644 --- a/src/vs/workbench/services/search/node/worker/searchWorkerIpc.ts +++ b/src/vs/workbench/services/search/node/worker/searchWorkerIpc.ts @@ -10,6 +10,7 @@ import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { ISerializedFileMatch } from '../search'; import { IPatternInfo } from 'vs/platform/search/common/search'; import { SearchWorker } from './searchWorker'; +import { Event } from 'vs/base/common/event'; export interface ISearchWorkerSearchArgs { pattern: IPatternInfo; @@ -41,6 +42,10 @@ export class SearchWorkerChannel implements ISearchWorkerChannel { constructor(private worker: SearchWorker) { } + listen(event: string, arg?: any): Event { + throw new Error('No events'); + } + call(command: string, arg?: any): TPromise { switch (command) { case 'initialize': return this.worker.initialize(); From 14b0ce99d81309edcabf92cec44bb431d5be7975 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 4 Jul 2018 09:14:21 +0200 Subject: [PATCH 141/283] debt - async winjs.promise, add cancelation token --- .../electron-browser/mainThreadDecorations.ts | 16 +++++----- .../decorations/browser/decorations.ts | 4 +-- .../decorations/browser/decorationsService.ts | 30 ++++++++++++------- .../test/browser/decorationsService.test.ts | 16 +++++++--- 4 files changed, 42 insertions(+), 24 deletions(-) diff --git a/src/vs/workbench/api/electron-browser/mainThreadDecorations.ts b/src/vs/workbench/api/electron-browser/mainThreadDecorations.ts index 1f8d8af2c36..4cef3fd8b1b 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDecorations.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadDecorations.ts @@ -10,14 +10,14 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ExtHostContext, MainContext, IExtHostContext, MainThreadDecorationsShape, ExtHostDecorationsShape, DecorationData, DecorationRequest } from '../node/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; import { IDecorationsService, IDecorationData } from 'vs/workbench/services/decorations/browser/decorations'; -import { TPromise } from 'vs/base/common/winjs.base'; import { values } from 'vs/base/common/collections'; +import { CancellationToken } from 'vs/base/common/cancellation'; class DecorationRequestsQueue { private _idPool = 0; private _requests: { [id: number]: DecorationRequest } = Object.create(null); - private _resolver: { [id: number]: Function } = Object.create(null); + private _resolver: { [id: number]: (data: DecorationData) => any } = Object.create(null); private _timer: number; @@ -27,16 +27,18 @@ class DecorationRequestsQueue { // } - enqueue(handle: number, uri: URI): TPromise { + enqueue(handle: number, uri: URI, token: CancellationToken): Promise { const id = ++this._idPool; - return new TPromise((resolve, reject) => { + const result = new Promise(resolve => { this._requests[id] = { id, handle, uri }; this._resolver[id] = resolve; this._processQueue(); - }, () => { + }); + token.onCancellationRequested(() => { delete this._requests[id]; delete this._resolver[id]; }); + return result; } private _processQueue(): void { @@ -87,8 +89,8 @@ export class MainThreadDecorations implements MainThreadDecorationsShape { const registration = this._decorationsService.registerDecorationsProvider({ label, onDidChange: emitter.event, - provideDecorations: (uri) => { - return this._requestQueue.enqueue(handle, uri).then(data => { + provideDecorations: (uri, token) => { + return this._requestQueue.enqueue(handle, uri, token).then(data => { if (!data) { return undefined; } diff --git a/src/vs/workbench/services/decorations/browser/decorations.ts b/src/vs/workbench/services/decorations/browser/decorations.ts index e777db83119..6d1e6607375 100644 --- a/src/vs/workbench/services/decorations/browser/decorations.ts +++ b/src/vs/workbench/services/decorations/browser/decorations.ts @@ -9,7 +9,7 @@ import URI from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { TPromise } from 'vs/base/common/winjs.base'; +import { CancellationToken } from 'vs/base/common/cancellation'; export const IDecorationsService = createDecorator('IFileDecorationsService'); @@ -32,7 +32,7 @@ export interface IDecoration { export interface IDecorationsProvider { readonly label: string; readonly onDidChange: Event; - provideDecorations(uri: URI): IDecorationData | TPromise; + provideDecorations(uri: URI, token: CancellationToken): IDecorationData | Thenable; } export interface IResourceDecorationChangeEvent { diff --git a/src/vs/workbench/services/decorations/browser/decorationsService.ts b/src/vs/workbench/services/decorations/browser/decorationsService.ts index 513955a5b04..146bafc26a3 100644 --- a/src/vs/workbench/services/decorations/browser/decorationsService.ts +++ b/src/vs/workbench/services/decorations/browser/decorationsService.ts @@ -17,8 +17,8 @@ import { IdGenerator } from 'vs/base/common/idGenerator'; import { IIterator } from 'vs/base/common/iterator'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import { localize } from 'vs/nls'; -import { TPromise } from 'vs/base/common/winjs.base'; import { isPromiseCanceledError } from 'vs/base/common/errors'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; class DecorationRule { @@ -180,7 +180,7 @@ class DecorationStyles { let usedDecorations = new Set(); for (let e = iter.next(); !e.done; e = iter.next()) { e.value.data.forEach((value, key) => { - if (!isThenable(value) && value) { + if (value && !(value instanceof DecorationDataRequest)) { usedDecorations.add(DecorationRule.keyOf(value)); } }); @@ -229,9 +229,16 @@ class FileDecorationChangeEvent implements IResourceDecorationChangeEvent { } } +class DecorationDataRequest { + constructor( + readonly source: CancellationTokenSource, + readonly thenable: Thenable, + ) { } +} + class DecorationProviderWrapper { - readonly data = TernarySearchTree.forPaths | IDecorationData>(); + readonly data = TernarySearchTree.forPaths(); private readonly _dispoable: IDisposable; constructor( @@ -275,7 +282,7 @@ class DecorationProviderWrapper { item = this._fetchData(uri); } - if (item && !isThenable(item)) { + if (item && !(item instanceof DecorationDataRequest)) { // found something (which isn't pending anymore) callback(item, false); } @@ -285,7 +292,7 @@ class DecorationProviderWrapper { const iter = this.data.findSuperstr(key); if (iter) { for (let item = iter.next(); !item.done; item = iter.next()) { - if (item.value && !isThenable(item.value)) { + if (item.value && !(item.value instanceof DecorationDataRequest)) { callback(item.value, true); } } @@ -297,27 +304,28 @@ class DecorationProviderWrapper { // check for pending request and cancel it const pendingRequest = this.data.get(uri.toString()); - if (TPromise.is(pendingRequest)) { - pendingRequest.cancel(); + if (pendingRequest instanceof DecorationDataRequest) { + pendingRequest.source.cancel(); this.data.delete(uri.toString()); } - const dataOrThenable = this._provider.provideDecorations(uri); + const source = new CancellationTokenSource(); + const dataOrThenable = this._provider.provideDecorations(uri, source.token); if (!isThenable(dataOrThenable)) { // sync -> we have a result now return this._keepItem(uri, dataOrThenable); } else { // async -> we have a result soon - const request = TPromise.wrap(dataOrThenable).then(data => { + const request = new DecorationDataRequest(source, Promise.resolve(dataOrThenable).then(data => { if (this.data.get(uri.toString()) === request) { this._keepItem(uri, data); } - }, err => { + }).catch(err => { if (!isPromiseCanceledError(err) && this.data.get(uri.toString()) === request) { this.data.delete(uri.toString()); } - }); + })); this.data.set(uri.toString(), request); return undefined; diff --git a/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts b/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts index f8469b69e23..c94ec82944a 100644 --- a/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts +++ b/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts @@ -12,6 +12,7 @@ import URI from 'vs/base/common/uri'; import { Event, toPromise, Emitter } from 'vs/base/common/event'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { TPromise } from 'vs/base/common/winjs.base'; +import { CancellationToken } from 'vs/base/common/cancellation'; suite('DecorationsService', function () { @@ -34,7 +35,7 @@ suite('DecorationsService', function () { readonly onDidChange: Event = Event.None; provideDecorations(uri: URI) { callCounter += 1; - return new TPromise(resolve => { + return new Promise(resolve => { setTimeout(() => resolve({ color: 'someBlue', tooltip: 'T' @@ -174,6 +175,7 @@ suite('DecorationsService', function () { test('Decorations not showing up for second root folder #48502', async function () { let cancelCount = 0; + let winjsCancelCount = 0; let callCount = 0; let provider = new class implements IDecorationsProvider { @@ -183,14 +185,19 @@ suite('DecorationsService', function () { label: string = 'foo'; - provideDecorations(uri: URI): TPromise { + provideDecorations(uri: URI, token: CancellationToken): TPromise { + + token.onCancellationRequested(() => { + cancelCount += 1; + }); + return new TPromise(resolve => { callCount += 1; setTimeout(() => { resolve({ letter: 'foo' }); }, 10); }, () => { - cancelCount += 1; + winjsCancelCount += 1; }); } }; @@ -204,6 +211,7 @@ suite('DecorationsService', function () { service.getDecoration(uri, false); assert.equal(cancelCount, 1); + assert.equal(winjsCancelCount, 0); assert.equal(callCount, 2); reg.dispose(); @@ -219,7 +227,7 @@ suite('DecorationsService', function () { if (uri.path.match(/hello$/)) { return { tooltip: 'FOO', weight: 17, bubble: true }; } else { - return new TPromise(_resolve => resolve = _resolve); + return new Promise(_resolve => resolve = _resolve); } } }); From fe82cb86cbcf606e0d7d0c218a988edf3acd828a Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 4 Jul 2018 09:05:20 +0200 Subject: [PATCH 142/283] remove more ppromise usage from theme picker --- .../themes/electron-browser/themes.contribution.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts b/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts index 4dea03c0c20..4d2291134ef 100644 --- a/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts +++ b/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts @@ -134,13 +134,11 @@ class SelectIconThemeAction extends Action { const placeHolder = localize('themes.selectIconTheme', "Select File Icon Theme"); const autoFocusIndex = firstIndex(picks, p => p.id === currentTheme.id); const delayer = new Delayer(100); + const chooseTheme = theme => delayer.trigger(() => selectTheme(theme || currentTheme, true), 0); + const tryTheme = theme => delayer.trigger(() => selectTheme(theme, false)); - return this.quickOpenService.pick(picks, { placeHolder, autoFocus: { autoFocusIndex } }) - .then( - theme => delayer.trigger(() => selectTheme(theme || currentTheme, true), 0), - null, - theme => delayer.trigger(() => selectTheme(theme, false)) - ); + return this.quickOpenService.pick(picks, { placeHolder, autoFocus: { autoFocusIndex }, onDidFocus: tryTheme }) + .then(chooseTheme); }); } } From a575f0c27abe8dd15bf76a00477742745ca6f11f Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 4 Jul 2018 09:30:10 +0200 Subject: [PATCH 143/283] debt - async winjs.promise, add cancelation token --- src/vs/editor/contrib/codelens/codelens.ts | 12 ++-- .../contrib/codelens/codelensController.ts | 61 ++++++++++--------- .../api/extHostLanguageFeatures.test.ts | 6 +- 3 files changed, 41 insertions(+), 38 deletions(-) diff --git a/src/vs/editor/contrib/codelens/codelens.ts b/src/vs/editor/contrib/codelens/codelens.ts index 92821ec5ee6..9c58affec6a 100644 --- a/src/vs/editor/contrib/codelens/codelens.ts +++ b/src/vs/editor/contrib/codelens/codelens.ts @@ -8,12 +8,10 @@ import { illegalArgument, onUnexpectedExternalError } from 'vs/base/common/errors'; import { mergeSort } from 'vs/base/common/arrays'; import URI from 'vs/base/common/uri'; -import { TPromise } from 'vs/base/common/winjs.base'; import { ITextModel } from 'vs/editor/common/model'; import { registerLanguageCommand } from 'vs/editor/browser/editorExtensions'; import { CodeLensProviderRegistry, CodeLensProvider, ICodeLensSymbol } from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { asWinJsPromise } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; export interface ICodeLensData { @@ -21,20 +19,20 @@ export interface ICodeLensData { provider: CodeLensProvider; } -export function getCodeLensData(model: ITextModel): TPromise { +export function getCodeLensData(model: ITextModel, token: CancellationToken): Promise { const symbols: ICodeLensData[] = []; const provider = CodeLensProviderRegistry.ordered(model); - const promises = provider.map(provider => asWinJsPromise(token => provider.provideCodeLenses(model, token)).then(result => { + const promises = provider.map(provider => Promise.resolve(provider.provideCodeLenses(model, token)).then(result => { if (Array.isArray(result)) { for (let symbol of result) { symbols.push({ symbol, provider }); } } - }, onUnexpectedExternalError)); + }).catch(onUnexpectedExternalError)); - return TPromise.join(promises).then(() => { + return Promise.all(promises).then(() => { return mergeSort(symbols, (a, b) => { // sort by lineNumber, provider-rank, and column @@ -70,7 +68,7 @@ registerLanguageCommand('_executeCodeLensProvider', function (accessor, args) { } const result: ICodeLensSymbol[] = []; - return getCodeLensData(model).then(value => { + return getCodeLensData(model, CancellationToken.None).then(value => { let resolve: Thenable[] = []; diff --git a/src/vs/editor/contrib/codelens/codelensController.ts b/src/vs/editor/contrib/codelens/codelensController.ts index 0cd0fb98162..be43648e661 100644 --- a/src/vs/editor/contrib/codelens/codelensController.ts +++ b/src/vs/editor/contrib/codelens/codelensController.ts @@ -5,10 +5,9 @@ 'use strict'; -import { RunOnceScheduler, asWinJsPromise } from 'vs/base/common/async'; +import { RunOnceScheduler, CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { TPromise } from 'vs/base/common/winjs.base'; import { ICommandService } from 'vs/platform/commands/common/commands'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { CodeLensProviderRegistry, ICodeLensSymbol } from 'vs/editor/common/modes'; @@ -30,9 +29,9 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { private _globalToDispose: IDisposable[]; private _localToDispose: IDisposable[]; private _lenses: CodeLens[]; - private _currentFindCodeLensSymbolsPromise: TPromise; + private _currentFindCodeLensSymbolsPromise: CancelablePromise; private _modelChangeCounter: number; - private _currentFindOccPromise: TPromise; + private _currentResolveCodeLensSymbolsPromise: CancelablePromise; private _detectVisibleLenses: RunOnceScheduler; constructor( @@ -72,9 +71,9 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { this._currentFindCodeLensSymbolsPromise = null; this._modelChangeCounter++; } - if (this._currentFindOccPromise) { - this._currentFindOccPromise.cancel(); - this._currentFindOccPromise = null; + if (this._currentResolveCodeLensSymbolsPromise) { + this._currentResolveCodeLensSymbolsPromise.cancel(); + this._currentResolveCodeLensSymbolsPromise = null; } this._localToDispose = dispose(this._localToDispose); } @@ -117,7 +116,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { this._currentFindCodeLensSymbolsPromise.cancel(); } - this._currentFindCodeLensSymbolsPromise = getCodeLensData(model); + this._currentFindCodeLensSymbolsPromise = createCancelablePromise(token => getCodeLensData(model, token)); this._currentFindCodeLensSymbolsPromise.then((result) => { if (counterValue === this._modelChangeCounter) { // only the last one wins @@ -267,9 +266,9 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { } private _onViewportChanged(): void { - if (this._currentFindOccPromise) { - this._currentFindOccPromise.cancel(); - this._currentFindOccPromise = null; + if (this._currentResolveCodeLensSymbolsPromise) { + this._currentResolveCodeLensSymbolsPromise.cancel(); + this._currentResolveCodeLensSymbolsPromise = null; } const model = this._editor.getModel(); @@ -291,28 +290,34 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { return; } - const promises = toResolve.map((request, i) => { + this._currentResolveCodeLensSymbolsPromise = createCancelablePromise(token => { - const resolvedSymbols = new Array(request.length); - const promises = request.map((request, i) => { - if (typeof request.provider.resolveCodeLens === 'function') { - return asWinJsPromise((token) => { - return request.provider.resolveCodeLens(model, request.symbol, token); - }).then(symbol => { - resolvedSymbols[i] = symbol; - }); - } - resolvedSymbols[i] = request.symbol; - return TPromise.as(void 0); + const promises = toResolve.map((request, i) => { + + const resolvedSymbols = new Array(request.length); + const promises = request.map((request, i) => { + if (typeof request.provider.resolveCodeLens === 'function') { + return Promise.resolve(request.provider.resolveCodeLens(model, request.symbol, token)).then(symbol => { + resolvedSymbols[i] = symbol; + }); + } + resolvedSymbols[i] = request.symbol; + return Promise.resolve(void 0); + }); + + return Promise.all(promises).then(() => { + lenses[i].updateCommands(resolvedSymbols); + }); }); - return TPromise.join(promises).then(() => { - lenses[i].updateCommands(resolvedSymbols); - }); + return Promise.all(promises); }); - this._currentFindOccPromise = TPromise.join(promises).then(() => { - this._currentFindOccPromise = null; + this._currentResolveCodeLensSymbolsPromise.then(() => { + this._currentResolveCodeLensSymbolsPromise = null; + }).catch(err => { + this._currentResolveCodeLensSymbolsPromise = null; + onUnexpectedError(err); }); } } diff --git a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts index d1cf84d2c74..b78bb4be389 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts @@ -204,7 +204,7 @@ suite('ExtHostLanguageFeatures', function () { })); return rpcProtocol.sync().then(() => { - return getCodeLensData(model).then(value => { + return getCodeLensData(model, CancellationToken.None).then(value => { assert.equal(value.length, 1); }); }); @@ -225,7 +225,7 @@ suite('ExtHostLanguageFeatures', function () { return rpcProtocol.sync().then(() => { - return getCodeLensData(model).then(value => { + return getCodeLensData(model, CancellationToken.None).then(value => { assert.equal(value.length, 1); let data = value[0]; @@ -249,7 +249,7 @@ suite('ExtHostLanguageFeatures', function () { return rpcProtocol.sync().then(() => { - return getCodeLensData(model).then(value => { + return getCodeLensData(model, CancellationToken.None).then(value => { assert.equal(value.length, 1); let data = value[0]; From 79d89bbb60c1f8e2b6c671564cdc06eef442a365 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 4 Jul 2018 10:26:32 +0200 Subject: [PATCH 144/283] clean VSCODE_PID from terminal environment --- src/vs/workbench/parts/terminal/node/terminalProcess.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/terminal/node/terminalProcess.ts b/src/vs/workbench/parts/terminal/node/terminalProcess.ts index e9126845545..8987b358049 100644 --- a/src/vs/workbench/parts/terminal/node/terminalProcess.ts +++ b/src/vs/workbench/parts/terminal/node/terminalProcess.ts @@ -117,7 +117,8 @@ function cleanEnv() { 'PTYROWS', 'PTYSHELLCMDLINE', 'VSCODE_LOGS', - 'VSCODE_PORTABLE' + 'VSCODE_PORTABLE', + 'VSCODE_PID', ]; keys.forEach(function (key) { if (process.env[key]) { From 96276aa06b119517f915f7a0db5e27dae08ed5e0 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 4 Jul 2018 10:27:01 +0200 Subject: [PATCH 145/283] fix ipc tests --- src/vs/base/parts/ipc/node/ipc.cp.ts | 54 +++++++++++++++++---- src/vs/base/parts/ipc/test/node/ipc.test.ts | 16 ------ 2 files changed, 44 insertions(+), 26 deletions(-) diff --git a/src/vs/base/parts/ipc/node/ipc.cp.ts b/src/vs/base/parts/ipc/node/ipc.cp.ts index 40d8fb8ff6b..da07daede35 100644 --- a/src/vs/base/parts/ipc/node/ipc.cp.ts +++ b/src/vs/base/parts/ipc/node/ipc.cp.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { ChildProcess, fork, ForkOptions } from 'child_process'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle'; import { TPromise } from 'vs/base/common/winjs.base'; import { Delayer } from 'vs/base/common/async'; import { deepClone, assign } from 'vs/base/common/objects'; -import { Emitter, fromNodeEventEmitter } from 'vs/base/common/event'; +import { Emitter, fromNodeEventEmitter, Event } from 'vs/base/common/event'; import { createQueuedSender } from 'vs/base/node/processes'; import { ChannelServer as IPCServer, ChannelClient as IPCClient, IChannelClient, IChannel } from 'vs/base/parts/ipc/common/ipc'; import { isRemoteConsoleLog, log } from 'vs/base/node/console'; @@ -74,7 +74,7 @@ export interface IIPCOptions { export class Client implements IChannelClient, IDisposable { private disposeDelayer: Delayer; - private activeRequests: TPromise[]; + private activeRequests: IDisposable[]; private child: ChildProcess; private _client: IPCClient; private channels: { [name: string]: IChannel }; @@ -89,11 +89,12 @@ export class Client implements IChannelClient, IDisposable { } getChannel(channelName: string): T { - const call = (command: string, arg: any) => this.request(channelName, command, arg); - return { call } as T; + const call = (command: string, arg: any) => this.requestPromise(channelName, command, arg); + const listen = (event: string, arg: any) => this.requestEvent(channelName, event, arg); + return { call, listen } as IChannel as T; } - protected request(channelName: string, name: string, arg: any): TPromise { + protected requestPromise(channelName: string, name: string, arg: any): TPromise { if (!this.disposeDelayer) { return TPromise.wrapError(new Error('disposed')); } @@ -110,7 +111,7 @@ export class Client implements IChannelClient, IDisposable { return; } - this.activeRequests.splice(this.activeRequests.indexOf(result), 1); + this.activeRequests.splice(this.activeRequests.indexOf(disposable), 1); if (this.activeRequests.length === 0) { this.disposeDelayer.trigger(() => this.disposeClient()); @@ -118,10 +119,44 @@ export class Client implements IChannelClient, IDisposable { }); }, () => request.cancel()); - this.activeRequests.push(result); + const disposable = toDisposable(() => result.cancel()); + this.activeRequests.push(disposable); return result; } + protected requestEvent(channelName: string, name: string, arg: any): Event { + if (!this.disposeDelayer) { + return Event.None; + } + + this.disposeDelayer.cancel(); + + let listener: IDisposable; + const emitter = new Emitter({ + onFirstListenerAdd: () => { + const channel = this.channels[channelName] || (this.channels[channelName] = this.client.getChannel(channelName)); + const event: Event = channel.listen(name, arg); + + listener = event(emitter.fire, emitter); + this.activeRequests.push(listener); + + }, + onLastListenerRemove: () => { + if (!this.activeRequests) { + return; + } + + this.activeRequests.splice(this.activeRequests.indexOf(listener), 1); + + if (this.activeRequests.length === 0) { + this.disposeDelayer.trigger(() => this.disposeClient()); + } + } + }); + + return emitter.event; + } + private get client(): IPCClient { if (!this._client) { const args = this.options && this.options.args ? this.options.args : []; @@ -178,8 +213,7 @@ export class Client implements IChannelClient, IDisposable { process.removeListener('exit', onExit); if (this.activeRequests) { - this.activeRequests.forEach(req => req.cancel()); - this.activeRequests = []; + this.activeRequests = dispose(this.activeRequests); } if (code !== 0 && signal !== 'SIGTERM') { diff --git a/src/vs/base/parts/ipc/test/node/ipc.test.ts b/src/vs/base/parts/ipc/test/node/ipc.test.ts index bd9c57671ae..3091fce1d7f 100644 --- a/src/vs/base/parts/ipc/test/node/ipc.test.ts +++ b/src/vs/base/parts/ipc/test/node/ipc.test.ts @@ -24,10 +24,6 @@ suite('IPC', () => { suite('child process', () => { test('createChannel', () => { - if (process.env['VSCODE_PID']) { - return undefined; // this test fails when run from within VS Code - } - const client = createClient(); const channel = client.getChannel('test'); const service = new TestServiceClient(channel); @@ -41,10 +37,6 @@ suite('IPC', () => { }); test('cancellation', () => { - if (process.env['VSCODE_PID']) { - return undefined; // this test fails when run from within VS Code - } - const client = createClient(); const channel = client.getChannel('test'); const service = new TestServiceClient(channel); @@ -61,10 +53,6 @@ suite('IPC', () => { }); test('events', () => { - if (process.env['VSCODE_PID']) { - return undefined; // this test fails when run from within VS Code - } - const client = createClient(); const channel = client.getChannel('test'); const service = new TestServiceClient(channel); @@ -87,10 +75,6 @@ suite('IPC', () => { }); test('event dispose', () => { - if (process.env['VSCODE_PID']) { - return undefined; // this test fails when run from within VS Code - } - const client = createClient(); const channel = client.getChannel('test'); const service = new TestServiceClient(channel); From ac65f118995e072b93612cb0e9f56ef0e294f80a Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 4 Jul 2018 10:46:38 +0200 Subject: [PATCH 146/283] debt - async winjs.promise, add cancelation token --- src/vs/base/common/async.ts | 22 ++++++++++++++ .../parameterHints/parameterHintsWidget.ts | 30 +++++++++---------- .../parameterHints/provideSignatureHelp.ts | 11 ++++--- .../api/extHostLanguageFeatures.test.ts | 4 +-- 4 files changed, 44 insertions(+), 23 deletions(-) diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 985c5185c3a..4c06af4e2f5 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -484,6 +484,28 @@ export function sequence(promiseFactories: ITask>[]): TPromise(promiseFactories: ITask>[], shouldStop: (t: T) => boolean = t => !!t, defaultValue: T = null): Promise { + + let index = 0; + const len = promiseFactories.length; + + const loop = () => { + if (index >= len) { + return Promise.resolve(defaultValue); + } + const factory = promiseFactories[index++]; + const promise = factory(); + return promise.then(result => { + if (shouldStop(result)) { + return Promise.resolve(result); + } + return loop(); + }); + }; + + return loop(); +} + export function first(promiseFactories: ITask>[], shouldStop: (t: T) => boolean = t => !!t, defaultValue: T = null): TPromise { let index = 0; const len = promiseFactories.length; diff --git a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts index c0390869a9f..3d695a84c97 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts +++ b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts @@ -13,7 +13,7 @@ import * as dom from 'vs/base/browser/dom'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { SignatureHelp, SignatureInformation, SignatureHelpProviderRegistry } from 'vs/editor/common/modes'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; -import { RunOnceScheduler } from 'vs/base/common/async'; +import { RunOnceScheduler, createCancelablePromise, CancelablePromise } from 'vs/base/common/async'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Event, Emitter, chain } from 'vs/base/common/event'; import { domEvent, stop } from 'vs/base/browser/event'; @@ -50,7 +50,7 @@ export class ParameterHintsModel extends Disposable { private triggerCharactersListeners: IDisposable[]; private active: boolean; private throttledDelayer: RunOnceScheduler; - private provideSignatureHelpRequest?: TPromise; + private provideSignatureHelpRequest?: CancelablePromise; constructor(editor: ICodeEditor) { super(); @@ -103,21 +103,21 @@ export class ParameterHintsModel extends Disposable { this.provideSignatureHelpRequest.cancel(); } - this.provideSignatureHelpRequest = provideSignatureHelp(this.editor.getModel(), this.editor.getPosition()) - .then(null, onUnexpectedError) - .then(result => { - if (!result || !result.signatures || result.signatures.length === 0) { - this.cancel(); - this._onCancel.fire(void 0); - return false; - } + this.provideSignatureHelpRequest = createCancelablePromise(token => provideSignatureHelp(this.editor.getModel(), this.editor.getPosition(), token)); - this.active = true; + this.provideSignatureHelpRequest.then(result => { + if (!result || !result.signatures || result.signatures.length === 0) { + this.cancel(); + this._onCancel.fire(void 0); + return false; + } - const event: IHintEvent = { hints: result }; - this._onHint.fire(event); - return true; - }); + this.active = true; + const event: IHintEvent = { hints: result }; + this._onHint.fire(event); + return true; + + }).catch(onUnexpectedError); } isTriggered(): boolean { diff --git a/src/vs/editor/contrib/parameterHints/provideSignatureHelp.ts b/src/vs/editor/contrib/parameterHints/provideSignatureHelp.ts index f12e2c2eccb..6a6f30d27d5 100644 --- a/src/vs/editor/contrib/parameterHints/provideSignatureHelp.ts +++ b/src/vs/editor/contrib/parameterHints/provideSignatureHelp.ts @@ -3,27 +3,26 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { asWinJsPromise, first } from 'vs/base/common/async'; +import { first2 } from 'vs/base/common/async'; import { onUnexpectedExternalError } from 'vs/base/common/errors'; -import { TPromise } from 'vs/base/common/winjs.base'; import { registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions'; import { Position } from 'vs/editor/common/core/position'; import { ITextModel } from 'vs/editor/common/model'; import { SignatureHelp, SignatureHelpProviderRegistry } from 'vs/editor/common/modes'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { CancellationToken } from 'vs/base/common/cancellation'; export const Context = { Visible: new RawContextKey('parameterHintsVisible', false), MultipleSignatures: new RawContextKey('parameterHintsMultipleSignatures', false), }; -export function provideSignatureHelp(model: ITextModel, position: Position): TPromise { +export function provideSignatureHelp(model: ITextModel, position: Position, token: CancellationToken = CancellationToken.None): Promise { const supports = SignatureHelpProviderRegistry.ordered(model); - return first(supports.map(support => () => { - return asWinJsPromise(token => support.provideSignatureHelp(model, position, token)) - .then(undefined, onUnexpectedExternalError); + return first2(supports.map(support => () => { + return Promise.resolve(support.provideSignatureHelp(model, position, token)).catch(onUnexpectedExternalError); })); } diff --git a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts index b78bb4be389..e638a644b43 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts @@ -872,7 +872,7 @@ suite('ExtHostLanguageFeatures', function () { return rpcProtocol.sync().then(() => { - return provideSignatureHelp(model, new EditorPosition(1, 1)).then(value => { + return provideSignatureHelp(model, new EditorPosition(1, 1), CancellationToken.None).then(value => { assert.ok(value); }); }); @@ -887,7 +887,7 @@ suite('ExtHostLanguageFeatures', function () { return rpcProtocol.sync().then(() => { - return provideSignatureHelp(model, new EditorPosition(1, 1)).then(value => { + return provideSignatureHelp(model, new EditorPosition(1, 1), CancellationToken.None).then(value => { assert.equal(value, undefined); }); }); From 027d346483e619d28310d78af06944c73226c6cf Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 4 Jul 2018 11:09:30 +0200 Subject: [PATCH 147/283] Go through error handling for unhandled rejection errors --- src/vs/code/electron-main/app.ts | 1 + src/vs/workbench/electron-browser/shell.ts | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index d48e917621e..f2a7d0cae04 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -99,6 +99,7 @@ export class CodeApplication { // We handle uncaught exceptions here to prevent electron from opening a dialog to the user errors.setUnexpectedErrorHandler(err => this.onUnexpectedError(err)); process.on('uncaughtException', err => this.onUnexpectedError(err)); + process.on('unhandledRejection', (reason: any, promise: Promise) => errors.onUnexpectedError(reason)); app.on('will-quit', () => { this.logService.trace('App#will-quit: disposing resources'); diff --git a/src/vs/workbench/electron-browser/shell.ts b/src/vs/workbench/electron-browser/shell.ts index 830a3843c3e..1096104def6 100644 --- a/src/vs/workbench/electron-browser/shell.ts +++ b/src/vs/workbench/electron-browser/shell.ts @@ -455,6 +455,15 @@ export class WorkbenchShell extends Disposable { open(): void { + // Listen on unhandled rejection events + window.addEventListener('unhandledrejection', (event) => { + // See https://developer.mozilla.org/en-US/docs/Web/API/PromiseRejectionEvent + errors.onUnexpectedError((event).reason); + + // Prevent the printing of this event to the console + event.preventDefault(); + }); + // Listen on unexpected errors errors.setUnexpectedErrorHandler((error: any) => { this.onUnexpectedError(error); From 054540cab6c8faaa1726076bf5177000e00eb5e2 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 4 Jul 2018 10:55:53 +0200 Subject: [PATCH 148/283] debt - use TimeoutTimer instead of promise --- src/vs/editor/contrib/suggest/suggestModel.ts | 78 +++++++++---------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/src/vs/editor/contrib/suggest/suggestModel.ts b/src/vs/editor/contrib/suggest/suggestModel.ts index d95f90c5e8a..e5ed0174696 100644 --- a/src/vs/editor/contrib/suggest/suggestModel.ts +++ b/src/vs/editor/contrib/suggest/suggestModel.ts @@ -89,8 +89,8 @@ export class SuggestModel implements IDisposable { private _toDispose: IDisposable[] = []; private _quickSuggestDelay: number; private _triggerCharacterListener: IDisposable; - private _triggerAutoSuggestPromise: TPromise; - private _triggerRefilter = new TimeoutTimer(); + private readonly _triggerQuickSuggest = new TimeoutTimer(); + private readonly _triggerRefilter = new TimeoutTimer(); private _state: State; private _requestPromise: TPromise; @@ -109,7 +109,6 @@ export class SuggestModel implements IDisposable { constructor(editor: ICodeEditor) { this._editor = editor; this._state = State.Idle; - this._triggerAutoSuggestPromise = null; this._requestPromise = null; this._completionModel = null; this._context = null; @@ -144,7 +143,7 @@ export class SuggestModel implements IDisposable { } dispose(): void { - dispose([this._onDidCancel, this._onDidSuggest, this._onDidTrigger, this._triggerCharacterListener, this._triggerRefilter]); + dispose([this._onDidCancel, this._onDidSuggest, this._onDidTrigger, this._triggerCharacterListener, this._triggerQuickSuggest, this._triggerRefilter]); this._toDispose = dispose(this._toDispose); dispose(this._completionModel); this.cancel(); @@ -209,9 +208,9 @@ export class SuggestModel implements IDisposable { this._triggerRefilter.cancel(); - if (this._triggerAutoSuggestPromise) { - this._triggerAutoSuggestPromise.cancel(); - this._triggerAutoSuggestPromise = null; + if (this._triggerQuickSuggest) { + this._triggerQuickSuggest.cancel(); + } if (this._requestPromise) { @@ -273,39 +272,40 @@ export class SuggestModel implements IDisposable { ) { this.cancel(); - this._triggerAutoSuggestPromise = TPromise.timeout(this._quickSuggestDelay); - this._triggerAutoSuggestPromise.then(() => { - if (LineContext.shouldAutoTrigger(this._editor)) { - const model = this._editor.getModel(); - const pos = this._editor.getPosition(); - - if (!model) { - return; - } - // validate enabled now - const { quickSuggestions } = this._editor.getConfiguration().contribInfo; - if (quickSuggestions === false) { - return; - } else if (quickSuggestions === true) { - // all good - } else { - // Check the type of the token that triggered this - model.tokenizeIfCheap(pos.lineNumber); - const lineTokens = model.getLineTokens(pos.lineNumber); - const tokenType = lineTokens.getStandardTokenType(lineTokens.findTokenIndexAtOffset(Math.max(pos.column - 1 - 1, 0))); - const inValidScope = quickSuggestions.other && tokenType === StandardTokenType.Other - || quickSuggestions.comments && tokenType === StandardTokenType.Comment - || quickSuggestions.strings && tokenType === StandardTokenType.String; - - if (!inValidScope) { - return; - } - } - - this.trigger({ auto: true }); + this._triggerQuickSuggest.cancelAndSet(() => { + if (!LineContext.shouldAutoTrigger(this._editor)) { + return; } - this._triggerAutoSuggestPromise = null; - }); + + const model = this._editor.getModel(); + const pos = this._editor.getPosition(); + if (!model) { + return; + } + // validate enabled now + const { quickSuggestions } = this._editor.getConfiguration().contribInfo; + if (quickSuggestions === false) { + return; + } else if (quickSuggestions === true) { + // all good + } else { + // Check the type of the token that triggered this + model.tokenizeIfCheap(pos.lineNumber); + const lineTokens = model.getLineTokens(pos.lineNumber); + const tokenType = lineTokens.getStandardTokenType(lineTokens.findTokenIndexAtOffset(Math.max(pos.column - 1 - 1, 0))); + const inValidScope = quickSuggestions.other && tokenType === StandardTokenType.Other + || quickSuggestions.comments && tokenType === StandardTokenType.Comment + || quickSuggestions.strings && tokenType === StandardTokenType.String; + + if (!inValidScope) { + return; + } + } + + // we made it till here -> trigger now + this.trigger({ auto: true }); + + }, this._quickSuggestDelay); } } } From c0af163b87c4423c1d83df6419f5c48dfa831248 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 4 Jul 2018 11:11:47 +0200 Subject: [PATCH 149/283] debt - async winjs.promise, add cancelation token --- src/vs/editor/contrib/suggest/suggest.ts | 18 +++++++++++++----- src/vs/editor/contrib/suggest/suggestModel.ts | 18 +++++++++++------- .../debug/electron-browser/breakpointWidget.ts | 10 ++++------ 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/vs/editor/contrib/suggest/suggest.ts b/src/vs/editor/contrib/suggest/suggest.ts index 566e1b12963..36e922a6963 100644 --- a/src/vs/editor/contrib/suggest/suggest.ts +++ b/src/vs/editor/contrib/suggest/suggest.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { asWinJsPromise, first } from 'vs/base/common/async'; +import { asWinJsPromise, first2 } from 'vs/base/common/async'; import { isFalsyOrEmpty } from 'vs/base/common/arrays'; import { compareIgnoreCase } from 'vs/base/common/strings'; import { assign } from 'vs/base/common/objects'; @@ -16,6 +16,7 @@ import { ISuggestResult, ISuggestSupport, ISuggestion, SuggestRegistry, SuggestC import { Position, IPosition } from 'vs/editor/common/core/position'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { CancellationToken } from 'vs/base/common/cancellation'; export const Context = { Visible: new RawContextKey('suggestWidgetVisible', false), @@ -47,7 +48,14 @@ export function setSnippetSuggestSupport(support: ISuggestSupport): ISuggestSupp return old; } -export function provideSuggestionItems(model: ITextModel, position: Position, snippetConfig: SnippetConfig = 'bottom', onlyFrom?: ISuggestSupport[], context?: SuggestContext): TPromise { +export function provideSuggestionItems( + model: ITextModel, + position: Position, + snippetConfig: SnippetConfig = 'bottom', + onlyFrom?: ISuggestSupport[], + context?: SuggestContext, + token: CancellationToken = CancellationToken.None +): Promise { const allSuggestions: ISuggestionItem[] = []; const acceptSuggestion = createSuggesionFilter(snippetConfig); @@ -69,13 +77,13 @@ export function provideSuggestionItems(model: ITextModel, position: Position, sn let hasResult = false; const factory = supports.map(supports => () => { // for each support in the group ask for suggestions - return TPromise.join(supports.map(support => { + return Promise.all(supports.map(support => { if (!isFalsyOrEmpty(onlyFrom) && onlyFrom.indexOf(support) < 0) { return undefined; } - return asWinJsPromise(token => support.provideCompletionItems(model, position, suggestConext, token)).then(container => { + return Promise.resolve(support.provideCompletionItems(model, position, suggestConext, token)).then(container => { const len = allSuggestions.length; @@ -104,7 +112,7 @@ export function provideSuggestionItems(model: ITextModel, position: Position, sn })); }); - const result = first(factory, () => hasResult).then(() => allSuggestions.sort(getSuggestionComparator(snippetConfig))); + const result = first2(factory, () => hasResult).then(() => allSuggestions.sort(getSuggestionComparator(snippetConfig))); // result.then(items => { // console.log(model.getWordUntilPosition(position), items.map(item => `${item.suggestion.label}, type=${item.suggestion.type}, incomplete?${item.container.incomplete}, overwriteBefore=${item.suggestion.overwriteBefore}`)); diff --git a/src/vs/editor/contrib/suggest/suggestModel.ts b/src/vs/editor/contrib/suggest/suggestModel.ts index e5ed0174696..4e7268f6ee7 100644 --- a/src/vs/editor/contrib/suggest/suggestModel.ts +++ b/src/vs/editor/contrib/suggest/suggestModel.ts @@ -5,12 +5,11 @@ 'use strict'; import { isFalsyOrEmpty } from 'vs/base/common/arrays'; -import { TimeoutTimer } from 'vs/base/common/async'; +import { TimeoutTimer, CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { values } from 'vs/base/common/map'; -import { TPromise } from 'vs/base/common/winjs.base'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { CursorChangeReason, ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; import { Position } from 'vs/editor/common/core/position'; @@ -93,7 +92,7 @@ export class SuggestModel implements IDisposable { private readonly _triggerRefilter = new TimeoutTimer(); private _state: State; - private _requestPromise: TPromise; + private _requestPromise: CancelablePromise; private _context: LineContext; private _currentSelection: Selection; @@ -356,11 +355,16 @@ export class SuggestModel implements IDisposable { suggestCtx = { triggerKind: SuggestTriggerKind.Invoke }; } - this._requestPromise = provideSuggestionItems(model, this._editor.getPosition(), + this._requestPromise = createCancelablePromise(token => provideSuggestionItems( + model, + this._editor.getPosition(), this._editor.getConfiguration().contribInfo.suggest.snippets, onlyFrom, - suggestCtx - ).then(items => { + suggestCtx, + token + )); + + this._requestPromise.then(items => { this._requestPromise = null; if (this._state === State.Idle) { @@ -386,7 +390,7 @@ export class SuggestModel implements IDisposable { ); this._onNewContext(ctx); - }).then(null, onUnexpectedError); + }).catch(onUnexpectedError); } private _onNewContext(ctx: LineContext): void { diff --git a/src/vs/workbench/parts/debug/electron-browser/breakpointWidget.ts b/src/vs/workbench/parts/debug/electron-browser/breakpointWidget.ts index bbf587bfb00..69c19b227fc 100644 --- a/src/vs/workbench/parts/debug/electron-browser/breakpointWidget.ts +++ b/src/vs/workbench/parts/debug/electron-browser/breakpointWidget.ts @@ -27,9 +27,7 @@ import uri from 'vs/base/common/uri'; import { SuggestRegistry, ISuggestResult, SuggestContext } from 'vs/editor/common/modes'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ITextModel } from 'vs/editor/common/model'; -import { wireCancellationToken } from 'vs/base/common/async'; import { provideSuggestionItems } from 'vs/editor/contrib/suggest/suggest'; -import { TPromise } from 'vs/base/common/winjs.base'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { transparent, editorForeground } from 'vs/platform/theme/common/colorRegistry'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -218,9 +216,9 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi this.toDispose.push(SuggestRegistry.register({ scheme: DEBUG_SCHEME, hasAccessToAllModels: true }, { provideCompletionItems: (model: ITextModel, position: Position, _context: SuggestContext, token: CancellationToken): Thenable => { - let suggestionsPromise: TPromise; + let suggestionsPromise: Promise; if (this.context === Context.CONDITION || this.context === Context.LOG_MESSAGE && this.isCurlyBracketOpen()) { - suggestionsPromise = provideSuggestionItems(this.editor.getModel(), new Position(this.lineNumber, 1), 'none', undefined, _context).then(suggestions => { + suggestionsPromise = provideSuggestionItems(this.editor.getModel(), new Position(this.lineNumber, 1), 'none', undefined, _context, token).then(suggestions => { let overwriteBefore = 0; if (this.context === Context.CONDITION) { @@ -242,10 +240,10 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi }; }); } else { - suggestionsPromise = TPromise.as({ suggestions: [] }); + suggestionsPromise = Promise.resolve({ suggestions: [] }); } - return wireCancellationToken(token, suggestionsPromise); + return suggestionsPromise; } })); } From 5ef47e74d8cfe05c352861847fcb69bd491e1143 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 4 Jul 2018 11:21:33 +0200 Subject: [PATCH 150/283] :lipstick: --- .../parts/extensions/electron-browser/extensionsActions.ts | 3 ++- .../parts/tasks/electron-browser/task.contribution.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts index 68b2f5565c5..88964425ea0 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts @@ -2057,7 +2057,8 @@ export abstract class AbstractConfigureRecommendedExtensionsAction extends Actio .then(selection => this.editorService.openEditor({ resource: workspaceConfigurationFile, options: { - selection + selection, + forceReload: true // because content has changed } })); } 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 6a058dff857..70ebf3100a0 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts @@ -1062,7 +1062,8 @@ class TaskService implements ITaskService { this.editorService.openEditor({ resource, options: { - pinned: false + pinned: false, + forceReload: true // because content might have changed } }); } From 3c99c24464389425ce256867cd3b8adae674122e Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 4 Jul 2018 11:26:03 +0200 Subject: [PATCH 151/283] debt - async winjs.promise, add cancelation token --- src/vs/editor/contrib/suggest/suggest.ts | 19 +++--- .../editor/contrib/suggest/suggestWidget.ts | 61 ++++++++----------- 2 files changed, 36 insertions(+), 44 deletions(-) diff --git a/src/vs/editor/contrib/suggest/suggest.ts b/src/vs/editor/contrib/suggest/suggest.ts index 36e922a6963..9bfe478497b 100644 --- a/src/vs/editor/contrib/suggest/suggest.ts +++ b/src/vs/editor/contrib/suggest/suggest.ts @@ -3,12 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { asWinJsPromise, first2 } from 'vs/base/common/async'; +import { first2 } from 'vs/base/common/async'; import { isFalsyOrEmpty } from 'vs/base/common/arrays'; import { compareIgnoreCase } from 'vs/base/common/strings'; import { assign } from 'vs/base/common/objects'; import { onUnexpectedExternalError } from 'vs/base/common/errors'; -import { TPromise } from 'vs/base/common/winjs.base'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import { registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions'; @@ -31,7 +30,7 @@ export interface ISuggestionItem { suggestion: ISuggestion; container: ISuggestResult; support: ISuggestSupport; - resolve(): TPromise; + resolve(token: CancellationToken): Thenable; } export type SnippetConfig = 'top' | 'bottom' | 'inline' | 'none'; @@ -133,13 +132,13 @@ function fixOverwriteBeforeAfter(suggestion: ISuggestion, container: ISuggestRes } } -function createSuggestionResolver(provider: ISuggestSupport, suggestion: ISuggestion, model: ITextModel, position: Position): () => TPromise { - return () => { +function createSuggestionResolver(provider: ISuggestSupport, suggestion: ISuggestion, model: ITextModel, position: Position): (token: CancellationToken) => Promise { + return (token) => { if (typeof provider.resolveCompletionItem === 'function') { - return asWinJsPromise(token => provider.resolveCompletionItem(model, position, suggestion, token)) - .then(value => { assign(suggestion, value); }); + return Promise.resolve(provider.resolveCompletionItem(model, position, suggestion, token)).then(value => { assign(suggestion, value); }); + } else { + return Promise.resolve(void 0); } - return TPromise.as(void 0); }; } @@ -221,13 +220,13 @@ registerDefaultLanguageCommand('_executeCompletionItemProvider', (model, positio return provideSuggestionItems(model, position).then(items => { for (const item of items) { if (resolving.length < maxItemsToResolve) { - resolving.push(item.resolve()); + resolving.push(item.resolve(CancellationToken.None)); } result.incomplete = result.incomplete || item.container.incomplete; result.suggestions.push(item.suggestion); } }).then(() => { - return TPromise.join(resolving); + return Promise.all(resolving); }).then(() => { return result; }); diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index 4d45c78a181..6e72075c97e 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -10,8 +10,7 @@ import * as nls from 'vs/nls'; import { createMatches } from 'vs/base/common/filters'; import * as strings from 'vs/base/common/strings'; import { Event, Emitter, chain } from 'vs/base/common/event'; -import { TPromise } from 'vs/base/common/winjs.base'; -import { isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors'; +import { onUnexpectedError } from 'vs/base/common/errors'; import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { addClass, append, $, hide, removeClass, show, toggleClass, getDomNodePagePosition, hasClass } from 'vs/base/browser/dom'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; @@ -33,6 +32,8 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { TimeoutTimer, CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; +import { CancellationToken } from 'vs/base/common/cancellation'; const sticky = false; // for development purposes const expandSuggestionDocsByDefault = false; @@ -361,7 +362,7 @@ export class SuggestWidget implements IContentWidget, IDelegate private state: State; private isAuto: boolean; private loadingTimeout: number; - private currentSuggestionDetails: TPromise; + private currentSuggestionDetails: CancelablePromise; private focusedItem: ICompletionItem; private ignoreFocusEvents = false; private completionModel: CompletionModel; @@ -377,8 +378,8 @@ export class SuggestWidget implements IContentWidget, IDelegate private suggestWidgetMultipleSuggestions: IContextKey; private suggestionSupportsAutoAccept: IContextKey; - private editorBlurTimeout: TPromise; - private showTimeout: TPromise; + private readonly editorBlurTimeout = new TimeoutTimer(); + private readonly showTimeout = new TimeoutTimer(); private toDispose: IDisposable[]; private onDidSelectEmitter = new Emitter(); @@ -482,11 +483,11 @@ export class SuggestWidget implements IContentWidget, IDelegate return; } - this.editorBlurTimeout = TPromise.timeout(150).then(() => { + this.editorBlurTimeout.cancelAndSet(() => { if (!this.editor.hasTextFocus()) { this.setState(State.Hidden); } - }); + }, 150); } private onEditorLayoutChange(): void { @@ -502,7 +503,7 @@ export class SuggestWidget implements IContentWidget, IDelegate const item = e.elements[0]; const index = e.indexes[0]; - item.resolve().then(() => { + item.resolve(CancellationToken.None).then(() => { this.onDidSelectEmitter.fire({ item, index, model: this.completionModel }); alert(nls.localize('suggestionAriaAccepted', "{0}, accepted", item.suggestion.label)); this.editor.focus(); @@ -586,22 +587,21 @@ export class SuggestWidget implements IContentWidget, IDelegate this.list.reveal(index); - this.currentSuggestionDetails = item.resolve() - .then(() => { - // item can have extra information, so re-render - this.ignoreFocusEvents = true; - this.list.splice(index, 1, [item]); - this.list.setFocus([index]); - this.ignoreFocusEvents = false; + this.currentSuggestionDetails = createCancelablePromise(token => item.resolve(token)); - if (this.expandDocsSettingFromStorage()) { - this.showDetails(); - } else { - removeClass(this.element, 'docs-side'); - } - }) - .then(null, err => !isPromiseCanceledError(err) && onUnexpectedError(err)) - .then(() => this.currentSuggestionDetails = null); + this.currentSuggestionDetails.then(() => { + // item can have extra information, so re-render + this.ignoreFocusEvents = true; + this.list.splice(index, 1, [item]); + this.list.setFocus([index]); + this.ignoreFocusEvents = false; + + if (this.expandDocsSettingFromStorage()) { + this.showDetails(); + } else { + removeClass(this.element, 'docs-side'); + } + }).catch(onUnexpectedError).then(() => this.currentSuggestionDetails = null); // emit an event this.onDidFocusEmitter.fire({ item, index, model: this.completionModel }); @@ -925,10 +925,10 @@ export class SuggestWidget implements IContentWidget, IDelegate this.suggestWidgetVisible.set(true); - this.showTimeout = TPromise.timeout(100).then(() => { + this.showTimeout.cancelAndSet(() => { addClass(this.element, 'visible'); this.onDidShowEmitter.fire(this); - }); + }, 100); } private hide(): void { @@ -1082,15 +1082,8 @@ export class SuggestWidget implements IContentWidget, IDelegate this.loadingTimeout = null; } - if (this.editorBlurTimeout) { - this.editorBlurTimeout.cancel(); - this.editorBlurTimeout = null; - } - - if (this.showTimeout) { - this.showTimeout.cancel(); - this.showTimeout = null; - } + this.editorBlurTimeout.dispose(); + this.showTimeout.dispose(); } } From a1a3ab33c2192fe438f4a6415743f5b3828f7809 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 4 Jul 2018 11:35:32 +0200 Subject: [PATCH 152/283] debt - avoid winjs.promise, add cancelation token --- .../contrib/wordHighlighter/wordHighlighter.ts | 16 ++++++++-------- .../api/extHostLanguageFeatures.test.ts | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts b/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts index b54ddc7f25a..1b7b9016979 100644 --- a/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts +++ b/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts @@ -5,9 +5,8 @@ import * as nls from 'vs/nls'; -import { asWinJsPromise, first } from 'vs/base/common/async'; +import { first2, createCancelablePromise, CancelablePromise } from 'vs/base/common/async'; import { onUnexpectedExternalError } from 'vs/base/common/errors'; -import { TPromise } from 'vs/base/common/winjs.base'; import { Range } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { registerEditorContribution, EditorAction, IActionOptions, registerEditorAction, registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions'; @@ -25,6 +24,7 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { firstIndex, isFalsyOrEmpty } from 'vs/base/common/arrays'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ITextModel, TrackedRangeStickiness, OverviewRulerLane, IModelDeltaDecoration } from 'vs/editor/common/model'; +import { CancellationToken } from 'vs/base/common/cancellation'; export const editorWordHighlight = registerColor('editor.wordHighlightBackground', { dark: '#575757B8', light: '#57575740', hc: null }, nls.localize('wordHighlight', 'Background color of a symbol during read-access, like reading a variable. The color must not be opaque to not hide underlying decorations.'), true); export const editorWordHighlightStrong = registerColor('editor.wordHighlightStrongBackground', { dark: '#004972B8', light: '#0e639c40', hc: null }, nls.localize('wordHighlightStrong', 'Background color of a symbol during write-access, like writing to a variable. The color must not be opaque to not hide underlying decorations.'), true); @@ -36,15 +36,15 @@ export const overviewRulerWordHighlightStrongForeground = registerColor('editorO export const ctxHasWordHighlights = new RawContextKey('hasWordHighlights', false); -export function getOccurrencesAtPosition(model: ITextModel, position: Position): TPromise { +export function getOccurrencesAtPosition(model: ITextModel, position: Position, token: CancellationToken = CancellationToken.None): Promise { const orderedByScore = DocumentHighlightProviderRegistry.ordered(model); // in order of score ask the occurrences provider // until someone response with a good result // (good = none empty array) - return first(orderedByScore.map(provider => () => { - return asWinJsPromise(token => provider.provideDocumentHighlights(model, position, token)) + return first2(orderedByScore.map(provider => () => { + return Promise.resolve(provider.provideDocumentHighlights(model, position, token)) .then(undefined, onUnexpectedExternalError); }), result => !isFalsyOrEmpty(result)); } @@ -61,7 +61,7 @@ class WordHighlighter { private toUnhook: IDisposable[]; private workerRequestTokenId: number = 0; - private workerRequest: TPromise = null; + private workerRequest: CancelablePromise = null; private workerRequestCompleted: boolean = false; private workerRequestValue: DocumentHighlight[] = []; @@ -291,7 +291,7 @@ class WordHighlighter { let myRequestId = ++this.workerRequestTokenId; this.workerRequestCompleted = false; - this.workerRequest = getOccurrencesAtPosition(this.model, this.editor.getPosition()); + this.workerRequest = createCancelablePromise(token => getOccurrencesAtPosition(this.model, this.editor.getPosition(), token)); this.workerRequest.then(data => { if (myRequestId === this.workerRequestTokenId) { @@ -299,7 +299,7 @@ class WordHighlighter { this.workerRequestValue = data || []; this._beginRenderDecorations(); } - }).done(); + }); } this._lastWordRange = currentWordRange; diff --git a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts index e638a644b43..4d258b1f81b 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts @@ -491,7 +491,7 @@ suite('ExtHostLanguageFeatures', function () { return rpcProtocol.sync().then(() => { - return getOccurrencesAtPosition(model, new EditorPosition(1, 2)).then(value => { + return getOccurrencesAtPosition(model, new EditorPosition(1, 2), CancellationToken.None).then(value => { assert.equal(value.length, 1); let [entry] = value; assert.deepEqual(entry.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 5 }); @@ -515,7 +515,7 @@ suite('ExtHostLanguageFeatures', function () { return rpcProtocol.sync().then(() => { - return getOccurrencesAtPosition(model, new EditorPosition(1, 2)).then(value => { + return getOccurrencesAtPosition(model, new EditorPosition(1, 2), CancellationToken.None).then(value => { assert.equal(value.length, 1); let [entry] = value; assert.deepEqual(entry.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 5 }); @@ -539,7 +539,7 @@ suite('ExtHostLanguageFeatures', function () { return rpcProtocol.sync().then(() => { - return getOccurrencesAtPosition(model, new EditorPosition(1, 2)).then(value => { + return getOccurrencesAtPosition(model, new EditorPosition(1, 2), CancellationToken.None).then(value => { assert.equal(value.length, 1); let [entry] = value; assert.deepEqual(entry.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 3 }); @@ -564,7 +564,7 @@ suite('ExtHostLanguageFeatures', function () { return rpcProtocol.sync().then(() => { - return getOccurrencesAtPosition(model, new EditorPosition(1, 2)).then(value => { + return getOccurrencesAtPosition(model, new EditorPosition(1, 2), CancellationToken.None).then(value => { assert.equal(value.length, 1); }); }); From ad984e8fac4900706e2b2a09526a9c7f6936e15a Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 4 Jul 2018 11:44:08 +0200 Subject: [PATCH 153/283] debt - avoid winjs.promise, add cancelation token --- src/vs/editor/contrib/codeAction/codeAction.ts | 6 +++--- .../editor/contrib/codeAction/codeActionCommands.ts | 5 +++-- src/vs/editor/contrib/codeAction/codeActionModel.ts | 11 ++++++----- src/vs/editor/contrib/codeAction/codeActionWidget.ts | 4 ++-- src/vs/editor/contrib/codeAction/lightBulbWidget.ts | 4 ++-- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/codeAction.ts b/src/vs/editor/contrib/codeAction/codeAction.ts index 983a88f179a..9a6ea89f441 100644 --- a/src/vs/editor/contrib/codeAction/codeAction.ts +++ b/src/vs/editor/contrib/codeAction/codeAction.ts @@ -7,7 +7,6 @@ import { isFalsyOrEmpty, mergeSort, flatten } from 'vs/base/common/arrays'; import { asWinJsPromise } from 'vs/base/common/async'; import { illegalArgument, onUnexpectedExternalError, isPromiseCanceledError } from 'vs/base/common/errors'; import URI from 'vs/base/common/uri'; -import { TPromise } from 'vs/base/common/winjs.base'; import { registerLanguageCommand } from 'vs/editor/browser/editorExtensions'; import { Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; @@ -15,8 +14,9 @@ import { CodeAction, CodeActionProviderRegistry, CodeActionContext, CodeActionTr import { IModelService } from 'vs/editor/common/services/modelService'; import { CodeActionFilter, CodeActionKind, CodeActionTrigger } from './codeActionTrigger'; import { Selection } from 'vs/editor/common/core/selection'; +import { CancellationToken } from 'vs/base/common/cancellation'; -export function getCodeActions(model: ITextModel, rangeOrSelection: Range | Selection, trigger?: CodeActionTrigger): TPromise { +export function getCodeActions(model: ITextModel, rangeOrSelection: Range | Selection, trigger?: CodeActionTrigger, token: CancellationToken = CancellationToken.None): Promise { const codeActionContext: CodeActionContext = { only: trigger && trigger.filter && trigger.filter.kind ? trigger.filter.kind.value : undefined, trigger: trigger && trigger.type === 'manual' ? CodeActionTriggerKind.Manual : CodeActionTriggerKind.Automatic @@ -38,7 +38,7 @@ export function getCodeActions(model: ITextModel, rangeOrSelection: Range | Sele }); }); - return TPromise.join(promises) + return Promise.all(promises) .then(flatten) .then(allCodeActions => mergeSort(allCodeActions, codeActionsComparator)); } diff --git a/src/vs/editor/contrib/codeAction/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/codeActionCommands.ts index 8af707d45ba..c86a8b15608 100644 --- a/src/vs/editor/contrib/codeAction/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/codeActionCommands.ts @@ -25,6 +25,7 @@ import { LightBulbWidget } from './lightBulbWidget'; import { escapeRegExpCharacters } from 'vs/base/common/strings'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { IProgressService } from 'vs/platform/progress/common/progress'; +import { CancelablePromise } from 'vs/base/common/async'; function contextKeyForSupportedActions(kind: CodeActionKind) { return ContextKeyExpr.regex( @@ -46,7 +47,7 @@ export class QuickFixController implements IEditorContribution { private _lightBulbWidget: LightBulbWidget; private _disposables: IDisposable[] = []; - private _activeRequest: TPromise | undefined; + private _activeRequest: CancelablePromise | undefined; constructor(editor: ICodeEditor, @IMarkerService markerService: IMarkerService, @@ -124,7 +125,7 @@ export class QuickFixController implements IEditorContribution { this._codeActionContextMenu.show(this._lightBulbWidget.model.actions, coords); } - public triggerFromEditorSelection(filter?: CodeActionFilter, autoApply?: CodeActionAutoApply): TPromise { + public triggerFromEditorSelection(filter?: CodeActionFilter, autoApply?: CodeActionAutoApply): Thenable { return this._model.trigger({ type: 'manual', filter, autoApply }); } diff --git a/src/vs/editor/contrib/codeAction/codeActionModel.ts b/src/vs/editor/contrib/codeAction/codeActionModel.ts index acbd68b1fa1..98211ae24dd 100644 --- a/src/vs/editor/contrib/codeAction/codeActionModel.ts +++ b/src/vs/editor/contrib/codeAction/codeActionModel.ts @@ -17,6 +17,7 @@ import { IMarkerService } from 'vs/platform/markers/common/markers'; import { getCodeActions } from './codeAction'; import { CodeActionTrigger } from './codeActionTrigger'; import { IProgressService } from 'vs/platform/progress/common/progress'; +import { createCancelablePromise, CancelablePromise } from 'vs/base/common/async'; export const SUPPORTED_CODE_ACTIONS = new RawContextKey('supportedCodeAction', ''); @@ -99,7 +100,7 @@ export class CodeActionOracle { return selection; } - private _createEventAndSignalChange(trigger: CodeActionTrigger, selection: Selection | undefined): TPromise { + private _createEventAndSignalChange(trigger: CodeActionTrigger, selection: Selection | undefined): Thenable { if (!selection) { // cancel this._signalChange({ @@ -113,10 +114,10 @@ export class CodeActionOracle { const model = this._editor.getModel(); const markerRange = this._getRangeOfMarker(selection); const position = markerRange ? markerRange.getStartPosition() : selection.getStartPosition(); - const actions = getCodeActions(model, selection, trigger); + const actions = createCancelablePromise(token => getCodeActions(model, selection, trigger, token)); if (this._progressService && trigger.type === 'manual') { - this._progressService.showWhile(actions, 250); + this._progressService.showWhile(TPromise.wrap(actions), 250); } this._signalChange({ @@ -134,7 +135,7 @@ export interface CodeActionsComputeEvent { trigger: CodeActionTrigger; rangeOrSelection: Range | Selection; position: Position; - actions: TPromise; + actions: CancelablePromise; } export class CodeActionModel { @@ -196,7 +197,7 @@ export class CodeActionModel { } } - trigger(trigger: CodeActionTrigger): TPromise { + trigger(trigger: CodeActionTrigger): Thenable { if (this._codeActionOracle) { return this._codeActionOracle.trigger(trigger); } diff --git a/src/vs/editor/contrib/codeAction/codeActionWidget.ts b/src/vs/editor/contrib/codeAction/codeActionWidget.ts index 0020eca69a4..b680ed6008b 100644 --- a/src/vs/editor/contrib/codeAction/codeActionWidget.ts +++ b/src/vs/editor/contrib/codeAction/codeActionWidget.ts @@ -28,7 +28,7 @@ export class CodeActionContextMenu { private readonly _onApplyCodeAction: (action: CodeAction) => TPromise ) { } - show(fixes: TPromise, at: { x: number; y: number } | Position) { + show(fixes: Thenable, at: { x: number; y: number } | Position) { const actions = fixes.then(value => { return value.map(action => { @@ -53,7 +53,7 @@ export class CodeActionContextMenu { } return at; }, - getActions: () => actions, + getActions: () => TPromise.wrap(actions), onHide: () => { this._visible = false; this._editor.focus(); diff --git a/src/vs/editor/contrib/codeAction/lightBulbWidget.ts b/src/vs/editor/contrib/codeAction/lightBulbWidget.ts index fd77972c961..0fd9d8270f1 100644 --- a/src/vs/editor/contrib/codeAction/lightBulbWidget.ts +++ b/src/vs/editor/contrib/codeAction/lightBulbWidget.ts @@ -116,7 +116,7 @@ export class LightBulbWidget implements IDisposable, IContentWidget { this._model = value; const selection = this._model.rangeOrSelection; - this._model.actions.done(fixes => { + this._model.actions.then(fixes => { if (!token.isCancellationRequested && fixes && fixes.length > 0) { if (selection.isEmpty() && fixes.every(fix => fix.kind && CodeActionKind.Refactor.contains(fix.kind))) { this.hide(); @@ -126,7 +126,7 @@ export class LightBulbWidget implements IDisposable, IContentWidget { } else { this.hide(); } - }, err => { + }).catch(err => { this.hide(); }); } From 987160c70adb3fdab30da30fef7d1c05a74ff858 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 4 Jul 2018 11:59:14 +0200 Subject: [PATCH 154/283] debt - avoid winjs.promise, add cancelation token --- .../goToDefinition/goToDefinitionCommands.ts | 3 ++- .../contrib/referenceSearch/referenceSearch.ts | 13 +++++++------ .../contrib/referenceSearch/referencesController.ts | 3 ++- .../api/extHostLanguageFeatures.test.ts | 6 +++--- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts b/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts index 849dd29d8d0..8c15d98772c 100644 --- a/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts +++ b/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts @@ -26,6 +26,7 @@ import { IProgressService } from 'vs/platform/progress/common/progress'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ITextModel, IWordAtPosition } from 'vs/editor/common/model'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { createCancelablePromise } from 'vs/base/common/async'; export class DefinitionActionConfig { @@ -159,7 +160,7 @@ export class DefinitionAction extends EditorAction { private _openInPeek(editorService: ICodeEditorService, target: ICodeEditor, model: ReferencesModel) { let controller = ReferencesController.get(target); if (controller) { - controller.toggleWidget(target.getSelection(), TPromise.as(model), { + controller.toggleWidget(target.getSelection(), createCancelablePromise(_ => Promise.resolve(model)), { getMetaTitle: (model) => { return this._getMetaTitle(model); }, diff --git a/src/vs/editor/contrib/referenceSearch/referenceSearch.ts b/src/vs/editor/contrib/referenceSearch/referenceSearch.ts index 1f68bd41f23..cb21013f62b 100644 --- a/src/vs/editor/contrib/referenceSearch/referenceSearch.ts +++ b/src/vs/editor/contrib/referenceSearch/referenceSearch.ts @@ -17,7 +17,7 @@ import { Range } from 'vs/editor/common/core/range'; import { PeekContext, getOuterEditor } from './peekViewWidget'; import { ReferencesController, RequestOptions, ctxReferenceSearchVisible } from './referencesController'; import { ReferencesModel, OneReference } from './referencesModel'; -import { asWinJsPromise } from 'vs/base/common/async'; +import { asWinJsPromise, createCancelablePromise } from 'vs/base/common/async'; import { onUnexpectedExternalError } from 'vs/base/common/errors'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; @@ -28,6 +28,7 @@ import { ctxReferenceWidgetSearchTreeFocused } from 'vs/editor/contrib/reference import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; import URI from 'vs/base/common/uri'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { CancellationToken } from 'vs/base/common/cancellation'; export const defaultReferenceSearchOptions: RequestOptions = { getMetaTitle(model) { @@ -85,7 +86,7 @@ export class ReferenceAction extends EditorAction { } let range = editor.getSelection(); let model = editor.getModel(); - let references = provideReferences(model, range.getStartPosition()).then(references => new ReferencesModel(references)); + let references = createCancelablePromise(token => provideReferences(model, range.getStartPosition(), token).then(references => new ReferencesModel(references))); controller.toggleWidget(range, references, defaultReferenceSearchOptions); } } @@ -113,7 +114,7 @@ let findReferencesCommand: ICommandHandler = (accessor: ServicesAccessor, resour return undefined; } - let references = provideReferences(control.getModel(), Position.lift(position)).then(references => new ReferencesModel(references)); + let references = createCancelablePromise(token => provideReferences(control.getModel(), Position.lift(position), token).then(references => new ReferencesModel(references))); let range = new Range(position.lineNumber, position.column, position.lineNumber, position.column); return TPromise.as(controller.toggleWidget(range, references, defaultReferenceSearchOptions)); }); @@ -137,7 +138,7 @@ let showReferencesCommand: ICommandHandler = (accessor: ServicesAccessor, resour return TPromise.as(controller.toggleWidget( new Range(position.lineNumber, position.column, position.lineNumber, position.column), - TPromise.as(new ReferencesModel(references)), + createCancelablePromise(_ => Promise.reject(new ReferencesModel(references))), defaultReferenceSearchOptions)).then(() => true); }); }; @@ -266,7 +267,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: openReferenceToSide }); -export function provideReferences(model: ITextModel, position: Position): TPromise { +export function provideReferences(model: ITextModel, position: Position, token: CancellationToken): Promise { // collect references from all providers const promises = ReferenceProviderRegistry.ordered(model).map(provider => { @@ -282,7 +283,7 @@ export function provideReferences(model: ITextModel, position: Position): TPromi }); }); - return TPromise.join(promises).then(references => { + return Promise.all(promises).then(references => { let result: Location[] = []; for (let ref of references) { if (ref) { diff --git a/src/vs/editor/contrib/referenceSearch/referencesController.ts b/src/vs/editor/contrib/referenceSearch/referencesController.ts index 2c03e0d6ebc..92dcaddafe9 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesController.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesController.ts @@ -25,6 +25,7 @@ import { Position } from 'vs/editor/common/core/position'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { Location } from 'vs/editor/common/modes'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { CancelablePromise } from 'vs/base/common/async'; export const ctxReferenceSearchVisible = new RawContextKey('referenceSearchVisible', false); @@ -82,7 +83,7 @@ export abstract class ReferencesController implements editorCommon.IEditorContri this._editor = null; } - public toggleWidget(range: Range, modelPromise: TPromise, options: RequestOptions): void { + public toggleWidget(range: Range, modelPromise: CancelablePromise, options: RequestOptions): void { // close current widget and return early is position didn't change let widgetPosition: Position; diff --git a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts index 4d258b1f81b..4b12b3c4b9a 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts @@ -588,7 +588,7 @@ suite('ExtHostLanguageFeatures', function () { return rpcProtocol.sync().then(() => { - return provideReferences(model, new EditorPosition(1, 2)).then(value => { + return provideReferences(model, new EditorPosition(1, 2), CancellationToken.None).then(value => { assert.equal(value.length, 2); let [first, second] = value; @@ -608,7 +608,7 @@ suite('ExtHostLanguageFeatures', function () { return rpcProtocol.sync().then(() => { - return provideReferences(model, new EditorPosition(1, 2)).then(value => { + return provideReferences(model, new EditorPosition(1, 2), CancellationToken.None).then(value => { assert.equal(value.length, 1); let [item] = value; @@ -634,7 +634,7 @@ suite('ExtHostLanguageFeatures', function () { return rpcProtocol.sync().then(() => { - return provideReferences(model, new EditorPosition(1, 2)).then(value => { + return provideReferences(model, new EditorPosition(1, 2), CancellationToken.None).then(value => { assert.equal(value.length, 1); }); From 3479e4a508eb00e6b1b182d97c65147f3c39b6d9 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 4 Jul 2018 12:07:33 +0200 Subject: [PATCH 155/283] debt - avoid winjs.promise, add cancelation token --- src/vs/editor/contrib/hover/getHover.ts | 11 ++++------- src/vs/editor/contrib/hover/hoverOperation.ts | 19 +++++++++++-------- .../editor/contrib/hover/modesContentHover.ts | 8 ++++---- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/vs/editor/contrib/hover/getHover.ts b/src/vs/editor/contrib/hover/getHover.ts index a6bf28af9c8..466f04cab88 100644 --- a/src/vs/editor/contrib/hover/getHover.ts +++ b/src/vs/editor/contrib/hover/getHover.ts @@ -7,22 +7,19 @@ import { coalesce } from 'vs/base/common/arrays'; import { onUnexpectedExternalError } from 'vs/base/common/errors'; -import { TPromise } from 'vs/base/common/winjs.base'; import { ITextModel } from 'vs/editor/common/model'; import { registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions'; import { Hover, HoverProviderRegistry } from 'vs/editor/common/modes'; -import { asWinJsPromise } from 'vs/base/common/async'; import { Position } from 'vs/editor/common/core/position'; +import { CancellationToken } from 'vs/base/common/cancellation'; -export function getHover(model: ITextModel, position: Position): TPromise { +export function getHover(model: ITextModel, position: Position, token: CancellationToken = CancellationToken.None): Promise { const supports = HoverProviderRegistry.ordered(model); const values: Hover[] = []; const promises = supports.map((support, idx) => { - return asWinJsPromise((token) => { - return support.provideHover(model, position, token); - }).then((result) => { + return Promise.resolve(support.provideHover(model, position, token)).then((result) => { if (result) { let hasRange = (typeof result.range !== 'undefined'); let hasHtmlContent = typeof result.contents !== 'undefined' && result.contents && result.contents.length > 0; @@ -35,7 +32,7 @@ export function getHover(model: ITextModel, position: Position): TPromise coalesce(values)); + return Promise.all(promises).then(() => coalesce(values)); } registerDefaultLanguageCommand('_executeHoverProvider', getHover); diff --git a/src/vs/editor/contrib/hover/hoverOperation.ts b/src/vs/editor/contrib/hover/hoverOperation.ts index fa6b0e949d1..16699608cd6 100644 --- a/src/vs/editor/contrib/hover/hoverOperation.ts +++ b/src/vs/editor/contrib/hover/hoverOperation.ts @@ -4,16 +4,16 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { RunOnceScheduler } from 'vs/base/common/async'; +import { RunOnceScheduler, CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { TPromise } from 'vs/base/common/winjs.base'; +import { CancellationToken } from 'vs/base/common/cancellation'; export interface IHoverComputer { /** * This is called after half the hover time */ - computeAsync?: () => TPromise; + computeAsync?: (token: CancellationToken) => Promise; /** * This is called after all the hover time @@ -57,7 +57,7 @@ export class HoverOperation { private _firstWaitScheduler: RunOnceScheduler; private _secondWaitScheduler: RunOnceScheduler; private _loadingMessageScheduler: RunOnceScheduler; - private _asyncComputationPromise: TPromise; + private _asyncComputationPromise: CancelablePromise; private _asyncComputationPromiseDone: boolean; private _completeCallback: (r: Result) => void; @@ -103,10 +103,13 @@ export class HoverOperation { if (this._computer.computeAsync) { this._asyncComputationPromiseDone = false; - this._asyncComputationPromise = this._computer.computeAsync().then((asyncResult: Result) => { - this._asyncComputationPromiseDone = true; - this._withAsyncResult(asyncResult); - }, (e) => this._onError(e)); + this._asyncComputationPromise = createCancelablePromise(token => { + return this._computer.computeAsync(token).then((asyncResult: Result) => { + this._asyncComputationPromiseDone = true; + this._withAsyncResult(asyncResult); + }, (e) => this._onError(e)); + }); + } else { this._asyncComputationPromiseDone = true; } diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index 2713b5d2f4d..d4c916a5661 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -6,7 +6,6 @@ import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; -import { TPromise } from 'vs/base/common/winjs.base'; import { IRange, Range } from 'vs/editor/common/core/range'; import { Position } from 'vs/editor/common/core/position'; import { HoverProviderRegistry, Hover, IColor, DocumentColorProvider } from 'vs/editor/common/modes'; @@ -24,6 +23,7 @@ import { Color, RGBA } from 'vs/base/common/color'; import { IDisposable, Disposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { getColorPresentations } from 'vs/editor/contrib/colorPicker/color'; import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { CancellationToken } from 'vs/base/common/cancellation'; const $ = dom.$; class ColorHover { @@ -57,17 +57,17 @@ class ModesContentComputer implements IHoverComputer { this._result = []; } - computeAsync(): TPromise { + computeAsync(token: CancellationToken): Promise { const model = this._editor.getModel(); if (!HoverProviderRegistry.has(model)) { - return TPromise.as(null); + return Promise.resolve(null); } return getHover(model, new Position( this._range.startLineNumber, this._range.startColumn - )); + ), token); } computeSync(): HoverPart[] { From 95d7e43ba9fb527ad4b829803846aac62219daea Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 4 Jul 2018 12:09:26 +0200 Subject: [PATCH 156/283] debt - use TimeoutTimer instead of TPromise.timeout --- .../parts/welcome/page/electron-browser/welcomePage.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts b/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts index 67102680756..e9860c97d85 100644 --- a/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts +++ b/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts @@ -38,6 +38,7 @@ import { IWorkspaceIdentifier, getWorkspaceLabel, ISingleFolderWorkspaceIdentifi import { IEditorInputFactory, EditorInput } from 'vs/workbench/common/editor'; import { getIdAndVersionFromLocalExtensionId } from 'vs/platform/extensionManagement/node/extensionManagementUtil'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { TimeoutTimer } from 'vs/base/common/async'; used(); @@ -431,10 +432,10 @@ class WelcomePage { [{ label: localize('ok', "OK"), run: () => { - const messageDelay = TPromise.timeout(300); - messageDelay.then(() => { + const messageDelay = new TimeoutTimer(); + messageDelay.cancelAndSet(() => { this.notificationService.info(strings.installing.replace('{0}', extensionSuggestion.name)); - }); + }, 300); TPromise.join(extensionSuggestion.isKeymap ? extensions.filter(extension => isKeymapExtension(this.tipsService, extension) && extension.globallyEnabled) .map(extension => { return this.extensionEnablementService.setEnablement(extension.local, EnablementState.Disabled); From 8a0e53a63b697c8a85094f09d02bfec5561f3631 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 4 Jul 2018 12:12:57 +0200 Subject: [PATCH 157/283] debt - use TimeoutTimer instead of TPromise.timeout --- .../contrib/colorPicker/colorDetector.ts | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/vs/editor/contrib/colorPicker/colorDetector.ts b/src/vs/editor/contrib/colorPicker/colorDetector.ts index 97fbb7b8852..9de89ee83d7 100644 --- a/src/vs/editor/contrib/colorPicker/colorDetector.ts +++ b/src/vs/editor/contrib/colorPicker/colorDetector.ts @@ -17,6 +17,7 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { getColors, IColorData } from 'vs/editor/contrib/colorPicker/color'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; +import { TimeoutTimer } from 'vs/base/common/async'; const MAX_DECORATORS = 500; @@ -29,7 +30,7 @@ export class ColorDetector implements IEditorContribution { private _globalToDispose: IDisposable[] = []; private _localToDispose: IDisposable[] = []; private _computePromise: TPromise; - private _timeoutPromise: TPromise; + private _timeoutTimer: TimeoutTimer; private _decorationsIds: string[] = []; private _colorDatas = new Map(); @@ -61,7 +62,7 @@ export class ColorDetector implements IEditorContribution { } })); - this._timeoutPromise = null; + this._timeoutTimer = null; this._computePromise = null; this._isEnabled = this.isEnabled(); this.onModelChanged(); @@ -115,12 +116,12 @@ export class ColorDetector implements IEditorContribution { } this._localToDispose.push(this._editor.onDidChangeModelContent((e) => { - if (!this._timeoutPromise) { - this._timeoutPromise = TPromise.timeout(ColorDetector.RECOMPUTE_TIME); - this._timeoutPromise.then(() => { - this._timeoutPromise = null; + if (!this._timeoutTimer) { + this._timeoutTimer = new TimeoutTimer(); + this._timeoutTimer.cancelAndSet(() => { + this._timeoutTimer = null; this.beginCompute(); - }); + }, ColorDetector.RECOMPUTE_TIME); } })); this.beginCompute(); @@ -135,9 +136,9 @@ export class ColorDetector implements IEditorContribution { } private stop(): void { - if (this._timeoutPromise) { - this._timeoutPromise.cancel(); - this._timeoutPromise = null; + if (this._timeoutTimer) { + this._timeoutTimer.cancel(); + this._timeoutTimer = null; } if (this._computePromise) { this._computePromise.cancel(); From 3c5beb9fb0f375ed925f48cbc07018833b33185a Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 4 Jul 2018 12:28:22 +0200 Subject: [PATCH 158/283] debt - avoid winjs.promise, add cancelation token --- src/vs/editor/contrib/colorPicker/color.ts | 11 ++++++----- .../editor/contrib/colorPicker/colorDetector.ts | 15 ++++++++------- src/vs/editor/contrib/hover/modesContentHover.ts | 4 ++-- .../api/extHostLanguageFeatures.test.ts | 2 +- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/vs/editor/contrib/colorPicker/color.ts b/src/vs/editor/contrib/colorPicker/color.ts index e3f8d687510..2ebc3d1b8ca 100644 --- a/src/vs/editor/contrib/colorPicker/color.ts +++ b/src/vs/editor/contrib/colorPicker/color.ts @@ -13,6 +13,7 @@ import { registerLanguageCommand } from 'vs/editor/browser/editorExtensions'; import { Range, IRange } from 'vs/editor/common/core/range'; import { illegalArgument } from 'vs/base/common/errors'; import { IModelService } from 'vs/editor/common/services/modelService'; +import { CancellationToken } from 'vs/base/common/cancellation'; export interface IColorData { @@ -20,10 +21,10 @@ export interface IColorData { provider: DocumentColorProvider; } -export function getColors(model: ITextModel): TPromise { +export function getColors(model: ITextModel, token: CancellationToken): Promise { const colors: IColorData[] = []; const providers = ColorProviderRegistry.ordered(model).reverse(); - const promises = providers.map(provider => asWinJsPromise(token => provider.provideDocumentColors(model, token)).then(result => { + const promises = providers.map(provider => Promise.resolve(provider.provideDocumentColors(model, token)).then(result => { if (Array.isArray(result)) { for (let colorInfo of result) { colors.push({ colorInfo, provider }); @@ -31,11 +32,11 @@ export function getColors(model: ITextModel): TPromise { } })); - return TPromise.join(promises).then(() => colors); + return Promise.all(promises).then(() => colors); } -export function getColorPresentations(model: ITextModel, colorInfo: IColorInformation, provider: DocumentColorProvider): TPromise { - return asWinJsPromise(token => provider.provideColorPresentations(model, colorInfo, token)); +export function getColorPresentations(model: ITextModel, colorInfo: IColorInformation, provider: DocumentColorProvider, token: CancellationToken): Promise { + return Promise.resolve(provider.provideColorPresentations(model, colorInfo, token)); } registerLanguageCommand('_executeDocumentColorProvider', function (accessor, args) { diff --git a/src/vs/editor/contrib/colorPicker/colorDetector.ts b/src/vs/editor/contrib/colorPicker/colorDetector.ts index 9de89ee83d7..b3836d6aa68 100644 --- a/src/vs/editor/contrib/colorPicker/colorDetector.ts +++ b/src/vs/editor/contrib/colorPicker/colorDetector.ts @@ -6,7 +6,6 @@ import { RGBA } from 'vs/base/common/color'; import { hash } from 'vs/base/common/hash'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { TPromise } from 'vs/base/common/winjs.base'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -17,7 +16,7 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { getColors, IColorData } from 'vs/editor/contrib/colorPicker/color'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; -import { TimeoutTimer } from 'vs/base/common/async'; +import { TimeoutTimer, CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; const MAX_DECORATORS = 500; @@ -29,7 +28,7 @@ export class ColorDetector implements IEditorContribution { private _globalToDispose: IDisposable[] = []; private _localToDispose: IDisposable[] = []; - private _computePromise: TPromise; + private _computePromise: CancelablePromise; private _timeoutTimer: TimeoutTimer; private _decorationsIds: string[] = []; @@ -128,10 +127,12 @@ export class ColorDetector implements IEditorContribution { } private beginCompute(): void { - this._computePromise = getColors(this._editor.getModel()).then(colorInfos => { - this.updateDecorations(colorInfos); - this.updateColorDecorators(colorInfos); - this._computePromise = null; + this._computePromise = createCancelablePromise(token => { + return getColors(this._editor.getModel(), token).then(colorInfos => { + this.updateDecorations(colorInfos); + this.updateColorDecorators(colorInfos); + this._computePromise = null; + }); }); } diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index d4c916a5661..26db8c908f0 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -339,7 +339,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { const model = new ColorPickerModel(color, [], 0); const widget = new ColorPickerWidget(fragment, model, this._editor.getConfiguration().pixelRatio, this._themeService); - getColorPresentations(editorModel, colorInfo, msg.provider).then(colorPresentations => { + getColorPresentations(editorModel, colorInfo, msg.provider, CancellationToken.None).then(colorPresentations => { model.colorPresentations = colorPresentations; const originalText = this._editor.getModel().getValueInRange(msg.range); model.guessColorPresentation(color, originalText); @@ -381,7 +381,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { blue: color.rgba.b / 255, alpha: color.rgba.a } - }, msg.provider).then((colorPresentations) => { + }, msg.provider, CancellationToken.None).then((colorPresentations) => { model.colorPresentations = colorPresentations; }); }; diff --git a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts index 4b12b3c4b9a..b9995cf46f8 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts @@ -1199,7 +1199,7 @@ suite('ExtHostLanguageFeatures', function () { })); return rpcProtocol.sync().then(() => { - return getColors(model).then(value => { + return getColors(model, CancellationToken.None).then(value => { assert.equal(value.length, 1); let [first] = value; From e0a5cf18ce6e6ee63ddd7e2ef9bdf5eb6c3245ec Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 4 Jul 2018 14:47:23 +0200 Subject: [PATCH 159/283] debt - remove one Promise.join overload, #53526 --- src/vs/base/common/winjs.base.d.ts | 1 - src/vs/workbench/services/configuration/node/configuration.ts | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vs/base/common/winjs.base.d.ts b/src/vs/base/common/winjs.base.d.ts index b176120a06b..f5b941a8873 100644 --- a/src/vs/base/common/winjs.base.d.ts +++ b/src/vs/base/common/winjs.base.d.ts @@ -39,7 +39,6 @@ export declare class Promise { public static join(promises: [T1 | PromiseLike, T2 | PromiseLike]): Promise<[T1, T2]>; public static join(promises: (T | PromiseLike)[]): Promise; - public static join(promises: { [n: string]: T | PromiseLike }): Promise<{ [n: string]: T }>; public static any(promises: (T | PromiseLike)[]): Promise<{ key: string; value: Promise; }>; diff --git a/src/vs/workbench/services/configuration/node/configuration.ts b/src/vs/workbench/services/configuration/node/configuration.ts index a47582c4722..e09d62ad085 100644 --- a/src/vs/workbench/services/configuration/node/configuration.ts +++ b/src/vs/workbench/services/configuration/node/configuration.ts @@ -284,7 +284,7 @@ export class FileServiceBasedFolderConfiguration extends AbstractFolderConfigura } }).then(null, err => [] /* never fail this call */); - return bulkContentFetchromise.then(() => TPromise.join(workspaceFilePathToConfiguration).then(result => collections.values(result))); + return bulkContentFetchromise.then(() => TPromise.join(collections.values(workspaceFilePathToConfiguration))); } private handleWorkspaceFileEvents(event: FileChangesEvent): void { @@ -489,4 +489,4 @@ export class FolderConfiguration extends Disposable implements IFolderConfigurat } return TPromise.as(null); } -} \ No newline at end of file +} From 2902a13bfc81fd33ce868170bfb15b17e9e32a97 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 4 Jul 2018 14:53:19 +0200 Subject: [PATCH 160/283] Editor: load changes to file in the background if file already loaded (fixes #53532) --- .../browser/parts/editor/binaryEditor.ts | 2 +- .../browser/parts/editor/textDiffEditor.ts | 2 +- .../parts/editor/textResourceEditor.ts | 2 +- src/vs/workbench/common/editor.ts | 8 ++- .../common/editor/dataUriEditorInput.ts | 2 +- .../common/editor/diffEditorInput.ts | 20 ++----- .../common/editor/resourceEditorInput.ts | 2 +- .../extensions/common/extensionsInput.ts | 2 +- .../runtimeExtensionsEditor.ts | 2 +- .../browser/editors/fileEditorTracker.ts | 53 +++++++------------ .../files/browser/editors/textFileEditor.ts | 2 +- .../files/common/editors/fileEditorInput.ts | 12 +++-- .../test/browser/fileEditorInput.test.ts | 27 ++++------ .../electron-browser/webviewEditorInput.ts | 2 +- .../electron-browser/walkThroughPart.ts | 2 +- .../walkThrough/node/walkThroughInput.ts | 2 +- .../common/preferencesEditorInput.ts | 4 +- .../common/textFileEditorModelManager.ts | 27 +++++++++- .../services/textfile/common/textfiles.ts | 12 ++++- .../test/textFileEditorModelManager.test.ts | 2 +- .../browser/parts/editor/baseEditor.test.ts | 4 +- .../test/common/editor/editor.test.ts | 2 +- .../common/editor/editorDiffModel.test.ts | 4 +- .../test/common/editor/editorInput.test.ts | 2 +- 24 files changed, 103 insertions(+), 96 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/binaryEditor.ts b/src/vs/workbench/browser/parts/editor/binaryEditor.ts index aa9d7062c83..3b662fff332 100644 --- a/src/vs/workbench/browser/parts/editor/binaryEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryEditor.ts @@ -73,7 +73,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { setInput(input: EditorInput, options: EditorOptions, token: CancellationToken): Thenable { return super.setInput(input, options, token).then(() => { - return input.resolve(true).then(model => { + return input.resolve().then(model => { // Check for cancellation if (token.isCancellationRequested) { diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index 190eb410696..f59bae6f10b 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -104,7 +104,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { // Set input and resolve return super.setInput(input, options, token).then(() => { - return input.resolve(true).then(resolvedModel => { + return input.resolve().then(resolvedModel => { // Check for cancellation if (token.isCancellationRequested) { diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index 2154600d6a8..0c0d212617b 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -61,7 +61,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor { // Set input and resolve return super.setInput(input, options, token).then(() => { - return input.resolve(true).then((resolvedModel: EditorModel) => { + return input.resolve().then((resolvedModel: EditorModel) => { // Check for cancellation if (token.isCancellationRequested) { diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 97889553c0b..e49e2d26fc5 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -395,11 +395,9 @@ export abstract class EditorInput extends Disposable implements IEditorInput { /** * Returns a type of EditorModel that represents the resolved input. Subclasses should - * override to provide a meaningful model. The optional second argument allows to specify - * if the EditorModel should be refreshed before returning it. Depending on the implementation - * this could mean to refresh the editor model contents with the version from disk. + * override to provide a meaningful model. */ - abstract resolve(refresh?: boolean): TPromise; + abstract resolve(): TPromise; /** * An editor that is dirty will be asked to be saved once it closes. @@ -582,7 +580,7 @@ export class SideBySideEditorInput extends EditorInput { this._register(this.master.onDidChangeLabel(() => this._onDidChangeLabel.fire())); } - resolve(refresh?: boolean): TPromise { + resolve(): TPromise { return TPromise.as(null); } diff --git a/src/vs/workbench/common/editor/dataUriEditorInput.ts b/src/vs/workbench/common/editor/dataUriEditorInput.ts index cc8bb4cb10c..07f9455bd3f 100644 --- a/src/vs/workbench/common/editor/dataUriEditorInput.ts +++ b/src/vs/workbench/common/editor/dataUriEditorInput.ts @@ -65,7 +65,7 @@ export class DataUriEditorInput extends EditorInput { return this.description; } - resolve(refresh?: boolean): TPromise { + resolve(): TPromise { return this.instantiationService.createInstance(BinaryEditorModel, this.resource, this.getName()).load().then(m => m as BinaryEditorModel); } diff --git a/src/vs/workbench/common/editor/diffEditorInput.ts b/src/vs/workbench/common/editor/diffEditorInput.ts index 7625ffdf06f..36f7632e566 100644 --- a/src/vs/workbench/common/editor/diffEditorInput.ts +++ b/src/vs/workbench/common/editor/diffEditorInput.ts @@ -36,23 +36,13 @@ export class DiffEditorInput extends SideBySideEditorInput { return this.master; } - resolve(refresh?: boolean): TPromise { - let modelPromise: TPromise; - - // Use Cached Model - if (this.cachedModel && !refresh) { - modelPromise = TPromise.as(this.cachedModel); - } + resolve(): TPromise { // Create Model - we never reuse our cached model if refresh is true because we cannot // decide for the inputs within if the cached model can be reused or not. There may be // inputs that need to be loaded again and thus we always recreate the model and dispose // the previous one - if any. - else { - modelPromise = this.createModel(refresh); - } - - return modelPromise.then((resolvedModel: DiffEditorModel) => { + return this.createModel().then(resolvedModel => { if (this.cachedModel) { this.cachedModel.dispose(); } @@ -71,9 +61,9 @@ export class DiffEditorInput extends SideBySideEditorInput { // Join resolve call over two inputs and build diff editor model return TPromise.join([ - this.originalInput.resolve(refresh), - this.modifiedInput.resolve(refresh) - ]).then((models) => { + this.originalInput.resolve(), + this.modifiedInput.resolve() + ]).then(models => { const originalEditorModel = models[0]; const modifiedEditorModel = models[1]; diff --git a/src/vs/workbench/common/editor/resourceEditorInput.ts b/src/vs/workbench/common/editor/resourceEditorInput.ts index 44a3789a400..572510986ac 100644 --- a/src/vs/workbench/common/editor/resourceEditorInput.ts +++ b/src/vs/workbench/common/editor/resourceEditorInput.ts @@ -82,7 +82,7 @@ export class ResourceEditorInput extends EditorInput { return descriptor; } - resolve(refresh?: boolean): TPromise { + resolve(): TPromise { if (!this.modelReference) { this.modelReference = this.textModelResolverService.createModelReference(this.resource); } diff --git a/src/vs/workbench/parts/extensions/common/extensionsInput.ts b/src/vs/workbench/parts/extensions/common/extensionsInput.ts index 23d3e199d16..3a7642cd39a 100644 --- a/src/vs/workbench/parts/extensions/common/extensionsInput.ts +++ b/src/vs/workbench/parts/extensions/common/extensionsInput.ts @@ -44,7 +44,7 @@ export class ExtensionsInput extends EditorInput { return this.extension === otherExtensionInput.extension; } - resolve(refresh?: boolean): TPromise { + resolve(): TPromise { return TPromise.as(null); } diff --git a/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts index eafae3d229d..ed9c80d3b1a 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -443,7 +443,7 @@ export class RuntimeExtensionsInput extends EditorInput { return true; } - resolve(refresh?: boolean): TPromise { + resolve(): TPromise { return TPromise.as(null); } diff --git a/src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts b/src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts index 4a3e6340596..d18cd6f8988 100644 --- a/src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts +++ b/src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts @@ -6,12 +6,11 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import * as errors from 'vs/base/common/errors'; import URI from 'vs/base/common/uri'; import * as paths from 'vs/base/common/paths'; import { IEditorViewState } from 'vs/editor/common/editorCommon'; import { toResource, SideBySideEditorInput, IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; -import { ITextFileService, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { FileOperationEvent, FileOperation, IFileService, FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files'; import { FileEditorInput } from 'vs/workbench/parts/files/common/editors/fileEditorInput'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; @@ -20,7 +19,6 @@ import { distinct } from 'vs/base/common/arrays'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { isLinux } from 'vs/base/common/platform'; -import { ResourceQueue } from 'vs/base/common/async'; import { ResourceMap } from 'vs/base/common/map'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -34,7 +32,6 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut protected closeOnFileDelete: boolean; - private modelLoadQueue: ResourceQueue; private activeOutOfWorkspaceWatchers: ResourceMap; constructor( @@ -50,7 +47,6 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut ) { super(); - this.modelLoadQueue = new ResourceQueue(); this.activeOutOfWorkspaceWatchers = new ResourceMap(); this.onConfigurationUpdated(configurationService.getValue()); @@ -101,7 +97,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut }) .filter(model => model && !model.isDirty()), m => m.getResource().toString() - ).forEach(model => this.queueModelLoad(model)); + ).forEach(model => this.textFileService.models.reload(model)); } } @@ -125,7 +121,9 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut private onFileChanges(e: FileChangesEvent): void { // Handle updates - this.handleUpdates(e); + if (e.gotAdded() || e.gotUpdated()) { + this.handleUpdates(e); + } // Handle deletes if (e.gotDeleted()) { @@ -286,11 +284,23 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut private handleUpdates(e: FileChangesEvent): void { - // Handle updates to visible binary editors - this.handleUpdatesToVisibleBinaryEditors(e); - // Handle updates to text models this.handleUpdatesToTextModels(e); + + // Handle updates to visible binary editors + this.handleUpdatesToVisibleBinaryEditors(e); + } + + private handleUpdatesToTextModels(e: FileChangesEvent): void { + + // Collect distinct (saved) models to update. + // + // Note: we also consider the added event because it could be that a file was added + // and updated right after. + distinct([...e.getUpdated(), ...e.getAdded()] + .map(u => this.textFileService.models.get(u.resource)) + .filter(model => model && !model.isDirty()), m => m.getResource().toString()) + .forEach(model => this.textFileService.models.reload(model)); } private handleUpdatesToVisibleBinaryEditors(e: FileChangesEvent): void { @@ -313,29 +323,6 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut }); } - private handleUpdatesToTextModels(e: FileChangesEvent): void { - - // Collect distinct (saved) models to update. - // - // Note: we also consider the added event because it could be that a file was added - // and updated right after. - distinct([...e.getUpdated(), ...e.getAdded()] - .map(u => this.textFileService.models.get(u.resource)) - .filter(model => model && !model.isDirty()), m => m.getResource().toString()) - .forEach(model => this.queueModelLoad(model)); - } - - private queueModelLoad(model: ITextFileEditorModel): void { - - // Load model to update (use a queue to prevent accumulation of loads - // when the load actually takes long. At most we only want the queue - // to have a size of 2 (1 running load and 1 queued load). - const queue = this.modelLoadQueue.queueFor(model.getResource()); - if (queue.size <= 1) { - queue.queue(() => model.load().then(null, errors.onUnexpectedError)); - } - } - private handleOutOfWorkspaceWatchers(): void { const visibleOutOfWorkspacePaths = new ResourceMap(); this.editorService.visibleEditors.map(editorInput => { diff --git a/src/vs/workbench/parts/files/browser/editors/textFileEditor.ts b/src/vs/workbench/parts/files/browser/editors/textFileEditor.ts index f48ed0f4cf6..ee794e4861d 100644 --- a/src/vs/workbench/parts/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/parts/files/browser/editors/textFileEditor.ts @@ -104,7 +104,7 @@ export class TextFileEditor extends BaseTextEditor { // Set input and resolve return super.setInput(input, options, token).then(() => { - return input.resolve(true).then(resolvedModel => { + return input.resolve().then(resolvedModel => { // Check for cancellation if (token.isCancellationRequested) { diff --git a/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts b/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts index df16421d173..29b277207b2 100644 --- a/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts @@ -243,7 +243,7 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { return this.forceOpenAsBinary ? BINARY_FILE_EDITOR_ID : TEXT_FILE_EDITOR_ID; } - resolve(refresh?: boolean): TPromise { + resolve(): TPromise { // Resolve as binary if (this.forceOpenAsBinary) { @@ -251,13 +251,17 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { } // Resolve as text - return this.doResolveAsText(refresh); + return this.doResolveAsText(); } - private doResolveAsText(reload?: boolean): TPromise { + private doResolveAsText(): TPromise { // Resolve as text - return this.textFileService.models.loadOrCreate(this.resource, { encoding: this.preferredEncoding, reload, allowBinary: this.forceOpenAsText }).then(model => { + return this.textFileService.models.loadOrCreate(this.resource, { + encoding: this.preferredEncoding, + reload: { async: true }, // trigger a reload of the model if it exists already but do not wait to show the model + allowBinary: this.forceOpenAsText + }).then(model => { // This is a bit ugly, because we first resolve the model and then resolve a model reference. the reason being that binary // or very large files do not resolve to a text file model but should be opened as binary files without text. First calling into diff --git a/src/vs/workbench/parts/files/test/browser/fileEditorInput.test.ts b/src/vs/workbench/parts/files/test/browser/fileEditorInput.test.ts index e56adb04c42..2355fbf8f1b 100644 --- a/src/vs/workbench/parts/files/test/browser/fileEditorInput.test.ts +++ b/src/vs/workbench/parts/files/test/browser/fileEditorInput.test.ts @@ -63,19 +63,19 @@ suite('Files - FileEditorInput', () => { const inputToResolve: FileEditorInput = instantiationService.createInstance(FileEditorInput, toResource(this, '/foo/bar/file.js'), void 0); const sameOtherInput: FileEditorInput = instantiationService.createInstance(FileEditorInput, toResource(this, '/foo/bar/file.js'), void 0); - return inputToResolve.resolve(true).then(resolved => { + return inputToResolve.resolve().then(resolved => { assert.ok(inputToResolve.isResolved()); const resolvedModelA = resolved; - return inputToResolve.resolve(true).then(resolved => { + return inputToResolve.resolve().then(resolved => { assert(resolvedModelA === resolved); // OK: Resolved Model cached globally per input - return sameOtherInput.resolve(true).then(otherResolved => { + return sameOtherInput.resolve().then(otherResolved => { assert(otherResolved === resolvedModelA); // OK: Resolved Model cached globally per input inputToResolve.dispose(); - return inputToResolve.resolve(true).then(resolved => { + return inputToResolve.resolve().then(resolved => { assert(resolvedModelA === resolved); // Model is still the same because we had 2 clients inputToResolve.dispose(); @@ -83,17 +83,12 @@ suite('Files - FileEditorInput', () => { resolvedModelA.dispose(); - return inputToResolve.resolve(true).then(resolved => { + return inputToResolve.resolve().then(resolved => { assert(resolvedModelA !== resolved); // Different instance, because input got disposed let stat = (resolved as TextFileEditorModel).getStat(); - return inputToResolve.resolve(true).then(resolved => { + return inputToResolve.resolve().then(resolved => { assert(stat !== (resolved as TextFileEditorModel).getStat()); // Different stat, because resolve always goes to the server for refresh - - stat = (resolved as TextFileEditorModel).getStat(); - return inputToResolve.resolve(false).then(resolved => { - assert(stat === (resolved as TextFileEditorModel).getStat()); // Same stat, because not refreshed - }); }); }); }); @@ -122,7 +117,7 @@ suite('Files - FileEditorInput', () => { input.setEncoding('utf16', EncodingMode.Encode); assert.equal(input.getEncoding(), 'utf16'); - return input.resolve(true).then((resolved: TextFileEditorModel) => { + return input.resolve().then((resolved: TextFileEditorModel) => { assert.equal(input.getEncoding(), resolved.getEncoding()); resolved.dispose(); @@ -132,7 +127,7 @@ suite('Files - FileEditorInput', () => { test('save', function () { const input = instantiationService.createInstance(FileEditorInput, toResource(this, '/foo/bar/updatefile.js'), void 0); - return input.resolve(true).then((resolved: TextFileEditorModel) => { + return input.resolve().then((resolved: TextFileEditorModel) => { resolved.textEditorModel.setValue('changed'); assert.ok(input.isDirty()); @@ -147,7 +142,7 @@ suite('Files - FileEditorInput', () => { test('revert', function () { const input = instantiationService.createInstance(FileEditorInput, toResource(this, '/foo/bar/updatefile.js'), void 0); - return input.resolve(true).then((resolved: TextFileEditorModel) => { + return input.resolve().then((resolved: TextFileEditorModel) => { resolved.textEditorModel.setValue('changed'); assert.ok(input.isDirty()); @@ -164,7 +159,7 @@ suite('Files - FileEditorInput', () => { accessor.textFileService.setResolveTextContentErrorOnce(new FileOperationError('error', FileOperationResult.FILE_IS_BINARY)); - return input.resolve(true).then(resolved => { + return input.resolve().then(resolved => { assert.ok(resolved); resolved.dispose(); @@ -176,7 +171,7 @@ suite('Files - FileEditorInput', () => { accessor.textFileService.setResolveTextContentErrorOnce(new FileOperationError('error', FileOperationResult.FILE_TOO_LARGE)); - return input.resolve(true).then(resolved => { + return input.resolve().then(resolved => { assert.ok(resolved); resolved.dispose(); diff --git a/src/vs/workbench/parts/webview/electron-browser/webviewEditorInput.ts b/src/vs/workbench/parts/webview/electron-browser/webviewEditorInput.ts index 8a2a3f989bc..6d48bf36101 100644 --- a/src/vs/workbench/parts/webview/electron-browser/webviewEditorInput.ts +++ b/src/vs/workbench/parts/webview/electron-browser/webviewEditorInput.ts @@ -154,7 +154,7 @@ export class WebviewEditorInput extends EditorInput { } } - public resolve(refresh?: boolean): TPromise { + public resolve(): TPromise { if (this.reviver && !this._revived) { this._revived = true; return this.reviver.reviveWebview(this).then(() => new EditorModel()); diff --git a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughPart.ts b/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughPart.ts index dd6a525c2fd..0c8665ff148 100644 --- a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughPart.ts +++ b/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughPart.ts @@ -259,7 +259,7 @@ export class WalkThroughPart extends BaseEditor { return super.setInput(input, options, token) .then(() => { - return input.resolve(true); + return input.resolve(); }) .then(model => { if (token.isCancellationRequested) { diff --git a/src/vs/workbench/parts/welcome/walkThrough/node/walkThroughInput.ts b/src/vs/workbench/parts/welcome/walkThrough/node/walkThroughInput.ts index 81b269fa5e6..2a9d7dae0c9 100644 --- a/src/vs/workbench/parts/welcome/walkThrough/node/walkThroughInput.ts +++ b/src/vs/workbench/parts/welcome/walkThrough/node/walkThroughInput.ts @@ -102,7 +102,7 @@ export class WalkThroughInput extends EditorInput { return this.options.onReady; } - resolve(refresh?: boolean): TPromise { + resolve(): TPromise { if (!this.promise) { this.promise = this.textModelResolverService.createModelReference(this.options.resource) .then(ref => { diff --git a/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts b/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts index b81ca559188..045707197af 100644 --- a/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts +++ b/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts @@ -70,7 +70,7 @@ export class KeybindingsEditorInput extends EditorInput { return nls.localize('keybindingsInputName', "Keyboard Shortcuts"); } - resolve(refresh?: boolean): TPromise { + resolve(): TPromise { return TPromise.as(this.keybindingsModel); } @@ -97,7 +97,7 @@ export class SettingsEditor2Input extends EditorInput { return nls.localize('settingsEditor2InputName', "Settings (Preview)"); } - resolve(refresh?: boolean): TPromise { + resolve(): TPromise { return >this.preferencesService.createPreferencesEditorModel(URI.parse('vscode://defaultsettings/0/settings.json')); } diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index c37e191b716..2836c9adcce 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -13,6 +13,8 @@ import { ITextFileEditorModel, ITextFileEditorModelManager, TextFileModelChangeE import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ResourceMap } from 'vs/base/common/map'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { ResourceQueue } from 'vs/base/common/async'; export class TextFileEditorModelManager extends Disposable implements ITextFileEditorModelManager { @@ -51,6 +53,8 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE private mapResourceToModel: ResourceMap; private mapResourceToPendingModelLoaders: ResourceMap>; + private modelReloadQueue: ResourceQueue = new ResourceQueue(); + constructor( @ILifecycleService private lifecycleService: ILifecycleService, @IInstantiationService private instantiationService: IInstantiationService @@ -142,7 +146,17 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE let model = this.get(resource); if (model) { if (options && options.reload) { - modelPromise = model.load(modelLoadOptions); + + // async reload: trigger a reload but return immediately + if (options.reload.async) { + modelPromise = TPromise.as(model); + this.reload(model); + } + + // sync reload: do not return until model reloaded + else { + modelPromise = model.load(modelLoadOptions); + } } else { modelPromise = TPromise.as(model); } @@ -213,6 +227,17 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE }); } + reload(model: ITextFileEditorModel): void { + + // Load model to reload (use a queue to prevent accumulation of loads + // when the load actually takes long. At most we only want the queue + // to have a size of 2 (1 running load and 1 queued load). + const queue = this.modelReloadQueue.queueFor(model.getResource()); + if (queue.size <= 1) { + queue.queue(() => model.load().then(null, onUnexpectedError)); + } + } + getAll(resource?: URI, filter?: (model: ITextFileEditorModel) => boolean): ITextFileEditorModel[] { if (resource) { const res = this.mapResourceToModel.get(resource); diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 5329c91f2e2..a2f5beedbb1 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -147,9 +147,16 @@ export interface IModelLoadOrCreateOptions { encoding?: string; /** - * Wether to reload the model if it already exists. + * If the model was already loaded before, allows to trigger + * a reload of it to fetch the latest contents: + * - async: loadOrCreate() will return immediately and trigger + * a reload that will run in the background. + * - sync: loadOrCreate() will only return resolved when the + * model has finished reloading. */ - reload?: boolean; + reload?: { + async: boolean + }; /** * Allow to load a model even if we think it is a binary file. @@ -179,6 +186,7 @@ export interface ITextFileEditorModelManager { getAll(resource?: URI): ITextFileEditorModel[]; loadOrCreate(resource: URI, options?: IModelLoadOrCreateOptions): TPromise; + reload(model: ITextFileEditorModel): void; disposeModel(model: ITextFileEditorModel): void; } diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts index b07a9c25b47..fee25713dd3 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts @@ -105,7 +105,7 @@ suite('Files - TextFileEditorModelManager', () => { const resource = URI.file('/test.html'); const encoding = 'utf8'; - return manager.loadOrCreate(resource, { encoding, reload: true }).then(model => { + return manager.loadOrCreate(resource, { encoding }).then(model => { assert.ok(model); assert.equal(model.getEncoding(), encoding); assert.equal(manager.get(resource), model); diff --git a/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts index 7cbde0bb8c9..fcb377a0c43 100644 --- a/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts @@ -71,7 +71,7 @@ class MyInput extends EditorInput { return ''; } - resolve(refresh?: boolean): any { + resolve(): any { return null; } } @@ -81,7 +81,7 @@ class MyOtherInput extends EditorInput { return ''; } - resolve(refresh?: boolean): any { + resolve(): any { return null; } } diff --git a/src/vs/workbench/test/common/editor/editor.test.ts b/src/vs/workbench/test/common/editor/editor.test.ts index 4e169cf7e1d..3fefb60de2d 100644 --- a/src/vs/workbench/test/common/editor/editor.test.ts +++ b/src/vs/workbench/test/common/editor/editor.test.ts @@ -35,7 +35,7 @@ class FileEditorInput extends EditorInput { return this.resource; } - resolve(refresh?: boolean): TPromise { + resolve(): TPromise { return TPromise.as(null); } } diff --git a/src/vs/workbench/test/common/editor/editorDiffModel.test.ts b/src/vs/workbench/test/common/editor/editorDiffModel.test.ts index b4ce15928a4..b8a7e0f3411 100644 --- a/src/vs/workbench/test/common/editor/editorDiffModel.test.ts +++ b/src/vs/workbench/test/common/editor/editorDiffModel.test.ts @@ -55,7 +55,7 @@ suite('Workbench editor model', () => { let otherInput = instantiationService.createInstance(ResourceEditorInput, 'name2', 'description', URI.from({ scheme: 'test', authority: null, path: 'thePath' })); let diffInput = new DiffEditorInput('name', 'description', input, otherInput); - return diffInput.resolve(true).then((model: any) => { + return diffInput.resolve().then((model: any) => { assert(model); assert(model instanceof TextDiffEditorModel); @@ -63,7 +63,7 @@ suite('Workbench editor model', () => { assert(diffEditorModel.original); assert(diffEditorModel.modified); - return diffInput.resolve(true).then((model: any) => { + return diffInput.resolve().then((model: any) => { assert(model.isResolved()); assert(diffEditorModel !== model.textDiffEditorModel); diff --git a/src/vs/workbench/test/common/editor/editorInput.test.ts b/src/vs/workbench/test/common/editor/editorInput.test.ts index b162d802d25..94eea4a8805 100644 --- a/src/vs/workbench/test/common/editor/editorInput.test.ts +++ b/src/vs/workbench/test/common/editor/editorInput.test.ts @@ -11,7 +11,7 @@ import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; class MyEditorInput extends EditorInput { getTypeId(): string { return ''; } - resolve(refresh?: boolean): any { return null; } + resolve(): any { return null; } } suite('Workbench editor input', () => { From 1fe004198ece114304dda1dfee357d943c50ca51 Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 4 Jul 2018 15:03:14 +0200 Subject: [PATCH 161/283] menu: remove terminal menu --- src/vs/platform/actions/common/actions.ts | 1 - .../parts/menubar/menubar.contribution.ts | 100 ------------------ .../browser/parts/menubar/menubarPart.ts | 5 +- 3 files changed, 1 insertion(+), 105 deletions(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index c4b1e0f958c..e05cc8aa5fe 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -97,7 +97,6 @@ export class MenuId { static readonly MenubarWindowMenu = new MenuId(); static readonly MenubarPreferencesMenu = new MenuId(); static readonly MenubarHelpMenu = new MenuId(); - static readonly MenubarTerminalMenu = new MenuId(); readonly id: string = String(MenuId.ID++); } diff --git a/src/vs/workbench/browser/parts/menubar/menubar.contribution.ts b/src/vs/workbench/browser/parts/menubar/menubar.contribution.ts index 8d877ddba77..13625f8f7aa 100644 --- a/src/vs/workbench/browser/parts/menubar/menubar.contribution.ts +++ b/src/vs/workbench/browser/parts/menubar/menubar.contribution.ts @@ -16,7 +16,6 @@ layoutMenuRegistration(); goMenuRegistration(); debugMenuRegistration(); tasksMenuRegistration(); -terminalMenuRegistration(); if (isMacintosh) { windowMenuRegistration(); @@ -1494,102 +1493,3 @@ function helpMenuRegistration() { order: 1 }); } - -function terminalMenuRegistration() { - - // Manage - - MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { - group: '1_manage', - command: { - id: 'workbench.action.terminal.new', - title: nls.localize({ key: 'miNewTerminal', comment: ['&& denotes a mnemonic'] }, "&&New Terminal") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { - group: '1_manage', - command: { - id: 'workbench.action.terminal.split', - title: nls.localize({ key: 'miSplitTerminal', comment: ['&& denotes a mnemonic'] }, "&&Split Terminal") - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { - group: '1_manage', - command: { - id: 'workbench.action.terminal.kill', - title: nls.localize({ key: 'miKillTerminal', comment: ['&& denotes a mnemonic'] }, "&&Kill Terminal") - }, - order: 3 - }); - - // Run - - MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { - group: '2_run', - command: { - id: 'workbench.action.terminal.clear', - title: nls.localize({ key: 'miClear', comment: ['&& denotes a mnemonic'] }, "&&Clear") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { - group: '2_run', - command: { - id: 'workbench.action.terminal.runActiveFile', - title: nls.localize({ key: 'miRunActiveFile', comment: ['&& denotes a mnemonic'] }, "Run &&Active File") - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { - group: '2_run', - command: { - id: 'workbench.action.terminal.runSelectedFile', - title: nls.localize({ key: 'miRunSelectedText', comment: ['&& denotes a mnemonic'] }, "Run &&Selected Text") - }, - order: 3 - }); - - // Selection - - MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { - group: '3_selection', - command: { - id: 'workbench.action.terminal.scrollToPreviousCommand', - title: nls.localize({ key: 'miScrollToPreviousCommand', comment: ['&& denotes a mnemonic'] }, "Scroll To Previous Command") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { - group: '3_selection', - command: { - id: 'workbench.action.terminal.scrollToNextCommand', - title: nls.localize({ key: 'miScrollToNextCommand', comment: ['&& denotes a mnemonic'] }, "Scroll To Next Command") - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { - group: '3_selection', - command: { - id: 'workbench.action.terminal.selectToPreviousCommand', - title: nls.localize({ key: 'miSelectToPreviousCommand', comment: ['&& denotes a mnemonic'] }, "Select To Previous Command") - }, - order: 3 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { - group: '3_selection', - command: { - id: 'workbench.action.terminal.selectToNextCommand', - title: nls.localize({ key: 'miSelectToNextCommand', comment: ['&& denotes a mnemonic'] }, "Select To Next Command") - }, - order: 4 - }); -} \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/menubar/menubarPart.ts b/src/vs/workbench/browser/parts/menubar/menubarPart.ts index 31c84306e65..160f114b213 100644 --- a/src/vs/workbench/browser/parts/menubar/menubarPart.ts +++ b/src/vs/workbench/browser/parts/menubar/menubarPart.ts @@ -60,7 +60,6 @@ export class MenubarPart extends Part { 'Selection': IMenu; 'View': IMenu; 'Go': IMenu; - 'Terminal': IMenu; 'Debug': IMenu; 'Tasks': IMenu; 'Window'?: IMenu; @@ -74,7 +73,6 @@ export class MenubarPart extends Part { 'Selection': nls.localize({ key: 'mSelection', comment: ['&& denotes a mnemonic'] }, "&&Selection"), 'View': nls.localize({ key: 'mView', comment: ['&& denotes a mnemonic'] }, "&&View"), 'Go': nls.localize({ key: 'mGoto', comment: ['&& denotes a mnemonic'] }, "&&Go"), - 'Terminal': nls.localize({ key: 'mTerminal', comment: ['&& denotes a mnemonic'] }, "&&Terminal"), 'Debug': nls.localize({ key: 'mDebug', comment: ['&& denotes a mnemonic'] }, "&&Debug"), 'Tasks': nls.localize({ key: 'mTasks', comment: ['&& denotes a mnemonic'] }, "&&Tasks"), 'Help': nls.localize({ key: 'mHelp', comment: ['&& denotes a mnemonic'] }, "&&Help") @@ -125,7 +123,6 @@ export class MenubarPart extends Part { 'Selection': this._register(this.menuService.createMenu(MenuId.MenubarSelectionMenu, this.contextKeyService)), 'View': this._register(this.menuService.createMenu(MenuId.MenubarViewMenu, this.contextKeyService)), 'Go': this._register(this.menuService.createMenu(MenuId.MenubarGoMenu, this.contextKeyService)), - 'Terminal': this._register(this.menuService.createMenu(MenuId.MenubarTerminalMenu, this.contextKeyService)), 'Debug': this._register(this.menuService.createMenu(MenuId.MenubarDebugMenu, this.contextKeyService)), 'Tasks': this._register(this.menuService.createMenu(MenuId.MenubarTasksMenu, this.contextKeyService)), 'Help': this._register(this.menuService.createMenu(MenuId.MenubarHelpMenu, this.contextKeyService)) @@ -862,4 +859,4 @@ class ModifierKeyEmitter extends Emitter { super.dispose(); this._subscriptions = dispose(this._subscriptions); } -} \ No newline at end of file +} From ff84c91a8c76ebac27cef86d51d527f769c9023a Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 4 Jul 2018 14:58:41 +0200 Subject: [PATCH 162/283] debt - use TimeoutTimer instead of WinJS.Promise.timeout --- src/vs/workbench/browser/parts/quickinput/quickInput.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.ts b/src/vs/workbench/browser/parts/quickinput/quickInput.ts index c6eea59e167..c790e200b18 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.ts @@ -42,6 +42,7 @@ import URI from 'vs/base/common/uri'; import { IdGenerator } from 'vs/base/common/idGenerator'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { equals } from 'vs/base/common/arrays'; +import { TimeoutTimer } from 'vs/base/common/async'; const $ = dom.$; @@ -104,7 +105,7 @@ class QuickInput implements IQuickInput { this.onDidHideEmitter, ]; - private busyDelay: TPromise; + private busyDelay: TimeoutTimer; constructor(protected ui: QuickInputUI) { } @@ -215,12 +216,12 @@ class QuickInput implements IQuickInput { this.ui.title.textContent = title; } if (this.busy && !this.busyDelay) { - this.busyDelay = TPromise.timeout(800); - this.busyDelay.then(() => { + this.busyDelay = new TimeoutTimer(); + this.busyDelay.setIfNotSet(() => { if (this.visible) { this.ui.progressBar.infinite(); } - }, () => { /* ignore */ }); + }, 800); } if (!this.busy && this.busyDelay) { this.ui.progressBar.stop(); From 0a7fcd09da714b790f0ee58bdea082564a3ea13f Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 4 Jul 2018 14:58:57 +0200 Subject: [PATCH 163/283] debt - avoid WinJS.Promise.cancel --- src/vs/platform/dialogs/node/dialogService.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/dialogs/node/dialogService.ts b/src/vs/platform/dialogs/node/dialogService.ts index b1896b82a8f..4304af55184 100644 --- a/src/vs/platform/dialogs/node/dialogService.ts +++ b/src/vs/platform/dialogs/node/dialogService.ts @@ -8,6 +8,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IDialogService, IConfirmation, IConfirmationResult } from 'vs/platform/dialogs/common/dialogs'; import Severity from 'vs/base/common/severity'; import { localize } from 'vs/nls'; +import { canceled } from 'vs/base/common/errors'; export class CommandLineDialogService implements IDialogService { @@ -31,7 +32,7 @@ export class CommandLineDialogService implements IDialogService { }); rl.once('SIGINT', () => { rl.close(); - promise.cancel(); + e(canceled()); }); }); return promise; @@ -64,4 +65,4 @@ export class CommandLineDialogService implements IDialogService { } as IConfirmationResult; }); } -} \ No newline at end of file +} From 09ba193fdc10584a37c16245ff08578005cdcbf5 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 4 Jul 2018 15:06:21 +0200 Subject: [PATCH 164/283] debt - avoid promise cancel --- src/vs/editor/contrib/folding/folding.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/contrib/folding/folding.ts b/src/vs/editor/contrib/folding/folding.ts index 68151b424c8..389901f50ff 100644 --- a/src/vs/editor/contrib/folding/folding.ts +++ b/src/vs/editor/contrib/folding/folding.ts @@ -9,7 +9,7 @@ import 'vs/css!./folding'; import * as nls from 'vs/nls'; import * as types from 'vs/base/common/types'; import { escapeRegExpCharacters } from 'vs/base/common/strings'; -import { RunOnceScheduler, Delayer, asWinJsPromise } from 'vs/base/common/async'; +import { RunOnceScheduler, Delayer, CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { KeyCode, KeyMod, KeyChord } from 'vs/base/common/keyCodes'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { TPromise } from 'vs/base/common/winjs.base'; @@ -67,7 +67,7 @@ export class FoldingController implements IEditorContribution { private hiddenRangeModel: HiddenRangeModel; private rangeProvider: RangeProvider; - private foldingRegionPromise: TPromise; + private foldingRegionPromise: CancelablePromise; private foldingStateMemento: FoldingStateMemento; @@ -261,8 +261,8 @@ export class FoldingController implements IEditorContribution { if (!this.foldingModel) { // null if editor has been disposed, or folding turned off return null; } - let foldingRegionPromise = this.foldingRegionPromise = asWinJsPromise(token => this.getRangeProvider(this.foldingModel.textModel).compute(token)); - return foldingRegionPromise.then(foldingRanges => { + let foldingRegionPromise = this.foldingRegionPromise = createCancelablePromise(token => this.getRangeProvider(this.foldingModel.textModel).compute(token)); + return TPromise.wrap(foldingRegionPromise.then(foldingRanges => { if (foldingRanges && foldingRegionPromise === this.foldingRegionPromise) { // new request or cancelled in the meantime? // some cursors might have moved into hidden regions, make sure they are in expanded regions let selections = this.editor.getSelections(); @@ -270,7 +270,7 @@ export class FoldingController implements IEditorContribution { this.foldingModel.update(foldingRanges, selectionLineNumbers); } return this.foldingModel; - }); + })); }); } } From 9a8647648cc59dbe7ba23330ebd8542eb9c50085 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 4 Jul 2018 15:14:39 +0200 Subject: [PATCH 165/283] fix #53533 --- .../workbench/parts/outline/electron-browser/outlinePanel.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts b/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts index bf5660f37fd..3650e6cf8cb 100644 --- a/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts +++ b/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts @@ -136,11 +136,15 @@ class RequestOracle { let modeListener = codeEditor.onDidChangeModelLanguage(_ => { this._callback(codeEditor, undefined); }); + let disposeListener = codeEditor.onDidDispose(() => { + this._callback(undefined, undefined); + }); this._sessionDisposable = { dispose() { contentListener.dispose(); clearTimeout(handle); modeListener.dispose(); + disposeListener.dispose(); } }; } From 5b7e1163432090bebca2c36d395d000a1752538b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 4 Jul 2018 15:18:51 +0200 Subject: [PATCH 166/283] debt - less TPromise-usage --- .../services/progress/browser/progressService2.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/services/progress/browser/progressService2.ts b/src/vs/workbench/services/progress/browser/progressService2.ts index df20cc78925..9a1eacf76a6 100644 --- a/src/vs/workbench/services/progress/browser/progressService2.ts +++ b/src/vs/workbench/services/progress/browser/progressService2.ts @@ -15,7 +15,7 @@ import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; import { Registry } from 'vs/platform/registry/common/platform'; import { StatusbarAlignment, IStatusbarRegistry, StatusbarItemDescriptor, Extensions, IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar'; import { TPromise } from 'vs/base/common/winjs.base'; -import { always } from 'vs/base/common/async'; +import { always, timeout } from 'vs/base/common/async'; import { ProgressBadge, IActivityService } from 'vs/workbench/services/activity/common/activity'; import { INotificationService, Severity, INotificationHandle, INotificationActions } from 'vs/platform/notification/common/notification'; import { Action } from 'vs/base/common/actions'; @@ -130,8 +130,8 @@ export class ProgressService2 implements IProgressService2 { this._updateWindowProgress(); // show progress for at least 150ms - always(TPromise.join([ - TPromise.timeout(150), + always(Promise.all([ + timeout(150), promise ]), () => { const idx = this._stack.indexOf(task); @@ -142,7 +142,7 @@ export class ProgressService2 implements IProgressService2 { }, 150); // cancel delay if promise finishes below 150ms - always(TPromise.wrap(promise), () => clearTimeout(delayHandle)); + always(promise, () => clearTimeout(delayHandle)); return promise; } @@ -270,7 +270,7 @@ export class ProgressService2 implements IProgressService2 { }); // Show progress for at least 800ms and then hide once done or canceled - always(TPromise.join([TPromise.timeout(800), p]), () => { + always(Promise.all([timeout(800), p]), () => { if (handle) { handle.close(); } From 86cb609e2975b4d23caccd405a257f56187fce39 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 4 Jul 2018 15:21:33 +0200 Subject: [PATCH 167/283] debt - use Thenable in IProgressService#showWhile --- src/vs/platform/progress/common/progress.ts | 5 ++--- .../workbench/parts/preferences/browser/preferencesEditor.ts | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index a1fca9abef9..405170fa1e1 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { TPromise } from 'vs/base/common/winjs.base'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; @@ -24,7 +23,7 @@ export interface IProgressService { * Indicate progress for the duration of the provided promise. Progress will stop in * any case of promise completion, error or cancellation. */ - showWhile(promise: TPromise, delay?: number): TPromise; + showWhile(promise: Thenable, delay?: number): Thenable; } export interface IProgressRunner { @@ -126,4 +125,4 @@ export class LongRunningOperation { dispose(): void { this.currentOperationDisposables = dispose(this.currentOperationDisposables); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts b/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts index 9b12f126db0..0211aa5d897 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts @@ -242,7 +242,7 @@ export class PreferencesEditor extends BaseEditor { if (query) { return TPromise.join([ this.localSearchDelayer.trigger(() => this.preferencesRenderers.localFilterPreferences(query)), - this.remoteSearchThrottle.trigger(() => this.progressService.showWhile(this.preferencesRenderers.remoteSearchPreferences(query), 500)) + this.remoteSearchThrottle.trigger(() => TPromise.wrap(this.progressService.showWhile(this.preferencesRenderers.remoteSearchPreferences(query), 500))) ]) as TPromise; } else { // When clearing the input, update immediately to clear it From 36953b44d71773ca36fb36d2cef4104d220f14e7 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 4 Jul 2018 15:30:01 +0200 Subject: [PATCH 168/283] use events instead of PPromise for search IPC --- .../services/search/node/fileSearch.ts | 5 +- .../services/search/node/rawSearchService.ts | 229 ++++++++++-------- .../services/search/node/ripgrepTextSearch.ts | 8 +- .../workbench/services/search/node/search.ts | 35 ++- .../services/search/node/searchIpc.ts | 36 +-- .../services/search/node/searchService.ts | 38 ++- .../services/search/node/textSearch.ts | 5 +- .../search/test/node/searchService.test.ts | 179 ++++++++------ 8 files changed, 325 insertions(+), 210 deletions(-) diff --git a/src/vs/workbench/services/search/node/fileSearch.ts b/src/vs/workbench/services/search/node/fileSearch.ts index 1182c79e506..dcfb3c3db8e 100644 --- a/src/vs/workbench/services/search/node/fileSearch.ts +++ b/src/vs/workbench/services/search/node/fileSearch.ts @@ -25,7 +25,7 @@ import { IProgress, IUncachedSearchStats } from 'vs/platform/search/common/searc import * as extfs from 'vs/base/node/extfs'; import * as flow from 'vs/base/node/flow'; -import { IRawFileMatch, ISerializedSearchComplete, IRawSearch, ISearchEngine, IFolderSearch } from './search'; +import { IRawFileMatch, IRawSearch, ISearchEngine, IFolderSearch, ISerializedSearchSuccess } from './search'; import { spawnRipgrepCmd } from './ripgrepFileSearch'; import { rgErrorMsgForDisplay } from './ripgrepTextSearch'; @@ -721,9 +721,10 @@ export class Engine implements ISearchEngine { this.walker = new FileWalker(config); } - public search(onResult: (result: IRawFileMatch) => void, onProgress: (progress: IProgress) => void, done: (error: Error, complete: ISerializedSearchComplete) => void): void { + public search(onResult: (result: IRawFileMatch) => void, onProgress: (progress: IProgress) => void, done: (error: Error, complete: ISerializedSearchSuccess) => void): void { this.walker.walk(this.folderQueries, this.extraFiles, onResult, onProgress, (err: Error, isLimitHit: boolean) => { done(err, { + type: 'success', limitHit: isLimitHit, stats: this.walker.getStats() }); diff --git a/src/vs/workbench/services/search/node/rawSearchService.ts b/src/vs/workbench/services/search/node/rawSearchService.ts index fd40541b99c..d267e0cd3a0 100644 --- a/src/vs/workbench/services/search/node/rawSearchService.ts +++ b/src/vs/workbench/services/search/node/rawSearchService.ts @@ -11,7 +11,7 @@ import { join, sep } from 'path'; import * as arrays from 'vs/base/common/arrays'; import * as objects from 'vs/base/common/objects'; import * as strings from 'vs/base/common/strings'; -import { PPromise, TPromise } from 'vs/base/common/winjs.base'; +import { TPromise } from 'vs/base/common/winjs.base'; import { compareItemsByScore, IItemAccessor, prepareQuery, ScorerCache } from 'vs/base/parts/quickopen/common/quickOpenScorer'; import { MAX_FILE_SIZE } from 'vs/platform/files/node/files'; import { ICachedSearchStats, IProgress } from 'vs/platform/search/common/search'; @@ -19,10 +19,14 @@ import { Engine as FileSearchEngine, FileWalker } from 'vs/workbench/services/se import { RipgrepEngine } from 'vs/workbench/services/search/node/ripgrepTextSearch'; import { Engine as TextSearchEngine } from 'vs/workbench/services/search/node/textSearch'; import { TextSearchWorkerProvider } from 'vs/workbench/services/search/node/textSearchWorkerProvider'; -import { IFileSearchProgressItem, IRawFileMatch, IRawSearch, IRawSearchService, ISearchEngine, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, ITelemetryEvent } from './search'; +import { IFileSearchProgressItem, IRawFileMatch, IRawSearch, IRawSearchService, ISearchEngine, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, ITelemetryEvent, ISerializedSearchSuccess } from './search'; +import { Event, Emitter } from 'vs/base/common/event'; gracefulFs.gracefulify(fs); +type IProgressCallback = (p: ISerializedSearchProgressItem) => void; +type IFileProgressCallback = (p: IFileSearchProgressItem) => void; + export class SearchService implements IRawSearchService { private static readonly BATCH_SIZE = 512; @@ -31,29 +35,52 @@ export class SearchService implements IRawSearchService { private textSearchWorkerProvider: TextSearchWorkerProvider; - private telemetryPipe: (event: ITelemetryEvent) => void; + private _onTelemetry = new Emitter(); + readonly onTelemetry: Event = this._onTelemetry.event; - public fileSearch(config: IRawSearch): PPromise { - return this.doFileSearch(FileSearchEngine, config, SearchService.BATCH_SIZE); + public fileSearch(config: IRawSearch, batchSize = SearchService.BATCH_SIZE): Event { + let promise: TPromise; + + const emitter = new Emitter({ + onFirstListenerAdd: () => { + promise = this.doFileSearch(FileSearchEngine, config, p => emitter.fire(p), batchSize) + .then(c => emitter.fire(c), err => emitter.fire({ type: 'error', error: err })); + }, + onLastListenerRemove: () => { + promise.cancel(); + } + }); + + return emitter.event; } - public textSearch(config: IRawSearch): PPromise { - return config.useRipgrep ? - this.ripgrepTextSearch(config) : - this.legacyTextSearch(config); + public textSearch(config: IRawSearch): Event { + let promise: TPromise; + + const emitter = new Emitter({ + onFirstListenerAdd: () => { + promise = (config.useRipgrep ? this.ripgrepTextSearch(config, p => emitter.fire(p)) : this.legacyTextSearch(config, p => emitter.fire(p))) + .then(c => emitter.fire(c), err => emitter.fire({ type: 'error', error: err })); + }, + onLastListenerRemove: () => { + promise.cancel(); + } + }); + + return emitter.event; } - public ripgrepTextSearch(config: IRawSearch): PPromise { + private ripgrepTextSearch(config: IRawSearch, progressCallback: IProgressCallback): TPromise { config.maxFilesize = MAX_FILE_SIZE; let engine = new RipgrepEngine(config); - return new PPromise((c, e, p) => { + return new TPromise((c, e) => { // Use BatchedCollector to get new results to the frontend every 2s at least, until 50 results have been returned - const collector = new BatchedCollector(SearchService.BATCH_SIZE, p); + const collector = new BatchedCollector(SearchService.BATCH_SIZE, progressCallback); engine.search((match) => { collector.addItem(match, match.numMatches); }, (message) => { - p(message); + progressCallback(message); }, (error, stats) => { collector.flush(); @@ -68,7 +95,7 @@ export class SearchService implements IRawSearchService { }); } - public legacyTextSearch(config: IRawSearch): PPromise { + private legacyTextSearch(config: IRawSearch, progressCallback: IProgressCallback): TPromise { if (!this.textSearchWorkerProvider) { this.textSearchWorkerProvider = new TextSearchWorkerProvider(); } @@ -86,75 +113,75 @@ export class SearchService implements IRawSearchService { }), this.textSearchWorkerProvider); - return this.doTextSearch(engine, SearchService.BATCH_SIZE); + return this.doTextSearch(engine, progressCallback, SearchService.BATCH_SIZE); } - public doFileSearch(EngineClass: { new(config: IRawSearch): ISearchEngine; }, config: IRawSearch, batchSize?: number): PPromise { + doFileSearch(EngineClass: { new(config: IRawSearch): ISearchEngine; }, config: IRawSearch, progressCallback: IProgressCallback, batchSize?: number): TPromise { + const fileProgressCallback: IFileProgressCallback = progress => { + if (Array.isArray(progress)) { + progressCallback(progress.map(m => this.rawMatchToSearchItem(m))); + } else if ((progress).relativePath) { + progressCallback(this.rawMatchToSearchItem(progress)); + } else { + progressCallback(progress); + } + }; if (config.sortByScore) { - let sortedSearch = this.trySortedSearchFromCache(config); + let sortedSearch = this.trySortedSearchFromCache(config, fileProgressCallback); if (!sortedSearch) { const walkerConfig = config.maxResults ? objects.assign({}, config, { maxResults: null }) : config; const engine = new EngineClass(walkerConfig); - sortedSearch = this.doSortedSearch(engine, config); + sortedSearch = this.doSortedSearch(engine, config, progressCallback, fileProgressCallback); } - return new PPromise((c, e, p) => { + return new TPromise((c, e) => { process.nextTick(() => { // allow caller to register progress callback first sortedSearch.then(([result, rawMatches]) => { const serializedMatches = rawMatches.map(rawMatch => this.rawMatchToSearchItem(rawMatch)); - this.sendProgress(serializedMatches, p, batchSize); + this.sendProgress(serializedMatches, progressCallback, batchSize); c(result); - }, e, p); + }, e); }); }, () => { sortedSearch.cancel(); }); } - let searchPromise: PPromise; - return new PPromise((c, e, p) => { - const engine = new EngineClass(config); - searchPromise = this.doSearch(engine, batchSize) - .then(c, e, progress => { - if (Array.isArray(progress)) { - p(progress.map(m => this.rawMatchToSearchItem(m))); - } else if ((progress).relativePath) { - p(this.rawMatchToSearchItem(progress)); - } else { - p(progress); - } - }); - }, () => { - searchPromise.cancel(); - }); + const engine = new EngineClass(config); + + return this.doSearch(engine, fileProgressCallback, batchSize); } private rawMatchToSearchItem(match: IRawFileMatch): ISerializedFileMatch { return { path: match.base ? join(match.base, match.relativePath) : match.relativePath }; } - private doSortedSearch(engine: ISearchEngine, config: IRawSearch): PPromise<[ISerializedSearchComplete, IRawFileMatch[]], IProgress> { - let searchPromise: PPromise; - let allResultsPromise = new PPromise<[ISerializedSearchComplete, IRawFileMatch[]], IFileSearchProgressItem>((c, e, p) => { + private doSortedSearch(engine: ISearchEngine, config: IRawSearch, progressCallback: IProgressCallback, fileProgressCallback: IFileProgressCallback): TPromise<[ISerializedSearchSuccess, IRawFileMatch[]]> { + let searchPromise: TPromise; + const emitter = new Emitter(); + + let allResultsPromise = new TPromise<[ISerializedSearchSuccess, IRawFileMatch[]]>((c, e) => { let results: IRawFileMatch[] = []; - searchPromise = this.doSearch(engine, -1) + + const innerProgressCallback: IFileProgressCallback = progress => { + if (Array.isArray(progress)) { + results = progress; + } else { + fileProgressCallback(progress); + emitter.fire(progress); + } + }; + + searchPromise = this.doSearch(engine, innerProgressCallback, -1) .then(result => { c([result, results]); - if (this.telemetryPipe) { - // __GDPR__TODO__ classify event - this.telemetryPipe({ - eventName: 'fileSearch', - data: result.stats - }); - } - }, e, progress => { - if (Array.isArray(progress)) { - results = progress; - } else { - p(progress); - } - }); + // __GDPR__TODO__ classify event + this._onTelemetry.fire({ + eventName: 'fileSearch', + data: result.stats + }); + }, e); }, () => { searchPromise.cancel(); }); @@ -162,7 +189,10 @@ export class SearchService implements IRawSearchService { let cache: Cache; if (config.cacheKey) { cache = this.getOrCreateCache(config.cacheKey); - cache.resultsToSearchCache[config.filePattern] = allResultsPromise; + cache.resultsToSearchCache[config.filePattern] = { + promise: allResultsPromise, + event: emitter.event + }; allResultsPromise.then(null, err => { delete cache.resultsToSearchCache[config.filePattern]; }); @@ -170,7 +200,7 @@ export class SearchService implements IRawSearchService { } let chained: TPromise; - return new PPromise<[ISerializedSearchComplete, IRawFileMatch[]], IProgress>((c, e, p) => { + return new TPromise<[ISerializedSearchSuccess, IRawFileMatch[]]>((c, e) => { chained = allResultsPromise.then(([result, results]) => { const scorerCache: ScorerCache = cache ? cache.scorerCache : Object.create(null); const unsortedResultTime = Date.now(); @@ -179,14 +209,15 @@ export class SearchService implements IRawSearchService { const sortedResultTime = Date.now(); c([{ + type: 'success', stats: objects.assign({}, result.stats, { unsortedResultTime, sortedResultTime }), limitHit: result.limitHit || typeof config.maxResults === 'number' && results.length > config.maxResults - }, sortedResults]); + } as ISerializedSearchSuccess, sortedResults]); }); - }, e, p); + }, e); }, () => { chained.cancel(); }); @@ -200,17 +231,17 @@ export class SearchService implements IRawSearchService { return this.caches[cacheKey] = new Cache(); } - private trySortedSearchFromCache(config: IRawSearch): PPromise<[ISerializedSearchComplete, IRawFileMatch[]], IProgress> { + private trySortedSearchFromCache(config: IRawSearch, progressCallback: IFileProgressCallback): TPromise<[ISerializedSearchSuccess, IRawFileMatch[]]> { const cache = config.cacheKey && this.caches[config.cacheKey]; if (!cache) { return undefined; } const cacheLookupStartTime = Date.now(); - const cached = this.getResultsFromCache(cache, config.filePattern); + const cached = this.getResultsFromCache(cache, config.filePattern, progressCallback); if (cached) { let chained: TPromise; - return new PPromise<[ISerializedSearchComplete, IRawFileMatch[]], IProgress>((c, e, p) => { + return new TPromise<[ISerializedSearchSuccess, IRawFileMatch[]]>((c, e) => { chained = cached.then(([result, results, cacheStats]) => { const cacheLookupResultTime = Date.now(); return this.sortResults(config, results, cache.scorerCache) @@ -234,13 +265,14 @@ export class SearchService implements IRawSearchService { } c([ { + type: 'success', limitHit: result.limitHit || typeof config.maxResults === 'number' && results.length > config.maxResults, stats: stats - }, + } as ISerializedSearchSuccess, sortedResults ]); }); - }, e, p); + }, e); }, () => { chained.cancel(); }); @@ -259,7 +291,7 @@ export class SearchService implements IRawSearchService { return arrays.topAsync(results, compare, config.maxResults, 10000); } - private sendProgress(results: ISerializedFileMatch[], progressCb: (batch: ISerializedFileMatch[]) => void, batchSize: number) { + private sendProgress(results: ISerializedFileMatch[], progressCb: IProgressCallback, batchSize: number) { if (batchSize && batchSize > 0) { for (let i = 0; i < results.length; i += batchSize) { progressCb(results.slice(i, i + batchSize)); @@ -269,10 +301,10 @@ export class SearchService implements IRawSearchService { } } - private getResultsFromCache(cache: Cache, searchValue: string): PPromise<[ISerializedSearchComplete, IRawFileMatch[], CacheStats], IProgress> { + private getResultsFromCache(cache: Cache, searchValue: string, progressCallback: IFileProgressCallback): TPromise<[ISerializedSearchSuccess, IRawFileMatch[], CacheStats]> { // Find cache entries by prefix of search value const hasPathSep = searchValue.indexOf(sep) >= 0; - let cached: PPromise<[ISerializedSearchComplete, IRawFileMatch[]], IFileSearchProgressItem>; + let cachedRow: CacheRow; let wasResolved: boolean; for (let previousSearch in cache.resultsToSearchCache) { @@ -282,20 +314,25 @@ export class SearchService implements IRawSearchService { continue; // since a path character widens the search for potential more matches, require it in previous search too } - const c = cache.resultsToSearchCache[previousSearch]; - c.then(() => { wasResolved = false; }); + const row = cache.resultsToSearchCache[previousSearch]; + row.promise.then(() => { wasResolved = false; }); wasResolved = true; - cached = this.preventCancellation(c); + cachedRow = { + promise: this.preventCancellation(row.promise), + event: row.event + }; break; } } - if (!cached) { + if (!cachedRow) { return null; } - return new PPromise<[ISerializedSearchComplete, IRawFileMatch[], CacheStats], IProgress>((c, e, p) => { - cached.then(([complete, cachedEntries]) => { + const listener = cachedRow.event(progressCallback); + + return new TPromise<[ISerializedSearchSuccess, IRawFileMatch[], CacheStats]>((c, e) => { + cachedRow.promise.then(([complete, cachedEntries]) => { const cacheFilterStartTime = Date.now(); // Pattern match on results @@ -317,21 +354,22 @@ export class SearchService implements IRawSearchService { cacheFilterStartTime: cacheFilterStartTime, cacheFilterResultCount: cachedEntries.length }]); - }, e, p); + }, e); }, () => { - cached.cancel(); + cachedRow.promise.cancel(); + listener.dispose(); }); } - private doTextSearch(engine: TextSearchEngine, batchSize: number): PPromise { - return new PPromise((c, e, p) => { + private doTextSearch(engine: TextSearchEngine, progressCallback: IProgressCallback, batchSize: number): TPromise { + return new TPromise((c, e) => { // Use BatchedCollector to get new results to the frontend every 2s at least, until 50 results have been returned - const collector = new BatchedCollector(batchSize, p); + const collector = new BatchedCollector(batchSize, progressCallback); engine.search((matches) => { const totalMatches = matches.reduce((acc, m) => acc + m.numMatches, 0); collector.addItems(matches, totalMatches); }, (progress) => { - p(progress); + progressCallback(progress); }, (error, stats) => { collector.flush(); @@ -346,28 +384,28 @@ export class SearchService implements IRawSearchService { }); } - private doSearch(engine: ISearchEngine, batchSize?: number): PPromise { - return new PPromise((c, e, p) => { + private doSearch(engine: ISearchEngine, progressCallback: IFileProgressCallback, batchSize?: number): TPromise { + return new TPromise((c, e) => { let batch: IRawFileMatch[] = []; engine.search((match) => { if (match) { if (batchSize) { batch.push(match); if (batchSize > 0 && batch.length >= batchSize) { - p(batch); + progressCallback(batch); batch = []; } } else { - p(match); + progressCallback(match); } } }, (progress) => { process.nextTick(() => { - p(progress); + progressCallback(progress); }); }, (error, stats) => { if (batch.length) { - p(batch); + progressCallback(batch); } if (error) { e(error); @@ -385,19 +423,11 @@ export class SearchService implements IRawSearchService { return TPromise.as(undefined); } - public fetchTelemetry(): PPromise { - return new PPromise((c, e, p) => { - this.telemetryPipe = p; - }, () => { - this.telemetryPipe = null; - }); - } - - private preventCancellation(promise: PPromise): PPromise { - return new PPromise((c, e, p) => { + private preventCancellation(promise: TPromise): TPromise { + return new TPromise((c, e) => { // Allow for piled up cancellations to come through first. process.nextTick(() => { - promise.then(c, e, p); + promise.then(c, e); }); }, () => { // Do not propagate. @@ -405,9 +435,14 @@ export class SearchService implements IRawSearchService { } } +interface CacheRow { + promise: TPromise<[ISerializedSearchSuccess, IRawFileMatch[]]>; + event: Event; +} + class Cache { - public resultsToSearchCache: { [searchValue: string]: PPromise<[ISerializedSearchComplete, IRawFileMatch[]], IFileSearchProgressItem>; } = Object.create(null); + public resultsToSearchCache: { [searchValue: string]: CacheRow; } = Object.create(null); public scorerCache: ScorerCache = Object.create(null); } diff --git a/src/vs/workbench/services/search/node/ripgrepTextSearch.ts b/src/vs/workbench/services/search/node/ripgrepTextSearch.ts index c42d0659d93..81898c0880d 100644 --- a/src/vs/workbench/services/search/node/ripgrepTextSearch.ts +++ b/src/vs/workbench/services/search/node/ripgrepTextSearch.ts @@ -18,7 +18,7 @@ import * as encoding from 'vs/base/node/encoding'; import * as extfs from 'vs/base/node/extfs'; import { IProgress } from 'vs/platform/search/common/search'; import { rgPath } from 'vscode-ripgrep'; -import { FileMatch, IFolderSearch, IRawSearch, ISerializedFileMatch, ISerializedSearchComplete, LineMatch } from './search'; +import { FileMatch, IFolderSearch, IRawSearch, ISerializedFileMatch, LineMatch, ISerializedSearchSuccess } from './search'; // If vscode-ripgrep is in an .asar file, then the binary is unpacked. const rgDiskPath = rgPath.replace(/\bnode_modules\.asar\b/, 'node_modules.asar.unpacked'); @@ -44,10 +44,11 @@ export class RipgrepEngine { } // TODO@Rob - make promise-based once the old search is gone, and I don't need them to have matching interfaces anymore - search(onResult: (match: ISerializedFileMatch) => void, onMessage: (message: IProgress) => void, done: (error: Error, complete: ISerializedSearchComplete) => void): void { + search(onResult: (match: ISerializedFileMatch) => void, onMessage: (message: IProgress) => void, done: (error: Error, complete: ISerializedSearchSuccess) => void): void { if (!this.config.folderQueries.length && !this.config.extraFiles.length) { process.removeListener('exit', this.killRgProcFn); done(null, { + type: 'success', limitHit: false, stats: null }); @@ -94,6 +95,7 @@ export class RipgrepEngine { this.cancel(); process.removeListener('exit', this.killRgProcFn); done(null, { + type: 'success', limitHit: true, stats: null }); @@ -124,11 +126,13 @@ export class RipgrepEngine { process.removeListener('exit', this.killRgProcFn); if (stderr && !gotData && (displayMsg = rgErrorMsgForDisplay(stderr))) { done(new Error(displayMsg), { + type: 'success', limitHit: false, stats: null }); } else { done(null, { + type: 'success', limitHit: false, stats: null }); diff --git a/src/vs/workbench/services/search/node/search.ts b/src/vs/workbench/services/search/node/search.ts index a1061c52d22..7cf5124dd45 100644 --- a/src/vs/workbench/services/search/node/search.ts +++ b/src/vs/workbench/services/search/node/search.ts @@ -5,10 +5,11 @@ 'use strict'; -import { PPromise, TPromise } from 'vs/base/common/winjs.base'; +import { TPromise } from 'vs/base/common/winjs.base'; import { IExpression } from 'vs/base/common/glob'; import { IProgress, ILineMatch, IPatternInfo, ISearchStats } from 'vs/platform/search/common/search'; import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; +import { Event } from 'vs/base/common/event'; export interface IFolderSearch { folder: string; @@ -41,10 +42,10 @@ export interface ITelemetryEvent { } export interface IRawSearchService { - fileSearch(search: IRawSearch): PPromise; - textSearch(search: IRawSearch): PPromise; + fileSearch(search: IRawSearch): Event; + textSearch(search: IRawSearch): Event; clearCache(cacheKey: string): TPromise; - fetchTelemetry(): PPromise; + readonly onTelemetry: Event; } export interface IRawFileMatch { @@ -55,15 +56,37 @@ export interface IRawFileMatch { } export interface ISearchEngine { - search: (onResult: (matches: T) => void, onProgress: (progress: IProgress) => void, done: (error: Error, complete: ISerializedSearchComplete) => void) => void; + search: (onResult: (matches: T) => void, onProgress: (progress: IProgress) => void, done: (error: Error, complete: ISerializedSearchSuccess) => void) => void; cancel: () => void; } -export interface ISerializedSearchComplete { +export interface ISerializedSearchSuccess { + type: 'success'; limitHit: boolean; stats: ISearchStats; } +export interface ISerializedSearchError { + type: 'error'; + error: any; +} + +export type ISerializedSearchComplete = ISerializedSearchSuccess | ISerializedSearchError; + +export function isSerializedSearchComplete(arg: ISerializedSearchProgressItem | ISerializedSearchComplete): arg is ISerializedSearchComplete { + if ((arg as any).type === 'error') { + return true; + } else if ((arg as any).type === 'success') { + return true; + } else { + return false; + } +} + +export function isSerializedSearchSuccess(arg: ISerializedSearchComplete): arg is ISerializedSearchSuccess { + return arg.type === 'success'; +} + export interface ISerializedFileMatch { path: string; lineMatches?: ILineMatch[]; diff --git a/src/vs/workbench/services/search/node/searchIpc.ts b/src/vs/workbench/services/search/node/searchIpc.ts index ffa1d267bb9..6caafb0e822 100644 --- a/src/vs/workbench/services/search/node/searchIpc.ts +++ b/src/vs/workbench/services/search/node/searchIpc.ts @@ -5,16 +5,16 @@ 'use strict'; -import { PPromise, TPromise } from 'vs/base/common/winjs.base'; +import { TPromise } from 'vs/base/common/winjs.base'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IRawSearchService, IRawSearch, ISerializedSearchComplete, ISerializedSearchProgressItem, ITelemetryEvent } from './search'; import { Event } from 'vs/base/common/event'; export interface ISearchChannel extends IChannel { - call(command: 'fileSearch', search: IRawSearch): PPromise; - call(command: 'textSearch', search: IRawSearch): PPromise; + listen(event: 'telemetry'): Event; + listen(event: 'fileSearch', search: IRawSearch): Event; + listen(event: 'textSearch', search: IRawSearch): Event; call(command: 'clearCache', cacheKey: string): TPromise; - call(command: 'fetchTelemetry'): PPromise; call(command: string, arg: any): TPromise; } @@ -22,38 +22,38 @@ export class SearchChannel implements ISearchChannel { constructor(private service: IRawSearchService) { } - listen(event: string, arg?: any): Event { - throw new Error('No events'); + listen(event: string, arg?: any): Event { + switch (event) { + case 'telemetry': return this.service.onTelemetry; + case 'fileSearch': return this.service.textSearch(arg); + case 'textSearch': return this.service.textSearch(arg); + } + throw new Error('Event not found'); } call(command: string, arg?: any): TPromise { switch (command) { - case 'fileSearch': return this.service.fileSearch(arg); - case 'textSearch': return this.service.textSearch(arg); case 'clearCache': return this.service.clearCache(arg); - case 'fetchTelemetry': return this.service.fetchTelemetry(); } - return undefined; + throw new Error('Call not found'); } } export class SearchChannelClient implements IRawSearchService { + get onTelemetry(): Event { return this.channel.listen('telemetry'); } + constructor(private channel: ISearchChannel) { } - fileSearch(search: IRawSearch): PPromise { - return this.channel.call('fileSearch', search); + fileSearch(search: IRawSearch): Event { + return this.channel.listen('fileSearch', search); } - textSearch(search: IRawSearch): PPromise { - return this.channel.call('textSearch', search); + textSearch(search: IRawSearch): Event { + return this.channel.listen('textSearch', search); } clearCache(cacheKey: string): TPromise { return this.channel.call('clearCache', cacheKey); } - - fetchTelemetry(): PPromise { - return this.channel.call('fetchTelemetry'); - } } \ No newline at end of file diff --git a/src/vs/workbench/services/search/node/searchService.ts b/src/vs/workbench/services/search/node/searchService.ts index cc292c17701..bdc651e9950 100644 --- a/src/vs/workbench/services/search/node/searchService.ts +++ b/src/vs/workbench/services/search/node/searchService.ts @@ -15,7 +15,7 @@ import { IProgress, LineMatch, FileMatch, ISearchComplete, ISearchProgressItem, import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IRawSearch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedFileMatch, IRawSearchService, ITelemetryEvent } from './search'; +import { IRawSearch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedFileMatch, IRawSearchService, ITelemetryEvent, isSerializedSearchComplete, isSerializedSearchSuccess, ISerializedSearchSuccess } from './search'; import { ISearchChannel, SearchChannelClient } from './searchIpc'; import { IEnvironmentService, IDebugParams } from 'vs/platform/environment/common/environment'; import { ResourceMap } from 'vs/base/common/map'; @@ -26,6 +26,7 @@ import { Schemas } from 'vs/base/common/network'; import * as pfs from 'vs/base/node/pfs'; import { ILogService } from 'vs/platform/log/common/log'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { Event } from 'vs/base/common/event'; export class SearchService implements ISearchService { public _serviceBrand: any; @@ -320,14 +321,14 @@ export class DiskSearch implements ISearchResultProvider { const existingFolders = folderQueries.filter((q, index) => exists[index]); const rawSearch = this.rawSearchQuery(query, existingFolders); - let request: PPromise; + let event: Event; if (query.type === QueryType.File) { - request = this.raw.fileSearch(rawSearch); + event = this.raw.fileSearch(rawSearch); } else { - request = this.raw.textSearch(rawSearch); + event = this.raw.textSearch(rawSearch); } - return DiskSearch.collectResults(request); + return DiskSearch.collectResultsFromEvent(event); }); } @@ -372,7 +373,28 @@ export class DiskSearch implements ISearchResultProvider { return rawSearch; } - public static collectResults(request: PPromise): PPromise { + public static collectResultsFromEvent(event: Event): PPromise { + const promise = new PPromise((c, e, p) => { + setTimeout(() => { + const listener = event(ev => { + if (isSerializedSearchComplete(ev)) { + if (isSerializedSearchSuccess(ev)) { + c(ev); + } else { + e(ev.error); + } + listener.dispose(); + } else { + p(ev); + } + }); + }, 0); + }); + + return DiskSearch.collectResults(promise); + } + + public static collectResults(request: PPromise): PPromise { let result: IFileMatch[] = []; return new PPromise((c, e, p) => { request.done((complete) => { @@ -420,6 +442,8 @@ export class DiskSearch implements ISearchResultProvider { } public fetchTelemetry(): PPromise { - return this.raw.fetchTelemetry(); + return new PPromise((c, e, p) => { + this.raw.onTelemetry(p); + }); } } diff --git a/src/vs/workbench/services/search/node/textSearch.ts b/src/vs/workbench/services/search/node/textSearch.ts index c2002bb88ed..e2a14693c19 100644 --- a/src/vs/workbench/services/search/node/textSearch.ts +++ b/src/vs/workbench/services/search/node/textSearch.ts @@ -11,7 +11,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { IProgress } from 'vs/platform/search/common/search'; import { FileWalker } from 'vs/workbench/services/search/node/fileSearch'; -import { ISerializedFileMatch, ISerializedSearchComplete, IRawSearch, ISearchEngine } from './search'; +import { ISerializedFileMatch, IRawSearch, ISearchEngine, ISerializedSearchSuccess } from './search'; import { ISearchWorker } from './worker/searchWorkerIpc'; import { ITextSearchWorkerProvider } from './textSearchWorkerProvider'; @@ -60,7 +60,7 @@ export class Engine implements ISearchEngine { }); } - search(onResult: (match: ISerializedFileMatch[]) => void, onProgress: (progress: IProgress) => void, done: (error: Error, complete: ISerializedSearchComplete) => void): void { + search(onResult: (match: ISerializedFileMatch[]) => void, onProgress: (progress: IProgress) => void, done: (error: Error, complete: ISerializedSearchSuccess) => void): void { this.workers = this.workerProvider.getWorkers(); this.initializeWorkers(); @@ -86,6 +86,7 @@ export class Engine implements ISearchEngine { if (!this.isDone && this.processedBytes === this.totalBytes && this.walkerIsDone) { this.isDone = true; done(this.walkerError, { + type: 'success', limitHit: this.limitReached, stats: this.walker.getStats() }); diff --git a/src/vs/workbench/services/search/test/node/searchService.test.ts b/src/vs/workbench/services/search/test/node/searchService.test.ts index 5d33088e881..9ceecbfa748 100644 --- a/src/vs/workbench/services/search/test/node/searchService.test.ts +++ b/src/vs/workbench/services/search/test/node/searchService.test.ts @@ -9,9 +9,11 @@ import * as assert from 'assert'; import * as path from 'path'; import { IProgress, IUncachedSearchStats } from 'vs/platform/search/common/search'; -import { ISearchEngine, IRawSearch, IRawFileMatch, ISerializedFileMatch, ISerializedSearchComplete, IFolderSearch } from 'vs/workbench/services/search/node/search'; +import { ISearchEngine, IRawSearch, IRawFileMatch, ISerializedFileMatch, IFolderSearch, ISerializedSearchSuccess, ISerializedSearchProgressItem, ISerializedSearchComplete } from 'vs/workbench/services/search/node/search'; import { SearchService as RawSearchService } from 'vs/workbench/services/search/node/rawSearchService'; import { DiskSearch } from 'vs/workbench/services/search/node/searchService'; +import { Emitter, Event } from 'vs/base/common/event'; +import { TPromise } from 'vs/base/common/winjs.base'; const TEST_FOLDER_QUERIES = [ { folder: path.normalize('/some/where') } @@ -44,12 +46,13 @@ class TestSearchEngine implements ISearchEngine { TestSearchEngine.last = this; } - public search(onResult: (match: IRawFileMatch) => void, onProgress: (progress: IProgress) => void, done: (error: Error, complete: ISerializedSearchComplete) => void): void { + public search(onResult: (match: IRawFileMatch) => void, onProgress: (progress: IProgress) => void, done: (error: Error, complete: ISerializedSearchSuccess) => void): void { const self = this; (function next() { process.nextTick(() => { if (self.isCanceled) { done(null, { + type: 'success', limitHit: false, stats: stats }); @@ -58,6 +61,7 @@ class TestSearchEngine implements ISearchEngine { const result = self.result(); if (!result) { done(null, { + type: 'success', limitHit: false, stats: stats }); @@ -101,17 +105,17 @@ suite('SearchService', () => { const service = new RawSearchService(); let results = 0; - return service.doFileSearch(Engine, rawSearch) - .then(() => { - assert.strictEqual(results, 5); - }, null, value => { - if (!Array.isArray(value)) { - assert.deepStrictEqual(value, match); - results++; - } else { - assert.fail(JSON.stringify(value)); - } - }); + const cb: (p: ISerializedSearchProgressItem) => void = value => { + if (!Array.isArray(value)) { + assert.deepStrictEqual(value, match); + results++; + } else { + assert.fail(JSON.stringify(value)); + } + }; + + return service.doFileSearch(Engine, rawSearch, cb) + .then(() => assert.strictEqual(results, 5)); }); test('Batch results', function () { @@ -121,19 +125,20 @@ suite('SearchService', () => { const service = new RawSearchService(); const results = []; - return service.doFileSearch(Engine, rawSearch, 10) - .then(() => { - assert.deepStrictEqual(results, [10, 10, 5]); - }, null, value => { - if (Array.isArray(value)) { - value.forEach(m => { - assert.deepStrictEqual(m, match); - }); - results.push(value.length); - } else { - assert.fail(JSON.stringify(value)); - } - }); + const cb: (p: ISerializedSearchProgressItem) => void = value => { + if (Array.isArray(value)) { + value.forEach(m => { + assert.deepStrictEqual(m, match); + }); + results.push(value.length); + } else { + assert.fail(JSON.stringify(value)); + } + }; + + return service.doFileSearch(Engine, rawSearch, cb, 10).then(() => { + assert.deepStrictEqual(results, [10, 10, 5]); + }); }); test('Collect batched results', function () { @@ -143,8 +148,24 @@ suite('SearchService', () => { const Engine = TestSearchEngine.bind(null, () => i-- && rawMatch); const service = new RawSearchService(); + function fileSearch(config: IRawSearch, batchSize: number): Event { + let promise: TPromise; + + const emitter = new Emitter({ + onFirstListenerAdd: () => { + promise = service.doFileSearch(Engine, config, p => emitter.fire(p), batchSize) + .then(c => emitter.fire(c), err => emitter.fire({ type: 'error', error: err })); + }, + onLastListenerRemove: () => { + promise.cancel(); + } + }); + + return emitter.event; + } + const progressResults = []; - return DiskSearch.collectResults(service.doFileSearch(Engine, rawSearch, 10)) + return DiskSearch.collectResultsFromEvent(fileSearch(rawSearch, 10)) .then(result => { assert.strictEqual(result.results.length, 25, 'Result'); assert.strictEqual(progressResults.length, 25, 'Progress'); @@ -167,7 +188,7 @@ suite('SearchService', () => { }, }; - return DiskSearch.collectResults(service.fileSearch(query)) + return DiskSearch.collectResultsFromEvent(service.fileSearch(query)) .then(result => { assert.strictEqual(result.results.length, 1, 'Result'); }); @@ -186,7 +207,7 @@ suite('SearchService', () => { }, }; - return DiskSearch.collectResults(service.fileSearch(query)) + return DiskSearch.collectResultsFromEvent(service.fileSearch(query)) .then(result => { assert.strictEqual(result.results.length, 0, 'Result'); assert.ok(result.limitHit); @@ -206,20 +227,22 @@ suite('SearchService', () => { const service = new RawSearchService(); const results = []; - return service.doFileSearch(Engine, { - folderQueries: TEST_FOLDER_QUERIES, - filePattern: 'bb', - sortByScore: true, - maxResults: 2 - }, 1).then(() => { - assert.notStrictEqual(typeof TestSearchEngine.last.config.maxResults, 'number'); - assert.deepStrictEqual(results, [path.normalize('/some/where/bbc'), path.normalize('/some/where/bab')]); - }, null, value => { + const cb = value => { if (Array.isArray(value)) { results.push(...value.map(v => v.path)); } else { assert.fail(JSON.stringify(value)); } + }; + + return service.doFileSearch(Engine, { + folderQueries: TEST_FOLDER_QUERIES, + filePattern: 'bb', + sortByScore: true, + maxResults: 2 + }, cb, 1).then(() => { + assert.notStrictEqual(typeof TestSearchEngine.last.config.maxResults, 'number'); + assert.deepStrictEqual(results, [path.normalize('/some/where/bbc'), path.normalize('/some/where/bab')]); }); }); @@ -230,23 +253,24 @@ suite('SearchService', () => { const service = new RawSearchService(); const results = []; + const cb = value => { + if (Array.isArray(value)) { + value.forEach(m => { + assert.deepStrictEqual(m, match); + }); + results.push(value.length); + } else { + assert.fail(JSON.stringify(value)); + } + }; return service.doFileSearch(Engine, { folderQueries: TEST_FOLDER_QUERIES, filePattern: 'a', sortByScore: true, maxResults: 23 - }, 10) + }, cb, 10) .then(() => { assert.deepStrictEqual(results, [10, 10, 3]); - }, null, value => { - if (Array.isArray(value)) { - value.forEach(m => { - assert.deepStrictEqual(m, match); - }); - results.push(value.length); - } else { - assert.fail(JSON.stringify(value)); - } }); }); @@ -263,37 +287,39 @@ suite('SearchService', () => { const service = new RawSearchService(); const results = []; - return service.doFileSearch(Engine, { - folderQueries: TEST_FOLDER_QUERIES, - filePattern: 'b', - sortByScore: true, - cacheKey: 'x' - }, -1).then(complete => { - assert.strictEqual(complete.stats.fromCache, false); - assert.deepStrictEqual(results, [path.normalize('/some/where/bcb'), path.normalize('/some/where/bbc'), path.normalize('/some/where/aab')]); - }, null, value => { + const cb = value => { if (Array.isArray(value)) { results.push(...value.map(v => v.path)); } else { assert.fail(JSON.stringify(value)); } + }; + return service.doFileSearch(Engine, { + folderQueries: TEST_FOLDER_QUERIES, + filePattern: 'b', + sortByScore: true, + cacheKey: 'x' + }, cb, -1).then(complete => { + assert.strictEqual(complete.stats.fromCache, false); + assert.deepStrictEqual(results, [path.normalize('/some/where/bcb'), path.normalize('/some/where/bbc'), path.normalize('/some/where/aab')]); }).then(() => { const results = []; - return service.doFileSearch(Engine, { - folderQueries: TEST_FOLDER_QUERIES, - filePattern: 'bc', - sortByScore: true, - cacheKey: 'x' - }, -1).then(complete => { - assert.ok(complete.stats.fromCache); - assert.deepStrictEqual(results, [path.normalize('/some/where/bcb'), path.normalize('/some/where/bbc')]); - }, null, value => { + const cb = value => { if (Array.isArray(value)) { results.push(...value.map(v => v.path)); } else { assert.fail(JSON.stringify(value)); } - }); + }; + return service.doFileSearch(Engine, { + folderQueries: TEST_FOLDER_QUERIES, + filePattern: 'bc', + sortByScore: true, + cacheKey: 'x' + }, cb, -1).then(complete => { + assert.ok(complete.stats.fromCache); + assert.deepStrictEqual(results, [path.normalize('/some/where/bcb'), path.normalize('/some/where/bbc')]); + }, null); }).then(() => { return service.clearCache('x'); }).then(() => { @@ -304,20 +330,21 @@ suite('SearchService', () => { size: 3 }); const results = []; - return service.doFileSearch(Engine, { - folderQueries: TEST_FOLDER_QUERIES, - filePattern: 'bc', - sortByScore: true, - cacheKey: 'x' - }, -1).then(complete => { - assert.strictEqual(complete.stats.fromCache, false); - assert.deepStrictEqual(results, [path.normalize('/some/where/bc')]); - }, null, value => { + const cb = value => { if (Array.isArray(value)) { results.push(...value.map(v => v.path)); } else { assert.fail(JSON.stringify(value)); } + }; + return service.doFileSearch(Engine, { + folderQueries: TEST_FOLDER_QUERIES, + filePattern: 'bc', + sortByScore: true, + cacheKey: 'x' + }, cb, -1).then(complete => { + assert.strictEqual(complete.stats.fromCache, false); + assert.deepStrictEqual(results, [path.normalize('/some/where/bc')]); }); }); }); From 5e67324ced57285c0f2827f98f8f7e272bf2bf94 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 4 Jul 2018 14:48:23 +0100 Subject: [PATCH 169/283] use HKLM for classes root key fixes #53438 --- build/win32/code.iss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/win32/code.iss b/build/win32/code.iss index d180ca4ae9b..cad2e27d65e 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -92,7 +92,7 @@ Filename: "{app}\{#ExeBasename}.exe"; Description: "{cm:LaunchProgram,{#NameLong #if "user" == InstallTarget #define SoftwareClassesRootKey "HKCU" #else -#define SoftwareClassesRootKey "HKCR" +#define SoftwareClassesRootKey "HKLM" #endif Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.ascx\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles From 2cf659c756583d5d8b9e5f7658bc05b6694b6b02 Mon Sep 17 00:00:00 2001 From: Isidor Nikolic Date: Wed, 4 Jul 2018 15:53:37 +0200 Subject: [PATCH 170/283] Revert "menu: remove terminal menu" --- src/vs/platform/actions/common/actions.ts | 1 + .../parts/menubar/menubar.contribution.ts | 100 ++++++++++++++++++ .../browser/parts/menubar/menubarPart.ts | 5 +- 3 files changed, 105 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index e05cc8aa5fe..c4b1e0f958c 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -97,6 +97,7 @@ export class MenuId { static readonly MenubarWindowMenu = new MenuId(); static readonly MenubarPreferencesMenu = new MenuId(); static readonly MenubarHelpMenu = new MenuId(); + static readonly MenubarTerminalMenu = new MenuId(); readonly id: string = String(MenuId.ID++); } diff --git a/src/vs/workbench/browser/parts/menubar/menubar.contribution.ts b/src/vs/workbench/browser/parts/menubar/menubar.contribution.ts index 13625f8f7aa..8d877ddba77 100644 --- a/src/vs/workbench/browser/parts/menubar/menubar.contribution.ts +++ b/src/vs/workbench/browser/parts/menubar/menubar.contribution.ts @@ -16,6 +16,7 @@ layoutMenuRegistration(); goMenuRegistration(); debugMenuRegistration(); tasksMenuRegistration(); +terminalMenuRegistration(); if (isMacintosh) { windowMenuRegistration(); @@ -1493,3 +1494,102 @@ function helpMenuRegistration() { order: 1 }); } + +function terminalMenuRegistration() { + + // Manage + + MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { + group: '1_manage', + command: { + id: 'workbench.action.terminal.new', + title: nls.localize({ key: 'miNewTerminal', comment: ['&& denotes a mnemonic'] }, "&&New Terminal") + }, + order: 1 + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { + group: '1_manage', + command: { + id: 'workbench.action.terminal.split', + title: nls.localize({ key: 'miSplitTerminal', comment: ['&& denotes a mnemonic'] }, "&&Split Terminal") + }, + order: 2 + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { + group: '1_manage', + command: { + id: 'workbench.action.terminal.kill', + title: nls.localize({ key: 'miKillTerminal', comment: ['&& denotes a mnemonic'] }, "&&Kill Terminal") + }, + order: 3 + }); + + // Run + + MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { + group: '2_run', + command: { + id: 'workbench.action.terminal.clear', + title: nls.localize({ key: 'miClear', comment: ['&& denotes a mnemonic'] }, "&&Clear") + }, + order: 1 + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { + group: '2_run', + command: { + id: 'workbench.action.terminal.runActiveFile', + title: nls.localize({ key: 'miRunActiveFile', comment: ['&& denotes a mnemonic'] }, "Run &&Active File") + }, + order: 2 + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { + group: '2_run', + command: { + id: 'workbench.action.terminal.runSelectedFile', + title: nls.localize({ key: 'miRunSelectedText', comment: ['&& denotes a mnemonic'] }, "Run &&Selected Text") + }, + order: 3 + }); + + // Selection + + MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { + group: '3_selection', + command: { + id: 'workbench.action.terminal.scrollToPreviousCommand', + title: nls.localize({ key: 'miScrollToPreviousCommand', comment: ['&& denotes a mnemonic'] }, "Scroll To Previous Command") + }, + order: 1 + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { + group: '3_selection', + command: { + id: 'workbench.action.terminal.scrollToNextCommand', + title: nls.localize({ key: 'miScrollToNextCommand', comment: ['&& denotes a mnemonic'] }, "Scroll To Next Command") + }, + order: 2 + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { + group: '3_selection', + command: { + id: 'workbench.action.terminal.selectToPreviousCommand', + title: nls.localize({ key: 'miSelectToPreviousCommand', comment: ['&& denotes a mnemonic'] }, "Select To Previous Command") + }, + order: 3 + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { + group: '3_selection', + command: { + id: 'workbench.action.terminal.selectToNextCommand', + title: nls.localize({ key: 'miSelectToNextCommand', comment: ['&& denotes a mnemonic'] }, "Select To Next Command") + }, + order: 4 + }); +} \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/menubar/menubarPart.ts b/src/vs/workbench/browser/parts/menubar/menubarPart.ts index 160f114b213..31c84306e65 100644 --- a/src/vs/workbench/browser/parts/menubar/menubarPart.ts +++ b/src/vs/workbench/browser/parts/menubar/menubarPart.ts @@ -60,6 +60,7 @@ export class MenubarPart extends Part { 'Selection': IMenu; 'View': IMenu; 'Go': IMenu; + 'Terminal': IMenu; 'Debug': IMenu; 'Tasks': IMenu; 'Window'?: IMenu; @@ -73,6 +74,7 @@ export class MenubarPart extends Part { 'Selection': nls.localize({ key: 'mSelection', comment: ['&& denotes a mnemonic'] }, "&&Selection"), 'View': nls.localize({ key: 'mView', comment: ['&& denotes a mnemonic'] }, "&&View"), 'Go': nls.localize({ key: 'mGoto', comment: ['&& denotes a mnemonic'] }, "&&Go"), + 'Terminal': nls.localize({ key: 'mTerminal', comment: ['&& denotes a mnemonic'] }, "&&Terminal"), 'Debug': nls.localize({ key: 'mDebug', comment: ['&& denotes a mnemonic'] }, "&&Debug"), 'Tasks': nls.localize({ key: 'mTasks', comment: ['&& denotes a mnemonic'] }, "&&Tasks"), 'Help': nls.localize({ key: 'mHelp', comment: ['&& denotes a mnemonic'] }, "&&Help") @@ -123,6 +125,7 @@ export class MenubarPart extends Part { 'Selection': this._register(this.menuService.createMenu(MenuId.MenubarSelectionMenu, this.contextKeyService)), 'View': this._register(this.menuService.createMenu(MenuId.MenubarViewMenu, this.contextKeyService)), 'Go': this._register(this.menuService.createMenu(MenuId.MenubarGoMenu, this.contextKeyService)), + 'Terminal': this._register(this.menuService.createMenu(MenuId.MenubarTerminalMenu, this.contextKeyService)), 'Debug': this._register(this.menuService.createMenu(MenuId.MenubarDebugMenu, this.contextKeyService)), 'Tasks': this._register(this.menuService.createMenu(MenuId.MenubarTasksMenu, this.contextKeyService)), 'Help': this._register(this.menuService.createMenu(MenuId.MenubarHelpMenu, this.contextKeyService)) @@ -859,4 +862,4 @@ class ModifierKeyEmitter extends Emitter { super.dispose(); this._subscriptions = dispose(this._subscriptions); } -} +} \ No newline at end of file From ef035db4c3c0029a291f1644c91812f1a72931ca Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 4 Jul 2018 16:24:22 +0200 Subject: [PATCH 171/283] add missing monaco.d.ts changes --- src/vs/monaco.d.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index f46a6a4bbd2..2a2ea6db909 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -83,7 +83,6 @@ declare namespace monaco { public static join(promises: [T1 | PromiseLike, T2 | PromiseLike]): Promise<[T1, T2]>; public static join(promises: (T | PromiseLike)[]): Promise; - public static join(promises: { [n: string]: T | PromiseLike }): Promise<{ [n: string]: T }>; public static any(promises: (T | PromiseLike)[]): Promise<{ key: string; value: Promise; }>; From a115ec6f706aebd5ba3f50cba8384224f55d8072 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 4 Jul 2018 16:29:40 +0200 Subject: [PATCH 172/283] fix compile issue --- src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts index c5f086b0724..0c9d89d3eaa 100644 --- a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts @@ -39,6 +39,7 @@ import { first } from 'vs/base/common/collections'; import { DocumentSymbolProviderRegistry } from 'vs/editor/common/modes'; import { Range } from 'vs/editor/common/core/range'; import { OutlineDataSource, OutlineRenderer, OutlineItemComparator, OutlineController } from 'vs/editor/contrib/documentSymbols/outlineTree'; +import { CancellationToken } from 'vs/base/common/cancellation'; class FileElement { constructor( @@ -179,7 +180,7 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { this._editorDisposables.push(oracle); oracle.event(async _ => { - let model = await asDisposablePromise(OutlineModel.create(control.getModel()), undefined, this._editorDisposables).promise; + let model = await asDisposablePromise(OutlineModel.create(control.getModel(), CancellationToken.None), undefined, this._editorDisposables).promise; if (!model) { return; } From a535dd799512aec4c109b029ed900efd2dd4d0ae Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 4 Jul 2018 17:23:28 +0200 Subject: [PATCH 173/283] fix tests (revert #53532) --- src/vs/workbench/parts/files/common/editors/fileEditorInput.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts b/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts index 29b277207b2..1f441487577 100644 --- a/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts @@ -259,7 +259,7 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { // Resolve as text return this.textFileService.models.loadOrCreate(this.resource, { encoding: this.preferredEncoding, - reload: { async: true }, // trigger a reload of the model if it exists already but do not wait to show the model + reload: { async: false }, // trigger a reload of the model if it exists already but do not wait to show the model allowBinary: this.forceOpenAsText }).then(model => { From c1b51f9f07ec979c38932e64aafbb25ca2d6f01e Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 4 Jul 2018 17:40:32 +0200 Subject: [PATCH 174/283] align watcher interfaces --- .../services/files/node/watcher/nsfw/nsfwWatcherService.ts | 4 ++-- .../workbench/services/files/node/watcher/nsfw/watcher.ts | 6 +++++- .../services/files/node/watcher/nsfw/watcherIpc.ts | 6 +++--- .../services/files/node/watcher/nsfw/watcherService.ts | 6 +++++- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts b/src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts index b11e518b965..3dbfb49213a 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts @@ -9,7 +9,7 @@ import * as path from 'path'; import * as platform from 'vs/base/common/platform'; import * as watcher from 'vs/workbench/services/files/node/watcher/common'; import * as nsfw from 'vscode-nsfw'; -import { IWatcherService, IWatcherRequest } from 'vs/workbench/services/files/node/watcher/nsfw/watcher'; +import { IWatcherService, IWatcherRequest, IWatcherOptions } from 'vs/workbench/services/files/node/watcher/nsfw/watcher'; import { TPromise, ProgressCallback, TValueCallback, ErrorCallback } from 'vs/base/common/winjs.base'; import { ThrottledDelayer } from 'vs/base/common/async'; import { FileChangeType } from 'vs/platform/files/common/files'; @@ -41,7 +41,7 @@ export class NsfwWatcherService implements IWatcherService { private _verboseLogging: boolean; private enospcErrorLogged: boolean; - public initialize(verboseLogging: boolean): TPromise { + public initialize(options: IWatcherOptions): TPromise { this._verboseLogging = true; this._watcherPromise = new TPromise((c, e, p) => { this._errorCallback = e; diff --git a/src/vs/workbench/services/files/node/watcher/nsfw/watcher.ts b/src/vs/workbench/services/files/node/watcher/nsfw/watcher.ts index 7e571c74ea4..efad426ca5a 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/watcher.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/watcher.ts @@ -12,7 +12,11 @@ export interface IWatcherRequest { ignored: string[]; } +export interface IWatcherOptions { + verboseLogging: boolean; +} + export interface IWatcherService { - initialize(verboseLogging: boolean): TPromise; + initialize(options: IWatcherOptions): TPromise; setRoots(roots: IWatcherRequest[]): TPromise; } \ No newline at end of file diff --git a/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts b/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts index 849173e7c70..844a9e65e82 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts @@ -7,7 +7,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IWatcherRequest, IWatcherService } from './watcher'; +import { IWatcherRequest, IWatcherService, IWatcherOptions } from './watcher'; import { Event } from 'vs/base/common/event'; export interface IWatcherChannel extends IChannel { @@ -37,8 +37,8 @@ export class WatcherChannelClient implements IWatcherService { constructor(private channel: IWatcherChannel) { } - initialize(verboseLogging: boolean): TPromise { - return this.channel.call('initialize', verboseLogging); + initialize(options: IWatcherOptions): TPromise { + return this.channel.call('initialize', options); } setRoots(roots: IWatcherRequest[]): TPromise { diff --git a/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts b/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts index e5403578de2..d758ddadfc6 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts @@ -55,10 +55,14 @@ export class FileWatcher { ); this.toDispose.push(client); + const options = { + verboseLogging: this.verboseLogging + }; + // Initialize watcher const channel = getNextTickChannel(client.getChannel('watcher')); this.service = new WatcherChannelClient(channel); - this.service.initialize(this.verboseLogging).then(null, err => { + this.service.initialize(options).then(null, err => { if (!this.isDisposed && !isPromiseCanceledError(err)) { return TPromise.wrapError(err); // the service lib uses the promise cancel error to indicate the process died, we do not want to bubble this up } From a53ae59ce9c8b6695bdf1cb190115ba65690d1fb Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 4 Jul 2018 17:55:41 +0200 Subject: [PATCH 175/283] outline model work --- .../browser/parts/editor/editorBreadcrumbs.ts | 8 ++ .../parts/editor/editorBreadcrumbsModel.ts | 129 ++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 src/vs/workbench/browser/parts/editor/editorBreadcrumbsModel.ts diff --git a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts index 0c9d89d3eaa..c1736330fd1 100644 --- a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts @@ -40,6 +40,7 @@ import { DocumentSymbolProviderRegistry } from 'vs/editor/common/modes'; import { Range } from 'vs/editor/common/core/range'; import { OutlineDataSource, OutlineRenderer, OutlineItemComparator, OutlineController } from 'vs/editor/contrib/documentSymbols/outlineTree'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { EditorBreadcrumbsModel } from 'vs/workbench/browser/parts/editor/editorBreadcrumbsModel'; class FileElement { constructor( @@ -148,6 +149,13 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { this._widget.splice(0, this._widget.items().length, fileItems); let control = this._editorGroup.activeControl.getControl() as ICodeEditor; + + let model = new EditorBreadcrumbsModel(input.getResource(), isCodeEditor(control) ? control : undefined, this._workspaceService); + let listener = model.onDidUpdate(_ => { + console.log(model.getElements()); + }); + this._editorDisposables.push(model, listener); + if (!isCodeEditor(control)) { return; } diff --git a/src/vs/workbench/browser/parts/editor/editorBreadcrumbsModel.ts b/src/vs/workbench/browser/parts/editor/editorBreadcrumbsModel.ts new file mode 100644 index 00000000000..f19a2cd1905 --- /dev/null +++ b/src/vs/workbench/browser/parts/editor/editorBreadcrumbsModel.ts @@ -0,0 +1,129 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { OutlineModel, OutlineGroup, OutlineElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; +import URI from 'vs/base/common/uri'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import * as paths from 'vs/base/common/paths'; +import { isEqual } from 'vs/base/common/resources'; +import { DocumentSymbolProviderRegistry } from 'vs/editor/common/modes'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { debounceEvent, Event, Emitter } from 'vs/base/common/event'; +import { size } from 'vs/base/common/collections'; + +export class FileElement { + constructor( + readonly uri: URI, + readonly isFile: boolean + ) { } +} + +export type BreadcrumbElement = FileElement | OutlineGroup | OutlineElement; + +export class EditorBreadcrumbsModel { + + private readonly _disposables: IDisposable[] = []; + private readonly _fileElements: FileElement[] = []; + private _outlineDisposables: IDisposable[] = []; + private _outlineModel: OutlineModel; + + private _onDidUpdate = new Emitter(); + readonly onDidUpdate: Event = this._onDidUpdate.event; + + constructor( + private readonly _uri: URI, + private readonly _editor: ICodeEditor | undefined, + @IWorkspaceContextService workspaceService: IWorkspaceContextService, + ) { + this._fileElements = EditorBreadcrumbsModel._getFileElements(this._uri, workspaceService); + this._bindToEditor(); + this._onDidUpdate.fire(this); + } + + dispose(): void { + dispose(this._disposables); + } + + getElements(): ReadonlyArray { + if (!this._editor || !this._outlineModel) { + return this._fileElements; + } + + let item: OutlineGroup | OutlineElement = this._outlineModel.getItemEnclosingPosition(this._editor.getPosition()); + let items: (OutlineGroup | OutlineElement)[] = []; + while (item) { + items.push(item); + let parent = item.parent; + if (parent instanceof OutlineModel) { + break; + } + if (parent instanceof OutlineGroup && size(parent.parent.children) === 1) { + break; + } + item = parent; + } + + return (this._fileElements as BreadcrumbElement[]).concat(items.reverse()); + } + + private static _getFileElements(uri: URI, workspaceService: IWorkspaceContextService): FileElement[] { + let result: FileElement[] = []; + let workspace = workspaceService.getWorkspaceFolder(uri); + let path = uri.path; + while (path !== '/') { + if (workspace && isEqual(workspace.uri, uri)) { + break; + } + result.push(new FileElement(uri, result.length === 0)); + path = paths.dirname(path); + uri = uri.with({ path }); + } + return result.reverse(); + } + + private _bindToEditor(): void { + if (!this._editor) { + return; + } + this._updateOutline(); + this._disposables.push(DocumentSymbolProviderRegistry.onDidChange(_ => this._updateOutline())); + this._disposables.push(this._editor.onDidChangeModel(_ => this._updateOutline())); + this._disposables.push(this._editor.onDidChangeModelLanguage(_ => this._updateOutline())); + this._disposables.push(debounceEvent(this._editor.onDidChangeModelContent, _ => _, 350)(_ => this._updateOutline())); + } + + private _updateOutline(): void { + + this._outlineDisposables = dispose(this._outlineDisposables); + + const model = this._editor.getModel(); + if (!model || !DocumentSymbolProviderRegistry.has(model) || !isEqual(model.uri, this._uri)) { + return; + } + + const source = new CancellationTokenSource(); + + this._outlineDisposables.push({ + dispose: () => { + source.cancel(); + source.dispose(); + } + }); + OutlineModel.create(model, source.token).then(model => { + this._outlineModel = model; + this._onDidUpdate.fire(this); + this._outlineDisposables.push(debounceEvent(this._editor.onDidChangeCursorPosition, _ => _, 250)(_ => this._onDidUpdate.fire(this))); + }).catch(err => { + this._outlineModel = undefined; + this._onDidUpdate.fire(this); + onUnexpectedError(err); + }); + } +} From 20d26aaeccfc872d73b20f77b93da3cde2d7c93a Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 4 Jul 2018 21:25:04 +0200 Subject: [PATCH 176/283] accessibility: fix debug section names fixes #52307 --- src/vs/workbench/parts/debug/browser/breakpointsView.ts | 4 ++-- .../parts/debug/electron-browser/watchExpressionsView.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/parts/debug/browser/breakpointsView.ts b/src/vs/workbench/parts/debug/browser/breakpointsView.ts index ba6e7cb7b92..77f2cd56085 100644 --- a/src/vs/workbench/parts/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/parts/debug/browser/breakpointsView.ts @@ -35,7 +35,7 @@ import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; const $ = dom.$; @@ -58,7 +58,7 @@ export class BreakpointsView extends ViewletPanel { @IContextViewService private contextViewService: IContextViewService, @IConfigurationService configurationService: IConfigurationService ) { - super(options, keybindingService, contextMenuService, configurationService); + super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('breakpointsSection', "Breakpoints Section") }, keybindingService, contextMenuService, configurationService); this.minimumBodySize = this.maximumBodySize = this.getExpandedBodySize(); this.settings = options.viewletSettings; diff --git a/src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts b/src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts index c1b3079a4c2..5a517047652 100644 --- a/src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts +++ b/src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts @@ -50,7 +50,7 @@ export class WatchExpressionsView extends TreeViewsViewletPanel { @IInstantiationService private instantiationService: IInstantiationService, @IConfigurationService configurationService: IConfigurationService ) { - super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('expressionsSection', "Expressions Section") }, keybindingService, contextMenuService, configurationService); + super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('watchExpressionsSection', "Watch Expressions Section") }, keybindingService, contextMenuService, configurationService); this.settings = options.viewletSettings; this.onWatchExpressionsUpdatedScheduler = new RunOnceScheduler(() => { From 7e0c22604932495c4018aa4d517ed057fb2ac8f6 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 4 Jul 2018 22:35:51 +0200 Subject: [PATCH 177/283] remove ppromise usage from nsfw ipc --- src/vs/base/common/errors.ts | 4 ++ src/vs/base/common/event.ts | 2 + src/vs/base/parts/ipc/node/ipc.cp.ts | 5 ++ .../node/watcher/nsfw/nsfwWatcherService.ts | 22 +++----- .../files/node/watcher/nsfw/watcher.ts | 4 +- .../files/node/watcher/nsfw/watcherIpc.ts | 17 ++++-- .../files/node/watcher/nsfw/watcherService.ts | 56 +++++++------------ 7 files changed, 53 insertions(+), 57 deletions(-) diff --git a/src/vs/base/common/errors.ts b/src/vs/base/common/errors.ts index 46b7f230ff8..5a2b335664d 100644 --- a/src/vs/base/common/errors.ts +++ b/src/vs/base/common/errors.ts @@ -242,6 +242,10 @@ export interface IErrorWithActions { actions?: IAction[]; } +export function isError(obj: any): obj is Error { + return obj instanceof Error; +} + export function isErrorWithActions(obj: any): obj is IErrorWithActions { return obj instanceof Error && Array.isArray((obj as IErrorWithActions).actions); } diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index 2f88230192f..eb4c7cef1c1 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -423,6 +423,8 @@ export function forEach(event: Event, each: (i: I) => void): Event { return (listener, thisArgs = null, disposables?) => event(i => { each(i); listener.call(thisArgs, i); }, null, disposables); } +export function filterEvent(event: Event, filter: (e: T) => boolean): Event; +export function filterEvent(event: Event, filter: (e: T | R) => e is R): Event; export function filterEvent(event: Event, filter: (e: T) => boolean): Event { return (listener, thisArgs = null, disposables?) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables); } diff --git a/src/vs/base/parts/ipc/node/ipc.cp.ts b/src/vs/base/parts/ipc/node/ipc.cp.ts index da07daede35..b81be1c3d74 100644 --- a/src/vs/base/parts/ipc/node/ipc.cp.ts +++ b/src/vs/base/parts/ipc/node/ipc.cp.ts @@ -79,6 +79,9 @@ export class Client implements IChannelClient, IDisposable { private _client: IPCClient; private channels: { [name: string]: IChannel }; + private _onDidProcessExit = new Emitter<{ code: number, signal: string }>(); + readonly onDidProcessExit = this._onDidProcessExit.event; + constructor(private modulePath: string, private options: IIPCOptions) { const timeout = options && options.timeout ? options.timeout : 60000; this.disposeDelayer = new Delayer(timeout); @@ -221,6 +224,8 @@ export class Client implements IChannelClient, IDisposable { this.disposeDelayer.cancel(); this.disposeClient(); } + + this._onDidProcessExit.fire({ code, signal }); }); } diff --git a/src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts b/src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts index 3dbfb49213a..600fdc7c547 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts @@ -10,10 +10,11 @@ import * as platform from 'vs/base/common/platform'; import * as watcher from 'vs/workbench/services/files/node/watcher/common'; import * as nsfw from 'vscode-nsfw'; import { IWatcherService, IWatcherRequest, IWatcherOptions } from 'vs/workbench/services/files/node/watcher/nsfw/watcher'; -import { TPromise, ProgressCallback, TValueCallback, ErrorCallback } from 'vs/base/common/winjs.base'; +import { TPromise, TValueCallback } from 'vs/base/common/winjs.base'; import { ThrottledDelayer } from 'vs/base/common/async'; import { FileChangeType } from 'vs/platform/files/common/files'; import { normalizeNFC } from 'vs/base/common/normalization'; +import { Event, Emitter } from 'vs/base/common/event'; const nsfwActionToRawChangeType: { [key: number]: number } = []; nsfwActionToRawChangeType[nsfw.actions.CREATED] = FileChangeType.ADDED; @@ -35,20 +36,15 @@ export class NsfwWatcherService implements IWatcherService { private static readonly FS_EVENT_DELAY = 50; // aggregate and only emit events when changes have stopped for this duration (in ms) private _pathWatchers: { [watchPath: string]: IPathWatcher } = {}; - private _watcherPromise: TPromise; - private _progressCallback: ProgressCallback; - private _errorCallback: ErrorCallback; private _verboseLogging: boolean; private enospcErrorLogged: boolean; - public initialize(options: IWatcherOptions): TPromise { - this._verboseLogging = true; - this._watcherPromise = new TPromise((c, e, p) => { - this._errorCallback = e; - this._progressCallback = p; + private _onWatchEvent = new Emitter(); + readonly onWatchEvent = this._onWatchEvent.event; - }); - return this._watcherPromise; + watch(options: IWatcherOptions): Event { + this._verboseLogging = true; + return this.onWatchEvent; } private _watch(request: IWatcherRequest): void { @@ -70,7 +66,7 @@ export class NsfwWatcherService implements IWatcherService { // See https://github.com/Microsoft/vscode/issues/7950 if (e === 'Inotify limit reached' && !this.enospcErrorLogged) { this.enospcErrorLogged = true; - this._errorCallback(new Error('Inotify limit reached (ENOSPC)')); + this._onWatchEvent.fire(new Error('Inotify limit reached (ENOSPC)')); } }); @@ -119,7 +115,7 @@ export class NsfwWatcherService implements IWatcherService { // Broadcast to clients normalized const res = watcher.normalize(events); - this._progressCallback(res); + this._onWatchEvent.fire(res); // Logging if (this._verboseLogging) { diff --git a/src/vs/workbench/services/files/node/watcher/nsfw/watcher.ts b/src/vs/workbench/services/files/node/watcher/nsfw/watcher.ts index efad426ca5a..fb38b0d3633 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/watcher.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/watcher.ts @@ -6,6 +6,8 @@ 'use strict'; import { TPromise } from 'vs/base/common/winjs.base'; +import { Event } from 'vs/base/common/event'; +import { IRawFileChange } from 'vs/workbench/services/files/node/watcher/common'; export interface IWatcherRequest { basePath: string; @@ -17,6 +19,6 @@ export interface IWatcherOptions { } export interface IWatcherService { - initialize(options: IWatcherOptions): TPromise; + watch(options: IWatcherOptions): Event; setRoots(roots: IWatcherRequest[]): TPromise; } \ No newline at end of file diff --git a/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts b/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts index 844a9e65e82..859c631ecf3 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts @@ -9,24 +9,29 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IWatcherRequest, IWatcherService, IWatcherOptions } from './watcher'; import { Event } from 'vs/base/common/event'; +import { IRawFileChange } from 'vs/workbench/services/files/node/watcher/common'; export interface IWatcherChannel extends IChannel { - call(command: 'initialize', verboseLogging: boolean): TPromise; + listen(event: 'watch', verboseLogging: boolean): Event; + listen(event: string, arg?: any): Event; + call(command: 'setRoots', request: IWatcherRequest[]): TPromise; - call(command: string, arg: any): TPromise; + call(command: string, arg?: any): TPromise; } export class WatcherChannel implements IWatcherChannel { constructor(private service: IWatcherService) { } - listen(event: string, arg?: any): Event { + listen(event: string, arg?: any): Event { + switch (event) { + case 'watch': return this.service.watch(arg); + } throw new Error('No events'); } call(command: string, arg: any): TPromise { switch (command) { - case 'initialize': return this.service.initialize(arg); case 'setRoots': return this.service.setRoots(arg); } return undefined; @@ -37,8 +42,8 @@ export class WatcherChannelClient implements IWatcherService { constructor(private channel: IWatcherChannel) { } - initialize(options: IWatcherOptions): TPromise { - return this.channel.call('initialize', options); + watch(options: IWatcherOptions): Event { + return this.channel.listen('watch', options); } setRoots(roots: IWatcherRequest[]): TPromise { diff --git a/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts b/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts index d758ddadfc6..81d2b70d092 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts @@ -5,7 +5,6 @@ 'use strict'; -import { TPromise } from 'vs/base/common/winjs.base'; import { getNextTickChannel } from 'vs/base/parts/ipc/common/ipc'; import { Client } from 'vs/base/parts/ipc/node/ipc.cp'; import uri from 'vs/base/common/uri'; @@ -16,7 +15,8 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; -import { isPromiseCanceledError } from 'vs/base/common/errors'; +import { filterEvent } from 'vs/base/common/event'; +import { isError } from 'util'; export class FileWatcher { private static readonly MAX_RESTARTS = 5; @@ -24,7 +24,7 @@ export class FileWatcher { private service: WatcherChannelClient; private isDisposed: boolean; private restartCounter: number; - private toDispose: IDisposable[]; + private toDispose: IDisposable[] = []; constructor( private contextService: IWorkspaceContextService, @@ -35,17 +35,14 @@ export class FileWatcher { ) { this.isDisposed = false; this.restartCounter = 0; - this.toDispose = []; } public startWatching(): () => void { - const args = ['--type=watcherService']; - const client = new Client( uri.parse(require.toUrl('bootstrap')).fsPath, { serverName: 'Watcher', - args, + args: ['--type=watcherService'], env: { AMD_ENTRYPOINT: 'vs/workbench/services/files/node/watcher/nsfw/watcherApp', PIPE_LOGGING: 'true', @@ -55,20 +52,7 @@ export class FileWatcher { ); this.toDispose.push(client); - const options = { - verboseLogging: this.verboseLogging - }; - - // Initialize watcher - const channel = getNextTickChannel(client.getChannel('watcher')); - this.service = new WatcherChannelClient(channel); - this.service.initialize(options).then(null, err => { - if (!this.isDisposed && !isPromiseCanceledError(err)) { - return TPromise.wrapError(err); // the service lib uses the promise cancel error to indicate the process died, we do not want to bubble this up - } - return void 0; - }, (events: IRawFileChange[]) => this.onRawFileEvents(events)).done(() => { - + client.onDidProcessExit(() => { // our watcher app should never be completed because it keeps on watching. being in here indicates // that the watcher process died and we want to restart it here. we only do it a max number of times if (!this.isDisposed) { @@ -80,11 +64,20 @@ export class FileWatcher { this.errorLogger('[FileWatcher] failed to start after retrying for some time, giving up. Please report this as a bug report!'); } } - }, error => { - if (!this.isDisposed) { - this.errorLogger(error); - } - }); + }, null, this.toDispose); + + // Initialize watcher + const channel = getNextTickChannel(client.getChannel('watcher')); + this.service = new WatcherChannelClient(channel); + + const options = { verboseLogging: this.verboseLogging }; + const onWatchEvent = filterEvent(this.service.watch(options), () => !this.isDisposed); + + const onError = filterEvent(onWatchEvent, isError); + onError(err => this.errorLogger(err.stack), null, this.toDispose); + + const onFileChanges = filterEvent(onWatchEvent, (e): e is IRawFileChange[] => Array.isArray(e) && e.length > 0); + onFileChanges(e => this.onFileChanges(toFileChangesEvent(e)), null, this.toDispose); // Start watching this.updateFolders(); @@ -122,17 +115,6 @@ export class FileWatcher { })); } - private onRawFileEvents(events: IRawFileChange[]): void { - if (this.isDisposed) { - return; - } - - // Emit through event emitter - if (events.length > 0) { - this.onFileChanges(toFileChangesEvent(events)); - } - } - private dispose(): void { this.isDisposed = true; this.toDispose = dispose(this.toDispose); From b4d512a30127f348ffc2b28fdc27bdfc0a2165c9 Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Wed, 4 Jul 2018 20:39:42 -0700 Subject: [PATCH 178/283] cleanup submenu container when disposed fixes #52917 --- src/vs/base/browser/ui/menu/menu.ts | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index fe7be1d3a35..2b7f6df7e2e 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -13,7 +13,7 @@ import { ResolvedKeybinding, KeyCode } from 'vs/base/common/keyCodes'; import { Event } from 'vs/base/common/event'; import { addClass, EventType, EventHelper, EventLike } from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { $ } from 'vs/base/browser/builder'; +import { $, Builder } from 'vs/base/browser/builder'; export interface IMenuOptions { context?: any; @@ -152,6 +152,7 @@ class MenuActionItem extends ActionItem { class SubmenuActionItem extends MenuActionItem { private mysubmenu: Menu; + private submenuContainer: Builder; private mouseOver: boolean; constructor( @@ -224,18 +225,21 @@ class SubmenuActionItem extends MenuActionItem { if (this.parentData.submenu && (force || (this.parentData.submenu !== this.mysubmenu))) { this.parentData.submenu.dispose(); this.parentData.submenu = null; + + this.submenuContainer.dispose(); + this.submenuContainer = null; } } private createSubmenu() { if (!this.parentData.submenu) { - const submenuContainer = $(this.builder).div({ class: 'monaco-submenu menubar-menu-items-holder context-view' }); + this.submenuContainer = $(this.builder).div({ class: 'monaco-submenu menubar-menu-items-holder context-view' }); - $(submenuContainer).style({ + $(this.submenuContainer).style({ 'left': `${$(this.builder).getClientArea().width}px` }); - $(submenuContainer).on(EventType.KEY_UP, (e) => { + $(this.submenuContainer).on(EventType.KEY_UP, (e) => { let event = new StandardKeyboardEvent(e as KeyboardEvent); if (event.equals(KeyCode.LeftArrow)) { EventHelper.stop(e, true); @@ -243,10 +247,13 @@ class SubmenuActionItem extends MenuActionItem { this.parentData.parent.focus(); this.parentData.submenu.dispose(); this.parentData.submenu = null; + + this.submenuContainer.dispose(); + this.submenuContainer = null; } }); - $(submenuContainer).on(EventType.KEY_DOWN, (e) => { + $(this.submenuContainer).on(EventType.KEY_DOWN, (e) => { let event = new StandardKeyboardEvent(e as KeyboardEvent); if (event.equals(KeyCode.LeftArrow)) { EventHelper.stop(e, true); @@ -254,7 +261,7 @@ class SubmenuActionItem extends MenuActionItem { }); - this.parentData.submenu = new Menu(submenuContainer.getHTMLElement(), this.submenuActions, this.submenuOptions); + this.parentData.submenu = new Menu(this.submenuContainer.getHTMLElement(), this.submenuActions, this.submenuOptions); this.parentData.submenu.focus(); this.mysubmenu = this.parentData.submenu; @@ -268,5 +275,10 @@ class SubmenuActionItem extends MenuActionItem { this.mysubmenu.dispose(); this.mysubmenu = null; } + + if (this.submenuContainer) { + this.submenuContainer.dispose(); + this.submenuContainer = null; + } } } \ No newline at end of file From 056105e4191d5de0f045ef3a7e90d60ab791a619 Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Wed, 4 Jul 2018 23:38:28 -0700 Subject: [PATCH 179/283] update spacing for menu items and separators (#53564) --- .../workbench/browser/parts/menubar/media/menubarpart.css | 4 ++-- src/vs/workbench/electron-browser/media/shell.css | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/browser/parts/menubar/media/menubarpart.css b/src/vs/workbench/browser/parts/menubar/media/menubarpart.css index 3f1a72cca49..62b8c849063 100644 --- a/src/vs/workbench/browser/parts/menubar/media/menubarpart.css +++ b/src/vs/workbench/browser/parts/menubar/media/menubarpart.css @@ -8,7 +8,7 @@ position: absolute; font-size: 12px; box-sizing: border-box; - padding-left: 30px; + padding-left: 35px; padding-right: 138px; height: 30px; } @@ -25,7 +25,7 @@ flex-shrink: 0; align-items: center; box-sizing: border-box; - padding: 0px 5px; + padding: 0px 8px; position: relative; cursor: default; -webkit-app-region: no-drag; diff --git a/src/vs/workbench/electron-browser/media/shell.css b/src/vs/workbench/electron-browser/media/shell.css index 3b5709bfcac..64869f0de97 100644 --- a/src/vs/workbench/electron-browser/media/shell.css +++ b/src/vs/workbench/electron-browser/media/shell.css @@ -69,11 +69,16 @@ padding: .5em 0; } -.monaco-shell .monaco-menu .monaco-action-bar.vertical .action-label, +.monaco-shell .monaco-menu .monaco-action-bar.vertical .action-label:not(.separator), .monaco-shell .monaco-menu .monaco-action-bar.vertical .keybinding { padding: 0.5em 2em; } +.monaco-shell .monaco-menu .monaco-action-bar.vertical .action-label.separator { + padding: 0.2em 0 0 0; + margin-bottom: 0.2em; +} + .monaco-shell .monaco-menu .monaco-action-bar.vertical .submenu-indicator { padding: 0.5em 1em; } From 44fe658659dc179cf6207be8d71e09a646882756 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 5 Jul 2018 08:51:27 +0200 Subject: [PATCH 180/283] fix #53532 again (#53568) --- .../browser/editors/fileEditorTracker.ts | 21 +++++- .../files/common/editors/fileEditorInput.ts | 2 +- .../textfile/common/textFileEditorModel.ts | 66 +++++++++++-------- .../common/textFileEditorModelManager.ts | 16 +---- .../services/textfile/common/textfiles.ts | 1 - 5 files changed, 59 insertions(+), 47 deletions(-) diff --git a/src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts b/src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts index d18cd6f8988..8b16509b50c 100644 --- a/src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts +++ b/src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts @@ -10,7 +10,7 @@ import URI from 'vs/base/common/uri'; import * as paths from 'vs/base/common/paths'; import { IEditorViewState } from 'vs/editor/common/editorCommon'; import { toResource, SideBySideEditorInput, IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { FileOperationEvent, FileOperation, IFileService, FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files'; import { FileEditorInput } from 'vs/workbench/parts/files/common/editors/fileEditorInput'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; @@ -27,11 +27,14 @@ import { IWindowService } from 'vs/platform/windows/common/windows'; import { BINARY_FILE_EDITOR_ID } from 'vs/workbench/parts/files/common/files'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService'; +import { ResourceQueue } from 'vs/base/common/async'; +import { onUnexpectedError } from 'vs/base/common/errors'; export class FileEditorTracker extends Disposable implements IWorkbenchContribution { protected closeOnFileDelete: boolean; + private modelLoadQueue: ResourceQueue; private activeOutOfWorkspaceWatchers: ResourceMap; constructor( @@ -47,6 +50,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut ) { super(); + this.modelLoadQueue = new ResourceQueue(); this.activeOutOfWorkspaceWatchers = new ResourceMap(); this.onConfigurationUpdated(configurationService.getValue()); @@ -97,7 +101,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut }) .filter(model => model && !model.isDirty()), m => m.getResource().toString() - ).forEach(model => this.textFileService.models.reload(model)); + ).forEach(model => this.queueModelLoad(model)); } } @@ -300,7 +304,18 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut distinct([...e.getUpdated(), ...e.getAdded()] .map(u => this.textFileService.models.get(u.resource)) .filter(model => model && !model.isDirty()), m => m.getResource().toString()) - .forEach(model => this.textFileService.models.reload(model)); + .forEach(model => this.queueModelLoad(model)); + } + + private queueModelLoad(model: ITextFileEditorModel): void { + + // Load model to update (use a queue to prevent accumulation of loads + // when the load actually takes long. At most we only want the queue + // to have a size of 2 (1 running load and 1 queued load). + const queue = this.modelLoadQueue.queueFor(model.getResource()); + if (queue.size <= 1) { + queue.queue(() => model.load().then(null, onUnexpectedError)); + } } private handleUpdatesToVisibleBinaryEditors(e: FileChangesEvent): void { diff --git a/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts b/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts index 1f441487577..29b277207b2 100644 --- a/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts @@ -259,7 +259,7 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { // Resolve as text return this.textFileService.models.loadOrCreate(this.resource, { encoding: this.preferredEncoding, - reload: { async: false }, // trigger a reload of the model if it exists already but do not wait to show the model + reload: { async: true }, // trigger a reload of the model if it exists already but do not wait to show the model allowBinary: this.forceOpenAsText }).then(model => { diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index cf8b858ec15..256b69426a8 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -319,42 +319,55 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil etag = this.lastResolvedDiskStat.etag; // otherwise respect etag to support caching } + // Ensure to track the versionId before doing a long running operation + // to make sure the model was not changed in the meantime which would + // indicate that the user or program has made edits. If we would ignore + // this, we could potentially loose the changes that were made because + // after resolving the content we update the model and reset the dirty + // flag. + const currentVersionId = this.versionId; + // Resolve Content return this.textFileService .resolveTextContent(this.resource, { acceptTextOnly: !allowBinary, etag, encoding: this.preferredEncoding }) - .then(content => this.handleLoadSuccess(content), error => this.handleLoadError(error)); - } + .then(content => { - private handleLoadSuccess(content: IRawTextContent): TPromise { + // Clear orphaned state when loading was successful + this.setOrphaned(false); - // Clear orphaned state when load was successful - this.setOrphaned(false); + // Guard against the model having changed in the meantime + if (currentVersionId === this.versionId) { + return this.loadWithContent(content); + } - return this.loadWithContent(content); - } + return this; + }, error => { + const result = error.fileOperationResult; - private handleLoadError(error: FileOperationError): TPromise { - const result = error.fileOperationResult; + // Apply orphaned state based on error code + this.setOrphaned(result === FileOperationResult.FILE_NOT_FOUND); - // Apply orphaned state based on error code - this.setOrphaned(result === FileOperationResult.FILE_NOT_FOUND); + // NotModified status is expected and can be handled gracefully + if (result === FileOperationResult.FILE_NOT_MODIFIED_SINCE) { - // NotModified status is expected and can be handled gracefully - if (result === FileOperationResult.FILE_NOT_MODIFIED_SINCE) { - this.setDirty(false); // Ensure we are not tracking a stale state + // Guard against the model having changed in the meantime + if (currentVersionId === this.versionId) { + this.setDirty(false); // Ensure we are not tracking a stale state + } - return TPromise.as(this); - } + return TPromise.as(this); + } - // Ignore when a model has been resolved once and the file was deleted meanwhile. Since - // we already have the model loaded, we can return to this state and update the orphaned - // flag to indicate that this model has no version on disk anymore. - if (this.isResolved() && result === FileOperationResult.FILE_NOT_FOUND) { - return TPromise.as(this); - } + // Ignore when a model has been resolved once and the file was deleted meanwhile. Since + // we already have the model loaded, we can return to this state and update the orphaned + // flag to indicate that this model has no version on disk anymore. + if (this.isResolved() && result === FileOperationResult.FILE_NOT_FOUND) { + return TPromise.as(this); + } - // Otherwise bubble up the error - return TPromise.wrapError(error); + // Otherwise bubble up the error + return TPromise.wrapError(error); + }); } private loadWithContent(content: IRawTextContent, backup?: URI): TPromise { @@ -385,7 +398,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil diag('load() - resolved content', this.resource, new Date()); // Update our resolved disk stat model - const resolvedStat: IFileStat = { + this.updateLastResolvedDiskStat({ resource: this.resource, name: content.name, mtime: content.mtime, @@ -394,8 +407,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil isSymbolicLink: false, children: void 0, isReadonly: content.isReadonly - }; - this.updateLastResolvedDiskStat(resolvedStat); + } as IFileStat); // Keep the original encoding to not loose it when saving const oldEncoding = this.contentEncoding; diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index 2836c9adcce..32e0164ecae 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -14,7 +14,6 @@ import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ResourceMap } from 'vs/base/common/map'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { ResourceQueue } from 'vs/base/common/async'; export class TextFileEditorModelManager extends Disposable implements ITextFileEditorModelManager { @@ -53,8 +52,6 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE private mapResourceToModel: ResourceMap; private mapResourceToPendingModelLoaders: ResourceMap>; - private modelReloadQueue: ResourceQueue = new ResourceQueue(); - constructor( @ILifecycleService private lifecycleService: ILifecycleService, @IInstantiationService private instantiationService: IInstantiationService @@ -150,7 +147,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE // async reload: trigger a reload but return immediately if (options.reload.async) { modelPromise = TPromise.as(model); - this.reload(model); + model.load(options).then(null, onUnexpectedError); } // sync reload: do not return until model reloaded @@ -227,17 +224,6 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE }); } - reload(model: ITextFileEditorModel): void { - - // Load model to reload (use a queue to prevent accumulation of loads - // when the load actually takes long. At most we only want the queue - // to have a size of 2 (1 running load and 1 queued load). - const queue = this.modelReloadQueue.queueFor(model.getResource()); - if (queue.size <= 1) { - queue.queue(() => model.load().then(null, onUnexpectedError)); - } - } - getAll(resource?: URI, filter?: (model: ITextFileEditorModel) => boolean): ITextFileEditorModel[] { if (resource) { const res = this.mapResourceToModel.get(resource); diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index a2f5beedbb1..716b6c1eb36 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -186,7 +186,6 @@ export interface ITextFileEditorModelManager { getAll(resource?: URI): ITextFileEditorModel[]; loadOrCreate(resource: URI, options?: IModelLoadOrCreateOptions): TPromise; - reload(model: ITextFileEditorModel): void; disposeModel(model: ITextFileEditorModel): void; } From 0bacc92e58838d52a43121865c18e8c73e5ffd99 Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Wed, 4 Jul 2018 23:53:25 -0700 Subject: [PATCH 181/283] add missing null check in case not our submenu fixes #53566 --- src/vs/base/browser/ui/menu/menu.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index 2b7f6df7e2e..2eb053fbdc0 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -226,8 +226,10 @@ class SubmenuActionItem extends MenuActionItem { this.parentData.submenu.dispose(); this.parentData.submenu = null; - this.submenuContainer.dispose(); - this.submenuContainer = null; + if (this.submenuContainer) { + this.submenuContainer.dispose(); + this.submenuContainer = null; + } } } From 11c5b9381b8d1db012055201d48cc4d37299afce Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Wed, 4 Jul 2018 23:56:25 -0700 Subject: [PATCH 182/283] better fix for #53459 --- src/vs/workbench/browser/layout.ts | 2 +- src/vs/workbench/electron-browser/workbench.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 6979382b238..01fe21c6a4c 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -219,7 +219,7 @@ export class WorkbenchLayout extends Disposable implements IVerticalSashLayoutPr } @memoize - private get partLayoutInfo() { + public get partLayoutInfo() { return { titlebar: { height: TITLE_BAR_HEIGHT diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index 4dc1424ef1c..7191ec03a8c 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -1205,7 +1205,7 @@ export class Workbench extends Disposable implements IPartService { getTitleBarOffset(): number { let offset = 0; if (this.isVisible(Parts.TITLEBAR_PART)) { - offset = this.getContainer(Parts.TITLEBAR_PART).getBoundingClientRect().height; + offset = this.workbenchLayout.partLayoutInfo.titlebar.height; } return offset; From b6b8c160ff3f93f4874a7c71d4908ed3ddbdd769 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 5 Jul 2018 09:14:50 +0200 Subject: [PATCH 183/283] review comments --- src/vs/base/parts/ipc/node/ipc.cp.ts | 1 + .../services/files/node/watcher/nsfw/nsfwWatcherService.ts | 2 +- .../services/files/node/watcher/nsfw/watcherService.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/base/parts/ipc/node/ipc.cp.ts b/src/vs/base/parts/ipc/node/ipc.cp.ts index b81be1c3d74..38a8a96f813 100644 --- a/src/vs/base/parts/ipc/node/ipc.cp.ts +++ b/src/vs/base/parts/ipc/node/ipc.cp.ts @@ -242,6 +242,7 @@ export class Client implements IChannelClient, IDisposable { } dispose() { + this._onDidProcessExit.dispose(); this.disposeDelayer.cancel(); this.disposeDelayer = null; this.disposeClient(); diff --git a/src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts b/src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts index 600fdc7c547..af8eb9b35b3 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts @@ -43,7 +43,7 @@ export class NsfwWatcherService implements IWatcherService { readonly onWatchEvent = this._onWatchEvent.event; watch(options: IWatcherOptions): Event { - this._verboseLogging = true; + this._verboseLogging = options.verboseLogging; return this.onWatchEvent; } diff --git a/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts b/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts index 81d2b70d092..4cdf1a6ee94 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts @@ -16,7 +16,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { filterEvent } from 'vs/base/common/event'; -import { isError } from 'util'; +import { isError } from 'vs/base/common/errors'; export class FileWatcher { private static readonly MAX_RESTARTS = 5; From d38925c9a004ecab3472e077218705743cccfae5 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 5 Jul 2018 09:27:45 +0200 Subject: [PATCH 184/283] fix bad typing --- src/vs/base/parts/ipc/common/ipc.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index de387bb039b..ed35efadee0 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -539,14 +539,18 @@ export function getNextTickChannel(channel: T): T { .then(() => channel.call(command, arg)); }; - const listen = (event: string, arg: any) => { + const listen = (event: string, arg: any): Event => { if (didTick) { return channel.listen(event, arg); } - return TPromise.timeout(0) + const relay = new Relay(); + + TPromise.timeout(0) .then(() => didTick = true) - .then(() => channel.listen(event, arg)); + .then(() => relay.input = channel.listen(event, arg)); + + return relay.event; }; return { call, listen } as T; From f114b9750ad78bce6cc292e1aeb415f74f8c653a Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 5 Jul 2018 10:05:27 +0200 Subject: [PATCH 185/283] remove PProgress from QuickInput related to #53487 --- .../platform/quickinput/common/quickInput.ts | 6 ++++-- .../electron-browser/mainThreadQuickOpen.ts | 19 ++++++++++--------- src/vs/workbench/api/node/extHost.protocol.ts | 2 +- .../browser/parts/quickinput/quickInput.ts | 8 ++++---- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/vs/platform/quickinput/common/quickInput.ts b/src/vs/platform/quickinput/common/quickInput.ts index 12a15db8b3c..0ec12af97a1 100644 --- a/src/vs/platform/quickinput/common/quickInput.ts +++ b/src/vs/platform/quickinput/common/quickInput.ts @@ -23,7 +23,7 @@ export interface IQuickNavigateConfiguration { keybindings: ResolvedKeybinding[]; } -export interface IPickOptions { +export interface IPickOptions { /** * an optional string to show as place holder in the input box to guide the user what she picks on @@ -49,6 +49,8 @@ export interface IPickOptions { * an optional flag to make this picker multi-select */ canPickMany?: boolean; + + onDidFocus?: (entry: T) => void; } export interface IInputOptions { @@ -177,7 +179,7 @@ export interface IQuickInputService { /** * Opens the quick input box for selecting items and returns a promise with the user selected item(s) if any. */ - pick(picks: TPromise, options?: O, token?: CancellationToken): TPromise; + pick>(picks: TPromise, options?: O, token?: CancellationToken): TPromise; /** * Opens the quick input box for text input and returns a promise with the user typed value if any. diff --git a/src/vs/workbench/api/electron-browser/mainThreadQuickOpen.ts b/src/vs/workbench/api/electron-browser/mainThreadQuickOpen.ts index 5afe23566d3..7435dda07b6 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadQuickOpen.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadQuickOpen.ts @@ -38,7 +38,7 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { public dispose(): void { } - $show(options: IPickOptions): TPromise { + $show(options: IPickOptions): TPromise { const myToken = ++this._token; this._contents = new TPromise((c, e) => { @@ -55,16 +55,21 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { }; }); + options = { + ...options, + onDidFocus: el => { + if (el) { + this._proxy.$onItemSelected((el).handle); + } + } + }; + if (options.canPickMany) { return asWinJsPromise(token => this._quickInputService.pick(this._contents, options as { canPickMany: true }, token)).then(items => { if (items) { return items.map(item => item.handle); } return undefined; - }, undefined, progress => { - if (progress) { - this._proxy.$onItemSelected((progress).handle); - } }); } else { return asWinJsPromise(token => this._quickInputService.pick(this._contents, options, token)).then(item => { @@ -72,10 +77,6 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { return item.handle; } return undefined; - }, undefined, progress => { - if (progress) { - this._proxy.$onItemSelected((progress).handle); - } }); } } diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 053a2e768b6..c124332150f 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -402,7 +402,7 @@ export interface TransferInputBox extends BaseTransferQuickInput { } export interface MainThreadQuickOpenShape extends IDisposable { - $show(options: IPickOptions): TPromise; + $show(options: IPickOptions): TPromise; $setItems(items: TransferQuickPickItems[]): TPromise; $setError(error: Error): TPromise; $input(options: vscode.InputBoxOptions, validateInput: boolean): TPromise; diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.ts b/src/vs/workbench/browser/parts/quickinput/quickInput.ts index c790e200b18..b56ee969d43 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.ts @@ -891,8 +891,8 @@ export class QuickInputService extends Component implements IQuickInputService { this.updateStyles(); } - pick(picks: TPromise, options: O = {}, token: CancellationToken = CancellationToken.None): TPromise { - return new TPromise((resolve, reject, progress) => { + pick>(picks: TPromise, options: O = {}, token: CancellationToken = CancellationToken.None): TPromise { + return new TPromise((resolve, reject) => { if (token.isCancellationRequested) { resolve(undefined); return; @@ -914,8 +914,8 @@ export class QuickInputService extends Component implements IQuickInputService { }), input.onDidChangeActive(items => { const focused = items[0]; - if (focused) { - progress(focused); + if (focused && options.onDidFocus) { + options.onDidFocus(focused); } }), input.onDidChangeSelection(items => { From b60b390330d6b3948bb471505caefd6c089ece21 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 5 Jul 2018 10:09:39 +0200 Subject: [PATCH 186/283] less nervous breadcrumbs model --- .../parts/editor/editorBreadcrumbsModel.ts | 111 ++++++++++++------ 1 file changed, 72 insertions(+), 39 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorBreadcrumbsModel.ts b/src/vs/workbench/browser/parts/editor/editorBreadcrumbsModel.ts index f19a2cd1905..cda2c2252fd 100644 --- a/src/vs/workbench/browser/parts/editor/editorBreadcrumbsModel.ts +++ b/src/vs/workbench/browser/parts/editor/editorBreadcrumbsModel.ts @@ -5,18 +5,21 @@ 'use strict'; -import { OutlineModel, OutlineGroup, OutlineElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; -import URI from 'vs/base/common/uri'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { equals } from 'vs/base/common/arrays'; +import { TimeoutTimer } from 'vs/base/common/async'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { size } from 'vs/base/common/collections'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { debounceEvent, Emitter, Event } from 'vs/base/common/event'; +import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import * as paths from 'vs/base/common/paths'; import { isEqual } from 'vs/base/common/resources'; +import URI from 'vs/base/common/uri'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IPosition } from 'vs/editor/common/core/position'; import { DocumentSymbolProviderRegistry } from 'vs/editor/common/modes'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { debounceEvent, Event, Emitter } from 'vs/base/common/event'; -import { size } from 'vs/base/common/collections'; +import { OutlineElement, OutlineGroup, OutlineModel } from 'vs/editor/contrib/documentSymbols/outlineModel'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; export class FileElement { constructor( @@ -31,8 +34,9 @@ export class EditorBreadcrumbsModel { private readonly _disposables: IDisposable[] = []; private readonly _fileElements: FileElement[] = []; + + private _outlineElements: (OutlineGroup | OutlineElement)[] = []; private _outlineDisposables: IDisposable[] = []; - private _outlineModel: OutlineModel; private _onDidUpdate = new Emitter(); readonly onDidUpdate: Event = this._onDidUpdate.event; @@ -52,25 +56,7 @@ export class EditorBreadcrumbsModel { } getElements(): ReadonlyArray { - if (!this._editor || !this._outlineModel) { - return this._fileElements; - } - - let item: OutlineGroup | OutlineElement = this._outlineModel.getItemEnclosingPosition(this._editor.getPosition()); - let items: (OutlineGroup | OutlineElement)[] = []; - while (item) { - items.push(item); - let parent = item.parent; - if (parent instanceof OutlineModel) { - break; - } - if (parent instanceof OutlineGroup && size(parent.parent.children) === 1) { - break; - } - item = parent; - } - - return (this._fileElements as BreadcrumbElement[]).concat(items.reverse()); + return [].concat(this._fileElements, this._outlineElements); } private static _getFileElements(uri: URI, workspaceService: IWorkspaceContextService): FileElement[] { @@ -96,15 +82,18 @@ export class EditorBreadcrumbsModel { this._disposables.push(DocumentSymbolProviderRegistry.onDidChange(_ => this._updateOutline())); this._disposables.push(this._editor.onDidChangeModel(_ => this._updateOutline())); this._disposables.push(this._editor.onDidChangeModelLanguage(_ => this._updateOutline())); - this._disposables.push(debounceEvent(this._editor.onDidChangeModelContent, _ => _, 350)(_ => this._updateOutline())); + this._disposables.push(debounceEvent(this._editor.onDidChangeModelContent, _ => _, 350)(_ => this._updateOutline(true))); } - private _updateOutline(): void { + private _updateOutline(didChangeContent?: boolean): void { this._outlineDisposables = dispose(this._outlineDisposables); + if (!didChangeContent) { + this._updateOutlineElements([]); + } - const model = this._editor.getModel(); - if (!model || !DocumentSymbolProviderRegistry.has(model) || !isEqual(model.uri, this._uri)) { + const buffer = this._editor.getModel(); + if (!buffer || !DocumentSymbolProviderRegistry.has(buffer) || !isEqual(buffer.uri, this._uri)) { return; } @@ -116,14 +105,58 @@ export class EditorBreadcrumbsModel { source.dispose(); } }); - OutlineModel.create(model, source.token).then(model => { - this._outlineModel = model; - this._onDidUpdate.fire(this); - this._outlineDisposables.push(debounceEvent(this._editor.onDidChangeCursorPosition, _ => _, 250)(_ => this._onDidUpdate.fire(this))); + OutlineModel.create(buffer, source.token).then(model => { + this._updateOutlineElements(this._getOutlineElements(model, this._editor.getPosition())); + const timeout = new TimeoutTimer(); + const lastVersionId = buffer.getVersionId(); + this._outlineDisposables.push(this._editor.onDidChangeCursorPosition(_ => { + timeout.cancelAndSet(() => { + if (lastVersionId === buffer.getVersionId()) { + this._updateOutlineElements(this._getOutlineElements(model, this._editor.getPosition())); + } + }, 150); + })); + this._outlineDisposables.push(timeout); }).catch(err => { - this._outlineModel = undefined; - this._onDidUpdate.fire(this); + this._updateOutlineElements([]); onUnexpectedError(err); }); } + + private _getOutlineElements(model: OutlineModel, position: IPosition): (OutlineGroup | OutlineElement)[] { + if (!model) { + return []; + } + let item: OutlineGroup | OutlineElement = model.getItemEnclosingPosition(position); + let chain: (OutlineGroup | OutlineElement)[] = []; + while (item) { + chain.push(item); + let parent = item.parent; + if (parent instanceof OutlineModel) { + break; + } + if (parent instanceof OutlineGroup && size(parent.parent.children) === 1) { + break; + } + item = parent; + } + return chain; + } + + private _updateOutlineElements(elements: (OutlineGroup | OutlineElement)[]): void { + if (!equals(elements, this._outlineElements, EditorBreadcrumbsModel.outlineElementEquals)) { + this._outlineElements = elements; + this._onDidUpdate.fire(this); + } + } + + private static outlineElementEquals(a: OutlineGroup | OutlineElement, b: OutlineGroup | OutlineElement): boolean { + if (a === b) { + return true; + } else if (!a || !b) { + return false; + } else { + return a.id === b.id; + } + } } From bbe47a440876af697bc21babb8c9d6280e87c426 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 5 Jul 2018 10:13:22 +0200 Subject: [PATCH 187/283] remove progress usage from QuickOpenController related to #53487 --- .../workbench/browser/parts/quickopen/quickOpenController.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts index 178903e64f2..e944fda2f69 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts @@ -181,13 +181,13 @@ export class QuickOpenController extends Component implements IQuickOpenService this.pickOpenWidget.hide(HideReason.CANCELED); } - return new TPromise((resolve, reject, progress) => { + return new TPromise((resolve, reject) => { function onItem(item: IPickOpenEntry): string | IPickOpenEntry { return item && isAboutStrings ? item.label : item; } - this.doPick(entryPromise, options, token).then(item => resolve(onItem(item)), err => reject(err), item => progress(onItem(item))); + this.doPick(entryPromise, options, token).then(item => resolve(onItem(item)), err => reject(err)); }); } From 0679d90bca576aaa6398e26e10dd06f6f64eeaa5 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 5 Jul 2018 10:29:19 +0200 Subject: [PATCH 188/283] remove more progress usage from QuickOpenController related to #53487 --- .../workbench/browser/parts/quickopen/quickOpenController.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts index e944fda2f69..7cab9eec672 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts @@ -249,7 +249,7 @@ export class QuickOpenController extends Component implements IQuickOpenService this.pickOpenWidget.layout(this.layoutDimensions); } - return new TPromise((complete, error, progress) => { + return new TPromise((complete, error) => { // Detect cancellation while pick promise is loading this.pickOpenWidget.setCallbacks({ @@ -285,8 +285,6 @@ export class QuickOpenController extends Component implements IQuickOpenService if (options.onDidFocus) { options.onDidFocus(e); } - - progress(e); }; return this.instantiationService.createInstance(PickOpenEntry, e, index, onPreview, () => this.pickOpenWidget.refresh()); }); From 119ef4fec8336150dc95a6fe86a7a9b51bc976f8 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 5 Jul 2018 11:28:18 +0200 Subject: [PATCH 189/283] fix watcher errors --- src/vs/base/common/errors.ts | 4 ---- .../files/node/watcher/nsfw/nsfwWatcherService.ts | 8 ++++---- .../workbench/services/files/node/watcher/nsfw/watcher.ts | 6 +++++- .../services/files/node/watcher/nsfw/watcherIpc.ts | 4 ++-- .../services/files/node/watcher/nsfw/watcherService.ts | 6 +++--- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/vs/base/common/errors.ts b/src/vs/base/common/errors.ts index 5a2b335664d..46b7f230ff8 100644 --- a/src/vs/base/common/errors.ts +++ b/src/vs/base/common/errors.ts @@ -242,10 +242,6 @@ export interface IErrorWithActions { actions?: IAction[]; } -export function isError(obj: any): obj is Error { - return obj instanceof Error; -} - export function isErrorWithActions(obj: any): obj is IErrorWithActions { return obj instanceof Error && Array.isArray((obj as IErrorWithActions).actions); } diff --git a/src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts b/src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts index af8eb9b35b3..98691d9a030 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts @@ -9,7 +9,7 @@ import * as path from 'path'; import * as platform from 'vs/base/common/platform'; import * as watcher from 'vs/workbench/services/files/node/watcher/common'; import * as nsfw from 'vscode-nsfw'; -import { IWatcherService, IWatcherRequest, IWatcherOptions } from 'vs/workbench/services/files/node/watcher/nsfw/watcher'; +import { IWatcherService, IWatcherRequest, IWatcherOptions, IWatchError } from 'vs/workbench/services/files/node/watcher/nsfw/watcher'; import { TPromise, TValueCallback } from 'vs/base/common/winjs.base'; import { ThrottledDelayer } from 'vs/base/common/async'; import { FileChangeType } from 'vs/platform/files/common/files'; @@ -39,10 +39,10 @@ export class NsfwWatcherService implements IWatcherService { private _verboseLogging: boolean; private enospcErrorLogged: boolean; - private _onWatchEvent = new Emitter(); + private _onWatchEvent = new Emitter(); readonly onWatchEvent = this._onWatchEvent.event; - watch(options: IWatcherOptions): Event { + watch(options: IWatcherOptions): Event { this._verboseLogging = options.verboseLogging; return this.onWatchEvent; } @@ -66,7 +66,7 @@ export class NsfwWatcherService implements IWatcherService { // See https://github.com/Microsoft/vscode/issues/7950 if (e === 'Inotify limit reached' && !this.enospcErrorLogged) { this.enospcErrorLogged = true; - this._onWatchEvent.fire(new Error('Inotify limit reached (ENOSPC)')); + this._onWatchEvent.fire({ message: 'Inotify limit reached (ENOSPC)' }); } }); diff --git a/src/vs/workbench/services/files/node/watcher/nsfw/watcher.ts b/src/vs/workbench/services/files/node/watcher/nsfw/watcher.ts index fb38b0d3633..771933f3559 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/watcher.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/watcher.ts @@ -18,7 +18,11 @@ export interface IWatcherOptions { verboseLogging: boolean; } +export interface IWatchError { + message: string; +} + export interface IWatcherService { - watch(options: IWatcherOptions): Event; + watch(options: IWatcherOptions): Event; setRoots(roots: IWatcherRequest[]): TPromise; } \ No newline at end of file diff --git a/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts b/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts index 859c631ecf3..1502478f301 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts @@ -7,7 +7,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IWatcherRequest, IWatcherService, IWatcherOptions } from './watcher'; +import { IWatcherRequest, IWatcherService, IWatcherOptions, IWatchError } from './watcher'; import { Event } from 'vs/base/common/event'; import { IRawFileChange } from 'vs/workbench/services/files/node/watcher/common'; @@ -42,7 +42,7 @@ export class WatcherChannelClient implements IWatcherService { constructor(private channel: IWatcherChannel) { } - watch(options: IWatcherOptions): Event { + watch(options: IWatcherOptions): Event { return this.channel.listen('watch', options); } diff --git a/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts b/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts index 4cdf1a6ee94..d2791142347 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts @@ -16,7 +16,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { filterEvent } from 'vs/base/common/event'; -import { isError } from 'vs/base/common/errors'; +import { IWatchError } from 'vs/workbench/services/files/node/watcher/nsfw/watcher'; export class FileWatcher { private static readonly MAX_RESTARTS = 5; @@ -73,8 +73,8 @@ export class FileWatcher { const options = { verboseLogging: this.verboseLogging }; const onWatchEvent = filterEvent(this.service.watch(options), () => !this.isDisposed); - const onError = filterEvent(onWatchEvent, isError); - onError(err => this.errorLogger(err.stack), null, this.toDispose); + const onError = filterEvent(onWatchEvent, (e): e is IWatchError => typeof e.message === 'string'); + onError(err => this.errorLogger(err.message), null, this.toDispose); const onFileChanges = filterEvent(onWatchEvent, (e): e is IRawFileChange[] => Array.isArray(e) && e.length > 0); onFileChanges(e => this.onFileChanges(toFileChangesEvent(e)), null, this.toDispose); From 76564e22ea81c4892ebea94e5aa09e625ec5a737 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 5 Jul 2018 11:38:08 +0200 Subject: [PATCH 190/283] :lipstick: --- .../textfile/common/textFileEditorModel.ts | 77 ++++--------------- 1 file changed, 14 insertions(+), 63 deletions(-) diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 256b69426a8..334f58c68ea 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -42,7 +42,10 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil static DEFAULT_ORPHANED_CHANGE_BUFFER_DELAY = 100; private static saveErrorHandler: ISaveErrorHandler; + static setSaveErrorHandler(handler: ISaveErrorHandler): void { TextFileEditorModel.saveErrorHandler = handler; } + private static saveParticipant: ISaveParticipant; + static setSaveParticipant(handler: ISaveParticipant): void { TextFileEditorModel.saveParticipant = handler; } private readonly _onDidContentChange: Emitter = this._register(new Emitter()); get onDidContentChange(): Event { return this._onDidContentChange.event; } @@ -87,6 +90,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil @IHashService private hashService: IHashService ) { super(modelService, modeService); + this.resource = resource; this.preferredEncoding = preferredEncoding; this.inOrphanMode = false; @@ -177,56 +181,27 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } private updateAutoSaveConfiguration(config: IAutoSaveConfiguration): void { - if (typeof config.autoSaveDelay === 'number' && config.autoSaveDelay > 0) { - this.autoSaveAfterMillies = config.autoSaveDelay; - this.autoSaveAfterMilliesEnabled = true; - } else { - this.autoSaveAfterMillies = void 0; - this.autoSaveAfterMilliesEnabled = false; - } + const autoSaveAfterMilliesEnabled = (typeof config.autoSaveDelay === 'number') && config.autoSaveDelay > 0; + + this.autoSaveAfterMilliesEnabled = autoSaveAfterMilliesEnabled; + this.autoSaveAfterMillies = autoSaveAfterMilliesEnabled ? config.autoSaveDelay : void 0; } private onFilesAssociationChange(): void { - this.updateTextEditorModelMode(); - } - - private updateTextEditorModelMode(modeId?: string): void { if (!this.textEditorModel) { return; } const firstLineText = this.getFirstLineText(this.textEditorModel); - const mode = this.getOrCreateMode(this.modeService, modeId, firstLineText); + const mode = this.getOrCreateMode(this.modeService, void 0, firstLineText); this.modelService.setMode(this.textEditorModel, mode); } - /** - * The current version id of the model. - */ getVersionId(): number { return this.versionId; } - /** - * Set a save error handler to install code that executes when save errors occur. - */ - static setSaveErrorHandler(handler: ISaveErrorHandler): void { - TextFileEditorModel.saveErrorHandler = handler; - } - - /** - * Set a save participant handler to react on models getting saved. - */ - static setSaveParticipant(handler: ISaveParticipant): void { - TextFileEditorModel.saveParticipant = handler; - } - - /** - * Discards any local changes and replaces the model with the contents of the version on disk. - * - * @param if the parameter soft is true, will not attempt to load the contents from disk. - */ revert(soft?: boolean): TPromise { if (!this.isResolved()) { return TPromise.wrap(null); @@ -261,9 +236,9 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil load(options?: ILoadOptions): TPromise { diag('load() - enter', this.resource, new Date()); - // It is very important to not reload the model when the model is dirty. We only want to reload the model from the disk - // if no save is pending to avoid data loss. This might cause a save conflict in case the file has been modified on the disk - // meanwhile, but this is a very low risk. + // It is very important to not reload the model when the model is dirty. + // We also only want to reload the model from the disk if no save is pending + // to avoid data loss. if (this.dirty || this.saveSequentializer.hasPendingSave()) { diag('load() - exit - without loading because model is dirty or being saved', this.resource, new Date()); @@ -272,14 +247,14 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Only for new models we support to load from backup if (!this.textEditorModel && !this.createTextEditorModelPromise) { - return this.loadWithBackup(options); + return this.loadFromBackup(options); } // Otherwise load from file resource return this.loadFromFile(options); } - private loadWithBackup(options?: ILoadOptions): TPromise { + private loadFromBackup(options?: ILoadOptions): TPromise { return this.backupFileService.loadBackupResource(this.resource).then(backup => { // Make sure meanwhile someone else did not suceed or start loading @@ -602,9 +577,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } } - /** - * Saves the current versionId of this editor model if it is dirty. - */ save(options: ISaveOptions = Object.create(null)): TPromise { if (!this.isResolved()) { return TPromise.wrap(null); @@ -873,30 +845,18 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil TextFileEditorModel.saveErrorHandler.onSaveError(error, this); } - /** - * Returns true if the content of this model has changes that are not yet saved back to the disk. - */ isDirty(): boolean { return this.dirty; } - /** - * Returns the time in millies when this working copy was attempted to be saved. - */ getLastSaveAttemptTime(): number { return this.lastSaveAttemptTime; } - /** - * Returns the time in millies when this working copy was last modified by the user or some other program. - */ getETag(): string { return this.lastResolvedDiskStat ? this.lastResolvedDiskStat.etag : null; } - /** - * Answers if this model is in a specific state. - */ hasState(state: ModelState): boolean { switch (state) { case ModelState.CONFLICT: @@ -986,23 +946,14 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return this.lastResolvedDiskStat && this.lastResolvedDiskStat.isReadonly; } - /** - * Returns true if the dispose() method of this model has been called. - */ isDisposed(): boolean { return this.disposed; } - /** - * Returns the full resource URI of the file this text file editor model is about. - */ getResource(): URI { return this.resource; } - /** - * Stat accessor only used by tests. - */ getStat(): IFileStat { return this.lastResolvedDiskStat; } From 58f46dbdf649dc94170d8c7cc2424782cee06b30 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 5 Jul 2018 12:09:15 +0200 Subject: [PATCH 191/283] Fix #51088 --- .../configuration/node/configurationService.ts | 9 ++++++--- .../workspace/node/workspaceEditingService.ts | 11 +++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/services/configuration/node/configurationService.ts b/src/vs/workbench/services/configuration/node/configurationService.ts index f304dc4c546..90f013f6b4a 100644 --- a/src/vs/workbench/services/configuration/node/configurationService.ts +++ b/src/vs/workbench/services/configuration/node/configurationService.ts @@ -295,9 +295,9 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat return this._configuration.keys(); } - initialize(arg: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWindowConfiguration): TPromise { + initialize(arg: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWindowConfiguration, postInitialisation: () => void = () => null): TPromise { return this.createWorkspace(arg) - .then(workspace => this.updateWorkspaceAndInitializeConfiguration(workspace)); + .then(workspace => this.updateWorkspaceAndInitializeConfiguration(workspace, postInitialisation)); } acquireFileService(fileService: IFileService): void { @@ -361,7 +361,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat return TPromise.as(new Workspace(id)); } - private updateWorkspaceAndInitializeConfiguration(workspace: Workspace): TPromise { + private updateWorkspaceAndInitializeConfiguration(workspace: Workspace, postInitialisation: () => void): TPromise { const hasWorkspaceBefore = !!this.workspace; let previousState: WorkbenchState; let previousWorkspacePath: string; @@ -377,6 +377,9 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat } return this.initializeConfiguration().then(() => { + + postInitialisation(); + // Trigger changes after configuration initialization so that configuration is up to date. if (hasWorkspaceBefore) { const newState = this.getWorkbenchState(); diff --git a/src/vs/workbench/services/workspace/node/workspaceEditingService.ts b/src/vs/workbench/services/workspace/node/workspaceEditingService.ts index 598ed4084f2..a8b690fc006 100644 --- a/src/vs/workbench/services/workspace/node/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspace/node/workspaceEditingService.ts @@ -188,9 +188,11 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { // Stop the extension host first to give extensions most time to shutdown this.extensionService.stopExtensionHost(); + let extensionHostStarted: boolean = false; const startExtensionHost = () => { this.extensionService.startExtensionHost(); + extensionHostStarted = true; }; return mainSidePromise().then(result => { @@ -206,14 +208,15 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { // Reinitialize configuration service const workspaceImpl = this.contextService as WorkspaceService; - return workspaceImpl.initialize(result.workspace); + return workspaceImpl.initialize(result.workspace, startExtensionHost); }); } return TPromise.as(void 0); - }).then(startExtensionHost, error => { - startExtensionHost(); // in any case start the extension host again! - + }).then(null, error => { + if (!extensionHostStarted) { + startExtensionHost(); // start the extension host if not started + } return TPromise.wrapError(error); }); } From 37faf3a30e31eb4a0e86d09633cff09e96373212 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 5 Jul 2018 13:01:56 +0200 Subject: [PATCH 192/283] remove ppromise usage from chokidar --- .../watcher/unix/chokidarWatcherService.ts | 27 ++++------- .../unix/test/chockidarWatcherService.test.ts | 20 ++++---- .../files/node/watcher/unix/watcher.ts | 12 +++-- .../files/node/watcher/unix/watcherIpc.ts | 19 +++++--- .../files/node/watcher/unix/watcherService.ts | 48 +++++++------------ 5 files changed, 55 insertions(+), 71 deletions(-) diff --git a/src/vs/workbench/services/files/node/watcher/unix/chokidarWatcherService.ts b/src/vs/workbench/services/files/node/watcher/unix/chokidarWatcherService.ts index 550297d06f0..ec8c491949c 100644 --- a/src/vs/workbench/services/files/node/watcher/unix/chokidarWatcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/unix/chokidarWatcherService.ts @@ -21,7 +21,8 @@ import { normalizeNFC } from 'vs/base/common/normalization'; import { realcaseSync } from 'vs/base/node/extfs'; import { isMacintosh } from 'vs/base/common/platform'; import * as watcherCommon from 'vs/workbench/services/files/node/watcher/common'; -import { IWatcherRequest, IWatcherService, IWatcherOptions } from 'vs/workbench/services/files/node/watcher/unix/watcher'; +import { IWatcherRequest, IWatcherService, IWatcherOptions, IWatchError } from 'vs/workbench/services/files/node/watcher/unix/watcher'; +import { Emitter, Event } from 'vs/base/common/event'; interface IWatcher { requests: ExtendedWatcherRequest[]; @@ -44,29 +45,20 @@ export class ChokidarWatcherService implements IWatcherService { private _watchers: { [watchPath: string]: IWatcher }; private _watcherCount: number; - private _watcherPromise: TPromise; private _options: IWatcherOptions & IChockidarWatcherOptions; private spamCheckStartTime: number; private spamWarningLogged: boolean; private enospcErrorLogged: boolean; - private _errorCallback: (error: Error) => void; - private _fileChangeCallback: (changes: watcherCommon.IRawFileChange[]) => void; - public initialize(options: IWatcherOptions & IChockidarWatcherOptions): TPromise { + private _onWatchEvent = new Emitter(); + readonly onWatchEvent = this._onWatchEvent.event; + + watch(options: IWatcherOptions & IChockidarWatcherOptions): Event { this._options = options; this._watchers = Object.create(null); this._watcherCount = 0; - this._watcherPromise = new TPromise((c, e, p) => { - this._errorCallback = (error) => { - this.stop(); - e(error); - }; - this._fileChangeCallback = p; - }, () => { - this.stop(); - }); - return this._watcherPromise; + return this.onWatchEvent; } public setRoots(requests: IWatcherRequest[]): TPromise { @@ -233,7 +225,7 @@ export class ChokidarWatcherService implements IWatcherService { // Broadcast to clients normalized const res = watcherCommon.normalize(events); - this._fileChangeCallback(res); + this._onWatchEvent.fire(res); // Logging if (this._options.verboseLogging) { @@ -257,7 +249,8 @@ export class ChokidarWatcherService implements IWatcherService { if ((error).code === 'ENOSPC') { if (!this.enospcErrorLogged) { this.enospcErrorLogged = true; - this._errorCallback(new Error('Inotify limit reached (ENOSPC)')); + this.stop(); + this._onWatchEvent.fire({ message: 'Inotify limit reached (ENOSPC)' }); } } else { console.error(error.toString()); diff --git a/src/vs/workbench/services/files/node/watcher/unix/test/chockidarWatcherService.test.ts b/src/vs/workbench/services/files/node/watcher/unix/test/chockidarWatcherService.test.ts index 9f9ce6309c7..ef26ad06b85 100644 --- a/src/vs/workbench/services/files/node/watcher/unix/test/chockidarWatcherService.test.ts +++ b/src/vs/workbench/services/files/node/watcher/unix/test/chockidarWatcherService.test.ts @@ -137,19 +137,15 @@ suite.skip('Chockidar watching', () => { await pfs.mkdirp(bFolder); await pfs.mkdirp(b2Folder); - const promise = service.initialize({ verboseLogging: false, pollingInterval: 200 }); - promise.then(null, - e => { - console.log('set error', e); - error = e; - }, - p => { - if (Array.isArray(p)) { - result.push(...p); - } + const opts = { verboseLogging: false, pollingInterval: 200 }; + service.watch(opts)(e => { + if (Array.isArray(e)) { + result.push(...e); + } else { + console.log('set error', e.message); + error = e.message; } - ); - + }); }); suiteTeardown(async () => { diff --git a/src/vs/workbench/services/files/node/watcher/unix/watcher.ts b/src/vs/workbench/services/files/node/watcher/unix/watcher.ts index 8ed35e6c792..771933f3559 100644 --- a/src/vs/workbench/services/files/node/watcher/unix/watcher.ts +++ b/src/vs/workbench/services/files/node/watcher/unix/watcher.ts @@ -6,6 +6,8 @@ 'use strict'; import { TPromise } from 'vs/base/common/winjs.base'; +import { Event } from 'vs/base/common/event'; +import { IRawFileChange } from 'vs/workbench/services/files/node/watcher/common'; export interface IWatcherRequest { basePath: string; @@ -16,7 +18,11 @@ export interface IWatcherOptions { verboseLogging: boolean; } -export interface IWatcherService { - initialize(options: IWatcherOptions): TPromise; - setRoots(roots: IWatcherRequest[]): TPromise; +export interface IWatchError { + message: string; } + +export interface IWatcherService { + watch(options: IWatcherOptions): Event; + setRoots(roots: IWatcherRequest[]): TPromise; +} \ No newline at end of file diff --git a/src/vs/workbench/services/files/node/watcher/unix/watcherIpc.ts b/src/vs/workbench/services/files/node/watcher/unix/watcherIpc.ts index 8b24dc59c28..1502478f301 100644 --- a/src/vs/workbench/services/files/node/watcher/unix/watcherIpc.ts +++ b/src/vs/workbench/services/files/node/watcher/unix/watcherIpc.ts @@ -7,26 +7,31 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IWatcherRequest, IWatcherService, IWatcherOptions } from 'vs/workbench/services/files/node/watcher/unix/watcher'; +import { IWatcherRequest, IWatcherService, IWatcherOptions, IWatchError } from './watcher'; import { Event } from 'vs/base/common/event'; +import { IRawFileChange } from 'vs/workbench/services/files/node/watcher/common'; export interface IWatcherChannel extends IChannel { - call(command: 'initialize', options: IWatcherOptions): TPromise; + listen(event: 'watch', verboseLogging: boolean): Event; + listen(event: string, arg?: any): Event; + call(command: 'setRoots', request: IWatcherRequest[]): TPromise; - call(command: string, arg: any): TPromise; + call(command: string, arg?: any): TPromise; } export class WatcherChannel implements IWatcherChannel { constructor(private service: IWatcherService) { } - listen(event: string, arg?: any): Event { + listen(event: string, arg?: any): Event { + switch (event) { + case 'watch': return this.service.watch(arg); + } throw new Error('No events'); } call(command: string, arg: any): TPromise { switch (command) { - case 'initialize': return this.service.initialize(arg); case 'setRoots': return this.service.setRoots(arg); } return undefined; @@ -37,8 +42,8 @@ export class WatcherChannelClient implements IWatcherService { constructor(private channel: IWatcherChannel) { } - initialize(options: IWatcherOptions): TPromise { - return this.channel.call('initialize', options); + watch(options: IWatcherOptions): Event { + return this.channel.listen('watch', options); } setRoots(roots: IWatcherRequest[]): TPromise { diff --git a/src/vs/workbench/services/files/node/watcher/unix/watcherService.ts b/src/vs/workbench/services/files/node/watcher/unix/watcherService.ts index e51c287854f..45ca79e02f5 100644 --- a/src/vs/workbench/services/files/node/watcher/unix/watcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/unix/watcherService.ts @@ -5,7 +5,6 @@ 'use strict'; -import { TPromise } from 'vs/base/common/winjs.base'; import { getNextTickChannel } from 'vs/base/parts/ipc/common/ipc'; import { Client } from 'vs/base/parts/ipc/node/ipc.cp'; import uri from 'vs/base/common/uri'; @@ -13,10 +12,11 @@ import { toFileChangesEvent, IRawFileChange } from 'vs/workbench/services/files/ import { IWatcherChannel, WatcherChannelClient } from 'vs/workbench/services/files/node/watcher/unix/watcherIpc'; import { FileChangesEvent, IFilesConfiguration } from 'vs/platform/files/common/files'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { isPromiseCanceledError } from 'vs/base/common/errors'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Schemas } from 'vs/base/common/network'; +import { filterEvent } from 'vs/base/common/event'; +import { IWatchError } from 'vs/workbench/services/files/node/watcher/unix/watcher'; export class FileWatcher { private static readonly MAX_RESTARTS = 5; @@ -55,20 +55,7 @@ export class FileWatcher { ); this.toDispose.push(client); - const channel = getNextTickChannel(client.getChannel('watcher')); - this.service = new WatcherChannelClient(channel); - - const options = { - verboseLogging: this.verboseLogging - }; - - this.service.initialize(options).then(null, err => { - if (!this.isDisposed && !isPromiseCanceledError(err)) { - return TPromise.wrapError(err); // the service lib uses the promise cancel error to indicate the process died, we do not want to bubble this up - } - return void 0; - }, (events: IRawFileChange[]) => this.onRawFileEvents(events)).done(() => { - + client.onDidProcessExit(() => { // our watcher app should never be completed because it keeps on watching. being in here indicates // that the watcher process died and we want to restart it here. we only do it a max number of times if (!this.isDisposed) { @@ -80,11 +67,19 @@ export class FileWatcher { this.errorLogger('[FileWatcher] failed to start after retrying for some time, giving up. Please report this as a bug report!'); } } - }, error => { - if (!this.isDisposed) { - this.errorLogger(error); - } - }); + }, null, this.toDispose); + + const channel = getNextTickChannel(client.getChannel('watcher')); + this.service = new WatcherChannelClient(channel); + + const options = { verboseLogging: this.verboseLogging }; + const onWatchEvent = filterEvent(this.service.watch(options), () => !this.isDisposed); + + const onError = filterEvent(onWatchEvent, (e): e is IWatchError => typeof e.message === 'string'); + onError(err => this.errorLogger(err.message), null, this.toDispose); + + const onFileChanges = filterEvent(onWatchEvent, (e): e is IRawFileChange[] => Array.isArray(e) && e.length > 0); + onFileChanges(e => this.onFileChanges(toFileChangesEvent(e)), null, this.toDispose); // Start watching this.updateFolders(); @@ -123,17 +118,6 @@ export class FileWatcher { })); } - private onRawFileEvents(events: IRawFileChange[]): void { - if (this.isDisposed) { - return; - } - - // Emit through event emitter - if (events.length > 0) { - this.onFileChanges(toFileChangesEvent(events)); - } - } - private dispose(): void { this.isDisposed = true; this.toDispose = dispose(this.toDispose); From 89599eaf5d701827b387c6aa794cc06b6df8721d Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 5 Jul 2018 14:15:49 +0200 Subject: [PATCH 193/283] debt - avoid WinJS.Promise.cancel --- src/vs/base/browser/builder.ts | 27 +++++++++-------- .../textfile/common/textFileEditorModel.ts | 29 ++++++++++--------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/vs/base/browser/builder.ts b/src/vs/base/browser/builder.ts index 74b574df9a0..9c285274a99 100644 --- a/src/vs/base/browser/builder.ts +++ b/src/vs/base/browser/builder.ts @@ -5,9 +5,8 @@ 'use strict'; import 'vs/css!./builder'; -import { TPromise } from 'vs/base/common/winjs.base'; import * as types from 'vs/base/common/types'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import * as strings from 'vs/base/common/strings'; import * as assert from 'vs/base/common/assert'; import * as DOM from 'vs/base/browser/dom'; @@ -907,7 +906,7 @@ export class Builder implements IDisposable { this.attr('aria-hidden', 'false'); // Cancel any pending showDelayed() invocation - this.cancelVisibilityPromise(); + this.cancelVisibilityTimeout(); return this; } @@ -922,15 +921,15 @@ export class Builder implements IDisposable { showDelayed(delay: number): Builder { // Cancel any pending showDelayed() invocation - this.cancelVisibilityPromise(); + this.cancelVisibilityTimeout(); - let promise = TPromise.timeout(delay); - this.setProperty(VISIBILITY_BINDING_ID, promise); - - promise.done(() => { + // Install new delay for showing + const handle = setTimeout(() => { this.removeProperty(VISIBILITY_BINDING_ID); this.show(); - }); + }, delay); + + this.setProperty(VISIBILITY_BINDING_ID, toDisposable(() => clearTimeout(handle))); return this; } @@ -945,7 +944,7 @@ export class Builder implements IDisposable { this.attr('aria-hidden', 'true'); // Cancel any pending showDelayed() invocation - this.cancelVisibilityPromise(); + this.cancelVisibilityTimeout(); return this; } @@ -957,10 +956,10 @@ export class Builder implements IDisposable { return this.hasClass('monaco-builder-hidden') || this.currentElement.style.display === 'none'; } - private cancelVisibilityPromise(): void { - let promise: TPromise = this.getProperty(VISIBILITY_BINDING_ID); - if (promise) { - promise.cancel(); + private cancelVisibilityTimeout(): void { + const visibilityDisposable = this.getProperty(VISIBILITY_BINDING_ID) as IDisposable; + if (visibilityDisposable) { + visibilityDisposable.dispose(); this.removeProperty(VISIBILITY_BINDING_ID); } } diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 334f58c68ea..f9e41489ea9 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -32,6 +32,7 @@ import { IHashService } from 'vs/workbench/services/hash/common/hashService'; import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { isLinux } from 'vs/base/common/platform'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; /** * The text file editor model listens to changes to its underlying code editor model and saves these changes through the file service back to the disk. @@ -63,7 +64,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private blockModelContentChange: boolean; private autoSaveAfterMillies: number; private autoSaveAfterMilliesEnabled: boolean; - private autoSavePromise: TPromise; + private autoSaveDisposable: IDisposable; private contentChangeEventScheduler: RunOnceScheduler; private orphanedChangeEventScheduler: RunOnceScheduler; private saveSequentializer: SaveSequentializer; @@ -208,7 +209,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } // Cancel any running auto-save - this.cancelAutoSavePromise(); + this.cancelPendingAutoSave(); // Unset flags const undo = this.setDirty(false); @@ -552,28 +553,28 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } } - private doAutoSave(versionId: number): TPromise { + private doAutoSave(versionId: number): void { diag(`doAutoSave() - enter for versionId ${versionId}`, this.resource, new Date()); // Cancel any currently running auto saves to make this the one that succeeds - this.cancelAutoSavePromise(); + this.cancelPendingAutoSave(); - // Create new save promise and keep it - this.autoSavePromise = TPromise.timeout(this.autoSaveAfterMillies).then(() => { + // Create new save timer and store it for disposal as needed + const handle = setTimeout(() => { // Only trigger save if the version id has not changed meanwhile if (versionId === this.versionId) { this.doSave(versionId, { reason: SaveReason.AUTO }).done(null, onUnexpectedError); // Very important here to not return the promise because if the timeout promise is canceled it will bubble up the error otherwise - do not change } - }); + }, this.autoSaveAfterMillies); - return this.autoSavePromise; + this.autoSaveDisposable = toDisposable(() => clearTimeout(handle)); } - private cancelAutoSavePromise(): void { - if (this.autoSavePromise) { - this.autoSavePromise.cancel(); - this.autoSavePromise = void 0; + private cancelPendingAutoSave(): void { + if (this.autoSaveDisposable) { + this.autoSaveDisposable.dispose(); + this.autoSaveDisposable = void 0; } } @@ -585,7 +586,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil diag('save() - enter', this.resource, new Date()); // Cancel any currently running auto saves to make this the one that succeeds - this.cancelAutoSavePromise(); + this.cancelPendingAutoSave(); return this.doSave(this.versionId, options); } @@ -966,7 +967,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.createTextEditorModelPromise = null; - this.cancelAutoSavePromise(); + this.cancelPendingAutoSave(); super.dispose(); } From b44d778a82f0cfcb7092ae4d9b3a3ebc76a40639 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 5 Jul 2018 15:02:55 +0200 Subject: [PATCH 194/283] some breadcrumb work --- .../ui/breadcrumbs/breadcrumbsWidget.ts | 65 ++- .../browser/parts/editor/editorBreadcrumbs.ts | 381 ++++++++++-------- .../parts/editor/editorBreadcrumbsModel.ts | 2 +- 3 files changed, 237 insertions(+), 211 deletions(-) diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index e8d2e242514..5a77dafaae2 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -13,44 +13,32 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { Event, Emitter } from 'vs/base/common/event'; import { Color } from 'vs/base/common/color'; +import { commonPrefixLength } from 'vs/base/common/arrays'; -export class BreadcrumbsItem { - - constructor( - readonly node: HTMLElement, - readonly more: boolean - ) { - - } - - dispose(): void { - // - } +export abstract class BreadcrumbsItem { + dispose(): void { } + abstract equals(other: BreadcrumbsItem): boolean; + abstract render(container: HTMLElement): void; } export class SimpleBreadcrumbsItem extends BreadcrumbsItem { - constructor(text: string, title: string = text, more: boolean = true) { - super(document.createElement('div'), more); - this.node.innerText = text; - this.node.title = title; - } -} - -export class RenderedBreadcrumbsItem extends BreadcrumbsItem { - - readonly element: E; - private _disposables: IDisposable[] = []; - - constructor(render: (element: E, container: HTMLDivElement, bucket: IDisposable[]) => any, element: E, more: boolean) { - super(document.createElement('div'), more); - this.element = element; - render(element, this.node as HTMLDivElement, this._disposables); + constructor( + readonly text: string, + readonly title: string = text + ) { + super(); } - dispose() { - dispose(this._disposables); - super.dispose(); + equals(other: this) { + return other === this || other instanceof SimpleBreadcrumbsItem && other.text === this.text && other.title === this.title; + } + + render(container: HTMLElement): void { + let node = document.createElement('div'); + node.title = this.title; + node.innerText = this.text; + container.appendChild(node); } } @@ -214,13 +202,10 @@ export class BreadcrumbsWidget { this._onDidSelectItem.fire(this._items[this._selectedItemIdx]); } - items(): ReadonlyArray { - return this._items; - } - - splice(start: number, deleteCount: number, items: BreadcrumbsItem[]): void { - let removed = this._items.splice(start, deleteCount, ...items); - this._render(start); + setItems(items: BreadcrumbsItem[]): void { + let prefix = commonPrefixLength(this._items, items, (a, b) => a.equals(b)); + let removed = this._items.splice(prefix, this._items.length - prefix, ...items.slice(prefix)); + this._render(prefix); dispose(removed); } @@ -251,9 +236,9 @@ export class BreadcrumbsWidget { private _renderItem(item: BreadcrumbsItem, container: HTMLDivElement): void { dom.clearNode(container); - dom.append(container, item.node); + item.render(container); + dom.append(container); dom.addClass(container, 'monaco-breadcrumb-item'); - dom.toggleClass(container, 'monaco-breadcrumb-item-more', item.more); } private _onClick(event: IMouseEvent): void { diff --git a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts index c1736330fd1..4e83cec3f0f 100644 --- a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts @@ -5,52 +5,88 @@ 'use strict'; -import 'vs/css!./media/editorbreadcrumbs'; import * as dom from 'vs/base/browser/dom'; -import { BreadcrumbsWidget, RenderedBreadcrumbsItem } from 'vs/base/browser/ui/breadcrumbs/breadcrumbsWidget'; +import { BreadcrumbsItem, BreadcrumbsWidget } from 'vs/base/browser/ui/breadcrumbs/breadcrumbsWidget'; +import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; +import { compareFileNames } from 'vs/base/common/comparers'; +import { debounceEvent, Emitter, Event } from 'vs/base/common/event'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import * as paths from 'vs/base/common/paths'; +import { isEqual } from 'vs/base/common/resources'; import URI from 'vs/base/common/uri'; -import { IContextKey, IContextKeyService, RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { FileKind, IFileService, IFileStat } from 'vs/platform/files/common/files'; -import { IInstantiationService, IConstructorSignature2 } from 'vs/platform/instantiation/common/instantiation'; -import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { FileLabel } from 'vs/workbench/browser/labels'; -import { EditorInput } from 'vs/workbench/common/editor'; -import { IEditorBreadcrumbs, IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService'; -import { ITreeConfiguration, IDataSource, IRenderer, ISelectionEvent, ISorter, ITree } from 'vs/base/parts/tree/browser/tree'; import { TPromise } from 'vs/base/common/winjs.base'; +import { IDataSource, IRenderer, ISelectionEvent, ISorter, ITree, ITreeConfiguration } from 'vs/base/parts/tree/browser/tree'; +import 'vs/css!./media/editorbreadcrumbs'; +import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { OutlineElement, OutlineGroup, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; +import { OutlineController, OutlineDataSource, OutlineItemComparator, OutlineRenderer } from 'vs/editor/contrib/documentSymbols/outlineTree'; +import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { FileKind, IFileService, IFileStat } from 'vs/platform/files/common/files'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { WorkbenchTree } from 'vs/platform/list/browser/listService'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { debounceEvent, Emitter, Event } from 'vs/base/common/event'; -import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { isEqual, dirname } from 'vs/base/common/resources'; -import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { attachBreadcrumbsStyler } from 'vs/platform/theme/common/styler'; -import { compareFileNames } from 'vs/base/common/comparers'; -import { isCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { OutlineModel, OutlineGroup, OutlineElement, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; -import { asDisposablePromise, setDisposableTimeout } from 'vs/base/common/async'; -import { IPosition } from 'vs/editor/common/core/position'; -import { first } from 'vs/base/common/collections'; -import { DocumentSymbolProviderRegistry } from 'vs/editor/common/modes'; -import { Range } from 'vs/editor/common/core/range'; -import { OutlineDataSource, OutlineRenderer, OutlineItemComparator, OutlineController } from 'vs/editor/contrib/documentSymbols/outlineTree'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { EditorBreadcrumbsModel } from 'vs/workbench/browser/parts/editor/editorBreadcrumbsModel'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { FileLabel } from 'vs/workbench/browser/labels'; +import { BreadcrumbElement, EditorBreadcrumbsModel, FileElement } from 'vs/workbench/browser/parts/editor/editorBreadcrumbsModel'; +import { EditorInput } from 'vs/workbench/common/editor'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { IEditorBreadcrumbs, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; + + +class Item extends BreadcrumbsItem { + + private readonly _disposables: IDisposable[] = []; -class FileElement { constructor( - readonly name: string, - readonly kind: FileKind, - readonly uri: URI, - ) { } -} + readonly element: BreadcrumbElement, + @IInstantiationService private readonly _instantiationService: IInstantiationService + ) { + super(); + } -type BreadcrumbElement = FileElement | OutlineGroup | OutlineElement; + dispose(): void { + dispose(this._disposables); + } + + equals(other: BreadcrumbsItem): boolean { + if (!(other instanceof Item)) { + return false; + } + if (this.element instanceof FileElement && other.element instanceof FileElement) { + return isEqual(this.element.uri, other.element.uri); + } + if (this.element instanceof TreeElement && other.element instanceof TreeElement) { + return this.element.id === other.element.id; + } + return false; + } + + render(container: HTMLElement): void { + if (this.element instanceof FileElement) { + // file/folder + let label = this._instantiationService.createInstance(FileLabel, container, {}); + label.setFile(this.element.uri, { + hidePath: true, + fileKind: this.element.isFile ? FileKind.FILE : FileKind.FOLDER + }); + this._disposables.push(label); + + } else if (this.element instanceof OutlineGroup) { + // provider + let label = new IconLabel(container); + label.setValue(this.element.provider.displayName); + this._disposables.push(label); + + } else if (this.element instanceof OutlineElement) { + // symbol + let label = new IconLabel(container); + label.setValue(this.element.symbol.name); + this._disposables.push(label); + } + } +} export class EditorBreadcrumbs implements IEditorBreadcrumbs { @@ -64,15 +100,13 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { private readonly _domNode: HTMLDivElement; private readonly _widget: BreadcrumbsWidget; - private _editorDisposables = new Array(); + private _breadcrumbsDisposables = new Array(); constructor( container: HTMLElement, private readonly _editorGroup: IEditorGroup, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IFileService private readonly _fileService: IFileService, - @IContextViewService private readonly _contextViewService: IContextViewService, - @IEditorService private readonly _editorService: IEditorService, @IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IThemeService private readonly _themeService: IThemeService, @@ -108,116 +142,123 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { openEditor(input: EditorInput): void { - this._editorDisposables = dispose(this._editorDisposables); + this._breadcrumbsDisposables = dispose(this._breadcrumbsDisposables); let uri = input.getResource(); if (!uri || !this._fileService.canHandleResource(uri)) { return this.closeEditor(undefined); } - this._ckBreadcrumbsVisible.set(true); dom.toggleClass(this._domNode, 'hidden', false); - - const render = (element: FileElement, target: HTMLElement, disposables: IDisposable[]) => { - let label = this._instantiationService.createInstance(FileLabel, target, {}); - label.setFile(element.uri, { fileKind: element.kind, hidePath: true, fileDecorations: { colors: false, badges: false } }); - disposables.push(label); - }; - - let fileItems: RenderedBreadcrumbsItem[] = []; - let workspace = this._workspaceService.getWorkspaceFolder(uri); - let path = uri.path; - - while (true) { - let first = fileItems.length === 0; - let name = paths.basename(path); - uri = uri.with({ path }); - if (workspace && isEqual(workspace.uri, uri, true)) { - break; - } - fileItems.unshift(new RenderedBreadcrumbsItem( - render, - new FileElement(name, first ? FileKind.FILE : FileKind.FOLDER, uri), - !first - )); - path = paths.dirname(path); - if (path === '/') { - break; - } - } - - this._widget.splice(0, this._widget.items().length, fileItems); + this._ckBreadcrumbsVisible.set(true); let control = this._editorGroup.activeControl.getControl() as ICodeEditor; - let model = new EditorBreadcrumbsModel(input.getResource(), isCodeEditor(control) ? control : undefined, this._workspaceService); - let listener = model.onDidUpdate(_ => { - console.log(model.getElements()); - }); - this._editorDisposables.push(model, listener); + let listener = model.onDidUpdate(_ => this._widget.setItems(model.getElements().map(element => new Item(element, this._instantiationService)))); + this._widget.setItems(model.getElements().map(element => new Item(element, this._instantiationService))); - if (!isCodeEditor(control)) { - return; - } + this._breadcrumbsDisposables.push(model, listener); - let oracle = new class extends Emitter { + // const render = (element: FileElement, target: HTMLElement, disposables: IDisposable[]) => { + // let label = this._instantiationService.createInstance(FileLabel, target, {}); + // label.setFile(element.uri, { fileKind: element.kind, hidePath: true, fileDecorations: { colors: false, badges: false } }); + // disposables.push(label); + // }; - private readonly _listener: IDisposable[] = []; + // let fileItems: RenderedBreadcrumbsItem[] = []; + // let workspace = this._workspaceService.getWorkspaceFolder(uri); + // let path = uri.path; - constructor() { - super(); - DocumentSymbolProviderRegistry.onDidChange(_ => this.fire()); - this._listener.push(control.onDidChangeModel(_ => this._checkModel())); - this._listener.push(control.onDidChangeModelLanguage(_ => this._checkModel())); - this._listener.push(setDisposableTimeout(_ => this._checkModel(), 0)); - } + // while (true) { + // let first = fileItems.length === 0; + // let name = paths.basename(path); + // uri = uri.with({ path }); + // if (workspace && isEqual(workspace.uri, uri, true)) { + // break; + // } + // fileItems.unshift(new RenderedBreadcrumbsItem( + // render, + // new FileElement(name, first ? FileKind.FILE : FileKind.FOLDER, uri), + // !first + // )); + // path = paths.dirname(path); + // if (path === '/') { + // break; + // } + // } - private _checkModel() { - if (control.getModel() && isEqual(control.getModel().uri, input.getResource())) { - this.fire(); - } - } + // this._widget.splice(0, this._widget.items().length, fileItems); - dispose(): void { - dispose(this._listener); - super.dispose(); - } - }; + // let control = this._editorGroup.activeControl.getControl() as ICodeEditor; - this._editorDisposables.push(oracle); + // let model = new EditorBreadcrumbsModel(input.getResource(), isCodeEditor(control) ? control : undefined, this._workspaceService); + // let listener = model.onDidUpdate(_ => console.log(model.getElements())); + // console.log(model.getElements()); + // this._breadcrumbsDisposables.push(model, listener); - oracle.event(async _ => { - let model = await asDisposablePromise(OutlineModel.create(control.getModel(), CancellationToken.None), undefined, this._editorDisposables).promise; - if (!model) { - return; - } - type OutlineItem = OutlineElement | OutlineGroup; + // if (!isCodeEditor(control)) { + // return; + // } - let render = (element: OutlineItem, target: HTMLElement, disposables: IDisposable[]) => { - let label = this._instantiationService.createInstance(FileLabel, target, {}); - if (element instanceof OutlineElement) { - label.setLabel({ name: element.symbol.name }); - } else { - label.setLabel({ name: element.provider.displayName }); - } - disposables.push(label); - }; - let showOutlineForPosition = (position: IPosition) => { - let element = model.getItemEnclosingPosition(position) as TreeElement; - let outlineItems: RenderedBreadcrumbsItem[] = []; - while (element instanceof OutlineGroup || element instanceof OutlineElement) { - outlineItems.unshift(new RenderedBreadcrumbsItem(render, element, !!first(element.children))); - element = element.parent; - } - // todo@joh compare items for equality and only update changed... - this._widget.splice(fileItems.length, this._widget.items().length - fileItems.length, outlineItems); - }; + // let oracle = new class extends Emitter { - showOutlineForPosition(control.getPosition()); - debounceEvent(control.onDidChangeCursorPosition, (last, cur) => cur, 100)(_ => showOutlineForPosition(control.getPosition()), undefined, this._editorDisposables); - }); + // private readonly _listener: IDisposable[] = []; + + // constructor() { + // super(); + // DocumentSymbolProviderRegistry.onDidChange(_ => this.fire()); + // this._listener.push(control.onDidChangeModel(_ => this._checkModel())); + // this._listener.push(control.onDidChangeModelLanguage(_ => this._checkModel())); + // this._listener.push(setDisposableTimeout(_ => this._checkModel(), 0)); + // } + + // private _checkModel() { + // if (control.getModel() && isEqual(control.getModel().uri, input.getResource())) { + // this.fire(); + // } + // } + + // dispose(): void { + // dispose(this._listener); + // super.dispose(); + // } + // }; + + // this._breadcrumbsDisposables.push(oracle); + + // oracle.event(async _ => { + // let model = await asDisposablePromise(OutlineModel.create(control.getModel(), CancellationToken.None), undefined, this._breadcrumbsDisposables).promise; + // if (!model) { + // return; + // } + // type OutlineItem = OutlineElement | OutlineGroup; + + // let render = (element: OutlineItem, target: HTMLElement, disposables: IDisposable[]) => { + // let label = this._instantiationService.createInstance(FileLabel, target, {}); + // if (element instanceof OutlineElement) { + // label.setLabel({ name: element.symbol.name }); + // } else { + // label.setLabel({ name: element.provider.displayName }); + // } + // disposables.push(label); + // }; + + // let showOutlineForPosition = (position: IPosition) => { + // let element = model.getItemEnclosingPosition(position) as TreeElement; + // let outlineItems: RenderedBreadcrumbsItem[] = []; + // while (element instanceof OutlineGroup || element instanceof OutlineElement) { + // outlineItems.unshift(new RenderedBreadcrumbsItem(render, element, !!first(element.children))); + // element = element.parent; + // } + // // todo@joh compare items for equality and only update changed... + // this._widget.splice(fileItems.length, this._widget.items().length - fileItems.length, outlineItems); + // }; + + // showOutlineForPosition(control.getPosition()); + // debounceEvent(control.onDidChangeCursorPosition, (last, cur) => cur, 100)(_ => showOutlineForPosition(control.getPosition()), undefined, this._breadcrumbsDisposables); + // }); } closeEditor(input: EditorInput): void { @@ -244,56 +285,56 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { } } - private _onDidSelectItem(item: RenderedBreadcrumbsItem): void { + private _onDidSelectItem(item: Item): void { this._editorGroup.focus(); - let ctor: IConstructorSignature2; - let input: any; - if (item.element instanceof FileElement) { - ctor = BreadcrumbsFilePicker; - input = dirname(item.element.uri); - } else { - ctor = BreadcrumbsOutlinePicker; - input = item.element.parent; - } + // let ctor: IConstructorSignature2; + // let input: any; + // if (item.element instanceof FileElement) { + // ctor = BreadcrumbsFilePicker; + // input = dirname(item.element.uri); + // } else { + // ctor = BreadcrumbsOutlinePicker; + // input = item.element.parent; + // } - this._contextViewService.showContextView({ - getAnchor() { - return item.node; - }, - render: (container: HTMLElement) => { - dom.addClasses(container, 'show-file-icons'); - let res = this._instantiationService.createInstance(ctor, container, input); - res.layout({ width: 250, height: 300 }); - res.onDidPickElement(data => { - this._contextViewService.hideContextView(); - this._widget.select(undefined); - if (!data) { - return; - } - if (URI.isUri(data)) { - // open new editor - this._editorService.openEditor({ resource: data }); + // this._contextViewService.showContextView({ + // getAnchor() { + // return item.node; + // }, + // render: (container: HTMLElement) => { + // dom.addClasses(container, 'show-file-icons'); + // let res = this._instantiationService.createInstance(ctor, container, input); + // res.layout({ width: 250, height: 300 }); + // res.onDidPickElement(data => { + // this._contextViewService.hideContextView(); + // this._widget.select(undefined); + // if (!data) { + // return; + // } + // if (URI.isUri(data)) { + // // open new editor + // this._editorService.openEditor({ resource: data }); - } else if (data instanceof OutlineElement) { + // } else if (data instanceof OutlineElement) { - let resource: URI; - let candidate = data.parent; - while (candidate) { - if (candidate instanceof OutlineModel) { - resource = candidate.textModel.uri; - break; - } - candidate = candidate.parent; - } + // let resource: URI; + // let candidate = data.parent; + // while (candidate) { + // if (candidate instanceof OutlineModel) { + // resource = candidate.textModel.uri; + // break; + // } + // candidate = candidate.parent; + // } - this._editorService.openEditor({ resource, options: { selection: Range.collapseToStart(data.symbol.selectionRange) } }); + // this._editorService.openEditor({ resource, options: { selection: Range.collapseToStart(data.symbol.selectionRange) } }); - } - }); - return res; - }, - }); + // } + // }); + // return res; + // }, + // }); } } diff --git a/src/vs/workbench/browser/parts/editor/editorBreadcrumbsModel.ts b/src/vs/workbench/browser/parts/editor/editorBreadcrumbsModel.ts index cda2c2252fd..913175259ac 100644 --- a/src/vs/workbench/browser/parts/editor/editorBreadcrumbsModel.ts +++ b/src/vs/workbench/browser/parts/editor/editorBreadcrumbsModel.ts @@ -140,7 +140,7 @@ export class EditorBreadcrumbsModel { } item = parent; } - return chain; + return chain.reverse(); } private _updateOutlineElements(elements: (OutlineGroup | OutlineElement)[]): void { From 5744e8180aa53286e373a36e1388bf2f7554dcbf Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 5 Jul 2018 12:55:56 +0200 Subject: [PATCH 195/283] Fix #49708 --- .../common/configurationModels.ts | 25 +++++++++++++------ .../test/common/configurationModels.test.ts | 11 +++++++- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/services/configuration/common/configurationModels.ts b/src/vs/workbench/services/configuration/common/configurationModels.ts index 0d0fda5d2c1..68d7dc962d3 100644 --- a/src/vs/workbench/services/configuration/common/configurationModels.ts +++ b/src/vs/workbench/services/configuration/common/configurationModels.ts @@ -8,7 +8,7 @@ import { equals } from 'vs/base/common/objects'; import { compare, toValuesTree, IConfigurationChangeEvent, ConfigurationTarget, IConfigurationModel, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; import { Configuration as BaseConfiguration, ConfigurationModelParser, ConfigurationChangeEvent, ConfigurationModel, AbstractConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IConfigurationRegistry, IConfigurationPropertySchema, Extensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, IConfigurationPropertySchema, Extensions, ConfigurationScope, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; import { IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; import { Workspace } from 'vs/platform/workspace/common/workspace'; import { ResourceMap } from 'vs/base/common/map'; @@ -101,18 +101,27 @@ export class FolderSettingsModelParser extends ConfigurationModelParser { } private parseWorkspaceSettings(rawSettings: any): void { - const rawWorkspaceSettings = {}; const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); - for (let key in rawSettings) { - const scope = this.getScope(key, configurationProperties); - if (this.scopes.indexOf(scope) !== -1) { - rawWorkspaceSettings[key] = rawSettings[key]; - } - } + const rawWorkspaceSettings = this.filterByScope(rawSettings, configurationProperties, true); const configurationModel = this.parseRaw(rawWorkspaceSettings); this._settingsModel = new ConfigurationModel(configurationModel.contents, configurationModel.keys, configurationModel.overrides); } + private filterByScope(properties: {}, configurationProperties: { [qualifiedKey: string]: IConfigurationPropertySchema }, filterOverriddenProperties: boolean): {} { + const result = {}; + for (let key in properties) { + if (OVERRIDE_PROPERTY_PATTERN.test(key) && filterOverriddenProperties) { + result[key] = this.filterByScope(properties[key], configurationProperties, false); + } else { + const scope = this.getScope(key, configurationProperties); + if (this.scopes.indexOf(scope) !== -1) { + result[key] = properties[key]; + } + } + } + return result; + } + private getScope(key: string, configurationProperties: { [qualifiedKey: string]: IConfigurationPropertySchema }): ConfigurationScope { const propertySchema = configurationProperties[key]; return propertySchema ? propertySchema.scope : ConfigurationScope.WINDOW; diff --git a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts index 2a397b9df49..ec8107bed48 100644 --- a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts +++ b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts @@ -30,7 +30,8 @@ suite('FolderSettingsModelParser', () => { 'FolderSettingsModelParser.resource': { 'type': 'string', 'default': 'isSet', - scope: ConfigurationScope.RESOURCE + scope: ConfigurationScope.RESOURCE, + overridable: true }, 'FolderSettingsModelParser.application': { 'type': 'string', @@ -57,6 +58,14 @@ suite('FolderSettingsModelParser', () => { assert.deepEqual(testObject.configurationModel.contents, { 'FolderSettingsModelParser': { 'resource': 'resource' } }); }); + test('parse overridable resource settings', () => { + const testObject = new FolderSettingsModelParser('settings', [ConfigurationScope.RESOURCE]); + + testObject.parse(JSON.stringify({ '[json]': { 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.application': 'executable' } })); + + assert.deepEqual(testObject.configurationModel.overrides, [{ 'contents': { 'FolderSettingsModelParser': { 'resource': 'resource' } }, 'identifiers': ['json'] }]); + }); + test('reprocess folder settings excludes application setting', () => { const testObject = new FolderSettingsModelParser('settings', [ConfigurationScope.RESOURCE, ConfigurationScope.WINDOW]); From 7d9464d3ef22ffbeb6c4dee0241f3a1081dcae06 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 5 Jul 2018 15:07:08 +0200 Subject: [PATCH 196/283] #49708 Fix schema --- .../common/configurationRegistry.ts | 23 +++++++++++++++---- .../node/configurationService.ts | 2 +- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index 716575efa05..ef37ffe80d5 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -34,6 +34,12 @@ export interface IConfigurationRegistry { */ notifyConfigurationSchemaUpdated(configuration: IConfigurationNode): void; + /** + * Event that fires whenver a configuration has been + * registered. + */ + onDidSchemaChange: Event; + /** * Event that fires whenver a configuration has been * registered. @@ -110,6 +116,9 @@ class ConfigurationRegistry implements IConfigurationRegistry { private overrideIdentifiers: string[] = []; private overridePropertyPattern: string; + private readonly _onDidSchemaChange: Emitter = new Emitter(); + readonly onDidSchemaChange: Event = this._onDidSchemaChange.event; + private readonly _onDidRegisterConfiguration: Emitter = new Emitter(); readonly onDidRegisterConfiguration: Event = this._onDidRegisterConfiguration.event; @@ -176,7 +185,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { } private validateAndRegisterProperties(configuration: IConfigurationNode, validate: boolean = true, scope: ConfigurationScope = ConfigurationScope.WINDOW, overridable: boolean = false): string[] { - scope = configuration.scope !== void 0 && configuration.scope !== null ? configuration.scope : scope; + scope = types.isUndefinedOrNull(configuration.scope) ? scope : configuration.scope; overridable = configuration.overridable || overridable; let propertyKeys = []; let properties = configuration.properties; @@ -198,8 +207,11 @@ class ConfigurationRegistry implements IConfigurationRegistry { if (overridable) { property.overridable = true; } - if (property.scope === void 0) { - property.scope = scope; + + if (OVERRIDE_PROPERTY_PATTERN.test(key)) { + property.scope = void 0; // No scope for overridable properties `[${identifier}]` + } else { + property.scope = types.isUndefinedOrNull(property.scope) ? scope : property.scope; } // Add to properties maps @@ -240,7 +252,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { function register(configuration: IConfigurationNode) { let properties = configuration.properties; if (properties) { - for (let key in properties) { + for (const key in properties) { allSettings.properties[key] = properties[key]; switch (properties[key].scope) { case ConfigurationScope.APPLICATION: @@ -261,6 +273,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { } } register(configuration); + this._onDidSchemaChange.fire(); } private updateSchemaForOverrideSettingsConfiguration(configuration: IConfigurationNode): void { @@ -292,6 +305,8 @@ class ConfigurationRegistry implements IConfigurationRegistry { applicationSettings.patternProperties[this.overridePropertyPattern] = patternProperties; windowSettings.patternProperties[this.overridePropertyPattern] = patternProperties; resourceSettings.patternProperties[this.overridePropertyPattern] = patternProperties; + + this._onDidSchemaChange.fire(); } private update(configuration: IConfigurationNode): void { diff --git a/src/vs/workbench/services/configuration/node/configurationService.ts b/src/vs/workbench/services/configuration/node/configurationService.ts index 90f013f6b4a..641b8bdf8d2 100644 --- a/src/vs/workbench/services/configuration/node/configurationService.ts +++ b/src/vs/workbench/services/configuration/node/configurationService.ts @@ -80,7 +80,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat this._register(this.userConfiguration.onDidChangeConfiguration(() => this.onUserConfigurationChanged())); this._register(this.workspaceConfiguration.onDidUpdateConfiguration(() => this.onWorkspaceConfigurationChanged())); - this._register(Registry.as(Extensions.Configuration).onDidRegisterConfiguration(e => this.registerConfigurationSchemas())); + this._register(Registry.as(Extensions.Configuration).onDidSchemaChange(e => this.registerConfigurationSchemas())); this._register(Registry.as(Extensions.Configuration).onDidRegisterConfiguration(configurationProperties => this.onDefaultConfigurationChanged(configurationProperties))); this.workspaceEditingQueue = new Queue(); From 394cd30a2cfed8b04195aceb7be276f32c8f417a Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 5 Jul 2018 15:10:53 +0200 Subject: [PATCH 197/283] add comments --- .../services/configuration/node/configurationService.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/services/configuration/node/configurationService.ts b/src/vs/workbench/services/configuration/node/configurationService.ts index 641b8bdf8d2..f464bea68ba 100644 --- a/src/vs/workbench/services/configuration/node/configurationService.ts +++ b/src/vs/workbench/services/configuration/node/configurationService.ts @@ -295,9 +295,9 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat return this._configuration.keys(); } - initialize(arg: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWindowConfiguration, postInitialisation: () => void = () => null): TPromise { + initialize(arg: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWindowConfiguration, postInitialisationTask: () => void = () => null): TPromise { return this.createWorkspace(arg) - .then(workspace => this.updateWorkspaceAndInitializeConfiguration(workspace, postInitialisation)); + .then(workspace => this.updateWorkspaceAndInitializeConfiguration(workspace, postInitialisationTask)); } acquireFileService(fileService: IFileService): void { @@ -361,7 +361,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat return TPromise.as(new Workspace(id)); } - private updateWorkspaceAndInitializeConfiguration(workspace: Workspace, postInitialisation: () => void): TPromise { + private updateWorkspaceAndInitializeConfiguration(workspace: Workspace, postInitialisationTask: () => void): TPromise { const hasWorkspaceBefore = !!this.workspace; let previousState: WorkbenchState; let previousWorkspacePath: string; @@ -378,7 +378,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat return this.initializeConfiguration().then(() => { - postInitialisation(); + postInitialisationTask(); // Post initialisation task should be run before triggering events. // Trigger changes after configuration initialization so that configuration is up to date. if (hasWorkspaceBefore) { From 4adbb0558c7884ef322ae7ec4ca5dbc47ac77d96 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 5 Jul 2018 15:50:49 +0200 Subject: [PATCH 198/283] add tests for breadcrumbs model --- .../editor/editorBreadcrumbModel.test.ts | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/vs/workbench/test/browser/parts/editor/editorBreadcrumbModel.test.ts diff --git a/src/vs/workbench/test/browser/parts/editor/editorBreadcrumbModel.test.ts b/src/vs/workbench/test/browser/parts/editor/editorBreadcrumbModel.test.ts new file mode 100644 index 00000000000..c0942cd5dc4 --- /dev/null +++ b/src/vs/workbench/test/browser/parts/editor/editorBreadcrumbModel.test.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as assert from 'assert'; +import URI from 'vs/base/common/uri'; +import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { EditorBreadcrumbsModel, FileElement } from 'vs/workbench/browser/parts/editor/editorBreadcrumbsModel'; +import { TestContextService } from 'vs/workbench/test/workbenchTestServices'; + + +suite('Breadcrumb Model', function () { + + const workspaceService = new TestContextService(new Workspace('ffff', 'Test', [new WorkspaceFolder({ uri: URI.parse('foo:/bar/baz/ws'), name: 'ws', index: 0 })])); + + test('only uri, inside workspace', function () { + + let model = new EditorBreadcrumbsModel(URI.parse('foo:/bar/baz/ws/some/path/file.ts'), undefined, workspaceService); + let elements = model.getElements(); + + assert.equal(elements.length, 3); + let [one, two, three] = elements as FileElement[]; + assert.equal(one.isFile, false); + assert.equal(two.isFile, false); + assert.equal(three.isFile, true); + assert.equal(one.uri.toString(), 'foo:/bar/baz/ws/some'); + assert.equal(two.uri.toString(), 'foo:/bar/baz/ws/some/path'); + assert.equal(three.uri.toString(), 'foo:/bar/baz/ws/some/path/file.ts'); + }); + + test('only uri, outside workspace', function () { + + let model = new EditorBreadcrumbsModel(URI.parse('foo:/outside/file.ts'), undefined, workspaceService); + let elements = model.getElements(); + + assert.equal(elements.length, 2); + let [one, two] = elements as FileElement[]; + assert.equal(one.isFile, false); + assert.equal(two.isFile, true); + assert.equal(one.uri.toString(), 'foo:/outside'); + assert.equal(two.uri.toString(), 'foo:/outside/file.ts'); + }); +}); From e283c32b9db5faedfee01e30731edc6f21054bdf Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 5 Jul 2018 15:55:28 +0200 Subject: [PATCH 199/283] fix #52447 --- .../src/singlefolder-tests/workspace.test.ts | 3 +-- .../bulkEdit/electron-browser/bulkEditService.ts | 2 +- .../services/textfile/common/textFileService.ts | 14 ++++++++++++++ .../services/textfile/common/textfiles.ts | 6 ++++++ 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index 9d50cafbb17..ccdfbd8829d 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -689,8 +689,7 @@ suite('workspace-namespace', () => { we = new vscode.WorkspaceEdit(); we.createFile(docUri, { overwrite: true }); assert.ok(await vscode.workspace.applyEdit(we)); - // todo@ben - // assert.equal((await vscode.workspace.openTextDocument(docUri)).getText(), ''); + assert.equal((await vscode.workspace.openTextDocument(docUri)).getText(), ''); }); test('WorkspaceEdit: create & ignoreIfExists', async function () { diff --git a/src/vs/workbench/services/bulkEdit/electron-browser/bulkEditService.ts b/src/vs/workbench/services/bulkEdit/electron-browser/bulkEditService.ts index 7b1ca552400..a23fcba984b 100644 --- a/src/vs/workbench/services/bulkEdit/electron-browser/bulkEditService.ts +++ b/src/vs/workbench/services/bulkEdit/electron-browser/bulkEditService.ts @@ -343,7 +343,7 @@ export class BulkEdit { } else if (edit.newUri && !edit.oldUri) { let ignoreIfExists = edit.options && edit.options.ignoreIfExists; if (!ignoreIfExists || !await this._fileService.existsFile(edit.newUri)) { - await this._fileService.createFile(edit.newUri, undefined, { overwrite }); + await this._textFileService.create(edit.newUri, undefined, { overwrite }); } } } diff --git a/src/vs/workbench/services/textfile/common/textFileService.ts b/src/vs/workbench/services/textfile/common/textFileService.ts index 6a6b4648038..5e1e00594c6 100644 --- a/src/vs/workbench/services/textfile/common/textFileService.ts +++ b/src/vs/workbench/services/textfile/common/textFileService.ts @@ -692,6 +692,20 @@ export abstract class TextFileService extends Disposable implements ITextFileSer }); } + create(resource: URI, contents?: string, options?: { overwrite?: boolean }): TPromise { + return this.fileService.createFile(resource, contents, options).then(() => { + + // If we had an existing model for the given resource, load + // it again to make sure it is up to date with the contents + // we just wrote into the underlying resource. + if (!!this.models.get(resource)) { + return this.models.loadOrCreate(resource, { reload: { async: false } }).then(() => void 0); + } + + return void 0; + }); + } + delete(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): TPromise { const dirtyFiles = this.getDirty().filter(dirty => isEqualOrParent(dirty, resource, !platform.isLinux /* ignorecase */)); diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 716b6c1eb36..737fbaed9c4 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -328,6 +328,12 @@ export interface ITextFileService extends IDisposable { */ revertAll(resources?: URI[], options?: IRevertOptions): TPromise; + /** + * Create a file. If the file exists it will be overwritten with the contents if + * the options enable to overwrite. + */ + create(resource: URI, contents?: string, options?: { overwrite?: boolean }): TPromise; + /** * Delete a file. If the file is dirty, it will get reverted and then deleted from disk. */ From 89ab4395d6fe2ce3dbfbc0194499fffa9b0635ba Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 5 Jul 2018 16:00:33 +0200 Subject: [PATCH 200/283] slightly better fix for #52447 --- .../services/textfile/common/textFileService.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/services/textfile/common/textFileService.ts b/src/vs/workbench/services/textfile/common/textFileService.ts index 5e1e00594c6..f77b7d2ecc1 100644 --- a/src/vs/workbench/services/textfile/common/textFileService.ts +++ b/src/vs/workbench/services/textfile/common/textFileService.ts @@ -693,13 +693,16 @@ export abstract class TextFileService extends Disposable implements ITextFileSer } create(resource: URI, contents?: string, options?: { overwrite?: boolean }): TPromise { + const existingModel = this.models.get(resource); + return this.fileService.createFile(resource, contents, options).then(() => { // If we had an existing model for the given resource, load // it again to make sure it is up to date with the contents - // we just wrote into the underlying resource. - if (!!this.models.get(resource)) { - return this.models.loadOrCreate(resource, { reload: { async: false } }).then(() => void 0); + // we just wrote into the underlying resource by calling + // revert() + if (existingModel && !existingModel.isDisposed()) { + return existingModel.revert(); } return void 0; From 4645522d820b824c3e254033b0417a3571963446 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 5 Jul 2018 16:06:13 +0200 Subject: [PATCH 201/283] enable test (for #52414) --- .../vscode-api-tests/src/singlefolder-tests/workspace.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index ccdfbd8829d..2e9fe0d0bbf 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -613,8 +613,8 @@ suite('workspace-namespace', () => { let newDoc = await vscode.workspace.openTextDocument(newUri); assert.equal(newDoc.getText(), 'HelloFoo'); - // let doc = await vscode.workspace.openTextDocument(docUri); - // assert.equal(doc.getText(), 'Bar'); + let doc = await vscode.workspace.openTextDocument(docUri); + assert.equal(doc.getText(), 'Bar'); }); test('WorkspaceEdit api - after saving a deleted file, it still shows up as deleted. #42667', async function () { From 9db22db1de26f2d74d8bc0563f50a62330c55ad2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 5 Jul 2018 16:18:25 +0200 Subject: [PATCH 202/283] kein bock auf mock --- .../api/mainThreadEditors.test.ts | 28 +++++-------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts index 41120cfed58..0589271ab33 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts @@ -23,7 +23,6 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { TestFileService, TestEditorService, TestEditorGroupsService, TestEnvironmentService, TestContextService } from 'vs/workbench/test/workbenchTestServices'; import { TPromise } from 'vs/base/common/winjs.base'; -import { IFileStat } from 'vs/platform/files/common/files'; import { ResourceTextEdit } from 'vs/editor/common/modes'; import { BulkEditService } from 'vs/workbench/services/bulkEdit/electron-browser/bulkEditService'; import { NullLogService } from 'vs/platform/log/common/log'; @@ -48,16 +47,14 @@ suite('MainThreadEditors', () => { createdResources.clear(); deletedResources.clear(); - const fileService = new class extends TestFileService { - createFile(uri: URI): TPromise { - createdResources.add(uri); - return TPromise.wrap(createMockFileStat(uri)); - } - }; - + const fileService = new TestFileService(); const textFileService = new class extends mock() { isDirty() { return false; } + create(uri: URI, contents?: string, options?: any) { + createdResources.add(uri); + return TPromise.as(void 0); + } delete(resource: URI) { deletedResources.add(resource); return TPromise.as(void 0); @@ -75,7 +72,7 @@ suite('MainThreadEditors', () => { const workbenchEditorService = new TestEditorService(); const editorGroupService = new TestEditorGroupsService(); - const bulkEditService = new BulkEditService(new NullLogService(), modelService, new TestEditorService(), null, fileService, textFileService, TestEnvironmentService, new TestContextService()); + const bulkEditService = new BulkEditService(new NullLogService(), modelService, new TestEditorService(), null, new TestFileService(), textFileService, TestEnvironmentService, new TestContextService()); const rpcProtocol = new TestRPCProtocol(); rpcProtocol.set(ExtHostContext.ExtHostDocuments, new class extends mock() { @@ -132,7 +129,7 @@ suite('MainThreadEditors', () => { }); }); - test(`applyWorkspaceEdit with only resource edit`, () => { + test(`pasero applyWorkspaceEdit with only resource edit`, () => { return editors.$tryApplyWorkspaceEdit({ edits: [ { oldUri: resource, newUri: resource, options: undefined }, @@ -147,14 +144,3 @@ suite('MainThreadEditors', () => { }); }); }); - - -function createMockFileStat(target: URI): IFileStat { - return { - etag: '', - isDirectory: false, - name: target.path, - mtime: 0, - resource: target - }; -} From 0f576d4c532d7766742c5fc910cae02cb0b70555 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 5 Jul 2018 16:36:29 +0200 Subject: [PATCH 203/283] Fix #53489 --- src/vs/workbench/browser/parts/views/views.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index 217543d55d0..612b59f257f 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -421,8 +421,8 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { this.storageService = storageService; this.contextService = contextService; - this._register(this.onDidAdd(() => this.saveVisibilityStates())); - this._register(this.onDidRemove(() => this.saveVisibilityStates())); + this._register(this.onDidAdd(viewDescriptorRefs => this.saveVisibilityStates(viewDescriptorRefs.map(r => r.viewDescriptor)))); + this._register(this.onDidRemove(viewDescriptorRefs => this.saveVisibilityStates(viewDescriptorRefs.map(r => r.viewDescriptor)))); } saveViewsStates(): void { @@ -436,9 +436,9 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { this.storageService.store(this.viewletStateStorageId, JSON.stringify(storedViewsStates), this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? StorageScope.WORKSPACE : StorageScope.GLOBAL); } - private saveVisibilityStates(): void { - const storedViewsVisibilityStates: { id: string, isHidden: boolean }[] = []; - for (const viewDescriptor of this.viewDescriptors) { + private saveVisibilityStates(viewDescriptors: IViewDescriptor[]): void { + const storedViewsVisibilityStates = PersistentContributableViewsModel.loadViewsVisibilityState(this.hiddenViewsStorageId, this.storageService, this.contextService); + for (const viewDescriptor of viewDescriptors) { if (viewDescriptor.canToggleVisibility) { const viewState = this.viewStates.get(viewDescriptor.id); storedViewsVisibilityStates.push({ id: viewDescriptor.id, isHidden: viewState ? !viewState.visible : void 0 }); @@ -450,8 +450,7 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { private static loadViewsStates(viewletStateStorageId: string, hiddenViewsStorageId: string, storageService: IStorageService, contextService: IWorkspaceContextService): Map { const viewStates = new Map(); const storedViewsStates = JSON.parse(storageService.get(viewletStateStorageId, contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? StorageScope.WORKSPACE : StorageScope.GLOBAL, '{}')); - const storedVisibilityStates = >JSON.parse(storageService.get(hiddenViewsStorageId, StorageScope.GLOBAL, '[]')); - const viewsVisibilityStates = <{ id: string, isHidden: boolean }[]>storedVisibilityStates.map(c => typeof c === 'string' /* migration */ ? { id: c, isHidden: true } : c); + const viewsVisibilityStates = PersistentContributableViewsModel.loadViewsVisibilityState(hiddenViewsStorageId, storageService, contextService); for (const { id, isHidden } of viewsVisibilityStates) { const viewState = storedViewsStates[id]; if (viewState) { @@ -469,6 +468,11 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { return viewStates; } + private static loadViewsVisibilityState(hiddenViewsStorageId: string, storageService: IStorageService, contextService: IWorkspaceContextService): { id: string, isHidden: boolean }[] { + const storedVisibilityStates = >JSON.parse(storageService.get(hiddenViewsStorageId, StorageScope.GLOBAL, '[]')); + return <{ id: string, isHidden: boolean }[]>storedVisibilityStates.map(c => typeof c === 'string' /* migration */ ? { id: c, isHidden: true } : c); + } + dispose(): void { this.saveViewsStates(); super.dispose(); From 326d55451dc5c43c5e1bfb9cc14d703fe57dd8e2 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 5 Jul 2018 16:49:53 +0200 Subject: [PATCH 204/283] move height into control --- src/vs/workbench/browser/parts/editor/editor.ts | 1 - src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts | 4 ++++ src/vs/workbench/browser/parts/editor/editorGroupView.ts | 6 +++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index 5581eccbd48..5e63f57bfad 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -20,7 +20,6 @@ import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export const EDITOR_TITLE_HEIGHT = 35; -export const BREAD_CRUMBS_HEIGHT = 25; export const DEFAULT_EDITOR_MIN_DIMENSIONS = new Dimension(220, 70); export const DEFAULT_EDITOR_MAX_DIMENSIONS = new Dimension(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY); diff --git a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts index 4e83cec3f0f..d012ecf2a37 100644 --- a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts @@ -130,6 +130,10 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { this._ckBreadcrumbsVisible.reset(); } + getPreferredHeight(): number { + return 25; + } + layout(dim: dom.Dimension): void { this._domNode.style.width = `${dim.width}px`; this._domNode.style.height = `${dim.height}px`; diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 76b79f57032..39385de58cb 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -34,7 +34,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { RunOnceWorker } from 'vs/base/common/async'; import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch'; import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl'; -import { IEditorGroupsAccessor, IEditorGroupView, IEditorPartOptionsChangeEvent, EDITOR_TITLE_HEIGHT, BREAD_CRUMBS_HEIGHT, getActiveTextEditorOptions, IEditorOpeningEvent } from 'vs/workbench/browser/parts/editor/editor'; +import { IEditorGroupsAccessor, IEditorGroupView, IEditorPartOptionsChangeEvent, EDITOR_TITLE_HEIGHT, getActiveTextEditorOptions, IEditorOpeningEvent } from 'vs/workbench/browser/parts/editor/editor'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { join } from 'vs/base/common/paths'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -1376,8 +1376,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Forward to controls this.titleAreaControl.layout(new Dimension(this.dimension.width, EDITOR_TITLE_HEIGHT)); - this.breadcrumbsControl.layout(new Dimension(this.dimension.width, BREAD_CRUMBS_HEIGHT)); - this.editorControl.layout(new Dimension(this.dimension.width, this.dimension.height - (EDITOR_TITLE_HEIGHT + BREAD_CRUMBS_HEIGHT))); + this.breadcrumbsControl.layout(new Dimension(this.dimension.width, this.breadcrumbsControl.getPreferredHeight())); + this.editorControl.layout(new Dimension(this.dimension.width, this.dimension.height - (EDITOR_TITLE_HEIGHT + this.breadcrumbsControl.getPreferredHeight()))); } toJSON(): ISerializedEditorGroup { From ce694d60efd4644ea63c240a341c57f2e21ebdc3 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 5 Jul 2018 17:13:16 +0200 Subject: [PATCH 205/283] Fix #52220 --- .../extensions/node/extensionsWorkbenchService.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts index 7fee4c8e629..03a9cc7540c 100644 --- a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts @@ -753,7 +753,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, for (const extension of extensions) { let dependents = this.getDependentsAfterDisablement(extension, allExtensions, this.local, enablementState); if (dependents.length) { - return TPromise.wrapError(new Error(this.getDependentsErrorMessage(extension, dependents))); + return TPromise.wrapError(new Error(this.getDependentsErrorMessage(extension, allExtensions, dependents))); } } } @@ -806,7 +806,17 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, }); } - private getDependentsErrorMessage(extension: IExtension, dependents: IExtension[]): string { + private getDependentsErrorMessage(extension: IExtension, allDisabledExtensions: IExtension[], dependents: IExtension[]): string { + for (const e of [extension, ...allDisabledExtensions]) { + let dependentsOfTheExtension = dependents.filter(d => d.dependencies.some(id => areSameExtensions({ id }, e))); + if (dependentsOfTheExtension.length) { + return this.getErrorMessageForDisablingAnExtensionWithDependents(e, dependentsOfTheExtension); + } + } + return ''; + } + + private getErrorMessageForDisablingAnExtensionWithDependents(extension: IExtension, dependents: IExtension[]): string { if (dependents.length === 1) { return nls.localize('singleDependentError', "Cannot disable extension '{0}'. Extension '{1}' depends on this.", extension.displayName, dependents[0].displayName); } From 6fad856f33390a1f5c7b9955b097946a38cd7f87 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 5 Jul 2018 17:27:40 +0200 Subject: [PATCH 206/283] Add telemetry for toggling view visibility --- src/vs/workbench/browser/parts/views/viewsViewlet.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index 66b31e8c237..183b36edfb6 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -334,8 +334,16 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView }); } - private toggleViewVisibility(id: string): void { - this.viewsModel.setVisible(id, !this.viewsModel.isVisible(id)); + private toggleViewVisibility(viewId: string): void { + const visible = !this.viewsModel.isVisible(viewId); + /* __GDPR__ + "views.toggleVisibility" : { + "viewId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "visible": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('views.toggledVisibility', { viewId, visible }); + this.viewsModel.setVisible(viewId, visible); } private saveViewSizes(): void { From acc5bf3ad15c04e82407508621158ef481cacadc Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 5 Jul 2018 17:47:22 +0200 Subject: [PATCH 207/283] :lipstick: --- src/vs/editor/contrib/suggest/suggestModel.ts | 88 ++++++++++--------- 1 file changed, 46 insertions(+), 42 deletions(-) diff --git a/src/vs/editor/contrib/suggest/suggestModel.ts b/src/vs/editor/contrib/suggest/suggestModel.ts index 4e7268f6ee7..aefe330316b 100644 --- a/src/vs/editor/contrib/suggest/suggestModel.ts +++ b/src/vs/editor/contrib/suggest/suggestModel.ts @@ -263,49 +263,53 @@ export class SuggestModel implements IDisposable { if (this._state === State.Idle) { - // trigger 24x7 IntelliSense when idle, enabled, when cursor - // moved RIGHT, and when at a good position - if (this._editor.getConfiguration().contribInfo.quickSuggestions !== false - && (prevSelection.containsRange(this._currentSelection) - || prevSelection.getEndPosition().isBeforeOrEqual(this._currentSelection.getPosition())) - ) { - this.cancel(); - - this._triggerQuickSuggest.cancelAndSet(() => { - if (!LineContext.shouldAutoTrigger(this._editor)) { - return; - } - - const model = this._editor.getModel(); - const pos = this._editor.getPosition(); - if (!model) { - return; - } - // validate enabled now - const { quickSuggestions } = this._editor.getConfiguration().contribInfo; - if (quickSuggestions === false) { - return; - } else if (quickSuggestions === true) { - // all good - } else { - // Check the type of the token that triggered this - model.tokenizeIfCheap(pos.lineNumber); - const lineTokens = model.getLineTokens(pos.lineNumber); - const tokenType = lineTokens.getStandardTokenType(lineTokens.findTokenIndexAtOffset(Math.max(pos.column - 1 - 1, 0))); - const inValidScope = quickSuggestions.other && tokenType === StandardTokenType.Other - || quickSuggestions.comments && tokenType === StandardTokenType.Comment - || quickSuggestions.strings && tokenType === StandardTokenType.String; - - if (!inValidScope) { - return; - } - } - - // we made it till here -> trigger now - this.trigger({ auto: true }); - - }, this._quickSuggestDelay); + if (this._editor.getConfiguration().contribInfo.quickSuggestions === false) { + // not enabled + return; } + + if (!prevSelection.containsRange(this._currentSelection) && !prevSelection.getEndPosition().isBeforeOrEqual(this._currentSelection.getPosition())) { + // cursor didn't move RIGHT + return; + } + + this.cancel(); + + this._triggerQuickSuggest.cancelAndSet(() => { + if (!LineContext.shouldAutoTrigger(this._editor)) { + return; + } + + const model = this._editor.getModel(); + const pos = this._editor.getPosition(); + if (!model) { + return; + } + // validate enabled now + const { quickSuggestions } = this._editor.getConfiguration().contribInfo; + if (quickSuggestions === false) { + return; + } else if (quickSuggestions === true) { + // all good + } else { + // Check the type of the token that triggered this + model.tokenizeIfCheap(pos.lineNumber); + const lineTokens = model.getLineTokens(pos.lineNumber); + const tokenType = lineTokens.getStandardTokenType(lineTokens.findTokenIndexAtOffset(Math.max(pos.column - 1 - 1, 0))); + const inValidScope = quickSuggestions.other && tokenType === StandardTokenType.Other + || quickSuggestions.comments && tokenType === StandardTokenType.Comment + || quickSuggestions.strings && tokenType === StandardTokenType.String; + + if (!inValidScope) { + return; + } + } + + // we made it till here -> trigger now + this.trigger({ auto: true }); + + }, this._quickSuggestDelay); + } } From 096d627d2b3738c4b4471ce03b55d673225339dd Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 5 Jul 2018 09:11:17 -0700 Subject: [PATCH 208/283] Make terminal access APIs stable Fixes #52574 --- src/vs/vscode.d.ts | 11 +++++++++++ src/vs/vscode.proposed.d.ts | 11 ----------- src/vs/workbench/api/node/extHost.api.impl.ts | 6 +++--- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 78fc4ef3556..7e66b3f3a7c 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -5784,6 +5784,17 @@ declare module 'vscode' { */ export const onDidChangeTextEditorViewColumn: Event; + /** + * The currently opened terminals or an empty array. + */ + export const terminals: ReadonlyArray; + + /** + * An [event](#Event) which fires when a terminal has been created, either through the + * [createTerminal](#window.createTerminal) API or commands. + */ + export const onDidOpenTerminal: Event; + /** * An [event](#Event) which fires when a terminal is disposed. */ diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index d378593816e..a04b43ec424 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -615,11 +615,6 @@ declare module 'vscode' { } export namespace window { - /** - * The currently opened terminals or an empty array. - */ - export const terminals: ReadonlyArray; - /** * The currently active terminal or `undefined`. The active terminal is the one that * currently has focus or most recently had focus. @@ -633,12 +628,6 @@ declare module 'vscode' { */ export const onDidChangeActiveTerminal: Event; - /** - * An [event](#Event) which fires when a terminal has been created, either through the - * [createTerminal](#window.createTerminal) API or commands. - */ - export const onDidOpenTerminal: Event; - /** * Create a [TerminalRenderer](#TerminalRenderer). * diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index cea90bb2a5b..9911e27bbe8 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -335,7 +335,7 @@ export function createApiFactory( return proposedApiFunction(extension, extHostTerminalService.activeTerminal); }, get terminals() { - return proposedApiFunction(extension, extHostTerminalService.terminals); + return extHostTerminalService.terminals; }, showTextDocument(documentOrUri: vscode.TextDocument | vscode.Uri, columnOrOptions?: vscode.ViewColumn | vscode.TextDocumentShowOptions, preserveFocus?: boolean): TPromise { let documentPromise: TPromise; @@ -372,9 +372,9 @@ export function createApiFactory( onDidCloseTerminal(listener, thisArg?, disposables?) { return extHostTerminalService.onDidCloseTerminal(listener, thisArg, disposables); }, - onDidOpenTerminal: proposedApiFunction(extension, (listener, thisArg?, disposables?) => { + onDidOpenTerminal(listener, thisArg?, disposables?) { return extHostTerminalService.onDidOpenTerminal(listener, thisArg, disposables); - }), + }, onDidChangeActiveTerminal: proposedApiFunction(extension, (listener, thisArg?, disposables?) => { return extHostTerminalService.onDidChangeActiveTerminal(listener, thisArg, disposables); }), From 2d7a7abb28b63ea1e100d75bc32769a7440886ab Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Thu, 5 Jul 2018 11:05:42 -0700 Subject: [PATCH 209/283] Handle spaces in portable install path on Linux for process explorer calculations, fixes #53166 --- src/vs/base/node/ps.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/base/node/ps.ts b/src/vs/base/node/ps.ts index f0410cc42d4..a7d2d36d0ca 100644 --- a/src/vs/base/node/ps.ts +++ b/src/vs/base/node/ps.ts @@ -206,7 +206,8 @@ export function listProcesses(rootPid: number): Promise { // The cpu usage value reported on Linux is the average over the process lifetime, // recalculate the usage over a one second interval - let cmd = URI.parse(require.toUrl('vs/base/node/cpuUsage.sh')).fsPath; + // JSON.stringify is needed to escape spaces, https://github.com/nodejs/node/issues/6803 + let cmd = JSON.stringify(URI.parse(require.toUrl('vs/base/node/cpuUsage.sh')).fsPath); cmd += ' ' + pids.join(' '); exec(cmd, {}, (err, stdout, stderr) => { From 689141523fec516da14f59f22558b776f6caed5f Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Thu, 5 Jul 2018 14:36:02 -0700 Subject: [PATCH 210/283] Reformat test .asar file, fixes #53194 --- build/gulpfile.hygiene.js | 1 + .../pathCompletionFixtures/src/data/foo.asar | Bin 351 -> 28 bytes 2 files changed, 1 insertion(+) diff --git a/build/gulpfile.hygiene.js b/build/gulpfile.hygiene.js index 6a4cd05d17f..cc637831664 100644 --- a/build/gulpfile.hygiene.js +++ b/build/gulpfile.hygiene.js @@ -103,6 +103,7 @@ const copyrightFilter = [ '!**/*.opts', '!**/*.disabled', '!**/*.code-workspace', + '!**/*.asar', '!build/**/*.init', '!resources/linux/snap/snapcraft.yaml', '!resources/linux/snap/electron-launch', diff --git a/extensions/css-language-features/server/test/pathCompletionFixtures/src/data/foo.asar b/extensions/css-language-features/server/test/pathCompletionFixtures/src/data/foo.asar index adae63e647cb9188d7df4b63c9335b1c9811dbd1..0bf0d7a5033ef4df5b4b9d2ac73719e59cfcc18e 100644 GIT binary patch literal 28 ecmZQ!U|4)!elCX`-W7!L1*`V^LU*(Zh1Qx;KcbW z;V~)p?tmdU;liUSID{Ai0S(I4nX e5mNJH;U_tTFe^qD#Pv_rD|)T1wP4Hjrdt5Bx>(Hs From bd953248ad6a8e2f8896bfb78b3ae40e0fd47426 Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Thu, 5 Jul 2018 15:36:29 -0700 Subject: [PATCH 211/283] Revert "Reformat test .asar file, fixes #53194" This reverts commit 689141523fec516da14f59f22558b776f6caed5f. --- build/gulpfile.hygiene.js | 1 - .../pathCompletionFixtures/src/data/foo.asar | Bin 28 -> 351 bytes 2 files changed, 1 deletion(-) diff --git a/build/gulpfile.hygiene.js b/build/gulpfile.hygiene.js index cc637831664..6a4cd05d17f 100644 --- a/build/gulpfile.hygiene.js +++ b/build/gulpfile.hygiene.js @@ -103,7 +103,6 @@ const copyrightFilter = [ '!**/*.opts', '!**/*.disabled', '!**/*.code-workspace', - '!**/*.asar', '!build/**/*.init', '!resources/linux/snap/snapcraft.yaml', '!resources/linux/snap/electron-launch', diff --git a/extensions/css-language-features/server/test/pathCompletionFixtures/src/data/foo.asar b/extensions/css-language-features/server/test/pathCompletionFixtures/src/data/foo.asar index 0bf0d7a5033ef4df5b4b9d2ac73719e59cfcc18e..adae63e647cb9188d7df4b63c9335b1c9811dbd1 100644 GIT binary patch literal 351 zcmcIfOA5j;5Z!x4)!elCX`-W7!L1*`V^LU*(Zh1Qx;KcbW z;V~)p?tmdU;liUSID{Ai0S(I4nX e5mNJH;U_tTFe^qD#Pv_rD|)T1wP4Hjrdt5Bx>(Hs literal 28 ecmZQ!U| Date: Thu, 5 Jul 2018 22:30:26 -0400 Subject: [PATCH 212/283] Removed OS specific text on 'Open in Terminal' command. #53604 --- .../execution/electron-browser/execution.contribution.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/parts/execution/electron-browser/execution.contribution.ts b/src/vs/workbench/parts/execution/electron-browser/execution.contribution.ts index 3bd97be51bb..00f631a566e 100644 --- a/src/vs/workbench/parts/execution/electron-browser/execution.contribution.ts +++ b/src/vs/workbench/parts/execution/electron-browser/execution.contribution.ts @@ -129,15 +129,13 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: OPEN_NATIVE_CONSOLE_COMMAND_ID, - title: env.isWindows ? nls.localize('globalConsoleActionWin', "Open New Command Prompt") : - nls.localize('globalConsoleActionMacLinux', "Open New Terminal") + title: nls.localize('globalConsoleAction', "Open New Terminal") } }); const openConsoleCommand = { id: OPEN_IN_TERMINAL_COMMAND_ID, - title: env.isWindows ? nls.localize('scopedConsoleActionWin', "Open in Command Prompt") : - nls.localize('scopedConsoleActionMacLinux', "Open in Terminal") + title: nls.localize('scopedConsoleAction', "Open in Terminal") }; MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { group: 'navigation', From ecf81857e3e8bc4c9c12c063d4f737a8cf4e23a3 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 6 Jul 2018 09:27:01 +0200 Subject: [PATCH 213/283] suggest user setup for windows users --- .../electron-browser/update.contribution.ts | 19 ++++--- .../parts/update/electron-browser/update.ts | 54 +++++++++++++++++++ 2 files changed, 67 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/parts/update/electron-browser/update.contribution.ts b/src/vs/workbench/parts/update/electron-browser/update.contribution.ts index 1a2d956f296..a87651e4bfc 100644 --- a/src/vs/workbench/parts/update/electron-browser/update.contribution.ts +++ b/src/vs/workbench/parts/update/electron-browser/update.contribution.ts @@ -13,15 +13,22 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } fr import { IGlobalActivityRegistry, GlobalActivityExtensions } from 'vs/workbench/common/activity'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { ShowCurrentReleaseNotesAction, ProductContribution, UpdateContribution, Win3264BitContribution } from './update'; +import { ShowCurrentReleaseNotesAction, ProductContribution, UpdateContribution, Win3264BitContribution, WinUserSetupContribution } from './update'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import product from 'vs/platform/node/product'; -Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(ProductContribution, LifecyclePhase.Running); +const workbench = Registry.as(WorkbenchExtensions.Workbench); -if (platform.isWindows && process.arch === 'ia32') { - Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(Win3264BitContribution, LifecyclePhase.Running); +workbench.registerWorkbenchContribution(ProductContribution, LifecyclePhase.Running); + +if (platform.isWindows) { + if (process.arch === 'ia32') { + workbench.registerWorkbenchContribution(Win3264BitContribution, LifecyclePhase.Running); + } + + if (product.target !== 'user') { + workbench.registerWorkbenchContribution(WinUserSetupContribution, LifecyclePhase.Running); + } } Registry.as(GlobalActivityExtensions) diff --git a/src/vs/workbench/parts/update/electron-browser/update.ts b/src/vs/workbench/parts/update/electron-browser/update.ts index dd4c68f0af4..110be30861b 100644 --- a/src/vs/workbench/parts/update/electron-browser/update.ts +++ b/src/vs/workbench/parts/update/electron-browser/update.ts @@ -209,6 +209,60 @@ export class Win3264BitContribution implements IWorkbenchContribution { } } +export class WinUserSetupContribution implements IWorkbenchContribution { + + private static readonly KEY = 'update/win32-usersetup'; + + private static readonly STABLE_URL = 'https://vscode-update.azurewebsites.net/latest/win32-x64-user/stable'; + private static readonly STABLE_URL_32BIT = 'https://vscode-update.azurewebsites.net/latest/win32-user/stable'; + private static readonly INSIDER_URL = 'https://vscode-update.azurewebsites.net/latest/win32-x64-user/insider'; + private static readonly INSIDER_URL_32BIT = 'https://vscode-update.azurewebsites.net/latest/win32-user/insider'; + + // TODO@joao this needs to change to the 1.26 release notes + private static readonly READ_MORE = 'https://aka.ms/vscode-win32-user-setup'; + + constructor( + @IStorageService storageService: IStorageService, + @INotificationService notificationService: INotificationService, + @IEnvironmentService environmentService: IEnvironmentService, + @IOpenerService private openerService: IOpenerService + ) { + if (!environmentService.isBuilt || environmentService.disableUpdates) { + return; + } + + const neverShowAgain = new NeverShowAgain(WinUserSetupContribution.KEY, storageService); + + if (!neverShowAgain.shouldShow()) { + return; + } + + const handle = notificationService.prompt( + severity.Info, + nls.localize('usersetup', "We recommend switching to our new User Setup distribution of {0} for Windows! Click [here]({1}) to learn more.", product.nameShort, WinUserSetupContribution.READ_MORE), + [ + { + label: nls.localize('downloadnow', "Download"), + run: () => { + const url = product.quality === 'insider' + ? (process.arch === 'ia32' ? WinUserSetupContribution.INSIDER_URL_32BIT : WinUserSetupContribution.INSIDER_URL) + : (process.arch === 'ia32' ? WinUserSetupContribution.STABLE_URL_32BIT : WinUserSetupContribution.STABLE_URL); + + return this.openerService.open(URI.parse(url)); + } + }, + { + label: nls.localize('neveragain', "Don't Show Again"), + isSecondary: true, + run: () => { + neverShowAgain.action.run(handle); + neverShowAgain.action.dispose(); + } + }] + ); + } +} + class CommandAction extends Action { constructor( From 66d811f8886b6332687d4c10ae1cf22a622c3913 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 6 Jul 2018 09:53:55 +0200 Subject: [PATCH 214/283] darwin cli: delete symlink as admin --- .../cli/electron-browser/cli.contribution.ts | 71 +++++++++++++------ 1 file changed, 50 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/parts/cli/electron-browser/cli.contribution.ts b/src/vs/workbench/parts/cli/electron-browser/cli.contribution.ts index fb2293d8434..d4526043633 100644 --- a/src/vs/workbench/parts/cli/electron-browser/cli.contribution.ts +++ b/src/vs/workbench/parts/cli/electron-browser/cli.contribution.ts @@ -29,12 +29,13 @@ let _source: string = null; function getSource(): string { if (!_source) { const root = URI.parse(require.toUrl('')).fsPath; - _source = path.resolve(root, '..', 'bin', 'code'); + _source = path.resolve(root, '..', 'resources', 'darwin', 'bin', 'code.sh'); } return _source; } function isAvailable(): TPromise { + console.log(getSource()); return pfs.exists(getSource()); } @@ -70,19 +71,16 @@ class InstallAction extends Action { if (!isAvailable || isInstalled) { return TPromise.as(null); } else { - const createSymlink = () => { - return pfs.unlink(this.target) - .then(null, ignore('ENOENT')) - .then(() => pfs.symlink(getSource(), this.target)); - }; + return pfs.unlink(this.target) + .then(null, ignore('ENOENT')) + .then(() => pfs.symlink(getSource(), this.target)) + .then(null, err => { + if (err.code === 'EACCES' || err.code === 'ENOENT') { + return this.createBinFolderAndSymlinkAsAdmin(); + } - return createSymlink().then(null, err => { - if (err.code === 'EACCES' || err.code === 'ENOENT') { - return this.createBinFolderAndSymlink(); - } - - return TPromise.wrapError(err); - }); + return TPromise.wrapError(err); + }); } }) .then(() => { @@ -100,7 +98,7 @@ class InstallAction extends Action { .then(null, ignore('ENOENT', false)); } - private createBinFolderAndSymlink(): TPromise { + private createBinFolderAndSymlinkAsAdmin(): TPromise { return new TPromise((c, e) => { const buttons = [nls.localize('ok', "OK"), nls.localize('cancel2', "Cancel")]; @@ -131,7 +129,8 @@ class UninstallAction extends Action { id: string, label: string, @INotificationService private notificationService: INotificationService, - @ILogService private logService: ILogService + @ILogService private logService: ILogService, + @IDialogService private dialogService: IDialogService ) { super(id, label); } @@ -148,12 +147,42 @@ class UninstallAction extends Action { return undefined; } - return pfs.unlink(this.target) - .then(null, ignore('ENOENT')) - .then(() => { - this.logService.trace('cli#uninstall', this.target); - this.notificationService.info(nls.localize('successFrom', "Shell command '{0}' successfully uninstalled from PATH.", product.applicationName)); - }); + const uninstall = () => { + return pfs.unlink(this.target) + .then(null, ignore('ENOENT')); + }; + + return uninstall().then(null, err => { + if (err.code === 'EACCES') { + return this.deleteSymlinkAsAdmin(); + } + + return TPromise.wrapError(err); + }).then(() => { + this.logService.trace('cli#uninstall', this.target); + this.notificationService.info(nls.localize('successFrom', "Shell command '{0}' successfully uninstalled from PATH.", product.applicationName)); + }); + }); + } + + private deleteSymlinkAsAdmin(): TPromise { + return new TPromise((c, e) => { + const buttons = [nls.localize('ok', "OK"), nls.localize('cancel2', "Cancel")]; + + this.dialogService.show(Severity.Info, nls.localize('warnEscalationUninstall', "Code will now prompt with 'osascript' for Administrator privileges to uninstall the shell command."), buttons, { cancelId: 1 }).then(choice => { + switch (choice) { + case 0 /* OK */: + const command = 'osascript -e "do shell script \\"rm \'' + this.target + '\'\\" with administrator privileges"'; + + nfcall(cp.exec, command, {}) + .then(null, _ => TPromise.wrapError(new Error(nls.localize('cantUninstall', "Unable to uninstall the shell command '{0}'.", this.target)))) + .done(c, e); + break; + case 1 /* Cancel */: + e(new Error(nls.localize('aborted', "Aborted"))); + break; + } + }); }); } } From bc067a81889f68eefcc2295c65e600e195ca97ed Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 6 Jul 2018 10:06:11 +0200 Subject: [PATCH 215/283] disable quick suggestions when in snippet mode, #50776 --- src/vs/editor/common/config/editorOptions.ts | 5 ++++- src/vs/editor/contrib/snippet/snippetController2.ts | 4 ++++ src/vs/editor/contrib/suggest/completionModel.ts | 4 ++-- src/vs/editor/contrib/suggest/suggestModel.ts | 6 ++++++ src/vs/editor/contrib/suggest/test/completionModel.test.ts | 6 +++--- src/vs/editor/contrib/suggest/test/suggestModel.test.ts | 6 ++++-- src/vs/monaco.d.ts | 1 + 7 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 2ea6bc0e971..5e103a91062 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -844,6 +844,7 @@ export interface InternalEditorHoverOptions { export interface InternalSuggestOptions { readonly filterGraceful: boolean; readonly snippets: 'top' | 'bottom' | 'inline' | 'none'; + readonly snippetsPreventQuickSuggestions: boolean; } export interface EditorWrappingInfo { @@ -1751,6 +1752,7 @@ export class EditorOptionsValidator { return { filterGraceful: _boolean(opts.suggest.filterGraceful, defaults.filterGraceful), snippets: _stringSet<'top' | 'bottom' | 'inline' | 'none'>(opts.snippetSuggestions, defaults.snippets, ['top', 'bottom', 'inline', 'none']), + snippetsPreventQuickSuggestions: true, }; } @@ -2470,7 +2472,8 @@ export const EDITOR_DEFAULTS: IValidatedEditorOptions = { suggestLineHeight: 0, suggest: { filterGraceful: true, - snippets: 'inline' + snippets: 'inline', + snippetsPreventQuickSuggestions: true }, selectionHighlight: true, occurrencesHighlight: true, diff --git a/src/vs/editor/contrib/snippet/snippetController2.ts b/src/vs/editor/contrib/snippet/snippetController2.ts index 83abd441a26..3022a612e99 100644 --- a/src/vs/editor/contrib/snippet/snippetController2.ts +++ b/src/vs/editor/contrib/snippet/snippetController2.ts @@ -205,6 +205,10 @@ export class SnippetController2 implements IEditorContribution { this._updateState(); } + isInSnippet(): boolean { + return this._inSnippet.get(); + } + getSessionEnclosingRange(): Range { if (this._session) { return this._session.getEnclosingRange(); diff --git a/src/vs/editor/contrib/suggest/completionModel.ts b/src/vs/editor/contrib/suggest/completionModel.ts index d67493343bf..06208d010d0 100644 --- a/src/vs/editor/contrib/suggest/completionModel.ts +++ b/src/vs/editor/contrib/suggest/completionModel.ts @@ -9,7 +9,7 @@ import { fuzzyScore, fuzzyScoreGracefulAggressive, anyScore } from 'vs/base/comm import { isDisposable } from 'vs/base/common/lifecycle'; import { ISuggestResult, ISuggestSupport } from 'vs/editor/common/modes'; import { ISuggestionItem } from './suggest'; -import { InternalSuggestOptions } from 'vs/editor/common/config/editorOptions'; +import { InternalSuggestOptions, EDITOR_DEFAULTS } from 'vs/editor/common/config/editorOptions'; export interface ICompletionItem extends ISuggestionItem { matches?: number[]; @@ -58,7 +58,7 @@ export class CompletionModel { private _isIncomplete: Set; private _stats: ICompletionStats; - constructor(items: ISuggestionItem[], column: number, lineContext: LineContext, options: InternalSuggestOptions = { filterGraceful: true, snippets: 'inline' }) { + constructor(items: ISuggestionItem[], column: number, lineContext: LineContext, options: InternalSuggestOptions = EDITOR_DEFAULTS.contribInfo.suggest) { this._items = items; this._column = column; this._options = options; diff --git a/src/vs/editor/contrib/suggest/suggestModel.ts b/src/vs/editor/contrib/suggest/suggestModel.ts index aefe330316b..4a427b85dfe 100644 --- a/src/vs/editor/contrib/suggest/suggestModel.ts +++ b/src/vs/editor/contrib/suggest/suggestModel.ts @@ -18,6 +18,7 @@ import { ITextModel, IWordAtPosition } from 'vs/editor/common/model'; import { ISuggestSupport, StandardTokenType, SuggestContext, SuggestRegistry, SuggestTriggerKind } from 'vs/editor/common/modes'; import { CompletionModel } from './completionModel'; import { ISuggestionItem, getSuggestionComparator, provideSuggestionItems, getSnippetSuggestSupport } from './suggest'; +import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; export interface ICancelEvent { readonly retrigger: boolean; @@ -273,6 +274,11 @@ export class SuggestModel implements IDisposable { return; } + if (this._editor.getConfiguration().contribInfo.suggest.snippetsPreventQuickSuggestions && SnippetController2.get(this._editor).isInSnippet()) { + // no quick suggestion when in snippet mode + return; + } + this.cancel(); this._triggerQuickSuggest.cancelAndSet(() => { diff --git a/src/vs/editor/contrib/suggest/test/completionModel.test.ts b/src/vs/editor/contrib/suggest/test/completionModel.test.ts index 2786aef089a..bcb1a675914 100644 --- a/src/vs/editor/contrib/suggest/test/completionModel.test.ts +++ b/src/vs/editor/contrib/suggest/test/completionModel.test.ts @@ -167,7 +167,7 @@ suite('CompletionModel', function () { ], 1, { leadingLineContent: 's', characterCountDelta: 0 - }, { snippets: 'top', filterGraceful: true }); + }, { snippets: 'top', snippetsPreventQuickSuggestions: true, filterGraceful: true }); assert.equal(model.items.length, 2); const [a, b] = model.items; @@ -186,7 +186,7 @@ suite('CompletionModel', function () { ], 1, { leadingLineContent: 's', characterCountDelta: 0 - }, { snippets: 'bottom', filterGraceful: true }); + }, { snippets: 'bottom', snippetsPreventQuickSuggestions: true, filterGraceful: true }); assert.equal(model.items.length, 2); const [a, b] = model.items; @@ -204,7 +204,7 @@ suite('CompletionModel', function () { ], 1, { leadingLineContent: 's', characterCountDelta: 0 - }, { snippets: 'inline', filterGraceful: true }); + }, { snippets: 'inline', snippetsPreventQuickSuggestions: true, filterGraceful: true }); assert.equal(model.items.length, 2); const [a, b] = model.items; diff --git a/src/vs/editor/contrib/suggest/test/suggestModel.test.ts b/src/vs/editor/contrib/suggest/test/suggestModel.test.ts index 69a6c6870e0..d5a5daeae4b 100644 --- a/src/vs/editor/contrib/suggest/test/suggestModel.test.ts +++ b/src/vs/editor/contrib/suggest/test/suggestModel.test.ts @@ -30,13 +30,15 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; function createMockEditor(model: TextModel): TestCodeEditor { - return createTestCodeEditor({ + let editor = createTestCodeEditor({ model: model, serviceCollection: new ServiceCollection( [ITelemetryService, NullTelemetryService], [IStorageService, NullStorageService] - ) + ), }); + editor.registerAndInstantiateContribution(SnippetController2); + return editor; } suite('SuggestModel - Context', function () { diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 2a2ea6db909..1aeacef3598 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3123,6 +3123,7 @@ declare namespace monaco.editor { export interface InternalSuggestOptions { readonly filterGraceful: boolean; readonly snippets: 'top' | 'bottom' | 'inline' | 'none'; + readonly snippetsPreventQuickSuggestions: boolean; } export interface EditorWrappingInfo { From da4dae447a2a4344bfd8a6fccc852d9e477664e7 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 6 Jul 2018 10:15:49 +0200 Subject: [PATCH 216/283] add option `editor.suggest.snippetsPreventQuickSuggestions` #50776 --- src/vs/editor/common/config/commonEditorConfig.ts | 5 +++++ src/vs/editor/common/config/editorOptions.ts | 10 ++++++++-- src/vs/monaco.d.ts | 4 ++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index 1ab88ea05ce..9774ab0405b 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -573,6 +573,11 @@ const editorConfiguration: IConfigurationNode = { default: true, description: nls.localize('suggest.filterGraceful', "Controls whether filtering and sorting suggestions accounts for small typos.") }, + 'editor.suggest.snippetsPreventQuickSuggestions': { + type: 'boolean', + default: true, + description: nls.localize('suggest.snippetsPreventQuickSuggestions', "Control whether an active snippet prevents quick suggestions.") + }, 'editor.selectionHighlight': { 'type': 'boolean', 'default': EDITOR_DEFAULTS.contribInfo.selectionHighlight, diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 5e103a91062..14365ee9925 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -163,6 +163,10 @@ export interface ISuggestOptions { * Enable graceful matching. Defaults to true. */ filterGraceful?: boolean; + /** + * Prevent quick suggestions when a snippet is active. Defaults to true. + */ + snippetsPreventQuickSuggestions?: boolean; } /** @@ -1253,7 +1257,9 @@ export class InternalEditorOptions { } else if (!a || !b) { return false; } else { - return a.filterGraceful === b.filterGraceful && a.snippets === b.snippets; + return a.filterGraceful === b.filterGraceful + && a.snippets === b.snippets + && a.snippetsPreventQuickSuggestions === b.snippetsPreventQuickSuggestions; } } @@ -1752,7 +1758,7 @@ export class EditorOptionsValidator { return { filterGraceful: _boolean(opts.suggest.filterGraceful, defaults.filterGraceful), snippets: _stringSet<'top' | 'bottom' | 'inline' | 'none'>(opts.snippetSuggestions, defaults.snippets, ['top', 'bottom', 'inline', 'none']), - snippetsPreventQuickSuggestions: true, + snippetsPreventQuickSuggestions: _boolean(opts.suggest.snippetsPreventQuickSuggestions, defaults.filterGraceful), }; } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 1aeacef3598..d176c102959 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2513,6 +2513,10 @@ declare namespace monaco.editor { * Enable graceful matching. Defaults to true. */ filterGraceful?: boolean; + /** + * Prevent quick suggestions when a snippet is active. Defaults to true. + */ + snippetsPreventQuickSuggestions?: boolean; } /** From bab31465aeefbe9f4c31f5895b772975478ee2f8 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 6 Jul 2018 10:39:44 +0200 Subject: [PATCH 217/283] debt - use more minimal edits when virtual documents update --- .../mainThreadDocumentContentProviders.ts | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/api/electron-browser/mainThreadDocumentContentProviders.ts b/src/vs/workbench/api/electron-browser/mainThreadDocumentContentProviders.ts index ccf9eeb2637..dfd5a60757e 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDocumentContentProviders.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadDocumentContentProviders.ts @@ -4,17 +4,20 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import URI, { UriComponents } from 'vs/base/common/uri'; +import { onUnexpectedError } from 'vs/base/common/errors'; import { IDisposable } from 'vs/base/common/lifecycle'; +import URI, { UriComponents } from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; -import { ITextModel, DefaultEndOfLine } from 'vs/editor/common/model'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { MainThreadDocumentContentProvidersShape, ExtHostContext, ExtHostDocumentContentProvidersShape, MainContext, IExtHostContext } from '../node/extHost.protocol'; -import { createTextBuffer } from 'vs/editor/common/model/textModel'; -import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { EditOperation } from 'vs/editor/common/core/editOperation'; +import { Range } from 'vs/editor/common/core/range'; +import { ITextModel } from 'vs/editor/common/model'; +import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; import { IModelService } from 'vs/editor/common/services/modelService'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; +import { ExtHostContext, ExtHostDocumentContentProvidersShape, IExtHostContext, MainContext, MainThreadDocumentContentProvidersShape } from '../node/extHost.protocol'; @extHostNamedCustomer(MainContext.MainThreadDocumentContentProviders) export class MainThreadDocumentContentProviders implements MainThreadDocumentContentProvidersShape { @@ -27,6 +30,7 @@ export class MainThreadDocumentContentProviders implements MainThreadDocumentCon @ITextModelService private readonly _textModelResolverService: ITextModelService, @IModeService private readonly _modeService: IModeService, @IModelService private readonly _modelService: IModelService, + @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, @ICodeEditorService codeEditorService: ICodeEditorService ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocumentContentProviders); @@ -67,10 +71,11 @@ export class MainThreadDocumentContentProviders implements MainThreadDocumentCon return; } - const textBuffer = createTextBuffer(value, DefaultEndOfLine.CRLF); - - if (!model.equalsTextBuffer(textBuffer)) { - model.setValueFromTextBuffer(textBuffer); - } + this._editorWorkerService.computeMoreMinimalEdits(model.uri, [{ text: value, range: model.getFullModelRange() }]).then(edits => { + if (edits.length > 0) { + // use the evil-edit as these models show in readonly-editor only + model.applyEdits(edits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text))); + } + }, onUnexpectedError); } } From e16b8f968bc359bf0fd5a901b53070f74c431f1a Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 6 Jul 2018 10:42:59 +0200 Subject: [PATCH 218/283] remove arrays.last --- src/vs/base/browser/ui/tree/treeModel.ts | 6 +++--- src/vs/base/common/arrays.ts | 4 ---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/vs/base/browser/ui/tree/treeModel.ts b/src/vs/base/browser/ui/tree/treeModel.ts index 7acf8a90a4b..8d3c14b727b 100644 --- a/src/vs/base/browser/ui/tree/treeModel.ts +++ b/src/vs/base/browser/ui/tree/treeModel.ts @@ -7,7 +7,6 @@ import { ISpliceable } from 'vs/base/common/sequence'; import { IIterator, map, collect, iter, empty } from 'vs/base/common/iterator'; -import { last } from 'vs/base/common/arrays'; import { Emitter, Event } from 'vs/base/common/event'; export interface ITreeElement { @@ -108,7 +107,8 @@ export class TreeModel { const treeListElementsToInsert: ITreeNode[] = []; const elementsToInsert = getTreeElementIterator(toInsert); const nodesToInsert = collect(map(elementsToInsert, el => treeElementToNode(el, parentNode, visible, treeListElementsToInsert))); - const deletedNodes = parentNode.children.splice(last(location), deleteCount, ...nodesToInsert); + const lastIndex = location[location.length - 1]; + const deletedNodes = parentNode.children.splice(lastIndex, deleteCount, ...nodesToInsert); const visibleDeleteCount = getVisibleCount(deletedNodes); parentNode.visibleCount += getVisibleCount(nodesToInsert) - visibleDeleteCount; @@ -175,7 +175,7 @@ export class TreeModel { private findNode(location: number[]): { node: IMutableTreeNode, listIndex: number, visible: boolean } { const { parentNode, listIndex, visible } = this.findParentNode(location); - const index = last(location); + const index = location[location.length - 1]; if (index < 0 || index > parentNode.children.length) { throw new Error('Invalid tree location'); diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index 98fdb2262ae..8053f635f15 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -24,10 +24,6 @@ export function tail2(arr: T[]): [T[], T] { return [arr.slice(0, arr.length - 1), arr[arr.length - 1]]; } -export function last(arr: T[]): T { - return arr[arr.length - 1]; -} - export function equals(one: ReadonlyArray, other: ReadonlyArray, itemEquals: (a: T, b: T) => boolean = (a, b) => a === b): boolean { if (one.length !== other.length) { return false; From be1ef8a9c533f7b56b9168f336d907051b0bb69f Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 6 Jul 2018 11:32:58 +0200 Subject: [PATCH 219/283] debt - less array creation in stable sort --- src/vs/base/common/arrays.ts | 66 +++++++++++++++----------- src/vs/base/test/common/arrays.test.ts | 5 ++ 2 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index 8053f635f15..0a8cd0b2eb0 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -77,48 +77,56 @@ export function findFirstInSorted(array: T[], p: (x: T) => boolean): number { return low; } +type Compare = (a: T, b: T) => number; + /** * Like `Array#sort` but always stable. Usually runs a little slower `than Array#sort` * so only use this when actually needing stable sort. */ -export function mergeSort(data: T[], compare: (a: T, b: T) => number): T[] { - _divideAndMerge(data, compare); +export function mergeSort(data: T[], compare: Compare): T[] { + _sort(data, compare, 0, data.length - 1, []); return data; } -function _divideAndMerge(data: T[], compare: (a: T, b: T) => number): void { - if (data.length <= 1) { - // sorted - return; +function _merge(a: T[], compare: Compare, lo: number, mid: number, hi: number, aux: T[]): void { + let leftIdx = lo, rightIdx = mid + 1; + for (let i = lo; i <= hi; i++) { + aux[i] = a[i]; } - const p = (data.length / 2) | 0; - const left = data.slice(0, p); - const right = data.slice(p); - - _divideAndMerge(left, compare); - _divideAndMerge(right, compare); - - let leftIdx = 0; - let rightIdx = 0; - let i = 0; - while (leftIdx < left.length && rightIdx < right.length) { - let ret = compare(left[leftIdx], right[rightIdx]); - if (ret <= 0) { - // smaller_equal -> take left to preserve order - data[i++] = left[leftIdx++]; + for (let i = lo; i <= hi; i++) { + if (leftIdx > mid) { + // left side consumed + a[i] = aux[rightIdx++]; + } else if (rightIdx > hi) { + // right side consumed + a[i] = aux[leftIdx++]; + } else if (compare(aux[rightIdx], aux[leftIdx]) < 0) { + // right element is less -> comes first + a[i] = aux[rightIdx++]; } else { - // greater -> take right - data[i++] = right[rightIdx++]; + // left element comes first (less or equal) + a[i] = aux[leftIdx++]; } } - while (leftIdx < left.length) { - data[i++] = left[leftIdx++]; - } - while (rightIdx < right.length) { - data[i++] = right[rightIdx++]; - } } +function _sort(a: T[], compare: Compare, lo: number, hi: number, aux: T[]) { + if (hi <= lo) { + return; + } + let mid = lo + ((hi - lo) / 2) | 0; + _sort(a, compare, lo, mid, aux); + _sort(a, compare, mid + 1, hi, aux); + if (compare(a[mid], a[mid + 1]) <= 0) { + // left and right are sorted and if the last-left element is less + // or equals than the first-right element there is nothing else + // to do + return; + } + _merge(a, compare, lo, mid, hi, aux); +} + + export function groupBy(data: T[], compare: (a: T, b: T) => number): T[][] { const result: T[][] = []; let currentGroup: T[]; diff --git a/src/vs/base/test/common/arrays.test.ts b/src/vs/base/test/common/arrays.test.ts index ee1a6e18b58..cff3a614965 100644 --- a/src/vs/base/test/common/arrays.test.ts +++ b/src/vs/base/test/common/arrays.test.ts @@ -52,6 +52,11 @@ suite('Arrays', () => { assert.deepEqual(data, [1, 2, 3, 4, 5, 6, 7, 8]); }); + test('mergeSort, sorted array', function () { + let data = arrays.mergeSort([1, 2, 3, 4, 5, 6], (a, b) => a - b); + assert.deepEqual(data, [1, 2, 3, 4, 5, 6]); + }); + test('mergeSort, is stable', function () { let numbers = arrays.mergeSort([33, 22, 11, 4, 99, 1], (a, b) => 0); From e045147e4441ef09a5ed071712428c9ef8770c33 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 6 Jul 2018 12:14:28 +0200 Subject: [PATCH 220/283] improve conflicts during git rebase flow --- extensions/git/src/commands.ts | 25 ++++++------- extensions/git/src/git.ts | 48 +++++++++++++++--------- extensions/git/src/repository.ts | 64 ++++++++++++++++++++++++++++---- extensions/git/src/statusbar.ts | 3 +- 4 files changed, 100 insertions(+), 40 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 2d0b52d2bc6..94844436390 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -1273,16 +1273,7 @@ export class CommandCenter { return; } - try { - await choice.run(repository); - } catch (err) { - if (err.gitErrorCode !== GitErrorCodes.Conflict) { - throw err; - } - - const message = localize('merge conflicts', "There are merge conflicts. Resolve them before committing."); - await window.showWarningMessage(message); - } + await choice.run(repository); } @command('git.createTag', { repository: true }) @@ -1655,10 +1646,11 @@ export class CommandCenter { return result.catch(async err => { const options: MessageOptions = { - modal: err.gitErrorCode === GitErrorCodes.DirtyWorkTree + modal: true }; let message: string; + let type: 'error' | 'warning' = 'error'; switch (err.gitErrorCode) { case GitErrorCodes.DirtyWorkTree: @@ -1667,6 +1659,11 @@ export class CommandCenter { case GitErrorCodes.PushRejected: message = localize('cant push', "Can't push refs to remote. Try running 'Pull' first to integrate your changes."); break; + case GitErrorCodes.Conflict: + message = localize('merge conflicts', "There are merge conflicts. Resolve them before committing."); + type = 'warning'; + options.modal = false; + break; default: const hint = (err.stderr || err.message || String(err)) .replace(/^error: /mi, '') @@ -1687,11 +1684,11 @@ export class CommandCenter { return; } - options.modal = true; - const outputChannel = this.outputChannel as OutputChannel; const openOutputChannelChoice = localize('open git log', "Open Git Log"); - const choice = await window.showErrorMessage(message, options, openOutputChannelChoice); + const choice = type === 'error' + ? await window.showErrorMessage(message, options, openOutputChannelChoice) + : await window.showWarningMessage(message, options, openOutputChannelChoice); if (choice === openOutputChannelChoice) { outputChannel.show(); diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 71a8cbc9014..7f65a849ecc 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -336,7 +336,7 @@ export const GitErrorCodes = { NoStashFound: 'NoStashFound', LocalChangesOverwritten: 'LocalChangesOverwritten', NoUpstreamBranch: 'NoUpstreamBranch', - IsInSubmodule: 'IsInSubmodule' + IsInSubmodule: 'IsInSubmodule', }; function getGitErrorCode(stderr: string): string | undefined { @@ -876,27 +876,41 @@ export class Repository { try { await this.run(args, { input: message || '' }); } catch (commitErr) { - if (/not possible because you have unmerged files/.test(commitErr.stderr || '')) { - commitErr.gitErrorCode = GitErrorCodes.UnmergedChanges; - throw commitErr; - } + await this.handleCommitError(commitErr); + } + } - try { - await this.run(['config', '--get-all', 'user.name']); - } catch (err) { - err.gitErrorCode = GitErrorCodes.NoUserNameConfigured; - throw err; - } + async rebaseContinue(): Promise { + const args = ['rebase', '--continue']; - try { - await this.run(['config', '--get-all', 'user.email']); - } catch (err) { - err.gitErrorCode = GitErrorCodes.NoUserEmailConfigured; - throw err; - } + try { + await this.run(args); + } catch (commitErr) { + await this.handleCommitError(commitErr); + } + } + private async handleCommitError(commitErr: any): Promise { + if (/not possible because you have unmerged files/.test(commitErr.stderr || '')) { + commitErr.gitErrorCode = GitErrorCodes.UnmergedChanges; throw commitErr; } + + try { + await this.run(['config', '--get-all', 'user.name']); + } catch (err) { + err.gitErrorCode = GitErrorCodes.NoUserNameConfigured; + throw err; + } + + try { + await this.run(['config', '--get-all', 'user.email']); + } catch (err) { + err.gitErrorCode = GitErrorCodes.NoUserEmailConfigured; + throw err; + } + + throw commitErr; } async branch(name: string, checkout: boolean): Promise { diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 5c5fd6814a3..6051cc3cb48 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -299,7 +299,8 @@ export enum Operation { Stash = 'Stash', CheckIgnore = 'CheckIgnore', LSTree = 'LSTree', - SubmoduleUpdate = 'SubmoduleUpdate' + SubmoduleUpdate = 'SubmoduleUpdate', + RebaseContinue = 'RebaseContinue', } function isReadOnly(operation: Operation): boolean { @@ -479,6 +480,22 @@ export class Repository implements Disposable { return this._submodules; } + private _rebaseCommit: Commit | undefined = undefined; + + set rebaseCommit(rebaseCommit: Commit | undefined) { + if (this._rebaseCommit && !rebaseCommit) { + this.inputBox.value = ''; + } else if (rebaseCommit && (!this._rebaseCommit || this._rebaseCommit.hash !== rebaseCommit.hash)) { + this.inputBox.value = rebaseCommit.message; + } + + this._rebaseCommit = rebaseCommit; + } + + get rebaseCommit(): Commit | undefined { + return this._rebaseCommit; + } + private _operations = new OperationsImpl(); get operations(): Operations { return this._operations; } @@ -553,6 +570,15 @@ export class Repository implements Disposable { } validateInput(text: string, position: number): SourceControlInputBoxValidation | undefined { + if (this.rebaseCommit) { + if (this.rebaseCommit.message !== text) { + return { + message: localize('commit in rebase', "It's not possible to change the commit message in the middle of a rebase. Please complete the rebase operation and use interactive rebase instead."), + type: SourceControlInputBoxValidationType.Warning + }; + } + } + const config = workspace.getConfiguration('git'); const setting = config.get<'always' | 'warn' | 'off'>('inputValidation'); @@ -636,13 +662,23 @@ export class Repository implements Disposable { } async commit(message: string, opts: CommitOptions = Object.create(null)): Promise { - await this.run(Operation.Commit, async () => { - if (opts.all) { - await this.repository.add([]); - } + if (this.rebaseCommit) { + await this.run(Operation.RebaseContinue, async () => { + if (opts.all) { + await this.repository.add([]); + } - await this.repository.commit(message, opts); - }); + await this.repository.rebaseContinue(); + }); + } else { + await this.run(Operation.Commit, async () => { + if (opts.all) { + await this.repository.add([]); + } + + await this.repository.commit(message, opts); + }); + } } async clean(resources: Uri[]): Promise { @@ -1025,12 +1061,13 @@ export class Repository implements Disposable { // noop } - const [refs, remotes, submodules] = await Promise.all([this.repository.getRefs(), this.repository.getRemotes(), this.repository.getSubmodules()]); + const [refs, remotes, submodules, rebaseCommit] = await Promise.all([this.repository.getRefs(), this.repository.getRemotes(), this.repository.getSubmodules(), this.getRebaseCommit()]); this._HEAD = HEAD; this._refs = refs; this._remotes = remotes; this._submodules = submodules; + this.rebaseCommit = rebaseCommit; const index: Resource[] = []; const workingTree: Resource[] = []; @@ -1089,6 +1126,17 @@ export class Repository implements Disposable { this._onDidChangeStatus.fire(); } + private async getRebaseCommit(): Promise { + const rebaseHeadPath = path.join(this.repository.root, '.git', 'REBASE_HEAD'); + + try { + const rebaseHead = await new Promise((c, e) => fs.readFile(rebaseHeadPath, 'utf8', (err, result) => err ? e(err) : c(result))); + return await this.getCommit(rebaseHead.trim()); + } catch (err) { + return undefined; + } + } + private onFSChange(uri: Uri): void { const config = workspace.getConfiguration('git'); const autorefresh = config.get('autorefresh'); diff --git a/extensions/git/src/statusbar.ts b/extensions/git/src/statusbar.ts index be97b3355cb..979c961f64d 100644 --- a/extensions/git/src/statusbar.ts +++ b/extensions/git/src/statusbar.ts @@ -24,7 +24,8 @@ class CheckoutStatusBar { } get command(): Command | undefined { - const title = `$(git-branch) ${this.repository.headLabel}`; + const rebasing = !!this.repository.rebaseCommit; + const title = `$(git-branch) ${this.repository.headLabel}${rebasing ? ` (${localize('rebasing', 'Rebasing')})` : ''}`; return { command: 'git.checkout', From c0cfa06328052eae3a95e183ea03f5f82b874a23 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 6 Jul 2018 12:20:56 +0200 Subject: [PATCH 221/283] fix #44106 --- extensions/git/src/commands.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 94844436390..518f1a9f46b 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -667,12 +667,19 @@ export class CommandCenter { @command('git.stageAll', { repository: true }) async stageAll(repository: Repository): Promise { const resources = repository.mergeGroup.resourceStates.filter(s => s instanceof Resource) as Resource[]; - const mergeConflicts = resources.filter(s => s.resourceGroupType === ResourceGroupType.Merge); + const merge = resources.filter(s => s.resourceGroupType === ResourceGroupType.Merge); + const bothModified = merge.filter(s => s.type === Status.BOTH_MODIFIED); + const promises = bothModified.map(s => grep(s.resourceUri.fsPath, /^<{7}|^={7}|^>{7}/)); + const unresolvedBothModified = await Promise.all(promises); + const unresolvedConflicts = [ + ...merge.filter(s => s.type !== Status.BOTH_MODIFIED), + ...bothModified.filter((s, i) => unresolvedBothModified[i]) + ]; - if (mergeConflicts.length > 0) { - const message = mergeConflicts.length > 1 - ? localize('confirm stage files with merge conflicts', "Are you sure you want to stage {0} files with merge conflicts?", mergeConflicts.length) - : localize('confirm stage file with merge conflicts', "Are you sure you want to stage {0} with merge conflicts?", path.basename(mergeConflicts[0].resourceUri.fsPath)); + if (unresolvedConflicts.length > 0) { + const message = unresolvedConflicts.length > 1 + ? localize('confirm stage files with merge conflicts', "Are you sure you want to stage {0} files with merge conflicts?", merge.length) + : localize('confirm stage file with merge conflicts', "Are you sure you want to stage {0} with merge conflicts?", path.basename(merge[0].resourceUri.fsPath)); const yes = localize('yes', "Yes"); const pick = await window.showWarningMessage(message, { modal: true }, yes); From f950abaa810d6654170b9240136fbd96c1005054 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 6 Jul 2018 12:34:38 +0200 Subject: [PATCH 222/283] git: extract categorizeResourceByResolution --- extensions/git/src/commands.ts | 50 +++++++++++++++++----------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 4091728ee59..0e697949e70 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -137,6 +137,22 @@ const ImageMimetypes = [ 'image/bmp' ]; +async function categorizeResourceByResolution(resources: Resource[]): Promise<{ merge: Resource[], resolved: Resource[], unresolved: Resource[] }> { + const selection = resources.filter(s => s instanceof Resource) as Resource[]; + const merge = selection.filter(s => s.resourceGroupType === ResourceGroupType.Merge); + const isBothAddedOrModified = (s: Resource) => s.type === Status.BOTH_MODIFIED || s.type === Status.BOTH_ADDED; + const possibleUnresolved = merge.filter(isBothAddedOrModified); + const promises = possibleUnresolved.map(s => grep(s.resourceUri.fsPath, /^<{7}|^={7}|^>{7}/)); + const unresolvedBothModified = await Promise.all(promises); + const resolved = possibleUnresolved.filter((s, i) => !unresolvedBothModified[i]); + const unresolved = [ + ...merge.filter(s => !isBothAddedOrModified(s)), + ...possibleUnresolved.filter((s, i) => unresolvedBothModified[i]) + ]; + + return { merge, resolved, unresolved }; +} + export class CommandCenter { private disposables: Disposable[]; @@ -629,21 +645,12 @@ export class CommandCenter { } const selection = resourceStates.filter(s => s instanceof Resource) as Resource[]; - const merge = selection.filter(s => s.resourceGroupType === ResourceGroupType.Merge); - const isBothAddedOrModified = (s: Resource) => s.type === Status.BOTH_MODIFIED || s.type === Status.BOTH_ADDED; - const bothModified = merge.filter(isBothAddedOrModified); - const promises = bothModified.map(s => grep(s.resourceUri.fsPath, /^<{7}|^={7}|^>{7}/)); - const unresolvedBothModified = await Promise.all(promises); - const resolvedConflicts = bothModified.filter((s, i) => !unresolvedBothModified[i]); - const unresolvedConflicts = [ - ...merge.filter(s => !isBothAddedOrModified(s)), - ...bothModified.filter((s, i) => unresolvedBothModified[i]) - ]; + const { resolved, unresolved } = await categorizeResourceByResolution(selection); - if (unresolvedConflicts.length > 0) { - const message = unresolvedConflicts.length > 1 - ? localize('confirm stage files with merge conflicts', "Are you sure you want to stage {0} files with merge conflicts?", unresolvedConflicts.length) - : localize('confirm stage file with merge conflicts', "Are you sure you want to stage {0} with merge conflicts?", path.basename(unresolvedConflicts[0].resourceUri.fsPath)); + if (unresolved.length > 0) { + const message = unresolved.length > 1 + ? localize('confirm stage files with merge conflicts', "Are you sure you want to stage {0} files with merge conflicts?", unresolved.length) + : localize('confirm stage file with merge conflicts', "Are you sure you want to stage {0} with merge conflicts?", path.basename(unresolved[0].resourceUri.fsPath)); const yes = localize('yes', "Yes"); const pick = await window.showWarningMessage(message, { modal: true }, yes); @@ -654,7 +661,7 @@ export class CommandCenter { } const workingTree = selection.filter(s => s.resourceGroupType === ResourceGroupType.WorkingTree); - const scmResources = [...workingTree, ...resolvedConflicts, ...unresolvedConflicts]; + const scmResources = [...workingTree, ...resolved, ...unresolved]; this.outputChannel.appendLine(`git.stage.scmResources ${scmResources.length}`); if (!scmResources.length) { @@ -668,17 +675,10 @@ export class CommandCenter { @command('git.stageAll', { repository: true }) async stageAll(repository: Repository): Promise { const resources = repository.mergeGroup.resourceStates.filter(s => s instanceof Resource) as Resource[]; - const merge = resources.filter(s => s.resourceGroupType === ResourceGroupType.Merge); - const bothModified = merge.filter(s => s.type === Status.BOTH_MODIFIED); - const promises = bothModified.map(s => grep(s.resourceUri.fsPath, /^<{7}|^={7}|^>{7}/)); - const unresolvedBothModified = await Promise.all(promises); - const unresolvedConflicts = [ - ...merge.filter(s => s.type !== Status.BOTH_MODIFIED), - ...bothModified.filter((s, i) => unresolvedBothModified[i]) - ]; + const { merge, unresolved } = await categorizeResourceByResolution(resources); - if (unresolvedConflicts.length > 0) { - const message = unresolvedConflicts.length > 1 + if (unresolved.length > 0) { + const message = unresolved.length > 1 ? localize('confirm stage files with merge conflicts', "Are you sure you want to stage {0} files with merge conflicts?", merge.length) : localize('confirm stage file with merge conflicts', "Are you sure you want to stage {0} with merge conflicts?", path.basename(merge[0].resourceUri.fsPath)); From b0ef947992f10643e5d8f3e854adaaa3c6865681 Mon Sep 17 00:00:00 2001 From: Dirk Baeumer Date: Fri, 6 Jul 2018 12:37:23 +0200 Subject: [PATCH 223/283] Addressed #53673: Cake Extension for VS Code no longer fully working in 1.25.0 release --- .../workbench/api/electron-browser/mainThreadTask.ts | 5 +++-- src/vs/workbench/api/node/extHost.protocol.ts | 2 +- src/vs/workbench/api/node/extHostTask.ts | 12 ++++++++++-- src/vs/workbench/parts/tasks/common/taskService.ts | 3 ++- .../tasks/electron-browser/task.contribution.ts | 7 +++++-- 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/api/electron-browser/mainThreadTask.ts b/src/vs/workbench/api/electron-browser/mainThreadTask.ts index 45c911be480..6658414014b 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadTask.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadTask.ts @@ -11,6 +11,7 @@ import * as Objects from 'vs/base/common/objects'; import { TPromise } from 'vs/base/common/winjs.base'; import * as Types from 'vs/base/common/types'; import * as Platform from 'vs/base/common/platform'; +import { IStringDictionary } from 'vs/base/common/collections'; import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; @@ -386,8 +387,8 @@ export class MainThreadTask implements MainThreadTaskShape { public $registerTaskProvider(handle: number): TPromise { this._taskService.registerTaskProvider(handle, { - provideTasks: () => { - return this._proxy.$provideTasks(handle).then((value) => { + provideTasks: (validTypes: IStringDictionary) => { + return this._proxy.$provideTasks(handle, validTypes).then((value) => { let tasks: Task[] = []; for (let task of value.tasks) { let taskTransfer = task._source as any as ExtensionTaskSourceTransfer; diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index c124332150f..e78edaceeea 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -878,7 +878,7 @@ export interface ExtHostSCMShape { } export interface ExtHostTaskShape { - $provideTasks(handle: number): TPromise; + $provideTasks(handle: number, validTypes: { [key: string]: boolean; }): TPromise; $onDidStartTask(execution: TaskExecutionDTO): void; $onDidStartTaskProcess(value: TaskProcessStartedDTO): void; $onDidEndTaskProcess(value: TaskProcessEndedDTO): void; diff --git a/src/vs/workbench/api/node/extHostTask.ts b/src/vs/workbench/api/node/extHostTask.ts index 386a0ed6580..c8a40bb16a8 100644 --- a/src/vs/workbench/api/node/extHostTask.ts +++ b/src/vs/workbench/api/node/extHostTask.ts @@ -860,15 +860,23 @@ export class ExtHostTask implements ExtHostTaskShape { } } - public $provideTasks(handle: number): TPromise { + public $provideTasks(handle: number, validTypes: { [key: string]: boolean; }): TPromise { let handler = this._handlers.get(handle); if (!handler) { return TPromise.wrapError(new Error('no handler found')); } return asWinJsPromise(token => handler.provider.provideTasks(token)).then(value => { + let sanitized: vscode.Task[] = []; + for (let task of value) { + if (task.definition && validTypes[task.definition.type] === true) { + sanitized.push(task); + } else { + console.error(`Dropping task [${task.source}, ${task.name}]. Its type is not known to the system.`); + } + } let workspaceFolders = this._workspaceService.getWorkspaceFolders(); return { - tasks: Tasks.from(value, workspaceFolders && workspaceFolders.length > 0 ? workspaceFolders[0] : undefined, handler.extension), + tasks: Tasks.from(sanitized, workspaceFolders && workspaceFolders.length > 0 ? workspaceFolders[0] : undefined, handler.extension), extension: handler.extension }; }); diff --git a/src/vs/workbench/parts/tasks/common/taskService.ts b/src/vs/workbench/parts/tasks/common/taskService.ts index 97c8695f834..50e7dadcfe5 100644 --- a/src/vs/workbench/parts/tasks/common/taskService.ts +++ b/src/vs/workbench/parts/tasks/common/taskService.ts @@ -13,13 +13,14 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { Task, ContributedTask, CustomTask, TaskSet, TaskSorter, TaskEvent, TaskIdentifier } from 'vs/workbench/parts/tasks/common/tasks'; import { ITaskSummary, TaskTerminateResponse, TaskSystemInfo } from 'vs/workbench/parts/tasks/common/taskSystem'; +import { IStringDictionary } from 'vs/base/common/collections'; export { ITaskSummary, Task, TaskTerminateResponse }; export const ITaskService = createDecorator('taskService'); export interface ITaskProvider { - provideTasks(): TPromise; + provideTasks(validTypes: IStringDictionary): TPromise; } export interface RunOptions { 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 70ebf3100a0..1f274236426 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts @@ -1298,7 +1298,9 @@ class TaskService implements ITaskService { } private getGroupedTasks(): TPromise { - return this.extensionService.activateByEvent('onCommand:workbench.action.tasks.runTask').then(() => { + return TPromise.join([this.extensionService.activateByEvent('onCommand:workbench.action.tasks.runTask'), TaskDefinitionRegistry.onReady()]).then(() => { + let validTypes: IStringDictionary = Object.create(null); + TaskDefinitionRegistry.all().forEach(definition => validTypes[definition.taskType] = true); return new TPromise((resolve, reject) => { let result: TaskSet[] = []; let counter: number = 0; @@ -1327,7 +1329,7 @@ class TaskService implements ITaskService { if (this.schemaVersion === JsonSchemaVersion.V2_0_0 && this._providers.size > 0) { this._providers.forEach((provider) => { counter++; - provider.provideTasks().done(done, error); + provider.provideTasks(validTypes).done(done, error); }); } else { resolve(result); @@ -2485,6 +2487,7 @@ let schema: IJSONSchema = { import schemaVersion1 from './jsonSchema_v1'; import schemaVersion2 from './jsonSchema_v2'; +import { TaskDefinitionRegistry } from 'vs/workbench/parts/tasks/common/taskDefinitionRegistry'; schema.definitions = { ...schemaVersion1.definitions, ...schemaVersion2.definitions, From 875eab9b7b3babd63db8eec09f9424e2473195d0 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 6 Jul 2018 12:43:22 +0200 Subject: [PATCH 224/283] update docs --- src/vs/vscode.d.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index fb96d7b0d88..dc0b0fbc616 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -6168,18 +6168,12 @@ declare module 'vscode' { * be able to handle uris which are directed to the extension itself. A uri must respect * the following rules: * - * - The uri-scheme must be the product name (eg. `vscode`); - * - The uri-authority must be the extension id (eg. `vscode-git`); + * - The uri-scheme must be the product name; + * - The uri-authority must be the extension id (eg. `my.extension`); * - The uri-path, -query and -fragment parts are arbitrary. * - * For example, if the `vscode.git` extension registers a uri handler, it will only - * be allowed to handle uris with the prefix `{product}://vscode.git`. All the following - * uris are examples: - * - * - `vscode://vscode.git` - * - `vscode://vscode.git/` - * - `vscode://vscode.git/status` - * - `vscode://vscode.git/clone?when=now` + * For example, if the `my.extension` extension registers a uri handler, it will only + * be allowed to handle uris with the prefix `product-name://my.extension`. * * An extension can only register a single uri handler in its entire activation lifetime. * From 97aa61c04952d3781dcbac5fbc473265b503ef24 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 6 Jul 2018 14:35:11 +0200 Subject: [PATCH 225/283] whitespace --- src/vs/vscode.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index a2b480f95c6..27a09386ff7 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -6244,10 +6244,10 @@ declare module 'vscode' { /** * Registers a webview panel serializer. * - * Extensions that support reviving should have an`"onWebviewPanel:viewType"` activation event and - * make sure that[registerWebviewPanelSerializer](#registerWebviewPanelSerializer) is called during activation. + * Extensions that support reviving should have an `"onWebviewPanel:viewType"` activation event and + * make sure that [registerWebviewPanelSerializer](#registerWebviewPanelSerializer) is called during activation. * - * Only a single serializer may be registered at a time for a given`viewType`. + * Only a single serializer may be registered at a time for a given `viewType`. * * @param viewType Type of the webview panel that can be serialized. * @param serializer Webview serializer. From 0b9ed7266908ec9dbc90313d7895c1e6a986acb1 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 6 Jul 2018 14:45:44 +0200 Subject: [PATCH 226/283] improve selection restoration --- extensions/git/src/commands.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index f55e13db394..9d60c38e16a 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -791,10 +791,10 @@ export class CommandCenter { edit.replace(modifiedUri, new Range(new Position(0, 0), modifiedDocument.lineAt(modifiedDocument.lineCount - 1).range.end), result); workspace.applyEdit(edit); + await modifiedDocument.save(); + textEditor.selections = selectionsBeforeRevert; textEditor.revealRange(visibleRangesBeforeRevert[0]); - - await modifiedDocument.save(); } @command('git.unstage') From 3a54676f500a9e8c86770d80aad3d75f50c1ae1b Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 6 Jul 2018 14:53:13 +0200 Subject: [PATCH 227/283] move successful push notification into repository --- extensions/git/package.json | 2 +- extensions/git/src/commands.ts | 4 ---- extensions/git/src/repository.ts | 10 ++++++++++ 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 410fb6f0aa9..eb99282e708 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1156,4 +1156,4 @@ "@types/which": "^1.0.28", "mocha": "^3.2.0" } -} +} \ No newline at end of file diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 3eb6f857899..9d60c38e16a 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -1395,10 +1395,6 @@ export class CommandCenter { try { await repository.push(repository.HEAD); - const gitConfig = workspace.getConfiguration('git'); - if (gitConfig.get('showPushSuccessNotification')) { - window.showInformationMessage(localize('push success', "Successfully pushed.")); - } } catch (err) { if (err.gitErrorCode !== GitErrorCodes.NoUpstreamBranch) { throw err; diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 6051cc3cb48..4908ddac81f 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -557,6 +557,16 @@ export class Repository implements Disposable { this.disposables.push(new AutoFetcher(this, globalState)); + // https://github.com/Microsoft/vscode/issues/39039 + const onSuccessfulPush = filterEvent(this.onDidRunOperation, e => e.operation === Operation.Push && !e.error); + onSuccessfulPush(() => { + const gitConfig = workspace.getConfiguration('git'); + + if (gitConfig.get('showPushSuccessNotification')) { + window.showInformationMessage(localize('push success', "Successfully pushed.")); + } + }, null, this.disposables); + const statusBar = new StatusBarCommands(this); this.disposables.push(statusBar); statusBar.onDidChange(() => this._sourceControl.statusBarCommands = statusBar.commands, null, this.disposables); From b5e8f88e3bc47bd06e55f53c1c9e5d0af5951a5f Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 6 Jul 2018 15:03:36 +0200 Subject: [PATCH 228/283] use more resource level settings for git --- extensions/git/package.json | 3 +++ extensions/git/src/commands.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 3b0e2eee881..9d6d69c296e 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -943,11 +943,13 @@ }, "git.enableSmartCommit": { "type": "boolean", + "scope": "resource", "description": "%config.enableSmartCommit%", "default": false }, "git.enableCommitSigning": { "type": "boolean", + "scope": "resource", "description": "%config.enableCommitSigning%", "default": false }, @@ -958,6 +960,7 @@ }, "git.promptToSaveFilesBeforeCommit": { "type": "boolean", + "scope": "resource", "default": false, "description": "%config.promptToSaveFilesBeforeCommit%" }, diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 61440ce043a..e1f5f965d53 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -991,7 +991,7 @@ export class CommandCenter { getCommitMessage: () => Promise, opts?: CommitOptions ): Promise { - const config = workspace.getConfiguration('git'); + const config = workspace.getConfiguration('git', Uri.file(repository.root)); const promptToSaveFilesBeforeCommit = config.get('promptToSaveFilesBeforeCommit') === true; if (promptToSaveFilesBeforeCommit) { From 43e0afebd83a25b479fad8d88872a6c8ff24b4ef Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 6 Jul 2018 15:07:37 +0200 Subject: [PATCH 229/283] Fix #52393 --- .../markers/electron-browser/markersPanel.ts | 32 +++++++++++++++---- .../electron-browser/markersPanelActions.ts | 1 + .../electron-browser/media/markers.css | 3 +- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts index a5976612345..62916140d4a 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts @@ -50,6 +50,7 @@ export class MarkersPanel extends Panel { private treeContainer: HTMLElement; private messageBoxContainer: HTMLElement; + private ariaLabelElement: HTMLElement; private panelSettings: any; private currentResourceGotAddedToMarkersData: boolean = false; @@ -76,8 +77,9 @@ export class MarkersPanel extends Panel { dom.addClass(parent, 'markers-panel'); - let container = dom.append(parent, dom.$('.markers-panel-container')); + const container = dom.append(parent, dom.$('.markers-panel-container')); + this.createArialLabelElement(container); this.createMessageBox(container); this.createTree(container); this.createActions(); @@ -183,11 +185,17 @@ export class MarkersPanel extends Panel { private createMessageBox(parent: HTMLElement): void { this.messageBoxContainer = dom.append(parent, dom.$('.message-box-container')); + this.messageBoxContainer.setAttribute('aria-labelledby', 'markers-panel-arialabel'); + } + + private createArialLabelElement(parent: HTMLElement): void { + this.ariaLabelElement = dom.append(parent, dom.$('')); + this.ariaLabelElement.setAttribute('id', 'markers-panel-arialabel'); + this.ariaLabelElement.setAttribute('aria-live', 'polite'); } private createTree(parent: HTMLElement): void { - this.treeContainer = dom.append(parent, dom.$('.tree-container')); - dom.addClass(this.treeContainer, 'show-file-icons'); + this.treeContainer = dom.append(parent, dom.$('.tree-container.show-file-icons')); const renderer = this.instantiationService.createInstance(Viewer.Renderer); const dnd = this.instantiationService.createInstance(SimpleFileResourceDragAndDrop, obj => obj instanceof ResourceMarkers ? obj.uri : void 0); const controller = this.instantiationService.createInstance(Controller); @@ -291,11 +299,18 @@ export class MarkersPanel extends Panel { } private renderMessage(): void { - const markersModel = this.markersWorkbenchService.markersModel; - const hasFilteredResources = markersModel.hasFilteredResources(); dom.clearNode(this.messageBoxContainer); - dom.toggleClass(this.messageBoxContainer, 'hidden', hasFilteredResources); - if (!hasFilteredResources) { + const markersModel = this.markersWorkbenchService.markersModel; + if (markersModel.hasFilteredResources()) { + const { total, filtered } = markersModel.stats(); + if (filtered === total) { + this.ariaLabelElement.setAttribute('aria-label', localize('No problems filtered', "Showing {0} problems", total)); + } else { + this.ariaLabelElement.setAttribute('aria-label', localize('problems filtered', "Showing {0} of {1} problems", filtered, total)); + } + this.messageBoxContainer.removeAttribute('tabIndex'); + } else { + this.messageBoxContainer.setAttribute('tabIndex', '0'); if (markersModel.hasResources()) { if (markersModel.filterOptions.filter) { this.renderFilteredByFilterMessage(this.messageBoxContainer); @@ -315,6 +330,7 @@ export class MarkersPanel extends Panel { link.textContent = localize('disableFilesExclude', "Disable Files Exclude Filter."); link.setAttribute('tabIndex', '0'); dom.addDisposableListener(link, dom.EventType.CLICK, () => this.filterInputActionItem.useFilesExclude = false); + this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_FILE_EXCLUSIONS_FILTER); } private renderFilteredByFilterMessage(container: HTMLElement) { @@ -324,11 +340,13 @@ export class MarkersPanel extends Panel { link.textContent = localize('clearFilter', "Clear Filter."); link.setAttribute('tabIndex', '0'); dom.addDisposableListener(link, dom.EventType.CLICK, () => this.filterInputActionItem.clear()); + this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_FILTERS); } private renderNoProblemsMessage(container: HTMLElement) { const span = dom.append(container, dom.$('span')); span.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_BUILT; + this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_BUILT); } private autoExpand(): void { diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts index b6479f0a8ec..4cffca371fd 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts @@ -157,6 +157,7 @@ export class MarkersFilterActionItem extends BaseActionItem { ariaLabel: Messages.MARKERS_PANEL_FILTER_ARIA_LABEL, history: this.itemOptions.filterHistory })); + this.filterInputBox.inputElement.setAttribute('aria-labelledby', 'markers-panel-arialabel'); this._register(attachInputBoxStyler(this.filterInputBox, this.themeService)); this.filterInputBox.value = this.itemOptions.filterText; this._register(this.filterInputBox.onDidChange(filter => this.delayedFilterUpdate.trigger(() => this.onDidInputChange()))); diff --git a/src/vs/workbench/parts/markers/electron-browser/media/markers.css b/src/vs/workbench/parts/markers/electron-browser/media/markers.css index 1aa1fecffe9..4741c81c69e 100644 --- a/src/vs/workbench/parts/markers/electron-browser/media/markers.css +++ b/src/vs/workbench/parts/markers/electron-browser/media/markers.css @@ -79,8 +79,7 @@ display: none; } -.markers-panel .markers-panel-container .tree-container.hidden, -.markers-panel .markers-panel-container .message-box-container.hidden { +.markers-panel .markers-panel-container .tree-container.hidden { display: none; visibility: hidden; } From 2b03845eef4b0b748493185db77112b704b41a30 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 6 Jul 2018 15:09:41 +0200 Subject: [PATCH 230/283] :lipstick: --- extensions/git/package.json | 2 +- extensions/git/src/model.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 38b45d6d3e4..3354be990bf 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1002,7 +1002,7 @@ "default": false, "description": "%config.alwaysSignOff%" }, - "git.ignoreRepositories": { + "git.ignoredRepositories": { "type": "array", "default": [], "scope": "window", diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 36c4f7b4ca4..1739f983437 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -190,7 +190,6 @@ export class Model { const config = workspace.getConfiguration('git', Uri.file(path)); const enabled = config.get('enabled') === true; - const ignoredRepos = new Set(config.get>('ignoreRepositories')); if (!enabled) { return; @@ -208,6 +207,9 @@ export class Model { return; } + const config = workspace.getConfiguration('git'); + const ignoredRepos = new Set(config.get>('ignoredRepositories')); + if (ignoredRepos.has(rawRoot)) { return; } From afbf447ccde41e14fa1272da5590a903e66c2497 Mon Sep 17 00:00:00 2001 From: Erich Gamma Date: Fri, 6 Jul 2018 15:13:44 +0200 Subject: [PATCH 231/283] Fix for #53710 adopt TS support for checking JSON files --- build/gulpfile.editor.js | 3 +-- build/gulpfile.mixin.js | 2 -- build/gulpfile.vscode.js | 10 +++++++--- build/gulpfile.vscode.linux.js | 7 ++++--- build/gulpfile.vscode.win32.js | 2 -- build/lib/builtInExtensions.js | 1 - build/lib/optimize.ts | 5 +++++ build/tsconfig.json | 1 + 8 files changed, 18 insertions(+), 13 deletions(-) diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index 3fc14da5045..abfeb7e4a4b 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -15,7 +15,6 @@ const cp = require('child_process'); var root = path.dirname(__dirname); var sha1 = util.getVersion(root); -// @ts-ignore Microsoft/TypeScript#21262 complains about a require of a JSON file var semver = require('./monaco/package.json').version; var headerVersion = semver + '(' + sha1 + ')'; @@ -230,7 +229,7 @@ gulp.task('editor-distro', ['clean-editor-distro', 'compile-editor-esm', 'minify }); gulp.task('analyze-editor-distro', function () { - // @ts-ignore Microsoft/TypeScript#21262 complains about a require of a JSON file + // @ts-ignore var bundleInfo = require('../out-editor/bundleInfo.json'); var graph = bundleInfo.graph; var bundles = bundleInfo.bundles; diff --git a/build/gulpfile.mixin.js b/build/gulpfile.mixin.js index a370981ab3c..29cce77c5dd 100644 --- a/build/gulpfile.mixin.js +++ b/build/gulpfile.mixin.js @@ -15,7 +15,6 @@ const remote = require('gulp-remote-src'); const zip = require('gulp-vinyl-zip'); const assign = require('object-assign'); -// @ts-ignore Microsoft/TypeScript#21262 complains about a require of a JSON file const pkg = require('../package.json'); gulp.task('mixin', function () { @@ -56,7 +55,6 @@ gulp.task('mixin', function () { .pipe(util.rebase(2)) .pipe(productJsonFilter) .pipe(buffer()) - // @ts-ignore Microsoft/TypeScript#21262 complains about a require of a JSON file .pipe(json(o => assign({}, require('../product.json'), o))) .pipe(productJsonFilter.restore); diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 526e5e56f5a..22d0b2fcdf5 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -25,9 +25,7 @@ const buildfile = require('../src/buildfile'); const common = require('./lib/optimize'); const root = path.dirname(__dirname); const commit = util.getVersion(root); -// @ts-ignore Microsoft/TypeScript#21262 complains about a require of a JSON file const packageJson = require('../package.json'); -// @ts-ignore Microsoft/TypeScript#21262 complains about a require of a JSON file const product = require('../product.json'); const crypto = require('crypto'); const i18n = require('./lib/i18n'); @@ -40,12 +38,12 @@ const productionDependencies = deps.getProductionDependencies(path.dirname(__dir // @ts-ignore const baseModules = Object.keys(process.binding('natives')).filter(n => !/^_|\//.test(n)); const nodeModules = ['electron', 'original-fs'] + // @ts-ignore JSON checking: dependencies property is optional .concat(Object.keys(product.dependencies || {})) .concat(_.uniq(productionDependencies.map(d => d.name))) .concat(baseModules); // Build -// @ts-ignore Microsoft/TypeScript#21262 complains about a require of a JSON file const builtInExtensions = require('./builtInExtensions.json'); const excludedExtensions = [ @@ -120,6 +118,8 @@ gulp.task('clean-minified-vscode', util.rimraf('out-vscode-min')); gulp.task('minify-vscode', ['clean-minified-vscode', 'optimize-index-js'], common.minifyTask('out-vscode', baseUrl)); // Package + +// @ts-ignore JSON checking: darwinCredits is optional const darwinCreditsTemplate = product.darwinCredits && _.template(fs.readFileSync(path.join(root, product.darwinCredits), 'utf8')); const config = { @@ -148,6 +148,8 @@ const config = { linuxExecutableName: product.applicationName, winIcon: 'resources/win32/code.ico', token: process.env['VSCODE_MIXIN_PASSWORD'] || process.env['GITHUB_TOKEN'] || void 0, + + // @ts-ignore JSON checking: electronRepository is optional repo: product.electronRepository || void 0 }; @@ -255,6 +257,7 @@ function packageTask(platform, arch, opts) { .pipe(filter(['**', '!**/*.js.map'])); let version = packageJson.version; + // @ts-ignore JSON checking: quality is optional const quality = product.quality; if (quality && quality !== 'stable') { @@ -286,6 +289,7 @@ function packageTask(platform, arch, opts) { const depsSrc = [ ..._.flatten(productionDependencies.map(d => path.relative(root, d.path)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`])), + // @ts-ignore JSON checking: dependencies is optional ..._.flatten(Object.keys(product.dependencies || {}).map(d => [`node_modules/${d}/**`, `!node_modules/${d}/**/{test,tests}/**`])) ]; diff --git a/build/gulpfile.vscode.linux.js b/build/gulpfile.vscode.linux.js index ecbc45df320..49f2cad8ad7 100644 --- a/build/gulpfile.vscode.linux.js +++ b/build/gulpfile.vscode.linux.js @@ -12,11 +12,8 @@ const shell = require('gulp-shell'); const es = require('event-stream'); const vfs = require('vinyl-fs'); const util = require('./lib/util'); -// @ts-ignore Microsoft/TypeScript#21262 complains about a require of a JSON file const packageJson = require('../package.json'); -// @ts-ignore Microsoft/TypeScript#21262 complains about a require of a JSON file const product = require('../product.json'); -// @ts-ignore Microsoft/TypeScript#21262 complains about a require of a JSON file const rpmDependencies = require('../resources/linux/rpm/dependencies.json'); const linuxPackageRevision = Math.floor(new Date().getTime() / 1000); @@ -75,7 +72,9 @@ function prepareDebPackage(arch) { const postinst = gulp.src('resources/linux/debian/postinst.template', { base: '.' }) .pipe(replace('@@NAME@@', product.applicationName)) .pipe(replace('@@ARCHITECTURE@@', debArch)) + // @ts-ignore JSON checking: quality is optional .pipe(replace('@@QUALITY@@', product.quality || '@@QUALITY@@')) + // @ts-ignore JSON checking: updateUrl is optional .pipe(replace('@@UPDATEURL@@', product.updateUrl || '@@UPDATEURL@@')) .pipe(rename('DEBIAN/postinst')); @@ -133,7 +132,9 @@ function prepareRpmPackage(arch) { .pipe(replace('@@RELEASE@@', linuxPackageRevision)) .pipe(replace('@@ARCHITECTURE@@', rpmArch)) .pipe(replace('@@LICENSE@@', product.licenseName)) + // @ts-ignore JSON checking: quality is optional .pipe(replace('@@QUALITY@@', product.quality || '@@QUALITY@@')) + // @ts-ignore JSON checking: updateUrl is optional .pipe(replace('@@UPDATEURL@@', product.updateUrl || '@@UPDATEURL@@')) .pipe(replace('@@DEPENDENCIES@@', rpmDependencies[rpmArch].join(', '))) .pipe(rename('SPECS/' + product.applicationName + '.spec')); diff --git a/build/gulpfile.vscode.win32.js b/build/gulpfile.vscode.win32.js index a4268e7ce34..7d1ea35a6ab 100644 --- a/build/gulpfile.vscode.win32.js +++ b/build/gulpfile.vscode.win32.js @@ -12,9 +12,7 @@ const assert = require('assert'); const cp = require('child_process'); const _7z = require('7zip')['7z']; const util = require('./lib/util'); -// @ts-ignore Microsoft/TypeScript#21262 complains about a require of a JSON file const pkg = require('../package.json'); -// @ts-ignore Microsoft/TypeScript#21262 complains about a require of a JSON file const product = require('../product.json'); const vfs = require('vinyl-fs'); const mkdirp = require('mkdirp'); diff --git a/build/lib/builtInExtensions.js b/build/lib/builtInExtensions.js index c03360cf002..88266f3b901 100644 --- a/build/lib/builtInExtensions.js +++ b/build/lib/builtInExtensions.js @@ -17,7 +17,6 @@ const ext = require('./extensions'); const util = require('gulp-util'); const root = path.dirname(path.dirname(__dirname)); -// @ts-ignore Microsoft/TypeScript#21262 complains about a require of a JSON file const builtInExtensions = require('../builtInExtensions.json'); const controlFilePath = path.join(os.homedir(), '.vscode-oss-dev', 'extensions', 'control.json'); diff --git a/build/lib/optimize.ts b/build/lib/optimize.ts index 7c5f15309c3..86e8db56a7d 100644 --- a/build/lib/optimize.ts +++ b/build/lib/optimize.ts @@ -22,6 +22,7 @@ import * as gulpUtil from 'gulp-util'; import * as flatmap from 'gulp-flatmap'; import * as pump from 'pump'; import * as sm from 'source-map'; +import { Language } from './i18n'; const REPO_ROOT_PATH = path.join(__dirname, '../..'); @@ -159,6 +160,10 @@ export interface IOptimizeTaskOpts { * (out folder name) */ out: string; + /** + * (out folder name) + */ + languages?: Language[]; } export function optimizeTask(opts: IOptimizeTaskOpts): () => NodeJS.ReadWriteStream { diff --git a/build/tsconfig.json b/build/tsconfig.json index d60805e7f77..b2cc8c8a0ab 100644 --- a/build/tsconfig.json +++ b/build/tsconfig.json @@ -6,6 +6,7 @@ "removeComments": false, "preserveConstEnums": true, "sourceMap": false, + "resolveJsonModule": true, "experimentalDecorators": true, // enable JavaScript type checking for the language service // use the tsconfig.build.json for compiling wich disable JavaScript From 6539701d1fe65473939d356b35c523a260c63066 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 6 Jul 2018 15:18:18 +0200 Subject: [PATCH 232/283] Fix #51998 --- .../parts/markers/electron-browser/markersPanel.ts | 2 +- .../markers/electron-browser/markersTreeViewer.ts | 10 +++++++++- .../parts/markers/electron-browser/messages.ts | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts index 62916140d4a..dde2adc6b77 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts @@ -204,7 +204,7 @@ export class MarkersPanel extends Panel { filter: new Viewer.DataFilter(), renderer, controller, - accessibilityProvider: new Viewer.MarkersTreeAccessibilityProvider(), + accessibilityProvider: this.instantiationService.createInstance(Viewer.MarkersTreeAccessibilityProvider), dnd }, { twistiePixels: 20, diff --git a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts index 5dee170ab08..b4eb6819d23 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts @@ -21,6 +21,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IDisposable } from 'vs/base/common/lifecycle'; import { getPathLabel } from 'vs/base/common/labels'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; interface IResourceMarkersTemplateData { resourceLabel: ResourceLabel; @@ -256,9 +257,16 @@ export class Renderer implements IRenderer { export class MarkersTreeAccessibilityProvider implements IAccessibilityProvider { + constructor( + @IWorkspaceContextService private contextService: IWorkspaceContextService, + @IEnvironmentService private environmentService: IEnvironmentService + ) { + } + public getAriaLabel(tree: ITree, element: any): string { if (element instanceof ResourceMarkers) { - return Messages.MARKERS_TREE_ARIA_LABEL_RESOURCE(element.name, element.filteredCount); + const path = getPathLabel(element.uri, this.environmentService, this.contextService) || element.uri.fsPath; + return Messages.MARKERS_TREE_ARIA_LABEL_RESOURCE(element.filteredCount, element.name, paths.dirname(path)); } if (element instanceof Marker) { return Messages.MARKERS_TREE_ARIA_LABEL_MARKER(element); diff --git a/src/vs/workbench/parts/markers/electron-browser/messages.ts b/src/vs/workbench/parts/markers/electron-browser/messages.ts index 7368df79ae4..f54ddaea508 100644 --- a/src/vs/workbench/parts/markers/electron-browser/messages.ts +++ b/src/vs/workbench/parts/markers/electron-browser/messages.ts @@ -45,7 +45,7 @@ export default class Messages { public static readonly MARKERS_PANEL_AT_LINE_COL_NUMBER = (ln: number, col: number): string => { return nls.localize('markers.panel.at.ln.col.number', "({0}, {1})", '' + ln, '' + col); }; - public static readonly MARKERS_TREE_ARIA_LABEL_RESOURCE = (fileName: string, noOfProblems: number): string => { return nls.localize('problems.tree.aria.label.resource', "{0} with {1} problems", fileName, noOfProblems); }; + public static readonly MARKERS_TREE_ARIA_LABEL_RESOURCE = (noOfProblems: number, fileName: string, folder: string): string => { return nls.localize('problems.tree.aria.label.resource', "{0} problems in file {1} of folder {2}", noOfProblems, fileName, folder); }; public static readonly MARKERS_TREE_ARIA_LABEL_MARKER = (marker: Marker): string => { const relatedInformationMessage = marker.resourceRelatedInformation.length ? nls.localize('problems.tree.aria.label.marker.relatedInformation', " This problem has references to {0} locations.", marker.resourceRelatedInformation.length) : ''; switch (marker.raw.severity) { From 689c38f4aa49319fed8e537eef78412ec89afa18 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 6 Jul 2018 15:19:27 +0200 Subject: [PATCH 233/283] :lipstick: --- extensions/git/package.json | 8 ++++---- extensions/git/package.nls.json | 2 +- extensions/git/src/commands.ts | 36 ++++++++++++--------------------- extensions/git/src/model.ts | 12 +++++------ 4 files changed, 24 insertions(+), 34 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index c88fe45f9c8..8495bf5d0e5 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -39,8 +39,8 @@ } }, { - "command": "git.loadRepo", - "title": "%command.loadRepo%", + "command": "git.openRepository", + "title": "%command.openRepository%", "category": "Git", "icon": { "light": "resources/icons/light/git.svg", @@ -346,7 +346,7 @@ "when": "config.git.enabled" }, { - "command": "git.loadRepo", + "command": "git.openRepository", "when": "config.git.enabled" }, { @@ -549,7 +549,7 @@ "when": "config.git.enabled && !scmProvider && gitOpenRepositoryCount == 0 && workspaceFolderCount != 0" }, { - "command": "git.loadRepo", + "command": "git.openRepository", "group": "navigation", "when": "config.git.enabled && !scmProvider && gitOpenRepositoryCount == 0 && workspaceFolderCount != 0" }, diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 8c4039b4bad..52b163b9c8a 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -3,7 +3,7 @@ "description": "Git SCM Integration", "command.clone": "Clone", "command.init": "Initialize Repository", - "command.loadRepo": "Load Repo", + "command.openRepository": "Open Repository", "command.close": "Close Repository", "command.refresh": "Refresh", "command.openChange": "Open Changes", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index bcb5fea256c..ff316ad86c8 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -501,38 +501,28 @@ export class CommandCenter { } await this.git.init(path); - await this.model.tryOpenRepository(path); + await this.model.openRepository(path); } - @command('git.loadRepo', { repository: false }) - async loadRepo(path?: string): Promise { - + @command('git.openRepository', { repository: false }) + async openRepository(path?: string): Promise { if (!path) { - path = await window.showInputBox({ - prompt: localize('repopath', "Repository Path"), - ignoreFocusOut: true + const result = await window.showOpenDialog({ + canSelectFiles: false, + canSelectFolders: true, + canSelectMany: false, + defaultUri: Uri.file(os.homedir()), + openLabel: localize('open repo', "Open Repository") }); - } - - if (path) { - - try { - - if (this.model.getRepository(path)) { - await this.model.tryOpenRepository(path); - } - - else { - window.showInformationMessage(localize('notfound', "Could not find a repository at this path")); - } + if (!result || result.length === 0) { return; } - catch (err) { - //If something went wrong, tryOpenRepository should have already given error - } + path = result[0].fsPath; } + + await this.model.openRepository(path); } @command('git.close', { repository: true }) diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 1739f983437..1d5217f583e 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -99,7 +99,7 @@ export class Model { children .filter(child => child !== '.git') - .forEach(child => this.tryOpenRepository(path.join(root, child))); + .forEach(child => this.openRepository(path.join(root, child))); } catch (err) { // noop } @@ -118,7 +118,7 @@ export class Model { @debounce(500) private eventuallyScanPossibleGitRepositories(): void { for (const path of this.possibleGitRepositoryPaths) { - this.tryOpenRepository(path); + this.openRepository(path); } this.possibleGitRepositoryPaths.clear(); @@ -139,7 +139,7 @@ export class Model { .filter(r => !activeRepositories.has(r!.repository)) .filter(r => !(workspace.workspaceFolders || []).some(f => isDescendant(f.uri.fsPath, r!.repository.root))) as OpenRepository[]; - possibleRepositoryFolders.forEach(p => this.tryOpenRepository(p.uri.fsPath)); + possibleRepositoryFolders.forEach(p => this.openRepository(p.uri.fsPath)); openRepositoriesToDispose.forEach(r => r.dispose()); } @@ -153,7 +153,7 @@ export class Model { .filter(({ root }) => workspace.getConfiguration('git', root).get('enabled') !== true) .map(({ repository }) => repository); - possibleRepositoryFolders.forEach(p => this.tryOpenRepository(p.uri.fsPath)); + possibleRepositoryFolders.forEach(p => this.openRepository(p.uri.fsPath)); openRepositoriesToDispose.forEach(r => r.dispose()); } @@ -178,12 +178,12 @@ export class Model { return; } - this.tryOpenRepository(path.dirname(uri.fsPath)); + this.openRepository(path.dirname(uri.fsPath)); }); } @sequentialize - async tryOpenRepository(path: string): Promise { + async openRepository(path: string): Promise { if (this.getRepository(path)) { return; } From 80766044f342e995bd0919c242e38a294a41aa65 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 6 Jul 2018 15:22:21 +0200 Subject: [PATCH 234/283] remove command from scm title --- extensions/git/package.json | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 8495bf5d0e5..c9c82fafd80 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -41,11 +41,7 @@ { "command": "git.openRepository", "title": "%command.openRepository%", - "category": "Git", - "icon": { - "light": "resources/icons/light/git.svg", - "dark": "resources/icons/dark/git.svg" - } + "category": "Git" }, { "command": "git.close", @@ -548,11 +544,6 @@ "group": "navigation", "when": "config.git.enabled && !scmProvider && gitOpenRepositoryCount == 0 && workspaceFolderCount != 0" }, - { - "command": "git.openRepository", - "group": "navigation", - "when": "config.git.enabled && !scmProvider && gitOpenRepositoryCount == 0 && workspaceFolderCount != 0" - }, { "command": "git.commit", "group": "navigation", From ba9881227271c20eab6337d669a2d2f41bf11642 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 6 Jul 2018 15:31:43 +0200 Subject: [PATCH 235/283] :lipstick: --- extensions/git/src/commands.ts | 5 ++++- extensions/git/src/git.ts | 6 +++--- extensions/git/src/test/git.test.ts | 12 ++++++------ 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 3ebf2045b12..b910537bffe 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -1172,16 +1172,19 @@ export class CommandCenter { const HEAD = repository.HEAD; if (!HEAD || !HEAD.commit) { + window.showWarningMessage(localize('no more', "Can't undo because HEAD doesn't point to any commit.")); return; } const commit = await repository.getCommit('HEAD'); - if (commit.previousHashes.length > 0) { + + if (commit.parents.length > 0) { await repository.reset('HEAD~'); } else { await repository.deleteRef('HEAD'); await this.unstageAll(repository); } + repository.inputBox.value = commit.message; } diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 14742452f66..b748675b56d 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -501,7 +501,7 @@ export class Git { export interface Commit { hash: string; message: string; - previousHashes: string[]; + parents: string[]; } export class GitStatusParser { @@ -638,8 +638,8 @@ export function parseGitCommit(raw: string): Commit | null { return null; } - const previousHashes = match[2] ? match[2].split(' ') : []; - return { hash: match[1], message: match[3], previousHashes }; + const parents = match[2] ? match[2].split(' ') : []; + return { hash: match[1], message: match[3], parents }; } export interface DiffOptions { diff --git a/extensions/git/src/test/git.test.ts b/extensions/git/src/test/git.test.ts index a1c5e196cfa..781f91b6f00 100644 --- a/extensions/git/src/test/git.test.ts +++ b/extensions/git/src/test/git.test.ts @@ -177,7 +177,7 @@ suite('git', () => { }); suite('parseGitCommit', () => { - test('single previous commit', () => { + test('single parent commit', () => { const GIT_OUTPUT_SINGLE_PARENT = `52c293a05038d865604c2284aa8698bd087915a1 8e5a374372b8393906c7e380dbb09349c5385554 This is a commit message.`; @@ -185,11 +185,11 @@ This is a commit message.`; assert.deepEqual(parseGitCommit(GIT_OUTPUT_SINGLE_PARENT), { hash: '52c293a05038d865604c2284aa8698bd087915a1', message: 'This is a commit message.', - previousHashes: ['8e5a374372b8393906c7e380dbb09349c5385554'] + parents: ['8e5a374372b8393906c7e380dbb09349c5385554'] }); }); - test('multiple previous commits', () => { + test('multiple parent commits', () => { const GIT_OUTPUT_MULTIPLE_PARENTS = `52c293a05038d865604c2284aa8698bd087915a1 8e5a374372b8393906c7e380dbb09349c5385554 df27d8c75b129ab9b178b386077da2822101b217 This is a commit message.`; @@ -197,11 +197,11 @@ This is a commit message.`; assert.deepEqual(parseGitCommit(GIT_OUTPUT_MULTIPLE_PARENTS), { hash: '52c293a05038d865604c2284aa8698bd087915a1', message: 'This is a commit message.', - previousHashes: ['8e5a374372b8393906c7e380dbb09349c5385554', 'df27d8c75b129ab9b178b386077da2822101b217'] + parents: ['8e5a374372b8393906c7e380dbb09349c5385554', 'df27d8c75b129ab9b178b386077da2822101b217'] }); }); - test('no previous commits', async () => { + test('no parent commits', async () => { const GIT_OUTPUT_NO_PARENTS = `52c293a05038d865604c2284aa8698bd087915a1 This is a commit message.`; @@ -209,7 +209,7 @@ This is a commit message.`; assert.deepEqual(parseGitCommit(GIT_OUTPUT_NO_PARENTS), { hash: '52c293a05038d865604c2284aa8698bd087915a1', message: 'This is a commit message.', - previousHashes: [] + parents: [] }); }); }); From 95773dc19dc262cb84219230c3b5d41dba832e29 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 6 Jul 2018 15:41:15 +0200 Subject: [PATCH 236/283] :lipstick: --- src/vs/base/browser/ui/countBadge/countBadge.css | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/vs/base/browser/ui/countBadge/countBadge.css b/src/vs/base/browser/ui/countBadge/countBadge.css index 1fdcfafa053..7429322f3e6 100644 --- a/src/vs/base/browser/ui/countBadge/countBadge.css +++ b/src/vs/base/browser/ui/countBadge/countBadge.css @@ -4,12 +4,13 @@ *--------------------------------------------------------------------------------------------*/ .monaco-count-badge { - padding: 0.2em; + padding: 0.3em 0.5em; border-radius: 1em; font-size: 85%; - min-width: 1em; - line-height: 1em; + min-width: 1.6em; + line-height: 1em; font-weight: normal; text-align: center; display: inline-block; -} + box-sizing: border-box; +} \ No newline at end of file From 7e8371c477bf020855fb441730a2899547d47e14 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 6 Jul 2018 15:48:23 +0200 Subject: [PATCH 237/283] Fix #52452 --- .../preferences/browser/keybindingsEditor.ts | 26 ++++++++++++++++--- .../preferences/browser/preferencesEditor.ts | 3 ++- .../preferences/browser/preferencesWidgets.ts | 7 ++++- .../preferences/browser/settingsEditor2.ts | 3 ++- 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/parts/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/parts/preferences/browser/keybindingsEditor.ts index 4d91fd39cb5..d8ba6e9bef1 100644 --- a/src/vs/workbench/parts/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/parts/preferences/browser/keybindingsEditor.ts @@ -74,6 +74,8 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor private searchFocusContextKey: IContextKey; private sortByPrecedence: Checkbox; + private ariaLabelElement: HTMLElement; + constructor( @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @@ -100,6 +102,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor createEditor(parent: HTMLElement): void { const keybindingsEditorElement = DOM.append(parent, $('div', { class: 'keybindings-editor' })); + this.createAriaLabelElement(keybindingsEditorElement); this.createOverlayContainer(keybindingsEditorElement); this.createHeader(keybindingsEditorElement); this.createBody(keybindingsEditorElement); @@ -268,6 +271,12 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor return TPromise.as(null); } + private createAriaLabelElement(parent: HTMLElement): void { + this.ariaLabelElement = DOM.append(parent, DOM.$('')); + this.ariaLabelElement.setAttribute('id', 'keybindings-editor-aria-label-element'); + this.ariaLabelElement.setAttribute('aria-live', 'assertive'); + } + private createOverlayContainer(parent: HTMLElement): void { this.overlayContainer = DOM.append(parent, $('.overlay-container')); this.overlayContainer.style.position = 'absolute'; @@ -293,7 +302,8 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor this.searchWidget = this._register(this.instantiationService.createInstance(SearchWidget, searchContainer, { ariaLabel: localize('SearchKeybindings.AriaLabel', "Search keybindings"), placeholder: localize('SearchKeybindings.Placeholder', "Search keybindings"), - focusKey: this.searchFocusContextKey + focusKey: this.searchFocusContextKey, + ariaLabelledBy: 'keybindings-editor-aria-label-element' })); this._register(this.searchWidget.onDidChange(searchValue => this.delayedFiltering.trigger(() => this.filterKeybindings()))); @@ -302,8 +312,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor isChecked: false, title: localize('sortByPrecedene', "Sort by Precedence") })); - this._register( - this.sortByPrecedence.onChange(() => this.renderKeybindingsEntries(false))); + this._register(this.sortByPrecedence.onChange(() => this.renderKeybindingsEntries(false))); searchContainer.appendChild(this.sortByPrecedence.domNode); this.createOpenKeybindingsElement(this.headerContainer); @@ -397,6 +406,9 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor if (this.keybindingsEditorModel) { const filter = this.searchWidget.getValue(); const keybindingsEntries: IKeybindingItemEntry[] = this.keybindingsEditorModel.fetch(filter, this.sortByPrecedence.checked); + + this.ariaLabelElement.setAttribute('aria-label', this.getAriaLabel()); + if (keybindingsEntries.length === 0) { this.latestEmptyFilters.push(filter); } @@ -425,6 +437,14 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor } } + private getAriaLabel(): string { + if (this.sortByPrecedence.checked) { + return localize('show sorted keybindings', "Showing Keybindings in precendence order"); + } else { + return localize('show keybindings', "Showing Keybindings in alphabetical order"); + } + } + private layoutKebindingsList(): void { const listHeight = this.dimension.height - (DOM.getDomNodePagePosition(this.headerContainer).height + 12 /*padding*/); this.keybindingsListContainer.style.height = `${listHeight}px`; diff --git a/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts b/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts index 0211aa5d897..1875e5070fc 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts @@ -126,7 +126,8 @@ export class PreferencesEditor extends BaseEditor { ariaLabel: nls.localize('SearchSettingsWidget.AriaLabel', "Search settings"), placeholder: nls.localize('SearchSettingsWidget.Placeholder', "Search Settings"), focusKey: this.searchFocusContextKey, - showResultCount: true + showResultCount: true, + ariaLive: 'assertive' })); this._register(this.searchWidget.onDidChange(value => this.onInputChanged())); this._register(this.searchWidget.onFocus(() => this.lastFocusedWidget = this.searchWidget)); diff --git a/src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts b/src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts index 723af7ffdc0..3c57b2f5fcb 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts @@ -561,6 +561,8 @@ export class SettingsTargetsWidget extends Widget { export interface SearchOptions extends IInputOptions { focusKey?: IContextKey; showResultCount?: boolean; + ariaLive?: string; + ariaLabelledBy?: string; } export class SearchWidget extends Widget { @@ -608,7 +610,10 @@ export class SearchWidget extends Widget { })); } - this.inputBox.inputElement.setAttribute('aria-live', 'assertive'); + this.inputBox.inputElement.setAttribute('aria-live', this.options.ariaLive || 'off'); + if (this.options.ariaLabelledBy) { + this.inputBox.inputElement.setAttribute('aria-labelledBy', this.options.ariaLabelledBy); + } const focusTracker = this._register(DOM.trackFocus(this.inputBox.inputElement)); this._register(focusTracker.onDidFocus(() => this._onFocus.fire())); diff --git a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts index 82caff36780..9e11c49536a 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts @@ -189,7 +189,8 @@ export class SettingsEditor2 extends BaseEditor { this.searchWidget = this._register(this.instantiationService.createInstance(SearchWidget, searchContainer, { ariaLabel: localize('SearchSettings.AriaLabel', "Search settings"), placeholder: localize('SearchSettings.Placeholder', "Search settings"), - focusKey: this.searchFocusContextKey + focusKey: this.searchFocusContextKey, + ariaLive: 'assertive' })); this._register(this.searchWidget.onDidChange(() => this.onSearchInputChanged())); From 7a8ed236ba6890d207cbe6dbc5eb660c7e12550f Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 6 Jul 2018 16:08:57 +0200 Subject: [PATCH 238/283] Read results also when filtered --- .../parts/preferences/browser/keybindingsEditor.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/parts/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/parts/preferences/browser/keybindingsEditor.ts index d8ba6e9bef1..2ef5827640a 100644 --- a/src/vs/workbench/parts/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/parts/preferences/browser/keybindingsEditor.ts @@ -407,7 +407,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor const filter = this.searchWidget.getValue(); const keybindingsEntries: IKeybindingItemEntry[] = this.keybindingsEditorModel.fetch(filter, this.sortByPrecedence.checked); - this.ariaLabelElement.setAttribute('aria-label', this.getAriaLabel()); + this.ariaLabelElement.setAttribute('aria-label', this.getAriaLabel(keybindingsEntries)); if (keybindingsEntries.length === 0) { this.latestEmptyFilters.push(filter); @@ -437,11 +437,11 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor } } - private getAriaLabel(): string { + private getAriaLabel(keybindingsEntries: IKeybindingItemEntry[]): string { if (this.sortByPrecedence.checked) { - return localize('show sorted keybindings', "Showing Keybindings in precendence order"); + return localize('show sorted keybindings', "Showing {0} Keybindings in precendence order", keybindingsEntries.length); } else { - return localize('show keybindings', "Showing Keybindings in alphabetical order"); + return localize('show keybindings', "Showing {0} Keybindings in alphabetical order", keybindingsEntries.length); } } From 718cde0af727a4c9b1aca47a0357c66bf827e3de Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 6 Jul 2018 16:13:01 +0200 Subject: [PATCH 239/283] Fix #52025 --- .../parts/preferences/browser/keybindingWidgets.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/parts/preferences/browser/keybindingWidgets.ts b/src/vs/workbench/parts/preferences/browser/keybindingWidgets.ts index ab0cfe703ed..9398a8c5046 100644 --- a/src/vs/workbench/parts/preferences/browser/keybindingWidgets.ts +++ b/src/vs/workbench/parts/preferences/browser/keybindingWidgets.ts @@ -214,7 +214,9 @@ export class DefineKeybindingWidget extends Widget { this._domNode.setClassName('defineKeybindingWidget'); this._domNode.setWidth(DefineKeybindingWidget.WIDTH); this._domNode.setHeight(DefineKeybindingWidget.HEIGHT); - dom.append(this._domNode.domNode, dom.$('.message', null, nls.localize('defineKeybinding.initial', "Press desired key combination and then press ENTER."))); + + const message = nls.localize('defineKeybinding.initial', "Press desired key combination and then press ENTER."); + dom.append(this._domNode.domNode, dom.$('.message', null, message)); this._register(attachStylerCallback(this.themeService, { editorWidgetBackground, widgetShadow }, colors => { if (colors.editorWidgetBackground) { @@ -230,7 +232,7 @@ export class DefineKeybindingWidget extends Widget { } })); - this._keybindingInputWidget = this._register(this.instantiationService.createInstance(KeybindingInputWidget, this._domNode.domNode, {})); + this._keybindingInputWidget = this._register(this.instantiationService.createInstance(KeybindingInputWidget, this._domNode.domNode, { ariaLabel: message })); this._register(this._keybindingInputWidget.onKeybinding(keybinding => this.onKeybinding(keybinding))); this._register(this._keybindingInputWidget.onEnter(() => this.hide())); this._register(this._keybindingInputWidget.onEscape(() => this.onCancel())); From 10e683de014b51f3aaf5114ae4f485b733eda5dd Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 6 Jul 2018 16:36:38 +0200 Subject: [PATCH 240/283] fix #53713 --- src/vs/code/electron-main/menus.ts | 4 ++-- .../workbench/browser/parts/editor/editor.contribution.ts | 4 ++-- src/vs/workbench/browser/parts/editor/editorActions.ts | 6 +++--- .../workbench/browser/parts/menubar/menubar.contribution.ts | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vs/code/electron-main/menus.ts b/src/vs/code/electron-main/menus.ts index c1a4ddf0b1a..cbcf41f5afa 100644 --- a/src/vs/code/electron-main/menus.ts +++ b/src/vs/code/electron-main/menus.ts @@ -682,7 +682,7 @@ export class CodeMenu { const twoRowsEditorLayout = this.createMenuItem(nls.localize({ key: 'miTwoRowsEditorLayout', comment: ['&& denotes a mnemonic'] }, "T&&wo Rows"), 'workbench.action.editorLayoutTwoRows'); const threeRowsEditorLayout = this.createMenuItem(nls.localize({ key: 'miThreeRowsEditorLayout', comment: ['&& denotes a mnemonic'] }, "Three &&Rows"), 'workbench.action.editorLayoutThreeRows'); const twoByTwoGridEditorLayout = this.createMenuItem(nls.localize({ key: 'miTwoByTwoGridEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Grid (2x2)"), 'workbench.action.editorLayoutTwoByTwoGrid'); - const twoColumnsRightEditorLayout = this.createMenuItem(nls.localize({ key: 'miTwoColumnsRightEditorLayout', comment: ['&& denotes a mnemonic'] }, "Two C&&olumns Right"), 'workbench.action.editorLayoutTwoColumnsRight'); + const twoRowsRightEditorLayout = this.createMenuItem(nls.localize({ key: 'miTwoRowsRightEditorLayout', comment: ['&& denotes a mnemonic'] }, "Two R&&ows Right"), 'workbench.action.editorLayoutTwoRowsRight'); const twoColumnsBottomEditorLayout = this.createMenuItem(nls.localize({ key: 'miTwoColumnsBottomEditorLayout', comment: ['&& denotes a mnemonic'] }, "Two &&Columns Bottom"), 'workbench.action.editorLayoutTwoColumnsBottom'); const toggleEditorLayout = this.createMenuItem(nls.localize({ key: 'miToggleEditorLayout', comment: ['&& denotes a mnemonic'] }, "Toggle Vertical/Horizontal &&Layout"), 'workbench.action.toggleEditorGroupLayout'); @@ -699,7 +699,7 @@ export class CodeMenu { twoRowsEditorLayout, threeRowsEditorLayout, twoByTwoGridEditorLayout, - twoColumnsRightEditorLayout, + twoRowsRightEditorLayout, twoColumnsBottomEditorLayout, __separator__(), toggleEditorLayout diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index f633777987f..20167466e78 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -37,7 +37,7 @@ import { ShowEditorsInActiveGroupAction, MoveEditorToLastGroupAction, OpenFirstEditorInGroup, MoveGroupUpAction, MoveGroupDownAction, FocusLastGroupAction, SplitEditorLeftAction, SplitEditorRightAction, SplitEditorUpAction, SplitEditorDownAction, MoveEditorToLeftGroupAction, MoveEditorToRightGroupAction, MoveEditorToAboveGroupAction, MoveEditorToBelowGroupAction, CloseAllEditorGroupsAction, JoinAllGroupsAction, FocusLeftGroup, FocusAboveGroup, FocusRightGroup, FocusBelowGroup, EditorLayoutSingleAction, EditorLayoutTwoColumnsAction, EditorLayoutThreeColumnsAction, EditorLayoutTwoByTwoGridAction, - EditorLayoutTwoRowsAction, EditorLayoutThreeRowsAction, EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoColumnsRightAction, NewEditorGroupLeftAction, NewEditorGroupRightAction, + EditorLayoutTwoRowsAction, EditorLayoutThreeRowsAction, EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoRowsRightAction, NewEditorGroupLeftAction, NewEditorGroupRightAction, NewEditorGroupAboveAction, NewEditorGroupBelowAction, SplitEditorOrthogonalAction } from 'vs/workbench/browser/parts/editor/editorActions'; import * as editorCommands from 'vs/workbench/browser/parts/editor/editorCommands'; @@ -368,7 +368,7 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutThreeColum registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutTwoRowsAction, EditorLayoutTwoRowsAction.ID, EditorLayoutTwoRowsAction.LABEL), 'View: Two Rows Editor Layout', category); registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutThreeRowsAction, EditorLayoutThreeRowsAction.ID, EditorLayoutThreeRowsAction.LABEL), 'View: Three Rows Editor Layout', category); registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutTwoByTwoGridAction, EditorLayoutTwoByTwoGridAction.ID, EditorLayoutTwoByTwoGridAction.LABEL), 'View: Grid Editor Layout (2x2)', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutTwoColumnsRightAction, EditorLayoutTwoColumnsRightAction.ID, EditorLayoutTwoColumnsRightAction.LABEL), 'View: Two Columns Right Editor Layout', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutTwoRowsRightAction, EditorLayoutTwoRowsRightAction.ID, EditorLayoutTwoRowsRightAction.LABEL), 'View: Two Rows Right Editor Layout', category); registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoColumnsBottomAction.ID, EditorLayoutTwoColumnsBottomAction.LABEL), 'View: Two Columns Bottom Editor Layout', category); // Register Editor Picker Actions including quick navigate support diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index f1af1f2d54b..5c579fa655d 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -1551,10 +1551,10 @@ export class EditorLayoutTwoColumnsBottomAction extends ExecuteCommandAction { } } -export class EditorLayoutTwoColumnsRightAction extends ExecuteCommandAction { +export class EditorLayoutTwoRowsRightAction extends ExecuteCommandAction { - static readonly ID = 'workbench.action.editorLayoutTwoColumnsRight'; - static readonly LABEL = nls.localize('editorLayoutTwoColumnsRight', "Two Columns Right Editor Layout"); + static readonly ID = 'workbench.action.editorLayoutTwoRowsRight'; + static readonly LABEL = nls.localize('editorLayoutTwoRowsRight', "Two Rows Right Editor Layout"); constructor( id: string, diff --git a/src/vs/workbench/browser/parts/menubar/menubar.contribution.ts b/src/vs/workbench/browser/parts/menubar/menubar.contribution.ts index 8d877ddba77..e1d7d69ccdd 100644 --- a/src/vs/workbench/browser/parts/menubar/menubar.contribution.ts +++ b/src/vs/workbench/browser/parts/menubar/menubar.contribution.ts @@ -840,8 +840,8 @@ function layoutMenuRegistration() { MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { group: '2_layouts', command: { - id: 'workbench.action.editorLayoutTwoColumnsRight', - title: nls.localize({ key: 'miTwoColumnsRightEditorLayout', comment: ['&& denotes a mnemonic'] }, "Two C&&olumns Right") + id: 'workbench.action.editorLayoutTwoRowsRight', + title: nls.localize({ key: 'miTwoRowsRightEditorLayout', comment: ['&& denotes a mnemonic'] }, "Two R&&ows Right") }, order: 8 }); From 00a1abe9127cf744fde7a73a64bc29aa7539688f Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 6 Jul 2018 16:37:53 +0200 Subject: [PATCH 241/283] cleanup git relative path calculation --- extensions/git/src/git.ts | 77 ++++++++++++++++++++++------- extensions/git/src/repository.ts | 16 ++++-- extensions/git/src/test/git.test.ts | 72 +++++++++++++++++++++++++-- 3 files changed, 138 insertions(+), 27 deletions(-) diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index c1fc82cb489..6cd7e5afa46 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -337,6 +337,7 @@ export const GitErrorCodes = { LocalChangesOverwritten: 'LocalChangesOverwritten', NoUpstreamBranch: 'NoUpstreamBranch', IsInSubmodule: 'IsInSubmodule', + WrongCase: 'WrongCase', }; function getGitErrorCode(stderr: string): string | undefined { @@ -642,6 +643,36 @@ export function parseGitCommit(raw: string): Commit | null { return { hash: match[1], message: match[3], parents }; } +interface LsTreeElement { + mode: string; + type: string; + object: string; + file: string; +} + +export function parseLsTree(raw: string): LsTreeElement[] { + return raw.split('\n') + .filter(l => !!l) + .map(line => /^(\S+)\s+(\S+)\s+(\S+)\s+(.*)$/.exec(line)!) + .filter(m => !!m) + .map(([, mode, type, object, file]) => ({ mode, type, object, file })); +} + +interface LsFilesElement { + mode: string; + object: string; + stage: string; + file: string; +} + +export function parseLsFiles(raw: string): LsFilesElement[] { + return raw.split('\n') + .filter(l => !!l) + .map(line => /^(\S+)\s+(\S+)\s+(\S+)\s+(.*)$/.exec(line)!) + .filter(m => !!m) + .map(([, mode, object, stage, file]) => ({ mode, object, stage, file })); +} + export interface DiffOptions { cached?: boolean; } @@ -710,13 +741,19 @@ export class Repository { return Promise.reject('Can\'t open file from git'); } - const { exitCode, stdout } = await exec(child); + const { exitCode, stdout, stderr } = await exec(child); if (exitCode) { - return Promise.reject(new GitError({ + const err = new GitError({ message: 'Could not show object.', exitCode - })); + }); + + if (/exists on disk, but not in/.test(stderr)) { + err.gitErrorCode = GitErrorCodes.WrongCase; + } + + return Promise.reject(err); } return stdout; @@ -751,25 +788,27 @@ export class Repository { return { mode, object, size: parseInt(size) }; } - async lstreeOutput(treeish: string, path: string): Promise { - if (!treeish) { // index - const { stdout } = await this.run(['ls-files', '--stage', '--', path]); - return stdout; - } - - const { stdout } = await this.run(['ls-tree', '-l', treeish, '--', path]); - return stdout; + async lstree2(treeish: string, path: string): Promise { + const { stdout } = await this.run(['ls-tree', treeish, '--', path]); + return parseLsTree(stdout); } - async relativePathToGitRelativePath(treeish: string, path: string): Promise { - let gitPath: string = path; - const pathPrefix = path.substring(0, path.lastIndexOf('/')); - const lstOutput = await this.lstreeOutput(treeish, pathPrefix + '/'); - const findResult = lstOutput.toUpperCase().indexOf(path.toUpperCase()); - if (findResult) { - gitPath = lstOutput.substr(findResult, path.length); + async lsfiles(path: string): Promise { + const { stdout } = await this.run(['ls-files', '--stage', '--', path]); + return parseLsFiles(stdout); + } + + async getGitRelativePath(treeish: string, relativePath: string): Promise { + const relativePathLowercase = relativePath.toLowerCase(); + const dirname = path.posix.dirname(relativePath) + '/'; + const elements: { file: string; }[] = treeish ? await this.lstree2(treeish, dirname) : await this.lsfiles(dirname); + const element = elements.filter(file => file.file.toLowerCase() === relativePathLowercase)[0]; + + if (!element) { + throw new GitError({ message: 'Git relative path not found.' }); } - return gitPath; + + return element.file; } async detectObjectType(object: string): Promise<{ mimetype: string, encoding?: string }> { diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index fa1daf8b36c..7c55a0ce22f 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -876,8 +876,8 @@ export class Repository implements Disposable { } async show(ref: string, filePath: string): Promise { - return this.run(Operation.Show, async () => { - let relativePath = path.relative(this.repository.root, filePath).replace(/\\/g, '/'); + return await this.run(Operation.Show, async () => { + const relativePath = path.relative(this.repository.root, filePath).replace(/\\/g, '/'); const configFiles = workspace.getConfiguration('files', Uri.file(filePath)); const defaultEncoding = configFiles.get('encoding'); const autoGuessEncoding = configFiles.get('autoGuessEncoding'); @@ -886,8 +886,16 @@ export class Repository implements Disposable { ref = 'HEAD'; } - relativePath = await this.repository.relativePathToGitRelativePath(ref, relativePath); - return this.repository.bufferString(`${ref}:${relativePath}`, defaultEncoding, autoGuessEncoding); + try { + return await this.repository.bufferString(`${ref}:${relativePath}`, defaultEncoding, autoGuessEncoding); + } catch (err) { + if (err.gitErrorCode === GitErrorCodes.WrongCase) { + const gitRelativePath = await this.repository.getGitRelativePath(ref, relativePath); + return await this.repository.bufferString(`${ref}:${gitRelativePath}`, defaultEncoding, autoGuessEncoding); + } + + throw err; + } }); } diff --git a/extensions/git/src/test/git.test.ts b/extensions/git/src/test/git.test.ts index 781f91b6f00..76533949e34 100644 --- a/extensions/git/src/test/git.test.ts +++ b/extensions/git/src/test/git.test.ts @@ -6,7 +6,7 @@ 'use strict'; import 'mocha'; -import { GitStatusParser, parseGitCommit, parseGitmodules } from '../git'; +import { GitStatusParser, parseGitCommit, parseGitmodules, parseLsTree, parseLsFiles } from '../git'; import * as assert from 'assert'; suite('git', () => { @@ -177,7 +177,7 @@ suite('git', () => { }); suite('parseGitCommit', () => { - test('single parent commit', () => { + test('single parent commit', function () { const GIT_OUTPUT_SINGLE_PARENT = `52c293a05038d865604c2284aa8698bd087915a1 8e5a374372b8393906c7e380dbb09349c5385554 This is a commit message.`; @@ -189,7 +189,7 @@ This is a commit message.`; }); }); - test('multiple parent commits', () => { + test('multiple parent commits', function () { const GIT_OUTPUT_MULTIPLE_PARENTS = `52c293a05038d865604c2284aa8698bd087915a1 8e5a374372b8393906c7e380dbb09349c5385554 df27d8c75b129ab9b178b386077da2822101b217 This is a commit message.`; @@ -201,7 +201,7 @@ This is a commit message.`; }); }); - test('no parent commits', async () => { + test('no parent commits', function () { const GIT_OUTPUT_NO_PARENTS = `52c293a05038d865604c2284aa8698bd087915a1 This is a commit message.`; @@ -213,4 +213,68 @@ This is a commit message.`; }); }); }); + + suite('parseLsTree', function () { + test('sample', function () { + const input = `040000 tree 0274a81f8ee9ca3669295dc40f510bd2021d0043 .vscode +100644 blob 1d487c1817262e4f20efbfa1d04c18f51b0046f6 Screen Shot 2018-06-01 at 14.48.05.png +100644 blob 686c16e4f019b734655a2576ce8b98749a9ffdb9 Screen Shot 2018-06-07 at 20.04.59.png +100644 blob 257cc5642cb1a054f08cc83f2d943e56fd3ebe99 boom.txt +100644 blob 86dc360dd25f13fa50ffdc8259e9653921f4f2b7 boomcaboom.txt +100644 blob a68b14060589b16d7ac75f67b905c918c03c06eb file.js +100644 blob f7bcfb05af46850d780f88c069edcd57481d822d file.md +100644 blob ab8b86114a051f6490f1ec5e3141b9a632fb46b5 hello.js +100644 blob 257cc5642cb1a054f08cc83f2d943e56fd3ebe99 what.js +100644 blob be859e3f412fa86513cd8bebe8189d1ea1a3e46d what.txt +100644 blob 56ec42c9dc6fcf4534788f0fe34b36e09f37d085 what.txt2`; + + const output = parseLsTree(input); + + assert.deepEqual(output, [ + { mode: '040000', type: 'tree', object: '0274a81f8ee9ca3669295dc40f510bd2021d0043', file: '.vscode' }, + { mode: '100644', type: 'blob', object: '1d487c1817262e4f20efbfa1d04c18f51b0046f6', file: 'Screen Shot 2018-06-01 at 14.48.05.png' }, + { mode: '100644', type: 'blob', object: '686c16e4f019b734655a2576ce8b98749a9ffdb9', file: 'Screen Shot 2018-06-07 at 20.04.59.png' }, + { mode: '100644', type: 'blob', object: '257cc5642cb1a054f08cc83f2d943e56fd3ebe99', file: 'boom.txt' }, + { mode: '100644', type: 'blob', object: '86dc360dd25f13fa50ffdc8259e9653921f4f2b7', file: 'boomcaboom.txt' }, + { mode: '100644', type: 'blob', object: 'a68b14060589b16d7ac75f67b905c918c03c06eb', file: 'file.js' }, + { mode: '100644', type: 'blob', object: 'f7bcfb05af46850d780f88c069edcd57481d822d', file: 'file.md' }, + { mode: '100644', type: 'blob', object: 'ab8b86114a051f6490f1ec5e3141b9a632fb46b5', file: 'hello.js' }, + { mode: '100644', type: 'blob', object: '257cc5642cb1a054f08cc83f2d943e56fd3ebe99', file: 'what.js' }, + { mode: '100644', type: 'blob', object: 'be859e3f412fa86513cd8bebe8189d1ea1a3e46d', file: 'what.txt' }, + { mode: '100644', type: 'blob', object: '56ec42c9dc6fcf4534788f0fe34b36e09f37d085', file: 'what.txt2' } + ]); + }); + }); + + suite('parseLsFiles', function () { + test('sample', function () { + const input = `100644 7a73a41bfdf76d6f793007240d80983a52f15f97 0 .vscode/settings.json +100644 1d487c1817262e4f20efbfa1d04c18f51b0046f6 0 Screen Shot 2018-06-01 at 14.48.05.png +100644 686c16e4f019b734655a2576ce8b98749a9ffdb9 0 Screen Shot 2018-06-07 at 20.04.59.png +100644 257cc5642cb1a054f08cc83f2d943e56fd3ebe99 0 boom.txt +100644 86dc360dd25f13fa50ffdc8259e9653921f4f2b7 0 boomcaboom.txt +100644 a68b14060589b16d7ac75f67b905c918c03c06eb 0 file.js +100644 f7bcfb05af46850d780f88c069edcd57481d822d 0 file.md +100644 ab8b86114a051f6490f1ec5e3141b9a632fb46b5 0 hello.js +100644 257cc5642cb1a054f08cc83f2d943e56fd3ebe99 0 what.js +100644 be859e3f412fa86513cd8bebe8189d1ea1a3e46d 0 what.txt +100644 56ec42c9dc6fcf4534788f0fe34b36e09f37d085 0 what.txt2`; + + const output = parseLsFiles(input); + + assert.deepEqual(output, [ + { mode: '100644', object: '7a73a41bfdf76d6f793007240d80983a52f15f97', stage: '0', file: '.vscode/settings.json' }, + { mode: '100644', object: '1d487c1817262e4f20efbfa1d04c18f51b0046f6', stage: '0', file: 'Screen Shot 2018-06-01 at 14.48.05.png' }, + { mode: '100644', object: '686c16e4f019b734655a2576ce8b98749a9ffdb9', stage: '0', file: 'Screen Shot 2018-06-07 at 20.04.59.png' }, + { mode: '100644', object: '257cc5642cb1a054f08cc83f2d943e56fd3ebe99', stage: '0', file: 'boom.txt' }, + { mode: '100644', object: '86dc360dd25f13fa50ffdc8259e9653921f4f2b7', stage: '0', file: 'boomcaboom.txt' }, + { mode: '100644', object: 'a68b14060589b16d7ac75f67b905c918c03c06eb', stage: '0', file: 'file.js' }, + { mode: '100644', object: 'f7bcfb05af46850d780f88c069edcd57481d822d', stage: '0', file: 'file.md' }, + { mode: '100644', object: 'ab8b86114a051f6490f1ec5e3141b9a632fb46b5', stage: '0', file: 'hello.js' }, + { mode: '100644', object: '257cc5642cb1a054f08cc83f2d943e56fd3ebe99', stage: '0', file: 'what.js' }, + { mode: '100644', object: 'be859e3f412fa86513cd8bebe8189d1ea1a3e46d', stage: '0', file: 'what.txt' }, + { mode: '100644', object: '56ec42c9dc6fcf4534788f0fe34b36e09f37d085', stage: '0', file: 'what.txt2' }, + ]); + }); + }); }); \ No newline at end of file From b6781b0058da9fe539821d6c2ff37e3c619546b9 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 6 Jul 2018 16:45:03 +0200 Subject: [PATCH 242/283] align lstree implementations --- extensions/git/src/commands.ts | 2 +- extensions/git/src/git.ts | 33 ++++++++++------------ extensions/git/src/repository.ts | 10 +++---- extensions/git/src/test/git.test.ts | 44 ++++++++++++++--------------- 4 files changed, 43 insertions(+), 46 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index b910537bffe..19c3cbdbe59 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -252,7 +252,7 @@ export class CommandCenter { gitRef = indexStatus ? '' : 'HEAD'; } - const { size, object } = await repository.lstree(gitRef, uri.fsPath); + const { size, object } = await repository.getObjectDetails(gitRef, uri.fsPath); const { mimetype } = await repository.detectObjectType(object); if (mimetype === 'text/plain') { diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 6cd7e5afa46..cea9edd799a 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -647,15 +647,16 @@ interface LsTreeElement { mode: string; type: string; object: string; + size: string; file: string; } export function parseLsTree(raw: string): LsTreeElement[] { return raw.split('\n') .filter(l => !!l) - .map(line => /^(\S+)\s+(\S+)\s+(\S+)\s+(.*)$/.exec(line)!) + .map(line => /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.*)$/.exec(line)!) .filter(m => !!m) - .map(([, mode, type, object, file]) => ({ mode, type, object, file })); + .map(([, mode, type, object, size, file]) => ({ mode, type, object, size, file })); } interface LsFilesElement { @@ -759,37 +760,33 @@ export class Repository { return stdout; } - async lstree(treeish: string, path: string): Promise<{ mode: string, object: string, size: number }> { + async getObjectDetails(treeish: string, path: string): Promise<{ mode: string, object: string, size: number }> { if (!treeish) { // index - const { stdout } = await this.run(['ls-files', '--stage', '--', path]); + const elements = await this.lsfiles(path); - const match = /^(\d+)\s+([0-9a-f]{40})\s+(\d+)/.exec(stdout); - - if (!match) { + if (elements.length === 0) { throw new GitError({ message: 'Error running ls-files' }); } - const [, mode, object] = match; + const { mode, object } = elements[0]; const catFile = await this.run(['cat-file', '-s', object]); const size = parseInt(catFile.stdout); return { mode, object, size }; } - const { stdout } = await this.run(['ls-tree', '-l', treeish, '--', path]); + const elements = await this.lstree(treeish, path); - const match = /^(\d+)\s+(\w+)\s+([0-9a-f]{40})\s+(\d+)/.exec(stdout); - - if (!match) { - throw new GitError({ message: 'Error running ls-tree' }); + if (elements.length === 0) { + throw new GitError({ message: 'Error running ls-files' }); } - const [, mode, , object, size] = match; + const { mode, object, size } = elements[0]; return { mode, object, size: parseInt(size) }; } - async lstree2(treeish: string, path: string): Promise { - const { stdout } = await this.run(['ls-tree', treeish, '--', path]); + async lstree(treeish: string, path: string): Promise { + const { stdout } = await this.run(['ls-tree', '-l', treeish, '--', path]); return parseLsTree(stdout); } @@ -801,7 +798,7 @@ export class Repository { async getGitRelativePath(treeish: string, relativePath: string): Promise { const relativePathLowercase = relativePath.toLowerCase(); const dirname = path.posix.dirname(relativePath) + '/'; - const elements: { file: string; }[] = treeish ? await this.lstree2(treeish, dirname) : await this.lsfiles(dirname); + const elements: { file: string; }[] = treeish ? await this.lstree(treeish, dirname) : await this.lsfiles(dirname); const element = elements.filter(file => file.file.toLowerCase() === relativePathLowercase)[0]; if (!element) { @@ -893,7 +890,7 @@ export class Repository { let mode: string; try { - const details = await this.lstree('HEAD', path); + const details = await this.getObjectDetails('HEAD', path); mode = details.mode; } catch (err) { mode = '100644'; diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 7c55a0ce22f..ece6ef8fb37 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -299,7 +299,7 @@ export enum Operation { Tag = 'Tag', Stash = 'Stash', CheckIgnore = 'CheckIgnore', - LSTree = 'LSTree', + GetObjectDetails = 'GetObjectDetails', SubmoduleUpdate = 'SubmoduleUpdate', RebaseContinue = 'RebaseContinue', } @@ -309,7 +309,7 @@ function isReadOnly(operation: Operation): boolean { case Operation.Show: case Operation.GetCommitTemplate: case Operation.CheckIgnore: - case Operation.LSTree: + case Operation.GetObjectDetails: return true; default: return false; @@ -320,7 +320,7 @@ function shouldShowProgress(operation: Operation): boolean { switch (operation) { case Operation.Fetch: case Operation.CheckIgnore: - case Operation.LSTree: + case Operation.GetObjectDetails: case Operation.Show: return false; default: @@ -906,8 +906,8 @@ export class Repository implements Disposable { }); } - lstree(ref: string, filePath: string): Promise<{ mode: string, object: string, size: number }> { - return this.run(Operation.LSTree, () => this.repository.lstree(ref, filePath)); + getObjectDetails(ref: string, filePath: string): Promise<{ mode: string, object: string, size: number }> { + return this.run(Operation.GetObjectDetails, () => this.repository.getObjectDetails(ref, filePath)); } detectObjectType(object: string): Promise<{ mimetype: string, encoding?: string }> { diff --git a/extensions/git/src/test/git.test.ts b/extensions/git/src/test/git.test.ts index 76533949e34..eee43347351 100644 --- a/extensions/git/src/test/git.test.ts +++ b/extensions/git/src/test/git.test.ts @@ -216,32 +216,32 @@ This is a commit message.`; suite('parseLsTree', function () { test('sample', function () { - const input = `040000 tree 0274a81f8ee9ca3669295dc40f510bd2021d0043 .vscode -100644 blob 1d487c1817262e4f20efbfa1d04c18f51b0046f6 Screen Shot 2018-06-01 at 14.48.05.png -100644 blob 686c16e4f019b734655a2576ce8b98749a9ffdb9 Screen Shot 2018-06-07 at 20.04.59.png -100644 blob 257cc5642cb1a054f08cc83f2d943e56fd3ebe99 boom.txt -100644 blob 86dc360dd25f13fa50ffdc8259e9653921f4f2b7 boomcaboom.txt -100644 blob a68b14060589b16d7ac75f67b905c918c03c06eb file.js -100644 blob f7bcfb05af46850d780f88c069edcd57481d822d file.md -100644 blob ab8b86114a051f6490f1ec5e3141b9a632fb46b5 hello.js -100644 blob 257cc5642cb1a054f08cc83f2d943e56fd3ebe99 what.js -100644 blob be859e3f412fa86513cd8bebe8189d1ea1a3e46d what.txt -100644 blob 56ec42c9dc6fcf4534788f0fe34b36e09f37d085 what.txt2`; + const input = `040000 tree 0274a81f8ee9ca3669295dc40f510bd2021d0043 - .vscode +100644 blob 1d487c1817262e4f20efbfa1d04c18f51b0046f6 491570 Screen Shot 2018-06-01 at 14.48.05.png +100644 blob 686c16e4f019b734655a2576ce8b98749a9ffdb9 764420 Screen Shot 2018-06-07 at 20.04.59.png +100644 blob 257cc5642cb1a054f08cc83f2d943e56fd3ebe99 4 boom.txt +100644 blob 86dc360dd25f13fa50ffdc8259e9653921f4f2b7 11 boomcaboom.txt +100644 blob a68b14060589b16d7ac75f67b905c918c03c06eb 24 file.js +100644 blob f7bcfb05af46850d780f88c069edcd57481d822d 201 file.md +100644 blob ab8b86114a051f6490f1ec5e3141b9a632fb46b5 8 hello.js +100644 blob 257cc5642cb1a054f08cc83f2d943e56fd3ebe99 4 what.js +100644 blob be859e3f412fa86513cd8bebe8189d1ea1a3e46d 24 what.txt +100644 blob 56ec42c9dc6fcf4534788f0fe34b36e09f37d085 261186 what.txt2`; const output = parseLsTree(input); assert.deepEqual(output, [ - { mode: '040000', type: 'tree', object: '0274a81f8ee9ca3669295dc40f510bd2021d0043', file: '.vscode' }, - { mode: '100644', type: 'blob', object: '1d487c1817262e4f20efbfa1d04c18f51b0046f6', file: 'Screen Shot 2018-06-01 at 14.48.05.png' }, - { mode: '100644', type: 'blob', object: '686c16e4f019b734655a2576ce8b98749a9ffdb9', file: 'Screen Shot 2018-06-07 at 20.04.59.png' }, - { mode: '100644', type: 'blob', object: '257cc5642cb1a054f08cc83f2d943e56fd3ebe99', file: 'boom.txt' }, - { mode: '100644', type: 'blob', object: '86dc360dd25f13fa50ffdc8259e9653921f4f2b7', file: 'boomcaboom.txt' }, - { mode: '100644', type: 'blob', object: 'a68b14060589b16d7ac75f67b905c918c03c06eb', file: 'file.js' }, - { mode: '100644', type: 'blob', object: 'f7bcfb05af46850d780f88c069edcd57481d822d', file: 'file.md' }, - { mode: '100644', type: 'blob', object: 'ab8b86114a051f6490f1ec5e3141b9a632fb46b5', file: 'hello.js' }, - { mode: '100644', type: 'blob', object: '257cc5642cb1a054f08cc83f2d943e56fd3ebe99', file: 'what.js' }, - { mode: '100644', type: 'blob', object: 'be859e3f412fa86513cd8bebe8189d1ea1a3e46d', file: 'what.txt' }, - { mode: '100644', type: 'blob', object: '56ec42c9dc6fcf4534788f0fe34b36e09f37d085', file: 'what.txt2' } + { mode: '040000', type: 'tree', object: '0274a81f8ee9ca3669295dc40f510bd2021d0043', size: '-', file: '.vscode' }, + { mode: '100644', type: 'blob', object: '1d487c1817262e4f20efbfa1d04c18f51b0046f6', size: '491570', file: 'Screen Shot 2018-06-01 at 14.48.05.png' }, + { mode: '100644', type: 'blob', object: '686c16e4f019b734655a2576ce8b98749a9ffdb9', size: '764420', file: 'Screen Shot 2018-06-07 at 20.04.59.png' }, + { mode: '100644', type: 'blob', object: '257cc5642cb1a054f08cc83f2d943e56fd3ebe99', size: '4', file: 'boom.txt' }, + { mode: '100644', type: 'blob', object: '86dc360dd25f13fa50ffdc8259e9653921f4f2b7', size: '11', file: 'boomcaboom.txt' }, + { mode: '100644', type: 'blob', object: 'a68b14060589b16d7ac75f67b905c918c03c06eb', size: '24', file: 'file.js' }, + { mode: '100644', type: 'blob', object: 'f7bcfb05af46850d780f88c069edcd57481d822d', size: '201', file: 'file.md' }, + { mode: '100644', type: 'blob', object: 'ab8b86114a051f6490f1ec5e3141b9a632fb46b5', size: '8', file: 'hello.js' }, + { mode: '100644', type: 'blob', object: '257cc5642cb1a054f08cc83f2d943e56fd3ebe99', size: '4', file: 'what.js' }, + { mode: '100644', type: 'blob', object: 'be859e3f412fa86513cd8bebe8189d1ea1a3e46d', size: '24', file: 'what.txt' }, + { mode: '100644', type: 'blob', object: '56ec42c9dc6fcf4534788f0fe34b36e09f37d085', size: '261186', file: 'what.txt2' } ]); }); }); From 5270d7400ba4aa664e0c859fd57d57847315f96a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 6 Jul 2018 16:55:02 +0200 Subject: [PATCH 243/283] fix #53675 --- src/vs/base/test/node/config.test.ts | 90 ++++++++++++++-------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/src/vs/base/test/node/config.test.ts b/src/vs/base/test/node/config.test.ts index 8a0002bc406..8be86fd083c 100644 --- a/src/vs/base/test/node/config.test.ts +++ b/src/vs/base/test/node/config.test.ts @@ -77,70 +77,70 @@ suite('Config', () => { }); }); - test('watching', function (done) { - this.timeout(10000); // watching is timing intense + // test('watching', function (done) { + // this.timeout(10000); // watching is timing intense - testFile('config', 'config.json').then(res => { - fs.writeFileSync(res.testFile, '// my comment\n{ "foo": "bar" }'); + // testFile('config', 'config.json').then(res => { + // fs.writeFileSync(res.testFile, '// my comment\n{ "foo": "bar" }'); - let watcher = new ConfigWatcher<{ foo: string; }>(res.testFile); - watcher.getConfig(); // ensure we are in sync + // let watcher = new ConfigWatcher<{ foo: string; }>(res.testFile); + // watcher.getConfig(); // ensure we are in sync - fs.writeFileSync(res.testFile, '// my comment\n{ "foo": "changed" }'); + // fs.writeFileSync(res.testFile, '// my comment\n{ "foo": "changed" }'); - watcher.onDidUpdateConfiguration(event => { - assert.ok(event); - assert.equal(event.config.foo, 'changed'); - assert.equal(watcher.getValue('foo'), 'changed'); + // watcher.onDidUpdateConfiguration(event => { + // assert.ok(event); + // assert.equal(event.config.foo, 'changed'); + // assert.equal(watcher.getValue('foo'), 'changed'); - watcher.dispose(); + // watcher.dispose(); - res.cleanUp().then(done, done); - }); - }, done); - }); + // res.cleanUp().then(done, done); + // }); + // }, done); + // }); - test('watching also works when file created later', function (done) { - this.timeout(10000); // watching is timing intense + // test('watching also works when file created later', function (done) { + // this.timeout(10000); // watching is timing intense - testFile('config', 'config.json').then(res => { - let watcher = new ConfigWatcher<{ foo: string; }>(res.testFile); - watcher.getConfig(); // ensure we are in sync + // testFile('config', 'config.json').then(res => { + // let watcher = new ConfigWatcher<{ foo: string; }>(res.testFile); + // watcher.getConfig(); // ensure we are in sync - fs.writeFileSync(res.testFile, '// my comment\n{ "foo": "changed" }'); + // fs.writeFileSync(res.testFile, '// my comment\n{ "foo": "changed" }'); - watcher.onDidUpdateConfiguration(event => { - assert.ok(event); - assert.equal(event.config.foo, 'changed'); - assert.equal(watcher.getValue('foo'), 'changed'); + // watcher.onDidUpdateConfiguration(event => { + // assert.ok(event); + // assert.equal(event.config.foo, 'changed'); + // assert.equal(watcher.getValue('foo'), 'changed'); - watcher.dispose(); + // watcher.dispose(); - res.cleanUp().then(done, done); - }); - }, done); - }); + // res.cleanUp().then(done, done); + // }); + // }, done); + // }); - test('watching detects the config file getting deleted', function (done) { - this.timeout(10000); // watching is timing intense + // test('watching detects the config file getting deleted', function (done) { + // this.timeout(10000); // watching is timing intense - testFile('config', 'config.json').then(res => { - fs.writeFileSync(res.testFile, '// my comment\n{ "foo": "bar" }'); + // testFile('config', 'config.json').then(res => { + // fs.writeFileSync(res.testFile, '// my comment\n{ "foo": "bar" }'); - let watcher = new ConfigWatcher<{ foo: string; }>(res.testFile); - watcher.getConfig(); // ensure we are in sync + // let watcher = new ConfigWatcher<{ foo: string; }>(res.testFile); + // watcher.getConfig(); // ensure we are in sync - watcher.onDidUpdateConfiguration(event => { - assert.ok(event); + // watcher.onDidUpdateConfiguration(event => { + // assert.ok(event); - watcher.dispose(); + // watcher.dispose(); - res.cleanUp().then(done, done); - }); + // res.cleanUp().then(done, done); + // }); - fs.unlinkSync(res.testFile); - }, done); - }); + // fs.unlinkSync(res.testFile); + // }, done); + // }); test('reload', function (done) { testFile('config', 'config.json').then(res => { From b00b509dd4f3e73657293476dc5e8cbde9188d89 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 6 Jul 2018 17:53:14 +0200 Subject: [PATCH 244/283] Fix #53599 --- .../electron-browser/markersPanelActions.ts | 44 ++++++++++--------- .../electron-browser/media/markers.css | 19 +++++--- 2 files changed, 36 insertions(+), 27 deletions(-) diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts index 4cffca371fd..fd918657fe9 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts @@ -93,13 +93,14 @@ export class MarkersFilterActionItem extends BaseActionItem { private delayedFilterUpdate: Delayer; private container: HTMLElement; + private element: HTMLElement; private filterInputBox: HistoryInputBox; private controlsContainer: HTMLInputElement; private filterBadge: HTMLInputElement; private filesExcludeFilter: Checkbox; constructor( - private itemOptions: IMarkersFilterActionItemOptions, + itemOptions: IMarkersFilterActionItemOptions, action: IAction, @IInstantiationService private instantiationService: IInstantiationService, @IContextViewService private contextViewService: IContextViewService, @@ -109,13 +110,13 @@ export class MarkersFilterActionItem extends BaseActionItem { ) { super(null, action); this.delayedFilterUpdate = new Delayer(500); + this.create(itemOptions); } render(container: HTMLElement): void { this.container = container; - DOM.addClass(this.container, 'markers-panel-action-filter'); - this.createInput(this.container); - this.createControls(this.container); + DOM.addClass(this.container, 'markers-panel-action-filter-container'); + DOM.append(this.container, this.element); this.adjustInputBox(); } @@ -124,7 +125,7 @@ export class MarkersFilterActionItem extends BaseActionItem { } getFilterText(): string { - return this.filterInputBox ? this.filterInputBox.value : this.itemOptions.filterText; + return this.filterInputBox.value; } getFilterHistory(): string[] { @@ -132,44 +133,47 @@ export class MarkersFilterActionItem extends BaseActionItem { } get useFilesExclude(): boolean { - return this.filesExcludeFilter ? this.filesExcludeFilter.checked : this.itemOptions.useFilesExclude; + return this.filesExcludeFilter.checked; } set useFilesExclude(useFilesExclude: boolean) { - if (this.filesExcludeFilter) { - if (this.filesExcludeFilter.checked !== useFilesExclude) { - this.filesExcludeFilter.checked = useFilesExclude; - this._onDidChange.fire(); - } + if (this.filesExcludeFilter.checked !== useFilesExclude) { + this.filesExcludeFilter.checked = useFilesExclude; + this._onDidChange.fire(); } } toggleLayout(small: boolean) { if (this.container) { DOM.toggleClass(this.container, 'small', small); - DOM.toggleClass(this.filterBadge, 'small', small); } } - private createInput(container: HTMLElement): void { + private create(itemOptions: IMarkersFilterActionItemOptions): void { + this.element = DOM.$('.markers-panel-action-filter'); + this.createInput(this.element, itemOptions); + this.createControls(this.element, itemOptions); + } + + private createInput(container: HTMLElement, itemOptions: IMarkersFilterActionItemOptions): void { this.filterInputBox = this._register(this.instantiationService.createInstance(ContextScopedHistoryInputBox, container, this.contextViewService, { placeholder: Messages.MARKERS_PANEL_FILTER_PLACEHOLDER, ariaLabel: Messages.MARKERS_PANEL_FILTER_ARIA_LABEL, - history: this.itemOptions.filterHistory + history: itemOptions.filterHistory })); this.filterInputBox.inputElement.setAttribute('aria-labelledby', 'markers-panel-arialabel'); this._register(attachInputBoxStyler(this.filterInputBox, this.themeService)); - this.filterInputBox.value = this.itemOptions.filterText; + this.filterInputBox.value = itemOptions.filterText; this._register(this.filterInputBox.onDidChange(filter => this.delayedFilterUpdate.trigger(() => this.onDidInputChange()))); this._register(DOM.addStandardDisposableListener(this.filterInputBox.inputElement, 'keydown', (keyboardEvent) => this.onInputKeyDown(keyboardEvent, this.filterInputBox))); this._register(DOM.addStandardDisposableListener(container, 'keydown', this.handleKeyboardEvent)); this._register(DOM.addStandardDisposableListener(container, 'keyup', this.handleKeyboardEvent)); } - private createControls(container: HTMLElement): void { + private createControls(container: HTMLElement, itemOptions: IMarkersFilterActionItemOptions): void { this.controlsContainer = DOM.append(container, DOM.$('.markers-panel-filter-controls')); this.createBadge(this.controlsContainer); - this.createFilesExcludeCheckbox(this.controlsContainer); + this.createFilesExcludeCheckbox(this.controlsContainer, itemOptions); } private createBadge(container: HTMLElement): void { @@ -188,11 +192,11 @@ export class MarkersFilterActionItem extends BaseActionItem { this._register(this.markersWorkbenchService.onDidChange(() => this.updateBadge())); } - private createFilesExcludeCheckbox(container: HTMLElement): void { + private createFilesExcludeCheckbox(container: HTMLElement, itemOptions: IMarkersFilterActionItemOptions): void { this.filesExcludeFilter = this._register(new Checkbox({ actionClassName: 'markers-panel-filter-filesExclude', - title: this.itemOptions.useFilesExclude ? Messages.MARKERS_PANEL_ACTION_TOOLTIP_DO_NOT_USE_FILES_EXCLUDE : Messages.MARKERS_PANEL_ACTION_TOOLTIP_USE_FILES_EXCLUDE, - isChecked: this.itemOptions.useFilesExclude + title: itemOptions.useFilesExclude ? Messages.MARKERS_PANEL_ACTION_TOOLTIP_DO_NOT_USE_FILES_EXCLUDE : Messages.MARKERS_PANEL_ACTION_TOOLTIP_USE_FILES_EXCLUDE, + isChecked: itemOptions.useFilesExclude })); this._register(this.filesExcludeFilter.onChange(() => { this.filesExcludeFilter.domNode.title = this.filesExcludeFilter.checked ? Messages.MARKERS_PANEL_ACTION_TOOLTIP_DO_NOT_USE_FILES_EXCLUDE : Messages.MARKERS_PANEL_ACTION_TOOLTIP_USE_FILES_EXCLUDE; diff --git a/src/vs/workbench/parts/markers/electron-browser/media/markers.css b/src/vs/workbench/parts/markers/electron-browser/media/markers.css index 4741c81c69e..0d0649cfabc 100644 --- a/src/vs/workbench/parts/markers/electron-browser/media/markers.css +++ b/src/vs/workbench/parts/markers/electron-browser/media/markers.css @@ -3,30 +3,35 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-action-bar .action-item.markers-panel-action-filter { +.monaco-action-bar .markers-panel-action-filter-container { cursor: default; margin-right: 10px; min-width: 150px; max-width: 500px; display: flex; - align-items: center; } -.monaco-action-bar .action-item.markers-panel-action-filter { +.monaco-action-bar .markers-panel-action-filter-container { flex: 0.7; } -.monaco-action-bar .action-item.markers-panel-action-filter.small { +.monaco-action-bar .markers-panel-action-filter-container.small { flex: 0.5; } -.monaco-action-bar .action-item.markers-panel-action-filter .monaco-inputbox { +.monaco-action-bar .markers-panel-action-filter { + display: flex; + align-items: center; + flex: 1; +} + +.monaco-action-bar .markers-panel-action-filter .monaco-inputbox { height: 24px; font-size: 12px; flex: 1; } -.vs .monaco-action-bar .action-item.markers-panel-action-filter .monaco-inputbox { +.vs .monaco-action-bar .markers-panel-action-filter .monaco-inputbox { height: 25px; border: 1px solid #ddd; } @@ -47,7 +52,7 @@ } .markers-panel-action-filter > .markers-panel-filter-controls > .markers-panel-filter-badge.hidden, -.markers-panel-action-filter > .markers-panel-filter-controls > .markers-panel-filter-badge.small { +.markers-panel-action-filter-container.small .markers-panel-action-filter > .markers-panel-filter-controls > .markers-panel-filter-badge { display: none; } From 65ae815e9caff15879b3dbd76370171298e6c2e8 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 6 Jul 2018 09:59:46 -0700 Subject: [PATCH 245/283] vscode-xterm@3.6.0-beta2 Fixes #53705 --- package.json | 2 +- yarn.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 54df1ab121c..695afb3d60c 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "vscode-nsfw": "1.0.17", "vscode-ripgrep": "^1.0.1", "vscode-textmate": "^4.0.1", - "vscode-xterm": "3.6.0-beta1", + "vscode-xterm": "3.6.0-beta2", "yauzl": "^2.9.1" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 1ac2e5486b8..f49e290a360 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6256,9 +6256,9 @@ vscode-textmate@^4.0.1: dependencies: oniguruma "^7.0.0" -vscode-xterm@3.6.0-beta1: - version "3.6.0-beta1" - resolved "https://registry.yarnpkg.com/vscode-xterm/-/vscode-xterm-3.6.0-beta1.tgz#6ce8b79d33f7506ac3571d92c15f9d65e8b856ca" +vscode-xterm@3.6.0-beta2: + version "3.6.0-beta2" + resolved "https://registry.yarnpkg.com/vscode-xterm/-/vscode-xterm-3.6.0-beta2.tgz#db2991cc2e73c7f5c30bd85b1096cb5d38112589" vso-node-api@^6.1.2-preview: version "6.1.2-preview" From 54fd81dbc4f79794eaf17e7e5f5bca0574880a3e Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Fri, 6 Jul 2018 11:13:15 -0700 Subject: [PATCH 246/283] Don't show participants label for new comments --- .../electron-browser/commentThreadWidget.ts | 87 ++++++++----------- 1 file changed, 38 insertions(+), 49 deletions(-) diff --git a/src/vs/workbench/parts/comments/electron-browser/commentThreadWidget.ts b/src/vs/workbench/parts/comments/electron-browser/commentThreadWidget.ts index e06e3c0ea21..1ee06237e2e 100644 --- a/src/vs/workbench/parts/comments/electron-browser/commentThreadWidget.ts +++ b/src/vs/workbench/parts/comments/electron-browser/commentThreadWidget.ts @@ -108,7 +108,6 @@ export class ReviewZoneWidget extends ZoneWidget { private _commentThread: modes.CommentThread; private _commentGlyph: CommentGlyphWidget; private _owner: number; - private _decorationIDs: string[]; private _localToDispose: IDisposable[]; public get owner(): number { @@ -134,7 +133,6 @@ export class ReviewZoneWidget extends ZoneWidget { this._owner = owner; this._commentThread = commentThread; this._isCollapsed = commentThread.collapsibleState !== modes.CommentThreadCollapsibleState.Expanded; - this._decorationIDs = []; this._localToDispose = []; this.create(); this.themeService.onThemeChange(this._applyTheme, this); @@ -150,12 +148,7 @@ export class ReviewZoneWidget extends ZoneWidget { public reveal(commentId?: string) { if (this._isCollapsed) { - if (this._decorationIDs && this._decorationIDs.length) { - let range = this.editor.getModel().getDecorationRange(this._decorationIDs[0]); - this.show(range, 2); - } else { - this.show({ lineNumber: this._commentThread.range.startLineNumber, column: 1 }, 2); - } + this.show({ lineNumber: this._commentThread.range.startLineNumber, column: 1 }, 2); } this._bodyElement.focus(); @@ -195,13 +188,9 @@ export class ReviewZoneWidget extends ZoneWidget { this._secondaryHeading = $('span.dirname').appendTo(titleElement).getHTMLElement(); this._metaHeading = $('span.meta').appendTo(titleElement).getHTMLElement(); - let primaryHeading = 'Participants:'; - $(this._primaryHeading).safeInnerHtml(primaryHeading); - this._primaryHeading.setAttribute('aria-label', primaryHeading); - - let secondaryHeading = this._commentThread.comments.filter(arrays.uniqueFilter(comment => comment.userName)).map(comment => `@${comment.userName}`).join(', '); - $(this._secondaryHeading).safeInnerHtml(secondaryHeading); - this._secondaryHeading.setAttribute('aria-label', secondaryHeading); + if (this._commentThread.comments.length) { + this.createParticipantsLabel(); + } const actionsContainer = $('.review-actions').appendTo(this._headElement); this._actionbarWidget = new ActionBar(actionsContainer.getHTMLElement(), {}); @@ -253,16 +242,6 @@ export class ReviewZoneWidget extends ZoneWidget { this._commentsElement.removeChild(commentElementsToDel[i].domNode); } - if (this._commentElements.length === 0) { - this._commentThread = commentThread; - commentThread.comments.forEach(comment => { - let newElement = new CommentNode(comment); - this._commentElements.push(newElement); - this._commentsElement.appendChild(newElement.domNode); - }); - return; - } - let lastCommentElement: HTMLElement = null; let newCommentNodeList: CommentNode[] = []; for (let i = newCommentsLen - 1; i >= 0; i--) { @@ -349,7 +328,15 @@ export class ReviewZoneWidget extends ZoneWidget { this.setCommentEditorDecorations(); // Only add the additional step of clicking a reply button to expand the textarea when there are existing comments - this.createReplyButton(); + if (hasExistingComments) { + this.createReplyButton(); + } else { + if (!dom.hasClass(this._commentForm, 'expand')) { + dom.addClass(this._commentForm, 'expand'); + this._commentEditor.focus(); + } + } + this._localToDispose.push(this._commentEditor.onKeyDown((ev: IKeyboardEvent) => { const hasExistingComments = this._commentThread.comments.length > 0; @@ -385,6 +372,7 @@ export class ReviewZoneWidget extends ZoneWidget { ); this.createReplyButton(); + this.createParticipantsLabel(); } this._commentEditor.setValue(''); @@ -416,31 +404,33 @@ export class ReviewZoneWidget extends ZoneWidget { } } - createReplyButton() { - const hasExistingComments = this._commentThread.comments.length > 0; - if (hasExistingComments) { - this._reviewThreadReplyButton = $('button.review-thread-reply-button').appendTo(this._commentForm).getHTMLElement(); - this._reviewThreadReplyButton.title = 'Reply...'; - this._reviewThreadReplyButton.textContent = 'Reply...'; - // bind click/escape actions for reviewThreadReplyButton and textArea - this._reviewThreadReplyButton.onclick = () => { - if (!dom.hasClass(this._commentForm, 'expand')) { - dom.addClass(this._commentForm, 'expand'); - this._commentEditor.focus(); - } - }; + createParticipantsLabel() { + const primaryHeading = 'Participants:'; + $(this._primaryHeading).safeInnerHtml(primaryHeading); + this._primaryHeading.setAttribute('aria-label', primaryHeading); - this._commentEditor.onDidBlurEditorWidget(() => { - if (this._commentEditor.getModel().getValueLength() === 0 && dom.hasClass(this._commentForm, 'expand')) { - dom.removeClass(this._commentForm, 'expand'); - } - }); - } else { + const secondaryHeading = this._commentThread.comments.filter(arrays.uniqueFilter(comment => comment.userName)).map(comment => `@${comment.userName}`).join(', '); + $(this._secondaryHeading).safeInnerHtml(secondaryHeading); + this._secondaryHeading.setAttribute('aria-label', secondaryHeading); + } + + createReplyButton() { + this._reviewThreadReplyButton = $('button.review-thread-reply-button').appendTo(this._commentForm).getHTMLElement(); + this._reviewThreadReplyButton.title = 'Reply...'; + this._reviewThreadReplyButton.textContent = 'Reply...'; + // bind click/escape actions for reviewThreadReplyButton and textArea + this._reviewThreadReplyButton.onclick = () => { if (!dom.hasClass(this._commentForm, 'expand')) { dom.addClass(this._commentForm, 'expand'); this._commentEditor.focus(); } - } + }; + + this._commentEditor.onDidBlurEditorWidget(() => { + if (this._commentEditor.getModel().getValueLength() === 0 && dom.hasClass(this._commentForm, 'expand')) { + dom.removeClass(this._commentForm, 'expand'); + } + }); } _refresh() { @@ -561,13 +551,12 @@ export class ReviewZoneWidget extends ZoneWidget { this._resizeObserver.disconnect(); this._resizeObserver = null; } - this.editor.changeDecorations(accessor => { - accessor.deltaDecorations(this._decorationIDs, []); - }); + if (this._commentGlyph) { this.editor.removeContentWidget(this._commentGlyph); this._commentGlyph = null; } + this._localToDispose.forEach(local => local.dispose()); this._onDidClose.fire(); } From 9989abf527b36622bcbffaed635604def9cdff67 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 6 Jul 2018 12:02:00 -0700 Subject: [PATCH 247/283] Correct term char width calculation under DOM renderer Fixes #51479 --- .../parts/terminal/electron-browser/terminalInstance.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 8bafe8eaaa7..7d1a41951b3 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -205,7 +205,13 @@ export class TerminalInstance implements ITerminalInstance { // order to be precise. font.charWidth/charHeight alone as insufficient // when window.devicePixelRatio changes. const scaledWidthAvailable = dimension.width * window.devicePixelRatio; - const scaledCharWidth = Math.floor(font.charWidth * window.devicePixelRatio) + font.letterSpacing; + + let scaledCharWidth: number; + if (this._configHelper.config.rendererType === 'dom') { + scaledCharWidth = font.charWidth * window.devicePixelRatio; + } else { + scaledCharWidth = Math.floor(font.charWidth * window.devicePixelRatio) + font.letterSpacing; + } this._cols = Math.max(Math.floor(scaledWidthAvailable / scaledCharWidth), 1); const scaledHeightAvailable = dimension.height * window.devicePixelRatio; From 8ab9119cf69c6a769a295179e5eab7b55449294c Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Fri, 6 Jul 2018 14:53:44 -0500 Subject: [PATCH 248/283] Fix a double dash in the previewer if an @param jsdoc tag has a hyphen after the param name (#53365) * Fix a double dash if the @param has a hyphen * Moved into the other regex * Add test for ignoring hypen after @param * Fixed test and moved to previewer.test.ts --- .../src/test/previewer.test.ts | 22 +++++++++++++++++++ .../src/utils/previewer.ts | 4 ++-- 2 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 extensions/typescript-language-features/src/test/previewer.test.ts diff --git a/extensions/typescript-language-features/src/test/previewer.test.ts b/extensions/typescript-language-features/src/test/previewer.test.ts new file mode 100644 index 00000000000..011187b2af4 --- /dev/null +++ b/extensions/typescript-language-features/src/test/previewer.test.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import 'mocha'; +import { tagsMarkdownPreview } from '../utils/previewer'; + +suite('typescript.previewer', () => { + test('Should ignore hyphens after a param tag', async () => { + assert.strictEqual( + tagsMarkdownPreview([ + { + name: 'param', + text: 'a - b' + } + ]), + '*@param* `a` — b'); + }); +}); + diff --git a/extensions/typescript-language-features/src/utils/previewer.ts b/extensions/typescript-language-features/src/utils/previewer.ts index 647ae3bf91b..ce10fcbbe04 100644 --- a/extensions/typescript-language-features/src/utils/previewer.ts +++ b/extensions/typescript-language-features/src/utils/previewer.ts @@ -27,7 +27,7 @@ function getTagBodyText(tag: Proto.JSDocTagInfo): string | undefined { function getTagDocumentation(tag: Proto.JSDocTagInfo): string | undefined { switch (tag.name) { case 'param': - const body = (tag.text || '').split(/^([\w\.]+)\s*/); + const body = (tag.text || '').split(/^([\w\.]+)\s*-?\s*/); if (body && body.length === 3) { const param = body[1]; const doc = body[2]; @@ -81,4 +81,4 @@ export function addMarkdownDocumentation( out.appendMarkdown('\n\n' + tagsPreview); } return out; -} \ No newline at end of file +} From 9eeae44f76145b0bffd48919228b14ded5afbda6 Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Fri, 6 Jul 2018 12:56:26 -0700 Subject: [PATCH 249/283] Prevent restoring a disabled panel --- src/vs/workbench/electron-browser/workbench.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index 7191ec03a8c..01b3628a51a 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -707,7 +707,12 @@ export class Workbench extends Disposable implements IPartService { const panelRegistry = Registry.as(PanelExtensions.Panels); const panelId = this.storageService.get(PanelPart.activePanelSettingsKey, StorageScope.WORKSPACE, panelRegistry.getDefaultPanelId()); if (!this.panelHidden && !!panelId) { - restorePromises.push(this.panelPart.openPanel(panelId, false)); + const isPanelToRestoreEnabled = !!this.panelPart.getPanels().filter(p => p.id === panelId).length; + if (isPanelToRestoreEnabled) { + restorePromises.push(this.panelPart.openPanel(panelId, false)); + } else { + restorePromises.push(this.panelPart.openPanel(panelRegistry.getDefaultPanelId(), false)); + } } // Restore Zen Mode if active From 3d35801127f0a62d58d752bc613506e836c5d120 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 6 Jul 2018 14:16:30 -0700 Subject: [PATCH 250/283] Make sure quickSuggestionsForPaths is respected on TS 2.9+ Fixes #53683 --- .../src/features/completions.ts | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/extensions/typescript-language-features/src/features/completions.ts b/extensions/typescript-language-features/src/features/completions.ts index c7ce7bf6e56..2c4ece54764 100644 --- a/extensions/typescript-language-features/src/features/completions.ts +++ b/extensions/typescript-language-features/src/features/completions.ts @@ -318,20 +318,9 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider } const enableDotCompletions = this.shouldEnableDotCompletions(document, position); - - const completionItems: vscode.CompletionItem[] = []; - for (const element of msg) { - if (element.kind === PConst.Kind.warning && !completionConfiguration.nameSuggestions) { - continue; - } - if (!completionConfiguration.autoImportSuggestions && element.hasAction) { - continue; - } - const item = new MyCompletionItem(position, document, line.text, element, enableDotCompletions, completionConfiguration.useCodeSnippetsOnMethodSuggest); - completionItems.push(item); - } - - return completionItems; + return msg + .filter(entry => !shouldExcludeCompletionEntry(entry, completionConfiguration)) + .map(entry => new MyCompletionItem(position, document, line.text, entry, enableDotCompletions, completionConfiguration.useCodeSnippetsOnMethodSuggest)); } public async resolveCompletionItem( @@ -595,6 +584,17 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider } } +function shouldExcludeCompletionEntry( + element: Proto.CompletionEntry, + completionConfiguration: CompletionConfiguration +) { + return ( + (!completionConfiguration.nameSuggestions && element.kind === PConst.Kind.warning) + || (!completionConfiguration.quickSuggestionsForPaths && + (element.kind === PConst.Kind.directory || element.kind === PConst.Kind.script)) + || (!completionConfiguration.autoImportSuggestions && element.hasAction) + ); +} export function register( selector: vscode.DocumentSelector, From 6fae52876b1f9e549a6e1b91789d5fff82d61f0c Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 8 Jul 2018 15:16:19 -0700 Subject: [PATCH 251/283] Fix #53445 - change build id generation to not depend on git tags --- build/gulpfile.vscode.js | 28 ++++++++----- build/lib/test/util.test.js | 56 -------------------------- build/lib/test/util.test.ts | 79 ------------------------------------- build/lib/util.js | 57 -------------------------- build/lib/util.ts | 61 ---------------------------- 5 files changed, 18 insertions(+), 263 deletions(-) delete mode 100644 build/lib/test/util.test.js delete mode 100644 build/lib/test/util.test.ts diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 22d0b2fcdf5..c363f4d26cf 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -271,10 +271,8 @@ function packageTask(platform, arch, opts) { const date = new Date().toISOString(); const productJsonUpdate = { commit, date, checksums }; - try { + if (shouldSetupSettingsSearch()) { productJsonUpdate.settingsSearchBuildId = getSettingsSearchBuildId(packageJson); - } catch (err) { - console.warn(err); } const productJsonStream = gulp.src(['product.json'], { base: '.' }) @@ -474,9 +472,8 @@ gulp.task('upload-vscode-sourcemaps', ['minify-vscode'], () => { const allConfigDetailsPath = path.join(os.tmpdir(), 'configuration.json'); gulp.task('upload-vscode-configuration', ['generate-vscode-configuration'], () => { - const branch = process.env.BUILD_SOURCEBRANCH; - - if (!/\/master$/.test(branch) && branch.indexOf('/release/') < 0) { + if (!shouldSetupSettingsSearch()) { + const branch = process.env.BUILD_SOURCEBRANCH; console.log(`Only runs on master and release branches, not ${branch}`); return; } @@ -499,13 +496,24 @@ gulp.task('upload-vscode-configuration', ['generate-vscode-configuration'], () = })); }); -function getSettingsSearchBuildId(packageJson) { - const previous = util.getPreviousVersion(packageJson.version); +function shouldSetupSettingsSearch() { + const branch = process.env.BUILD_SOURCEBRANCH; + return branch && (/\/master$/.test(branch) || branch.indexOf('/release/') >= 0); +} +function getSettingsSearchBuildId(packageJson) { try { - const out = cp.execSync(`git rev-list ${previous}..HEAD --count`); + const branch = process.env.BUILD_SOURCEBRANCH; + const branchId = branch.indexOf('/release/') >= 0 ? 0 : + /\/master$/.test(branch) ? 1 : + 2; // Some unexpected branch + + const out = cp.execSync(`git rev-list master --count`); const count = parseInt(out.toString()); - return util.versionStringToNumber(packageJson.version) * 1e4 + count; + + // + // 1.25.1, 1,234,567 commits, master = 1250112345671 + return util.versionStringToNumber(packageJson.version) * 1e8 + count * 10 + branchId; } catch (e) { throw new Error('Could not determine build number: ' + e.toString()); } diff --git a/build/lib/test/util.test.js b/build/lib/test/util.test.js deleted file mode 100644 index ef0616173b6..00000000000 --- a/build/lib/test/util.test.js +++ /dev/null @@ -1,56 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -var assert = require("assert"); -var util = require("../util"); -function getMockTagExists(tags) { - return function (tag) { return tags.indexOf(tag) >= 0; }; -} -suite('util tests', function () { - test('getPreviousVersion - patch', function () { - assert.equal(util.getPreviousVersion('1.2.3', getMockTagExists(['1.2.2', '1.2.1', '1.2.0', '1.1.0'])), '1.2.2'); - }); - test('getPreviousVersion - patch invalid', function () { - try { - util.getPreviousVersion('1.2.2', getMockTagExists(['1.2.0', '1.1.0'])); - } - catch (e) { - // expected - return; - } - throw new Error('Expected an exception'); - }); - test('getPreviousVersion - minor', function () { - assert.equal(util.getPreviousVersion('1.2.0', getMockTagExists(['1.1.0', '1.1.1', '1.1.2', '1.1.3'])), '1.1.3'); - assert.equal(util.getPreviousVersion('1.2.0', getMockTagExists(['1.1.0', '1.0.0'])), '1.1.0'); - }); - test('getPreviousVersion - minor gap', function () { - assert.equal(util.getPreviousVersion('1.2.0', getMockTagExists(['1.1.0', '1.1.1', '1.1.3'])), '1.1.1'); - }); - test('getPreviousVersion - minor invalid', function () { - try { - util.getPreviousVersion('1.2.0', getMockTagExists(['1.0.0'])); - } - catch (e) { - // expected - return; - } - throw new Error('Expected an exception'); - }); - test('getPreviousVersion - major', function () { - assert.equal(util.getPreviousVersion('2.0.0', getMockTagExists(['1.0.0', '1.1.0', '1.2.0', '1.2.1', '1.2.2'])), '1.2.2'); - }); - test('getPreviousVersion - major invalid', function () { - try { - util.getPreviousVersion('3.0.0', getMockTagExists(['1.0.0'])); - } - catch (e) { - // expected - return; - } - throw new Error('Expected an exception'); - }); -}); diff --git a/build/lib/test/util.test.ts b/build/lib/test/util.test.ts deleted file mode 100644 index 928e730f06c..00000000000 --- a/build/lib/test/util.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import assert = require('assert'); -import util = require('../util'); - -function getMockTagExists(tags: string[]) { - return (tag: string) => tags.indexOf(tag) >= 0; -} - -suite('util tests', () => { - test('getPreviousVersion - patch', () => { - assert.equal( - util.getPreviousVersion('1.2.3', getMockTagExists(['1.2.2', '1.2.1', '1.2.0', '1.1.0'])), - '1.2.2' - ); - }); - - test('getPreviousVersion - patch invalid', () => { - try { - util.getPreviousVersion('1.2.2', getMockTagExists(['1.2.0', '1.1.0'])); - } catch (e) { - // expected - return; - } - - throw new Error('Expected an exception'); - }); - - test('getPreviousVersion - minor', () => { - assert.equal( - util.getPreviousVersion('1.2.0', getMockTagExists(['1.1.0', '1.1.1', '1.1.2', '1.1.3'])), - '1.1.3' - ); - - assert.equal( - util.getPreviousVersion('1.2.0', getMockTagExists(['1.1.0', '1.0.0'])), - '1.1.0' - ); - }); - - test('getPreviousVersion - minor gap', () => { - assert.equal( - util.getPreviousVersion('1.2.0', getMockTagExists(['1.1.0', '1.1.1', '1.1.3'])), - '1.1.1' - ); - }); - - test('getPreviousVersion - minor invalid', () => { - try { - util.getPreviousVersion('1.2.0', getMockTagExists(['1.0.0'])); - } catch (e) { - // expected - return; - } - - throw new Error('Expected an exception'); - }); - - test('getPreviousVersion - major', () => { - assert.equal( - util.getPreviousVersion('2.0.0', getMockTagExists(['1.0.0', '1.1.0', '1.2.0', '1.2.1', '1.2.2'])), - '1.2.2' - ); - }); - - test('getPreviousVersion - major invalid', () => { - try { - util.getPreviousVersion('3.0.0', getMockTagExists(['1.0.0'])); - } catch (e) { - // expected - return; - } - - throw new Error('Expected an exception'); - }); -}); diff --git a/build/lib/util.js b/build/lib/util.js index 1a2d40327cd..dd6f0b56992 100644 --- a/build/lib/util.js +++ b/build/lib/util.js @@ -14,7 +14,6 @@ var fs = require("fs"); var _rimraf = require("rimraf"); var git = require("./git"); var VinylFile = require("vinyl"); -var cp = require("child_process"); var NoCancellationToken = { isCancellationRequested: function () { return false; } }; function incremental(streamProvider, initial, supportsCancellation) { var input = es.through(); @@ -211,62 +210,6 @@ function filter(fn) { return result; } exports.filter = filter; -function tagExists(tagName) { - try { - cp.execSync("git rev-parse " + tagName, { stdio: 'ignore' }); - return true; - } - catch (e) { - return false; - } -} -/** - * Returns the version previous to the given version. Throws if a git tag for that version doesn't exist. - * Given 1.17.2, return 1.17.1 - * 1.18.0 => 1.17.2. (or the highest 1.17.x) - * 2.0.0 => 1.18.0 (or the highest 1.x) - */ -function getPreviousVersion(versionStr, _tagExists) { - if (_tagExists === void 0) { _tagExists = tagExists; } - function getLatestTagFromBase(semverArr, componentToTest) { - var baseVersion = semverArr.join('.'); - if (!_tagExists(baseVersion)) { - throw new Error('Failed to find git tag for base version, ' + baseVersion); - } - var goodTag; - do { - goodTag = semverArr.join('.'); - semverArr[componentToTest]++; - } while (_tagExists(semverArr.join('.'))); - return goodTag; - } - var semverArr = versionStringToNumberArray(versionStr); - if (semverArr[2] > 0) { - semverArr[2]--; - var previous = semverArr.join('.'); - if (!_tagExists(previous)) { - throw new Error('Failed to find git tag for previous version, ' + previous); - } - return previous; - } - else if (semverArr[1] > 0) { - semverArr[1]--; - return getLatestTagFromBase(semverArr, 2); - } - else { - semverArr[0]--; - // Find 1.x.0 for latest x - var latestMinorVersion = getLatestTagFromBase(semverArr, 1); - // Find 1.x.y for latest y - return getLatestTagFromBase(versionStringToNumberArray(latestMinorVersion), 2); - } -} -exports.getPreviousVersion = getPreviousVersion; -function versionStringToNumberArray(versionStr) { - return versionStr - .split('.') - .map(function (s) { return parseInt(s); }); -} function versionStringToNumber(versionStr) { var semverRegex = /(\d+)\.(\d+)\.(\d+)/; var match = versionStr.match(semverRegex); diff --git a/build/lib/util.ts b/build/lib/util.ts index 9dcbbe72484..e1393b86c5b 100644 --- a/build/lib/util.ts +++ b/build/lib/util.ts @@ -17,7 +17,6 @@ import * as git from './git'; import * as VinylFile from 'vinyl'; import { ThroughStream } from 'through'; import * as sm from 'source-map'; -import * as cp from 'child_process'; export interface ICancellationToken { isCancellationRequested(): boolean; @@ -271,66 +270,6 @@ export function filter(fn: (data: any) => boolean): FilterStream { return result; } -function tagExists(tagName: string): boolean { - try { - cp.execSync(`git rev-parse ${tagName}`, { stdio: 'ignore' }); - return true; - } catch (e) { - return false; - } -} - -/** - * Returns the version previous to the given version. Throws if a git tag for that version doesn't exist. - * Given 1.17.2, return 1.17.1 - * 1.18.0 => 1.17.2. (or the highest 1.17.x) - * 2.0.0 => 1.18.0 (or the highest 1.x) - */ -export function getPreviousVersion(versionStr: string, _tagExists = tagExists) { - function getLatestTagFromBase(semverArr: number[], componentToTest: number): string { - const baseVersion = semverArr.join('.'); - if (!_tagExists(baseVersion)) { - throw new Error('Failed to find git tag for base version, ' + baseVersion); - } - - let goodTag; - do { - goodTag = semverArr.join('.'); - semverArr[componentToTest]++; - } while (_tagExists(semverArr.join('.'))); - - return goodTag; - } - - const semverArr = versionStringToNumberArray(versionStr); - if (semverArr[2] > 0) { - semverArr[2]--; - const previous = semverArr.join('.'); - if (!_tagExists(previous)) { - throw new Error('Failed to find git tag for previous version, ' + previous); - } - - return previous; - } else if (semverArr[1] > 0) { - semverArr[1]--; - return getLatestTagFromBase(semverArr, 2); - } else { - semverArr[0]--; - - // Find 1.x.0 for latest x - const latestMinorVersion = getLatestTagFromBase(semverArr, 1); - - // Find 1.x.y for latest y - return getLatestTagFromBase(versionStringToNumberArray(latestMinorVersion), 2); - } -} - -function versionStringToNumberArray(versionStr: string): number[] { - return versionStr - .split('.') - .map(s => parseInt(s)); -} - export function versionStringToNumber(versionStr: string) { const semverRegex = /(\d+)\.(\d+)\.(\d+)/; const match = versionStr.match(semverRegex); From 0ce2da8220db34e190647e9142eddd3c3819691b Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 8 Jul 2018 18:30:24 -0700 Subject: [PATCH 252/283] Bump node2 --- build/builtInExtensions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index 2d53c46e935..bf739296d29 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -6,7 +6,7 @@ }, { "name": "ms-vscode.node-debug2", - "version": "1.26.0", + "version": "1.26.1", "repo": "https://github.com/Microsoft/vscode-node-debug2" } ] From bbafa06a3d3db60bbd06d4d4cd582586f7126d93 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 8 Jul 2018 22:30:39 -0700 Subject: [PATCH 253/283] Fix settings search id generation --- build/gulpfile.vscode.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index c363f4d26cf..4831c70e07b 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -508,7 +508,7 @@ function getSettingsSearchBuildId(packageJson) { /\/master$/.test(branch) ? 1 : 2; // Some unexpected branch - const out = cp.execSync(`git rev-list master --count`); + const out = cp.execSync(`git rev-list HEAD --count`); const count = parseInt(out.toString()); // From eb42dab8ec86cbdfc6d4eb5a0708e7bc5bb036de Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 9 Jul 2018 09:56:54 +0200 Subject: [PATCH 254/283] #53139 Remove zh-hans and zh-hant from the proposals list --- src/vs/platform/localizations/node/localizations.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/localizations/node/localizations.ts b/src/vs/platform/localizations/node/localizations.ts index 2e7b180fc83..46bd114b4cf 100644 --- a/src/vs/platform/localizations/node/localizations.ts +++ b/src/vs/platform/localizations/node/localizations.ts @@ -28,7 +28,7 @@ interface ILanguagePack { translations: { [id: string]: string }; } -const systemLanguages: string[] = ['de', 'en', 'en-US', 'es', 'fr', 'it', 'ja', 'ko', 'ru', 'zh-CN', 'zh-Hans', 'zh-TW', 'zh-Hant']; +const systemLanguages: string[] = ['de', 'en', 'en-US', 'es', 'fr', 'it', 'ja', 'ko', 'ru', 'zh-CN', 'zh-TW']; if (product.quality !== 'stable') { systemLanguages.push('hu'); } From 9abfc06c5102875175bec47d437199bf2d401a29 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 9 Jul 2018 10:02:54 +0200 Subject: [PATCH 255/283] fix #53749 --- .../api/node/extHostTypeConverters.ts | 1 + .../api/extHostApiCommands.test.ts | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/vs/workbench/api/node/extHostTypeConverters.ts b/src/vs/workbench/api/node/extHostTypeConverters.ts index 96b275af75f..bf942604411 100644 --- a/src/vs/workbench/api/node/extHostTypeConverters.ts +++ b/src/vs/workbench/api/node/extHostTypeConverters.ts @@ -512,6 +512,7 @@ export namespace Suggest { result.documentation = htmlContent.isMarkdownString(suggestion.documentation) ? MarkdownString.to(suggestion.documentation) : suggestion.documentation; result.sortText = suggestion.sortText; result.filterText = suggestion.filterText; + result.preselect = suggestion.preselect; // 'overwrite[Before|After]'-logic let overwriteBefore = (typeof suggestion.overwriteBefore === 'number') ? suggestion.overwriteBefore : 0; diff --git a/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts b/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts index ba37b81ff7d..80a57fac79c 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts @@ -450,6 +450,38 @@ suite('ExtHostLanguageFeatureCommands', function () { }); + test('"vscode.executeCompletionItemProvider" doesnot return a preselect field #53749', async function () { + disposables.push(extHost.registerCompletionItemProvider(defaultSelector, { + provideCompletionItems(): any { + let a = new types.CompletionItem('item1'); + a.preselect = true; + let b = new types.CompletionItem('item2'); + let c = new types.CompletionItem('item3'); + c.preselect = true; + let d = new types.CompletionItem('item4'); + return new types.CompletionList([a, b, c, d], false); + } + }, [])); + + await rpcProtocol.sync(); + + let list = await commands.executeCommand( + 'vscode.executeCompletionItemProvider', + model.uri, + new types.Position(0, 4), + undefined + ); + + assert.ok(list instanceof types.CompletionList); + assert.equal(list.items.length, 4); + + let [a, b, c, d] = list.items; + assert.equal(a.preselect, true); + assert.equal(b.preselect, undefined); + assert.equal(c.preselect, true); + assert.equal(d.preselect, undefined); + }); + // --- quickfix test('QuickFix, back and forth', function () { From b18b79593674b4687241fe33d001f3347098846e Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 9 Jul 2018 10:15:55 +0200 Subject: [PATCH 256/283] fixes #53668 --- extensions/git/package.json | 13 +++++++++++-- extensions/git/package.nls.json | 2 +- extensions/git/src/model.ts | 11 +++++++++-- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 9a723045468..e17acd5e8bf 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -881,7 +881,16 @@ "scope": "application" }, "git.autoRepositoryDetection": { - "type": "boolean", + "type": [ + "boolean", + "string" + ], + "enum": [ + true, + false, + "subFolders", + "openEditors" + ], "description": "%config.autoRepositoryDetection%", "default": true }, @@ -1151,4 +1160,4 @@ "@types/which": "^1.0.28", "mocha": "^3.2.0" } -} +} \ No newline at end of file diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 9b1a2332d1f..85d30e3cc4a 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -51,7 +51,7 @@ "command.stashPopLatest": "Pop Latest Stash", "config.enabled": "Whether git is enabled", "config.path": "Path to the git executable", - "config.autoRepositoryDetection": "Whether repositories should be automatically detected", + "config.autoRepositoryDetection": "Configures when repositories should be automatically detected.", "config.autorefresh": "Whether auto refreshing is enabled", "config.autofetch": "Whether auto fetching is enabled", "config.enableLongCommitWarning": "Whether long commit messages should be warned about", diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 65036c93a7b..be89cbbf812 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -91,6 +91,13 @@ export class Model { * for git repositories. */ private async scanWorkspaceFolders(): Promise { + const config = workspace.getConfiguration('git'); + const autoRepositoryDetection = config.get('autoRepositoryDetection'); + + if (autoRepositoryDetection !== true && autoRepositoryDetection !== 'subFolders') { + return; + } + for (const folder of workspace.workspaceFolders || []) { const root = folder.uri.fsPath; @@ -159,9 +166,9 @@ export class Model { private onDidChangeVisibleTextEditors(editors: TextEditor[]): void { const config = workspace.getConfiguration('git'); - const enabled = config.get('autoRepositoryDetection') === true; + const autoRepositoryDetection = config.get('autoRepositoryDetection'); - if (!enabled) { + if (autoRepositoryDetection !== true && autoRepositoryDetection !== 'openEditors') { return; } From a5266b1d6a7d7059af517262038071c69c493c1f Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 9 Jul 2018 10:28:19 +0200 Subject: [PATCH 257/283] use collpase icons to separate elements --- .../ui/breadcrumbs/breadcrumbsWidget.css | 18 ++++++++++++++++-- .../base/browser/ui/breadcrumbs/collapsed.svg | 1 + .../browser/ui/breadcrumbs/collpased-dark.svg | 1 + 3 files changed, 18 insertions(+), 2 deletions(-) create mode 100755 src/vs/base/browser/ui/breadcrumbs/collapsed.svg create mode 100755 src/vs/base/browser/ui/breadcrumbs/collpased-dark.svg diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css index 0a6f4ced2c5..df5e37ddf8c 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css @@ -14,10 +14,9 @@ .monaco-breadcrumbs .monaco-breadcrumb-item { display: flex; align-items: center; - padding: 0 5px 0 3px; flex: 0 1 auto; white-space: nowrap; - cursor: pointer; + cursor: default; align-self: center; height: 100%; } @@ -25,3 +24,18 @@ .monaco-breadcrumbs .monaco-breadcrumb-item:nth-child(2) { /*first-child is the style-element*/ padding-left: 8px; } + +.monaco-breadcrumbs .monaco-breadcrumb-item:not(:last-child)::after { + background-image: url(./collapsed.svg); + width: 16px; + height: 16px; + display: inline-block; + background-size: 16px; + background-position: 50% 50%; + content: ' '; +} + +.vs-dark .monaco-breadcrumbs .monaco-breadcrumb-item:not(:last-child)::after { + background-image: url(./collpased-dark.svg); + +} diff --git a/src/vs/base/browser/ui/breadcrumbs/collapsed.svg b/src/vs/base/browser/ui/breadcrumbs/collapsed.svg new file mode 100755 index 00000000000..3a63808c358 --- /dev/null +++ b/src/vs/base/browser/ui/breadcrumbs/collapsed.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/base/browser/ui/breadcrumbs/collpased-dark.svg b/src/vs/base/browser/ui/breadcrumbs/collpased-dark.svg new file mode 100755 index 00000000000..cf5c3641aa7 --- /dev/null +++ b/src/vs/base/browser/ui/breadcrumbs/collpased-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file From 26da10144c44d4301f7936d20be62e25b4d5395e Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 9 Jul 2018 10:33:50 +0200 Subject: [PATCH 258/283] less styles for breadcrumbs --- .../ui/breadcrumbs/breadcrumbsWidget.ts | 36 ++++--------------- 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index 5a77dafaae2..aea7bef9669 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -44,14 +44,8 @@ export class SimpleBreadcrumbsItem extends BreadcrumbsItem { export interface IBreadcrumbsWidgetStyles { breadcrumbsBackground?: Color; - breadcrumbsItemHoverBackground?: Color; - breadcrumbsItemHoverForeground?: Color; - breadcrumbsItemFocusBackground?: Color; - breadcrumbsItemFocusForeground?: Color; - breadcrumbsActiveItemSelectionBackground?: Color; - breadcrumbsActiveItemSelectionForeground?: Color; - breadcrumbsInactiveItemSelectionBackground?: Color; - breadcrumbsInactiveItemSelectionForeground?: Color; + breadcrumbsActiveForeground?: Color; + breadcrumbsInactiveForeground?: Color; } export class BreadcrumbsWidget { @@ -123,29 +117,11 @@ export class BreadcrumbsWidget { if (style.breadcrumbsBackground) { content += `.monaco-breadcrumbs { background-color: ${style.breadcrumbsBackground}}`; } - if (style.breadcrumbsItemFocusForeground) { - content += `.monaco-breadcrumbs:focus .monaco-breadcrumb-item.focused { color: ${style.breadcrumbsItemFocusForeground}}\n`; + if (style.breadcrumbsActiveForeground) { + content += `.monaco-breadcrumbs:focus .monaco-breadcrumb-item { color: ${style.breadcrumbsActiveForeground}}\n`; } - if (style.breadcrumbsItemFocusBackground) { - content += `.monaco-breadcrumbs:focus .monaco-breadcrumb-item.focused { background-color: ${style.breadcrumbsItemFocusBackground}}\n`; - } - if (style.breadcrumbsItemFocusForeground) { - content += `.monaco-breadcrumbs .monaco-breadcrumb-item:hover { color: ${style.breadcrumbsItemHoverForeground}}\n`; - } - if (style.breadcrumbsItemFocusBackground) { - content += `.monaco-breadcrumbs .monaco-breadcrumb-item:hover { background-color: ${style.breadcrumbsItemHoverBackground}}\n`; - } - if (style.breadcrumbsActiveItemSelectionForeground) { - content += `.monaco-breadcrumbs:hover .monaco-breadcrumb-item.selected { color: ${style.breadcrumbsActiveItemSelectionForeground}}\n`; - } - if (style.breadcrumbsActiveItemSelectionBackground) { - content += `.monaco-breadcrumbs:hover .monaco-breadcrumb-item.selected { background-color: ${style.breadcrumbsActiveItemSelectionBackground}}\n`; - } - if (style.breadcrumbsInactiveItemSelectionForeground) { - content += `.monaco-breadcrumbs .monaco-breadcrumb-item.selected { color: ${style.breadcrumbsInactiveItemSelectionForeground}}\n`; - } - if (style.breadcrumbsInactiveItemSelectionBackground) { - content += `.monaco-breadcrumbs .monaco-breadcrumb-item.selected { background-color: ${style.breadcrumbsInactiveItemSelectionBackground}}\n`; + if (style.breadcrumbsInactiveForeground) { + content += `.monaco-breadcrumbs .monaco-breadcrumb-item { color: ${style.breadcrumbsInactiveForeground}}\n`; } if (this._styleElement.innerHTML !== content) { this._styleElement.innerHTML = content; From 1b0464601ac759db4215e4aab3336a074450a806 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 9 Jul 2018 10:36:23 +0200 Subject: [PATCH 259/283] :lipstick: --- src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css | 4 ---- .../browser/parts/editor/media/editorbreadcrumbs.css | 5 +++++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css index df5e37ddf8c..08b25c9bc4e 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css @@ -21,10 +21,6 @@ height: 100%; } -.monaco-breadcrumbs .monaco-breadcrumb-item:nth-child(2) { /*first-child is the style-element*/ - padding-left: 8px; -} - .monaco-breadcrumbs .monaco-breadcrumb-item:not(:last-child)::after { background-image: url(./collapsed.svg); width: 16px; diff --git a/src/vs/workbench/browser/parts/editor/media/editorbreadcrumbs.css b/src/vs/workbench/browser/parts/editor/media/editorbreadcrumbs.css index a141046f6bb..e3b45ae4166 100644 --- a/src/vs/workbench/browser/parts/editor/media/editorbreadcrumbs.css +++ b/src/vs/workbench/browser/parts/editor/media/editorbreadcrumbs.css @@ -6,3 +6,8 @@ .monaco-workbench>.part.editor>.content .editor-group-container:not(.active) .editor-breadcrumbs { opacity: .8; } + + +.monaco-workbench>.part.editor>.content .editor-group-container .editor-breadcrumbs .monaco-breadcrumbs .monaco-breadcrumb-item:nth-child(2) { /*first-child is the style-element*/ + padding-left: 8px; +} From 19df280c7f7cf73d9bafcde82a9e93b14026cfc2 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 9 Jul 2018 10:37:51 +0200 Subject: [PATCH 260/283] fixes #53849 --- extensions/git/src/git.ts | 4 ++-- extensions/git/src/repository.ts | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index cea9edd799a..a6d79456ed2 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -795,10 +795,10 @@ export class Repository { return parseLsFiles(stdout); } - async getGitRelativePath(treeish: string, relativePath: string): Promise { + async getGitRelativePath(ref: string, relativePath: string): Promise { const relativePathLowercase = relativePath.toLowerCase(); const dirname = path.posix.dirname(relativePath) + '/'; - const elements: { file: string; }[] = treeish ? await this.lstree(treeish, dirname) : await this.lsfiles(dirname); + const elements: { file: string; }[] = ref ? await this.lstree(ref, dirname) : await this.lsfiles(dirname); const element = elements.filter(file => file.file.toLowerCase() === relativePathLowercase)[0]; if (!element) { diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index ece6ef8fb37..b3af0698312 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -882,10 +882,6 @@ export class Repository implements Disposable { const defaultEncoding = configFiles.get('encoding'); const autoGuessEncoding = configFiles.get('autoGuessEncoding'); - if (ref === '') { - ref = 'HEAD'; - } - try { return await this.repository.bufferString(`${ref}:${relativePath}`, defaultEncoding, autoGuessEncoding); } catch (err) { From 7dad62c671f3aa3ac2208b790ac8f659426310ae Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Mon, 9 Jul 2018 10:50:30 +0200 Subject: [PATCH 261/283] Remove commas --- src/vs/vscode.proposed.d.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 819ddb33c41..6dcc58cc6df 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -663,7 +663,7 @@ declare module 'vscode' { * of items of type T. * * Note that in many cases the more convenient [window.showQuickPick](#window.showQuickPick) - * is easier to use. [window.createQuickPick](#window.createQuickPick) should be used, + * is easier to use. [window.createQuickPick](#window.createQuickPick) should be used * when [window.showQuickPick](#window.showQuickPick) does not offer the required flexibility. * * @return A new [QuickPick](#QuickPick). @@ -674,7 +674,7 @@ declare module 'vscode' { * Creates a [InputBox](#InputBox) to let the user enter some text input. * * Note that in many cases the more convenient [window.showInputBox](#window.showInputBox) - * is easier to use. [window.createInputBox](#window.createInputBox) should be used, + * is easier to use. [window.createInputBox](#window.createInputBox) should be used * when [window.showInputBox](#window.showInputBox) does not offer the required flexibility. * * @return A new [InputBox](#InputBox). @@ -779,7 +779,7 @@ declare module 'vscode' { * selecting multiple items. * * Note that in many cases the more convenient [window.showQuickPick](#window.showQuickPick) - * is easier to use. [window.createQuickPick](#window.createQuickPick) should be used, + * is easier to use. [window.createQuickPick](#window.createQuickPick) should be used * when [window.showQuickPick](#window.showQuickPick) does not offer the required flexibility. */ export interface QuickPick extends QuickInput { @@ -859,7 +859,7 @@ declare module 'vscode' { * A concrete [QuickInput](#QuickInput) to let the user input a text value. * * Note that in many cases the more convenient [window.showInputBox](#window.showInputBox) - * is easier to use. [window.createInputBox](#window.createInputBox) should be used, + * is easier to use. [window.createInputBox](#window.createInputBox) should be used * when [window.showInputBox](#window.showInputBox) does not offer the required flexibility. */ export interface InputBox extends QuickInput { From 866032fba4024cbeb3808a846ae6fc32196859fb Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Mon, 9 Jul 2018 10:55:35 +0200 Subject: [PATCH 262/283] protect against terminated debug adapter; fixes #53834 --- .../debug/electron-browser/rawDebugSession.ts | 67 ++++++++++--------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.ts b/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.ts index 645efdd8418..73740d649f3 100644 --- a/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.ts +++ b/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.ts @@ -428,43 +428,46 @@ export class RawDebugSession implements debug.IRawSession { private dispatchRequest(request: DebugProtocol.Request): void { - const response: DebugProtocol.Response = { - type: 'response', - seq: 0, - command: request.command, - request_seq: request.seq, - success: true - }; + if (this.debugAdapter) { - if (request.command === 'runInTerminal') { + const response: DebugProtocol.Response = { + type: 'response', + seq: 0, + command: request.command, + request_seq: request.seq, + success: true + }; - this._debugger.runInTerminal(request.arguments).then(_ => { - response.body = {}; - this.debugAdapter.sendResponse(response); - }, err => { + if (request.command === 'runInTerminal') { + + this._debugger.runInTerminal(request.arguments).then(_ => { + response.body = {}; + this.debugAdapter.sendResponse(response); + }, err => { + response.success = false; + response.message = err.message; + this.debugAdapter.sendResponse(response); + }); + + } else if (request.command === 'handshake') { + try { + const vsda = require.__$__nodeRequire('vsda'); + const obj = new vsda.signer(); + const sig = obj.sign(request.arguments.value); + response.body = { + signature: sig + }; + this.debugAdapter.sendResponse(response); + } catch (e) { + response.success = false; + response.message = e.message; + this.debugAdapter.sendResponse(response); + } + } else { response.success = false; - response.message = err.message; - this.debugAdapter.sendResponse(response); - }); - - } else if (request.command === 'handshake') { - try { - const vsda = require.__$__nodeRequire('vsda'); - const obj = new vsda.signer(); - const sig = obj.sign(request.arguments.value); - response.body = { - signature: sig - }; - this.debugAdapter.sendResponse(response); - } catch (e) { - response.success = false; - response.message = e.message; + response.message = `unknown request '${request.command}'`; this.debugAdapter.sendResponse(response); } - } else { - response.success = false; - response.message = `unknown request '${request.command}'`; - this.debugAdapter.sendResponse(response); } } From 2f041f0a9fc23bc83f810f6139bcad7d43b5cc88 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 9 Jul 2018 10:58:21 +0200 Subject: [PATCH 263/283] fix #53837 --- src/vs/workbench/browser/labels.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index 0db9f7aae65..3f6da3e94dc 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -341,15 +341,17 @@ export function getIconClasses(modelService: IModelService, modeService: IModeSe // Files else { - // Name - classes.push(`${name}-name-file-icon`); + // Name & Extension(s) + if (name) { + classes.push(`${name}-name-file-icon`); - // Extension(s) - const dotSegments = name.split('.'); - for (let i = 1; i < dotSegments.length; i++) { - classes.push(`${dotSegments.slice(i).join('.')}-ext-file-icon`); // add each combination of all found extensions if more than one + const dotSegments = name.split('.'); + for (let i = 1; i < dotSegments.length; i++) { + classes.push(`${dotSegments.slice(i).join('.')}-ext-file-icon`); // add each combination of all found extensions if more than one + } + + classes.push(`ext-file-icon`); // extra segment to increase file-ext score } - classes.push(`ext-file-icon`); // extra segment to increase file-ext score // Configured Language let configuredLangId = getConfiguredLangId(modelService, resource); From 559c2929e0d790b41109f7e52aad55228da53e00 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 9 Jul 2018 10:59:41 +0200 Subject: [PATCH 264/283] wire up pickers --- .../ui/breadcrumbs/breadcrumbsWidget.ts | 17 +- .../browser/parts/editor/editorBreadcrumbs.ts | 210 +++++------------- 2 files changed, 69 insertions(+), 158 deletions(-) diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index aea7bef9669..0ed02f6e1b1 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -48,6 +48,11 @@ export interface IBreadcrumbsWidgetStyles { breadcrumbsInactiveForeground?: Color; } +export interface IBreadcrumbsItemEvent { + item: BreadcrumbsItem; + node: HTMLElement; +} + export class BreadcrumbsWidget { private readonly _disposables = new Array(); @@ -55,12 +60,12 @@ export class BreadcrumbsWidget { private readonly _styleElement: HTMLStyleElement; private readonly _scrollable: DomScrollableElement; - private readonly _onDidSelectItem = new Emitter(); - private readonly _onDidFocusItem = new Emitter(); + private readonly _onDidSelectItem = new Emitter(); + private readonly _onDidFocusItem = new Emitter(); private readonly _onDidChangeFocus = new Emitter(); - readonly onDidSelectItem: Event = this._onDidSelectItem.event; - readonly onDidFocusItem: Event = this._onDidFocusItem.event; + readonly onDidSelectItem: Event = this._onDidSelectItem.event; + readonly onDidFocusItem: Event = this._onDidFocusItem.event; readonly onDidChangeFocus: Event = this._onDidChangeFocus.event; private readonly _items = new Array(); @@ -153,7 +158,7 @@ export class BreadcrumbsWidget { this._focusedItemIdx = nth; dom.addClass(this._nodes[this._focusedItemIdx], 'focused'); this._scrollable.setScrollPosition({ scrollLeft: this._nodes[this._focusedItemIdx].offsetLeft }); - this._onDidFocusItem.fire(this._items[this._focusedItemIdx]); + this._onDidFocusItem.fire({ item: this._items[this._focusedItemIdx], node: this._nodes[this._focusedItemIdx] }); return true; } @@ -175,7 +180,7 @@ export class BreadcrumbsWidget { } this._selectedItemIdx = nth; dom.addClass(this._nodes[this._selectedItemIdx], 'selected'); - this._onDidSelectItem.fire(this._items[this._selectedItemIdx]); + this._onDidSelectItem.fire({ item: this._items[this._selectedItemIdx], node: this._nodes[this._selectedItemIdx] }); } setItems(items: BreadcrumbsItem[]): void { diff --git a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts index d012ecf2a37..c824ecef19d 100644 --- a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts @@ -6,23 +6,25 @@ 'use strict'; import * as dom from 'vs/base/browser/dom'; -import { BreadcrumbsItem, BreadcrumbsWidget } from 'vs/base/browser/ui/breadcrumbs/breadcrumbsWidget'; +import { BreadcrumbsItem, BreadcrumbsWidget, IBreadcrumbsItemEvent } from 'vs/base/browser/ui/breadcrumbs/breadcrumbsWidget'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { compareFileNames } from 'vs/base/common/comparers'; import { debounceEvent, Emitter, Event } from 'vs/base/common/event'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { isEqual } from 'vs/base/common/resources'; +import { isEqual, dirname } from 'vs/base/common/resources'; import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { IDataSource, IRenderer, ISelectionEvent, ISorter, ITree, ITreeConfiguration } from 'vs/base/parts/tree/browser/tree'; import 'vs/css!./media/editorbreadcrumbs'; import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { OutlineElement, OutlineGroup, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; +import { Range } from 'vs/editor/common/core/range'; +import { OutlineElement, OutlineGroup, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; import { OutlineController, OutlineDataSource, OutlineItemComparator, OutlineRenderer } from 'vs/editor/contrib/documentSymbols/outlineTree'; import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { FileKind, IFileService, IFileStat } from 'vs/platform/files/common/files'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IConstructorSignature2, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { WorkbenchTree } from 'vs/platform/list/browser/listService'; import { attachBreadcrumbsStyler } from 'vs/platform/theme/common/styler'; @@ -32,6 +34,7 @@ import { FileLabel } from 'vs/workbench/browser/labels'; import { BreadcrumbElement, EditorBreadcrumbsModel, FileElement } from 'vs/workbench/browser/parts/editor/editorBreadcrumbsModel'; import { EditorInput } from 'vs/workbench/common/editor'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorBreadcrumbs, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; @@ -106,6 +109,8 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { container: HTMLElement, private readonly _editorGroup: IEditorGroup, @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IContextViewService private readonly _contextViewService: IContextViewService, + @IEditorService private readonly _editorService: IEditorService, @IFileService private readonly _fileService: IFileService, @IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @@ -162,107 +167,6 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { this._widget.setItems(model.getElements().map(element => new Item(element, this._instantiationService))); this._breadcrumbsDisposables.push(model, listener); - - - // const render = (element: FileElement, target: HTMLElement, disposables: IDisposable[]) => { - // let label = this._instantiationService.createInstance(FileLabel, target, {}); - // label.setFile(element.uri, { fileKind: element.kind, hidePath: true, fileDecorations: { colors: false, badges: false } }); - // disposables.push(label); - // }; - - // let fileItems: RenderedBreadcrumbsItem[] = []; - // let workspace = this._workspaceService.getWorkspaceFolder(uri); - // let path = uri.path; - - // while (true) { - // let first = fileItems.length === 0; - // let name = paths.basename(path); - // uri = uri.with({ path }); - // if (workspace && isEqual(workspace.uri, uri, true)) { - // break; - // } - // fileItems.unshift(new RenderedBreadcrumbsItem( - // render, - // new FileElement(name, first ? FileKind.FILE : FileKind.FOLDER, uri), - // !first - // )); - // path = paths.dirname(path); - // if (path === '/') { - // break; - // } - // } - - // this._widget.splice(0, this._widget.items().length, fileItems); - - // let control = this._editorGroup.activeControl.getControl() as ICodeEditor; - - // let model = new EditorBreadcrumbsModel(input.getResource(), isCodeEditor(control) ? control : undefined, this._workspaceService); - // let listener = model.onDidUpdate(_ => console.log(model.getElements())); - // console.log(model.getElements()); - // this._breadcrumbsDisposables.push(model, listener); - - // if (!isCodeEditor(control)) { - // return; - // } - - - // let oracle = new class extends Emitter { - - // private readonly _listener: IDisposable[] = []; - - // constructor() { - // super(); - // DocumentSymbolProviderRegistry.onDidChange(_ => this.fire()); - // this._listener.push(control.onDidChangeModel(_ => this._checkModel())); - // this._listener.push(control.onDidChangeModelLanguage(_ => this._checkModel())); - // this._listener.push(setDisposableTimeout(_ => this._checkModel(), 0)); - // } - - // private _checkModel() { - // if (control.getModel() && isEqual(control.getModel().uri, input.getResource())) { - // this.fire(); - // } - // } - - // dispose(): void { - // dispose(this._listener); - // super.dispose(); - // } - // }; - - // this._breadcrumbsDisposables.push(oracle); - - // oracle.event(async _ => { - // let model = await asDisposablePromise(OutlineModel.create(control.getModel(), CancellationToken.None), undefined, this._breadcrumbsDisposables).promise; - // if (!model) { - // return; - // } - // type OutlineItem = OutlineElement | OutlineGroup; - - // let render = (element: OutlineItem, target: HTMLElement, disposables: IDisposable[]) => { - // let label = this._instantiationService.createInstance(FileLabel, target, {}); - // if (element instanceof OutlineElement) { - // label.setLabel({ name: element.symbol.name }); - // } else { - // label.setLabel({ name: element.provider.displayName }); - // } - // disposables.push(label); - // }; - - // let showOutlineForPosition = (position: IPosition) => { - // let element = model.getItemEnclosingPosition(position) as TreeElement; - // let outlineItems: RenderedBreadcrumbsItem[] = []; - // while (element instanceof OutlineGroup || element instanceof OutlineElement) { - // outlineItems.unshift(new RenderedBreadcrumbsItem(render, element, !!first(element.children))); - // element = element.parent; - // } - // // todo@joh compare items for equality and only update changed... - // this._widget.splice(fileItems.length, this._widget.items().length - fileItems.length, outlineItems); - // }; - - // showOutlineForPosition(control.getPosition()); - // debounceEvent(control.onDidChangeCursorPosition, (last, cur) => cur, 100)(_ => showOutlineForPosition(control.getPosition()), undefined, this._breadcrumbsDisposables); - // }); } closeEditor(input: EditorInput): void { @@ -289,56 +193,48 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { } } - private _onDidSelectItem(item: Item): void { + private _onDidSelectItem(event: IBreadcrumbsItemEvent): void { this._editorGroup.focus(); - // let ctor: IConstructorSignature2; - // let input: any; - // if (item.element instanceof FileElement) { - // ctor = BreadcrumbsFilePicker; - // input = dirname(item.element.uri); - // } else { - // ctor = BreadcrumbsOutlinePicker; - // input = item.element.parent; - // } + this._contextViewService.showContextView({ + getAnchor() { + return event.node; + }, + render: (container: HTMLElement) => { + dom.addClasses(container, 'show-file-icons'); + let { element } = event.item as Item; + let ctor: IConstructorSignature2 = element instanceof FileElement ? BreadcrumbsFilePicker : BreadcrumbsOutlinePicker; + let res = this._instantiationService.createInstance(ctor, container, element); + res.layout({ width: 250, height: 300 }); + res.onDidPickElement(data => { + this._contextViewService.hideContextView(); + this._widget.select(undefined); + if (!data) { + return; + } + if (URI.isUri(data)) { + // open new editor + this._editorService.openEditor({ resource: data }); - // this._contextViewService.showContextView({ - // getAnchor() { - // return item.node; - // }, - // render: (container: HTMLElement) => { - // dom.addClasses(container, 'show-file-icons'); - // let res = this._instantiationService.createInstance(ctor, container, input); - // res.layout({ width: 250, height: 300 }); - // res.onDidPickElement(data => { - // this._contextViewService.hideContextView(); - // this._widget.select(undefined); - // if (!data) { - // return; - // } - // if (URI.isUri(data)) { - // // open new editor - // this._editorService.openEditor({ resource: data }); + } else if (data instanceof OutlineElement) { - // } else if (data instanceof OutlineElement) { + let resource: URI; + let candidate = data.parent; + while (candidate) { + if (candidate instanceof OutlineModel) { + resource = candidate.textModel.uri; + break; + } + candidate = candidate.parent; + } - // let resource: URI; - // let candidate = data.parent; - // while (candidate) { - // if (candidate instanceof OutlineModel) { - // resource = candidate.textModel.uri; - // break; - // } - // candidate = candidate.parent; - // } + this._editorService.openEditor({ resource, options: { selection: Range.collapseToStart(data.symbol.selectionRange) } }); - // this._editorService.openEditor({ resource, options: { selection: Range.collapseToStart(data.symbol.selectionRange) } }); - - // } - // }); - // return res; - // }, - // }); + } + }); + return res; + }, + }); } } @@ -355,7 +251,7 @@ export abstract class BreadcrumbsPicker { constructor( container: HTMLElement, - input: any, + input: BreadcrumbElement, @IInstantiationService protected readonly _instantiationService: IInstantiationService, @IThemeService protected readonly _themeService: IThemeService, ) { @@ -370,7 +266,7 @@ export abstract class BreadcrumbsPicker { this.focus.onDidBlur(_ => this._onDidPickElement.fire(undefined), undefined, this._disposables); this._tree.domFocus(); - this._tree.setInput(input); + this._tree.setInput(this._getInput(input)); } dispose(): void { @@ -386,6 +282,7 @@ export abstract class BreadcrumbsPicker { this._tree.layout(dim.height, dim.width); } + protected abstract _getInput(input: BreadcrumbElement): any; protected abstract _completeTreeConfiguration(config: ITreeConfiguration): ITreeConfiguration; protected abstract _onDidChangeSelection(e: any): void; } @@ -469,6 +366,11 @@ export class FileSorter implements ISorter { export class BreadcrumbsFilePicker extends BreadcrumbsPicker { + protected _getInput(input: BreadcrumbElement): any { + let { uri } = (input as FileElement); + return dirname(uri); + } + protected _completeTreeConfiguration(config: ITreeConfiguration): ITreeConfiguration { // todo@joh reuse explorer implementations? config.dataSource = this._instantiationService.createInstance(FileDataSource); @@ -488,6 +390,10 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker { export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker { + protected _getInput(input: BreadcrumbElement): any { + return (input as TreeElement).parent; + } + protected _completeTreeConfiguration(config: ITreeConfiguration): ITreeConfiguration { config.dataSource = this._instantiationService.createInstance(OutlineDataSource); config.renderer = this._instantiationService.createInstance(OutlineRenderer); @@ -497,7 +403,7 @@ export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker { } protected _onDidChangeSelection(e: ISelectionEvent): void { - if (e.payload && !e.payload.didClickElement) { + if (e.payload && e.payload.didClickOnTwistie) { return; } let [first] = e.selection; From b6a8d0c7614a3854cc85d9e694287b3758d9a185 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 9 Jul 2018 11:32:40 +0200 Subject: [PATCH 265/283] split outline styles into outline tree/panel styles, style context views --- .../documentSymbols/media/outlineTree.css | 66 +++++++++++++++++++ .../contrib/documentSymbols/outlineTree.ts | 7 +- .../browser/parts/editor/editorBreadcrumbs.ts | 2 +- .../outline/electron-browser/outlinePanel.css | 53 --------------- 4 files changed, 71 insertions(+), 57 deletions(-) create mode 100644 src/vs/editor/contrib/documentSymbols/media/outlineTree.css diff --git a/src/vs/editor/contrib/documentSymbols/media/outlineTree.css b/src/vs/editor/contrib/documentSymbols/media/outlineTree.css new file mode 100644 index 00000000000..f62841df77d --- /dev/null +++ b/src/vs/editor/contrib/documentSymbols/media/outlineTree.css @@ -0,0 +1,66 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-tree.focused .selected .outline-element-label, .monaco-tree.focused .selected .outline-element-decoration { + /* make sure selection color wins when a label is being selected */ + color: inherit !important; +} + +.monaco-tree .outline-element { + display: flex; + flex: 1; + flex-flow: row nowrap; + align-items: center; +} + +.monaco-tree .outline-element .outline-element-icon { + padding-right: 3px; +} + +/* .monaco-tree.no-icons .outline-element .outline-element-icon { + display: none; +} */ + +.monaco-tree .outline-element .outline-element-label { + text-overflow: ellipsis; + overflow: hidden; + color: var(--outline-element-color); +} + +.monaco-tree .outline-element .outline-element-label .monaco-highlighted-label .highlight { + font-weight: bold; +} + +.monaco-tree .outline-element .outline-element-detail { + visibility: hidden; + flex: 1; + flex-basis: 10%; + opacity: 0.8; + overflow: hidden; + text-overflow: ellipsis; + font-size: 90%; + padding-left: 4px; + padding-top: 3px; +} + +.monaco-tree .monaco-tree-row.focused .outline-element .outline-element-detail { + visibility: inherit; +} + +.monaco-tree .outline-element .outline-element-decoration { + opacity: 0.75; + font-size: 90%; + font-weight: 600; + padding: 0 12px 0 5px; + margin-left: auto; + text-align: center; + color: var(--outline-element-color); +} + +.monaco-tree .outline-element .outline-element-decoration.bubble { + font-family: octicons; + font-size: 14px; + opacity: 0.4; +} diff --git a/src/vs/editor/contrib/documentSymbols/outlineTree.ts b/src/vs/editor/contrib/documentSymbols/outlineTree.ts index 19489cbae98..e1181ea33cd 100644 --- a/src/vs/editor/contrib/documentSymbols/outlineTree.ts +++ b/src/vs/editor/contrib/documentSymbols/outlineTree.ts @@ -5,22 +5,23 @@ 'use strict'; import * as dom from 'vs/base/browser/dom'; -import 'vs/css!./media/symbol-icons'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { values } from 'vs/base/common/collections'; import { createMatches } from 'vs/base/common/filters'; import { TPromise } from 'vs/base/common/winjs.base'; import { IDataSource, IFilter, IRenderer, ISorter, ITree } from 'vs/base/parts/tree/browser/tree'; +import 'vs/css!./media/outlineTree'; +import 'vs/css!./media/symbol-icons'; import { Range } from 'vs/editor/common/core/range'; -import { symbolKindToCssClass, SymbolKind } from 'vs/editor/common/modes'; +import { SymbolKind, symbolKindToCssClass } from 'vs/editor/common/modes'; import { OutlineElement, OutlineGroup, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; import { localize } from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { WorkbenchTreeController } from 'vs/platform/list/browser/listService'; import { MarkerSeverity } from 'vs/platform/markers/common/markers'; import { listErrorForeground, listWarningForeground } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export enum OutlineItemCompareType { ByPosition, diff --git a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts index c824ecef19d..314b6848ead 100644 --- a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts @@ -201,7 +201,7 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { return event.node; }, render: (container: HTMLElement) => { - dom.addClasses(container, 'show-file-icons'); + dom.addClasses(container, 'monaco-workbench', 'show-file-icons'); let { element } = event.item as Item; let ctor: IConstructorSignature2 = element instanceof FileElement ? BreadcrumbsFilePicker : BreadcrumbsOutlinePicker; let res = this._instantiationService.createInstance(ctor, container, element); diff --git a/src/vs/workbench/parts/outline/electron-browser/outlinePanel.css b/src/vs/workbench/parts/outline/electron-browser/outlinePanel.css index 036064d3c6a..858ba8e3266 100644 --- a/src/vs/workbench/parts/outline/electron-browser/outlinePanel.css +++ b/src/vs/workbench/parts/outline/electron-browser/outlinePanel.css @@ -76,59 +76,6 @@ color: inherit !important; } -.monaco-workbench .outline-panel .outline-element { - display: flex; - flex: 1; - flex-flow: row nowrap; - align-items: center; -} - -.monaco-workbench .outline-panel .outline-element .outline-element-icon { - padding-right: 3px; -} - .monaco-workbench .outline-panel.no-icons .outline-element .outline-element-icon { display: none; } - -.monaco-workbench .outline-panel .outline-element .outline-element-label { - text-overflow: ellipsis; - overflow: hidden; - color: var(--outline-element-color); -} - -.monaco-workbench .outline-panel .outline-element .outline-element-label .monaco-highlighted-label .highlight { - font-weight: bold; -} - -.monaco-workbench .outline-panel .outline-element .outline-element-detail { - visibility: hidden; - flex: 1; - flex-basis: 10%; - opacity: 0.8; - overflow: hidden; - text-overflow: ellipsis; - font-size: 90%; - padding-left: 4px; - padding-top: 3px; -} - -.monaco-workbench .outline-panel .monaco-tree-row.focused .outline-element .outline-element-detail { - visibility: inherit; -} - -.monaco-workbench .outline-panel .outline-element .outline-element-decoration { - opacity: 0.75; - font-size: 90%; - font-weight: 600; - padding: 0 12px 0 5px; - margin-left: auto; - text-align: center; - color: var(--outline-element-color); -} - -.monaco-workbench .outline-panel .outline-element .outline-element-decoration.bubble { - font-family: octicons; - font-size: 14px; - opacity: 0.4; -} From 1030cf9007ae8413dace90d53db82816c401c045 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 9 Jul 2018 11:52:42 +0200 Subject: [PATCH 266/283] fix typo in fileSearch --- src/vs/workbench/services/search/node/searchIpc.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/search/node/searchIpc.ts b/src/vs/workbench/services/search/node/searchIpc.ts index 6caafb0e822..3450ab3e2fd 100644 --- a/src/vs/workbench/services/search/node/searchIpc.ts +++ b/src/vs/workbench/services/search/node/searchIpc.ts @@ -25,7 +25,7 @@ export class SearchChannel implements ISearchChannel { listen(event: string, arg?: any): Event { switch (event) { case 'telemetry': return this.service.onTelemetry; - case 'fileSearch': return this.service.textSearch(arg); + case 'fileSearch': return this.service.fileSearch(arg); case 'textSearch': return this.service.textSearch(arg); } throw new Error('Event not found'); From d17ceea08ecc8c60b4510682f10d979fda048ecb Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 9 Jul 2018 11:57:24 +0200 Subject: [PATCH 267/283] Update to powershell grammars. Fixes #52956 --- .../syntaxes/powershell.tmLanguage.json | 210 ++++--- .../test/colorize-results/test_ps1.json | 516 ++++++++++++------ 2 files changed, 472 insertions(+), 254 deletions(-) diff --git a/extensions/powershell/syntaxes/powershell.tmLanguage.json b/extensions/powershell/syntaxes/powershell.tmLanguage.json index 60dee137b19..ca3fd4d5a4d 100644 --- a/extensions/powershell/syntaxes/powershell.tmLanguage.json +++ b/extensions/powershell/syntaxes/powershell.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/PowerShell/EditorSyntax/commit/6f5438611c54922ea94c81532a2dcfee72190039", + "version": "https://github.com/PowerShell/EditorSyntax/commit/146e421358945dbfbd24a9dcf56d759bdb0693db", "name": "PowerShell", "scopeName": "source.powershell", "patterns": [ @@ -71,7 +71,17 @@ }, { "begin": "(?&1 & set\"", + "c": " 2>&1 & set", "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell string.quoted.double.powershell", "r": { "dark_plus": "string: #CE9178", @@ -1187,6 +1209,17 @@ "hc_black": "string: #CE9178" } }, + { + "c": "\"", + "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell string.quoted.double.powershell punctuation.definition.string.end.powershell", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, { "c": ")", "t": "source.powershell meta.scriptblock.powershell punctuation.section.group.end.powershell", @@ -1266,13 +1299,13 @@ }, { "c": "$", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell keyword.other.variable.definition.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell support.constant.automatic.powershell punctuation.definition.variable.powershell", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6" + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" } }, { @@ -1320,7 +1353,18 @@ } }, { - "c": "'^([^=]+)=(.*)'", + "c": "'", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell string.quoted.single.powershell punctuation.definition.string.begin.powershell", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "^([^=]+)=(.*)", "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell string.quoted.single.powershell", "r": { "dark_plus": "string: #CE9178", @@ -1330,6 +1374,17 @@ "hc_black": "string: #CE9178" } }, + { + "c": "'", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell string.quoted.single.powershell punctuation.definition.string.end.powershell", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, { "c": ")", "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell punctuation.section.group.end.powershell", @@ -1431,13 +1486,13 @@ }, { "c": "$", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell keyword.other.variable.definition.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell support.constant.automatic.powershell punctuation.definition.variable.powershell", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6" + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" } }, { @@ -1508,13 +1563,13 @@ }, { "c": "$", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell keyword.other.variable.definition.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell support.constant.automatic.powershell punctuation.definition.variable.powershell", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6" + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" } }, { @@ -1672,7 +1727,18 @@ } }, { - "c": "'Initializing Azure PowerShell environment...'", + "c": "'", + "t": "source.powershell string.quoted.single.powershell punctuation.definition.string.begin.powershell", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "Initializing Azure PowerShell environment...", "t": "source.powershell string.quoted.single.powershell", "r": { "dark_plus": "string: #CE9178", @@ -1683,14 +1749,25 @@ } }, { - "c": ";", - "t": "source.powershell keyword.other.statement-separator.powershell", + "c": "'", + "t": "source.powershell string.quoted.single.powershell punctuation.definition.string.end.powershell", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6" + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": ";", + "t": "source.powershell punctuation.terminator.statement.powershell", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" } }, { @@ -1870,7 +1947,18 @@ } }, { - "c": "'Please launch command under administrator account. It is needed for environment setting up and unit test.'", + "c": "'", + "t": "source.powershell meta.scriptblock.powershell string.quoted.single.powershell punctuation.definition.string.begin.powershell", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "Please launch command under administrator account. It is needed for environment setting up and unit test.", "t": "source.powershell meta.scriptblock.powershell string.quoted.single.powershell", "r": { "dark_plus": "string: #CE9178", @@ -1880,6 +1968,17 @@ "hc_black": "string: #CE9178" } }, + { + "c": "'", + "t": "source.powershell meta.scriptblock.powershell string.quoted.single.powershell punctuation.definition.string.end.powershell", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, { "c": " ", "t": "source.powershell meta.scriptblock.powershell", @@ -1915,13 +2014,13 @@ }, { "c": ";", - "t": "source.powershell meta.scriptblock.powershell keyword.other.statement-separator.powershell", + "t": "source.powershell meta.scriptblock.powershell punctuation.terminator.statement.powershell", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6" + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" } }, { @@ -1937,18 +2036,18 @@ }, { "c": "$", - "t": "source.powershell keyword.other.variable.definition.powershell", + "t": "source.powershell variable.other.readwrite.powershell punctuation.definition.variable.powershell", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6" + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" } }, { "c": "env:", - "t": "source.powershell support.variable.drive.powershell", + "t": "source.powershell variable.other.readwrite.powershell support.variable.drive.powershell", "r": { "dark_plus": "support.variable: #9CDCFE", "light_plus": "support.variable: #001080", @@ -2069,18 +2168,18 @@ }, { "c": "$", - "t": "source.powershell keyword.other.variable.definition.powershell", + "t": "source.powershell variable.other.readwrite.powershell punctuation.definition.variable.powershell", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6" + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" } }, { "c": "env:", - "t": "source.powershell support.variable.drive.powershell", + "t": "source.powershell variable.other.readwrite.powershell support.variable.drive.powershell", "r": { "dark_plus": "support.variable: #9CDCFE", "light_plus": "support.variable: #001080", @@ -2102,13 +2201,13 @@ }, { "c": ";", - "t": "source.powershell keyword.other.statement-separator.powershell", + "t": "source.powershell punctuation.terminator.statement.powershell", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6" + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" } }, { @@ -2190,7 +2289,7 @@ }, { "c": "\"", - "t": "source.powershell interpolated.simple.source.powershell string.quoted.double.powershell", + "t": "source.powershell interpolated.simple.source.powershell string.quoted.double.powershell punctuation.definition.string.begin.powershell", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -2201,18 +2300,18 @@ }, { "c": "$", - "t": "source.powershell interpolated.simple.source.powershell string.quoted.double.powershell keyword.other.variable.definition.powershell", + "t": "source.powershell interpolated.simple.source.powershell string.quoted.double.powershell variable.other.readwrite.powershell punctuation.definition.variable.powershell", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6" + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "variable: #9CDCFE" } }, { "c": "env:", - "t": "source.powershell interpolated.simple.source.powershell string.quoted.double.powershell support.variable.drive.powershell", + "t": "source.powershell interpolated.simple.source.powershell string.quoted.double.powershell variable.other.readwrite.powershell support.variable.drive.powershell", "r": { "dark_plus": "support.variable: #9CDCFE", "light_plus": "support.variable: #001080", @@ -2233,7 +2332,7 @@ } }, { - "c": "\\Microsoft Visual Studio 12.0\"", + "c": "\\Microsoft Visual Studio 12.0", "t": "source.powershell interpolated.simple.source.powershell string.quoted.double.powershell", "r": { "dark_plus": "string: #CE9178", @@ -2243,6 +2342,17 @@ "hc_black": "string: #CE9178" } }, + { + "c": "\"", + "t": "source.powershell interpolated.simple.source.powershell string.quoted.double.powershell punctuation.definition.string.end.powershell", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, { "c": ")", "t": "source.powershell punctuation.section.group.end.powershell", @@ -2289,13 +2399,13 @@ }, { "c": "$", - "t": "source.powershell meta.scriptblock.powershell keyword.other.variable.definition.powershell", + "t": "source.powershell meta.scriptblock.powershell variable.other.readwrite.powershell punctuation.definition.variable.powershell", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6" + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" } }, { @@ -2321,7 +2431,18 @@ } }, { - "c": "\"12.0\"", + "c": "\"", + "t": "source.powershell meta.scriptblock.powershell string.quoted.double.powershell punctuation.definition.string.begin.powershell", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "12.0", "t": "source.powershell meta.scriptblock.powershell string.quoted.double.powershell", "r": { "dark_plus": "string: #CE9178", @@ -2331,6 +2452,17 @@ "hc_black": "string: #CE9178" } }, + { + "c": "\"", + "t": "source.powershell meta.scriptblock.powershell string.quoted.double.powershell punctuation.definition.string.end.powershell", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, { "c": "}", "t": "source.powershell meta.scriptblock.powershell punctuation.section.braces.end.powershell", @@ -2399,13 +2531,13 @@ }, { "c": "$", - "t": "source.powershell meta.scriptblock.powershell keyword.other.variable.definition.powershell", + "t": "source.powershell meta.scriptblock.powershell variable.other.readwrite.powershell punctuation.definition.variable.powershell", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6" + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" } }, { @@ -2431,7 +2563,18 @@ } }, { - "c": "\"11.0\"", + "c": "\"", + "t": "source.powershell meta.scriptblock.powershell string.quoted.double.powershell punctuation.definition.string.begin.powershell", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "11.0", "t": "source.powershell meta.scriptblock.powershell string.quoted.double.powershell", "r": { "dark_plus": "string: #CE9178", @@ -2441,6 +2584,17 @@ "hc_black": "string: #CE9178" } }, + { + "c": "\"", + "t": "source.powershell meta.scriptblock.powershell string.quoted.double.powershell punctuation.definition.string.end.powershell", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, { "c": "}", "t": "source.powershell meta.scriptblock.powershell punctuation.section.braces.end.powershell", @@ -2454,13 +2608,13 @@ }, { "c": "$", - "t": "source.powershell keyword.other.variable.definition.powershell", + "t": "source.powershell variable.other.readwrite.powershell punctuation.definition.variable.powershell", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6" + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" } }, { @@ -2508,7 +2662,18 @@ } }, { - "c": "'\"{0}\\Microsoft Visual Studio {1}\\VC\\vcvarsall.bat\" x64'", + "c": "'", + "t": "source.powershell string.quoted.single.powershell punctuation.definition.string.begin.powershell", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "\"{0}\\Microsoft Visual Studio {1}\\VC\\vcvarsall.bat\" x64", "t": "source.powershell string.quoted.single.powershell", "r": { "dark_plus": "string: #CE9178", @@ -2518,6 +2683,17 @@ "hc_black": "string: #CE9178" } }, + { + "c": "'", + "t": "source.powershell string.quoted.single.powershell punctuation.definition.string.end.powershell", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, { "c": " ", "t": "source.powershell", @@ -2553,18 +2729,18 @@ }, { "c": "$", - "t": "source.powershell keyword.other.variable.definition.powershell", + "t": "source.powershell variable.other.readwrite.powershell punctuation.definition.variable.powershell", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6" + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" } }, { "c": "env:", - "t": "source.powershell support.variable.drive.powershell", + "t": "source.powershell variable.other.readwrite.powershell support.variable.drive.powershell", "r": { "dark_plus": "support.variable: #9CDCFE", "light_plus": "support.variable: #001080", @@ -2608,13 +2784,13 @@ }, { "c": "$", - "t": "source.powershell keyword.other.variable.definition.powershell", + "t": "source.powershell variable.other.readwrite.powershell punctuation.definition.variable.powershell", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6" + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" } }, { @@ -2630,13 +2806,13 @@ }, { "c": ";", - "t": "source.powershell keyword.other.statement-separator.powershell", + "t": "source.powershell punctuation.terminator.statement.powershell", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6" + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" } }, { @@ -2685,13 +2861,13 @@ }, { "c": "$", - "t": "source.powershell keyword.other.variable.definition.powershell", + "t": "source.powershell variable.other.readwrite.powershell punctuation.definition.variable.powershell", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6" + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" } }, { @@ -2707,13 +2883,13 @@ }, { "c": ";", - "t": "source.powershell keyword.other.statement-separator.powershell", + "t": "source.powershell punctuation.terminator.statement.powershell", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6" + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" } } ] \ No newline at end of file From d506fa59ada07fd4bf77f08340c9427719e845ad Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 9 Jul 2018 12:05:11 +0200 Subject: [PATCH 268/283] add setting to enable/disable breadcrumbs --- .../browser/parts/editor/editorBreadcrumbs.ts | 82 +++++++++++++++++-- .../browser/parts/editor/editorGroupView.ts | 7 ++ 2 files changed, 84 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts index 314b6848ead..f0f9bd00fe0 100644 --- a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts @@ -12,7 +12,7 @@ import { compareFileNames } from 'vs/base/common/comparers'; import { debounceEvent, Emitter, Event } from 'vs/base/common/event'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { isEqual, dirname } from 'vs/base/common/resources'; +import { dirname, isEqual } from 'vs/base/common/resources'; import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { IDataSource, IRenderer, ISelectionEvent, ISorter, ITree, ITreeConfiguration } from 'vs/base/parts/tree/browser/tree'; @@ -21,22 +21,26 @@ import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { Range } from 'vs/editor/common/core/range'; import { OutlineElement, OutlineGroup, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; import { OutlineController, OutlineDataSource, OutlineItemComparator, OutlineRenderer } from 'vs/editor/contrib/documentSymbols/outlineTree'; +import { localize } from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { FileKind, IFileService, IFileStat } from 'vs/platform/files/common/files'; import { IConstructorSignature2, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { WorkbenchTree } from 'vs/platform/list/browser/listService'; +import { Registry } from 'vs/platform/registry/common/platform'; import { attachBreadcrumbsStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { FileLabel } from 'vs/workbench/browser/labels'; import { BreadcrumbElement, EditorBreadcrumbsModel, FileElement } from 'vs/workbench/browser/parts/editor/editorBreadcrumbsModel'; +import { EditorGroupView } from 'vs/workbench/browser/parts/editor/editorGroupView'; import { EditorInput } from 'vs/workbench/common/editor'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorBreadcrumbs, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; - +import { IEditorBreadcrumbs, IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; class Item extends BreadcrumbsItem { @@ -99,6 +103,8 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { private readonly _ckBreadcrumbsVisible: IContextKey; private readonly _ckBreadcrumbsFocused: IContextKey; + private readonly _cfEnabled: Config; + private readonly _disposables = new Array(); private readonly _domNode: HTMLDivElement; private readonly _widget: BreadcrumbsWidget; @@ -107,7 +113,7 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { constructor( container: HTMLElement, - private readonly _editorGroup: IEditorGroup, + private readonly _editorGroup: EditorGroupView, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IContextViewService private readonly _contextViewService: IContextViewService, @IEditorService private readonly _editorService: IEditorService, @@ -115,6 +121,7 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { @IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IThemeService private readonly _themeService: IThemeService, + @IConfigurationService configurationService: IConfigurationService, ) { this._domNode = document.createElement('div'); dom.addClasses(this._domNode, 'editor-breadcrumbs', 'show-file-icons'); @@ -125,6 +132,17 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { this._widget.onDidChangeFocus(val => this._ckBreadcrumbsFocused.set(val), undefined, this._disposables); this._disposables.push(attachBreadcrumbsStyler(this._widget, this._themeService)); + this._cfEnabled = Config.create(configurationService, 'breadcrumbs.enabled'); + this._disposables.push(this._cfEnabled.onDidChange(value => { + if (!value) { + this.closeEditor(undefined); + this._editorGroup.relayout(); + } else if (this._editorGroup.activeEditor) { + this.openEditor(this._editorGroup.activeEditor); + this._editorGroup.relayout(); + } + })); + this._ckBreadcrumbsVisible = EditorBreadcrumbs.CK_BreadcrumbsVisible.bindTo(this._contextKeyService); this._ckBreadcrumbsFocused = EditorBreadcrumbs.CK_BreadcrumbsFocused.bindTo(this._contextKeyService); } @@ -133,10 +151,11 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { dispose(this._disposables); this._widget.dispose(); this._ckBreadcrumbsVisible.reset(); + this._cfEnabled.dispose(); } getPreferredHeight(): number { - return 25; + return this._cfEnabled.value ? 25 : 0; } layout(dim: dom.Dimension): void { @@ -150,6 +169,10 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { } openEditor(input: EditorInput): void { + if (!this._cfEnabled.value) { + // not enabled -> return early + return; + } this._breadcrumbsDisposables = dispose(this._breadcrumbsDisposables); @@ -413,6 +436,55 @@ export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker { } } +//#region config + +abstract class Config { + + name: string; + value: T; + onDidChange: Event; + abstract dispose(): void; + + static create(service: IConfigurationService, name: string): Config { + + let value: T = service.getValue(name); + let onDidChange = new Emitter(); + + let listener = service.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(name)) { + value = service.getValue(name); + onDidChange.fire(value); + } + }); + + return { + name, + get value() { return value; }, + onDidChange: onDidChange.event, + dispose(): void { + listener.dispose(); + onDidChange.dispose(); + } + }; + } +} + +Registry.as(Extensions.Configuration).registerConfiguration({ + id: 'breadcrumbs', + title: localize('title', "Breadcrumb Navigation"), + order: 101, + type: 'object', + properties: { + 'breadcrumbs.enabled': { + 'description': localize('enabled', "Enable/disable navigation breadcrumbss"), + 'type': 'boolean', + 'default': false + } + } +}); + +//#endregion + //#region commands KeybindingsRegistry.registerCommandAndKeybindingRule({ diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 39385de58cb..a31d41c5797 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -1380,6 +1380,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.editorControl.layout(new Dimension(this.dimension.width, this.dimension.height - (EDITOR_TITLE_HEIGHT + this.breadcrumbsControl.getPreferredHeight()))); } + relayout(): void { + if (this.dimension) { + const { width, height } = this.dimension; + this.layout(width, height); + } + } + toJSON(): ISerializedEditorGroup { return this._group.serialize(); } From 61e0cf066d44a2cb5ca1c38d87c0f190836a2aa1 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 9 Jul 2018 12:06:58 +0200 Subject: [PATCH 269/283] Restore "snap to minimize/maximize" feature with grid editor layout (fixes #51614) --- src/vs/base/browser/ui/grid/grid.ts | 7 ++++++- src/vs/workbench/browser/parts/editor/editorPart.ts | 8 ++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/grid/grid.ts b/src/vs/base/browser/ui/grid/grid.ts index 15e572c3030..649ca14073d 100644 --- a/src/vs/base/browser/ui/grid/grid.ts +++ b/src/vs/base/browser/ui/grid/grid.ts @@ -279,10 +279,15 @@ export class Grid implements IDisposable { getViewSize(view: T): number { const location = this.getViewLocation(view); const viewSize = this.gridview.getViewSize(location); - return getLocationOrientation(this.orientation, location) === Orientation.HORIZONTAL ? viewSize.width : viewSize.height; } + // TODO@joao cleanup + getViewSize2(view: T): { width: number; height: number; } { + const location = this.getViewLocation(view); + return this.gridview.getViewSize(location); + } + maximizeViewSize(view: T): void { const location = this.getViewLocation(view); this.gridview.maximizeViewSize(location); diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index e5631a42a10..36d3ffb96b5 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -544,6 +544,14 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor // Mark group as new active group.setActive(true); + // Maximize the group if it is currently minimized + if (this.gridWidget) { + const viewSize = this.gridWidget.getViewSize2(group); + if (viewSize.width === group.minimumWidth || viewSize.height === group.minimumHeight) { + this.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS); + } + } + // Event this._onDidActiveGroupChange.fire(group); } From f7d447ffa1e3ac6b778719f111412e769c1fbe14 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 9 Jul 2018 12:13:04 +0200 Subject: [PATCH 270/283] don't access disposed buffer --- .../browser/parts/editor/editorBreadcrumbsModel.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorBreadcrumbsModel.ts b/src/vs/workbench/browser/parts/editor/editorBreadcrumbsModel.ts index 913175259ac..87031bf8046 100644 --- a/src/vs/workbench/browser/parts/editor/editorBreadcrumbsModel.ts +++ b/src/vs/workbench/browser/parts/editor/editorBreadcrumbsModel.ts @@ -78,11 +78,15 @@ export class EditorBreadcrumbsModel { if (!this._editor) { return; } - this._updateOutline(); + // update as model changes this._disposables.push(DocumentSymbolProviderRegistry.onDidChange(_ => this._updateOutline())); this._disposables.push(this._editor.onDidChangeModel(_ => this._updateOutline())); this._disposables.push(this._editor.onDidChangeModelLanguage(_ => this._updateOutline())); this._disposables.push(debounceEvent(this._editor.onDidChangeModelContent, _ => _, 350)(_ => this._updateOutline(true))); + this._updateOutline(); + + // stop when editor dies + this._disposables.push(this._editor.onDidDispose(() => this._outlineDisposables = dispose(this._outlineDisposables))); } private _updateOutline(didChangeContent?: boolean): void { @@ -111,7 +115,7 @@ export class EditorBreadcrumbsModel { const lastVersionId = buffer.getVersionId(); this._outlineDisposables.push(this._editor.onDidChangeCursorPosition(_ => { timeout.cancelAndSet(() => { - if (lastVersionId === buffer.getVersionId()) { + if (!buffer.isDisposed() && lastVersionId === buffer.getVersionId()) { this._updateOutlineElements(this._getOutlineElements(model, this._editor.getPosition())); } }, 150); From a7f7ebf2fa9150f1977ca39b29528322bc54846f Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 9 Jul 2018 12:13:41 +0200 Subject: [PATCH 271/283] :lipstick: --- .../workbench/browser/parts/editor/editorBreadcrumbsModel.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorBreadcrumbsModel.ts b/src/vs/workbench/browser/parts/editor/editorBreadcrumbsModel.ts index 87031bf8046..2c1bf381f4d 100644 --- a/src/vs/workbench/browser/parts/editor/editorBreadcrumbsModel.ts +++ b/src/vs/workbench/browser/parts/editor/editorBreadcrumbsModel.ts @@ -148,13 +148,13 @@ export class EditorBreadcrumbsModel { } private _updateOutlineElements(elements: (OutlineGroup | OutlineElement)[]): void { - if (!equals(elements, this._outlineElements, EditorBreadcrumbsModel.outlineElementEquals)) { + if (!equals(elements, this._outlineElements, EditorBreadcrumbsModel._outlineElementEquals)) { this._outlineElements = elements; this._onDidUpdate.fire(this); } } - private static outlineElementEquals(a: OutlineGroup | OutlineElement, b: OutlineGroup | OutlineElement): boolean { + private static _outlineElementEquals(a: OutlineGroup | OutlineElement, b: OutlineGroup | OutlineElement): boolean { if (a === b) { return true; } else if (!a || !b) { From 20b042608c13fc5720dbef93840c78c972bcfa9b Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Mon, 9 Jul 2018 12:33:36 +0200 Subject: [PATCH 272/283] Rewording of sentences in Formatting section (fixes #53548) --- .../electron-browser/editor/vs_code_editor_walkthrough.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/editor/vs_code_editor_walkthrough.md b/src/vs/workbench/parts/welcome/walkThrough/electron-browser/editor/vs_code_editor_walkthrough.md index b2fd54b2e19..26477b37563 100644 --- a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/editor/vs_code_editor_walkthrough.md +++ b/src/vs/workbench/parts/welcome/walkThrough/electron-browser/editor/vs_code_editor_walkthrough.md @@ -103,7 +103,7 @@ function findFirstEvenNumber(arr) { ### Formatting -Keeping your code looking great is hard without a good formatter. Luckily it's easy to format content either the entire document with kb(editor.action.formatDocument). Formatting can be applied to the current selection with kb(editor.action.formatSelection). Both of these options are also available through the right-click context menu. +Keeping your code looking great is hard without a good formatter. Luckily it's easy to format content, either for the entire document with kb(editor.action.formatDocument) or for the current selection with kb(editor.action.formatSelection). Both of these options are also available through the right-click context menu. ```js var cars = ["Saab", "Volvo", "BMW"]; From f60a637d3894573e6eef3c353199e2b5ee73713a Mon Sep 17 00:00:00 2001 From: Dirk Baeumer Date: Mon, 9 Jul 2018 14:22:45 +0200 Subject: [PATCH 273/283] Adresses #53752: executeTask Cannot read property _key of undefined --- .../api/electron-browser/mainThreadTask.ts | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/api/electron-browser/mainThreadTask.ts b/src/vs/workbench/api/electron-browser/mainThreadTask.ts index 6658414014b..a3e9c819851 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadTask.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadTask.ts @@ -7,6 +7,7 @@ import * as nls from 'vs/nls'; import URI from 'vs/base/common/uri'; +import { generateUuid } from 'vs/base/common/uuid'; import * as Objects from 'vs/base/common/objects'; import { TPromise } from 'vs/base/common/winjs.base'; import * as Types from 'vs/base/common/types'; @@ -38,10 +39,10 @@ namespace TaskExecutionDTO { task: TaskDTO.from(value.task) }; } - export function to(value: TaskExecutionDTO, workspace: IWorkspaceContextService): TaskExecution { + export function to(value: TaskExecutionDTO, workspace: IWorkspaceContextService, executeOnly: boolean): TaskExecution { return { id: value.id, - task: TaskDTO.to(value.task, workspace) + task: TaskDTO.to(value.task, workspace, executeOnly) }; } } @@ -70,8 +71,15 @@ namespace TaskDefinitionDTO { delete result._key; return result; } - export function to(value: TaskDefinitionDTO): KeyedTaskIdentifier { - return TaskDefinition.createTaskIdentifier(value, console); + export function to(value: TaskDefinitionDTO, executeOnly: boolean): KeyedTaskIdentifier { + let result = TaskDefinition.createTaskIdentifier(value, console); + if (result === void 0 && executeOnly) { + result = { + _key: generateUuid(), + type: '$executeOnly' + }; + } + return result; } } @@ -302,7 +310,7 @@ namespace TaskDTO { return result; } - export function to(task: TaskDTO, workspace: IWorkspaceContextService): Task { + export function to(task: TaskDTO, workspace: IWorkspaceContextService, executeOnly: boolean): Task { if (typeof task.name !== 'string') { return undefined; } @@ -321,7 +329,7 @@ namespace TaskDTO { let source = TaskSourceDTO.to(task.source, workspace); let label = nls.localize('task.label', '{0}: {1}', source.label, task.name); - let definition = TaskDefinitionDTO.to(task.definition); + let definition = TaskDefinitionDTO.to(task.definition, executeOnly); let id = `${task.source.extensionId}.${definition._key}`; let result: ContributedTask = { _id: id, // uuidMap.getUUID(identifier), @@ -449,7 +457,7 @@ export class MainThreadTask implements MainThreadTaskShape { reject(new Error('Task not found')); }); } else { - let task = TaskDTO.to(value, this._workspaceContextServer); + let task = TaskDTO.to(value, this._workspaceContextServer, true); this._taskService.run(task); let result: TaskExecutionDTO = { id: task._id, From 5b4261c3afd8615e734f04f74a3e31df2bd832a6 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 9 Jul 2018 14:39:44 +0200 Subject: [PATCH 274/283] better selection/focus management in breadcrumbs --- .../ui/breadcrumbs/breadcrumbsWidget.css | 5 +- .../ui/breadcrumbs/breadcrumbsWidget.ts | 55 +++++++++++-------- .../browser/parts/editor/editorBreadcrumbs.ts | 16 ++++-- 3 files changed, 46 insertions(+), 30 deletions(-) diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css index 08b25c9bc4e..a7f820dee40 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css @@ -33,5 +33,8 @@ .vs-dark .monaco-breadcrumbs .monaco-breadcrumb-item:not(:last-child)::after { background-image: url(./collpased-dark.svg); - +} + +.monaco-breadcrumbs .monaco-breadcrumb-item.focused { + font-weight: bold; } diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index 0ed02f6e1b1..db98d5d18af 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -133,10 +133,18 @@ export class BreadcrumbsWidget { } } - focus(): void { + domFocus(): void { this._domNode.focus(); } + getFocused(): BreadcrumbsItem { + return this._items[this._focusedItemIdx]; + } + + setFocused(item: BreadcrumbsItem): void { + this._focus(this._items.indexOf(item)); + } + focusPrev(): any { this._focus((this._focusedItemIdx - 1 + this._nodes.length) % this._nodes.length); this._domNode.focus(); @@ -147,39 +155,39 @@ export class BreadcrumbsWidget { this._domNode.focus(); } - private _focus(nth: number): boolean { - if (this._focusedItemIdx >= 0 && this._focusedItemIdx < this._nodes.length) { - dom.removeClass(this._nodes[this._focusedItemIdx], 'focused'); - this._focusedItemIdx = -1; + private _focus(nth: number): void { + this._focusedItemIdx = -1; + for (let i = 0; i < this._nodes.length; i++) { + const node = this._nodes[i]; + if (i !== nth) { + dom.removeClass(node, 'focused'); + } else { + this._focusedItemIdx = i; + dom.addClass(node, 'focused'); + } } - if (nth < 0 || nth >= this._nodes.length) { - return false; - } - this._focusedItemIdx = nth; - dom.addClass(this._nodes[this._focusedItemIdx], 'focused'); - this._scrollable.setScrollPosition({ scrollLeft: this._nodes[this._focusedItemIdx].offsetLeft }); this._onDidFocusItem.fire({ item: this._items[this._focusedItemIdx], node: this._nodes[this._focusedItemIdx] }); - return true; } - getFocusedItem(): BreadcrumbsItem { - return this._items[this._focusedItemIdx]; + getSelected(): BreadcrumbsItem { + return this._items[this._selectedItemIdx]; } - select(item: BreadcrumbsItem): void { + setSelected(item: BreadcrumbsItem): void { this._select(this._items.indexOf(item)); } private _select(nth: number): void { - if (this._selectedItemIdx >= 0 && this._selectedItemIdx < this._nodes.length) { - dom.removeClass(this._nodes[this._selectedItemIdx], 'selected'); - this._selectedItemIdx = -1; + this._selectedItemIdx = -1; + for (let i = 0; i < this._nodes.length; i++) { + const node = this._nodes[i]; + if (i !== nth) { + dom.removeClass(node, 'selected'); + } else { + this._selectedItemIdx = i; + dom.addClass(node, 'selected'); + } } - if (nth < 0 || nth >= this._nodes.length) { - return; - } - this._selectedItemIdx = nth; - dom.addClass(this._nodes[this._selectedItemIdx], 'selected'); this._onDidSelectItem.fire({ item: this._items[this._selectedItemIdx], node: this._nodes[this._selectedItemIdx] }); } @@ -212,7 +220,6 @@ export class BreadcrumbsWidget { this._nodes[start] = node; } this.layout(undefined); - this._focus(this._nodes.length - 1); } private _renderItem(item: BreadcrumbsItem, container: HTMLDivElement): void { diff --git a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts index f0f9bd00fe0..065dc3c76bf 100644 --- a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts @@ -198,7 +198,7 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { } focus(): void { - this._widget.focus(); + this._widget.domFocus(); } focusNext(): void { @@ -210,15 +210,18 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { } select(): void { - const item = this._widget.getFocusedItem(); + const item = this._widget.getFocused(); if (item) { - this._widget.select(item); + this._widget.setSelected(item); } } private _onDidSelectItem(event: IBreadcrumbsItemEvent): void { - this._editorGroup.focus(); + if (!event.item) { + return; + } + this._editorGroup.focus(); this._contextViewService.showContextView({ getAnchor() { return event.node; @@ -231,7 +234,6 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { res.layout({ width: 250, height: 300 }); res.onDidPickElement(data => { this._contextViewService.hideContextView(); - this._widget.select(undefined); if (!data) { return; } @@ -257,6 +259,10 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { }); return res; }, + onHide: () => { + this._widget.setSelected(undefined); + this._widget.setFocused(undefined); + } }); } } From efba880587805ce50e9b6f7b36d384e3ec160291 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 9 Jul 2018 15:04:03 +0200 Subject: [PATCH 275/283] remove app.getPath('userData') calls fixes #53706 --- src/main.js | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/main.js b/src/main.js index 5d30c39f692..850cda67f2d 100644 --- a/src/main.js +++ b/src/main.js @@ -93,6 +93,19 @@ const args = minimist(process.argv, { ] }); +function getUserDataPath() { + if (isPortable) { + return path.join(portableDataPath, 'user-data'); + } + + return path.resolve(args['user-data-dir'] || paths.getDefaultUserDataPath(process.platform)); +} + +const userDataPath = getUserDataPath(); + +// Set userData path before app 'ready' event and call to process.chdir +app.setPath('userData', userDataPath); + //#region NLS function stripComments(content) { let regexp = /("(?:[^\\\"]*(?:\\.)?)*")|('(?:[^\\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g; @@ -183,8 +196,7 @@ function getUserDefinedLocale() { return Promise.resolve(locale.toLowerCase()); } - let userData = app.getPath('userData'); - let localeConfig = path.join(userData, 'User', 'locale.json'); + let localeConfig = path.join(userDataPath, 'User', 'locale.json'); return exists(localeConfig).then((result) => { if (result) { return readFile(localeConfig).then((content) => { @@ -203,8 +215,7 @@ function getUserDefinedLocale() { } function getLanguagePackConfigurations() { - let userData = app.getPath('userData'); - let configFile = path.join(userData, 'languagepacks.json'); + let configFile = path.join(userDataPath, 'languagepacks.json'); try { return require(configFile); } catch (err) { @@ -243,8 +254,6 @@ function getNLSConfiguration(locale) { return Promise.resolve({ locale: locale, availableLanguages: {} }); } - let userData = app.getPath('userData'); - // We have a built version so we have extracted nls file. Try to find // the right file to use. @@ -285,7 +294,7 @@ function getNLSConfiguration(locale) { return defaultResult(initialLocale); } let packId = packConfig.hash + '.' + locale; - let cacheRoot = path.join(userData, 'clp', packId); + let cacheRoot = path.join(userDataPath, 'clp', packId); let coreLocation = path.join(cacheRoot, commit); let translationsConfigFile = path.join(cacheRoot, 'tcf.json'); let corruptedFile = path.join(cacheRoot, 'corrupted.info'); @@ -395,23 +404,12 @@ const nodeCachedDataDir = new class { if (!commit) { return undefined; } - return path.join(app.getPath('userData'), 'CachedData', commit); + return path.join(userDataPath, 'CachedData', commit); } }; //#endregion -function getUserDataPath() { - if (isPortable) { - return path.join(portableDataPath, 'user-data'); - } - - return path.resolve(args['user-data-dir'] || paths.getDefaultUserDataPath(process.platform)); -} - -// Set userData path before app 'ready' event and call to process.chdir -app.setPath('userData', getUserDataPath()); - // Update cwd based on environment and platform try { if (process.platform === 'win32') { From 9b876381cbfd20b11ab8eb6a11e6aaccd24b8549 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 9 Jul 2018 06:10:57 -0700 Subject: [PATCH 276/283] Use m for Terminal menu mnemonic Fixes #53828 --- src/vs/code/electron-main/menus.ts | 2 +- src/vs/workbench/browser/parts/menubar/menubarPart.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/code/electron-main/menus.ts b/src/vs/code/electron-main/menus.ts index cbcf41f5afa..1cc94b2b298 100644 --- a/src/vs/code/electron-main/menus.ts +++ b/src/vs/code/electron-main/menus.ts @@ -241,7 +241,7 @@ export class CodeMenu { // Terminal const terminalMenu = new Menu(); - const terminalMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mTerminal', comment: ['&& denotes a mnemonic'] }, "&&Terminal")), submenu: terminalMenu }); + const terminalMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mTerminal', comment: ['&& denotes a mnemonic'] }, "Ter&&minal")), submenu: terminalMenu }); this.setTerminalMenu(terminalMenu); // Debug diff --git a/src/vs/workbench/browser/parts/menubar/menubarPart.ts b/src/vs/workbench/browser/parts/menubar/menubarPart.ts index 31c84306e65..208b9170360 100644 --- a/src/vs/workbench/browser/parts/menubar/menubarPart.ts +++ b/src/vs/workbench/browser/parts/menubar/menubarPart.ts @@ -74,7 +74,7 @@ export class MenubarPart extends Part { 'Selection': nls.localize({ key: 'mSelection', comment: ['&& denotes a mnemonic'] }, "&&Selection"), 'View': nls.localize({ key: 'mView', comment: ['&& denotes a mnemonic'] }, "&&View"), 'Go': nls.localize({ key: 'mGoto', comment: ['&& denotes a mnemonic'] }, "&&Go"), - 'Terminal': nls.localize({ key: 'mTerminal', comment: ['&& denotes a mnemonic'] }, "&&Terminal"), + 'Terminal': nls.localize({ key: 'mTerminal', comment: ['&& denotes a mnemonic'] }, "Ter&&minal"), 'Debug': nls.localize({ key: 'mDebug', comment: ['&& denotes a mnemonic'] }, "&&Debug"), 'Tasks': nls.localize({ key: 'mTasks', comment: ['&& denotes a mnemonic'] }, "&&Tasks"), 'Help': nls.localize({ key: 'mHelp', comment: ['&& denotes a mnemonic'] }, "&&Help") From e89b02b448aa17353d2b58da83bc5a3e33c593cc Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 9 Jul 2018 15:10:15 +0200 Subject: [PATCH 277/283] Fix #53872 --- .../sharedProcess/sharedProcessMain.ts | 3 ++- .../common/extensionManagementIpc.ts | 12 ++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 97f712452c9..deaea3955f3 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -43,6 +43,7 @@ import { LocalizationsChannel } from 'vs/platform/localizations/common/localizat import { DialogChannelClient } from 'vs/platform/dialogs/common/dialogIpc'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { DefaultURITransformer } from 'vs/base/common/uriIpc'; export interface ISharedProcessConfiguration { readonly machineId: string; @@ -134,7 +135,7 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I instantiationService2.invokeFunction(accessor => { const extensionManagementService = accessor.get(IExtensionManagementService); - const channel = new ExtensionManagementChannel(extensionManagementService); + const channel = new ExtensionManagementChannel(extensionManagementService, DefaultURITransformer); server.registerChannel('extensions', channel); // clean up deprecated extensions diff --git a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts index 1de227bfe06..0aa4e7577a7 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts @@ -33,7 +33,7 @@ export class ExtensionManagementChannel implements IExtensionManagementChannel { onUninstallExtension: Event; onDidUninstallExtension: Event; - constructor(private service: IExtensionManagementService) { + constructor(private service: IExtensionManagementService, private uriTransformer: IURITransformer) { this.onInstallExtension = buffer(service.onInstallExtension, true); this.onDidInstallExtension = buffer(service.onDidInstallExtension, true); this.onUninstallExtension = buffer(service.onUninstallExtension, true); @@ -55,15 +55,19 @@ export class ExtensionManagementChannel implements IExtensionManagementChannel { switch (command) { case 'install': return this.service.install(args[0]); case 'installFromGallery': return this.service.installFromGallery(args[0]); - case 'uninstall': return this.service.uninstall(args[0], args[1]); - case 'reinstallFromGallery': return this.service.reinstallFromGallery(args[0]); + case 'uninstall': return this.service.uninstall(this._transform(args[0]), args[1]); + case 'reinstallFromGallery': return this.service.reinstallFromGallery(this._transform(args[0])); case 'getInstalled': return this.service.getInstalled(args[0]); - case 'updateMetadata': return this.service.updateMetadata(args[0], args[1]); + case 'updateMetadata': return this.service.updateMetadata(this._transform(args[0]), args[1]); case 'getExtensionsReport': return this.service.getExtensionsReport(); } throw new Error('Invalid call'); } + + private _transform(extension: ILocalExtension): ILocalExtension { + return extension ? { ...extension, ...{ location: URI.revive(this.uriTransformer.transformIncoming(extension.location)) } } : extension; + } } export class ExtensionManagementChannelClient implements IExtensionManagementService { From a335286070faab8add9489e1d35ebb82751b303a Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 9 Jul 2018 15:51:55 +0200 Subject: [PATCH 278/283] keep focused item, reset focused when item has changed --- src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts | 8 +++++++- .../workbench/browser/parts/editor/editorBreadcrumbs.ts | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index db98d5d18af..29cee352905 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -13,7 +13,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { Event, Emitter } from 'vs/base/common/event'; import { Color } from 'vs/base/common/color'; -import { commonPrefixLength } from 'vs/base/common/arrays'; +import { commonPrefixLength, tail } from 'vs/base/common/arrays'; export abstract class BreadcrumbsItem { dispose(): void { } @@ -134,6 +134,8 @@ export class BreadcrumbsWidget { } domFocus(): void { + const focused = this.getFocused() || tail(this._items); + this.setFocused(focused); this._domNode.focus(); } @@ -196,6 +198,9 @@ export class BreadcrumbsWidget { let removed = this._items.splice(prefix, this._items.length - prefix, ...items.slice(prefix)); this._render(prefix); dispose(removed); + if (prefix >= this._focusedItemIdx) { + this._focus(-1); + } } private _render(start: number): void { @@ -224,6 +229,7 @@ export class BreadcrumbsWidget { private _renderItem(item: BreadcrumbsItem, container: HTMLDivElement): void { dom.clearNode(container); + container.className = ''; item.render(container); dom.append(container); dom.addClass(container, 'monaco-breadcrumb-item'); diff --git a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts index 065dc3c76bf..d88e3047feb 100644 --- a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts @@ -261,7 +261,7 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { }, onHide: () => { this._widget.setSelected(undefined); - this._widget.setFocused(undefined); + // this._widget.setFocused(undefined); } }); } From c209a9e149aa5a2762b6e1b54a149db223020b3d Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 9 Jul 2018 15:53:24 +0200 Subject: [PATCH 279/283] fix #53836 --- .../workbench/parts/outline/electron-browser/outlinePanel.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts b/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts index 3650e6cf8cb..66cbf82e5ae 100644 --- a/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts +++ b/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts @@ -352,6 +352,9 @@ export class OutlinePanel extends ViewletPanel { return false; } const keyInfo = mapping[event.code]; + if (!keyInfo) { + return false; + } if (keyInfo.value) { $this._input.focus(); return true; From dd68c6520b67d097453dc2766f976dc009a5af44 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 9 Jul 2018 16:25:20 +0200 Subject: [PATCH 280/283] don't access disposed model --- .../browser/parts/editor/editorBreadcrumbsModel.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorBreadcrumbsModel.ts b/src/vs/workbench/browser/parts/editor/editorBreadcrumbsModel.ts index 2c1bf381f4d..bb1e22f0e68 100644 --- a/src/vs/workbench/browser/parts/editor/editorBreadcrumbsModel.ts +++ b/src/vs/workbench/browser/parts/editor/editorBreadcrumbsModel.ts @@ -102,25 +102,26 @@ export class EditorBreadcrumbsModel { } const source = new CancellationTokenSource(); + const versionIdThen = buffer.getVersionId(); + const timeout = new TimeoutTimer(); this._outlineDisposables.push({ dispose: () => { source.cancel(); source.dispose(); + timeout.dispose(); } }); + OutlineModel.create(buffer, source.token).then(model => { this._updateOutlineElements(this._getOutlineElements(model, this._editor.getPosition())); - const timeout = new TimeoutTimer(); - const lastVersionId = buffer.getVersionId(); this._outlineDisposables.push(this._editor.onDidChangeCursorPosition(_ => { timeout.cancelAndSet(() => { - if (!buffer.isDisposed() && lastVersionId === buffer.getVersionId()) { + if (versionIdThen === buffer.getVersionId()) { this._updateOutlineElements(this._getOutlineElements(model, this._editor.getPosition())); } }, 150); })); - this._outlineDisposables.push(timeout); }).catch(err => { this._updateOutlineElements([]); onUnexpectedError(err); From 7059b50648c8909befceabb20da8a59fa34de815 Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Mon, 9 Jul 2018 16:54:28 +0200 Subject: [PATCH 281/283] node-debug@1.26.1 --- build/builtInExtensions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index bf739296d29..ab3a77f657b 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -1,7 +1,7 @@ [ { "name": "ms-vscode.node-debug", - "version": "1.26.0", + "version": "1.26.1", "repo": "https://github.com/Microsoft/vscode-node-debug" }, { From 25cabacf5d1111539c4e4f587855cf4e2d65cfec Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 9 Jul 2018 17:00:24 +0200 Subject: [PATCH 282/283] add log statements to catch weird bug --- src/vs/base/browser/ui/list/listView.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 1d560a8afec..bbfe7f0b134 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -282,6 +282,11 @@ export class ListView implements ISpliceable, IDisposable { private insertItemInDOM(index: number, beforeElement: HTMLElement | null): void { const item = this.items[index]; + if (!item) { + console.log(this.items); + throw new Error(`Got index ${index} and there are ${this.items.length} items. File issue to joao!`); + } + if (!item.row) { item.row = this.cache.alloc(item.templateId); } @@ -311,6 +316,12 @@ export class ListView implements ISpliceable, IDisposable { private removeItemFromDOM(index: number): void { const item = this.items[index]; + + if (!item) { + console.log(this.items); + throw new Error(`Got index ${index} and there are ${this.items.length} items. File issue to joao!`); + } + this.cache.release(item.row); item.row = null; } @@ -371,7 +382,12 @@ export class ListView implements ISpliceable, IDisposable { } private onScroll(e: ScrollEvent): void { - this.render(e.scrollTop, e.height); + try { + this.render(e.scrollTop, e.height); + } catch (err) { + console.log('Got bad scroll event:', e); + throw err; + } } private onTouchChange(event: GestureEvent): void { From 8422fccff1f6256f4bbf272b055de3ec7652f067 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 9 Jul 2018 17:25:59 +0200 Subject: [PATCH 283/283] add shadow/border to breadcrumb picker --- src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts | 6 ++++-- .../browser/parts/editor/media/editorbreadcrumbs.css | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts index d88e3047feb..d271ac2a475 100644 --- a/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/editorBreadcrumbs.ts @@ -227,7 +227,10 @@ export class EditorBreadcrumbs implements IEditorBreadcrumbs { return event.node; }, render: (container: HTMLElement) => { - dom.addClasses(container, 'monaco-workbench', 'show-file-icons'); + dom.addClasses(container, 'monaco-breadcrumbs-picker', 'monaco-workbench', 'show-file-icons'); + let color = this._themeService.getTheme().getColor(SIDE_BAR_BACKGROUND); + container.style.borderColor = color.darken(.2).toString(); + container.style.boxShadow = `${color.toString()} 6px 6px 6px -6px;`; let { element } = event.item as Item; let ctor: IConstructorSignature2 = element instanceof FileElement ? BreadcrumbsFilePicker : BreadcrumbsOutlinePicker; let res = this._instantiationService.createInstance(ctor, container, element); @@ -294,7 +297,6 @@ export abstract class BreadcrumbsPicker { this.focus = dom.trackFocus(this._domNode); this.focus.onDidBlur(_ => this._onDidPickElement.fire(undefined), undefined, this._disposables); - this._tree.domFocus(); this._tree.setInput(this._getInput(input)); } diff --git a/src/vs/workbench/browser/parts/editor/media/editorbreadcrumbs.css b/src/vs/workbench/browser/parts/editor/media/editorbreadcrumbs.css index e3b45ae4166..e40ca9ba696 100644 --- a/src/vs/workbench/browser/parts/editor/media/editorbreadcrumbs.css +++ b/src/vs/workbench/browser/parts/editor/media/editorbreadcrumbs.css @@ -11,3 +11,8 @@ .monaco-workbench>.part.editor>.content .editor-group-container .editor-breadcrumbs .monaco-breadcrumbs .monaco-breadcrumb-item:nth-child(2) { /*first-child is the style-element*/ padding-left: 8px; } + +.monaco-breadcrumbs-picker { + border: 1px #dddddd solid; + box-shadow: #dddddd 6px 6px 6px -6px; +}