Skip to content

Commit

Permalink
Support quick file-search with go to line
Browse files Browse the repository at this point in the history
The quick file-search text box (normally accessed via Ctrl + P)
now allows the option to append the input with ':' and a line number
to go to.

Signed-off-by: Alvaro Sanchez-Leon <[email protected]>
  • Loading branch information
alvsan09 committed May 13, 2021
1 parent 2eb4319 commit effb14f
Showing 1 changed file with 55 additions and 14 deletions.
69 changes: 55 additions & 14 deletions packages/file-search/src/browser/quick-file-open.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,49 @@ 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',
category: 'File',
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 {

Expand Down Expand Up @@ -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.
Expand All @@ -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)`;
Expand Down Expand Up @@ -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);
}

/**
Expand All @@ -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<string>();
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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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<QuickOpenItemOptions> {
const uri = uriOrString instanceof URI ? uriOrString : new URI(uriOrString);
let description = this.labelProvider.getLongName(uri.parent);
Expand Down

0 comments on commit effb14f

Please sign in to comment.