make search editor ranges work for hidden text

pull/110273/head
Connor Peet 2020-11-10 15:57:03 -08:00
parent 809db2993b
commit 046654ae65
No known key found for this signature in database
GPG Key ID: CF8FD2EA0DBC61BD
3 changed files with 53 additions and 23 deletions

View File

@ -8,6 +8,7 @@ import * as pathUtils from 'path';
const FILE_LINE_REGEX = /^(\S.*):$/;
const RESULT_LINE_REGEX = /^(\s+)(\d+)(:| )(\s+)(.*)$/;
const ELISION_REGEX = /\.{3}\(([0-9]+) characters skipped\)\.{3}/g;
const SEARCH_RESULT_SELECTOR = { language: 'search-result', exclusive: true };
const DIRECTIVES = ['# Query:', '# Flags:', '# Including:', '# Excluding:', '# ContextLines:'];
const FLAGS = ['RegExp', 'CaseSensitive', 'IgnoreExcludeSettings', 'WordMatch'];
@ -80,12 +81,18 @@ export function activate(context: vscode.ExtensionContext) {
return lineResult.allLocations;
}
const translateRangeSidewaysBy = (r: vscode.Range, n: number) =>
r.with({ start: new vscode.Position(r.start.line, Math.max(0, n - r.start.character)), end: new vscode.Position(r.end.line, Math.max(0, n - r.end.character)) });
const location = lineResult.locations.find(l => l.originSelectionRange.contains(position));
if (!location) {
return [];
}
const targetPos = new vscode.Position(
location.targetSelectionRange.start.line,
location.targetSelectionRange.start.character + (position.character - location.originSelectionRange.start.character)
);
return [{
...lineResult.location,
targetSelectionRange: translateRangeSidewaysBy(lineResult.location.targetSelectionRange!, position.character - 1)
...location,
targetSelectionRange: new vscode.Range(targetPos, targetPos),
}];
}
}),
@ -93,7 +100,7 @@ export function activate(context: vscode.ExtensionContext) {
vscode.languages.registerDocumentLinkProvider(SEARCH_RESULT_SELECTOR, {
async provideDocumentLinks(document: vscode.TextDocument, token: vscode.CancellationToken): Promise<vscode.DocumentLink[]> {
return parseSearchResults(document, token)
.filter(({ type }) => type === 'file')
.filter(isFileLine)
.map(({ location }) => ({ range: location.originSelectionRange!, target: location.targetUri }));
}
}),
@ -162,7 +169,7 @@ function relativePathToUri(path: string, resultsUri: vscode.Uri): vscode.Uri | u
}
type ParsedSearchFileLine = { type: 'file', location: vscode.LocationLink, allLocations: vscode.LocationLink[], path: string };
type ParsedSearchResultLine = { type: 'result', location: vscode.LocationLink, isContext: boolean, prefixRange: vscode.Range };
type ParsedSearchResultLine = { type: 'result', locations: Required<vscode.LocationLink>[], isContext: boolean, prefixRange: vscode.Range };
type ParsedSearchResults = Array<ParsedSearchFileLine | ParsedSearchResultLine>;
const isFileLine = (line: ParsedSearchResultLine | ParsedSearchFileLine): line is ParsedSearchFileLine => line.type === 'file';
const isResultLine = (line: ParsedSearchResultLine | ParsedSearchFileLine): line is ParsedSearchResultLine => line.type === 'result';
@ -211,17 +218,35 @@ function parseSearchResults(document: vscode.TextDocument, token?: vscode.Cancel
const lineNumber = +_lineNumber - 1;
const resultStart = (indentation + _lineNumber + seperator + resultIndentation).length;
const metadataOffset = (indentation + _lineNumber + seperator).length;
const targetRange = new vscode.Range(Math.max(lineNumber - 3, 0), 0, lineNumber + 3, line.length);
const location: vscode.LocationLink = {
targetRange: new vscode.Range(Math.max(lineNumber - 3, 0), 0, lineNumber + 3, line.length),
targetSelectionRange: new vscode.Range(lineNumber, metadataOffset, lineNumber, metadataOffset),
targetUri: currentTarget,
originSelectionRange: new vscode.Range(i, resultStart, i, line.length),
};
let lastEnd = resultStart;
let offset = 0;
let locations: Required<vscode.LocationLink>[] = [];
ELISION_REGEX.lastIndex = resultStart - 1;
for (let match: RegExpExecArray | null; (match = ELISION_REGEX.exec(line));) {
locations.push({
targetRange,
targetSelectionRange: new vscode.Range(lineNumber, offset, lineNumber, offset),
targetUri: currentTarget,
originSelectionRange: new vscode.Range(i, lastEnd, i, ELISION_REGEX.lastIndex),
});
currentTargetLocations?.push(location);
offset += (ELISION_REGEX.lastIndex - lastEnd) + Number(match[1]);
lastEnd = ELISION_REGEX.lastIndex + match[0].length;
}
links[i] = { type: 'result', location, isContext: seperator === ' ', prefixRange: new vscode.Range(i, 0, i, metadataOffset) };
if (lastEnd < line.length) {
locations.push({
targetRange,
targetSelectionRange: new vscode.Range(lineNumber, offset, lineNumber, offset),
targetUri: currentTarget,
originSelectionRange: new vscode.Range(i, lastEnd, i, line.length),
});
}
currentTargetLocations?.push(...locations);
links[i] = { type: 'result', locations, isContext: seperator === ' ', prefixRange: new vscode.Range(i, 0, i, metadataOffset) };
}
}

View File

@ -24,7 +24,11 @@ export const VIEW_ID = 'workbench.view.search';
export const SEARCH_EXCLUDE_CONFIG = 'search.exclude';
const SEARCH_SINGLE_RANGE_DIVIDER = '...';
// Warning: this pattern is used in the search editor to detect offsets. If you
// change this, also change the search-result built-in extension
const SEARCH_ELIDED_PREFIX = '...(';
const SEARCH_ELIDED_SUFFIX = ' characters skipped)...';
const SEARCH_ELIDED_MIN_LEN = (SEARCH_ELIDED_PREFIX.length + SEARCH_ELIDED_SUFFIX.length + 5) * 2;
export const ISearchService = createDecorator<ISearchService>('searchService');
@ -275,9 +279,10 @@ export class TextSearchMatch implements ITextSearchMatch {
for (const range of ranges) {
const previewStart = Math.max(range.startColumn - leadingChars, 0);
const previewEnd = range.startColumn + previewOptions.charsPerLine;
if (previewStart > lastEnd + leadingChars) {
result += SEARCH_SINGLE_RANGE_DIVIDER + text.slice(previewStart, previewEnd);
shift += previewStart - (lastEnd + SEARCH_SINGLE_RANGE_DIVIDER.length);
if (previewStart > lastEnd + leadingChars + SEARCH_ELIDED_MIN_LEN) {
const elision = SEARCH_ELIDED_PREFIX + (previewStart - lastEnd) + SEARCH_ELIDED_SUFFIX;
result += elision + text.slice(previewStart, previewEnd);
shift += previewStart - (lastEnd + elision.length);
} else {
result += text.slice(lastEnd, previewEnd);
}

View File

@ -102,11 +102,11 @@ suite('TextSearchResult', () => {
};
const range1 = new SearchRange(5, 4, 5, 7);
const range2 = new SearchRange(5, 53, 5, 56);
const range3 = new SearchRange(5, 61, 5, 64);
const result = new TextSearchMatch('foo bar 1234567890123456789012345678901234567890 foo bar baz bar', [range1, range2, range3], previewOptions);
assert.deepEqual(result.preview.matches, [new SearchRange(0, 4, 0, 7), new SearchRange(0, 19, 0, 22), new SearchRange(0, 27, 0, 30)]);
assert.equal(result.preview.text, 'foo bar 123456...o bar baz bar');
const range2 = new SearchRange(5, 133, 5, 136);
const range3 = new SearchRange(5, 141, 5, 144);
const result = new TextSearchMatch('foo bar 123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 foo bar baz bar', [range1, range2, range3], previewOptions);
assert.deepEqual(result.preview.matches, [new SearchRange(0, 4, 0, 7), new SearchRange(0, 46, 0, 49), new SearchRange(0, 54, 0, 57)]);
assert.equal(result.preview.text, 'foo bar 123456...(117 characters skipped)...o bar baz bar');
});
// test('all lines of multiline match', () => {