Skip to content

Commit

Permalink
Support quick file-search with goto line and column
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 filter input separators to indicate
the target line number and column. This follows the current vscode
patterns.

E.g.
<file filter>:<line number?>:<column?>
<file filter>#<line number?>#<column?>
<file filter>:<line number?>,<column?>

Signed-off-by: Alvaro Sanchez-Leon <[email protected]>
  • Loading branch information
alvsan09 committed May 17, 2021
1 parent 76f74f1 commit 6e31d41
Showing 1 changed file with 63 additions and 12 deletions.
75 changes: 63 additions & 12 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,22 @@ 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, Range } from '@theia/editor/lib/browser';

export const quickFileOpen: Command = {
id: 'file-search.openFile',
category: 'File',
label: 'Open File...'
};

export interface FilterAndRange {
filter: string;
range: Range;
}

// Supports patterns of <path><#|:><line><#|:|,><col?>
const LINE_COLON_PATTERN = /\s?[#:\(](?:line )?(\d*)(?:[#:,](\d*))?\)?\s*$/;

@injectable()
export class QuickFileOpenService implements QuickOpenModel, QuickOpenHandler {

Expand Down Expand Up @@ -69,10 +78,12 @@ export class QuickFileOpenService implements QuickOpenModel, QuickOpenHandler {
*/
protected isOpen: boolean = false;

protected filterAndRangeDefault = { filter: '', range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } };

/**
* The current lookFor string input by the user.
* Tracks the user file search filter and location range e.g. fileFilter:line:column or fileFilter:line,column
*/
protected currentLookFor: string = '';
protected filterAndRange: FilterAndRange = this.filterAndRangeDefault;

/**
* The score constants when comparing file search results.
Expand All @@ -94,7 +105,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 +137,11 @@ export class QuickFileOpenService implements QuickOpenModel, QuickOpenHandler {
this.hideIgnoredFiles = !this.hideIgnoredFiles;
} else {
this.hideIgnoredFiles = true;
this.currentLookFor = '';
this.filterAndRange = this.filterAndRangeDefault;
this.isOpen = true;
}

this.quickOpenService.open(this.currentLookFor);
this.quickOpenService.open(this.filterAndRange.filter);
}

/**
Expand All @@ -157,20 +168,22 @@ export class QuickFileOpenService implements QuickOpenModel, QuickOpenHandler {

const roots = this.workspaceService.tryGetRoots();

this.currentLookFor = lookFor;
this.filterAndRange = this.splitFilterAndRange(lookFor);
const fileFilter = this.filterAndRange.filter;

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(fileFilter, 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 (fileFilter.length > 0) {
const handler = async (results: string[]) => {
if (token.isCancellationRequested) {
return;
Expand Down Expand Up @@ -205,7 +218,7 @@ export class QuickFileOpenService implements QuickOpenModel, QuickOpenHandler {
acceptor([...recentlyUsedItems, ...sortedResults]);
};

this.fileSearchService.find(lookFor, {
this.fileSearchService.find(fileFilter, {
rootUris: roots.map(r => r.resource.toString()),
fuzzyMatch: true,
limit: 200,
Expand Down Expand Up @@ -254,7 +267,7 @@ export class QuickFileOpenService implements QuickOpenModel, QuickOpenHandler {
}

// Normalize the user query.
const query: string = normalize(this.currentLookFor);
const query: string = normalize(this.filterAndRange.filter);

/**
* Score a given string.
Expand Down Expand Up @@ -347,11 +360,17 @@ 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 {
return { selection: this.filterAndRange.range };
}

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 Expand Up @@ -386,4 +405,36 @@ export class QuickFileOpenService implements QuickOpenModel, QuickOpenHandler {
};
return new QuickOpenItem<QuickOpenItemOptions>(options);
}

/**
* Splits the given expression into a structure of search-file-filter and
* location-range.
*
* @param expression patterns of <path><#|:><line><#|:|,><col?>
*/
protected splitFilterAndRange(expression: string): FilterAndRange {
let lineNumber = 0;
let startColumn = 0;

// Find line and column number from the expression using RegExp.
const patternMatch = LINE_COLON_PATTERN.exec(expression);

if (patternMatch) {
const line = parseInt(patternMatch[1] ?? '', 10);
if (Number.isFinite(line)) {
lineNumber = line > 0 ? line - 1 : 0;

const column = parseInt(patternMatch[2] ?? '', 10);
startColumn = Number.isFinite(column) && column > 0 ? column - 1 : 0;
}
}

const position = Position.create(lineNumber, startColumn);
const range = { start: position, end: position };
const fileFilter = patternMatch ? expression.substr(0, patternMatch.index) : expression;
return {
filter: fileFilter,
range
};
}
}

0 comments on commit 6e31d41

Please sign in to comment.