From 2c13a768d66b0fbeccb65728d7787261dba345e6 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 8 Feb 2016 16:15:19 +0100 Subject: [PATCH] Allow to pick working files via quick open (fixes #441) --- .../parts/files/browser/files.contribution.ts | 41 +++- .../files/browser/views/workingFilesViewer.ts | 2 +- .../parts/files/browser/workingFilesPicker.ts | 176 ++++++++++++++++++ .../search/browser/search.contribution.ts | 2 - src/vs/workbench/workbench.main.js | 1 + 5 files changed, 217 insertions(+), 5 deletions(-) create mode 100644 src/vs/workbench/parts/files/browser/workingFilesPicker.ts diff --git a/src/vs/workbench/parts/files/browser/files.contribution.ts b/src/vs/workbench/parts/files/browser/files.contribution.ts index 5fd5aa68feb..01d82978c50 100644 --- a/src/vs/workbench/parts/files/browser/files.contribution.ts +++ b/src/vs/workbench/parts/files/browser/files.contribution.ts @@ -13,11 +13,14 @@ import {ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, Tog import nls = require('vs/nls'); import {SyncActionDescriptor} from 'vs/platform/actions/common/actions'; import {Registry} from 'vs/platform/platform'; +import {IQuickOpenService} from 'vs/workbench/services/quickopen/common/quickOpenService'; +import {QuickOpenAction} from 'vs/workbench/browser/actions/quickOpenAction'; import {IConfigurationRegistry, Extensions as ConfigurationExtensions} from 'vs/platform/configuration/common/configurationRegistry'; import {IWorkbenchActionRegistry, Extensions as ActionExtensions} from 'vs/workbench/common/actionRegistry'; import {IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions} from 'vs/workbench/common/contributions'; import {IEditorRegistry, Extensions as EditorExtensions, IEditorInputFactory} from 'vs/workbench/browser/parts/editor/baseEditor'; import {EditorInput, IFileEditorInput} from 'vs/workbench/common/editor'; +import {QuickOpenHandlerDescriptor, IQuickOpenRegistry, Extensions as QuickOpenExtensions} from 'vs/workbench/browser/quickopen'; import {FileEditorDescriptor} from 'vs/workbench/parts/files/browser/files'; import {AutoSaveConfiguration} from 'vs/platform/files/common/files'; import {FILE_EDITOR_INPUT_ID, VIEWLET_ID} from 'vs/workbench/parts/files/common/files'; @@ -65,7 +68,8 @@ let openViewletKb: IKeybindings = { }; // Register Action to Open Viewlet -(Registry.as(ActionExtensions.WorkbenchActions)).registerWorkbenchAction( +const registry = Registry.as(ActionExtensions.WorkbenchActions); +registry.registerWorkbenchAction( new SyncActionDescriptor(OpenExplorerViewletAction, OpenExplorerViewletAction.ID, OpenExplorerViewletAction.LABEL, openViewletKb), nls.localize('view', "View") ); @@ -232,4 +236,37 @@ configurationRegistry.registerConfiguration({ 'default': true } } -}); \ No newline at end of file +}); + +// Register quick open handler for working files + +const ALL_WORKING_FILES_PREFIX = '~'; + +class OpenWorkingFileByNameAction extends QuickOpenAction { + + public static ID = 'workbench.files.action.workingFilesPicker'; + public static LABEL = nls.localize('workingFilesPicker', "Open Working File by Name"); + + constructor(actionId: string, actionLabel: string, @IQuickOpenService quickOpenService: IQuickOpenService) { + super(actionId, actionLabel, ALL_WORKING_FILES_PREFIX, quickOpenService); + } +} + +(Registry.as(QuickOpenExtensions.Quickopen)).registerQuickOpenHandler( + new QuickOpenHandlerDescriptor( + 'vs/workbench/parts/files/browser/workingFilesPicker', + 'WorkingFilesPicker', + ALL_WORKING_FILES_PREFIX, + [ + { + prefix: ALL_WORKING_FILES_PREFIX, + needsEditor: false, + description: nls.localize('openWorkingFile', "Open Working File By Name") + } + ] + ) +); + +registry.registerWorkbenchAction(new SyncActionDescriptor(OpenWorkingFileByNameAction, OpenWorkingFileByNameAction.ID, OpenWorkingFileByNameAction.LABEL, { + primary: KeyMod.chord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_P) +}), nls.localize('filesCategory', "Files")); \ No newline at end of file diff --git a/src/vs/workbench/parts/files/browser/views/workingFilesViewer.ts b/src/vs/workbench/parts/files/browser/views/workingFilesViewer.ts index a11530f0094..5f66ed359c7 100644 --- a/src/vs/workbench/parts/files/browser/views/workingFilesViewer.ts +++ b/src/vs/workbench/parts/files/browser/views/workingFilesViewer.ts @@ -70,7 +70,7 @@ export class WorkingFilesDataSource implements IDataSource { export class WorkingFilesSorter implements ISorter { - constructor( @INullService ns) { } + constructor(@INullService ns) { } public compare(tree: ITree, element: any, otherElement: any): number { return WorkingFilesModel.compare(element, otherElement); diff --git a/src/vs/workbench/parts/files/browser/workingFilesPicker.ts b/src/vs/workbench/parts/files/browser/workingFilesPicker.ts new file mode 100644 index 00000000000..a70ca32d99a --- /dev/null +++ b/src/vs/workbench/parts/files/browser/workingFilesPicker.ts @@ -0,0 +1,176 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import {TPromise} from 'vs/base/common/winjs.base'; +import nls = require('vs/nls'); +import paths = require('vs/base/common/paths'); +import labels = require('vs/base/common/labels'); +import URI from 'vs/base/common/uri'; +import errors = require('vs/base/common/errors'); +import strings = require('vs/base/common/strings'); +import {IAutoFocus, Mode, IContext} from 'vs/base/parts/quickopen/common/quickOpen'; +import {QuickOpenModel, QuickOpenEntry, QuickOpenEntryGroup} from 'vs/base/parts/quickopen/browser/quickOpenModel'; +import {WorkingFilesModel, WorkingFileEntry} from 'vs/workbench/parts/files/common/workingFilesModel'; +import scorer = require('vs/base/common/scorer'); +import {QuickOpenHandler} from 'vs/workbench/browser/quickopen'; +import {ITextFileService} from 'vs/workbench/parts/files/common/files'; +import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; +import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; +import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; + +export class WorkingFilePickerEntry extends QuickOpenEntryGroup { + private name: string; + private description: string; + private workingFilesEntry: WorkingFileEntry; + + constructor( + name: string, + description: string, + entry: WorkingFileEntry, + @IWorkbenchEditorService private editorService: IWorkbenchEditorService + ) { + super(); + + this.workingFilesEntry = entry; + this.name = name; + this.description = description; + } + + public getPrefix(): string { + if (this.workingFilesEntry.dirty) { + return '\u25cf '; // dirty decoration + } + + return void 0; + } + + public getLabel(): string { + return this.name; + } + + public getAriaLabel(): string { + return nls.localize('entryAriaLabel', "{0}, working file picker", this.getLabel()); + } + + public getDescription(): string { + return this.description; + } + + public getResource(): URI { + return this.workingFilesEntry.resource; + } + + public getWorkingFilesEntry(): WorkingFileEntry { + return this.workingFilesEntry; + } + + public run(mode: Mode, context: IContext): boolean { + if (mode === Mode.OPEN) { + return this.runOpen(context); + } + + return super.run(mode, context); + } + + private runOpen(context: IContext): boolean { + let event = context.event; + let sideBySide = (event && (event.ctrlKey || event.metaKey || (event.payload && event.payload.originalEvent && (event.payload.originalEvent.ctrlKey || event.payload.originalEvent.metaKey)))); + + this.editorService.openEditor({ resource: this.workingFilesEntry.resource }, sideBySide).done(null, errors.onUnexpectedError); + + return true; + } +} + +export class WorkingFilesPicker extends QuickOpenHandler { + private scorerCache: { [key: string]: number }; + + constructor( + @IInstantiationService private instantiationService: IInstantiationService, + @IWorkspaceContextService private contextService: IWorkspaceContextService, + @ITextFileService private textFileService: ITextFileService + ) { + super(); + + this.scorerCache = Object.create(null); + } + + public getResults(searchValue: string): TPromise { + searchValue = searchValue.trim(); + + const normalizedSearchValueLowercase = strings.stripWildcards(searchValue).toLowerCase(); + + return TPromise.as(new QuickOpenModel(this.textFileService.getWorkingFilesModel().getEntries() + + // Convert working files to quick open entries + .map(e => { + let label = paths.basename(e.resource.fsPath); + let description = labels.getPathLabel(paths.dirname(e.resource.fsPath), this.contextService); + if (description === '.') { + description = null; // for untitled files + } + + return this.instantiationService.createInstance(WorkingFilePickerEntry, label, description, e); + }) + + // Filter by search value + .filter(e => { + if (!searchValue) { + return true; + } + + let targetToMatch = labels.getPathLabel(e.getResource(), this.contextService); + if (!scorer.matches(targetToMatch, normalizedSearchValueLowercase)) { + return false; + } + + const {labelHighlights, descriptionHighlights} = QuickOpenEntry.highlight(e, searchValue, true /* fuzzy highlight */); + e.setHighlights(labelHighlights, descriptionHighlights); + + return true; + }). + + // Sort by search value score or natural order if not searching + sort((e1, e2) => { + if (!searchValue) { + return WorkingFilesModel.compare(e1.getWorkingFilesEntry(), e2.getWorkingFilesEntry()); + } + + return QuickOpenEntry.compareByScore(e1, e2, searchValue, normalizedSearchValueLowercase, this.scorerCache); + }). + + // Apply group label + map((e, index) => { + if (index === 0) { + e.setGroupLabel(nls.localize('workingFilesGroupLabel', "working files")); + } + + return e; + }))); + } + + public getEmptyLabel(searchString: string): string { + if (searchString) { + return nls.localize('noResultsFound', "No matching working files found"); + } + + return nls.localize('noWorkingFiles', "List of working files is currently empty"); + } + + public getAutoFocus(searchValue: string): IAutoFocus { + if (searchValue) { + return { + autoFocusFirstEntry: true + }; + } + + return super.getAutoFocus(searchValue); + } + + public onClose(canceled: boolean): void { + this.scorerCache = Object.create(null); + } +} \ No newline at end of file diff --git a/src/vs/workbench/parts/search/browser/search.contribution.ts b/src/vs/workbench/parts/search/browser/search.contribution.ts index 629f2997d84..a041edf3906 100644 --- a/src/vs/workbench/parts/search/browser/search.contribution.ts +++ b/src/vs/workbench/parts/search/browser/search.contribution.ts @@ -42,7 +42,6 @@ KeybindingsRegistry.registerCommandDesc({ } }); - class OpenSearchViewletAction extends ToggleViewletAction { public static ID = VIEWLET_ID; public static LABEL = nls.localize('showSearchViewlet', "Show Search"); @@ -53,7 +52,6 @@ class OpenSearchViewletAction extends ToggleViewletAction { } class ExplorerViewerActionContributor extends ActionBarContributor { - private _instantiationService: IInstantiationService; private _contextService: IWorkspaceContextService; diff --git a/src/vs/workbench/workbench.main.js b/src/vs/workbench/workbench.main.js index 70829f6da92..701b06156a2 100644 --- a/src/vs/workbench/workbench.main.js +++ b/src/vs/workbench/workbench.main.js @@ -35,6 +35,7 @@ define([ 'vs/workbench/parts/quickopen/browser/quickopen.contribution', 'vs/workbench/parts/files/browser/explorerViewlet', + 'vs/workbench/parts/files/browser/workingFilesPicker', 'vs/workbench/parts/files/browser/fileActions.contribution', 'vs/workbench/parts/files/browser/files.contribution', 'vs/workbench/parts/files/electron-browser/files.electron.contribution', -- GitLab