Skip to content
This repository was archived by the owner on Jul 31, 2023. It is now read-only.

Commit 7f3ec7d

Browse files
committed
Implement WorkspaceSymbolProvider
1 parent e3b253f commit 7f3ec7d

File tree

2 files changed

+56
-14
lines changed

2 files changed

+56
-14
lines changed

locate/locate.js

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,55 @@ const _ = require('lodash');
77

88
const DECLARATION_TYPES = ['class', 'module', 'method', 'classMethod'];
99

10-
function flatten(locateInfo, file, parent) {
10+
function flatten(locateInfo, file, containerName = '') {
1111
return _.flatMap(locateInfo, (symbols, type) => {
1212
if (!_.includes(DECLARATION_TYPES, type)) {
1313
// Skip top-level include or posn property etc.
1414
return [];
1515
}
1616
return _.flatMap(symbols, (inner, name) => {
17-
const sep = { method: '#', classMethod: '.' }[type] || '::';
1817
const posn = inner.posn || { line: 0, char: 0 };
19-
const fullName = parent ? `${parent.fullName}${sep}${name}` : name;
18+
// TODO: parse name with multiple segments, e.g. File.read or ActiveRecord::Base, if necessary.
2019
const symbolInfo = {
2120
name: name,
2221
type: type,
2322
file: file,
2423
line: posn.line,
2524
char: posn.char,
26-
parent: parent,
27-
fullName: fullName
25+
containerName: containerName || ''
2826
};
2927
_.extend(symbolInfo, _.omit(inner, DECLARATION_TYPES));
30-
return [symbolInfo].concat(flatten(inner, file, symbolInfo));
28+
const sep = { method: '#', classMethod: '.' }[type] || '::';
29+
const fullName = containerName ? `${containerName}${sep}${name}` : name;
30+
return [symbolInfo].concat(flatten(inner, file, fullName));
3131
});
3232
});
3333
}
34+
function camelCaseRegExp(query) {
35+
const escaped = _.escapeRegExp(query)
36+
const prefix = escaped.charAt(0);
37+
return new RegExp(
38+
`[${prefix.toLowerCase()}${prefix.toUpperCase()}]` +
39+
escaped.slice(1).replace(/[A-Z]|([a-z])/g, (char, lower) => {
40+
if (lower) return `${char.toUpperCase()}${char}`;
41+
const lowered = char.toLowerCase()
42+
return `.*(?:${char}|_${lowered})`;
43+
})
44+
);
45+
}
46+
function filter(symbols, query, stringProvider) {
47+
// TODO: Ask MS to expose or separate matchesFuzzy method.
48+
// https://github.com/Microsoft/vscode/blob/a1d3c8a3006d0a3d68495122ea09a2a37bca69db/src/vs/base/common/filters.ts
49+
const isLowerCase = (query.toLowerCase() === query)
50+
const exact = new RegExp('^' + _.escapeRegExp(query) + '$', 'i');
51+
const prefix = new RegExp('^' + _.escapeRegExp(query), 'i');
52+
const substring = new RegExp(_.escapeRegExp(query), isLowerCase ? 'i' : '');
53+
const camelCase = camelCaseRegExp(query);
54+
return _([exact, prefix, substring, camelCase])
55+
.flatMap(regexp => symbols.filter(symbolInfo => regexp.test(stringProvider(symbolInfo))))
56+
.uniq()
57+
.value();
58+
}
3459
module.exports = class Locate {
3560
constructor(root, settings) {
3661
this.settings = settings;
@@ -58,6 +83,18 @@ module.exports = class Locate {
5883
.map(_.clone)
5984
.value();
6085
}
86+
query(query) {
87+
const match = query.match(/^(?:(.*)[.#])?([^.#]*)$/);
88+
const containerQuery = match[1];
89+
const nameQuery = match[2];
90+
if (!nameQuery) return [];
91+
92+
const symbols = _(this.tree).values().flatten().value();
93+
const matchedSymbols = filter(symbols, nameQuery, symbolInfo => symbolInfo.name);
94+
if (!containerQuery) return matchedSymbols;
95+
96+
return filter(matchedSymbols, containerQuery, symbolInfo => symbolInfo.containerName);
97+
}
6198
rm(absPath) {
6299
if (absPath in this.tree) delete this.tree[absPath];
63100
}

ruby.js

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -188,19 +188,24 @@ function activate(context) {
188188
console.warn(`Unknown symbol type: ${symbolInfo.type}`);
189189
return SymbolKind.Variable;
190190
};
191+
const symbolConverter = matches => matches.map(match => {
192+
const symbolKind = (symbolKindTable[match.type] || defaultSymbolKind)(match);
193+
const uri = vscode.Uri.file(match.file);
194+
const location = new Location(uri, new Position(match.line, match.char));
195+
return new SymbolInformation(match.name, symbolKind, match.containerName, location);
196+
});
191197
const docSymbolProvider = {
192198
provideDocumentSymbols: (document, token) => {
193-
return locate.listInFile(document.fileName)
194-
.then(matches => matches.map(match => {
195-
const symbolKind = (symbolKindTable[match.type] || defaultSymbolKind)(match);
196-
const parentName = match.parent ? match.parent.fullName : '';
197-
const uri = vscode.Uri.file(match.file);
198-
const location = new Location(uri, new Position(match.line, match.char));
199-
return new SymbolInformation(match.name, symbolKind, parentName, location);
200-
}));
199+
return locate.listInFile(document.fileName).then(symbolConverter);
201200
}
202201
};
203202
subs.push(vscode.languages.registerDocumentSymbolProvider(['ruby', 'erb'], docSymbolProvider));
203+
const workspaceSymbolProvider = {
204+
provideWorkspaceSymbols: (query, token) => {
205+
return symbolConverter(locate.query(query));
206+
}
207+
};
208+
subs.push(vscode.languages.registerWorkspaceSymbolProvider(workspaceSymbolProvider));
204209
}
205210

206211
subs.push(vscode.window.onDidChangeActiveTextEditor(balanceEvent));

0 commit comments

Comments
 (0)