Skip to content

Commit 551a532

Browse files
committed
Merge pull request #430 from Microsoft/outlining
enable outlining atop of new compiler
2 parents 7bf846a + a409ec9 commit 551a532

File tree

5 files changed

+232
-94
lines changed

5 files changed

+232
-94
lines changed

src/harness/fourslash.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1419,8 +1419,8 @@ module FourSlash {
14191419
for (var i = 0; i < spans.length; i++) {
14201420
var expectedSpan = spans[i];
14211421
var actualSpan = actual[i];
1422-
if (expectedSpan.start !== actualSpan.start() || expectedSpan.end !== actualSpan.end()) {
1423-
throw new Error('verifyOutliningSpans failed - span ' + (i + 1) + ' expected: (' + expectedSpan.start + ',' + expectedSpan.end + '), actual: (' + actualSpan.start() + ',' + actualSpan.end() + ')');
1422+
if (expectedSpan.start !== actualSpan.textSpan.start() || expectedSpan.end !== actualSpan.textSpan.end()) {
1423+
throw new Error('verifyOutliningSpans failed - span ' + (i + 1) + ' expected: (' + expectedSpan.start + ',' + expectedSpan.end + '), actual: (' + actualSpan.textSpan.start() + ',' + actualSpan.textSpan.end() + ')');
14241424
}
14251425
}
14261426
}

src/services/outliningElementsCollector.ts

Lines changed: 65 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -15,95 +15,75 @@
1515

1616
///<reference path='references.ts' />
1717

18-
module TypeScript.Services {
19-
export class OutliningElementsCollector extends TypeScript.DepthLimitedWalker {
20-
// The maximum depth for collecting spans; this will cause us to miss deeply nested function/modules spans,
21-
// but will guarantee performance will not be closely tied to tree depth.
22-
private static MaximumDepth: number = 10;
23-
private inObjectLiteralExpression: boolean = false;
24-
25-
private elements: TypeScript.TextSpan[] = [];
26-
27-
constructor() {
28-
super(OutliningElementsCollector.MaximumDepth);
29-
}
30-
31-
public visitClassDeclaration(node: TypeScript.ClassDeclarationSyntax): void {
32-
this.addOutlineRange(node, node.openBraceToken, node.closeBraceToken);
33-
super.visitClassDeclaration(node);
34-
}
35-
36-
public visitInterfaceDeclaration(node: TypeScript.InterfaceDeclarationSyntax): void {
37-
this.addOutlineRange(node, node.body.openBraceToken, node.body.closeBraceToken);
38-
super.visitInterfaceDeclaration(node);
39-
}
40-
41-
public visitModuleDeclaration(node: TypeScript.ModuleDeclarationSyntax): void {
42-
this.addOutlineRange(node, node.openBraceToken, node.closeBraceToken);
43-
super.visitModuleDeclaration(node);
44-
}
45-
46-
public visitEnumDeclaration(node: TypeScript.EnumDeclarationSyntax): void {
47-
this.addOutlineRange(node, node.openBraceToken, node.closeBraceToken);
48-
super.visitEnumDeclaration(node);
49-
}
50-
51-
public visitFunctionDeclaration(node: TypeScript.FunctionDeclarationSyntax): void {
52-
this.addOutlineRange(node, node.block, node.block);
53-
super.visitFunctionDeclaration(node);
54-
}
55-
56-
public visitFunctionExpression(node: TypeScript.FunctionExpressionSyntax): void {
57-
this.addOutlineRange(node, node.block, node.block);
58-
super.visitFunctionExpression(node);
59-
}
60-
61-
public visitConstructorDeclaration(node: TypeScript.ConstructorDeclarationSyntax): void {
62-
this.addOutlineRange(node, node.block, node.block);
63-
super.visitConstructorDeclaration(node);
64-
}
65-
66-
public visitMemberFunctionDeclaration(node: TypeScript.MemberFunctionDeclarationSyntax): void {
67-
this.addOutlineRange(node, node.block, node.block);
68-
super.visitMemberFunctionDeclaration(node);
69-
}
70-
71-
public visitGetAccessor(node: TypeScript.GetAccessorSyntax): void {
72-
if (!this.inObjectLiteralExpression) {
73-
this.addOutlineRange(node, node.block, node.block);
74-
}
75-
super.visitGetAccessor(node);
76-
}
18+
module ts {
19+
20+
export interface OutliningSpan {
21+
/**
22+
* @param textSpan The span of the document to actually collapse.
23+
* @param hintSpan The span of the document to display when the user hovers over the
24+
* collapsed span.
25+
* @param bannerText The text to display in the editor for the collapsed region.
26+
* @param autoCollapse Whether or not this region should be automatically collapsed when
27+
* the 'Collapse to Definitions' command is invoked.
28+
*/
29+
textSpan: TypeScript.TextSpan;
30+
hintSpan: TypeScript.TextSpan;
31+
bannerText: string;
32+
autoCollapse: boolean;
33+
}
7734

78-
public visitSetAccessor(node: TypeScript.SetAccessorSyntax): void {
79-
if (!this.inObjectLiteralExpression) {
80-
this.addOutlineRange(node, node.block, node.block);
35+
export module OutliningElementsCollector {
36+
export function collectElements(sourceFile: SourceFile): OutliningSpan[] {
37+
var elements: OutliningSpan[] = [];
38+
39+
function addOutlineRange(node: Node, startElement: Node, endElement: Node) {
40+
if (node && startElement && endElement) {
41+
var span: OutliningSpan = {
42+
textSpan: TypeScript.TextSpan.fromBounds(startElement.pos, endElement.end),
43+
hintSpan: TypeScript.TextSpan.fromBounds(node.getStart(), node.end),
44+
bannerText: "...",
45+
autoCollapse: false
46+
};
47+
elements.push(span);
48+
}
8149
}
82-
super.visitSetAccessor(node);
83-
}
8450

85-
public visitObjectLiteralExpression(node: TypeScript.ObjectLiteralExpressionSyntax): void {
86-
var savedInObjectLiteralExpression = this.inObjectLiteralExpression;
87-
this.inObjectLiteralExpression = true;
88-
super.visitObjectLiteralExpression(node);
89-
this.inObjectLiteralExpression = savedInObjectLiteralExpression;
90-
}
91-
92-
private addOutlineRange(node: TypeScript.ISyntaxNode, startElement: TypeScript.ISyntaxNodeOrToken, endElement: TypeScript.ISyntaxNodeOrToken) {
93-
if (startElement && endElement && !isShared(startElement) && !isShared(endElement)) {
94-
// Compute the position
95-
var start = TypeScript.start(startElement);
96-
var end = TypeScript.end(endElement);
97-
98-
// Push the new range
99-
this.elements.push(TypeScript.TextSpan.fromBounds(start, end));
51+
var depth = 0;
52+
var maxDepth = 10;
53+
function walk(n: Node): void {
54+
if (depth >= maxDepth) {
55+
return;
56+
}
57+
switch (n.kind) {
58+
case SyntaxKind.ClassDeclaration:
59+
case SyntaxKind.InterfaceDeclaration:
60+
case SyntaxKind.ModuleDeclaration:
61+
case SyntaxKind.EnumDeclaration:
62+
case SyntaxKind.ObjectLiteral:
63+
var openBrace = forEach(n.getChildren(), c => c.kind === SyntaxKind.OpenBraceToken && c);
64+
var closeBrace = forEach(n.getChildren(), c => c.kind === SyntaxKind.CloseBraceToken && c);
65+
addOutlineRange(n, openBrace, closeBrace);
66+
break;
67+
case SyntaxKind.Constructor:
68+
case SyntaxKind.FunctionDeclaration:
69+
case SyntaxKind.Method:
70+
case SyntaxKind.GetAccessor:
71+
case SyntaxKind.SetAccessor:
72+
var body = (<FunctionDeclaration>n).body;
73+
if (body) {
74+
var openBrace = forEach(body.getChildren(), c => c.kind === SyntaxKind.OpenBraceToken && c);
75+
var closeBrace = forEach(body.getChildren(), c => c.kind === SyntaxKind.CloseBraceToken && c);
76+
addOutlineRange(n, openBrace, closeBrace);
77+
}
78+
break;
79+
}
80+
depth++;
81+
forEachChild(n, walk);
82+
depth--;
10083
}
101-
}
10284

103-
public static collectElements(node: TypeScript.SourceUnitSyntax): TypeScript.TextSpan[] {
104-
var collector = new OutliningElementsCollector();
105-
visitNodeOrToken(collector, node);
106-
return collector.elements;
85+
walk(sourceFile);
86+
return elements;
10787
}
10888
}
109-
}
89+
}

src/services/services.ts

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ module ts {
482482
getNavigateToItems(searchValue: string): NavigateToItem[];
483483
getScriptLexicalStructure(fileName: string): NavigateToItem[];
484484

485-
getOutliningRegions(fileName: string): TypeScript.TextSpan[];
485+
getOutliningRegions(fileName: string): OutliningSpan[];
486486
getBraceMatchingAtPosition(fileName: string, position: number): TypeScript.TextSpan[];
487487
getIndentationAtPosition(fileName: string, position: number, options: EditorOptions): number;
488488

@@ -962,39 +962,79 @@ module ts {
962962
// currently edited file.
963963
private currentfilename: string = "";
964964
private currentFileVersion: number = -1;
965+
private currentSourceFile: SourceFile = null;
965966
private currentFileSyntaxTree: TypeScript.SyntaxTree = null;
966967
private currentFileScriptSnapshot: TypeScript.IScriptSnapshot = null;
967968

968969
constructor(private host: LanguageServiceHost) {
969970
this.hostCache = new HostCache(host);
970971
}
971972

972-
public getCurrentFileSyntaxTree(filename: string): TypeScript.SyntaxTree {
973+
private initialize(filename: string) {
974+
// ensure that both source file and syntax tree are either initialized or not initialized
975+
Debug.assert(!!this.currentFileSyntaxTree === !!this.currentSourceFile);
973976
this.hostCache = new HostCache(this.host);
974977

975978
var version = this.hostCache.getVersion(filename);
976979
var syntaxTree: TypeScript.SyntaxTree = null;
980+
var sourceFile: SourceFile;
977981

978982
if (this.currentFileSyntaxTree === null || this.currentfilename !== filename) {
979983
var scriptSnapshot = this.hostCache.getScriptSnapshot(filename);
980984
syntaxTree = this.createSyntaxTree(filename, scriptSnapshot);
985+
sourceFile = createSourceFileFromScriptSnapshot(filename, scriptSnapshot, getDefaultCompilerOptions(), version, /*isOpen*/ true);
986+
987+
fixupParentReferences(sourceFile);
981988
}
982989
else if (this.currentFileVersion !== version) {
983990
var scriptSnapshot = this.hostCache.getScriptSnapshot(filename);
984991
syntaxTree = this.updateSyntaxTree(filename, scriptSnapshot, this.currentFileSyntaxTree, this.currentFileVersion);
992+
993+
var editRange = this.hostCache.getScriptTextChangeRangeSinceVersion(filename, this.currentFileVersion);
994+
sourceFile = !editRange
995+
? createSourceFileFromScriptSnapshot(filename, scriptSnapshot, getDefaultCompilerOptions(), version, /*isOpen*/ true)
996+
: this.currentSourceFile.update(scriptSnapshot, version, /*isOpen*/ true, editRange);
997+
998+
fixupParentReferences(sourceFile);
985999
}
9861000

9871001
if (syntaxTree !== null) {
1002+
Debug.assert(sourceFile);
9881003
// All done, ensure state is up to date
9891004
this.currentFileScriptSnapshot = scriptSnapshot;
9901005
this.currentFileVersion = version;
9911006
this.currentfilename = filename;
9921007
this.currentFileSyntaxTree = syntaxTree;
1008+
this.currentSourceFile = sourceFile;
9931009
}
9941010

1011+
function fixupParentReferences(sourceFile: SourceFile) {
1012+
// normally parent references are set during binding.
1013+
// however here SourceFile data is used only for syntactic features so running the whole binding process is an overhead.
1014+
// walk over the nodes and set parent references
1015+
var parent: Node = sourceFile;
1016+
function walk(n: Node): void {
1017+
n.parent = parent;
1018+
1019+
var saveParent = parent;
1020+
parent = n;
1021+
forEachChild(n, walk);
1022+
parent = saveParent;
1023+
}
1024+
forEachChild(sourceFile, walk);
1025+
}
1026+
}
1027+
1028+
public getCurrentFileSyntaxTree(filename: string): TypeScript.SyntaxTree {
1029+
this.initialize(filename);
9951030
return this.currentFileSyntaxTree;
9961031
}
9971032

1033+
public getCurrentSourceFile(filename: string): SourceFile {
1034+
this.initialize(filename);
1035+
return this.currentSourceFile;
1036+
}
1037+
9981038
public getCurrentScriptSnapshot(filename: string): TypeScript.IScriptSnapshot {
9991039
// update currentFileScriptSnapshot as a part of 'getCurrentFileSyntaxTree' call
10001040
this.getCurrentFileSyntaxTree(filename);
@@ -1093,6 +1133,10 @@ module ts {
10931133
}
10941134
}
10951135

1136+
function createSourceFileFromScriptSnapshot(filename: string, scriptSnapshot: TypeScript.IScriptSnapshot, settings: CompilerOptions, version: number, isOpen: boolean) {
1137+
return createSourceFile(filename, scriptSnapshot.getText(0, scriptSnapshot.getLength()), settings.target, version, isOpen);
1138+
}
1139+
10961140
export function createDocumentRegistry(): DocumentRegistry {
10971141
var buckets: Map<Map<DocumentRegistryEntry>> = {};
10981142

@@ -1140,7 +1184,7 @@ module ts {
11401184
var bucket = getBucketForCompilationSettings(compilationSettings, /*createIfMissing*/ true);
11411185
var entry = lookUp(bucket, filename);
11421186
if (!entry) {
1143-
var sourceFile = createSourceFile(filename, scriptSnapshot.getText(0, scriptSnapshot.getLength()), compilationSettings.target, version, isOpen);
1187+
var sourceFile = createSourceFileFromScriptSnapshot(filename, scriptSnapshot, compilationSettings, version, isOpen);
11441188

11451189
bucket[filename] = entry = {
11461190
sourceFile: sourceFile,
@@ -2024,6 +2068,12 @@ module ts {
20242068
return syntaxTreeCache.getCurrentFileSyntaxTree(filename);
20252069
}
20262070

2071+
function getCurrentSourceFile(filename: string): SourceFile {
2072+
filename = TypeScript.switchToForwardSlashes(filename);
2073+
var currentSourceFile = syntaxTreeCache.getCurrentSourceFile(filename);
2074+
return currentSourceFile;
2075+
}
2076+
20272077
function getNameOrDottedNameSpan(filename: string, startPos: number, endPos: number): SpanInfo {
20282078
function getTypeInfoEligiblePath(filename: string, position: number, isConstructorValidPosition: boolean) {
20292079
var sourceUnit = syntaxTreeCache.getCurrentFileSyntaxTree(filename).sourceUnit();
@@ -2097,11 +2147,11 @@ module ts {
20972147
return items;
20982148
}
20992149

2100-
function getOutliningRegions(filename: string) {
2150+
function getOutliningRegions(filename: string): OutliningSpan[] {
21012151
// doesn't use compiler - no need to synchronize with host
21022152
filename = TypeScript.switchToForwardSlashes(filename);
2103-
var syntaxTree = getSyntaxTree(filename);
2104-
return TypeScript.Services.OutliningElementsCollector.collectElements(syntaxTree.sourceUnit());
2153+
var sourceFile = getCurrentSourceFile(filename);
2154+
return OutliningElementsCollector.collectElements(sourceFile);
21052155
}
21062156

21072157
function getBraceMatchingAtPosition(filename: string, position: number) {

src/services/shims.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -726,7 +726,10 @@ module ts {
726726
"getOutliningRegions(\"" + fileName + "\")",
727727
() => {
728728
var items = this.languageService.getOutliningRegions(fileName);
729-
return items;
729+
// return just the part of data that language service v2 can understand
730+
// language service v2 will use the entire OutliningSpan
731+
var spans = forEach(items, i => i.textSpan);
732+
return spans;
730733
});
731734
}
732735

0 commit comments

Comments
 (0)