diff --git a/packages/file-search/src/browser/quick-file-open.ts b/packages/file-search/src/browser/quick-file-open.ts index 19b3c811a7439..8eff32acb932c 100644 --- a/packages/file-search/src/browser/quick-file-open.ts +++ b/packages/file-search/src/browser/quick-file-open.ts @@ -30,6 +30,8 @@ import { NavigationLocationService } from '@theia/editor/lib/browser/navigation/ import * as fuzzy from '@theia/core/shared/fuzzy'; import { MessageService } from '@theia/core/lib/common/message-service'; import { FileSystemPreferences } from '@theia/filesystem/lib/browser'; +import { EditorOpenerOptions, Position } from '@theia/editor/lib/browser'; +import { integer } from '@theia/core/shared/vscode-languageserver-types'; export const quickFileOpen: Command = { id: 'file-search.openFile', @@ -37,6 +39,40 @@ export const quickFileOpen: Command = { label: 'Open File...' }; +export class FileQuery { + private _filePattern: string; + private _location: integer; + + constructor(fileQuery: string) { + this.parseFileQuery(fileQuery); + }; + + /** + * Parses the query in its different components and returns the corresponding file pattern of the query + * e.g. /path/to/file:10, will parse the query and return /path/to/file. + * + * @param fileQuery a file query in the form "filePathPattern:location number". + * + * @returns the filePathPattern portion of the query. + */ + parseFileQuery(fileQuery: string): string { + const queryParts = fileQuery.split(':', 2); + this._filePattern = queryParts[0].trim(); + const locationString = queryParts.length > 1 ? queryParts[1].trim() : ''; + const locationNumber = Number.parseInt(locationString); + this._location = locationNumber && locationNumber > 1 ? locationNumber - 1 : 0; + return this.filePattern; + } + + public get filePattern(): string { + return this._filePattern; + } + + public get position(): Position { + return Position.create(this._location, 0); + } +} + @injectable() export class QuickFileOpenService implements QuickOpenModel, QuickOpenHandler { @@ -69,10 +105,7 @@ export class QuickFileOpenService implements QuickOpenModel, QuickOpenHandler { */ protected isOpen: boolean = false; - /** - * The current lookFor string input by the user. - */ - protected currentLookFor: string = ''; + protected currentFileQuery: FileQuery = new FileQuery(''); /** * The score constants when comparing file search results. @@ -94,7 +127,7 @@ export class QuickFileOpenService implements QuickOpenModel, QuickOpenHandler { } getOptions(): QuickOpenOptions { - let placeholder = 'File name to search.'; + let placeholder = 'File name to search (append : to go to line).'; const keybinding = this.getKeyCommand(); if (keybinding) { placeholder += ` (Press ${keybinding} to show/hide ignored files)`; @@ -126,11 +159,11 @@ export class QuickFileOpenService implements QuickOpenModel, QuickOpenHandler { this.hideIgnoredFiles = !this.hideIgnoredFiles; } else { this.hideIgnoredFiles = true; - this.currentLookFor = ''; + this.currentFileQuery.parseFileQuery(''); this.isOpen = true; } - this.quickOpenService.open(this.currentLookFor); + this.quickOpenService.open(this.currentFileQuery.filePattern); } /** @@ -157,20 +190,21 @@ export class QuickFileOpenService implements QuickOpenModel, QuickOpenHandler { const roots = this.workspaceService.tryGetRoots(); - this.currentLookFor = lookFor; + const filePattern = this.currentFileQuery.parseFileQuery(lookFor); + const alreadyCollected = new Set(); const recentlyUsedItems: QuickOpenItem[] = []; const locations = [...this.navigationLocationService.locations()].reverse(); for (const location of locations) { const uriString = location.uri.toString(); - if (location.uri.scheme === 'file' && !alreadyCollected.has(uriString) && fuzzy.test(lookFor, uriString)) { + if (location.uri.scheme === 'file' && !alreadyCollected.has(uriString) && fuzzy.test(filePattern, uriString)) { const item = this.toItem(location.uri, { groupLabel: recentlyUsedItems.length === 0 ? 'recently opened' : undefined, showBorder: false }); recentlyUsedItems.push(item); alreadyCollected.add(uriString); } } - if (lookFor.length > 0) { + if (filePattern.length > 0) { const handler = async (results: string[]) => { if (token.isCancellationRequested) { return; @@ -205,7 +239,7 @@ export class QuickFileOpenService implements QuickOpenModel, QuickOpenHandler { acceptor([...recentlyUsedItems, ...sortedResults]); }; - this.fileSearchService.find(lookFor, { + this.fileSearchService.find(filePattern, { rootUris: roots.map(r => r.resource.toString()), fuzzyMatch: true, limit: 200, @@ -254,7 +288,7 @@ export class QuickFileOpenService implements QuickOpenModel, QuickOpenHandler { } // Normalize the user query. - const query: string = normalize(this.currentLookFor); + const query: string = normalize(this.currentFileQuery.filePattern); /** * Score a given string. @@ -347,11 +381,18 @@ export class QuickFileOpenService implements QuickOpenModel, QuickOpenHandler { } openFile(uri: URI): void { - this.openerService.getOpener(uri) - .then(opener => opener.open(uri)) + const options = this.buildOpenerOptions(); + const resolvedOpener = this.openerService.getOpener(uri, options); + resolvedOpener + .then(opener => opener.open(uri, options)) .catch(error => this.messageService.error(error)); } + protected buildOpenerOptions(): EditorOpenerOptions { + const position = this.currentFileQuery.position; + return { selection: { start: position, end: position } }; + } + private toItem(uriOrString: URI | string, group?: QuickOpenGroupItemOptions): QuickOpenItem { const uri = uriOrString instanceof URI ? uriOrString : new URI(uriOrString); let description = this.labelProvider.getLongName(uri.parent);