diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 5c96a4a4b2..dd86368c20 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -186,6 +186,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op const checksums = computeChecksums(out, [ 'vs/workbench/workbench.desktop.main.js', 'vs/workbench/workbench.desktop.main.css', + 'vs/workbench/services/extensions/node/extensionHostProcess.js', 'vs/code/electron-browser/workbench/workbench.html', 'vs/code/electron-browser/workbench/workbench.js' ]); diff --git a/extensions/git/package.json b/extensions/git/package.json index 9ff99819cd..054ea18afc 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1262,7 +1262,7 @@ "timeline/item/context": [ { "command": "git.timeline.openDiff", - "group": "1_actions", + "group": "1_timeline", "when": "config.git.enabled && !git.missing && timelineItem =~ /git:file\\b/" }, { diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 53266f95df..1222ed1467 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -443,7 +443,10 @@ export class Git { ); if (networkPath !== undefined) { return path.normalize( - repoUri.fsPath.replace(networkPath, `${letter.toLowerCase()}:`), + repoUri.fsPath.replace( + networkPath, + `${letter.toLowerCase()}:${networkPath.endsWith('\\') ? '\\' : ''}` + ), ); } } catch { } diff --git a/src/vs/base/common/fuzzyScorer.ts b/src/vs/base/common/fuzzyScorer.ts index 581ffe99f4..44562b8f23 100644 --- a/src/vs/base/common/fuzzyScorer.ts +++ b/src/vs/base/common/fuzzyScorer.ts @@ -268,10 +268,10 @@ export type FuzzyScore2 = [number /* score*/, IMatch[]]; const NO_SCORE2: FuzzyScore2 = [NO_MATCH, []]; -export function scoreFuzzy2(target: string, query: IPreparedQuery, patternStart = 0, matchOffset = 0): FuzzyScore2 { +export function scoreFuzzy2(target: string, query: IPreparedQuery, patternStart = 0, matchOffset = 0, skipMultiMatching = false): FuzzyScore2 { - // Score: multiple inputs - if (query.values && query.values.length > 1) { + // Score: multiple inputs (unless disabled) + if (!skipMultiMatching && query.values && query.values.length > 1) { return doScoreFuzzy2Multiple(target, query.values, patternStart, matchOffset); } diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts index 8da38a7c6d..018ad4e4ff 100644 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -662,7 +662,7 @@ class QuickPick extends QuickInput implements IQuickPi this.ui.list.clearFocus(); } })); - this.visibleDisposables.add(this.ui.inputBox.onKeyDown(event => { + this.visibleDisposables.add((this._hideInput ? this.ui.list : this.ui.inputBox).onKeyDown((event: KeyboardEvent | StandardKeyboardEvent) => { switch (event.keyCode) { case KeyCode.DownArrow: this.ui.list.focus(QuickInputListFocus.Next); diff --git a/src/vs/base/parts/quickinput/browser/quickInputList.ts b/src/vs/base/parts/quickinput/browser/quickInputList.ts index ed179d6690..f93f223c16 100644 --- a/src/vs/base/parts/quickinput/browser/quickInputList.ts +++ b/src/vs/base/parts/quickinput/browser/quickInputList.ts @@ -259,6 +259,8 @@ export class QuickInputList { onChangedCheckedElements: Event = this._onChangedCheckedElements.event; private readonly _onButtonTriggered = new Emitter>(); onButtonTriggered = this._onButtonTriggered.event; + private readonly _onKeyDown = new Emitter(); + onKeyDown: Event = this._onKeyDown.event; private readonly _onLeave = new Emitter(); onLeave: Event = this._onLeave.event; private _fireCheckedEvents = true; @@ -314,6 +316,8 @@ export class QuickInputList { } break; } + + this._onKeyDown.fire(event); })); this.disposables.push(this.list.onMouseDown(e => { if (e.browserEvent.button !== 2) { diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 415905965a..d27996084e 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { app, ipcMain as ipc, systemPreferences, shell, Event, contentTracing, protocol, powerMonitor, IpcMainEvent, BrowserWindow } from 'electron'; +import { app, ipcMain as ipc, systemPreferences, shell, Event, contentTracing, protocol, powerMonitor, IpcMainEvent, BrowserWindow, dialog } from 'electron'; import { IProcessEnvironment, isWindows, isMacintosh } from 'vs/base/common/platform'; import { WindowsMainService } from 'vs/platform/windows/electron-main/windowsMainService'; import { IWindowOpenable } from 'vs/platform/windows/common/windows'; @@ -80,6 +80,7 @@ import { coalesce } from 'vs/base/common/arrays'; import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; import { StorageKeysSyncRegistryChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { mnemonicButtonLabel, getPathLabel } from 'vs/base/common/labels'; export class CodeApplication extends Disposable { private windowsMainService: IWindowsMainService | undefined; @@ -605,6 +606,11 @@ export class CodeApplication extends Disposable { return undefined; } })).filter(pendingUriToHandle => { + // if URI should be blocked, filter it out + if (this.shouldBlockURI(pendingUriToHandle)) { + return false; + } + // filter out any protocol link that wants to open as window so that // we open the right set of windows on startup and not restore the // previous workspace too. @@ -623,6 +629,10 @@ export class CodeApplication extends Disposable { const environmentService = this.environmentService; urlService.registerHandler({ async handleURL(uri: URI): Promise { + // if URI should be blocked, behave as if it's handled + if (app.shouldBlockURI(uri)) { + return true; + } // Check for URIs to open in window const windowOpenableFromProtocolLink = app.getWindowOpenableFromProtocolLink(uri); @@ -727,6 +737,29 @@ export class CodeApplication extends Disposable { }); } + private shouldBlockURI(uri: URI): boolean { + if (uri.authority === Schemas.file && isWindows) { + const res = dialog.showMessageBoxSync({ + title: product.nameLong, + type: 'question', + buttons: [ + mnemonicButtonLabel(localize({ key: 'open', comment: ['&& denotes a mnemonic'] }, "&&Yes")), + mnemonicButtonLabel(localize({ key: 'cancel', comment: ['&& denotes a mnemonic'] }, "&&No")), + ], + cancelId: 1, + message: localize('confirmOpenMessage', "An external application wants to open '{0}' in {1}. Do you want to open this file or folder?", getPathLabel(uri.fsPath), product.nameShort), + detail: localize('confirmOpenDetail', "If you did not initiate this request, it may represent an attempted attack on your system. Unless you took an explicit action to initiate this request, you should press 'No'"), + noLink: true + }); + + if (res === 1) { + return true; + } + } + + return false; + } + private getWindowOpenableFromProtocolLink(uri: URI): IWindowOpenable | undefined { if (!uri.path) { return undefined; diff --git a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts index 46ac5a3140..5f3d321d0e 100644 --- a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts @@ -220,6 +220,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit const symbolLabel = trim(symbol.name); const symbolLabelWithIcon = `$(symbol-${SymbolKinds.toString(symbol.kind) || 'property'}) ${symbolLabel}`; + const symbolLabelIconOffset = symbolLabelWithIcon.length - symbolLabel.length; let containerLabel = symbol.containerName; if (options?.extraContainerLabel) { @@ -238,14 +239,28 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit if (query.original.length > filterPos) { - // Score by symbol - [symbolScore, symbolMatches] = scoreFuzzy2(symbolLabel, symbolQuery, filterPos, symbolLabelWithIcon.length - symbolLabel.length /* Readjust matches to account for codicons in label */); + // First: try to score on the entire query, it is possible that + // the symbol matches perfectly (e.g. searching for "change log" + // can be a match on a markdown symbol "change log"). In that + // case we want to skip the container query altogether. + let skipContainerQuery = false; + if (symbolQuery !== query) { + [symbolScore, symbolMatches] = scoreFuzzy2(symbolLabel, query, filterPos, symbolLabelIconOffset, true /* skip multi matching */); + if (symbolScore) { + skipContainerQuery = true; // since we consumed the query, skip any container matching + } + } + + // Otherwise: score on the symbol query and match on the container later if (!symbolScore) { - continue; + [symbolScore, symbolMatches] = scoreFuzzy2(symbolLabel, symbolQuery, filterPos, symbolLabelIconOffset); + if (!symbolScore) { + continue; + } } // Score by container if specified - if (containerQuery) { + if (!skipContainerQuery && containerQuery) { if (containerLabel && containerQuery.original.length > 0) { [containerScore, containerMatches] = scoreFuzzy2(containerLabel, containerQuery); } diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index b368ca7218..fc3e08618c 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -18,7 +18,7 @@ import { DelayedDragHandler } from 'vs/base/browser/dnd'; import { IActivity } from 'vs/workbench/common/activity'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Emitter } from 'vs/base/common/event'; -import { LocalSelectionTransfer, DraggedCompositeIdentifier, DraggedViewIdentifier, CompositeDragAndDropObserver, ICompositeDragAndDrop, Before2D } from 'vs/workbench/browser/dnd'; +import { CompositeDragAndDropObserver, ICompositeDragAndDrop, Before2D } from 'vs/workbench/browser/dnd'; import { Color } from 'vs/base/common/color'; export interface ICompositeActivity { @@ -452,7 +452,6 @@ export class CompositeActionViewItem extends ActivityActionViewItem { private static manageExtensionAction: ManageExtensionAction; private compositeActivity: IActivity | undefined; - private compositeTransfer: LocalSelectionTransfer; constructor( private compositeActivityAction: ActivityAction, @@ -470,8 +469,6 @@ export class CompositeActionViewItem extends ActivityActionViewItem { ) { super(compositeActivityAction, { draggable: true, colors, icon }, themeService); - this.compositeTransfer = LocalSelectionTransfer.getInstance(); - if (!CompositeActionViewItem.manageExtensionAction) { CompositeActionViewItem.manageExtensionAction = instantiationService.createInstance(ManageExtensionAction); } @@ -670,9 +667,6 @@ export class CompositeActionViewItem extends ActivityActionViewItem { dispose(): void { super.dispose(); - - this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype); - this.label.remove(); } } diff --git a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts index 3ab20e96d1..e90067df91 100644 --- a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts @@ -816,7 +816,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider this.openAnything(activeGlobalResource, { keyMods, range: editorSymbolPick.range?.selection, preserveFocus: event.inBackground }) + accept: (keyMods, event) => this.openAnything(activeGlobalResource, { keyMods, range: editorSymbolPick.range?.selection, preserveFocus: event.inBackground, forcePinned: event.inBackground }) }; }); } @@ -901,14 +901,14 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider this.openAnything(resourceOrEditor, { keyMods, range: this.pickState.lastRange, preserveFocus: event.inBackground }) + accept: (keyMods, event) => this.openAnything(resourceOrEditor, { keyMods, range: this.pickState.lastRange, preserveFocus: event.inBackground, forcePinned: event.inBackground }) }; } - private async openAnything(resourceOrEditor: URI | IEditorInput | IResourceEditorInput, options: { keyMods?: IKeyMods, preserveFocus?: boolean, range?: IRange, forceOpenSideBySide?: boolean }): Promise { + private async openAnything(resourceOrEditor: URI | IEditorInput | IResourceEditorInput, options: { keyMods?: IKeyMods, preserveFocus?: boolean, range?: IRange, forceOpenSideBySide?: boolean, forcePinned?: boolean }): Promise { const editorOptions: ITextEditorOptions = { preserveFocus: options.preserveFocus, - pinned: options.keyMods?.alt || this.configuration.openEditorPinned, + pinned: options.keyMods?.alt || options.forcePinned || this.configuration.openEditorPinned, selection: options.range ? Range.collapseToStart(options.range) : undefined }; diff --git a/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts b/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts index 80ba392309..73e9c3f143 100644 --- a/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts @@ -130,15 +130,31 @@ export class SymbolsQuickAccessProvider extends PickerQuickAccessProvider 0) { - [symbolScore, symbolMatches] = scoreFuzzy2(symbolLabel, symbolQuery, 0, symbolLabelWithIcon.length - symbolLabel.length /* Readjust matches to account for codicons in label */); + + // First: try to score on the entire query, it is possible that + // the symbol matches perfectly (e.g. searching for "change log" + // can be a match on a markdown symbol "change log"). In that + // case we want to skip the container query altogether. + if (symbolQuery !== query) { + [symbolScore, symbolMatches] = scoreFuzzy2(symbolLabel, query, 0, symbolLabelIconOffset, true /* skip multi matching */); + if (symbolScore) { + skipContainerQuery = true; // since we consumed the query, skip any container matching + } + } + + // Otherwise: score on the symbol query and match on the container later if (!symbolScore) { - continue; + [symbolScore, symbolMatches] = scoreFuzzy2(symbolLabel, symbolQuery, 0, symbolLabelIconOffset); + if (!symbolScore) { + continue; + } } } @@ -156,7 +172,7 @@ export class SymbolsQuickAccessProvider extends PickerQuickAccessProvider 0) { + if (!skipContainerQuery && containerQuery && containerQuery.original.length > 0) { if (containerLabel) { [containerScore, containerMatches] = scoreFuzzy2(containerLabel, containerQuery); } @@ -195,7 +211,7 @@ export class SymbolsQuickAccessProvider extends PickerQuickAccessProvider this.openSymbol(provider, symbol, token, { keyMods, preserveFocus: event.inBackground }), + accept: async (keyMods, event) => this.openSymbol(provider, symbol, token, { keyMods, preserveFocus: event.inBackground, forcePinned: event.inBackground }), }); } } @@ -208,7 +224,7 @@ export class SymbolsQuickAccessProvider extends PickerQuickAccessProvider { + private async openSymbol(provider: IWorkspaceSymbolProvider, symbol: IWorkspaceSymbol, token: CancellationToken, options: { keyMods: IKeyMods, forceOpenSideBySide?: boolean, preserveFocus?: boolean, forcePinned?: boolean }): Promise { // Resolve actual symbol to open for providers that can resolve let symbolToOpen = symbol; @@ -231,7 +247,7 @@ export class SymbolsQuickAccessProvider extends PickerQuickAccessProvider((resolve, reject) => { fs.readFile(fileUri.fsPath, (err, buff) => { if (err) { - return reject(err); + return resolve(IntegrityServiceImpl._createChecksumPair(fileUri, '', expected)); } resolve(IntegrityServiceImpl._createChecksumPair(fileUri, this._computeChecksum(buff), expected)); }); diff --git a/src/vs/workbench/services/search/common/searchService.ts b/src/vs/workbench/services/search/common/searchService.ts index 502ad3132e..858365df6f 100644 --- a/src/vs/workbench/services/search/common/searchService.ts +++ b/src/vs/workbench/services/search/common/searchService.ts @@ -203,10 +203,15 @@ export class SearchService extends Disposable implements ISearchService { this.fileSearchProviders.get(scheme) : this.textSearchProviders.get(scheme); - if (!provider && scheme === 'file') { + if (!provider && scheme === Schemas.file) { diskSearchQueries.push(...schemeFQs); } else { if (!provider) { + if (scheme !== Schemas.vscodeRemote) { + console.warn(`No search provider registered for scheme: ${scheme}`); + return; + } + console.warn(`No search provider registered for scheme: ${scheme}, waiting`); provider = await this.waitForProvider(query.type, scheme); }