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

Commit

Permalink
Implement WorkspaceSymbolProvider
Browse files Browse the repository at this point in the history
  • Loading branch information
ypresto authored and rebornix committed Jan 23, 2017
1 parent 9a8ef13 commit 40eaa99
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 14 deletions.
49 changes: 43 additions & 6 deletions locate/locate.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,55 @@ const _ = require('lodash');

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

function flatten(locateInfo, file, parent) {
function flatten(locateInfo, file, containerName = '') {
return _.flatMap(locateInfo, (symbols, type) => {
if (!_.includes(DECLARATION_TYPES, type)) {
// Skip top-level include or posn property etc.
return [];
}
return _.flatMap(symbols, (inner, name) => {
const sep = { method: '#', classMethod: '.' }[type] || '::';
const posn = inner.posn || { line: 0, char: 0 };
const fullName = parent ? `${parent.fullName}${sep}${name}` : name;
// TODO: parse name with multiple segments, e.g. File.read or ActiveRecord::Base, if necessary.
const symbolInfo = {
name: name,
type: type,
file: file,
line: posn.line,
char: posn.char,
parent: parent,
fullName: fullName
containerName: containerName || ''
};
_.extend(symbolInfo, _.omit(inner, DECLARATION_TYPES));
return [symbolInfo].concat(flatten(inner, file, symbolInfo));
const sep = { method: '#', classMethod: '.' }[type] || '::';
const fullName = containerName ? `${containerName}${sep}${name}` : name;
return [symbolInfo].concat(flatten(inner, file, fullName));
});
});
}
function camelCaseRegExp(query) {
const escaped = _.escapeRegExp(query)
const prefix = escaped.charAt(0);
return new RegExp(
`[${prefix.toLowerCase()}${prefix.toUpperCase()}]` +
escaped.slice(1).replace(/[A-Z]|([a-z])/g, (char, lower) => {
if (lower) return `[${char.toUpperCase()}${char}]`;
const lowered = char.toLowerCase()
return `.*(?:${char}|_${lowered})`;
})
);
}
function filter(symbols, query, stringProvider) {
// TODO: Ask MS to expose or separate matchesFuzzy method.
// https://github.com/Microsoft/vscode/blob/a1d3c8a3006d0a3d68495122ea09a2a37bca69db/src/vs/base/common/filters.ts
const isLowerCase = (query.toLowerCase() === query)
const exact = new RegExp('^' + _.escapeRegExp(query) + '$', 'i');
const prefix = new RegExp('^' + _.escapeRegExp(query), 'i');
const substring = new RegExp(_.escapeRegExp(query), isLowerCase ? 'i' : '');
const camelCase = camelCaseRegExp(query);
return _([exact, prefix, substring, camelCase])
.flatMap(regexp => symbols.filter(symbolInfo => regexp.test(stringProvider(symbolInfo))))
.uniq()
.value();
}
module.exports = class Locate {
constructor(root, settings) {
this.settings = settings;
Expand Down Expand Up @@ -58,6 +83,18 @@ module.exports = class Locate {
.map(_.clone)
.value();
}
query(query) {
const match = query.match(/^(?:(.*)[.#])?([^.#]*)$/);
const containerQuery = match[1];
const nameQuery = match[2];
if (!nameQuery) return [];

const symbols = _(this.tree).values().flatten().value();
const matchedSymbols = filter(symbols, nameQuery, symbolInfo => symbolInfo.name);
if (!containerQuery) return matchedSymbols;

return filter(matchedSymbols, containerQuery, symbolInfo => symbolInfo.containerName);
}
rm(absPath) {
if (absPath in this.tree) delete this.tree[absPath];
}
Expand Down
21 changes: 13 additions & 8 deletions ruby.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,19 +188,24 @@ function activate(context) {
console.warn(`Unknown symbol type: ${symbolInfo.type}`);
return SymbolKind.Variable;
};
const symbolConverter = matches => matches.map(match => {
const symbolKind = (symbolKindTable[match.type] || defaultSymbolKind)(match);
const uri = vscode.Uri.file(match.file);
const location = new Location(uri, new Position(match.line, match.char));
return new SymbolInformation(match.name, symbolKind, match.containerName, location);
});
const docSymbolProvider = {
provideDocumentSymbols: (document, token) => {
return locate.listInFile(document.fileName)
.then(matches => matches.map(match => {
const symbolKind = (symbolKindTable[match.type] || defaultSymbolKind)(match);
const parentName = match.parent ? match.parent.fullName : '';
const uri = vscode.Uri.file(match.file);
const location = new Location(uri, new Position(match.line, match.char));
return new SymbolInformation(match.name, symbolKind, parentName, location);
}));
return locate.listInFile(document.fileName).then(symbolConverter);
}
};
subs.push(vscode.languages.registerDocumentSymbolProvider(['ruby', 'erb'], docSymbolProvider));
const workspaceSymbolProvider = {
provideWorkspaceSymbols: (query, token) => {
return symbolConverter(locate.query(query));
}
};
subs.push(vscode.languages.registerWorkspaceSymbolProvider(workspaceSymbolProvider));
}

subs.push(vscode.window.onDidChangeActiveTextEditor(balanceEvent));
Expand Down

0 comments on commit 40eaa99

Please sign in to comment.