Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Jakefile
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ var servicesSources = [
return path.join(compilerDirectory, f);
}).concat([
"breakpoints.ts",
"navigateTo.ts",
"navigationBar.ts",
"outliningElementsCollector.ts",
"services.ts",
Expand Down
105 changes: 105 additions & 0 deletions src/services/navigateTo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
module ts.NavigateTo {
type RawNavigateToItem = { name: string; fileName: string; matchKind: MatchKind; declaration: Declaration };

enum MatchKind {
none = 0,
exact = 1,
substring = 2,
prefix = 3
}

export function getNavigateToItems(program: Program, cancellationToken: CancellationTokenObject, searchValue: string, maxResultCount: number): NavigateToItem[]{
// Split search value in terms array
var terms = searchValue.split(" ");

// default NavigateTo approach: if search term contains only lower-case chars - use case-insensitive search, otherwise switch to case-sensitive version
var searchTerms = map(terms, t => ({ caseSensitive: hasAnyUpperCaseCharacter(t), term: t }));

var rawItems: RawNavigateToItem[] = [];

// Search the declarations in all files and output matched NavigateToItem into array of NavigateToItem[]
forEach(program.getSourceFiles(), sourceFile => {
cancellationToken.throwIfCancellationRequested();

var fileName = sourceFile.fileName;
var declarations = sourceFile.getNamedDeclarations();
for (var i = 0, n = declarations.length; i < n; i++) {
var declaration = declarations[i];
// TODO(jfreeman): Skip this declaration if it has a computed name
var name = (<Identifier>declaration.name).text;
var matchKind = getMatchKind(searchTerms, name);
if (matchKind !== MatchKind.none) {
rawItems.push({ name, fileName, matchKind, declaration });
}
}
});

rawItems.sort((i1, i2) => i1.matchKind - i2.matchKind);
if (maxResultCount !== undefined) {
rawItems = rawItems.slice(0, maxResultCount);
}

var items = map(rawItems, createNavigateToItem);

return items;

function createNavigateToItem(rawItem: RawNavigateToItem): NavigateToItem {
var declaration = rawItem.declaration;
var container = <Declaration>getContainerNode(declaration);
return {
name: rawItem.name,
kind: getNodeKind(declaration),
kindModifiers: getNodeModifiers(declaration),
matchKind: MatchKind[rawItem.matchKind],
fileName: rawItem.fileName,
textSpan: createTextSpanFromBounds(declaration.getStart(), declaration.getEnd()),
// TODO(jfreeman): What should be the containerName when the container has a computed name?
containerName: container && container.name ? (<Identifier>container.name).text : "",
containerKind: container && container.name ? getNodeKind(container) : ""
};
}

function hasAnyUpperCaseCharacter(s: string): boolean {
for (var i = 0, n = s.length; i < n; i++) {
var c = s.charCodeAt(i);
if ((CharacterCodes.A <= c && c <= CharacterCodes.Z) ||
(c >= CharacterCodes.maxAsciiCharacter && s.charAt(i).toLocaleLowerCase() !== s.charAt(i))) {
return true;
}
}

return false;
}

function getMatchKind(searchTerms: { caseSensitive: boolean; term: string }[], name: string): MatchKind {
var matchKind = MatchKind.none;

if (name) {
for (var j = 0, n = searchTerms.length; j < n; j++) {
var searchTerm = searchTerms[j];
var nameToSearch = searchTerm.caseSensitive ? name : name.toLocaleLowerCase();
// in case of case-insensitive search searchTerm.term will already be lower-cased
var index = nameToSearch.indexOf(searchTerm.term);
if (index < 0) {
// Didn't match.
return MatchKind.none;
}

var termKind = MatchKind.substring;
if (index === 0) {
// here we know that match occur at the beginning of the string.
// if search term and declName has the same length - we have an exact match, otherwise declName have longer length and this will be prefix match
termKind = name.length === searchTerm.term.length ? MatchKind.exact : MatchKind.prefix;
}

// Update our match kind if we don't have one, or if this match is better.
if (matchKind === MatchKind.none || termKind < matchKind) {
matchKind = termKind;
}
}
}

return matchKind;
}
}
}
205 changes: 60 additions & 145 deletions src/services/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

/// <reference path='breakpoints.ts' />
/// <reference path='outliningElementsCollector.ts' />
/// <reference path='navigateTo.ts' />
/// <reference path='navigationBar.ts' />
/// <reference path='signatureHelp.ts' />
/// <reference path='utilities.ts' />
Expand Down Expand Up @@ -900,7 +901,7 @@ module ts {
getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[];
getOccurrencesAtPosition(fileName: string, position: number): ReferenceEntry[];

getNavigateToItems(searchValue: string): NavigateToItem[];
getNavigateToItems(searchValue: string, maxResultCount?: number): NavigateToItem[];
getNavigationBarItems(fileName: string): NavigationBarItem[];

getOutliningSpans(fileName: string): OutliningSpan[];
Expand Down Expand Up @@ -1378,13 +1379,6 @@ module ts {
public static typeAlias = "type alias name";
}

enum MatchKind {
none = 0,
exact = 1,
substring = 2,
prefix = 3
}

/// Language Service

interface CompletionSession {
Expand Down Expand Up @@ -1992,6 +1986,62 @@ module ts {
});
}

/* @internal */ export function getContainerNode(node: Node): Node {
while (true) {
node = node.parent;
if (!node) {
return undefined;
}
switch (node.kind) {
case SyntaxKind.SourceFile:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.ModuleDeclaration:
return node;
}
}
}

/* @internal */ export function getNodeKind(node: Node): string {
switch (node.kind) {
case SyntaxKind.ModuleDeclaration: return ScriptElementKind.moduleElement;
case SyntaxKind.ClassDeclaration: return ScriptElementKind.classElement;
case SyntaxKind.InterfaceDeclaration: return ScriptElementKind.interfaceElement;
case SyntaxKind.TypeAliasDeclaration: return ScriptElementKind.typeElement;
case SyntaxKind.EnumDeclaration: return ScriptElementKind.enumElement;
case SyntaxKind.VariableDeclaration:
return isConst(node)
? ScriptElementKind.constElement
: isLet(node)
? ScriptElementKind.letElement
: ScriptElementKind.variableElement;
case SyntaxKind.FunctionDeclaration: return ScriptElementKind.functionElement;
case SyntaxKind.GetAccessor: return ScriptElementKind.memberGetAccessorElement;
case SyntaxKind.SetAccessor: return ScriptElementKind.memberSetAccessorElement;
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
return ScriptElementKind.memberFunctionElement;
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
return ScriptElementKind.memberVariableElement;
case SyntaxKind.IndexSignature: return ScriptElementKind.indexSignatureElement;
case SyntaxKind.ConstructSignature: return ScriptElementKind.constructSignatureElement;
case SyntaxKind.CallSignature: return ScriptElementKind.callSignatureElement;
case SyntaxKind.Constructor: return ScriptElementKind.constructorImplementationElement;
case SyntaxKind.TypeParameter: return ScriptElementKind.typeParameterElement;
case SyntaxKind.EnumMember: return ScriptElementKind.variableElement;
case SyntaxKind.Parameter: return (node.flags & NodeFlags.AccessibilityModifier) ? ScriptElementKind.memberVariableElement : ScriptElementKind.parameterElement;
}
return ScriptElementKind.unknown;
}

export function createLanguageService(host: LanguageServiceHost, documentRegistry: DocumentRegistry = createDocumentRegistry()): LanguageService {
var syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host);
var ruleProvider: formatting.RulesProvider;
Expand Down Expand Up @@ -2722,29 +2772,6 @@ module ts {
}
}

function getContainerNode(node: Node): Node {
while (true) {
node = node.parent;
if (!node) {
return undefined;
}
switch (node.kind) {
case SyntaxKind.SourceFile:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.ModuleDeclaration:
return node;
}
}
}

// TODO(drosen): use contextual SemanticMeaning.
function getSymbolKind(symbol: Symbol, typeResolver: TypeChecker, location: Node): string {
var flags = symbol.getFlags();
Expand Down Expand Up @@ -2831,39 +2858,6 @@ module ts {
return ScriptElementKind.unknown;
}

function getNodeKind(node: Node): string {
switch (node.kind) {
case SyntaxKind.ModuleDeclaration: return ScriptElementKind.moduleElement;
case SyntaxKind.ClassDeclaration: return ScriptElementKind.classElement;
case SyntaxKind.InterfaceDeclaration: return ScriptElementKind.interfaceElement;
case SyntaxKind.TypeAliasDeclaration: return ScriptElementKind.typeElement;
case SyntaxKind.EnumDeclaration: return ScriptElementKind.enumElement;
case SyntaxKind.VariableDeclaration:
return isConst(node)
? ScriptElementKind.constElement
: isLet(node)
? ScriptElementKind.letElement
: ScriptElementKind.variableElement;
case SyntaxKind.FunctionDeclaration: return ScriptElementKind.functionElement;
case SyntaxKind.GetAccessor: return ScriptElementKind.memberGetAccessorElement;
case SyntaxKind.SetAccessor: return ScriptElementKind.memberSetAccessorElement;
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
return ScriptElementKind.memberFunctionElement;
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
return ScriptElementKind.memberVariableElement;
case SyntaxKind.IndexSignature: return ScriptElementKind.indexSignatureElement;
case SyntaxKind.ConstructSignature: return ScriptElementKind.constructSignatureElement;
case SyntaxKind.CallSignature: return ScriptElementKind.callSignatureElement;
case SyntaxKind.Constructor: return ScriptElementKind.constructorImplementationElement;
case SyntaxKind.TypeParameter: return ScriptElementKind.typeParameterElement;
case SyntaxKind.EnumMember: return ScriptElementKind.variableElement;
case SyntaxKind.Parameter: return (node.flags & NodeFlags.AccessibilityModifier) ? ScriptElementKind.memberVariableElement : ScriptElementKind.parameterElement;
}
return ScriptElementKind.unknown;
}

function getSymbolModifiers(symbol: Symbol): string {
return symbol && symbol.declarations && symbol.declarations.length > 0
? getNodeModifiers(symbol.declarations[0])
Expand Down Expand Up @@ -4663,89 +4657,10 @@ module ts {
}

/// NavigateTo
function getNavigateToItems(searchValue: string): NavigateToItem[] {
function getNavigateToItems(searchValue: string, maxResultCount?: number): NavigateToItem[] {
synchronizeHostData();

// Split search value in terms array
var terms = searchValue.split(" ");

// default NavigateTo approach: if search term contains only lower-case chars - use case-insensitive search, otherwise switch to case-sensitive version
var searchTerms = map(terms, t => ({ caseSensitive: hasAnyUpperCaseCharacter(t), term: t }));

var items: NavigateToItem[] = [];

// Search the declarations in all files and output matched NavigateToItem into array of NavigateToItem[]
forEach(program.getSourceFiles(), sourceFile => {
cancellationToken.throwIfCancellationRequested();

var fileName = sourceFile.fileName;
var declarations = sourceFile.getNamedDeclarations();
for (var i = 0, n = declarations.length; i < n; i++) {
var declaration = declarations[i];
// TODO(jfreeman): Skip this declaration if it has a computed name
var name = (<Identifier>declaration.name).text;
var matchKind = getMatchKind(searchTerms, name);
if (matchKind !== MatchKind.none) {
var container = <Declaration>getContainerNode(declaration);
items.push({
name: name,
kind: getNodeKind(declaration),
kindModifiers: getNodeModifiers(declaration),
matchKind: MatchKind[matchKind],
fileName: fileName,
textSpan: createTextSpanFromBounds(declaration.getStart(), declaration.getEnd()),
// TODO(jfreeman): What should be the containerName when the container has a computed name?
containerName: container && container.name ? (<Identifier>container.name).text : "",
containerKind: container && container.name ? getNodeKind(container) : ""
});
}
}
});

return items;

function hasAnyUpperCaseCharacter(s: string): boolean {
for (var i = 0, n = s.length; i < n; i++) {
var c = s.charCodeAt(i);
if ((CharacterCodes.A <= c && c <= CharacterCodes.Z) ||
(c >= CharacterCodes.maxAsciiCharacter && s.charAt(i).toLocaleLowerCase() !== s.charAt(i))) {
return true;
}
}

return false;
}

function getMatchKind(searchTerms: { caseSensitive: boolean; term: string }[], name: string): MatchKind {
var matchKind = MatchKind.none;

if (name) {
for (var j = 0, n = searchTerms.length; j < n; j++) {
var searchTerm = searchTerms[j];
var nameToSearch = searchTerm.caseSensitive ? name : name.toLocaleLowerCase();
// in case of case-insensitive search searchTerm.term will already be lower-cased
var index = nameToSearch.indexOf(searchTerm.term);
if (index < 0) {
// Didn't match.
return MatchKind.none;
}

var termKind = MatchKind.substring;
if (index === 0) {
// here we know that match occur at the beginning of the string.
// if search term and declName has the same length - we have an exact match, otherwise declName have longer length and this will be prefix match
termKind = name.length === searchTerm.term.length ? MatchKind.exact : MatchKind.prefix;
}

// Update our match kind if we don't have one, or if this match is better.
if (matchKind === MatchKind.none || termKind < matchKind) {
matchKind = termKind;
}
}
}

return matchKind;
}
return ts.NavigateTo.getNavigateToItems(program, cancellationToken, searchValue, maxResultCount);
}

function containErrors(diagnostics: Diagnostic[]): boolean {
Expand Down
8 changes: 4 additions & 4 deletions src/services/shims.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ module ts {
* Returns a JSON-encoded value of the type:
* { name: string; kind: string; kindModifiers: string; containerName: string; containerKind: string; matchKind: string; fileName: string; textSpan: { start: number; length: number}; } [] = [];
*/
getNavigateToItems(searchValue: string): string;
getNavigateToItems(searchValue: string, maxResultCount?: number): string;

/**
* Returns a JSON-encoded value of the type:
Expand Down Expand Up @@ -628,11 +628,11 @@ module ts {
/// NAVIGATE TO

/** Return a list of symbols that are interesting to navigate to */
public getNavigateToItems(searchValue: string): string {
public getNavigateToItems(searchValue: string, maxResultCount?: number): string {
return this.forwardJSONCall(
"getNavigateToItems('" + searchValue + "')",
"getNavigateToItems('" + searchValue + "', " + maxResultCount+ ")",
() => {
var items = this.languageService.getNavigateToItems(searchValue);
var items = this.languageService.getNavigateToItems(searchValue, maxResultCount);
return items;
});
}
Expand Down
Loading