From d7ed37e864c6a21b7012b96598d84b4576d7f0ac Mon Sep 17 00:00:00 2001 From: Fmstrat Date: Thu, 16 Jan 2020 09:39:19 -0500 Subject: [PATCH 0001/1262] add ignoreSubmodules option --- extensions/git/package.json | 6 ++++++ extensions/git/package.nls.json | 1 + extensions/git/src/git.ts | 10 ++++++++-- extensions/git/src/repository.ts | 3 +++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 3e049ddd7e5..75aa3083546 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1512,6 +1512,12 @@ "default": false, "description": "%config.alwaysSignOff%" }, + "git.ignoreSubmodules": { + "type": "boolean", + "scope": "resource", + "default": false, + "description": "%config.ignoreSubmodules%" + }, "git.ignoredRepositories": { "type": "array", "items": { diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 5c357b40eb0..a892d87d512 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -121,6 +121,7 @@ "config.detectSubmodulesLimit": "Controls the limit of git submodules detected.", "config.alwaysShowStagedChangesResourceGroup": "Always show the Staged Changes resource group.", "config.alwaysSignOff": "Controls the signoff flag for all commits.", + "config.ignoreSubmodules": "Ignore modifications to submodules in the file tree.", "config.ignoredRepositories": "List of git repositories to ignore.", "config.scanRepositories": "List of paths to search for git repositories in.", "config.showProgress": "Controls whether git actions should show progress.", diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index af92f83c1a6..322765dba4c 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -12,7 +12,7 @@ import { EventEmitter } from 'events'; import iconv = require('iconv-lite'); import * as filetype from 'file-type'; import { assign, groupBy, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent, splitInChunks, Limiter } from './util'; -import { CancellationToken, Progress } from 'vscode'; +import { CancellationToken, Progress, workspace } from 'vscode'; import { URI } from 'vscode-uri'; import { detectEncoding } from './encoding'; import { Ref, RefType, Branch, Remote, GitErrorCodes, LogOptions, Change, Status } from './api/git'; @@ -1619,7 +1619,13 @@ export class Repository { return new Promise<{ status: IFileStatus[]; didHitLimit: boolean; }>((c, e) => { const parser = new GitStatusParser(); const env = { GIT_OPTIONAL_LOCKS: '0' }; - const child = this.stream(['status', '-z', '-u'], { env }); + + const config = workspace.getConfiguration('git'); + const args = ['status', '-z', '-u']; + if (config.get('ignoreSubmodules')) { + args.push('--ignore-submodules'); + } + const child = this.stream(args, { env }); const onExit = (exitCode: number) => { if (exitCode !== 0) { diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 47832f9e0f0..361a35a64b9 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -731,6 +731,9 @@ export class Repository implements Disposable { const onConfigListenerForUntracked = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.untrackedChanges', root)); onConfigListenerForUntracked(this.updateModelState, this, this.disposables); + const onConfigListenerForIgnoreSubmodules = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.ignoreSubmodules', root)); + onConfigListenerForIgnoreSubmodules(this.updateModelState, this, this.disposables); + this.mergeGroup.hideWhenEmpty = true; this.untrackedGroup.hideWhenEmpty = true; From 2abdb90472470865d7e28cfbdbcd60b9ecf3d64a Mon Sep 17 00:00:00 2001 From: Oliver Larsson Date: Fri, 24 Jan 2020 21:20:44 +0100 Subject: [PATCH 0002/1262] git.pruneOnFetch setting implemented --- extensions/git/package.json | 6 ++++++ extensions/git/package.nls.json | 1 + extensions/git/src/repository.ts | 21 +++++++++++++++++---- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 2ba6515e963..ace8e1bd984 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1548,6 +1548,12 @@ "default": false, "description": "%config.fetchOnPull%" }, + "git.pruneOnFetch": { + "type": "boolean", + "scope": "resource", + "default": false, + "description": "%config.pruneOnFetch%" + }, "git.pullTags": { "type": "boolean", "scope": "resource", diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 5c357b40eb0..0b269e889dc 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -128,6 +128,7 @@ "config.confirmEmptyCommits": "Always confirm the creation of empty commits for the 'Git: Commit Empty' command.", "config.fetchOnPull": "Fetch all branches when pulling or just the current one.", "config.pullTags": "Fetch all tags when pulling.", + "config.pruneOnFetch": "Always prune when fetching.", "config.autoStash": "Stash any changes before pulling and restore them after successful pull.", "config.allowForcePush": "Controls whether force push (with or without lease) is enabled.", "config.useForcePushWithLease": "Controls whether force pushing uses the safer force-with-lease variant.", diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 47832f9e0f0..79f90cfdaeb 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1072,21 +1072,34 @@ export class Repository implements Disposable { @throttle async fetchDefault(options: { silent?: boolean } = {}): Promise { - await this.run(Operation.Fetch, () => this.repository.fetch(options)); + await this.fetchFrom({ silent: options.silent }); } @throttle async fetchPrune(): Promise { - await this.run(Operation.Fetch, () => this.repository.fetch({ prune: true })); + await this.fetchFrom({ prune: true }); } @throttle async fetchAll(): Promise { - await this.run(Operation.Fetch, () => this.repository.fetch({ all: true })); + await this.fetchFrom({ all: true }); } async fetch(remote?: string, ref?: string, depth?: number): Promise { - await this.run(Operation.Fetch, () => this.repository.fetch({ remote, ref, depth })); + await this.fetchFrom({ remote, ref, depth }); + } + + private async fetchFrom(options: { remote?: string, ref?: string, all?: boolean, prune?: boolean, depth?: number, silent?: boolean } = {}): Promise { + await this.run(Operation.Fetch, async () => { + const config = workspace.getConfiguration('git', Uri.file(this.root)); + const prune = config.get('pruneOnFetch'); + + if (prune) { + options.prune = prune; + } + + this.repository.fetch(options); + }); } @throttle From 712ceb8279a0da8652bae92d497e48a887c98684 Mon Sep 17 00:00:00 2001 From: jeanp413 Date: Sat, 25 Jan 2020 23:59:15 -0500 Subject: [PATCH 0003/1262] Fixes #89145 --- .../ui/tree/compressedObjectTreeModel.ts | 6 +- .../contrib/scm/browser/repositoryPane.ts | 88 +++++++++++++------ 2 files changed, 65 insertions(+), 29 deletions(-) diff --git a/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts b/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts index f1da4355047..54cb4e1de00 100644 --- a/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts +++ b/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts @@ -49,11 +49,11 @@ export function compress(element: ICompressedTreeElement): ITreeElement = children[0]; + if (childElement.incompressible) { break; } + element = childElement; elements.push(element.element); } diff --git a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts index 81f88f32cd7..075102cb8dd 100644 --- a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts +++ b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts @@ -367,20 +367,24 @@ export class SCMTreeKeyboardNavigationLabelProvider implements ICompressibleKeyb } } +function getSCMResourceId(element: TreeElement): string { + if (ResourceTree.isResourceNode(element)) { + const group = element.context; + return `${group.provider.contextValue}/${group.id}/$FOLDER/${element.uri.toString()}`; + } else if (isSCMResource(element)) { + const group = element.resourceGroup; + const provider = group.provider; + return `${provider.contextValue}/${group.id}/${element.sourceUri.toString()}`; + } else { + const provider = element.provider; + return `${provider.contextValue}/${element.id}`; + } +} + class SCMResourceIdentityProvider implements IIdentityProvider { getId(element: TreeElement): string { - if (ResourceTree.isResourceNode(element)) { - const group = element.context; - return `${group.provider.contextValue}/${group.id}/$FOLDER/${element.uri.toString()}`; - } else if (isSCMResource(element)) { - const group = element.resourceGroup; - const provider = group.provider; - return `${provider.contextValue}/${group.id}/${element.sourceUri.toString()}`; - } else { - const provider = element.provider; - return `${provider.contextValue}/${element.id}`; - } + return getSCMResourceId(element); } } @@ -391,19 +395,31 @@ interface IGroupItem { readonly disposable: IDisposable; } -function groupItemAsTreeElement(item: IGroupItem, mode: ViewModelMode): ICompressedTreeElement { - const children = mode === ViewModelMode.List - ? Iterator.map(Iterator.fromArray(item.resources), element => ({ element, incompressible: true })) - : Iterator.map(item.tree.root.children, node => asTreeElement(node, true)); - - return { element: item.group, children, incompressible: true, collapsible: true }; +interface IViewState { + readonly expanded: Set; } -function asTreeElement(node: IResourceNode, forceIncompressible: boolean): ICompressedTreeElement { +function groupItemAsTreeElement(item: IGroupItem, mode: ViewModelMode, viewState?: IViewState): ICompressedTreeElement { + const children = mode === ViewModelMode.List + ? Iterator.map(Iterator.fromArray(item.resources), element => ({ element, incompressible: true })) + : Iterator.map(item.tree.root.children, node => asTreeElement(node, true, viewState)); + + const element = item.group; + const collapsed = viewState ? !viewState.expanded.has(getSCMResourceId(element)) : false; + + return { element, children, incompressible: true, collapsed, collapsible: true }; +} + +function asTreeElement(node: IResourceNode, forceIncompressible: boolean, viewState?: IViewState): ICompressedTreeElement { + const element = (node.childrenCount === 0 && node.element) ? node.element : node; + const collapsed = viewState ? !viewState.expanded.has(getSCMResourceId(element)) : false; + return { - element: (node.childrenCount === 0 && node.element) ? node.element : node, - children: Iterator.map(node.children, node => asTreeElement(node, false)), - incompressible: !!node.element || forceIncompressible + element, + children: Iterator.map(node.children, node => asTreeElement(node, false, viewState)), + incompressible: !!node.element || forceIncompressible, + collapsed, + collapsible: node.childrenCount > 0 }; } @@ -439,6 +455,7 @@ class ViewModel { private visibilityDisposables = new DisposableStore(); private scrollTop: number | undefined; private firstVisible = true; + private viewState: IViewState | undefined; private disposables = new DisposableStore(); constructor( @@ -449,7 +466,7 @@ class ViewModel { @IConfigurationService protected configurationService: IConfigurationService, ) { } - private onDidSpliceGroups({ start, deleteCount, toInsert }: ISplice): void { + private onDidSpliceGroups({ start, deleteCount, toInsert }: ISplice, viewState?: IViewState): void { const itemsToInsert: IGroupItem[] = []; for (const group of toInsert) { @@ -477,7 +494,7 @@ class ViewModel { item.disposable.dispose(); } - this.refresh(); + this.refresh(undefined, viewState); } private onDidSpliceGroup(item: IGroupItem, { start, deleteCount, toInsert }: ISplice): void { @@ -500,7 +517,8 @@ class ViewModel { if (visible) { this.visibilityDisposables = new DisposableStore(); this.groups.onDidSplice(this.onDidSpliceGroups, this, this.visibilityDisposables); - this.onDidSpliceGroups({ start: 0, deleteCount: this.items.length, toInsert: this.groups.elements }); + this.onDidSpliceGroups({ start: 0, deleteCount: this.items.length, toInsert: this.groups.elements }, this.viewState); + this.viewState = undefined; if (typeof this.scrollTop === 'number') { this.tree.scrollTop = this.scrollTop; @@ -510,20 +528,38 @@ class ViewModel { this.editorService.onDidActiveEditorChange(this.onDidActiveEditorChange, this, this.visibilityDisposables); this.onDidActiveEditorChange(); } else { + this.updateViewState(); this.visibilityDisposables.dispose(); this.onDidSpliceGroups({ start: 0, deleteCount: this.items.length, toInsert: [] }); this.scrollTop = this.tree.scrollTop; } } - private refresh(item?: IGroupItem): void { + private refresh(item?: IGroupItem, viewState?: IViewState): void { if (item) { this.tree.setChildren(item.group, groupItemAsTreeElement(item, this.mode).children); } else { - this.tree.setChildren(null, this.items.map(item => groupItemAsTreeElement(item, this.mode))); + this.tree.setChildren(null, this.items.map(item => groupItemAsTreeElement(item, this.mode, viewState))); } } + private updateViewState(): void { + const expanded = new Set(); + const visit = (node: ITreeNode) => { + if (node.element && node.collapsible && !node.collapsed) { + expanded.add(getSCMResourceId(node.element)); + } + + for (const child of node.children) { + visit(child); + } + }; + + visit(this.tree.getNode()); + + this.viewState = { expanded }; + } + private onDidActiveEditorChange(): void { if (!this.configurationService.getValue('scm.autoReveal')) { return; From cdc6c051e5e710e1851effaf1b9ff3f685baea11 Mon Sep 17 00:00:00 2001 From: jeanp413 Date: Mon, 27 Jan 2020 03:36:01 -0500 Subject: [PATCH 0004/1262] Persist scm tree view state between sessions --- .../contrib/scm/browser/repositoryPane.ts | 74 +++++++++++-------- 1 file changed, 45 insertions(+), 29 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts index 075102cb8dd..a91fc19e5a4 100644 --- a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts +++ b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts @@ -395,24 +395,24 @@ interface IGroupItem { readonly disposable: IDisposable; } -interface IViewState { - readonly expanded: Set; +interface ITreeViewState { + readonly expanded: string[]; } -function groupItemAsTreeElement(item: IGroupItem, mode: ViewModelMode, viewState?: IViewState): ICompressedTreeElement { +function groupItemAsTreeElement(item: IGroupItem, mode: ViewModelMode, viewState?: ITreeViewState): ICompressedTreeElement { const children = mode === ViewModelMode.List ? Iterator.map(Iterator.fromArray(item.resources), element => ({ element, incompressible: true })) : Iterator.map(item.tree.root.children, node => asTreeElement(node, true, viewState)); const element = item.group; - const collapsed = viewState ? !viewState.expanded.has(getSCMResourceId(element)) : false; + const collapsed = viewState ? viewState.expanded.indexOf(getSCMResourceId(element)) === -1 : false; return { element, children, incompressible: true, collapsed, collapsible: true }; } -function asTreeElement(node: IResourceNode, forceIncompressible: boolean, viewState?: IViewState): ICompressedTreeElement { +function asTreeElement(node: IResourceNode, forceIncompressible: boolean, viewState?: ITreeViewState): ICompressedTreeElement { const element = (node.childrenCount === 0 && node.element) ? node.element : node; - const collapsed = viewState ? !viewState.expanded.has(getSCMResourceId(element)) : false; + const collapsed = viewState ? viewState.expanded.indexOf(getSCMResourceId(element)) === -1 : false; return { element, @@ -451,22 +451,27 @@ class ViewModel { this._onDidChangeMode.fire(mode); } + get treeViewState(): ITreeViewState | undefined { + this.updateViewState(); + return this._treeViewState; + } + private items: IGroupItem[] = []; private visibilityDisposables = new DisposableStore(); private scrollTop: number | undefined; private firstVisible = true; - private viewState: IViewState | undefined; private disposables = new DisposableStore(); constructor( private groups: ISequence, private tree: WorkbenchCompressibleObjectTree, private _mode: ViewModelMode, + private _treeViewState: ITreeViewState | undefined, @IEditorService protected editorService: IEditorService, - @IConfigurationService protected configurationService: IConfigurationService, + @IConfigurationService protected configurationService: IConfigurationService ) { } - private onDidSpliceGroups({ start, deleteCount, toInsert }: ISplice, viewState?: IViewState): void { + private onDidSpliceGroups({ start, deleteCount, toInsert }: ISplice): void { const itemsToInsert: IGroupItem[] = []; for (const group of toInsert) { @@ -494,7 +499,7 @@ class ViewModel { item.disposable.dispose(); } - this.refresh(undefined, viewState); + this.refresh(undefined, toInsert.length > 0 ? this._treeViewState : undefined); } private onDidSpliceGroup(item: IGroupItem, { start, deleteCount, toInsert }: ISplice): void { @@ -517,8 +522,7 @@ class ViewModel { if (visible) { this.visibilityDisposables = new DisposableStore(); this.groups.onDidSplice(this.onDidSpliceGroups, this, this.visibilityDisposables); - this.onDidSpliceGroups({ start: 0, deleteCount: this.items.length, toInsert: this.groups.elements }, this.viewState); - this.viewState = undefined; + this.onDidSpliceGroups({ start: 0, deleteCount: this.items.length, toInsert: this.groups.elements }); if (typeof this.scrollTop === 'number') { this.tree.scrollTop = this.scrollTop; @@ -535,19 +539,19 @@ class ViewModel { } } - private refresh(item?: IGroupItem, viewState?: IViewState): void { + private refresh(item?: IGroupItem, treeViewState?: ITreeViewState): void { if (item) { this.tree.setChildren(item.group, groupItemAsTreeElement(item, this.mode).children); } else { - this.tree.setChildren(null, this.items.map(item => groupItemAsTreeElement(item, this.mode, viewState))); + this.tree.setChildren(null, this.items.map(item => groupItemAsTreeElement(item, this.mode, treeViewState))); } } private updateViewState(): void { - const expanded = new Set(); + const expanded: string[] = []; const visit = (node: ITreeNode) => { if (node.element && node.collapsible && !node.collapsed) { - expanded.add(getSCMResourceId(node.element)); + expanded.push(getSCMResourceId(node.element)); } for (const child of node.children) { @@ -557,7 +561,7 @@ class ViewModel { visit(this.tree.getNode()); - this.viewState = { expanded }; + this._treeViewState = { expanded }; } private onDidActiveEditorChange(): void { @@ -883,18 +887,27 @@ export class RepositoryPane extends ViewPane { this._register(this.tree); let mode = this.configurationService.getValue<'tree' | 'list'>('scm.defaultViewMode') === 'list' ? ViewModelMode.List : ViewModelMode.Tree; + let treeViewState: ITreeViewState | undefined; const rootUri = this.repository.provider.rootUri; if (typeof rootUri !== 'undefined') { - const storageMode = this.storageService.get(`scm.repository.viewMode:${rootUri.toString()}`, StorageScope.WORKSPACE) as ViewModelMode; + const raw = this.storageService.get(`scm.repository.viewState:${rootUri.toString()}`, StorageScope.WORKSPACE); + if (raw) { + let data: any; + try { + data = JSON.parse(raw); + } catch (e) { + } - if (typeof storageMode === 'string') { - mode = storageMode; + if (typeof data.mode === 'string') { + mode = data.mode; + } + treeViewState = data.treeViewState; } } - this.viewModel = this.instantiationService.createInstance(ViewModel, this.repository.provider.groups, this.tree, mode); + this.viewModel = this.instantiationService.createInstance(ViewModel, this.repository.provider.groups, this.tree, mode, treeViewState); this._register(this.viewModel); addClass(this.listContainer, 'file-icon-themable-tree'); @@ -910,6 +923,17 @@ export class RepositoryPane extends ViewPane { this._register(this.onDidChangeBodyVisibility(this._onDidChangeVisibility, this)); this.updateActions(); + + this._register(this.storageService.onWillSaveState(() => { + if (typeof rootUri === 'undefined') { + return; + } + + this.storageService.store(`scm.repository.viewState:${rootUri.toString()}`, JSON.stringify({ + mode: this.viewModel.mode, + treeViewState: this.viewModel.treeViewState + }), StorageScope.WORKSPACE); + })); } private updateIndentStyles(theme: IFileIconTheme): void { @@ -921,14 +945,6 @@ export class RepositoryPane extends ViewPane { private onDidChangeMode(): void { this.updateIndentStyles(this.themeService.getFileIconTheme()); - - const rootUri = this.repository.provider.rootUri; - - if (typeof rootUri === 'undefined') { - return; - } - - this.storageService.store(`scm.repository.viewMode:${rootUri.toString()}`, this.viewModel.mode, StorageScope.WORKSPACE); } layoutBody(height: number | undefined = this.cachedHeight, width: number | undefined = this.cachedWidth): void { From f23fcb72f5595ea0192d5a2c5f4eff7d0a47410c Mon Sep 17 00:00:00 2001 From: Mathias Rasmussen Date: Sun, 1 Mar 2020 05:14:00 +0100 Subject: [PATCH 0005/1262] allow git amend message only --- extensions/git/src/commands.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index a27fa783fc0..477021ce2c5 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -1408,6 +1408,8 @@ export class CommandCenter { // no staged changes and no tracked unstaged changes || (noStagedChanges && smartCommitChanges === 'tracked' && repository.workingTreeGroup.resourceStates.every(r => r.type === Status.UNTRACKED)) ) + // amend allows changing only the commit message + && !opts.amend && !opts.empty ) { window.showInformationMessage(localize('no changes', "There are no changes to commit.")); From 37bca69ff169e7dedcd7d4a5bda81876fc880c12 Mon Sep 17 00:00:00 2001 From: jeanp413 Date: Sun, 1 Mar 2020 22:09:20 -0500 Subject: [PATCH 0006/1262] :lipstick: --- src/vs/workbench/contrib/scm/browser/repositoryPane.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts index 2acefed09cc..0206d0fa72c 100644 --- a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts +++ b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts @@ -457,7 +457,7 @@ class ViewModel { } } - this.refresh(); + this.refresh(undefined, this._treeViewState); this._onDidChangeMode.fire(mode); } @@ -479,7 +479,9 @@ class ViewModel { private _treeViewState: ITreeViewState | undefined, @IEditorService protected editorService: IEditorService, @IConfigurationService protected configurationService: IConfigurationService - ) { } + ) { + this.disposables.add(this.tree.onDidChangeCollapseState(() => this.updateViewState())); + } private onDidSpliceGroups({ start, deleteCount, toInsert }: ISplice): void { const itemsToInsert: IGroupItem[] = []; @@ -509,7 +511,7 @@ class ViewModel { item.disposable.dispose(); } - this.refresh(undefined, toInsert.length > 0 ? this._treeViewState : undefined); + this.refresh(undefined, this._treeViewState); } private onDidSpliceGroup(item: IGroupItem, { start, deleteCount, toInsert }: ISplice): void { @@ -551,7 +553,7 @@ class ViewModel { private refresh(item?: IGroupItem, treeViewState?: ITreeViewState): void { if (item) { - this.tree.setChildren(item.group, groupItemAsTreeElement(item, this.mode).children); + this.tree.setChildren(item.group, groupItemAsTreeElement(item, this.mode, treeViewState).children); } else { this.tree.setChildren(null, this.items.map(item => groupItemAsTreeElement(item, this.mode, treeViewState))); } From bc85a9ffdb3c1644cfd11a610ce846c6ad22cb06 Mon Sep 17 00:00:00 2001 From: Jacob Date: Thu, 2 Apr 2020 17:34:07 +0200 Subject: [PATCH 0007/1262] Added user choice for opening the folder always. --- extensions/git/src/commands.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 451ff28afca..68299863db7 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -514,7 +514,8 @@ export class CommandCenter { let message = localize('proposeopen', "Would you like to open the cloned repository?"); const open = localize('openrepo', "Open"); const openNewWindow = localize('openreponew', "Open in New Window"); - const choices = [open, openNewWindow]; + const openAlways = localize('openrepoalways', "Always open after cloning"); + const choices = [open, openNewWindow, openAlways]; const addToWorkspace = localize('add', "Add to Workspace"); if (workspace.workspaceFolders) { @@ -541,6 +542,9 @@ export class CommandCenter { workspace.updateWorkspaceFolders(workspace.workspaceFolders!.length, 0, { uri }); } else if (result === openNewWindow) { commands.executeCommand('vscode.openFolder', uri, true); + } else if (result === openAlways) { + commands.executeCommand('vscode.openFolder', uri); + // will add a command for always option later } } catch (err) { if (/already exists and is not an empty directory/.test(err && err.stderr || '')) { From 412a44e9bce95688dc9bb87c561cc3321217b6b1 Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Fri, 3 Apr 2020 12:51:59 +0300 Subject: [PATCH 0008/1262] Git: ask to save unsaved files before stashing --- extensions/git/src/commands.ts | 38 ++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 451ff28afca..c4a591c1e85 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -2242,6 +2242,44 @@ export class CommandCenter { return; } + const config = workspace.getConfiguration('git', Uri.file(repository.root)); + let promptToSaveFilesBeforeCommit = config.get<'always' | 'staged' | 'never'>('promptToSaveFilesBeforeCommit'); + + // migration + if (promptToSaveFilesBeforeCommit as any === true) { + promptToSaveFilesBeforeCommit = 'always'; + } else if (promptToSaveFilesBeforeCommit as any === false) { + promptToSaveFilesBeforeCommit = 'never'; + } + + if (promptToSaveFilesBeforeCommit !== 'never') { + let documents = workspace.textDocuments + .filter(d => !d.isUntitled && d.isDirty && isDescendant(repository.root, d.uri.fsPath)); + + if (promptToSaveFilesBeforeCommit === 'staged' || repository.indexGroup.resourceStates.length > 0) { + documents = documents + .filter(d => repository.indexGroup.resourceStates.some(s => pathEquals(s.resourceUri.fsPath, d.uri.fsPath))); + } + + if (documents.length > 0) { + const message = documents.length === 1 + ? localize('unsaved stash files single', "The following file has unsaved changes which won't be included in the stash if you proceed: {0}.\n\nWould you like to save it before committing?", path.basename(documents[0].uri.fsPath)) + : localize('unsaved stash files', "There are {0} unsaved files.\n\nWould you like to save them before stashing?", documents.length); + const saveAndStash = localize('save and stash', "Save All & Stash"); + const stash = localize('stash', "Stash Anyway"); + const pick = await window.showWarningMessage(message, { modal: true }, saveAndStash, stash); + + if (pick === saveAndStash) { + await Promise.all(documents.map(d => d.save())); + if (!includeUntracked) { + await repository.add(documents.map(d => d.uri)); + } + } else if (pick !== stash) { + return; // do not stash on cancel + } + } + } + const message = await this.getStashMessage(); if (typeof message === 'undefined') { From 156d5ab2812eb244473f7cded67bb4b68b63af1e Mon Sep 17 00:00:00 2001 From: Jacob Date: Fri, 3 Apr 2020 13:43:50 +0200 Subject: [PATCH 0009/1262] Added setting for opening cloned repository without prompt. #93300 --- extensions/git/package.json | 17 ++++++++ extensions/git/package.nls.json | 5 +++ extensions/git/src/commands.ts | 71 +++++++++++++++++++-------------- 3 files changed, 64 insertions(+), 29 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 75306b8a785..9eca6c7791c 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1467,6 +1467,23 @@ "scope": "resource", "default": "none" }, + "git.promptToOpenClonedRepository": { + "type": "string", + "enum": [ + "currentWindow", + "newWindow", + "noFolderOpened", + "showPrompt" + ], + "enumDescriptions": [ + "%config.promptToOpenClonedRepository.currentWindow%", + "%config.promptToOpenClonedRepository.newWindow%", + "%config.promptToOpenClonedRepository.noFolderOpened%", + "%config.promptToOpenClonedRepository.showPrompt%" + ], + "default": "showPrompt", + "description": "%config.promptToOpenClonedRepository%" + }, "git.showInlineOpenFileAction": { "type": "boolean", "default": true, diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index e163ffad48d..3f847c92fee 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -116,6 +116,11 @@ "config.postCommitCommand.none": "Don't run any command after a commit.", "config.postCommitCommand.push": "Run 'Git Push' after a successful commit.", "config.postCommitCommand.sync": "Run 'Git Sync' after a successful commit.", + "config.promptToOpenClonedRepository": "Controls whether to show a prompt after cloning a repository.", + "config.promptToOpenClonedRepository.currentWindow": "Open repository in current window.", + "config.promptToOpenClonedRepository.newWindow": "Open repository in new window.", + "config.promptToOpenClonedRepository.noFolderOpened": "Open in current window if no folder is opened. Otherwise show prompt.", + "config.promptToOpenClonedRepository.showPrompt": "Always show prompt to choose action.", "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.", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 68299863db7..f36772e6fd0 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -511,41 +511,54 @@ export class CommandCenter { (progress, token) => this.git.clone(url!, parentPath!, progress, token) ); - let message = localize('proposeopen', "Would you like to open the cloned repository?"); - const open = localize('openrepo', "Open"); - const openNewWindow = localize('openreponew', "Open in New Window"); - const openAlways = localize('openrepoalways', "Always open after cloning"); - const choices = [open, openNewWindow, openAlways]; - - const addToWorkspace = localize('add', "Add to Workspace"); - if (workspace.workspaceFolders) { - message = localize('proposeopen2', "Would you like to open the cloned repository, or add it to the current workspace?"); - choices.push(addToWorkspace); - } - - const result = await window.showInformationMessage(message, ...choices); - - const openFolder = result === open; - /* __GDPR__ - "clone" : { - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true } - } - */ - this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'success' }, { openFolder: openFolder ? 1 : 0 }); + let config = workspace.getConfiguration('git'); + let promptToOpenClonedRepository = config.get<'currentWindow' | 'newWindow' | 'noFolderOpened' | 'showPrompt'>('promptToOpenClonedRepository'); const uri = Uri.file(repositoryPath); - if (openFolder) { + if (promptToOpenClonedRepository === 'currentWindow') { commands.executeCommand('vscode.openFolder', uri); - } else if (result === addToWorkspace) { - workspace.updateWorkspaceFolders(workspace.workspaceFolders!.length, 0, { uri }); - } else if (result === openNewWindow) { + } else if (promptToOpenClonedRepository === 'newWindow') { commands.executeCommand('vscode.openFolder', uri, true); - } else if (result === openAlways) { - commands.executeCommand('vscode.openFolder', uri); - // will add a command for always option later + } else { + if (promptToOpenClonedRepository === 'noFolderOpened') { + if (!workspace.workspaceFolders) { + commands.executeCommand('vscode.openFolder', uri); + } + } + + let message = localize('proposeopen', "Would you like to open the cloned repository?"); + const open = localize('openrepo', "Open"); + const openNewWindow = localize('openreponew', "Open in New Window"); + const choices = [open, openNewWindow]; + + const addToWorkspace = localize('add', "Add to Workspace"); + if (workspace.workspaceFolders) { + message = localize('proposeopen2', "Would you like to open the cloned repository, or add it to the current workspace?"); + choices.push(addToWorkspace); + } + + const result = await window.showInformationMessage(message, ...choices); + + + const openFolder = result === open; + /* __GDPR__ + "clone" : { + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true } + } + */ + this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'success' }, { openFolder: openFolder ? 1 : 0 }); + + if (openFolder) { + commands.executeCommand('vscode.openFolder', uri); + } else if (result === addToWorkspace) { + workspace.updateWorkspaceFolders(workspace.workspaceFolders!.length, 0, { uri }); + } else if (result === openNewWindow) { + commands.executeCommand('vscode.openFolder', uri, true); + } } + } catch (err) { if (/already exists and is not an empty directory/.test(err && err.stderr || '')) { /* __GDPR__ From 4420bbfbcf7d284eb95132b23b5b82bc01b19a40 Mon Sep 17 00:00:00 2001 From: Nathaniel Palmer Date: Mon, 13 Apr 2020 17:13:39 -0400 Subject: [PATCH 0010/1262] Offer to show git command output on failure --- extensions/git/src/commands.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index e8a696735fc..b4ec7cc525b 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -236,6 +236,7 @@ interface PushOptions { export class CommandCenter { private disposables: Disposable[]; + private lastCommandErrorOutput = ''; constructor( private git: Git, @@ -252,6 +253,13 @@ export class CommandCenter { return commands.registerCommand(commandId, command); } }); + this.disposables.push( + workspace.registerTextDocumentContentProvider('git-output', this) + ); + } + + async provideTextDocumentContent(): Promise { + return this.lastCommandErrorOutput; } @command('git.setLogLevel') @@ -2435,6 +2443,16 @@ export class CommandCenter { const openOutputChannelChoice = localize('open git log', "Open Git Log"); const outputChannel = this.outputChannel as OutputChannel; choices.set(openOutputChannelChoice, () => outputChannel.show()); + const showCommandOutputChoice = localize('show command output', 'Show Command Output'); + if (err.stderr) { + choices.set(showCommandOutputChoice, () => { + this.lastCommandErrorOutput = err.stderr; + const uri = Uri.parse(`git-output://command-error/${err.gitCommand}-${Math.random().toString(16).slice(2, 10)}`); + workspace.openTextDocument(uri).then(doc => { + return window.showTextDocument(doc); + }); + }); + } switch (err.gitErrorCode) { case GitErrorCodes.DirtyWorkTree: From 1243ff76e4e66585418131c634de5a82070a3164 Mon Sep 17 00:00:00 2001 From: Jacob Date: Wed, 22 Apr 2020 14:27:55 +0200 Subject: [PATCH 0011/1262] Changed name of the setting to openAfterClone --- extensions/git/package.json | 12 ++++++------ extensions/git/package.nls.json | 10 +++++----- extensions/git/src/commands.ts | 8 ++++---- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 7ba43eedaa6..8d098c504a3 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1507,7 +1507,7 @@ "scope": "resource", "default": "none" }, - "git.promptToOpenClonedRepository": { + "git.openAfterClone": { "type": "string", "enum": [ "currentWindow", @@ -1516,13 +1516,13 @@ "showPrompt" ], "enumDescriptions": [ - "%config.promptToOpenClonedRepository.currentWindow%", - "%config.promptToOpenClonedRepository.newWindow%", - "%config.promptToOpenClonedRepository.noFolderOpened%", - "%config.promptToOpenClonedRepository.showPrompt%" + "%config.openAfterClone.currentWindow%", + "%config.openAfterClone.newWindow%", + "%config.openAfterClone.noFolderOpened%", + "%config.openAfterClone.showPrompt%" ], "default": "showPrompt", - "description": "%config.promptToOpenClonedRepository%" + "description": "%config.openAfterClone%" }, "git.showInlineOpenFileAction": { "type": "boolean", diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 3ef518de24e..7b0f2a57866 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -116,11 +116,11 @@ "config.postCommitCommand.none": "Don't run any command after a commit.", "config.postCommitCommand.push": "Run 'Git Push' after a successful commit.", "config.postCommitCommand.sync": "Run 'Git Sync' after a successful commit.", - "config.promptToOpenClonedRepository": "Controls whether to show a prompt after cloning a repository.", - "config.promptToOpenClonedRepository.currentWindow": "Open repository in current window.", - "config.promptToOpenClonedRepository.newWindow": "Open repository in new window.", - "config.promptToOpenClonedRepository.noFolderOpened": "Open in current window if no folder is opened. Otherwise show prompt.", - "config.promptToOpenClonedRepository.showPrompt": "Always show prompt to choose action.", + "config.openAfterClone": "Controls whether to show a prompt after cloning a repository.", + "config.openAfterClone.currentWindow": "Open repository in current window.", + "config.openAfterClone.newWindow": "Open repository in new window.", + "config.openAfterClone.noFolderOpened": "Open in current window if no folder is opened. Otherwise show prompt.", + "config.openAfterClone.showPrompt": "Always show prompt to choose action.", "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.", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index b707f75bdc2..8e5d5a06e3e 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -619,16 +619,16 @@ export class CommandCenter { ); let config = workspace.getConfiguration('git'); - let promptToOpenClonedRepository = config.get<'currentWindow' | 'newWindow' | 'noFolderOpened' | 'showPrompt'>('promptToOpenClonedRepository'); + let openAfterClone = config.get<'currentWindow' | 'newWindow' | 'noFolderOpened' | 'showPrompt'>('openAfterClone'); const uri = Uri.file(repositoryPath); - if (promptToOpenClonedRepository === 'currentWindow') { + if (openAfterClone === 'currentWindow') { commands.executeCommand('vscode.openFolder', uri); - } else if (promptToOpenClonedRepository === 'newWindow') { + } else if (openAfterClone === 'newWindow') { commands.executeCommand('vscode.openFolder', uri, true); } else { - if (promptToOpenClonedRepository === 'noFolderOpened') { + if (openAfterClone === 'noFolderOpened') { if (!workspace.workspaceFolders) { commands.executeCommand('vscode.openFolder', uri); } From 1531898fdb10cd559f62669f14afddcb1c179f3e Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 22 Apr 2020 21:22:48 +0200 Subject: [PATCH 0012/1262] avoid loading and twisty set at the same time --- src/vs/base/browser/ui/tree/abstractTree.ts | 12 +++++++++--- src/vs/base/browser/ui/tree/asyncDataTree.ts | 6 ++++-- src/vs/base/browser/ui/tree/objectTree.ts | 5 +++-- src/vs/base/browser/ui/tree/tree.ts | 2 +- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index a71e4d6ae48..e5ad99be7c6 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -401,15 +401,21 @@ class TreeRenderer implements IListRenderer } private renderTwistie(node: ITreeNode, templateData: ITreeListTemplateData) { + removeClasses(templateData.twistie, treeItemExpandedIcon.classNames); + + let twistieRendered = false; if (this.renderer.renderTwistie) { - this.renderer.renderTwistie(node.element, templateData.twistie); + twistieRendered = this.renderer.renderTwistie(node.element, templateData.twistie); } if (node.collapsible && (!this.hideTwistiesOfChildlessElements || node.visibleChildrenCount > 0)) { - addClasses(templateData.twistie, treeItemExpandedIcon.classNames, 'collapsible'); + if (!twistieRendered) { + addClasses(templateData.twistie, treeItemExpandedIcon.classNames); + } + addClasses(templateData.twistie, 'collapsible'); toggleClass(templateData.twistie, 'collapsed', node.collapsed); } else { - removeClasses(templateData.twistie, treeItemExpandedIcon.classNames, 'collapsible', 'collapsed'); + removeClasses(templateData.twistie, 'collapsible', 'collapsed'); } if (node.collapsible) { diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 1e9600e2bff..5102e51a76a 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -112,10 +112,11 @@ class AsyncDataTreeRenderer implements IT renderTwistie(element: IAsyncDataTreeNode, twistieElement: HTMLElement): boolean { if (element.slow) { addClasses(twistieElement, treeItemLoadingIcon.classNames); + return true; } else { removeClasses(twistieElement, treeItemLoadingIcon.classNames); + return false; } - return false; } disposeElement(node: ITreeNode, TFilterData>, index: number, templateData: IDataTreeListTemplateData, height: number | undefined): void { @@ -1056,10 +1057,11 @@ class CompressibleAsyncDataTreeRenderer i renderTwistie(element: IAsyncDataTreeNode, twistieElement: HTMLElement): boolean { if (element.slow) { addClasses(twistieElement, treeItemLoadingIcon.classNames); + return true; } else { removeClasses(twistieElement, treeItemLoadingIcon.classNames); + return false; } - return false; } disposeElement(node: ITreeNode, TFilterData>, index: number, templateData: IDataTreeListTemplateData, height: number | undefined): void { diff --git a/src/vs/base/browser/ui/tree/objectTree.ts b/src/vs/base/browser/ui/tree/objectTree.ts index 38647079853..c0fbecdf732 100644 --- a/src/vs/base/browser/ui/tree/objectTree.ts +++ b/src/vs/base/browser/ui/tree/objectTree.ts @@ -124,10 +124,11 @@ class CompressibleRenderer, TFilterData, TTemplateDat this.renderer.disposeTemplate(templateData.data); } - renderTwistie?(element: T, twistieElement: HTMLElement): void { + renderTwistie?(element: T, twistieElement: HTMLElement): boolean { if (this.renderer.renderTwistie) { - this.renderer.renderTwistie(element, twistieElement); + return this.renderer.renderTwistie(element, twistieElement); } + return false; } } diff --git a/src/vs/base/browser/ui/tree/tree.ts b/src/vs/base/browser/ui/tree/tree.ts index 085bb3d90b7..c17b41fbbed 100644 --- a/src/vs/base/browser/ui/tree/tree.ts +++ b/src/vs/base/browser/ui/tree/tree.ts @@ -129,7 +129,7 @@ export interface ITreeModel { } export interface ITreeRenderer extends IListRenderer, TTemplateData> { - renderTwistie?(element: T, twistieElement: HTMLElement): void; + renderTwistie?(element: T, twistieElement: HTMLElement): boolean; onDidChangeTwistieState?: Event; } From 8e8dc25e6597993141a94cb57f4b5def32313389 Mon Sep 17 00:00:00 2001 From: Evan Krause Date: Wed, 6 May 2020 14:43:00 -0700 Subject: [PATCH 0013/1262] Don't focus editor when un-expanded comment is hidden --- .../contrib/comments/browser/commentThreadWidget.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index b99e99ff0ce..f14e3fa6ffe 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -928,9 +928,11 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } hide() { - this._isExpanded = false; - // Focus the container so that the comment editor will be blurred before it is hidden - this.editor.focus(); + if (this._isExpanded) { + this._isExpanded = false; + // Focus the container so that the comment editor will be blurred before it is hidden + this.editor.focus(); + } super.hide(); } From b24cb8b47d4ed3703e5018f6c45f5feee70d2095 Mon Sep 17 00:00:00 2001 From: kingwl Date: Mon, 11 May 2020 19:01:45 +0800 Subject: [PATCH 0014/1262] Add rename by git context menu --- extensions/git/package.json | 13 +++++++++++++ extensions/git/package.nls.json | 1 + extensions/git/src/commands.ts | 21 +++++++++++++++++++++ extensions/git/src/git.ts | 5 +++++ extensions/git/src/repository.ts | 6 ++++++ extensions/types/lib.textEncoder.d.ts | 4 ++-- 6 files changed, 48 insertions(+), 2 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index ea99da5140a..b0b11e653a7 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -150,6 +150,12 @@ "category": "Git", "icon": "$(discard)" }, + { + "command": "git.rename", + "title": "%command.rename%", + "category": "Git", + "icon": "$(discard)" + }, { "command": "git.cleanAll", "title": "%command.cleanAll%", @@ -1296,6 +1302,13 @@ "group": "5_copy@2", "when": "config.git.enabled && !git.missing && timelineItem =~ /git:file:commit\\b/" } + ], + "explorer/context": [ + { + "command": "git.rename", + "group": "7_modification", + "when": "config.git.enabled && !git.missing && !explorerResourceIsRoot" + } ] }, "configuration": { diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 39965be4f75..eba9ea7eefc 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -21,6 +21,7 @@ "command.unstage": "Unstage Changes", "command.unstageAll": "Unstage All Changes", "command.unstageSelectedRanges": "Unstage Selected Ranges", + "command.rename": "Rename (Git)", "command.clean": "Discard Changes", "command.cleanAll": "Discard All Changes", "command.cleanAllTracked": "Discard All Tracked Changes", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index d6eeeec88ba..48984566cdd 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -19,6 +19,7 @@ import { grep, isDescendant, pathEquals } from './util'; import { Log, LogLevel } from './log'; import { GitTimelineItem } from './timelineProvider'; import { throttle, debounce } from './decorators'; +import { URI } from 'vscode-uri'; const localize = nls.loadMessageBundle(); @@ -930,6 +931,26 @@ export class CommandCenter { } } + @command('git.rename', { repository: true }) + async rename(repository: Repository, fromUri: URI): Promise { + this.outputChannel.appendLine(`git.rename ${fromUri.fsPath}`); + + const rootPath = workspace.rootPath; + const fromPath = workspace.asRelativePath(fromUri); + const fromBasename = path.basename(fromPath); + const toPath = await window.showInputBox({ + value: fromPath, + valueSelection: [fromPath.length - fromBasename.length, fromPath.length] + }); + if (!toPath?.trim()) { + return; + } + + const fullToPath = path.join(rootPath || '', toPath); + this.outputChannel.appendLine(`git.rename from ${fromPath} to ${fullToPath}`); + await repository.move(fromPath, fullToPath); + } + @command('git.stage') async stage(...resourceStates: SourceControlResourceState[]): Promise { this.outputChannel.appendLine(`git.stage ${resourceStates.length}`); diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 1b5c8d48371..8323ef3a459 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -1390,6 +1390,11 @@ export class Repository { await this.run(args); } + async move(from: string, to: string): Promise { + const args = ['mv', from, to]; + await this.run(args); + } + async setBranchUpstream(name: string, upstream: string): Promise { const args = ['branch', '--set-upstream-to', upstream, name]; await this.run(args); diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 9e85d0fca0c..7466cfd8112 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -310,6 +310,8 @@ export const enum Operation { Blame = 'Blame', Log = 'Log', LogFile = 'LogFile', + + Move = 'Move' } function isReadOnly(operation: Operation): boolean { @@ -1045,6 +1047,10 @@ export class Repository implements Disposable { await this.run(Operation.RenameBranch, () => this.repository.renameBranch(name)); } + async move(from: string, to: string): Promise { + await this.run(Operation.Move, () => this.repository.move(from, to)); + } + async getBranch(name: string): Promise { return await this.run(Operation.GetBranch, () => this.repository.getBranch(name)); } diff --git a/extensions/types/lib.textEncoder.d.ts b/extensions/types/lib.textEncoder.d.ts index 99a5b2271d6..02e1b4890af 100644 --- a/extensions/types/lib.textEncoder.d.ts +++ b/extensions/types/lib.textEncoder.d.ts @@ -7,5 +7,5 @@ // // Proper fix: https://github.com/microsoft/TypeScript/issues/31535 -declare var TextDecoder: typeof import('util').TextDecoder; -declare var TextEncoder: typeof import('util').TextEncoder; +declare let TextDecoder: typeof import('util').TextDecoder; +declare let TextEncoder: typeof import('util').TextEncoder; From 31ee5b96449103913998a073686fe69f1f6e3eef Mon Sep 17 00:00:00 2001 From: kingwl Date: Mon, 11 May 2020 22:57:34 +0800 Subject: [PATCH 0015/1262] fix something --- extensions/git/src/commands.ts | 3 +-- extensions/types/lib.textEncoder.d.ts | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 48984566cdd..c370133d8d7 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -19,7 +19,6 @@ import { grep, isDescendant, pathEquals } from './util'; import { Log, LogLevel } from './log'; import { GitTimelineItem } from './timelineProvider'; import { throttle, debounce } from './decorators'; -import { URI } from 'vscode-uri'; const localize = nls.loadMessageBundle(); @@ -932,7 +931,7 @@ export class CommandCenter { } @command('git.rename', { repository: true }) - async rename(repository: Repository, fromUri: URI): Promise { + async rename(repository: Repository, fromUri: Uri): Promise { this.outputChannel.appendLine(`git.rename ${fromUri.fsPath}`); const rootPath = workspace.rootPath; diff --git a/extensions/types/lib.textEncoder.d.ts b/extensions/types/lib.textEncoder.d.ts index 02e1b4890af..99a5b2271d6 100644 --- a/extensions/types/lib.textEncoder.d.ts +++ b/extensions/types/lib.textEncoder.d.ts @@ -7,5 +7,5 @@ // // Proper fix: https://github.com/microsoft/TypeScript/issues/31535 -declare let TextDecoder: typeof import('util').TextDecoder; -declare let TextEncoder: typeof import('util').TextEncoder; +declare var TextDecoder: typeof import('util').TextDecoder; +declare var TextEncoder: typeof import('util').TextEncoder; From 8561cbb8aef51abe906b43db8952dd14a359f60a Mon Sep 17 00:00:00 2001 From: kingwl Date: Tue, 12 May 2020 00:36:28 +0800 Subject: [PATCH 0016/1262] Add force checkout and smart checkout --- extensions/git/src/commands.ts | 69 +++++++++++++++++++++++++++------- extensions/git/src/git.ts | 1 + 2 files changed, 57 insertions(+), 13 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index d6eeeec88ba..f1e217422ff 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -6,7 +6,7 @@ import { lstat, Stats } from 'fs'; import * as os from 'os'; import * as path from 'path'; -import { commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, QuickPick } from 'vscode'; +import { commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, QuickPick, SourceControl } from 'vscode'; import TelemetryReporter from 'vscode-extension-telemetry'; import * as nls from 'vscode-nls'; import { Branch, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourceProvider, RemoteSource } from './api/git'; @@ -2506,18 +2506,7 @@ export class CommandCenter { if (!options.repository) { result = Promise.resolve(method.apply(this, args)); } else { - // try to guess the repository based on the first argument - const repository = this.model.getRepository(args[0]); - let repositoryPromise: Promise; - - if (repository) { - repositoryPromise = Promise.resolve(repository); - } else if (this.model.repositories.length === 1) { - repositoryPromise = Promise.resolve(this.model.repositories[0]); - } else { - repositoryPromise = this.model.pickRepository(); - } - + const repositoryPromise = this.guessRepository(args[0]); result = repositoryPromise.then(repository => { if (!repository) { return Promise.resolve(); @@ -2544,12 +2533,19 @@ export class CommandCenter { const choices = new Map void>(); const openOutputChannelChoice = localize('open git log', "Open Git Log"); + const forceCheckoutChoice = localize('force checkout', "Force Checkout"); + const smartCheckoutChoice = localize('smart checkout', "Smart Checkout"); const outputChannel = this.outputChannel as OutputChannel; choices.set(openOutputChannelChoice, () => outputChannel.show()); switch (err.gitErrorCode) { case GitErrorCodes.DirtyWorkTree: message = localize('clean repo', "Please clean your repository working tree before checkout."); + if (err.gitTreeish) { + options.modal = true; + choices.set(forceCheckoutChoice, () => forceCheckout(err.gitTreeish, args)); + choices.set(smartCheckoutChoice, () => smartCheckout(err.gitTreeish, args)); + } break; case GitErrorCodes.PushRejected: message = localize('cant push', "Can't push refs to remote. Try running 'Pull' first to integrate your changes."); @@ -2612,12 +2608,59 @@ export class CommandCenter { }); }; + const forceCheckout = async (treeish: string, args: any[]) => { + const repo = await this.guessRepository(args[0]); + if (!repo) { + return; + } + + this.outputChannel.appendLine('force checkout: clean all'); + await this.cleanAll(repo); + this.outputChannel.appendLine(`force checkout: checkout ${treeish}`); + await repo.checkout(treeish); + this.outputChannel.appendLine('force checkout: done'); + }; + + const smartCheckout = async (treeish: string, args: any[]) => { + const repo = await this.guessRepository(args[0]); + if (!repo) { + return; + } + + this.outputChannel.appendLine('smart checkout: stash'); + await repo.createStash(); + try { + this.outputChannel.appendLine(`smart checkout: checkout ${treeish}`); + await repo.checkout(treeish); + } finally { + this.outputChannel.appendLine('smart checkout pop stash'); + await repo.popStash(); + } + this.outputChannel.appendLine('smart checkout: done'); + }; + // patch this object, so people can call methods directly (this as any)[key] = result; return result; } + /** + * try to guess the repository based on the first argument + * @param sourceControl + */ + private guessRepository (sourceControl: SourceControl) { + const repository = this.model.getRepository(sourceControl); + + if (repository) { + return Promise.resolve(repository); + } else if (this.model.repositories.length === 1) { + return Promise.resolve(this.model.repositories[0]); + } else { + return this.model.pickRepository(); + } + } + private getSCMResource(uri?: Uri): Resource | undefined { uri = uri ? uri : (window.activeTextEditor && window.activeTextEditor.document.uri); diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 1b5c8d48371..62ca189e51a 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -1298,6 +1298,7 @@ export class Repository { } catch (err) { if (/Please,? commit your changes or stash them/.test(err.stderr || '')) { err.gitErrorCode = GitErrorCodes.DirtyWorkTree; + err.gitTreeish = treeish; } throw err; From 63ccc69f08f27bf5888d1706b80dd1a5a12286f5 Mon Sep 17 00:00:00 2001 From: Borja Zarco Date: Sun, 10 May 2020 23:57:21 -0400 Subject: [PATCH 0017/1262] Fix launch configuration input variable resolution. When resolving launch configuration variables during a debug session, the configuration target was not being specified, always defaulting to reading workspace folder inputs. This made it impossible for user or workspace file launch configurations to use input variables, as the inputs list was never found. This change forwards the launch configuration source to the configurationResolverService, so that it looks for the inputs list in the right place. Forwarding the source fixed single-root workspaces, but multi-root workspaces were skipping the inputs lookup, since they pass an undefined workspace folder... In this case, the workspace folder is not relevant, as the config and inputs are defined in the workspace file, and allowing resolution to continue yields the desired behavior. --- .../browser/debugConfigurationManager.ts | 13 ++++++-- .../workbench/contrib/debug/common/debug.ts | 3 +- .../contrib/debug/common/debugger.ts | 2 +- .../browser/configurationResolverService.ts | 6 ++-- .../configurationResolverService.test.ts | 33 +++++++++++++++++++ 5 files changed, 50 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index d0eff031334..17c3b1bda8e 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -625,8 +625,17 @@ abstract class AbstractLaunch { if (!config || !config.configurations) { return undefined; } - - return config.configurations.find(config => config && config.name === name); + const configuration = config.configurations.find(config => config && config.name === name); + if (configuration) { + if (this instanceof UserLaunch) { + configuration.__configurationTarget = ConfigurationTarget.USER; + } else if (this instanceof WorkspaceLaunch) { + configuration.__configurationTarget = ConfigurationTarget.WORKSPACE; + } else { + configuration.__configurationTarget = ConfigurationTarget.WORKSPACE_FOLDER; + } + } + return configuration; } async getInitialConfigurationContent(folderUri?: uri, type?: string, token?: CancellationToken): Promise { diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 997e8f4df84..26df2485c28 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -21,7 +21,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IDisposable } from 'vs/base/common/lifecycle'; import { TaskIdentifier } from 'vs/workbench/contrib/tasks/common/tasks'; import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { CancellationToken } from 'vs/base/common/cancellation'; import { DebugConfigurationProviderTriggerKind } from 'vs/workbench/api/common/extHostTypes'; import { DebugCompoundRoot } from 'vs/workbench/contrib/debug/common/debugCompoundRoot'; @@ -512,6 +512,7 @@ export interface IConfig extends IEnvConfig { linux?: IEnvConfig; // internals + __configurationTarget?: ConfigurationTarget; __sessionId?: string; __restart?: any; __autoAttach?: boolean; diff --git a/src/vs/workbench/contrib/debug/common/debugger.ts b/src/vs/workbench/contrib/debug/common/debugger.ts index ca1ee7ebfc3..8ede6963b15 100644 --- a/src/vs/workbench/contrib/debug/common/debugger.ts +++ b/src/vs/workbench/contrib/debug/common/debugger.ts @@ -107,7 +107,7 @@ export class Debugger implements IDebugger { substituteVariables(folder: IWorkspaceFolder | undefined, config: IConfig): Promise { return this.configurationManager.substituteVariables(this.type, folder, config).then(config => { - return this.configurationResolverService.resolveWithInteractionReplace(folder, config, 'launch', this.variables); + return this.configurationResolverService.resolveWithInteractionReplace(folder, config, 'launch', this.variables, config.__configurationTarget); }); } diff --git a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts index 42b3431dc41..0a35a47cd77 100644 --- a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts +++ b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts @@ -147,8 +147,8 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR // get all "inputs" let inputs: ConfiguredInput[] = []; - if (folder && this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY && section) { - let result = this.configurationService.inspect(section, { resource: folder.uri }); + if (this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY && section) { + let result = this.configurationService.inspect(section, { resource: folder?.uri }); if (result && (result.userValue || result.workspaceValue || result.workspaceFolderValue)) { switch (target) { case ConfigurationTarget.USER: inputs = (result.userValue)?.inputs; break; @@ -156,7 +156,7 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR default: inputs = (result.workspaceFolderValue)?.inputs; } } else { - const valueResult = this.configurationService.getValue(section, { resource: folder.uri }); + const valueResult = this.configurationService.getValue(section, { resource: folder?.uri }); if (valueResult) { inputs = valueResult.inputs; } diff --git a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts index 9d1f709f55c..de7980bef6e 100644 --- a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts +++ b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts @@ -443,6 +443,7 @@ suite('Configuration Resolver Service', () => { assert.equal(1, mockCommandService.callCount); }); }); + test('a single prompt input variable', () => { const configuration = { @@ -470,6 +471,7 @@ suite('Configuration Resolver Service', () => { assert.equal(0, mockCommandService.callCount); }); }); + test('a single pick input variable', () => { const configuration = { @@ -497,6 +499,7 @@ suite('Configuration Resolver Service', () => { assert.equal(0, mockCommandService.callCount); }); }); + test('a single command input variable', () => { const configuration = { @@ -524,6 +527,7 @@ suite('Configuration Resolver Service', () => { assert.equal(1, mockCommandService.callCount); }); }); + test('several input variables and command', () => { const configuration = { @@ -553,6 +557,35 @@ suite('Configuration Resolver Service', () => { assert.equal(2, mockCommandService.callCount); }); }); + + test('input variable with undefined workspace folder', () => { + + const configuration = { + 'name': 'Attach to Process', + 'type': 'node', + 'request': 'attach', + 'processId': '${input:input1}', + 'port': 5858, + 'sourceMaps': false, + 'outDir': null + }; + + return configurationResolverService!.resolveWithInteractionReplace(undefined, configuration, 'tasks').then(result => { + + assert.deepEqual(result, { + 'name': 'Attach to Process', + 'type': 'node', + 'request': 'attach', + 'processId': 'resolvedEnterinput1', + 'port': 5858, + 'sourceMaps': false, + 'outDir': null + }); + + assert.equal(0, mockCommandService.callCount); + }); + }); + test('contributed variable', () => { const buildTask = 'npm: compile'; const variable = 'defaultBuildTask'; From 352f231bec54ae53cf56262f80d45d25fc7e3e29 Mon Sep 17 00:00:00 2001 From: Borja Zarco Date: Mon, 11 May 2020 08:03:24 -0400 Subject: [PATCH 0018/1262] Do not define resource override unless folder is defined. --- .../browser/configurationResolverService.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts index 0a35a47cd77..5b9afb5b030 100644 --- a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts +++ b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts @@ -11,7 +11,7 @@ import { Schemas } from 'vs/base/common/network'; import { toResource } from 'vs/workbench/common/editor'; import { IStringDictionary, forEach, fromMap } from 'vs/base/common/collections'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, IConfigurationOverrides, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IWorkspaceFolder, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -148,7 +148,8 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR // get all "inputs" let inputs: ConfiguredInput[] = []; if (this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY && section) { - let result = this.configurationService.inspect(section, { resource: folder?.uri }); + const overrides: IConfigurationOverrides = folder ? { resource: folder.uri } : {}; + let result = this.configurationService.inspect(section, overrides); if (result && (result.userValue || result.workspaceValue || result.workspaceFolderValue)) { switch (target) { case ConfigurationTarget.USER: inputs = (result.userValue)?.inputs; break; @@ -156,7 +157,7 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR default: inputs = (result.workspaceFolderValue)?.inputs; } } else { - const valueResult = this.configurationService.getValue(section, { resource: folder?.uri }); + const valueResult = this.configurationService.getValue(section, overrides); if (valueResult) { inputs = valueResult.inputs; } From fc797d2430903cd49c06e97ed92be84bccb4eca8 Mon Sep 17 00:00:00 2001 From: Asif Hasan Date: Wed, 3 Jun 2020 18:00:11 -0500 Subject: [PATCH 0019/1262] fix 97472 --- extensions/git/src/repository.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 44a8858c63d..e93d0212a0c 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1162,11 +1162,12 @@ export class Repository implements Disposable { const fetchOnPull = config.get('fetchOnPull'); const tags = config.get('pullTags'); + // When fetchOnPull is enabled, fetch all branches when pulling if (fetchOnPull) { - await this.repository.pull(rebase, undefined, undefined, { unshallow, tags }); - } else { - await this.repository.pull(rebase, remote, branch, { unshallow, tags }); + await this.repository.fetch({ all: true }); } + + await this.repository.pull(rebase, remote, branch, { unshallow, tags }); }); }); } From df3af97279be7431f8fb6d333572e9f79c8cb80b Mon Sep 17 00:00:00 2001 From: Asif Hasan Date: Wed, 3 Jun 2020 18:23:37 -0500 Subject: [PATCH 0020/1262] fix fetchOnPull behavior for Sync --- extensions/git/src/git.ts | 6 ++++-- extensions/git/src/repository.ts | 13 ++++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 66d91da4d7a..b95e7009d10 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -1513,9 +1513,11 @@ export class Repository { await this.run(args); } - async fetch(options: { remote?: string, ref?: string, all?: boolean, prune?: boolean, depth?: number, silent?: boolean } = {}): Promise { + async fetch(options: { remote?: string, ref?: string, all?: boolean, prune?: boolean, depth?: number, silent?: boolean, readonly cancellationToken?: CancellationToken } = {}): Promise { const args = ['fetch']; - const spawnOptions: SpawnOptions = {}; + const spawnOptions: SpawnOptions = { + cancellationToken: options.cancellationToken, + }; if (options.remote) { args.push(options.remote); diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index e93d0212a0c..577eff4fd7c 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1225,9 +1225,16 @@ export class Repository implements Disposable { const tags = config.get('pullTags'); const supportCancellation = config.get('supportCancellation'); - const fn = fetchOnPull - ? async (cancellationToken?: CancellationToken) => await this.repository.pull(rebase, undefined, undefined, { tags, cancellationToken }) - : async (cancellationToken?: CancellationToken) => await this.repository.pull(rebase, remoteName, pullBranch, { tags, cancellationToken }); + const fn = async (cancellationToken?: CancellationToken) => { + + // When fetchOnPull is enabled, fetch all branches when pulling + if (fetchOnPull) { + await this.repository.fetch({ all: true, cancellationToken }); + } + + await this.repository.pull(rebase, remoteName, pullBranch, { tags, cancellationToken }); + }; + if (supportCancellation) { const opts: ProgressOptions = { From 3f585d74003b24c59583af529220e8503bc5294a Mon Sep 17 00:00:00 2001 From: kingwl Date: Sat, 9 May 2020 12:35:48 +0800 Subject: [PATCH 0021/1262] Add better support for checkout type config --- extensions/git/package.json | 15 ++----------- extensions/git/package.nls.json | 6 +----- extensions/git/src/commands.ts | 37 ++++++++++++++++++++++++--------- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index d33bafbad5f..2d056f6a393 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1399,20 +1399,9 @@ }, "git.checkoutType": { "type": "string", - "enum": [ - "all", - "local", - "tags", - "remote" - ], - "enumDescriptions": [ - "%config.checkoutType.all%", - "%config.checkoutType.local%", - "%config.checkoutType.tags%", - "%config.checkoutType.remote%" - ], + "markdownDescription": "%config.checkoutType%", - "default": "all" + "default": "local,remote,tags" }, "git.ignoreLegacyWarning": { "type": "boolean", diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index be817e22df1..b1506ed9568 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -90,11 +90,7 @@ "config.countBadge.all": "Count all changes.", "config.countBadge.tracked": "Count only tracked changes.", "config.countBadge.off": "Turn off counter.", - "config.checkoutType": "Controls what type of branches are listed when running `Checkout to...`.", - "config.checkoutType.all": "Show all references.", - "config.checkoutType.local": "Show only local branches.", - "config.checkoutType.tags": "Show only tags.", - "config.checkoutType.remote": "Show only remote branches.", + "config.checkoutType": "Controls what type of branches (local, remote or tags, split with ',') are listed when running `Checkout to...`.", "config.branchValidationRegex": "A regular expression to validate new branch names.", "config.branchWhitespaceChar": "The character to replace whitespace in new branch names.", "config.ignoreLegacyWarning": "Ignores the legacy Git warning.", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index e60dc39e6a6..9c2e59cf515 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -203,18 +203,35 @@ async function categorizeResourceByResolution(resources: Resource[]): Promise<{ function createCheckoutItems(repository: Repository): CheckoutItem[] { const config = workspace.getConfiguration('git'); - const checkoutType = config.get('checkoutType') || 'all'; - const includeTags = checkoutType === 'all' || checkoutType === 'tags'; - const includeRemotes = checkoutType === 'all' || checkoutType === 'remote'; + const checkoutType = config.get('checkoutType') || 'local,remote,tags'; + const checkoutTypes = checkoutType.trim().split(',').map(type => type.trim()); - const heads = repository.refs.filter(ref => ref.type === RefType.Head) - .map(ref => new CheckoutItem(ref)); - const tags = (includeTags ? repository.refs.filter(ref => ref.type === RefType.Tag) : []) - .map(ref => new CheckoutTagItem(ref)); - const remoteHeads = (includeRemotes ? repository.refs.filter(ref => ref.type === RefType.RemoteHead) : []) - .map(ref => new CheckoutRemoteHeadItem(ref)); + const results: CheckoutItem[] = []; + const invalids = new Set(); + const seens = new Set(); + checkoutTypes.forEach(type => { + if (seens.has(type)) { + return; + } + seens.add(type); - return [...heads, ...tags, ...remoteHeads]; + switch (type) { + case 'local': + results.push(...repository.refs.filter(ref => ref.type === RefType.Head).map(ref => new CheckoutItem(ref))); + break; + case 'remote': + results.push(...repository.refs.filter(ref => ref.type === RefType.RemoteHead).map(ref => new CheckoutRemoteHeadItem(ref))); + break; + case 'tags': + results.push(...repository.refs.filter(ref => ref.type === RefType.Tag).map(ref => new CheckoutTagItem(ref))); + break; + default: + invalids.add(type); + break; + } + }); + + return results; } function sanitizeRemoteName(name: string) { From ae540536b42f83e7503cf0c876488061f9ac34e5 Mon Sep 17 00:00:00 2001 From: kingwl Date: Fri, 5 Jun 2020 17:41:46 +0800 Subject: [PATCH 0022/1262] Rewrite checkout items --- extensions/git/src/commands.ts | 45 ++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 9c2e59cf515..a2b741ad4d3 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -6,7 +6,7 @@ import { lstat, Stats } from 'fs'; import * as os from 'os'; import * as path from 'path'; -import { commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, QuickPick } from 'vscode'; +import { commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, QuickPick, debug } from 'vscode'; import TelemetryReporter from 'vscode-extension-telemetry'; import * as nls from 'vscode-nls'; import { Branch, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourceProvider, RemoteSource } from './api/git'; @@ -70,6 +70,11 @@ class CheckoutRemoteHeadItem extends CheckoutItem { } } +interface RefTypeAndCheckoutItem { + refType: RefType; + checkoutItemCtor: { new(ref: Ref): CheckoutItem; }; +} + class BranchDeleteItem implements QuickPickItem { private get shortCommit(): string { return (this.ref.commit || '').substr(0, 8); } @@ -203,37 +208,39 @@ async function categorizeResourceByResolution(resources: Resource[]): Promise<{ function createCheckoutItems(repository: Repository): CheckoutItem[] { const config = workspace.getConfiguration('git'); - const checkoutType = config.get('checkoutType') || 'local,remote,tags'; - const checkoutTypes = checkoutType.trim().split(',').map(type => type.trim()); + const checkoutTypeString = config.get('checkoutType'); + + const checkoutTypeOptions = ['local', 'remote', 'tags']; + const checkoutTypes = checkoutTypeString?.trim().split(',').map(type => type.trim()).filter(type => checkoutTypeOptions.includes(type)); const results: CheckoutItem[] = []; - const invalids = new Set(); const seens = new Set(); - checkoutTypes.forEach(type => { + (checkoutTypes && checkoutTypes.length ? checkoutTypes : checkoutTypeOptions).forEach(type => { if (seens.has(type)) { return; } seens.add(type); - switch (type) { - case 'local': - results.push(...repository.refs.filter(ref => ref.type === RefType.Head).map(ref => new CheckoutItem(ref))); - break; - case 'remote': - results.push(...repository.refs.filter(ref => ref.type === RefType.RemoteHead).map(ref => new CheckoutRemoteHeadItem(ref))); - break; - case 'tags': - results.push(...repository.refs.filter(ref => ref.type === RefType.Tag).map(ref => new CheckoutTagItem(ref))); - break; - default: - invalids.add(type); - break; - } + const { refType, checkoutItemCtor } = getRefTypeAndCheckoutItem(type); + results.push(...repository.refs.filter(ref => ref.type === refType).map(ref => new checkoutItemCtor(ref))); }); return results; } +function getRefTypeAndCheckoutItem(type: string): RefTypeAndCheckoutItem { + switch (type) { + case 'local': + return { refType: RefType.Head, checkoutItemCtor: CheckoutItem }; + case 'remote': + return { refType: RefType.RemoteHead, checkoutItemCtor: CheckoutRemoteHeadItem }; + case 'tags': + return { refType: RefType.Tag, checkoutItemCtor: CheckoutTagItem }; + default: + throw new Error(`Unexpected type: ${type}`); + } +} + function sanitizeRemoteName(name: string) { name = name.trim(); return name && name.replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$|\[|\]$/g, '-'); From d9d1be4e490f76dbc53a7e25d582add9b2099441 Mon Sep 17 00:00:00 2001 From: kingwl Date: Fri, 5 Jun 2020 22:30:29 +0800 Subject: [PATCH 0023/1262] Avoid debug --- extensions/git/src/commands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index a2b741ad4d3..f21aa23f9ae 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -6,7 +6,7 @@ import { lstat, Stats } from 'fs'; import * as os from 'os'; import * as path from 'path'; -import { commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, QuickPick, debug } from 'vscode'; +import { commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, QuickPick } from 'vscode'; import TelemetryReporter from 'vscode-extension-telemetry'; import * as nls from 'vscode-nls'; import { Branch, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourceProvider, RemoteSource } from './api/git'; From b4c528cbfd2c85eed10f91f4aee9e35864fd4302 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 8 Aug 2020 14:05:51 -0500 Subject: [PATCH 0024/1262] fixes #103281 --- extensions/git/package.json | 6 ++++++ extensions/git/package.nls.json | 1 + extensions/git/src/repository.ts | 3 ++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 748868778dd..ba3e194ad03 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1568,6 +1568,12 @@ "description": "%config.enableStatusBarSync%", "scope": "resource" }, + "git.pushTags": { + "type": "boolean", + "scope": "resource", + "default": false, + "description": "%config.pushTags%" + }, "git.promptToSaveFilesBeforeCommit": { "type": "string", "enum": [ diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 0e8ea1c649e..af825fe317f 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -111,6 +111,7 @@ "config.discardAllScope": "Controls what changes are discarded by the `Discard all changes` command. `all` discards all changes. `tracked` discards only tracked files. `prompt` shows a prompt dialog every time the action is run.", "config.decorations.enabled": "Controls whether Git contributes colors and badges to the explorer and the open editors view.", "config.enableStatusBarSync": "Controls whether the Git Sync command appears in the status bar.", + "config.pushTags": "Push all tags when synchronizing.", "config.promptToSaveFilesBeforeCommit": "Controls whether Git should check for unsaved files before committing.", "config.promptToSaveFilesBeforeCommit.always": "Check for any unsaved files.", "config.promptToSaveFilesBeforeCommit.staged": "Check only for unsaved staged files.", diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index edebe7e54c1..3662ae60747 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1225,6 +1225,7 @@ export class Repository implements Disposable { const config = workspace.getConfiguration('git', Uri.file(this.root)); const fetchOnPull = config.get('fetchOnPull'); const tags = config.get('pullTags'); + const pushTags = config.get('pushTags'); const supportCancellation = config.get('supportCancellation'); const fn = fetchOnPull @@ -1252,7 +1253,7 @@ export class Repository implements Disposable { const shouldPush = this.HEAD && (typeof this.HEAD.ahead === 'number' ? this.HEAD.ahead > 0 : true); if (shouldPush) { - await this._push(remoteName, pushBranch); + await this._push(remoteName, pushBranch, false, pushTags); } }); }); From ff8d4feeb4361da1942f02ac040d92e42ecd8dc3 Mon Sep 17 00:00:00 2001 From: "sneakyfish5.sneaky@gmail.com" Date: Sun, 9 Aug 2020 16:48:22 -0500 Subject: [PATCH 0025/1262] Git: Add cherryPick command --- extensions/git/package.json | 9 +++++++++ extensions/git/package.nls.json | 1 + extensions/git/src/commands.ts | 15 +++++++++++++++ extensions/git/src/git.ts | 5 +++++ extensions/git/src/repository.ts | 5 +++++ 5 files changed, 35 insertions(+) diff --git a/extensions/git/package.json b/extensions/git/package.json index 748868778dd..767a70a5a68 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -326,6 +326,11 @@ "title": "%command.pushFollowTagsForce%", "category": "Git" }, + { + "command": "git.cherryPick", + "title": "%command.cherryPick%", + "category": "Git" + }, { "command": "git.addRemote", "title": "%command.addRemote%", @@ -673,6 +678,10 @@ "command": "git.pushWithTagsForce", "when": "config.git.enabled && !git.missing && config.git.allowForcePush && gitOpenRepositoryCount != 0" }, + { + "command": "git.cherryPick", + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" + }, { "command": "git.addRemote", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 0e8ea1c649e..0780ac89b8d 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -56,6 +56,7 @@ "command.pushToForce": "Push to... (Force)", "command.pushFollowTags": "Push (Follow Tags)", "command.pushFollowTagsForce": "Push (Follow Tags, Force)", + "command.cherryPick": "Cherry Pick...", "command.addRemote": "Add Remote...", "command.removeRemote": "Remove Remote", "command.sync": "Sync", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index f618a8669c2..a862568e22d 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -2027,6 +2027,21 @@ export class CommandCenter { await this._push(repository, { pushType: PushType.PushFollowTags, forcePush: true }); } + @command('git.cherryPick', { repository: true }) + async cherryPick(repository: Repository): Promise { + const inputCommitHash = await window.showInputBox({ + placeHolder: localize('commit hash', "Commit Hash"), + prompt: localize('provide commit hash', "Please provide the commit hash"), + ignoreFocusOut: true + }); + + if (!inputCommitHash) { + return; + } + + await repository.cherryPick(inputCommitHash); + } + @command('git.pushTo', { repository: true }) async pushTo(repository: Repository): Promise { await this._push(repository, { pushType: PushType.PushTo }); diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index d974c7bf1ca..9019a8e4772 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -1635,6 +1635,11 @@ export class Repository { } } + async cherryPick(commitHash: string): Promise { + const args = ['cherry-pick', commitHash]; + await this.run(args); + } + async blame(path: string): Promise { try { const args = ['blame', sanitizePath(path)]; diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index edebe7e54c1..124b34caf7a 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -294,6 +294,7 @@ export const enum Operation { Fetch = 'Fetch', Pull = 'Pull', Push = 'Push', + CherryPick = 'CherryPick', Sync = 'Sync', Show = 'Show', Stage = 'Stage', @@ -1195,6 +1196,10 @@ export class Repository implements Disposable { await this.run(Operation.Push, () => this._push(remote, undefined, false, true, forcePushMode)); } + async cherryPick(commitHash: string): Promise { + await this.run(Operation.CherryPick, () => this.repository.cherryPick(commitHash)); + } + async blame(path: string): Promise { return await this.run(Operation.Blame, () => this.repository.blame(path)); } From 11664e62a3c07902d240d9eb80d3e2eb856eee99 Mon Sep 17 00:00:00 2001 From: ae1020 Date: Tue, 18 Aug 2020 08:16:27 -0400 Subject: [PATCH 0026/1262] Make Clicking in Scrollbars Move By Page This changes clicking in the "gutter" area of a scrollbar from jumping to a position in the file proportional to the click, to the more standard behavior of "clicking before the slider does a page up (or left), and clicking after the slider does a page down (or right)". The behavior is requested in #43564 (and 101698, 100446, 87273 70267, 62834, 55139...) It would likely make a reasonable default, since clicking in the source preview right next to the scroll bar already permits jumping to absolute positions. However, this PR could also be amended to make it an option. Documentation of the implementation process is here: https://ae1020.github.io/vscode-scrollbar-go-by-page/ --- .../base/browser/ui/scrollbar/scrollbarState.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/vs/base/browser/ui/scrollbar/scrollbarState.ts b/src/vs/base/browser/ui/scrollbar/scrollbarState.ts index 48e20a5a033..57bb729e852 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollbarState.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollbarState.ts @@ -189,8 +189,10 @@ export class ScrollbarState { } /** - * Compute a desired `scrollPosition` such that `offset` ends up in the center of the slider. - * `offset` is based on the same coordinate system as the `sliderPosition`. + * Compute a desired `scrollPosition` from if offset is before or after the slider position. + * If offset is before slider, treat as a page up (or left). If after, page down (or right). + * `offset` and `_computedSliderPosition` are based on the same coordinate system. + * `_visibleSize` corresponds to a "page" of lines in the returned coordinate system. */ public getDesiredScrollPositionFromOffset(offset: number): number { if (!this._computedIsNeeded) { @@ -198,8 +200,14 @@ export class ScrollbarState { return 0; } - let desiredSliderPosition = offset - this._arrowSize - this._computedSliderSize / 2; - return Math.round(desiredSliderPosition / this._computedSliderRatio); + let correctedOffset = offset - this._arrowSize; // compensate if has arrows + let desiredScrollPosition = this._scrollPosition; + if (correctedOffset < this._computedSliderPosition) { + desiredScrollPosition -= this._visibleSize; // page up/left + } else { + desiredScrollPosition += this._visibleSize; // page down/right + } + return desiredScrollPosition; } /** From 8b21b331facc4c79b001bf7744f98b2017b6fbd2 Mon Sep 17 00:00:00 2001 From: ae1020 Date: Wed, 19 Aug 2020 20:22:39 -0400 Subject: [PATCH 0027/1262] Amend scrollbarState.test.ts for new positions The test file had calls to getDesiredScrollPositionFromOffset() which do not seem critical to the aspect being tested. This updates the two calls to reflect the new value resulting from movement by page. --- .../base/test/browser/ui/scrollbar/scrollbarState.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/base/test/browser/ui/scrollbar/scrollbarState.test.ts b/src/vs/base/test/browser/ui/scrollbar/scrollbarState.test.ts index 5ab03d9cd4c..d191083ce49 100644 --- a/src/vs/base/test/browser/ui/scrollbar/scrollbarState.test.ts +++ b/src/vs/base/test/browser/ui/scrollbar/scrollbarState.test.ts @@ -18,8 +18,9 @@ suite('ScrollbarState', () => { assert.equal(actual.getSliderSize(), 20); assert.equal(actual.getSliderPosition(), 249); + // 259 is greater than 230 so page down, 32787 + 339 = 33126 + assert.equal(actual.getDesiredScrollPositionFromOffset(259), 33126); - assert.equal(actual.getDesiredScrollPositionFromOffset(259), 32849); actual.setScrollPosition(32849); assert.equal(actual.getArrowSize(), 0); assert.equal(actual.getScrollPosition(), 32849); @@ -41,8 +42,9 @@ suite('ScrollbarState', () => { assert.equal(actual.getSliderSize(), 20); assert.equal(actual.getSliderPosition(), 230); + // 240 + 12 = 252; greater than 230 so page down, 32787 + 339 = 33126 + assert.equal(actual.getDesiredScrollPositionFromOffset(240 + 12), 33126); - assert.equal(actual.getDesiredScrollPositionFromOffset(240 + 12), 32811); actual.setScrollPosition(32811); assert.equal(actual.getArrowSize(), 12); assert.equal(actual.getScrollPosition(), 32811); From aca9ae328801177993dc735a10454779fde36960 Mon Sep 17 00:00:00 2001 From: ae1020 Date: Mon, 31 Aug 2020 12:00:47 -0400 Subject: [PATCH 0028/1262] Option: editor.scrollbar.gutterClickMovesByPage This makes paging behavior by clicking in the scroll bar gutter optional. --- .../browser/ui/scrollbar/abstractScrollbar.ts | 14 +++++++++++++- .../browser/ui/scrollbar/horizontalScrollbar.ts | 3 ++- .../browser/ui/scrollbar/scrollableElement.ts | 4 +++- .../ui/scrollbar/scrollableElementOptions.ts | 6 ++++++ .../base/browser/ui/scrollbar/scrollbarState.ts | 16 +++++++++++++++- .../browser/ui/scrollbar/verticalScrollbar.ts | 3 ++- .../browser/ui/scrollbar/scrollbarState.test.ts | 8 ++++++-- .../viewParts/editorScrollbar/editorScrollbar.ts | 1 + src/vs/editor/common/config/editorOptions.ts | 10 +++++++++- .../viewLayout/editorLayoutProvider.test.ts | 1 + src/vs/monaco.d.ts | 6 ++++++ 11 files changed, 64 insertions(+), 8 deletions(-) diff --git a/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts b/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts index 23bfdb2c7bf..5baba5c997f 100644 --- a/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts +++ b/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts @@ -38,12 +38,14 @@ export interface AbstractScrollbarOptions { visibility: ScrollbarVisibility; extraScrollbarClassName: string; scrollable: Scrollable; + gutterClickMovesByPage: boolean; } export abstract class AbstractScrollbar extends Widget { protected _host: ScrollbarHost; protected _scrollable: Scrollable; + protected _gutterClickMovesByPage: boolean; private _lazyRender: boolean; protected _scrollbarState: ScrollbarState; private _visibilityController: ScrollbarVisibilityController; @@ -59,6 +61,7 @@ export abstract class AbstractScrollbar extends Widget { this._lazyRender = opts.lazyRender; this._host = opts.host; this._scrollable = opts.scrollable; + this._gutterClickMovesByPage = opts.gutterClickMovesByPage; this._scrollbarState = opts.scrollbarState; this._visibilityController = this._register(new ScrollbarVisibilityController(opts.visibility, 'visible scrollbar ' + opts.extraScrollbarClassName, 'invisible scrollbar ' + opts.extraScrollbarClassName)); this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()); @@ -210,7 +213,16 @@ export abstract class AbstractScrollbar extends Widget { offsetX = e.posx - domNodePosition.left; offsetY = e.posy - domNodePosition.top; } - this._setDesiredScrollPositionNow(this._scrollbarState.getDesiredScrollPositionFromOffset(this._mouseDownRelativePosition(offsetX, offsetY))); + + let offset = this._mouseDownRelativePosition(offsetX, offsetY); + let scrollPos: number; + if (this._gutterClickMovesByPage) { + scrollPos = this._scrollbarState.getDesiredScrollPositionFromOffsetPaged(offset); + } else { + scrollPos = this._scrollbarState.getDesiredScrollPositionFromOffsetAbsolute(offset); + } + this._setDesiredScrollPositionNow(scrollPos); + if (e.leftButton) { e.preventDefault(); this._sliderMouseDown(e, () => { /*nothing to do*/ }); diff --git a/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts b/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts index 6e7f132e99f..9686d459bca 100644 --- a/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts +++ b/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts @@ -33,7 +33,8 @@ export class HorizontalScrollbar extends AbstractScrollbar { ), visibility: options.horizontal, extraScrollbarClassName: 'horizontal', - scrollable: scrollable + scrollable: scrollable, + gutterClickMovesByPage: options.gutterClickMovesByPage }); if (options.horizontalHasArrows) { diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts index 00bb9830d54..ba3c9b5f6f6 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts @@ -614,7 +614,9 @@ function resolveOptions(opts: ScrollableElementCreationOptions): ScrollableEleme vertical: (typeof opts.vertical !== 'undefined' ? opts.vertical : ScrollbarVisibility.Auto), verticalScrollbarSize: (typeof opts.verticalScrollbarSize !== 'undefined' ? opts.verticalScrollbarSize : 10), verticalHasArrows: (typeof opts.verticalHasArrows !== 'undefined' ? opts.verticalHasArrows : false), - verticalSliderSize: (typeof opts.verticalSliderSize !== 'undefined' ? opts.verticalSliderSize : 0) + verticalSliderSize: (typeof opts.verticalSliderSize !== 'undefined' ? opts.verticalSliderSize : 0), + + gutterClickMovesByPage: (typeof opts.gutterClickMovesByPage !== 'undefined' ? opts.gutterClickMovesByPage : false) }; result.horizontalSliderSize = (typeof opts.horizontalSliderSize !== 'undefined' ? opts.horizontalSliderSize : result.horizontalScrollbarSize); diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts b/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts index afb227be73b..491986dcc80 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts @@ -114,6 +114,11 @@ export interface ScrollableElementCreationOptions { * Defaults to false. */ verticalHasArrows?: boolean; + /** + * Scroll gutter clicks move by page vs. jump to position. + * Defaults to false. + */ + gutterClickMovesByPage?: boolean; } export interface ScrollableElementChangeOptions { @@ -146,4 +151,5 @@ export interface ScrollableElementResolvedOptions { verticalScrollbarSize: number; verticalSliderSize: number; verticalHasArrows: boolean; + gutterClickMovesByPage: boolean; } diff --git a/src/vs/base/browser/ui/scrollbar/scrollbarState.ts b/src/vs/base/browser/ui/scrollbar/scrollbarState.ts index 57bb729e852..0929cd2b297 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollbarState.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollbarState.ts @@ -188,13 +188,27 @@ export class ScrollbarState { return this._computedSliderPosition; } + /** + * Compute a desired `scrollPosition` such that `offset` ends up in the center of the slider. + * `offset` is based on the same coordinate system as the `sliderPosition`. + */ + public getDesiredScrollPositionFromOffsetAbsolute(offset: number): number { + if (!this._computedIsNeeded) { + // no need for a slider + return 0; + } + + let desiredSliderPosition = offset - this._arrowSize - this._computedSliderSize / 2; + return Math.round(desiredSliderPosition / this._computedSliderRatio); + } + /** * Compute a desired `scrollPosition` from if offset is before or after the slider position. * If offset is before slider, treat as a page up (or left). If after, page down (or right). * `offset` and `_computedSliderPosition` are based on the same coordinate system. * `_visibleSize` corresponds to a "page" of lines in the returned coordinate system. */ - public getDesiredScrollPositionFromOffset(offset: number): number { + public getDesiredScrollPositionFromOffsetPaged(offset: number): number { if (!this._computedIsNeeded) { // no need for a slider return 0; diff --git a/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts b/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts index 296913a3fd8..505dcc7f506 100644 --- a/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts +++ b/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts @@ -33,7 +33,8 @@ export class VerticalScrollbar extends AbstractScrollbar { ), visibility: options.vertical, extraScrollbarClassName: 'vertical', - scrollable: scrollable + scrollable: scrollable, + gutterClickMovesByPage: options.gutterClickMovesByPage }); if (options.verticalHasArrows) { diff --git a/src/vs/base/test/browser/ui/scrollbar/scrollbarState.test.ts b/src/vs/base/test/browser/ui/scrollbar/scrollbarState.test.ts index d191083ce49..ec2d384ede4 100644 --- a/src/vs/base/test/browser/ui/scrollbar/scrollbarState.test.ts +++ b/src/vs/base/test/browser/ui/scrollbar/scrollbarState.test.ts @@ -18,8 +18,10 @@ suite('ScrollbarState', () => { assert.equal(actual.getSliderSize(), 20); assert.equal(actual.getSliderPosition(), 249); + assert.equal(actual.getDesiredScrollPositionFromOffsetAbsolute(259), 32849); + // 259 is greater than 230 so page down, 32787 + 339 = 33126 - assert.equal(actual.getDesiredScrollPositionFromOffset(259), 33126); + assert.equal(actual.getDesiredScrollPositionFromOffsetPaged(259), 33126); actual.setScrollPosition(32849); assert.equal(actual.getArrowSize(), 0); @@ -42,8 +44,10 @@ suite('ScrollbarState', () => { assert.equal(actual.getSliderSize(), 20); assert.equal(actual.getSliderPosition(), 230); + assert.equal(actual.getDesiredScrollPositionFromOffsetAbsolute(240 + 12), 32811); + // 240 + 12 = 252; greater than 230 so page down, 32787 + 339 = 33126 - assert.equal(actual.getDesiredScrollPositionFromOffset(240 + 12), 33126); + assert.equal(actual.getDesiredScrollPositionFromOffsetPaged(240 + 12), 33126); actual.setScrollPosition(32811); assert.equal(actual.getArrowSize(), 12); diff --git a/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts b/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts index 21193bac130..595dd8670bc 100644 --- a/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts +++ b/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts @@ -56,6 +56,7 @@ export class EditorScrollbar extends ViewPart { mouseWheelScrollSensitivity: mouseWheelScrollSensitivity, fastScrollSensitivity: fastScrollSensitivity, scrollPredominantAxis: scrollPredominantAxis, + gutterClickMovesByPage: scrollbar.gutterClickMovesByPage, }; this.scrollbar = this._register(new SmoothScrollableElement(linesContent.domNode, scrollbarOptions, this._context.viewLayout.getScrollable())); diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 30004656390..73f90d11a35 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -2872,6 +2872,11 @@ export interface IEditorScrollbarOptions { * Defaults to `horizontalScrollbarSize`. */ horizontalSliderSize?: number; + /** + * Scroll gutter clicks move by page vs jump to position. + * Defaults to false. + */ + gutterClickMovesByPage?: boolean; } export interface InternalEditorScrollbarOptions { @@ -2887,6 +2892,7 @@ export interface InternalEditorScrollbarOptions { readonly horizontalSliderSize: number; readonly verticalScrollbarSize: number; readonly verticalSliderSize: number; + readonly gutterClickMovesByPage: boolean; } function _scrollbarVisibilityFromString(visibility: string | undefined, defaultValue: ScrollbarVisibility): ScrollbarVisibility { @@ -2917,7 +2923,8 @@ class EditorScrollbar extends BaseEditorOption { horizontalSliderSize: EditorOptions.scrollbar.defaultValue.horizontalSliderSize, verticalScrollbarSize: input.verticalScrollbarWidth, verticalSliderSize: EditorOptions.scrollbar.defaultValue.verticalSliderSize, + gutterClickMovesByPage: EditorOptions.scrollbar.defaultValue.gutterClickMovesByPage, }; options._write(EditorOption.scrollbar, scrollbarOptions); const lineNumbersOptions: InternalEditorRenderLineNumbersOptions = { diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index d371020b873..ed9ca3637d6 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3652,6 +3652,11 @@ declare namespace monaco.editor { * Defaults to `horizontalScrollbarSize`. */ horizontalSliderSize?: number; + /** + * Scroll gutter clicks move by page vs jump to position. + * Defaults to false. + */ + gutterClickMovesByPage?: boolean; } export interface InternalEditorScrollbarOptions { @@ -3667,6 +3672,7 @@ declare namespace monaco.editor { readonly horizontalSliderSize: number; readonly verticalScrollbarSize: number; readonly verticalSliderSize: number; + readonly gutterClickMovesByPage: boolean; } /** From 22ca0c1e8a751fe0f74495282f1610b981a519dd Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Wed, 9 Sep 2020 14:09:04 -0700 Subject: [PATCH 0029/1262] Add providerName option to git.api.getRemoteSources --- extensions/git/src/remoteSource.ts | 198 +++++++++++++++-------------- 1 file changed, 105 insertions(+), 93 deletions(-) diff --git a/extensions/git/src/remoteSource.ts b/extensions/git/src/remoteSource.ts index b736f606e67..2f3f493e75d 100644 --- a/extensions/git/src/remoteSource.ts +++ b/extensions/git/src/remoteSource.ts @@ -12,122 +12,134 @@ import { throttle, debounce } from './decorators'; const localize = nls.loadMessageBundle(); async function getQuickPickResult(quickpick: QuickPick): Promise { - const result = await new Promise(c => { - quickpick.onDidAccept(() => c(quickpick.selectedItems[0])); - quickpick.onDidHide(() => c(undefined)); - quickpick.show(); - }); + const result = await new Promise(c => { + quickpick.onDidAccept(() => c(quickpick.selectedItems[0])); + quickpick.onDidHide(() => c(undefined)); + quickpick.show(); + }); - quickpick.hide(); - return result; + quickpick.hide(); + return result; } class RemoteSourceProviderQuickPick { - private quickpick: QuickPick; + private quickpick: QuickPick; - constructor(private provider: RemoteSourceProvider) { - this.quickpick = window.createQuickPick(); - this.quickpick.ignoreFocusOut = true; + constructor(private provider: RemoteSourceProvider) { + this.quickpick = window.createQuickPick(); + this.quickpick.ignoreFocusOut = true; - if (provider.supportsQuery) { - this.quickpick.placeholder = localize('type to search', "Repository name (type to search)"); - this.quickpick.onDidChangeValue(this.onDidChangeValue, this); - } else { - this.quickpick.placeholder = localize('type to filter', "Repository name"); - } - } + if (provider.supportsQuery) { + this.quickpick.placeholder = localize('type to search', "Repository name (type to search)"); + this.quickpick.onDidChangeValue(this.onDidChangeValue, this); + } else { + this.quickpick.placeholder = localize('type to filter', "Repository name"); + } + } - @debounce(300) - private onDidChangeValue(): void { - this.query(); - } + @debounce(300) + private onDidChangeValue(): void { + this.query(); + } - @throttle - private async query(): Promise { - this.quickpick.busy = true; + @throttle + private async query(): Promise { + this.quickpick.busy = true; - try { - const remoteSources = await this.provider.getRemoteSources(this.quickpick.value) || []; + try { + const remoteSources = await this.provider.getRemoteSources(this.quickpick.value) || []; - if (remoteSources.length === 0) { - this.quickpick.items = [{ - label: localize('none found', "No remote repositories found."), - alwaysShow: true - }]; - } else { - this.quickpick.items = remoteSources.map(remoteSource => ({ - label: remoteSource.name, - description: remoteSource.description || (typeof remoteSource.url === 'string' ? remoteSource.url : remoteSource.url[0]), - remoteSource - })); - } - } catch (err) { - this.quickpick.items = [{ label: localize('error', "$(error) Error: {0}", err.message), alwaysShow: true }]; - console.error(err); - } finally { - this.quickpick.busy = false; - } - } + if (remoteSources.length === 0) { + this.quickpick.items = [{ + label: localize('none found', "No remote repositories found."), + alwaysShow: true + }]; + } else { + this.quickpick.items = remoteSources.map(remoteSource => ({ + label: remoteSource.name, + description: remoteSource.description || (typeof remoteSource.url === 'string' ? remoteSource.url : remoteSource.url[0]), + remoteSource + })); + } + } catch (err) { + this.quickpick.items = [{ label: localize('error', "$(error) Error: {0}", err.message), alwaysShow: true }]; + console.error(err); + } finally { + this.quickpick.busy = false; + } + } - async pick(): Promise { - this.query(); - const result = await getQuickPickResult(this.quickpick); - return result?.remoteSource; - } + async pick(): Promise { + this.query(); + const result = await getQuickPickResult(this.quickpick); + return result?.remoteSource; + } } export interface PickRemoteSourceOptions { - readonly providerLabel?: (provider: RemoteSourceProvider) => string; - readonly urlLabel?: string; + readonly providerLabel?: (provider: RemoteSourceProvider) => string; + readonly urlLabel?: string; + readonly providerName?: string; } export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions = {}): Promise { - const quickpick = window.createQuickPick<(QuickPickItem & { provider?: RemoteSourceProvider, url?: string })>(); - quickpick.ignoreFocusOut = true; + const quickpick = window.createQuickPick<(QuickPickItem & { provider?: RemoteSourceProvider, url?: string })>(); + quickpick.ignoreFocusOut = true; - const providers = model.getRemoteProviders() - .map(provider => ({ label: (provider.icon ? `$(${provider.icon}) ` : '') + (options.providerLabel ? options.providerLabel(provider) : provider.name), alwaysShow: true, provider })); + const targetProvider = model.getRemoteProviders().filter(provider => provider.name === options.providerName); + if (targetProvider && targetProvider.length === 1) { + await pickProviderSource(targetProvider[0]); + } else { + const providers = model.getRemoteProviders() + .map(provider => ({ label: (provider.icon ? `$(${provider.icon}) ` : '') + (options.providerLabel ? options.providerLabel(provider) : provider.name), alwaysShow: true, provider })); - quickpick.placeholder = providers.length === 0 - ? localize('provide url', "Provide repository URL") - : localize('provide url or pick', "Provide repository URL or pick a repository source."); + quickpick.placeholder = providers.length === 0 + ? localize('provide url', "Provide repository URL") + : localize('provide url or pick', "Provide repository URL or pick a repository source."); - const updatePicks = (value?: string) => { - if (value) { - quickpick.items = [{ - label: options.urlLabel ?? localize('url', "URL"), - description: value, - alwaysShow: true, - url: value - }, - ...providers]; - } else { - quickpick.items = providers; - } - }; + const updatePicks = (value?: string) => { + if (value) { + quickpick.items = [{ + label: options.urlLabel ?? localize('url', "URL"), + description: value, + alwaysShow: true, + url: value + }, + ...providers]; + } else { + quickpick.items = providers; + } + }; - quickpick.onDidChangeValue(updatePicks); - updatePicks(); + quickpick.onDidChangeValue(updatePicks); + updatePicks(); - const result = await getQuickPickResult(quickpick); + const result = await getQuickPickResult(quickpick); - if (result) { - if (result.url) { - return result.url; - } else if (result.provider) { - const quickpick = new RemoteSourceProviderQuickPick(result.provider); - const remote = await quickpick.pick(); + if (result) { + if (result.url) { + return result.url; + } else if (result.provider) { + return await pickProviderSource(result.provider); + } + } + } - if (remote) { - if (typeof remote.url === 'string') { - return remote.url; - } else if (remote.url.length > 0) { - return await window.showQuickPick(remote.url, { ignoreFocusOut: true, placeHolder: localize('pick url', "Choose a URL to clone from.") }); - } - } - } - } - - return undefined; + return undefined; +} + +async function pickProviderSource(provider: RemoteSourceProvider): Promise { + const quickpick = new RemoteSourceProviderQuickPick(provider); + const remote = await quickpick.pick(); + + if (remote) { + if (typeof remote.url === 'string') { + return remote.url; + } else if (remote.url.length > 0) { + return await window.showQuickPick(remote.url, { ignoreFocusOut: true, placeHolder: localize('pick url', "Choose a URL to clone from.") }); + } + } + + return undefined; } From 3890d7fba9f9f2d9576f77ef10bf31bc845dd60c Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Wed, 9 Sep 2020 14:31:22 -0700 Subject: [PATCH 0030/1262] Formatting --- extensions/git/src/remoteSource.ts | 200 ++++++++++++++--------------- 1 file changed, 100 insertions(+), 100 deletions(-) diff --git a/extensions/git/src/remoteSource.ts b/extensions/git/src/remoteSource.ts index 2f3f493e75d..a1acb2b9f98 100644 --- a/extensions/git/src/remoteSource.ts +++ b/extensions/git/src/remoteSource.ts @@ -12,134 +12,134 @@ import { throttle, debounce } from './decorators'; const localize = nls.loadMessageBundle(); async function getQuickPickResult(quickpick: QuickPick): Promise { - const result = await new Promise(c => { - quickpick.onDidAccept(() => c(quickpick.selectedItems[0])); - quickpick.onDidHide(() => c(undefined)); - quickpick.show(); - }); + const result = await new Promise(c => { + quickpick.onDidAccept(() => c(quickpick.selectedItems[0])); + quickpick.onDidHide(() => c(undefined)); + quickpick.show(); + }); - quickpick.hide(); - return result; + quickpick.hide(); + return result; } class RemoteSourceProviderQuickPick { - private quickpick: QuickPick; + private quickpick: QuickPick; - constructor(private provider: RemoteSourceProvider) { - this.quickpick = window.createQuickPick(); - this.quickpick.ignoreFocusOut = true; + constructor(private provider: RemoteSourceProvider) { + this.quickpick = window.createQuickPick(); + this.quickpick.ignoreFocusOut = true; - if (provider.supportsQuery) { - this.quickpick.placeholder = localize('type to search', "Repository name (type to search)"); - this.quickpick.onDidChangeValue(this.onDidChangeValue, this); - } else { - this.quickpick.placeholder = localize('type to filter', "Repository name"); - } - } + if (provider.supportsQuery) { + this.quickpick.placeholder = localize('type to search', "Repository name (type to search)"); + this.quickpick.onDidChangeValue(this.onDidChangeValue, this); + } else { + this.quickpick.placeholder = localize('type to filter', "Repository name"); + } + } - @debounce(300) - private onDidChangeValue(): void { - this.query(); - } + @debounce(300) + private onDidChangeValue(): void { + this.query(); + } - @throttle - private async query(): Promise { - this.quickpick.busy = true; + @throttle + private async query(): Promise { + this.quickpick.busy = true; - try { - const remoteSources = await this.provider.getRemoteSources(this.quickpick.value) || []; + try { + const remoteSources = await this.provider.getRemoteSources(this.quickpick.value) || []; - if (remoteSources.length === 0) { - this.quickpick.items = [{ - label: localize('none found', "No remote repositories found."), - alwaysShow: true - }]; - } else { - this.quickpick.items = remoteSources.map(remoteSource => ({ - label: remoteSource.name, - description: remoteSource.description || (typeof remoteSource.url === 'string' ? remoteSource.url : remoteSource.url[0]), - remoteSource - })); - } - } catch (err) { - this.quickpick.items = [{ label: localize('error', "$(error) Error: {0}", err.message), alwaysShow: true }]; - console.error(err); - } finally { - this.quickpick.busy = false; - } - } + if (remoteSources.length === 0) { + this.quickpick.items = [{ + label: localize('none found', "No remote repositories found."), + alwaysShow: true + }]; + } else { + this.quickpick.items = remoteSources.map(remoteSource => ({ + label: remoteSource.name, + description: remoteSource.description || (typeof remoteSource.url === 'string' ? remoteSource.url : remoteSource.url[0]), + remoteSource + })); + } + } catch (err) { + this.quickpick.items = [{ label: localize('error', "$(error) Error: {0}", err.message), alwaysShow: true }]; + console.error(err); + } finally { + this.quickpick.busy = false; + } + } - async pick(): Promise { - this.query(); - const result = await getQuickPickResult(this.quickpick); - return result?.remoteSource; - } + async pick(): Promise { + this.query(); + const result = await getQuickPickResult(this.quickpick); + return result?.remoteSource; + } } export interface PickRemoteSourceOptions { - readonly providerLabel?: (provider: RemoteSourceProvider) => string; - readonly urlLabel?: string; - readonly providerName?: string; + readonly providerLabel?: (provider: RemoteSourceProvider) => string; + readonly urlLabel?: string; + readonly providerName?: string; } export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions = {}): Promise { - const quickpick = window.createQuickPick<(QuickPickItem & { provider?: RemoteSourceProvider, url?: string })>(); - quickpick.ignoreFocusOut = true; + const quickpick = window.createQuickPick<(QuickPickItem & { provider?: RemoteSourceProvider, url?: string })>(); + quickpick.ignoreFocusOut = true; - const targetProvider = model.getRemoteProviders().filter(provider => provider.name === options.providerName); - if (targetProvider && targetProvider.length === 1) { - await pickProviderSource(targetProvider[0]); - } else { - const providers = model.getRemoteProviders() - .map(provider => ({ label: (provider.icon ? `$(${provider.icon}) ` : '') + (options.providerLabel ? options.providerLabel(provider) : provider.name), alwaysShow: true, provider })); + const targetedProvider = model.getRemoteProviders().filter(provider => provider.name === options.providerName); + if (targetedProvider && targetedProvider.length === 1) { + await pickProviderSource(targetedProvider[0]); + } else { + const providers = model.getRemoteProviders() + .map(provider => ({ label: (provider.icon ? `$(${provider.icon}) ` : '') + (options.providerLabel ? options.providerLabel(provider) : provider.name), alwaysShow: true, provider })); - quickpick.placeholder = providers.length === 0 - ? localize('provide url', "Provide repository URL") - : localize('provide url or pick', "Provide repository URL or pick a repository source."); + quickpick.placeholder = providers.length === 0 + ? localize('provide url', "Provide repository URL") + : localize('provide url or pick', "Provide repository URL or pick a repository source."); - const updatePicks = (value?: string) => { - if (value) { - quickpick.items = [{ - label: options.urlLabel ?? localize('url', "URL"), - description: value, - alwaysShow: true, - url: value - }, - ...providers]; - } else { - quickpick.items = providers; - } - }; + const updatePicks = (value?: string) => { + if (value) { + quickpick.items = [{ + label: options.urlLabel ?? localize('url', "URL"), + description: value, + alwaysShow: true, + url: value + }, + ...providers]; + } else { + quickpick.items = providers; + } + }; - quickpick.onDidChangeValue(updatePicks); - updatePicks(); + quickpick.onDidChangeValue(updatePicks); + updatePicks(); - const result = await getQuickPickResult(quickpick); + const result = await getQuickPickResult(quickpick); - if (result) { - if (result.url) { - return result.url; - } else if (result.provider) { - return await pickProviderSource(result.provider); - } - } - } + if (result) { + if (result.url) { + return result.url; + } else if (result.provider) { + return await pickProviderSource(result.provider); + } + } + } - return undefined; + return undefined; } async function pickProviderSource(provider: RemoteSourceProvider): Promise { - const quickpick = new RemoteSourceProviderQuickPick(provider); - const remote = await quickpick.pick(); + const quickpick = new RemoteSourceProviderQuickPick(provider); + const remote = await quickpick.pick(); - if (remote) { - if (typeof remote.url === 'string') { - return remote.url; - } else if (remote.url.length > 0) { - return await window.showQuickPick(remote.url, { ignoreFocusOut: true, placeHolder: localize('pick url', "Choose a URL to clone from.") }); - } - } + if (remote) { + if (typeof remote.url === 'string') { + return remote.url; + } else if (remote.url.length > 0) { + return await window.showQuickPick(remote.url, { ignoreFocusOut: true, placeHolder: localize('pick url', "Choose a URL to clone from.") }); + } + } - return undefined; + return undefined; } From 3cc907a220de728e44245ad8aee478c129b1a2a9 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Thu, 10 Sep 2020 14:02:42 -0700 Subject: [PATCH 0031/1262] Fix return --- extensions/git/src/remoteSource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/src/remoteSource.ts b/extensions/git/src/remoteSource.ts index a1acb2b9f98..1ecdc71c25e 100644 --- a/extensions/git/src/remoteSource.ts +++ b/extensions/git/src/remoteSource.ts @@ -89,7 +89,7 @@ export async function pickRemoteSource(model: Model, options: PickRemoteSourceOp const targetedProvider = model.getRemoteProviders().filter(provider => provider.name === options.providerName); if (targetedProvider && targetedProvider.length === 1) { - await pickProviderSource(targetedProvider[0]); + return await pickProviderSource(targetedProvider[0]); } else { const providers = model.getRemoteProviders() .map(provider => ({ label: (provider.icon ? `$(${provider.icon}) ` : '') + (options.providerLabel ? options.providerLabel(provider) : provider.name), alwaysShow: true, provider })); From 2f1c0213d35ee4d133b66e43ad68e158f5db5c9a Mon Sep 17 00:00:00 2001 From: Jonathan Belcher Date: Wed, 16 Sep 2020 12:36:58 -0400 Subject: [PATCH 0032/1262] Fix composition logic for Firefox MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: https://github.com/microsoft/vscode/issues/106392 In Firefox when inserting Emoji's using the macOS emoji input or using the compositor for characters with accents è, ë, î, etc. the character is inserted twice. Additionally, when adding an accented character the base character was also inserted. Examples: 🏅 inserts 🏅🏅 è inserts eèè This change does two things: - It removes `this._onType.fire(typeInput);` from the compositionupdate event listener. If you are in the middle of a composition there is no need to fire off the event. This change fixes the unaccented character from being inserted. `è` no longer inserts `eè`. - In the composition end function `browser.isFirefox` is added to the conditional that resets this._textAreaState to the value of the text area. This is needed as the insert of the composition is handled by L294. The textarea needs to be updated correctly when the compositions ends. Tested in Firefox (Windows 10, Ubuntu, macOS), Safari (macOS), Chrome (macOS), Edge Legacy (Windows 10), Edge (Windows 10) --- src/vs/editor/browser/controller/textAreaInput.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index 7b2868aa701..cc8178d4927 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -274,7 +274,6 @@ export class TextAreaInput extends Disposable { const [newState, typeInput] = deduceComposition(e.data); this._textAreaState = newState; - this._onType.fire(typeInput); this._onCompositionUpdate.fire(e); })); @@ -295,9 +294,9 @@ export class TextAreaInput extends Disposable { this._onType.fire(typeInput); } - // Due to isEdgeOrIE (where the textarea was not cleared initially) and isChrome (the textarea is not updated correctly when composition ends) + // Due to isEdgeOrIE (where the textarea was not cleared initially) and isChrome, isFirefox (the textarea is not updated correctly when composition ends) // we cannot assume the text at the end consists only of the composited text - if (browser.isEdge || browser.isChrome) { + if (browser.isEdge || browser.isChrome || browser.isFirefox) { this._textAreaState = TextAreaState.readFromTextArea(this._textArea); } From 822ca5f07ba82df53a25a07e8da9cc8f6bb68a2a Mon Sep 17 00:00:00 2001 From: Jonathan Belcher Date: Wed, 16 Sep 2020 13:00:36 -0400 Subject: [PATCH 0033/1262] don't declare a variable that isn't used textInput is no longer used. This removes the declaration as it is not used or needed. --- src/vs/editor/browser/controller/textAreaInput.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index cc8178d4927..2daf53ee2c3 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -272,7 +272,7 @@ export class TextAreaInput extends Disposable { return; } - const [newState, typeInput] = deduceComposition(e.data); + const [newState] = deduceComposition(e.data); this._textAreaState = newState; this._onCompositionUpdate.fire(e); })); From e1fa9403bd5dd07d7c8727bee135bab5aca60d39 Mon Sep 17 00:00:00 2001 From: turara Date: Mon, 28 Sep 2020 23:42:13 +0900 Subject: [PATCH 0034/1262] Add keybinding shortcut for "Preserve case" replace option Resolves #107208 --- .../base/browser/ui/findinput/replaceInput.ts | 4 +++- src/vs/editor/contrib/find/findController.ts | 20 +++++++++++++++++-- src/vs/editor/contrib/find/findModel.ts | 4 ++++ src/vs/editor/contrib/find/findWidget.ts | 4 ++++ .../browser/find/simpleFindReplaceWidget.ts | 1 + .../search/browser/search.contribution.ts | 11 ++++++++-- .../contrib/search/browser/searchActions.ts | 7 +++++++ .../contrib/search/browser/searchView.ts | 5 +++++ .../contrib/search/browser/searchWidget.ts | 1 + .../contrib/search/common/constants.ts | 1 + 10 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/vs/base/browser/ui/findinput/replaceInput.ts b/src/vs/base/browser/ui/findinput/replaceInput.ts index cdf5d65798d..901049b8134 100644 --- a/src/vs/base/browser/ui/findinput/replaceInput.ts +++ b/src/vs/base/browser/ui/findinput/replaceInput.ts @@ -28,6 +28,7 @@ export interface IReplaceInputOptions extends IReplaceInputStyles { readonly flexibleWidth?: boolean; readonly flexibleMaxHeight?: number; + readonly appendPreserveCaseLabel?: string; readonly history?: string[]; } @@ -128,6 +129,7 @@ export class ReplaceInput extends Widget { this.inputValidationErrorBackground = options.inputValidationErrorBackground; this.inputValidationErrorForeground = options.inputValidationErrorForeground; + const appendPreserveCaseLabel = options.appendPreserveCaseLabel || ''; const history = options.history || []; const flexibleHeight = !!options.flexibleHeight; const flexibleWidth = !!options.flexibleWidth; @@ -161,7 +163,7 @@ export class ReplaceInput extends Widget { })); this.preserveCase = this._register(new PreserveCaseCheckbox({ - appendTitle: '', + appendTitle: appendPreserveCaseLabel, isChecked: false, inputActiveOptionBorder: this.inputActiveOptionBorder, inputActiveOptionForeground: this.inputActiveOptionForeground, diff --git a/src/vs/editor/contrib/find/findController.ts b/src/vs/editor/contrib/find/findController.ts index 7e452f6a6cd..80ae3f7644e 100644 --- a/src/vs/editor/contrib/find/findController.ts +++ b/src/vs/editor/contrib/find/findController.ts @@ -12,7 +12,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, EditorCommand, ServicesAccessor, registerEditorAction, registerEditorCommand, registerEditorContribution, MultiEditorAction, registerMultiEditorAction } from 'vs/editor/browser/editorExtensions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { CONTEXT_FIND_INPUT_FOCUSED, CONTEXT_FIND_WIDGET_VISIBLE, FIND_IDS, FindModelBoundToEditorModel, ToggleCaseSensitiveKeybinding, ToggleRegexKeybinding, ToggleSearchScopeKeybinding, ToggleWholeWordKeybinding, CONTEXT_REPLACE_INPUT_FOCUSED } from 'vs/editor/contrib/find/findModel'; +import { CONTEXT_FIND_INPUT_FOCUSED, CONTEXT_FIND_WIDGET_VISIBLE, FIND_IDS, FindModelBoundToEditorModel, ToggleCaseSensitiveKeybinding, TogglePreserveCaseKeybinding, ToggleRegexKeybinding, ToggleSearchScopeKeybinding, ToggleWholeWordKeybinding, CONTEXT_REPLACE_INPUT_FOCUSED } from 'vs/editor/contrib/find/findModel'; import { FindOptionsWidget } from 'vs/editor/contrib/find/findOptionsWidget'; import { FindReplaceState, FindReplaceStateChangedEvent, INewFindReplaceState } from 'vs/editor/contrib/find/findState'; import { FindWidget, IFindController } from 'vs/editor/contrib/find/findWidget'; @@ -224,7 +224,9 @@ export class CommonFindController extends Disposable implements IEditorContribut public togglePreserveCase(): void { this._state.change({ preserveCase: !this._state.preserveCase }, false); - this.highlightFindOptions(); + if (!this._state.isRevealed) { + this.highlightFindOptions(); + } } public toggleSearchScope(): void { @@ -858,6 +860,20 @@ registerEditorCommand(new FindCommand({ } })); +registerEditorCommand(new FindCommand({ + id: FIND_IDS.TogglePreserveCaseCommand, + precondition: undefined, + handler: x => x.togglePreserveCase(), + kbOpts: { + weight: KeybindingWeight.EditorContrib + 5, + kbExpr: EditorContextKeys.focus, + primary: TogglePreserveCaseKeybinding.primary, + mac: TogglePreserveCaseKeybinding.mac, + win: TogglePreserveCaseKeybinding.win, + linux: TogglePreserveCaseKeybinding.linux + } +})); + registerEditorCommand(new FindCommand({ id: FIND_IDS.ReplaceOneAction, precondition: CONTEXT_FIND_WIDGET_VISIBLE, diff --git a/src/vs/editor/contrib/find/findModel.ts b/src/vs/editor/contrib/find/findModel.ts index a902f36c6d0..11ec1c63505 100644 --- a/src/vs/editor/contrib/find/findModel.ts +++ b/src/vs/editor/contrib/find/findModel.ts @@ -47,6 +47,10 @@ export const ToggleSearchScopeKeybinding: IKeybindings = { primary: KeyMod.Alt | KeyCode.KEY_L, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_L } }; +export const TogglePreserveCaseKeybinding: IKeybindings = { + primary: KeyMod.Alt | KeyCode.KEY_P, + mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_P } +}; export const FIND_IDS = { StartFindAction: 'actions.find', diff --git a/src/vs/editor/contrib/find/findWidget.ts b/src/vs/editor/contrib/find/findWidget.ts index 71864ba97f7..21fbb0060e0 100644 --- a/src/vs/editor/contrib/find/findWidget.ts +++ b/src/vs/editor/contrib/find/findWidget.ts @@ -353,6 +353,9 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL if (e.matchCase) { this._findInput.setCaseSensitive(this._state.matchCase); } + if (e.preserveCase) { + this._replaceInput.setPreserveCase(this._state.preserveCase); + } if (e.searchScope) { if (this._state.searchScope) { this._toggleSelectionFind.checked = true; @@ -1087,6 +1090,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL this._replaceInput = this._register(new ContextScopedReplaceInput(null, undefined, { label: NLS_REPLACE_INPUT_LABEL, placeholder: NLS_REPLACE_INPUT_PLACEHOLDER, + appendPreserveCaseLabel: this._keybindingLabelFor(FIND_IDS.TogglePreserveCaseCommand), history: [], flexibleHeight, flexibleWidth, diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.ts b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.ts index ad05b40ac5b..9b8cf7a37b9 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.ts @@ -140,6 +140,7 @@ export abstract class SimpleFindReplaceWidget extends Widget { this._findInput.setRegex(this._state.isRegex); this._findInput.setWholeWords(this._state.wholeWord); this._findInput.setCaseSensitive(this._state.matchCase); + this._replaceInput.setPreserveCase(this._state.preserveCase); this.findFirst(); })); diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 70b5e881f3e..c0b8a580b54 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -10,7 +10,7 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import * as platform from 'vs/base/common/platform'; import { dirname } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { ToggleCaseSensitiveKeybinding, ToggleRegexKeybinding, ToggleWholeWordKeybinding } from 'vs/editor/contrib/find/findModel'; +import { ToggleCaseSensitiveKeybinding, TogglePreserveCaseKeybinding, ToggleRegexKeybinding, ToggleWholeWordKeybinding } from 'vs/editor/contrib/find/findModel'; import * as nls from 'vs/nls'; import { ICommandAction, MenuId, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; @@ -31,7 +31,7 @@ import { Extensions as ViewExtensions, IViewsRegistry, IViewContainersRegistry, import { getMultiSelectedResources } from 'vs/workbench/contrib/files/browser/files'; import { ExplorerFolderContext, ExplorerRootContext, FilesExplorerFocusCondition, IExplorerService, VIEWLET_ID as VIEWLET_ID_FILES } from 'vs/workbench/contrib/files/common/files'; import { registerContributions as replaceContributions } from 'vs/workbench/contrib/search/browser/replaceContributions'; -import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, toggleRegexCommand, toggleWholeWordCommand, FindInFilesCommand, ToggleSearchOnTypeAction, ExpandAllAction } from 'vs/workbench/contrib/search/browser/searchActions'; +import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, togglePreserveCaseCommand, toggleRegexCommand, toggleWholeWordCommand, FindInFilesCommand, ToggleSearchOnTypeAction, ExpandAllAction } from 'vs/workbench/contrib/search/browser/searchActions'; import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; import { registerContributions as searchWidgetContributions } from 'vs/workbench/contrib/search/browser/searchWidget'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; @@ -642,6 +642,13 @@ KeybindingsRegistry.registerCommandAndKeybindingRule(Object.assign({ handler: toggleRegexCommand }, ToggleRegexKeybinding)); +KeybindingsRegistry.registerCommandAndKeybindingRule(Object.assign({ + id: Constants.TogglePreserveCaseId, + weight: KeybindingWeight.WorkbenchContrib, + when: Constants.SearchViewFocusedKey, + handler: togglePreserveCaseCommand +}, TogglePreserveCaseKeybinding)); + KeybindingsRegistry.registerCommandAndKeybindingRule({ id: Constants.AddCursorsAtSearchResults, weight: KeybindingWeight.WorkbenchContrib, diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index 040baac60b9..13534ce3655 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -83,6 +83,13 @@ export const toggleRegexCommand = (accessor: ServicesAccessor) => { } }; +export const togglePreserveCaseCommand = (accessor: ServicesAccessor) => { + const searchView = getSearchView(accessor.get(IViewsService)); + if (searchView) { + searchView.togglePreserveCase(); + } +}; + export class FocusNextInputAction extends Action { static readonly ID = 'search.focus.nextInputBox'; diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 2a53fe46356..bbfa8bcbd63 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -1160,6 +1160,11 @@ export class SearchView extends ViewPane { this.triggerQueryChange(); } + togglePreserveCase(): void { + this.searchWidget.replaceInput.setPreserveCase(!this.searchWidget.replaceInput.getPreserveCase()); + this.triggerQueryChange(); + } + setSearchParameters(args: IFindInFilesArgs = {}): void { if (typeof args.isCaseSensitive === 'boolean') { this.searchWidget.searchInput.setCaseSensitive(args.isCaseSensitive); diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 78d6587b423..d633afe199f 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -393,6 +393,7 @@ export class SearchWidget extends Widget { this.replaceInput = this._register(new ContextScopedReplaceInput(replaceBox, this.contextViewService, { label: nls.localize('label.Replace', 'Replace: Type replace term and press Enter to preview or Escape to cancel'), placeholder: nls.localize('search.replace.placeHolder', "Replace"), + appendPreserveCaseLabel: appendKeyBindingLabel('', this.keyBindingService.lookupKeybinding(Constants.TogglePreserveCaseId), this.keyBindingService), history: options.replaceHistory, flexibleHeight: true, flexibleMaxHeight: SearchWidget.INPUT_MAX_HEIGHT diff --git a/src/vs/workbench/contrib/search/common/constants.ts b/src/vs/workbench/contrib/search/common/constants.ts index f1e6975f788..38e04a3de24 100644 --- a/src/vs/workbench/contrib/search/common/constants.ts +++ b/src/vs/workbench/contrib/search/common/constants.ts @@ -26,6 +26,7 @@ export const CloseReplaceWidgetActionId = 'closeReplaceInFilesWidget'; export const ToggleCaseSensitiveCommandId = 'toggleSearchCaseSensitive'; export const ToggleWholeWordCommandId = 'toggleSearchWholeWord'; export const ToggleRegexCommandId = 'toggleSearchRegex'; +export const TogglePreserveCaseId = 'toggleSearchPreserveCase'; export const AddCursorsAtSearchResults = 'addCursorsAtSearchResults'; export const RevealInSideBarForSearchResults = 'search.action.revealInSideBar'; From 604d445cc3e9c539090a4a7edf72932da3b4a0df Mon Sep 17 00:00:00 2001 From: Dhaiyra Date: Thu, 1 Oct 2020 23:03:04 +0530 Subject: [PATCH 0035/1262] added preserve case and excluse setting in FindInFile interface --- src/vs/workbench/contrib/search/browser/searchActions.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index 574189c1a09..de9d9a0bdef 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -154,12 +154,14 @@ export abstract class FindOrReplaceInFilesAction extends Action { export interface IFindInFilesArgs { query?: string; replace?: string; + preserveCase?: boolean; triggerSearch?: boolean; filesToInclude?: string; filesToExclude?: string; isRegex?: boolean; isCaseSensitive?: boolean; matchWholeWord?: boolean; + excludeSettingAndIgnoreFiles?: boolean; } export const FindInFilesCommand: ICommandHandler = (accessor, args: IFindInFilesArgs = {}) => { From e8ceafb07a6a29eeae4d4cb4a56c010c40625cc2 Mon Sep 17 00:00:00 2001 From: Justin Steven Date: Fri, 2 Oct 2020 17:21:51 +1000 Subject: [PATCH 0036/1262] Fix a bypass for CVE-2020-16881 Fixes #107951 Uses child_process.execFile() rather than child_process.exec() to more effectively resolve the command injection vulnerability. --- extensions/npm/src/features/packageJSONContribution.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/npm/src/features/packageJSONContribution.ts b/extensions/npm/src/features/packageJSONContribution.ts index f154a875239..7103ce1fa1a 100644 --- a/extensions/npm/src/features/packageJSONContribution.ts +++ b/extensions/npm/src/features/packageJSONContribution.ts @@ -282,8 +282,8 @@ export class PackageJSONContribution implements IJSONContribution { private npmView(pack: string): Promise { return new Promise((resolve, _reject) => { - const command = 'npm view --json ' + pack + ' description dist-tags.latest homepage version'; - cp.exec(command, (error, stdout) => { + const args = ['view', '--json', pack, 'description', 'dist-tags.latest', 'homepage', 'version']; + cp.execFile('npm', args, (error, stdout) => { if (!error) { try { const content = JSON.parse(stdout); From 0ecb64a2c8945dd1193967019f0734af539ca9c3 Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 2 Oct 2020 11:18:44 +0200 Subject: [PATCH 0037/1262] bot: debug issues to connor --- .github/classifier.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/classifier.json b/.github/classifier.json index 4e9b695a0c1..96701987a9c 100644 --- a/.github/classifier.json +++ b/.github/classifier.json @@ -20,8 +20,8 @@ "context-keys": {"assign": []}, "css-less-scss": {"assign": ["aeschli"]}, "custom-editors": {"assign": ["mjbvz"]}, - "debug": {"assign": ["isidorn"]}, - "debug-console": {"assign": ["isidorn"]}, + "debug": {"assign": ["connor4312 "]}, + "debug-console": {"assign": ["connor4312 "]}, "dialogs": {"assign": ["sbatten"]}, "diff-editor": {"assign": []}, "dropdown": {"assign": []}, From 247e5dc148c0447937c232cf39558bd01f53f33b Mon Sep 17 00:00:00 2001 From: Rares Folea <30867783+raresraf@users.noreply.github.com> Date: Fri, 2 Oct 2020 17:42:47 +0300 Subject: [PATCH 0038/1262] Fix typo extensions/search-result/README.md (#107961) --- extensions/search-result/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/search-result/README.md b/extensions/search-result/README.md index c3e1b53525c..fe886e4bde1 100644 --- a/extensions/search-result/README.md +++ b/extensions/search-result/README.md @@ -2,4 +2,4 @@ **Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled. -This extension provides Syntax Highlighting, Symbol Infomation, Result Highlighting, and Go to Definition capabilities for the Search Results Editor. +This extension provides Syntax Highlighting, Symbol Information, Result Highlighting, and Go to Definition capabilities for the Search Results Editor. From 7701642b2618cdd284721de80d9c40eefa56fc3c Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 2 Oct 2020 09:42:54 -0700 Subject: [PATCH 0039/1262] update distro. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 06831b25932..770b44b0c1f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.50.0", - "distro": "66a02247b2916cb7cf8a3b70677e1d798c6527f2", + "distro": "9c21f6fe853c7843005a92bd4f6dea9b8eba03a2", "author": { "name": "Microsoft Corporation" }, From 916de64d89b265b84d3dff6baf3d6a6c04884b8c Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 2 Oct 2020 14:16:53 -0700 Subject: [PATCH 0040/1262] fix: debug buttons in a nested package.json Fixes #108000 --- extensions/npm/src/npmView.ts | 2 +- extensions/npm/src/scriptHover.ts | 5 +++-- extensions/npm/src/tasks.ts | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/extensions/npm/src/npmView.ts b/extensions/npm/src/npmView.ts index c1145d0806b..c1b5f6fdddd 100644 --- a/extensions/npm/src/npmView.ts +++ b/extensions/npm/src/npmView.ts @@ -137,7 +137,7 @@ export class NpmScriptsTreeDataProvider implements TreeDataProvider { } private async debugScript(script: NpmScript) { - startDebugging(script.task.name, script.getFolder()); + startDebugging(script.task.definition.script, path.dirname(script.package.resourceUri!.fsPath), script.getFolder()); } private findScript(document: TextDocument, script?: NpmScript): number { diff --git a/extensions/npm/src/scriptHover.ts b/extensions/npm/src/scriptHover.ts index f8a5482bef8..b1edc275267 100644 --- a/extensions/npm/src/scriptHover.ts +++ b/extensions/npm/src/scriptHover.ts @@ -11,6 +11,7 @@ import { createTask, startDebugging, findAllScriptRanges } from './tasks'; import * as nls from 'vscode-nls'; +import { dirname } from 'path'; const localize = nls.loadMessageBundle(); @@ -107,12 +108,12 @@ export class NpmScriptHoverProvider implements HoverProvider { } } - public debugScriptFromHover(args: any) { + public debugScriptFromHover(args: { script: string; documentUri: Uri }) { let script = args.script; let documentUri = args.documentUri; let folder = workspace.getWorkspaceFolder(documentUri); if (folder) { - startDebugging(script, folder); + startDebugging(script, dirname(documentUri.fsPath), folder); } } } diff --git a/extensions/npm/src/tasks.ts b/extensions/npm/src/tasks.ts index f7def2f8876..7c97a0d1015 100644 --- a/extensions/npm/src/tasks.ts +++ b/extensions/npm/src/tasks.ts @@ -357,11 +357,12 @@ export function runScript(script: string, document: TextDocument) { } } -export function startDebugging(scriptName: string, folder: WorkspaceFolder) { +export function startDebugging(scriptName: string, cwd: string, folder: WorkspaceFolder) { const config: DebugConfiguration = { type: 'pwa-node', request: 'launch', name: `Debug ${scriptName}`, + cwd, runtimeExecutable: getPackageManager(folder), runtimeArgs: [ 'run', From dfa980ebdad245a60b217de7468bc8ced953c906 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 2 Oct 2020 14:59:01 -0700 Subject: [PATCH 0041/1262] Fix NotebookDocumentBackup comment --- src/vs/vscode.proposed.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 43b7b86306a..a1e531adc0c 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1673,7 +1673,7 @@ declare module 'vscode' { /** * Unique identifier for the backup. * - * This id is passed back to your extension in `openCustomDocument` when opening a notebook editor from a backup. + * This id is passed back to your extension in `openNotebook` when opening a notebook editor from a backup. */ readonly id: string; From 8284242ed12dc43ee490a29b192a388c8193e3b7 Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 2 Oct 2020 15:22:24 -0700 Subject: [PATCH 0042/1262] fix #107438. --- .../browser/view/renderers/backLayerWebView.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 8cc06b08600..2a8b91faadf 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -23,7 +23,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { dirname, joinPath } from 'vs/base/common/resources'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { preloadsScriptStr } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads'; -import { Schemas } from 'vs/base/common/network'; +import { FileAccess, Schemas } from 'vs/base/common/network'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IFileService } from 'vs/platform/files/common/files'; import { VSBuffer } from 'vs/base/common/buffer'; @@ -356,9 +356,6 @@ export class BackLayerWebView extends Disposable { } async createWebview(): Promise { - const pathsPath = getPathFromAmdModule(require, 'vs/loader.js'); - const loader = asWebviewUri(this.environmentService, this.id, URI.file(pathsPath)); - let coreDependencies = ''; let resolveFunc: () => void; @@ -369,6 +366,9 @@ export class BackLayerWebView extends Disposable { const baseUrl = asWebviewUri(this.environmentService, this.id, dirname(this.documentUri)); if (!isWeb) { + const loaderUri = FileAccess.asFileUri('vs/loader.js', require); + const loader = asWebviewUri(this.environmentService, this.id, loaderUri); + coreDependencies = ` - -`; - const iframeContent = `data:text/html;charset=utf-8,${encodeURIComponent(html)}`; - iframe.setAttribute('src', iframeContent); - const timeout = setTimeout(() => { - this._onDidExit.fire([ExtensionHostExitCode.StartTimeout10s, 'The Web Worker Extension Host did not start in 10s']); - }, 10000); + iframe.setAttribute('src', `${webWorkerExtensionHostIframeSrc}?vscodeWebWorkerExtHostId=${vscodeWebWorkerExtHostId}`); const barrier = new Barrier(); let port!: MessagePort; + let barrierError: Error | null = null; + let barrierHasError = false; + let startTimeout: any = null; + + const rejectBarrier = (exitCode: number, error: Error) => { + barrierError = error; + barrierHasError = true; + onUnexpectedError(barrierError); + clearTimeout(startTimeout); + this._onDidExit.fire([ExtensionHostExitCode.UnexpectedError, barrierError.message]); + barrier.open(); + }; + + const resolveBarrier = (messagePort: MessagePort) => { + port = messagePort; + clearTimeout(startTimeout); + barrier.open(); + }; + + startTimeout = setTimeout(() => { + rejectBarrier(ExtensionHostExitCode.StartTimeout10s, new Error('The Web Worker Extension Host did not start in 10s')); + }, 10000); this._register(dom.addDisposableListener(window, 'message', (event) => { if (event.source !== iframe.contentWindow) { @@ -141,21 +168,15 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost err.message = message; err.name = name; err.stack = stack; - onUnexpectedError(err); - clearTimeout(timeout); - this._onDidExit.fire([ExtensionHostExitCode.UnexpectedError, err.message]); - return; + return rejectBarrier(ExtensionHostExitCode.UnexpectedError, err); } const { data } = event.data; if (barrier.isOpen() || !(data instanceof MessagePort)) { console.warn('UNEXPECTED message', event); - clearTimeout(timeout); - this._onDidExit.fire([ExtensionHostExitCode.UnexpectedError, 'UNEXPECTED message']); - return; + const err = new Error('UNEXPECTED message'); + return rejectBarrier(ExtensionHostExitCode.UnexpectedError, err); } - port = data; - clearTimeout(timeout); - barrier.open(); + resolveBarrier(data); })); document.body.appendChild(iframe); @@ -165,6 +186,10 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost // with the worker extension host await barrier.wait(); + if (barrierHasError) { + throw barrierError; + } + port.onmessage = (event) => { const { data } = event; if (!(data instanceof ArrayBuffer)) { diff --git a/src/vs/workbench/services/extensions/common/webWorkerIframe.ts b/src/vs/workbench/services/extensions/common/webWorkerIframe.ts deleted file mode 100644 index 7b354788d46..00000000000 --- a/src/vs/workbench/services/extensions/common/webWorkerIframe.ts +++ /dev/null @@ -1,47 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export const WEB_WORKER_IFRAME = { - sha: 'sha256-r24mDVsMuFEo8ChaY9ppVJKbY3CUM4I12Aw/yscWZbg=', - js: ` -(function() { - const workerSrc = document.getElementById('vscode-worker-src').getAttribute('data-value'); - const worker = new Worker(workerSrc, { name: 'WorkerExtensionHost' }); - const vscodeWebWorkerExtHostId = document.getElementById('vscode-web-worker-ext-host-id').getAttribute('data-value'); - - worker.onmessage = (event) => { - const { data } = event; - if (!(data instanceof MessagePort)) { - console.warn('Unknown data received', event); - window.parent.postMessage({ - vscodeWebWorkerExtHostId, - error: { - name: 'Error', - message: 'Unknown data received', - stack: [] - } - }, '*'); - return; - } - window.parent.postMessage({ - vscodeWebWorkerExtHostId, - data: data - }, '*', [data]); - }; - - worker.onerror = (event) => { - console.error(event.message, event.error); - window.parent.postMessage({ - vscodeWebWorkerExtHostId, - error: { - name: event.error ? event.error.name : '', - message: event.error ? event.error.message : '', - stack: event.error ? event.error.stack : [] - } - }, '*'); - }; -})(); -` -}; diff --git a/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts b/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts index 6a54c82b1af..dd554377b19 100644 --- a/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts +++ b/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts @@ -45,9 +45,9 @@ self.addEventListener = () => console.trace(`'addEventListener' has been blocked (self)['webkitResolveLocalFileSystemSyncURL'] = undefined; (self)['webkitResolveLocalFileSystemURL'] = undefined; -if (location.protocol === 'data:') { +if ((self).Worker) { // make sure new Worker(...) always uses data: - const _Worker = Worker; + const _Worker = (self).Worker; Worker = function (stringUrl: string | URL, options?: WorkerOptions) { const js = `importScripts('${stringUrl}');`; options = options || {}; diff --git a/src/vs/workbench/services/extensions/worker/httpWebWorkerExtensionHostIframe.html b/src/vs/workbench/services/extensions/worker/httpWebWorkerExtensionHostIframe.html new file mode 100644 index 00000000000..a99d3df29df --- /dev/null +++ b/src/vs/workbench/services/extensions/worker/httpWebWorkerExtensionHostIframe.html @@ -0,0 +1,50 @@ + + + + + + + + + diff --git a/src/vs/workbench/services/extensions/worker/httpsWebWorkerExtensionHostIframe.html b/src/vs/workbench/services/extensions/worker/httpsWebWorkerExtensionHostIframe.html new file mode 100644 index 00000000000..27b6114a13b --- /dev/null +++ b/src/vs/workbench/services/extensions/worker/httpsWebWorkerExtensionHostIframe.html @@ -0,0 +1,50 @@ + + + + + + + + + diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index 16accfae70b..68906ff6fa7 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -277,6 +277,11 @@ interface IWorkbenchConstructionOptions { */ readonly webviewEndpoint?: string; + /** + * An URL pointing to the web worker extension host