diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index fd91c7c70ec..26f86a02c38 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -732,22 +732,22 @@ module FourSlash { this.taoInvalidReason = 'verifyCurrentSignatureHelpIs NYI'; var help = this.getActiveSignatureHelp(); - assert.equal(help.signatureInfo, expected); + assert.equal(help.prefix + help.parameters.map(p => p.display).join(help.separator) + help.suffix, expected); } public verifyCurrentParameterIsVariable(isVariable: boolean) { this.taoInvalidReason = 'verifyCurrentParameterIsVariable NYI'; - var activeParameter = this.getActiveParameter(); - assert.isNotNull(activeParameter.parameter); - assert.equal(isVariable, activeParameter.parameter.isVariable); + var signature = this.getActiveSignatureHelp(); + assert.isNotNull(signature); + assert.equal(isVariable, signature.isVariadic); } public verifyCurrentParameterHelpName(name: string) { this.taoInvalidReason = 'verifyCurrentParameterHelpName NYI'; var activeParameter = this.getActiveParameter(); - var activeParameterName = activeParameter.parameter ? activeParameter.parameter.name : activeParameter.typeParameter.name; + var activeParameterName = activeParameter.name; assert.equal(activeParameterName, name); } @@ -756,16 +756,14 @@ module FourSlash { var activeSignature = this.getActiveSignatureHelp(); var activeParameter = this.getActiveParameter(); - var activeParameterMinChar = activeParameter.parameter ? activeParameter.parameter.minChar : activeParameter.typeParameter.minChar; - var activeParameterLimChar = activeParameter.parameter ? activeParameter.parameter.limChar : activeParameter.typeParameter.limChar; - assert.equal(activeSignature.signatureInfo.substring(activeParameterMinChar, activeParameterLimChar), parameter); + assert.equal(activeParameter.display, parameter); } public verifyCurrentParameterHelpDocComment(docComment: string) { this.taoInvalidReason = 'verifyCurrentParameterHelpDocComment NYI'; var activeParameter = this.getActiveParameter(); - var activeParameterDocComment = activeParameter.parameter ? activeParameter.parameter.docComment : activeParameter.typeParameter.docComment; + var activeParameterDocComment = activeParameter.documentation; assert.equal(activeParameterDocComment, docComment); } @@ -778,13 +776,13 @@ module FourSlash { public verifyCurrentSignatureHelpTypeParameterCount(expectedCount: number) { this.taoInvalidReason = 'verifyCurrentSignatureHelpTypeParameterCount NYI'; - assert.equal(this.getActiveSignatureHelp().typeParameters.length, expectedCount); + // assert.equal(this.getActiveSignatureHelp().typeParameters.length, expectedCount); } public verifyCurrentSignatureHelpDocComment(docComment: string) { this.taoInvalidReason = 'verifyCurrentSignatureHelpDocComment NYI'; - var actualDocComment = this.getActiveSignatureHelp().docComment; + var actualDocComment = this.getActiveSignatureHelp().documentation; assert.equal(actualDocComment, docComment); } @@ -792,15 +790,15 @@ module FourSlash { this.scenarioActions.push(''); this.scenarioActions.push(''); - var help = this.languageService.getSignatureAtPosition(this.activeFile.fileName, this.currentCaretPosition); - var actual = help && help.formal ? help.formal.length : 0; + var help = this.languageService.getSignatureHelpItems(this.activeFile.fileName, this.currentCaretPosition); + var actual = help && help.items ? help.items.length : 0; assert.equal(actual, expected); } public verifySignatureHelpPresent(shouldBePresent = true) { this.taoInvalidReason = 'verifySignatureHelpPresent NYI'; - var actual = this.languageService.getSignatureAtPosition(this.activeFile.fileName, this.currentCaretPosition); + var actual = this.languageService.getSignatureHelpItems(this.activeFile.fileName, this.currentCaretPosition); if (shouldBePresent) { if (!actual) { throw new Error("Expected signature help to be present, but it wasn't"); @@ -812,45 +810,32 @@ module FourSlash { } } - private getFormalParameter() { - var help = this.languageService.getSignatureAtPosition(this.activeFile.fileName, this.currentCaretPosition); - return help.formal; - } + //private getFormalParameter() { + // var help = this.languageService.getSignatureHelpItems(this.activeFile.fileName, this.currentCaretPosition); + // return help.formal; + //} private getActiveSignatureHelp() { - var help = this.languageService.getSignatureAtPosition(this.activeFile.fileName, this.currentCaretPosition); - var activeFormal = help.activeFormal; + var help = this.languageService.getSignatureHelpItems(this.activeFile.fileName, this.currentCaretPosition); // If the signature hasn't been narrowed down yet (e.g. no parameters have yet been entered), // 'activeFormal' will be -1 (even if there is only 1 signature). Signature help will show the // first signature in the signature group, so go with that - if (activeFormal === -1) { - activeFormal = 0; - } + var index = help.selectedItemIndex < 0 ? 0 : help.selectedItemIndex; - return help.formal[activeFormal]; + return help.items[index]; } - private getActiveParameter(): { parameter: ts.FormalParameterInfo; typeParameter: ts.FormalTypeParameterInfo; } { + private getActiveParameter(): ts.SignatureHelpParameter { var currentSig = this.getActiveSignatureHelp(); - var help = this.languageService.getSignatureAtPosition(this.activeFile.fileName, this.currentCaretPosition); + var help = this.languageService.getSignatureHelpItems(this.activeFile.fileName, this.currentCaretPosition); + + var item = help.items[help.selectedItemIndex]; + var state = this.languageService.getSignatureHelpCurrentArgumentState(this.activeFile.fileName, this.currentCaretPosition, help.applicableSpan.start()); // Same logic as in getActiveSignatureHelp - this value might be -1 until a parameter value actually gets typed - var currentParam = help.actual.currentParameter; - if (currentParam === -1) currentParam = 0; - - if (help.actual.currentParameterIsTypeParameter) { - return { - parameter: null, - typeParameter: currentSig.typeParameters[currentParam] - }; - } - else { - return { - parameter: currentSig.parameters[currentParam], - typeParameter: null - }; - } + var currentParam = state === null ? 0 : state.argumentIndex; + return item.parameters[currentParam]; } public getBreakpointStatementLocation(pos: number) { @@ -859,7 +844,7 @@ module FourSlash { var spanInfo = this.languageService.getBreakpointStatementAtPosition(this.activeFile.fileName, pos); var resultString = "\n**Pos: " + pos + " SpanInfo: " + JSON.stringify(spanInfo) + "\n** Statement: "; if (spanInfo !== null) { - resultString = resultString + this.activeFile.content.substr(spanInfo.minChar, spanInfo.limChar - spanInfo.minChar); + resultString = resultString + this.activeFile.content.substr(spanInfo.start(), spanInfo.length()); } return resultString; } @@ -890,7 +875,7 @@ module FourSlash { } public printCurrentParameterHelp() { - var help = this.languageService.getSignatureAtPosition(this.activeFile.fileName, this.currentCaretPosition); + var help = this.languageService.getSignatureHelpItems(this.activeFile.fileName, this.currentCaretPosition); Harness.IO.log(JSON.stringify(help)); } @@ -1071,7 +1056,7 @@ module FourSlash { if (ch === '(' || ch === ',') { /* Signature help*/ - this.languageService.getSignatureAtPosition(this.activeFile.fileName, offset); + this.languageService.getSignatureHelpItems(this.activeFile.fileName, offset); } else if (prevChar === ' ' && /A-Za-z_/.test(ch)) { /* Completions */ this.languageService.getCompletionsAtPosition(this.activeFile.fileName, offset, false); @@ -1111,7 +1096,7 @@ module FourSlash { // Handle formatting if (this.enableFormatting) { - var edits = this.languageService.getFormattingEditsOnPaste(this.activeFile.fileName, start, offset, this.formatCodeOptions); + var edits = this.languageService.getFormattingEditsForRange(this.activeFile.fileName, start, offset, this.formatCodeOptions); offset += this.applyEdits(this.activeFile.fileName, edits, true); this.editCheckpoint(this.activeFile.fileName); } @@ -1175,18 +1160,18 @@ module FourSlash { }; } - private applyEdits(fileName: string, edits: ts.TextEdit[], isFormattingEdit = false): number { + private applyEdits(fileName: string, edits: ts.TextChange[], isFormattingEdit = false): number { // We get back a set of edits, but langSvc.editScript only accepts one at a time. Use this to keep track // of the incremental offest from each edit to the next. Assumption is that these edit ranges don't overlap var runningOffset = 0; - edits = edits.sort((a, b) => a.minChar - b.minChar); + edits = edits.sort((a, b) => a.span.start() - b.span.start()); // Get a snapshot of the content of the file so we can make sure any formatting edits didn't destroy non-whitespace characters var snapshot = this.languageServiceShimHost.getScriptSnapshot(fileName); var oldContent = snapshot.getText(0, snapshot.getLength()); for (var j = 0; j < edits.length; j++) { - this.languageServiceShimHost.editScript(fileName, edits[j].minChar + runningOffset, edits[j].limChar + runningOffset, edits[j].text); - this.updateMarkersForEdit(fileName, edits[j].minChar + runningOffset, edits[j].limChar + runningOffset, edits[j].text); - var change = (edits[j].minChar - edits[j].limChar) + edits[j].text.length; + this.languageServiceShimHost.editScript(fileName, edits[j].span.start() + runningOffset, edits[j].span.end() + runningOffset, edits[j].newText); + this.updateMarkersForEdit(fileName, edits[j].span.start() + runningOffset, edits[j].span.end() + runningOffset, edits[j].newText); + var change = (edits[j].span.start() - edits[j].span.end()) + edits[j].newText.length; runningOffset += change; // TODO: Consider doing this at least some of the time for higher fidelity. Currently causes a failure (bug 707150) // this.languageService.getScriptLexicalStructure(fileName); @@ -1206,7 +1191,7 @@ module FourSlash { public formatDocument() { this.scenarioActions.push(''); - var edits = this.languageService.getFormattingEditsForDocument(this.activeFile.fileName, 0, this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName).getLength(), this.formatCodeOptions); + var edits = this.languageService.getFormattingEditsForDocument(this.activeFile.fileName, this.formatCodeOptions); this.currentCaretPosition += this.applyEdits(this.activeFile.fileName, edits, true); this.fixCaretPosition(); } @@ -1263,7 +1248,7 @@ module FourSlash { var definition = definitions[definitionIndex]; this.openFile(definition.fileName); - this.currentCaretPosition = definition.minChar; + this.currentCaretPosition = definition.textSpan.start(); } public verifyDefinitionLocationExists(negative: boolean) { @@ -1369,7 +1354,7 @@ module FourSlash { '\t Actual: null'); } - var actual = this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName).getText(span.minChar, span.limChar); + var actual = this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName).getText(span.start(), span.end()); if (actual !== text) { throw new Error('verifyCurrentNameOrDottedNameSpanText\n' + '\tExpected: "' + text + '"\n' + @@ -1381,7 +1366,7 @@ module FourSlash { var spanInfo = this.languageService.getNameOrDottedNameSpan(this.activeFile.fileName, pos, pos); var resultString = "\n**Pos: " + pos + " SpanInfo: " + JSON.stringify(spanInfo) + "\n** Statement: "; if (spanInfo !== null) { - resultString = resultString + this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName).getText(spanInfo.minChar, spanInfo.limChar); + resultString = resultString + this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName).getText(spanInfo.start(), spanInfo.end()); } return resultString; } @@ -1410,7 +1395,7 @@ module FourSlash { public verifyOutliningSpans(spans: TextSpan[]) { this.taoInvalidReason = 'verifyOutliningSpans NYI'; - var actual = this.languageService.getOutliningRegions(this.activeFile.fileName); + var actual = this.languageService.getOutliningSpans(this.activeFile.fileName); if (actual.length !== spans.length) { throw new Error('verifyOutliningSpans failed - expected total spans to be ' + spans.length + ', but was ' + actual.length); @@ -1584,70 +1569,61 @@ module FourSlash { public verifyGetScriptLexicalStructureListCount(expected: number) { this.taoInvalidReason = 'verifyNavigationItemsListContains impossible'; - var items = this.languageService.getScriptLexicalStructure(this.activeFile.fileName); - var actual = (items && items.length) || 0; + var items = this.languageService.getNavigationBarItems(this.activeFile.fileName); + var actual = this.getNavigationBarItemsCount(items); + if (expected != actual) { throw new Error('verifyGetScriptLexicalStructureListCount failed - found: ' + actual + ' navigation items, expected: ' + expected + '.'); } } + private getNavigationBarItemsCount(items: ts.NavigationBarItem[]) { + var result = 0; + if (items) { + for (var i = 0, n = items.length; i < n; i++) { + result++; + result += this.getNavigationBarItemsCount(items[i].childItems); + } + } + + return result; + } + public verifGetScriptLexicalStructureListContains( name: string, kind: string, - fileName?: string, - parentName?: string, - isAdditionalSpan?: boolean, markerPosition?: number) { this.taoInvalidReason = 'verifGetScriptLexicalStructureListContains impossible'; - var items = this.languageService.getScriptLexicalStructure(this.activeFile.fileName); + var items = this.languageService.getNavigationBarItems(this.activeFile.fileName); if (!items || items.length === 0) { throw new Error('verifyGetScriptLexicalStructureListContains failed - found 0 navigation items, expected at least one.'); } - for (var i = 0; i < items.length; i++) { - var item = items[i]; - if (item && item.name === name && item.kind === kind && - (fileName === undefined || item.fileName === fileName) && - (parentName === undefined || item.containerName === parentName)) { - if (markerPosition !== undefined || isAdditionalSpan !== undefined) { - if (isAdditionalSpan) { - if (item.additionalSpans && - item.additionalSpans.some(span => span.minChar <= markerPosition && markerPosition <= span.limChar)) { - // marker is in an additional span for this item. - return; - } - else { - throw new Error( - 'verifGetScriptLexicalStructureListContains failed - ' + - 'no additional span was found that contained the position: ' + JSON.stringify(markerPosition) + - ' in the item: ' + JSON.stringify(item)); - } - } - else if (!isAdditionalSpan) { - if (item.minChar <= markerPosition && - markerPosition <= item.minChar) { - // marker is in span normal item's span - return; - } - else { - throw new Error( - 'verifGetScriptLexicalStructureListContains failed - ' + - 'marker was positioned: ' + JSON.stringify(markerPosition) + - ' which is not in the item: ' + JSON.stringify(item)); - } - } + if (this.navigationBarItemsContains(items, name, kind)) { + return; + } + + var missingItem = { name: name, kind: kind }; + throw new Error('verifyGetScriptLexicalStructureListContains failed - could not find the item: ' + JSON.stringify(missingItem) + ' in the returned list: (' + JSON.stringify(items) + ')'); + } + + private navigationBarItemsContains(items: ts.NavigationBarItem[], name: string, kind: string) { + if (items) { + for (var i = 0; i < items.length; i++) { + var item = items[i]; + if (item && item.text === name && item.kind === kind) { + return true; } - else { - return; + + if (this.navigationBarItemsContains(item.childItems, name, kind)) { + return true; } } } - - var missingItem = { name: name, kind: kind, fileName: fileName, parentName: parentName }; - throw new Error('verifyGetScriptLexicalStructureListContains failed - could not find the item: ' + JSON.stringify(missingItem) + ' in the returned list: (' + JSON.stringify(items) + ')'); + return false; } public printNavigationItems(searchValue: string) { @@ -1663,14 +1639,14 @@ module FourSlash { } public printScriptLexicalStructureItems() { - var items = this.languageService.getScriptLexicalStructure(this.activeFile.fileName); + var items = this.languageService.getNavigationBarItems(this.activeFile.fileName); var length = items && items.length; Harness.IO.log('NavigationItems list (' + length + ' items)'); for (var i = 0; i < length; i++) { var item = items[i]; - Harness.IO.log('name: ' + item.name + ', kind: ' + item.kind + ', parentName: ' + item.containerName + ', fileName: ' + item.fileName); + Harness.IO.log('name: ' + item.text + ', kind: ' + item.kind); } } @@ -1689,7 +1665,7 @@ module FourSlash { for (var i = 0; i < occurances.length; i++) { var occurance = occurances[i]; - if (occurance && occurance.fileName === fileName && occurance.minChar === start && occurance.limChar === end) { + if (occurance && occurance.fileName === fileName && occurance.textSpan.start() === start && occurance.textSpan.end() === end) { if (typeof isWriteAccess !== "undefined" && occurance.isWriteAccess !== isWriteAccess) { throw new Error('verifyOccurancesAtPositionListContains failed - item isWriteAccess value doe not match, actual: ' + occurance.isWriteAccess + ', expected: ' + isWriteAccess + '.'); } diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 0f904b24a17..48f2f9b9653 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -281,7 +281,7 @@ module Harness.LanguageService { } /** Verify that applying edits to sourceFileName result in the content of the file baselineFileName */ - public checkEdits(sourceFileName: string, baselineFileName: string, edits: ts.TextEdit[]) { + public checkEdits(sourceFileName: string, baselineFileName: string, edits: ts.TextChange[]) { var script = Harness.IO.readFile(sourceFileName); var formattedScript = this.applyEdits(script, edits); var baseline = Harness.IO.readFile(baselineFileName); @@ -310,26 +310,26 @@ module Harness.LanguageService { /** Apply an array of text edits to a string, and return the resulting string. */ - public applyEdits(content: string, edits: ts.TextEdit[]): string { + public applyEdits(content: string, edits: ts.TextChange[]): string { var result = content; edits = this.normalizeEdits(edits); for (var i = edits.length - 1; i >= 0; i--) { var edit = edits[i]; - var prefix = result.substring(0, edit.minChar); - var middle = edit.text; - var suffix = result.substring(edit.limChar); + var prefix = result.substring(0, edit.span.start()); + var middle = edit.newText; + var suffix = result.substring(edit.span.end()); result = prefix + middle + suffix; } return result; } /** Normalize an array of edits by removing overlapping entries and sorting entries on the minChar position. */ - private normalizeEdits(edits: ts.TextEdit[]): ts.TextEdit[] { - var result: ts.TextEdit[] = []; + private normalizeEdits(edits: ts.TextChange[]): ts.TextChange[] { + var result: ts.TextChange[] = []; - function mapEdits(edits: ts.TextEdit[]): { edit: ts.TextEdit; index: number; }[] { - var result: { edit: ts.TextEdit; index: number; }[] = []; + function mapEdits(edits: ts.TextChange[]): { edit: ts.TextChange; index: number; }[] { + var result: { edit: ts.TextChange; index: number; }[] = []; for (var i = 0; i < edits.length; i++) { result.push({ edit: edits[i], index: i }); } @@ -337,7 +337,7 @@ module Harness.LanguageService { } var temp = mapEdits(edits).sort(function (a, b) { - var result = a.edit.minChar - b.edit.limChar; + var result = a.edit.span.start() - b.edit.span.start(); if (result === 0) result = a.index - b.index; return result; @@ -356,7 +356,7 @@ module Harness.LanguageService { } var nextEdit = temp[next].edit; - var gap = nextEdit.minChar - currentEdit.limChar; + var gap = nextEdit.span.start() - currentEdit.span.end(); // non-overlapping edits if (gap >= 0) { @@ -365,10 +365,10 @@ module Harness.LanguageService { next++; continue; } - + // overlapping edits: for now, we only support ignoring an next edit // entirely contained in the current edit. - if (currentEdit.minChar >= nextEdit.limChar) { + if (currentEdit.span.end() >= nextEdit.span.end()) { next++; continue; } diff --git a/src/services/braceMatcher.ts b/src/services/braceMatcher.ts index 96550dab2e7..3615f677024 100644 --- a/src/services/braceMatcher.ts +++ b/src/services/braceMatcher.ts @@ -24,93 +24,50 @@ module TypeScript.Services { public static getMatchSpans(syntaxTree: TypeScript.SyntaxTree, position: number): TypeScript.TextSpan[] { var result: TypeScript.TextSpan[] = []; - var currentToken = findToken(syntaxTree.sourceUnit(), position); + var token = findToken(syntaxTree.sourceUnit(), position); - BraceMatcher.getMatchingCloseBrace(currentToken, position, result); - BraceMatcher.getMatchingOpenBrace(currentToken, position, result); + if (start(token) === position) { + var matchKind = BraceMatcher.getMatchingTokenKind(token); + + if (matchKind !== null) { + var parentElement = token.parent; + + for (var i = 0, n = childCount(parentElement); i < n; i++) { + var current = childAt(parentElement, i); + + if (current !== null && fullWidth(current) > 0) { + if (current.kind() === matchKind) { + var range1 = new TypeScript.TextSpan(start(token), width(token)); + var range2 = new TypeScript.TextSpan(start(current), width(current)); + if (range1.start() < range2.start()) { + result.push(range1, range2); + } + else { + result.push(range2, range1); + } + break; + } + } + } + } + } return result; } - private static getMatchingCloseBrace(currentToken: TypeScript.ISyntaxToken, position: number, result: TypeScript.TextSpan[]) { - if (start(currentToken) === position) { - var closingBraceKind = BraceMatcher.getMatchingCloseBraceTokenKind(currentToken); - if (closingBraceKind !== null) { - var parentElement = currentToken.parent; - var currentPosition = fullStart(currentToken.parent); - for (var i = 0, n = childCount(parentElement); i < n; i++) { - var element = childAt(parentElement, i); - if (element !== null && fullWidth(element) > 0) { - if (element.kind() === closingBraceKind) { - var range1 = new TypeScript.TextSpan(position, width(currentToken)); - var range2 = new TypeScript.TextSpan(currentPosition + leadingTriviaWidth(element), width(element)); - result.push(range1, range2); - break; - } - - currentPosition += fullWidth(element); - } - } - } - } - } - - private static getMatchingOpenBrace(currentToken: TypeScript.ISyntaxToken, position: number, result: TypeScript.TextSpan[]) { - // Check if the current token to the left is a close brace - if (currentToken.fullStart() === position) { - currentToken = previousToken(currentToken); + private static getMatchingTokenKind(token: TypeScript.ISyntaxToken): TypeScript.SyntaxKind { + switch (token.kind()) { + case TypeScript.SyntaxKind.OpenBraceToken: return TypeScript.SyntaxKind.CloseBraceToken + case TypeScript.SyntaxKind.OpenParenToken: return TypeScript.SyntaxKind.CloseParenToken; + case TypeScript.SyntaxKind.OpenBracketToken: return TypeScript.SyntaxKind.CloseBracketToken; + case TypeScript.SyntaxKind.LessThanToken: return TypeScript.SyntaxKind.GreaterThanToken; + case TypeScript.SyntaxKind.CloseBraceToken: return TypeScript.SyntaxKind.OpenBraceToken + case TypeScript.SyntaxKind.CloseParenToken: return TypeScript.SyntaxKind.OpenParenToken; + case TypeScript.SyntaxKind.CloseBracketToken: return TypeScript.SyntaxKind.OpenBracketToken; + case TypeScript.SyntaxKind.GreaterThanToken: return TypeScript.SyntaxKind.LessThanToken; } - if (currentToken !== null && start(currentToken) === (position - 1)) { - var openBraceKind = BraceMatcher.getMatchingOpenBraceTokenKind(currentToken); - if (openBraceKind !== null) { - var parentElement = currentToken.parent; - var currentPosition = fullStart(currentToken.parent) + fullWidth(parentElement); - for (var i = childCount(parentElement) - 1 ; i >= 0; i--) { - var element = childAt(parentElement, i); - if (element !== null && fullWidth(element) > 0) { - if (element.kind() === openBraceKind) { - var range1 = new TypeScript.TextSpan(position - 1, width(currentToken)); - var range2 = new TypeScript.TextSpan(currentPosition - lastToken(element).trailingTriviaWidth() - width(element), width(element)); - result.push(range1, range2); - break; - } - - currentPosition -= fullWidth(element); - } - } - } - } - } - - private static getMatchingCloseBraceTokenKind(positionedElement: TypeScript.ISyntaxElement): TypeScript.SyntaxKind { - var element = positionedElement !== null && positionedElement; - switch (element.kind()) { - case TypeScript.SyntaxKind.OpenBraceToken: - return TypeScript.SyntaxKind.CloseBraceToken; - case TypeScript.SyntaxKind.OpenParenToken: - return TypeScript.SyntaxKind.CloseParenToken; - case TypeScript.SyntaxKind.OpenBracketToken: - return TypeScript.SyntaxKind.CloseBracketToken; - case TypeScript.SyntaxKind.LessThanToken: - return TypeScript.SyntaxUtilities.isAngleBracket(positionedElement) ? TypeScript.SyntaxKind.GreaterThanToken : null; - } - return null; - } - - private static getMatchingOpenBraceTokenKind(positionedElement: TypeScript.ISyntaxElement): TypeScript.SyntaxKind { - var element = positionedElement !== null && positionedElement; - switch (element.kind()) { - case TypeScript.SyntaxKind.CloseBraceToken: - return TypeScript.SyntaxKind.OpenBraceToken - case TypeScript.SyntaxKind.CloseParenToken: - return TypeScript.SyntaxKind.OpenParenToken; - case TypeScript.SyntaxKind.CloseBracketToken: - return TypeScript.SyntaxKind.OpenBracketToken; - case TypeScript.SyntaxKind.GreaterThanToken: - return TypeScript.SyntaxUtilities.isAngleBracket(positionedElement) ? TypeScript.SyntaxKind.LessThanToken : null; - } return null; } } -} +} \ No newline at end of file diff --git a/src/services/breakpoints.ts b/src/services/breakpoints.ts index 5dd057ac2af..12de22d3bd0 100644 --- a/src/services/breakpoints.ts +++ b/src/services/breakpoints.ts @@ -4,16 +4,13 @@ /// module TypeScript.Services.Breakpoints { - function createBreakpointSpanInfo(parentElement: TypeScript.ISyntaxElement, ...childElements: TypeScript.ISyntaxElement[]): ts.SpanInfo { + function createBreakpointSpanInfo(parentElement: TypeScript.ISyntaxElement, ...childElements: TypeScript.ISyntaxElement[]): TextSpan { if (!parentElement) { return null; } if (childElements.length == 0) { - return { - minChar: TypeScript.start(parentElement), - limChar: TypeScript.end(parentElement) - }; + return TextSpan.fromBounds(TypeScript.start(parentElement), TypeScript.end(parentElement)); } var start: number; @@ -28,24 +25,18 @@ module TypeScript.Services.Breakpoints { } } - return { - minChar: start, - limChar: end - }; + return TextSpan.fromBounds(start, end); } - function createBreakpointSpanInfoWithLimChar(startElement: TypeScript.ISyntaxElement, limChar: number): ts.SpanInfo { - return { - minChar: start(startElement), - limChar: limChar - }; + function createBreakpointSpanInfoWithLimChar(startElement: TypeScript.ISyntaxElement, limChar: number): TextSpan { + return TextSpan.fromBounds(start(startElement), limChar); } class BreakpointResolver { constructor(private posLine: number, private lineMap: TypeScript.LineMap) { } - private breakpointSpanOfToken(positionedToken: TypeScript.ISyntaxToken): ts.SpanInfo { + private breakpointSpanOfToken(positionedToken: TypeScript.ISyntaxToken): TextSpan { switch (positionedToken.kind()) { case TypeScript.SyntaxKind.OpenBraceToken: return this.breakpointSpanOfOpenBrace(positionedToken); @@ -74,7 +65,7 @@ module TypeScript.Services.Breakpoints { return this.breakpointSpanOfContainingNode(positionedToken); } - private breakpointSpanOfOpenBrace(openBraceToken: TypeScript.ISyntaxToken): ts.SpanInfo { + private breakpointSpanOfOpenBrace(openBraceToken: TypeScript.ISyntaxToken): TextSpan { var container = Syntax.containingNode(openBraceToken); if (container) { var originalContainer = container; @@ -168,7 +159,7 @@ module TypeScript.Services.Breakpoints { return null; } - private breakpointSpanOfCloseBrace(closeBraceToken: TypeScript.ISyntaxToken): ts.SpanInfo { + private breakpointSpanOfCloseBrace(closeBraceToken: TypeScript.ISyntaxToken): TextSpan { var container = Syntax.containingNode(closeBraceToken); if (container) { var originalContainer = container; @@ -243,7 +234,7 @@ module TypeScript.Services.Breakpoints { } - private breakpointSpanOfComma(commaToken: TypeScript.ISyntaxToken): ts.SpanInfo { + private breakpointSpanOfComma(commaToken: TypeScript.ISyntaxToken): TextSpan { var commaParent = commaToken.parent; if (isSeparatedList(commaParent)) { var grandParent = commaParent.parent; @@ -271,7 +262,7 @@ module TypeScript.Services.Breakpoints { return this.breakpointSpanOfContainingNode(commaToken); } - private breakpointSpanOfCloseParen(closeParenToken: TypeScript.ISyntaxToken): ts.SpanInfo { + private breakpointSpanOfCloseParen(closeParenToken: TypeScript.ISyntaxToken): TextSpan { var closeParenParent = closeParenToken.parent; if (closeParenParent) { switch (closeParenParent.kind()) { @@ -293,7 +284,7 @@ module TypeScript.Services.Breakpoints { return blockSyntax.statements && blockSyntax.statements.length != 0; } - private breakpointSpanOfFirstStatementInBlock(blockNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointSpanOfFirstStatementInBlock(blockNode: TypeScript.ISyntaxNode): TextSpan { if (!blockNode) { return null; } @@ -316,7 +307,7 @@ module TypeScript.Services.Breakpoints { } } - private breakpointSpanOfLastStatementInBlock(blockNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointSpanOfLastStatementInBlock(blockNode: TypeScript.ISyntaxNode): TextSpan { if (!blockNode) { return null; } @@ -339,7 +330,7 @@ module TypeScript.Services.Breakpoints { } } - private breakpointSpanOfFirstChildOfSyntaxList(positionedList: TypeScript.ISyntaxNodeOrToken[]): ts.SpanInfo { + private breakpointSpanOfFirstChildOfSyntaxList(positionedList: TypeScript.ISyntaxNodeOrToken[]): TextSpan { if (!positionedList) { return null; } @@ -363,7 +354,7 @@ module TypeScript.Services.Breakpoints { } } - private breakpointSpanOfLastChildOfSyntaxList(positionedList: TypeScript.ISyntaxNodeOrToken[]): ts.SpanInfo { + private breakpointSpanOfLastChildOfSyntaxList(positionedList: TypeScript.ISyntaxNodeOrToken[]): TextSpan { if (!positionedList) { return null; } @@ -385,7 +376,7 @@ module TypeScript.Services.Breakpoints { } } - private breakpointSpanOfNode(positionedNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointSpanOfNode(positionedNode: ISyntaxNode): TextSpan { var node = positionedNode; switch (node.kind()) { // Declarations with elements @@ -493,7 +484,7 @@ module TypeScript.Services.Breakpoints { } return false; } - + private isInitializerOfForStatement(expressionNode: TypeScript.ISyntaxNode): boolean { if (!expressionNode) { return false; @@ -552,12 +543,12 @@ module TypeScript.Services.Breakpoints { return false; } - private breakpointOfLeftOfCommaExpression(commaExpressionNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointOfLeftOfCommaExpression(commaExpressionNode: TypeScript.ISyntaxNode): TextSpan { var commaExpression = commaExpressionNode; return this.breakpointSpanOf(commaExpression.left); } - private breakpointOfExpression(expressionNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointOfExpression(expressionNode: TypeScript.ISyntaxNode): TextSpan { if (this.isInitializerOfForStatement(expressionNode) || this.isConditionOfForStatement(expressionNode) || this.isIncrememtorOfForStatement(expressionNode)) { @@ -582,7 +573,7 @@ module TypeScript.Services.Breakpoints { return this.breakpointSpanOfContainingNode(expressionNode); } - private breakpointSpanOfStatement(statementNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointSpanOfStatement(statementNode: TypeScript.ISyntaxNode): TextSpan { var statement = statementNode; if (statement.kind() == TypeScript.SyntaxKind.EmptyStatement) { return null; @@ -722,7 +713,7 @@ module TypeScript.Services.Breakpoints { return positionedNode && !TypeScript.SyntaxUtilities.isAmbientDeclarationSyntax(positionedNode); } - private breakpointSpanOfDeclarationWithElements(positionedNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointSpanOfDeclarationWithElements(positionedNode: TypeScript.ISyntaxNode): TextSpan { if (!this.canHaveBreakpointInDeclaration(positionedNode)) { return null; } @@ -751,7 +742,7 @@ module TypeScript.Services.Breakpoints { return !!varDeclaratorSyntax.equalsValueClause; } - private breakpointSpanOfVariableDeclarator(varDeclaratorNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointSpanOfVariableDeclarator(varDeclaratorNode: TypeScript.ISyntaxNode): TextSpan { if (!this.canHaveBreakpointInVariableDeclarator(varDeclaratorNode)) { return null; } @@ -799,7 +790,7 @@ module TypeScript.Services.Breakpoints { return false; } - private breakpointSpanOfVariableDeclaration(varDeclarationNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointSpanOfVariableDeclaration(varDeclarationNode: TypeScript.ISyntaxNode): TextSpan { if (!this.canHaveBreakpointInDeclaration(varDeclarationNode)) { return null; } @@ -830,7 +821,7 @@ module TypeScript.Services.Breakpoints { return this.canHaveBreakpointInVariableDeclaration(variableStatement.variableDeclaration); } - private breakpointSpanOfVariableStatement(varStatementNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointSpanOfVariableStatement(varStatementNode: TypeScript.ISyntaxNode): TextSpan { if (!this.canHaveBreakpointInVariableStatement(varStatementNode)) { return null; } @@ -842,7 +833,7 @@ module TypeScript.Services.Breakpoints { return createBreakpointSpanInfoWithLimChar(varStatementNode, end(childAt(varDeclarators, 0))); } - private breakpointSpanOfParameter(parameterNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointSpanOfParameter(parameterNode: TypeScript.ISyntaxNode): TextSpan { if (parameterNode.parent.kind() === SyntaxKind.SimpleArrowFunctionExpression) { return this.breakpointSpanOfNode(parameterNode.parent); } @@ -860,7 +851,7 @@ module TypeScript.Services.Breakpoints { } } - private breakpointSpanOfMemberVariableDeclaration(memberVarDeclarationNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointSpanOfMemberVariableDeclaration(memberVarDeclarationNode: TypeScript.ISyntaxNode): TextSpan { if (TypeScript.SyntaxUtilities.isAmbientDeclarationSyntax(memberVarDeclarationNode)) { return null; } @@ -874,7 +865,7 @@ module TypeScript.Services.Breakpoints { } } - private breakpointSpanOfImportDeclaration(importDeclarationNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointSpanOfImportDeclaration(importDeclarationNode: TypeScript.ISyntaxNode): TextSpan { if (TypeScript.SyntaxUtilities.isAmbientDeclarationSyntax(importDeclarationNode)) { return null; } @@ -883,7 +874,7 @@ module TypeScript.Services.Breakpoints { return createBreakpointSpanInfo(importDeclarationNode, importSyntax.modifiers, importSyntax.importKeyword, importSyntax.identifier, importSyntax.equalsToken, importSyntax.moduleReference); } - private breakpointSpanOfEnumDeclaration(enumDeclarationNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointSpanOfEnumDeclaration(enumDeclarationNode: TypeScript.ISyntaxNode): TextSpan { if (!this.canHaveBreakpointInDeclaration(enumDeclarationNode)) { return null; } @@ -891,7 +882,7 @@ module TypeScript.Services.Breakpoints { return createBreakpointSpanInfo(enumDeclarationNode); } - private breakpointSpanOfFirstEnumElement(enumDeclarationNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointSpanOfFirstEnumElement(enumDeclarationNode: TypeScript.ISyntaxNode): TextSpan { var enumDeclarationSyntax = enumDeclarationNode; var enumElements = enumDeclarationSyntax.enumElements; if (enumElements && childCount(enumElements)) { @@ -901,7 +892,7 @@ module TypeScript.Services.Breakpoints { return null; } - private breakpointSpanOfEnumElement(enumElementNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointSpanOfEnumElement(enumElementNode: TypeScript.ISyntaxNode): TextSpan { if (TypeScript.SyntaxUtilities.isAmbientDeclarationSyntax(enumElementNode)) { return null; } @@ -909,45 +900,45 @@ module TypeScript.Services.Breakpoints { return createBreakpointSpanInfo(enumElementNode); } - private breakpointSpanOfIfStatement(ifStatementNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointSpanOfIfStatement(ifStatementNode: TypeScript.ISyntaxNode): TextSpan { var ifStatement = ifStatementNode; return createBreakpointSpanInfo(ifStatementNode, ifStatement.ifKeyword, ifStatement.openParenToken, ifStatement.condition, ifStatement.closeParenToken); } - private breakpointSpanOfElseClause(elseClauseNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointSpanOfElseClause(elseClauseNode: TypeScript.ISyntaxNode): TextSpan { var elseClause = elseClauseNode; return this.breakpointSpanOf(elseClause.statement); } - private breakpointSpanOfForInStatement(forInStatementNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointSpanOfForInStatement(forInStatementNode: TypeScript.ISyntaxNode): TextSpan { var forInStatement = forInStatementNode; return createBreakpointSpanInfo(forInStatementNode, forInStatement.forKeyword, forInStatement.openParenToken, forInStatement.variableDeclaration, forInStatement.left, forInStatement.inKeyword, forInStatement.expression, forInStatement.closeParenToken); } - private breakpointSpanOfForStatement(forStatementNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointSpanOfForStatement(forStatementNode: TypeScript.ISyntaxNode): TextSpan { var forStatement = forStatementNode; return this.breakpointSpanOf(forStatement.variableDeclaration ? forStatement.variableDeclaration : forStatement.initializer); } - private breakpointSpanOfWhileStatement(whileStatementNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointSpanOfWhileStatement(whileStatementNode: TypeScript.ISyntaxNode): TextSpan { var whileStatement = whileStatementNode; return createBreakpointSpanInfo(whileStatementNode, whileStatement.whileKeyword, whileStatement.openParenToken, whileStatement.condition, whileStatement.closeParenToken); } - private breakpointSpanOfDoStatement(doStatementNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointSpanOfDoStatement(doStatementNode: TypeScript.ISyntaxNode): TextSpan { var doStatement = doStatementNode; return createBreakpointSpanInfo(doStatementNode, doStatement.whileKeyword, doStatement.openParenToken, doStatement.condition, doStatement.closeParenToken); } - private breakpointSpanOfSwitchStatement(switchStatementNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointSpanOfSwitchStatement(switchStatementNode: TypeScript.ISyntaxNode): TextSpan { var switchStatement = switchStatementNode; return createBreakpointSpanInfo(switchStatementNode, switchStatement.switchKeyword, switchStatement.openParenToken, switchStatement.expression, switchStatement.closeParenToken); } - private breakpointSpanOfFirstStatementOfFirstCaseClause(switchStatementNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointSpanOfFirstStatementOfFirstCaseClause(switchStatementNode: TypeScript.ISyntaxNode): TextSpan { var switchStatement = switchStatementNode; if (switchStatement.switchClauses && switchStatement.switchClauses.length == 0) { return null; @@ -964,7 +955,7 @@ module TypeScript.Services.Breakpoints { return this.breakpointSpanOfFirstChildOfSyntaxList(statements); } - private breakpointSpanOfLastStatementOfLastCaseClause(switchStatementNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointSpanOfLastStatementOfLastCaseClause(switchStatementNode: TypeScript.ISyntaxNode): TextSpan { var switchStatement = switchStatementNode; if (switchStatement.switchClauses && switchStatement.switchClauses.length == 0) { return null; @@ -981,37 +972,37 @@ module TypeScript.Services.Breakpoints { return this.breakpointSpanOfLastChildOfSyntaxList(statements); } - private breakpointSpanOfCaseSwitchClause(caseClauseNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointSpanOfCaseSwitchClause(caseClauseNode: TypeScript.ISyntaxNode): TextSpan { var caseSwitchClause = caseClauseNode; return this.breakpointSpanOfFirstChildOfSyntaxList(caseSwitchClause.statements); } - private breakpointSpanOfDefaultSwitchClause(defaultSwithClauseNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointSpanOfDefaultSwitchClause(defaultSwithClauseNode: TypeScript.ISyntaxNode): TextSpan { var defaultSwitchClause = defaultSwithClauseNode; return this.breakpointSpanOfFirstChildOfSyntaxList(defaultSwitchClause.statements); } - private breakpointSpanOfWithStatement(withStatementNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointSpanOfWithStatement(withStatementNode: TypeScript.ISyntaxNode): TextSpan { var withStatement = withStatementNode; return this.breakpointSpanOf(withStatement.statement); } - private breakpointSpanOfTryStatement(tryStatementNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointSpanOfTryStatement(tryStatementNode: TypeScript.ISyntaxNode): TextSpan { var tryStatement = tryStatementNode; return this.breakpointSpanOfFirstStatementInBlock(tryStatement.block); } - private breakpointSpanOfCatchClause(catchClauseNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointSpanOfCatchClause(catchClauseNode: TypeScript.ISyntaxNode): TextSpan { var catchClause = catchClauseNode; return createBreakpointSpanInfo(catchClauseNode, catchClause.catchKeyword, catchClause.openParenToken, catchClause.identifier, catchClause.typeAnnotation, catchClause.closeParenToken); } - private breakpointSpanOfFinallyClause(finallyClauseNode: TypeScript.ISyntaxNode): ts.SpanInfo { + private breakpointSpanOfFinallyClause(finallyClauseNode: TypeScript.ISyntaxNode): TextSpan { var finallyClause = finallyClauseNode; return this.breakpointSpanOfFirstStatementInBlock(finallyClause.block); } - private breakpointSpanOfParenthesizedArrowFunctionExpression(arrowFunctionExpression: ParenthesizedArrowFunctionExpressionSyntax): ts.SpanInfo { + private breakpointSpanOfParenthesizedArrowFunctionExpression(arrowFunctionExpression: ParenthesizedArrowFunctionExpressionSyntax): TextSpan { if (arrowFunctionExpression.block) { return this.breakpointSpanOfFirstStatementInBlock(arrowFunctionExpression.block); } @@ -1020,7 +1011,7 @@ module TypeScript.Services.Breakpoints { } } - private breakpointSpanOfSimpleArrowFunctionExpression(arrowFunctionExpression: SimpleArrowFunctionExpressionSyntax): ts.SpanInfo { + private breakpointSpanOfSimpleArrowFunctionExpression(arrowFunctionExpression: SimpleArrowFunctionExpressionSyntax): TextSpan { if (arrowFunctionExpression.block) { return this.breakpointSpanOfFirstStatementInBlock(arrowFunctionExpression.block); } @@ -1029,7 +1020,7 @@ module TypeScript.Services.Breakpoints { } } - private breakpointSpanOfContainingNode(positionedElement: ISyntaxElement): ts.SpanInfo { + private breakpointSpanOfContainingNode(positionedElement: ISyntaxElement): TextSpan { var current = positionedElement.parent; while (!isNode(current)) { current = current.parent; @@ -1038,7 +1029,7 @@ module TypeScript.Services.Breakpoints { return this.breakpointSpanOf(current); } - private breakpointSpanIfStartsOnSameLine(positionedElement: TypeScript.ISyntaxElement): ts.SpanInfo { + private breakpointSpanIfStartsOnSameLine(positionedElement: TypeScript.ISyntaxElement): TextSpan { if (positionedElement && this.posLine == this.lineMap.getLineNumberFromPosition(start(positionedElement))) { return this.breakpointSpanOf(positionedElement); } @@ -1046,7 +1037,7 @@ module TypeScript.Services.Breakpoints { return null; } - public breakpointSpanOf(positionedElement: TypeScript.ISyntaxElement): ts.SpanInfo { + public breakpointSpanOf(positionedElement: TypeScript.ISyntaxElement): TextSpan { if (!positionedElement) { return null; } @@ -1075,7 +1066,7 @@ module TypeScript.Services.Breakpoints { } } - export function getBreakpointLocation(syntaxTree: TypeScript.SyntaxTree, askedPos: number): ts.SpanInfo { + export function getBreakpointLocation(syntaxTree: TypeScript.SyntaxTree, askedPos: number): TextSpan { // Cannot set breakpoint in dts file if (TypeScript.isDTSFile(syntaxTree.fileName())) { return null; diff --git a/src/services/formatting/formattingManager.ts b/src/services/formatting/formattingManager.ts index 57c854196a8..359f19c84ed 100644 --- a/src/services/formatting/formattingManager.ts +++ b/src/services/formatting/formattingManager.ts @@ -19,28 +19,26 @@ module TypeScript.Services.Formatting { export class FormattingManager { private options: FormattingOptions; - constructor(private syntaxTree: SyntaxTree, private snapshot: ITextSnapshot, private rulesProvider: RulesProvider, editorOptions: ts.EditorOptions) { + constructor(private syntaxTree: SyntaxTree, + private snapshot: ITextSnapshot, + private rulesProvider: RulesProvider, + editorOptions: ts.EditorOptions) { // // TODO: convert to use FormattingOptions instead of EditorOptions - this.options = new FormattingOptions(!editorOptions.ConvertTabsToSpaces, editorOptions.TabSize, editorOptions.IndentSize, editorOptions.NewLineCharacter); + this.options = new FormattingOptions(!editorOptions.ConvertTabsToSpaces, editorOptions.TabSize, editorOptions.IndentSize, editorOptions.NewLineCharacter) } - public formatSelection(minChar: number, limChar: number): ts.TextEdit[] { + public formatSelection(minChar: number, limChar: number): ts.TextChange[] { var span = TextSpan.fromBounds(minChar, limChar); return this.formatSpan(span, FormattingRequestKind.FormatSelection); } - public formatDocument(minChar: number, limChar: number): ts.TextEdit[] { - var span = TextSpan.fromBounds(minChar, limChar); + public formatDocument(): ts.TextChange[] { + var span = TextSpan.fromBounds(0, this.snapshot.getLength()); return this.formatSpan(span, FormattingRequestKind.FormatDocument); } - public formatOnPaste(minChar: number, limChar: number): ts.TextEdit[] { - var span = TextSpan.fromBounds(minChar, limChar); - return this.formatSpan(span, FormattingRequestKind.FormatOnPaste); - } - - public formatOnSemicolon(caretPosition: number): ts.TextEdit[] { + public formatOnSemicolon(caretPosition: number): ts.TextChange[] { var sourceUnit = this.syntaxTree.sourceUnit(); var semicolonPositionedToken = findToken(sourceUnit, caretPosition - 1); @@ -48,7 +46,7 @@ module TypeScript.Services.Formatting { // Find the outer most parent that this semicolon terminates var current: ISyntaxElement = semicolonPositionedToken; while (current.parent !== null && - end(current.parent) === end(semicolonPositionedToken) && + end(current.parent) === end(semicolonPositionedToken) && current.parent.kind() !== SyntaxKind.List) { current = current.parent; } @@ -63,7 +61,7 @@ module TypeScript.Services.Formatting { return []; } - public formatOnClosingCurlyBrace(caretPosition: number): ts.TextEdit[] { + public formatOnClosingCurlyBrace(caretPosition: number): ts.TextChange[] { var sourceUnit = this.syntaxTree.sourceUnit(); var closeBracePositionedToken = findToken(sourceUnit, caretPosition - 1); @@ -71,8 +69,8 @@ module TypeScript.Services.Formatting { // Find the outer most parent that this closing brace terminates var current: ISyntaxElement = closeBracePositionedToken; while (current.parent !== null && - end(current.parent) === end(closeBracePositionedToken) && - current.parent.kind() !== SyntaxKind.List) { + end(current.parent) === end(closeBracePositionedToken) && + current.parent.kind() !== SyntaxKind.List) { current = current.parent; } @@ -86,7 +84,7 @@ module TypeScript.Services.Formatting { return []; } - public formatOnEnter(caretPosition: number): ts.TextEdit[] { + public formatOnEnter(caretPosition: number): ts.TextChange[] { var lineNumber = this.snapshot.getLineNumberFromPosition(caretPosition); if (lineNumber > 0) { @@ -103,23 +101,20 @@ module TypeScript.Services.Formatting { return []; } - private formatSpan(span: TextSpan, formattingRequestKind: FormattingRequestKind): ts.TextEdit[] { + private formatSpan(span: TextSpan, formattingRequestKind: FormattingRequestKind): ts.TextChange[] { // Always format from the beginning of the line var startLine = this.snapshot.getLineFromPosition(span.start()); span = TextSpan.fromBounds(startLine.startPosition(), span.end()); - var result: ts.TextEdit[] = []; + var result: ts.TextChange[] = []; var formattingEdits = Formatter.getEdits(span, this.syntaxTree.sourceUnit(), this.options, true, this.snapshot, this.rulesProvider, formattingRequestKind); // // TODO: Change the ILanguageService interface to return TextEditInfo (with start, and length) instead of TextEdit (with minChar and limChar) formattingEdits.forEach((item) => { - result.push({ - minChar: item.position, - limChar: item.position + item.length, - text: item.replaceWith - }); + var edit = new ts.TextChange(new TextSpan(item.position, item.length), item.replaceWith); + result.push(edit); }); return result; diff --git a/src/services/formatting/textSnapshot.ts b/src/services/formatting/textSnapshot.ts index c91ea7ae492..4f2904dd76f 100644 --- a/src/services/formatting/textSnapshot.ts +++ b/src/services/formatting/textSnapshot.ts @@ -17,6 +17,7 @@ module TypeScript.Services.Formatting { export interface ITextSnapshot { + getLength(): number; getText(span: TextSpan): string; getLineNumberFromPosition(position: number): number; getLineFromPosition(position: number): ITextSnapshotLine; @@ -30,6 +31,10 @@ module TypeScript.Services.Formatting { this.lines = []; } + public getLength(): number { + return this.snapshot.length(); + } + public getText(span: TextSpan): string { return this.snapshot.substr(span.start(), span.length()); } diff --git a/src/services/getScriptLexicalStructureWalker.ts b/src/services/getScriptLexicalStructureWalker.ts index b42f34fa927..bfce8e0067c 100644 --- a/src/services/getScriptLexicalStructureWalker.ts +++ b/src/services/getScriptLexicalStructureWalker.ts @@ -1,121 +1,22 @@ - /// + module TypeScript.Services { - interface LexicalScope { - items: ts.Map; - itemNames: string[]; - childScopes: ts.Map; - childScopeNames: string[]; - } + export class NavigationBarItemGetter { + private hasGlobalNode = false; - export class GetScriptLexicalStructureWalker extends TypeScript.SyntaxWalker { - private nameStack: string[] = []; - private kindStack: string[] = []; + private getIndent(node: ISyntaxNode): number { + var indent = this.hasGlobalNode ? 1 : 0; - private parentScopes: LexicalScope[] = []; - private currentScope: LexicalScope; + var current = node.parent; + while (current != null) { + if (current.kind() == SyntaxKind.ModuleDeclaration || current.kind() === SyntaxKind.FunctionDeclaration) { + indent++; + } - private createScope(): LexicalScope { - return { - items: TypeScript.createIntrinsicsObject(), - childScopes: TypeScript.createIntrinsicsObject(), - childScopeNames: [], - itemNames: [] - }; - } - - private pushNewContainerScope(containerName: string, kind: string): LexicalScope { - Debug.assert(containerName, "No scope name provided"); - - var key = kind + "+" + containerName; - this.nameStack.push(containerName); - this.kindStack.push(kind); - - var parentScope = this.currentScope; - this.parentScopes.push(parentScope); - - var scope = ts.lookUp(parentScope.childScopes, key); - if (!scope) { - scope = this.createScope() - parentScope.childScopes[key] = scope; - parentScope.childScopeNames.push(key); + current = current.parent; } - this.currentScope = scope; - return parentScope; - } - - private popScope() { - Debug.assert(this.parentScopes.length > 0, "No parent scopes to return to") - this.currentScope = this.parentScopes.pop(); - this.kindStack.pop(); - this.nameStack.pop(); - } - - constructor(private fileName: string) { - super(); - this.currentScope = this.createScope(); - } - - private collectItems(items: ts.NavigateToItem[], scope = this.currentScope) { - scope.itemNames.forEach(item => { - items.push(scope.items[item]); - }); - - scope.childScopeNames.forEach(childScope => { - this.collectItems(items, scope.childScopes[childScope]); - }); - } - - static getListsOfAllScriptLexicalStructure(items: ts.NavigateToItem[], fileName: string, unit: TypeScript.SourceUnitSyntax) { - var visitor = new GetScriptLexicalStructureWalker(fileName); - visitNodeOrToken(visitor, unit); - visitor.collectItems(items); - } - - private createItem(node: TypeScript.ISyntaxNode, modifiers: ISyntaxToken[], kind: string, name: string): void { - var key = kind + "+" + name; - - if (ts.lookUp(this.currentScope.items, key) !== undefined) { - this.addAdditionalSpan(node, key); - return; - } - - var item: ts.NavigateToItem = { - name: name, - kind: kind, - matchKind: ts.MatchKind.exact, - fileName: this.fileName, - kindModifiers: this.getKindModifiers(modifiers), - minChar: start(node), - limChar: end(node), - containerName: this.nameStack.join("."), - containerKind: this.kindStack.length === 0 ? "" : TypeScript.ArrayUtilities.last(this.kindStack), - }; - this.currentScope.items[key] = item; - this.currentScope.itemNames.push(key); - } - - private addAdditionalSpan( - node: TypeScript.ISyntaxNode, - key: string) { - - var item = ts.lookUp(this.currentScope.items, key); - Debug.assert(item !== undefined); - - var start = TypeScript.start(node); - var span: ts.SpanInfo = { - minChar: start, - limChar: start + width(node) - }; - - - if (item.additionalSpans) { - item.additionalSpans.push(span); - } - else { - item.additionalSpans = [span]; - } + return indent; } private getKindModifiers(modifiers: TypeScript.ISyntaxToken[]): string { @@ -128,30 +29,221 @@ module TypeScript.Services { return result.length > 0 ? result.join(',') : ts.ScriptElementKindModifier.none; } - public visitModuleDeclaration(node: TypeScript.ModuleDeclarationSyntax): void { - var names = this.getModuleNames(node); - this.visitModuleDeclarationWorker(node, names, 0); + public getItems(node: TypeScript.SourceUnitSyntax): ts.NavigationBarItem[] { + return this.getItemsWorker(() => this.getTopLevelNodes(node), n => this.createTopLevelItem(n)); } - private visitModuleDeclarationWorker(node: TypeScript.ModuleDeclarationSyntax, names: string[], nameIndex: number): void { - if (nameIndex === names.length) { - // We're after all the module names, descend and process all children. - super.visitModuleDeclaration(node); + private getChildNodes(nodes: IModuleElementSyntax[]): ISyntaxNode[] { + var childNodes: ISyntaxNode[] = []; + + for (var i = 0, n = nodes.length; i < n; i++) { + var node = nodes[i]; + + if (node.kind() === SyntaxKind.FunctionDeclaration) { + childNodes.push(node); + } + else if (node.kind() === SyntaxKind.VariableStatement) { + var variableDeclaration = (node).variableDeclaration; + childNodes.push.apply(childNodes, variableDeclaration.variableDeclarators); + } } - else { - var name = names[nameIndex]; - var kind = ts.ScriptElementKind.moduleElement; - this.createItem(node, node.modifiers, kind, name); + return childNodes; + } - this.pushNewContainerScope(name, kind); + private getTopLevelNodes(node: SourceUnitSyntax): ISyntaxNode[] { + var topLevelNodes: ISyntaxNode[] = []; + topLevelNodes.push(node); - this.visitModuleDeclarationWorker(node, names, nameIndex + 1); + this.addTopLevelNodes(node.moduleElements, topLevelNodes); - this.popScope(); + return topLevelNodes; + } + + private addTopLevelNodes(nodes: IModuleElementSyntax[], topLevelNodes: ISyntaxNode[]): void { + for (var i = 0, n = nodes.length; i < n; i++) { + var node = nodes[i]; + switch (node.kind()) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.InterfaceDeclaration: + topLevelNodes.push(node); + break; + + case SyntaxKind.ModuleDeclaration: + var moduleDeclaration = node; + topLevelNodes.push(node); + this.addTopLevelNodes(moduleDeclaration.moduleElements, topLevelNodes); + break; + + case SyntaxKind.FunctionDeclaration: + var functionDeclaration = node; + if (this.isTopLevelFunctionDeclaration(functionDeclaration)) { + topLevelNodes.push(node); + this.addTopLevelNodes(functionDeclaration.block.statements, topLevelNodes); + } + break; + } } } + public isTopLevelFunctionDeclaration(functionDeclaration: FunctionDeclarationSyntax) { + // A function declaration is 'top level' if it contains any function declarations + // within it. + return functionDeclaration.block && ArrayUtilities.any(functionDeclaration.block.statements, s => s.kind() === SyntaxKind.FunctionDeclaration); + } + + private getItemsWorker(getNodes: () => ISyntaxNode[], createItem: (n: ISyntaxNode) => ts.NavigationBarItem): ts.NavigationBarItem[] { + var items: ts.NavigationBarItem[] = []; + + var keyToItem = createIntrinsicsObject(); + + var nodes = getNodes(); + for (var i = 0, n = nodes.length; i < n; i++) { + var child = nodes[i]; + var item = createItem(child); + if (item != null) { + if (item.text.length > 0) { + var key = item.text + "-" + item.kind; + + var itemWithSameName = keyToItem[key]; + if (itemWithSameName) { + // We had an item with the same name. Merge these items together. + this.merge(itemWithSameName, item); + } + else { + keyToItem[key] = item; + items.push(item); + } + } + } + } + + return items; + } + + private merge(target: ts.NavigationBarItem, source: ts.NavigationBarItem) { + // First, add any spans in the source to the target. + target.spans.push.apply(target.spans, source.spans); + + if (source.childItems) { + if (!target.childItems) { + target.childItems = []; + } + + // Next, recursively merge or add any children in the source as appropriate. + outer: + for (var i = 0, n = source.childItems.length; i < n; i++) { + var sourceChild = source.childItems[i]; + + for (var j = 0, m = target.childItems.length; j < m; j++) { + var targetChild = target.childItems[j]; + + if (targetChild.text === sourceChild.text && targetChild.kind === sourceChild.kind) { + // Found a match. merge them. + this.merge(targetChild, sourceChild); + continue outer; + } + } + + // Didn't find a match, just add this child to the list. + target.childItems.push(sourceChild); + } + } + } + + private createChildItem(node: ISyntaxNode): ts.NavigationBarItem { + switch (node.kind()) { + case SyntaxKind.Parameter: + var parameter = node; + if (parameter.modifiers.length === 0) { + return null; + } + return new ts.NavigationBarItem(parameter.identifier.text(), ts.ScriptElementKind.memberVariableElement, this.getKindModifiers(parameter.modifiers), [TextSpan.fromBounds(start(node), end(node))]); + + case SyntaxKind.MemberFunctionDeclaration: + var memberFunction = node; + return new ts.NavigationBarItem(memberFunction.propertyName.text(), ts.ScriptElementKind.memberFunctionElement, this.getKindModifiers(memberFunction.modifiers), [TextSpan.fromBounds(start(node), end(node))]); + + case SyntaxKind.GetAccessor: + var getAccessor = node; + return new ts.NavigationBarItem(getAccessor.propertyName.text(), ts.ScriptElementKind.memberGetAccessorElement, this.getKindModifiers(getAccessor.modifiers), [TextSpan.fromBounds(start(node), end(node))]); + + case SyntaxKind.SetAccessor: + var setAccessor = node; + return new ts.NavigationBarItem(setAccessor.propertyName.text(), ts.ScriptElementKind.memberSetAccessorElement, this.getKindModifiers(setAccessor.modifiers), [TextSpan.fromBounds(start(node), end(node))]); + + case SyntaxKind.IndexSignature: + var indexSignature = node; + return new ts.NavigationBarItem("[]", ts.ScriptElementKind.indexSignatureElement, ts.ScriptElementKindModifier.none, [TextSpan.fromBounds(start(node), end(node))]); + + case SyntaxKind.EnumElement: + var enumElement = node; + return new ts.NavigationBarItem(enumElement.propertyName.text(), ts.ScriptElementKind.memberVariableElement, ts.ScriptElementKindModifier.none, [TextSpan.fromBounds(start(node), end(node))]); + + case SyntaxKind.CallSignature: + var callSignature = node; + return new ts.NavigationBarItem("()", ts.ScriptElementKind.callSignatureElement, ts.ScriptElementKindModifier.none, [TextSpan.fromBounds(start(node), end(node))]); + + case SyntaxKind.ConstructSignature: + var constructSignature = node; + return new ts.NavigationBarItem("new()", ts.ScriptElementKind.constructSignatureElement, ts.ScriptElementKindModifier.none, [TextSpan.fromBounds(start(node), end(node))]); + + case SyntaxKind.MethodSignature: + var methodSignature = node; + return new ts.NavigationBarItem(methodSignature.propertyName.text(), ts.ScriptElementKind.memberFunctionElement, ts.ScriptElementKindModifier.none, [TextSpan.fromBounds(start(node), end(node))]); + + case SyntaxKind.PropertySignature: + var propertySignature = node; + return new ts.NavigationBarItem(propertySignature.propertyName.text(), ts.ScriptElementKind.memberVariableElement, ts.ScriptElementKindModifier.none, [TextSpan.fromBounds(start(node), end(node))]); + + case SyntaxKind.FunctionDeclaration: + var functionDeclaration = node; + if (!this.isTopLevelFunctionDeclaration(functionDeclaration)) { + return new ts.NavigationBarItem(functionDeclaration.identifier.text(), ts.ScriptElementKind.functionElement, this.getKindModifiers(functionDeclaration.modifiers), [TextSpan.fromBounds(start(node), end(node))]); + } + break; + + case SyntaxKind.MemberVariableDeclaration: + var memberVariableDeclaration = node; + return new ts.NavigationBarItem(memberVariableDeclaration.variableDeclarator.propertyName.text(), ts.ScriptElementKind.memberVariableElement, this.getKindModifiers(memberVariableDeclaration.modifiers), [TextSpan.fromBounds(start(memberVariableDeclaration.variableDeclarator), end(memberVariableDeclaration.variableDeclarator))]); + + case SyntaxKind.VariableDeclarator: + var variableDeclarator = node; + return new ts.NavigationBarItem(variableDeclarator.propertyName.text(), ts.ScriptElementKind.variableElement, ts.ScriptElementKindModifier.none, [TextSpan.fromBounds(start(variableDeclarator), end(variableDeclarator))]); + + case SyntaxKind.ConstructorDeclaration: + var constructorDeclaration = node; + return new ts.NavigationBarItem("constructor", ts.ScriptElementKind.constructorImplementationElement, ts.ScriptElementKindModifier.none, [TextSpan.fromBounds(start(node), end(node))]); + } + + return null; + } + + private createTopLevelItem(node: ISyntaxNode): ts.NavigationBarItem { + switch (node.kind()) { + case SyntaxKind.SourceUnit: + return this.createSourceUnitItem(node); + + case SyntaxKind.ClassDeclaration: + return this.createClassItem(node); + + case SyntaxKind.EnumDeclaration: + return this.createEnumItem(node); + + case SyntaxKind.InterfaceDeclaration: + return this.createIterfaceItem(node); + + case SyntaxKind.ModuleDeclaration: + return this.createModuleItem(node); + + case SyntaxKind.FunctionDeclaration: + return this.createFunctionItem(node); + } + + return null; + } + private getModuleNames(node: TypeScript.ModuleDeclarationSyntax): string[] { var result: string[] = []; @@ -176,180 +268,84 @@ module TypeScript.Services { } } - public visitClassDeclaration(node: TypeScript.ClassDeclarationSyntax): void { - var name = node.identifier.text(); - var kind = ts.ScriptElementKind.classElement; + private createModuleItem(node: ModuleDeclarationSyntax): ts.NavigationBarItem { + var moduleNames = this.getModuleNames(node); - this.createItem(node, node.modifiers, kind, name); + var childItems = this.getItemsWorker(() => this.getChildNodes(node.moduleElements), n => this.createChildItem(n)); - this.pushNewContainerScope(name, kind); - - super.visitClassDeclaration(node); - - this.popScope(); + return new ts.NavigationBarItem(moduleNames.join("."), + ts.ScriptElementKind.moduleElement, + this.getKindModifiers(node.modifiers), + [TextSpan.fromBounds(start(node), end(node))], + childItems, + this.getIndent(node)); } - public visitInterfaceDeclaration(node: TypeScript.InterfaceDeclarationSyntax): void { - var name = node.identifier.text(); - var kind = ts.ScriptElementKind.interfaceElement; + private createFunctionItem(node: FunctionDeclarationSyntax) { + var childItems = this.getItemsWorker(() => node.block.statements, n => this.createChildItem(n)); - this.createItem(node, node.modifiers, kind, name); - - this.pushNewContainerScope(name, kind); - - super.visitInterfaceDeclaration(node); - - this.popScope(); + return new ts.NavigationBarItem(node.identifier.text(), + ts.ScriptElementKind.functionElement, + this.getKindModifiers(node.modifiers), + [TextSpan.fromBounds(start(node), end(node))], + childItems, + this.getIndent(node)); } - public visitObjectType(node: TypeScript.ObjectTypeSyntax): void { - // Ignore an object type if we aren't inside an interface declaration. We don't want - // to add some random object type's members to the nav bar. - if (node.parent.kind() === SyntaxKind.InterfaceDeclaration) { - super.visitObjectType(node); - } - } + private createSourceUnitItem(node: SourceUnitSyntax): ts.NavigationBarItem { + var childItems = this.getItemsWorker(() => this.getChildNodes(node.moduleElements), n => this.createChildItem(n)); - public visitEnumDeclaration(node: TypeScript.EnumDeclarationSyntax): void { - var name = node.identifier.text(); - var kind = ts.ScriptElementKind.enumElement; - - this.createItem(node, node.modifiers, kind, name); - - this.pushNewContainerScope(name, kind); - - super.visitEnumDeclaration(node); - - this.popScope(); - } - - public visitConstructorDeclaration(node: TypeScript.ConstructorDeclarationSyntax): void { - this.createItem(node, TypeScript.Syntax.emptyList(), ts.ScriptElementKind.constructorImplementationElement, "constructor"); - - // Search the parameter list of class properties - var parameters = node.callSignature.parameterList.parameters; - if (parameters) { - for (var i = 0, n = parameters.length; i < n; i++) { - var parameter = parameters[i]; - - Debug.assert(parameter.kind() === SyntaxKind.Parameter); - - if (SyntaxUtilities.containsToken(parameter.modifiers, SyntaxKind.PublicKeyword) || - SyntaxUtilities.containsToken(parameter.modifiers, SyntaxKind.PrivateKeyword)) { - this.createItem(node, parameter.modifiers, ts.ScriptElementKind.memberVariableElement, parameter.identifier.text()); - } - } + if (childItems === null || childItems.length === 0) { + return null; } - // No need to descend into a constructor; + this.hasGlobalNode = true; + return new ts.NavigationBarItem("", + ts.ScriptElementKind.moduleElement, + ts.ScriptElementKindModifier.none, + [TextSpan.fromBounds(start(node), end(node))], + childItems); } - public visitMemberFunctionDeclaration(node: TypeScript.MemberFunctionDeclarationSyntax): void { - this.createItem(node, node.modifiers, ts.ScriptElementKind.memberFunctionElement, node.propertyName.text()); + private createClassItem(node: ClassDeclarationSyntax): ts.NavigationBarItem { + var constructor = ArrayUtilities.firstOrDefault( + node.classElements, n => n.kind() === SyntaxKind.ConstructorDeclaration); - // No need to descend into a member function; + // Add the constructor parameters in as children of hte class (for property parameters). + var nodes: ISyntaxNode[] = constructor + ? (constructor.callSignature.parameterList.parameters).concat(node.classElements) + : node.classElements; + + var childItems = this.getItemsWorker(() => nodes, n => this.createChildItem(n)); + return new ts.NavigationBarItem( + node.identifier.text(), + ts.ScriptElementKind.classElement, + this.getKindModifiers(node.modifiers), + [TextSpan.fromBounds(start(node), end(node))], + childItems, + this.getIndent(node)); } - public visitGetAccessor(node: TypeScript.GetAccessorSyntax): void { - this.createItem(node, node.modifiers, ts.ScriptElementKind.memberGetAccessorElement, node.propertyName.text()); - - // No need to descend into a member accessor; + private createEnumItem(node: TypeScript.EnumDeclarationSyntax): ts.NavigationBarItem { + var childItems = this.getItemsWorker(() => node.enumElements, n => this.createChildItem(n)); + return new ts.NavigationBarItem( + node.identifier.text(), + ts.ScriptElementKind.enumElement, + this.getKindModifiers(node.modifiers), + [TextSpan.fromBounds(start(node), end(node))], + childItems, + this.getIndent(node)); } - public visitSetAccessor(node: TypeScript.SetAccessorSyntax): void { - this.createItem(node, node.modifiers, ts.ScriptElementKind.memberSetAccessorElement, node.propertyName.text()); - - // No need to descend into a member accessor; - } - - public visitVariableDeclarator(node: TypeScript.VariableDeclaratorSyntax): void { - var modifiers = node.parent.kind() === SyntaxKind.MemberVariableDeclaration - ? (node.parent).modifiers - : TypeScript.Syntax.emptyList(); - var kind = node.parent.kind() === SyntaxKind.MemberVariableDeclaration - ? ts.ScriptElementKind.memberVariableElement - : ts.ScriptElementKind.variableElement; - this.createItem(node, modifiers, kind, node.propertyName.text()); - - // No need to descend into a variable declarator; - } - - public visitIndexSignature(node: TypeScript.IndexSignatureSyntax): void { - this.createItem(node, TypeScript.Syntax.emptyList(), ts.ScriptElementKind.indexSignatureElement, "[]"); - - // No need to descend into an index signature; - } - - public visitEnumElement(node: TypeScript.EnumElementSyntax): void { - this.createItem(node, TypeScript.Syntax.emptyList(), ts.ScriptElementKind.memberVariableElement, node.propertyName.text()); - - // No need to descend into an enum element; - } - - public visitCallSignature(node: TypeScript.CallSignatureSyntax): void { - this.createItem(node, TypeScript.Syntax.emptyList(), ts.ScriptElementKind.callSignatureElement, "()"); - - // No need to descend into a call signature; - } - - public visitConstructSignature(node: TypeScript.ConstructSignatureSyntax): void { - this.createItem(node, TypeScript.Syntax.emptyList(), ts.ScriptElementKind.constructSignatureElement, "new()"); - - // No need to descend into a construct signature; - } - - public visitMethodSignature(node: TypeScript.MethodSignatureSyntax): void { - this.createItem(node, TypeScript.Syntax.emptyList(), ts.ScriptElementKind.memberFunctionElement, node.propertyName.text()); - - // No need to descend into a method signature; - } - - public visitPropertySignature(node: TypeScript.PropertySignatureSyntax): void { - this.createItem(node, TypeScript.Syntax.emptyList(), ts.ScriptElementKind.memberVariableElement, node.propertyName.text()); - - // No need to descend into a property signature; - } - - public visitFunctionDeclaration(node: TypeScript.FunctionDeclarationSyntax): void { - // in the case of: - // declare function - // the parser will synthesize an identifier. - // we shouldn't add an unnamed function declaration - if (width(node.identifier) > 0) { - this.createItem(node, node.modifiers, ts.ScriptElementKind.functionElement, node.identifier.text()); - } - - // No need to descend into a function declaration; - } - - // Common statement types. Don't even bother walking into them as we'll never find anything - // inside that we'd put in the navbar. - - public visitBlock(node: TypeScript.BlockSyntax): void { - } - - public visitIfStatement(node: TypeScript.IfStatementSyntax): void { - } - - public visitExpressionStatement(node: TypeScript.ExpressionStatementSyntax): void { - } - - public visitThrowStatement(node: TypeScript.ThrowStatementSyntax): void { - } - - public visitReturnStatement(node: TypeScript.ReturnStatementSyntax): void { - } - - public visitSwitchStatement(node: TypeScript.SwitchStatementSyntax): void { - } - - public visitWithStatement(node: TypeScript.WithStatementSyntax): void { - } - - public visitTryStatement(node: TypeScript.TryStatementSyntax): void { - } - - public visitLabeledStatement(node: TypeScript.LabeledStatementSyntax): void { + private createIterfaceItem(node: TypeScript.InterfaceDeclarationSyntax): ts.NavigationBarItem { + var childItems = this.getItemsWorker(() => node.body.typeMembers, n => this.createChildItem(n)); + return new ts.NavigationBarItem( + node.identifier.text(), + ts.ScriptElementKind.interfaceElement, + this.getKindModifiers(node.modifiers), + [TextSpan.fromBounds(start(node), end(node))], + childItems, + this.getIndent(node)); } } } \ No newline at end of file diff --git a/src/services/services.ts b/src/services/services.ts index 9ff6266d54e..201b43432d3 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -458,9 +458,6 @@ module ts { // with a language service host instance // export interface LanguageService { - // Note: refresh is a no-op now. It is only around for back compat purposes. - refresh(): void; - cleanupSemanticCache(): void; getSyntacticDiagnostics(fileName: string): Diagnostic[]; @@ -472,28 +469,30 @@ module ts { getTypeAtPosition(fileName: string, position: number): TypeInfo; - getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): SpanInfo; + getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): TypeScript.TextSpan; - getBreakpointStatementAtPosition(fileName: string, position: number): SpanInfo; + getBreakpointStatementAtPosition(fileName: string, position: number): TypeScript.TextSpan; - getSignatureAtPosition(fileName: string, position: number): SignatureInfo; + getSignatureHelpItems(fileName: string, position: number): SignatureHelpItems; + getSignatureHelpCurrentArgumentState(fileName: string, position: number, applicableSpanStart: number): SignatureHelpState; + getRenameInfo(fileName: string, position: number): RenameInfo; getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[]; getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[]; getOccurrencesAtPosition(fileName: string, position: number): ReferenceEntry[]; getImplementorsAtPosition(fileName: string, position: number): ReferenceEntry[]; getNavigateToItems(searchValue: string): NavigateToItem[]; - getScriptLexicalStructure(fileName: string): NavigateToItem[]; + getNavigationBarItems(fileName: string): NavigationBarItem[]; - getOutliningRegions(fileName: string): OutliningSpan[]; + getOutliningSpans(fileName: string): OutliningSpan[]; + getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[]; getBraceMatchingAtPosition(fileName: string, position: number): TypeScript.TextSpan[]; getIndentationAtPosition(fileName: string, position: number, options: EditorOptions): number; - getFormattingEditsForRange(fileName: string, minChar: number, limChar: number, options: FormatCodeOptions): TextEdit[]; - getFormattingEditsForDocument(fileName: string, minChar: number, limChar: number, options: FormatCodeOptions): TextEdit[]; - getFormattingEditsOnPaste(fileName: string, minChar: number, limChar: number, options: FormatCodeOptions): TextEdit[]; - getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions): TextEdit[]; + getFormattingEditsForRange(fileName: string, start: number, end: number, options: FormatCodeOptions): TextChange[]; + getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions): TextChange[]; + getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions): TextChange[]; getEmitOutput(fileName: string): EmitOutput; @@ -502,30 +501,68 @@ module ts { dispose(): void; } - export interface ReferenceEntry { - fileName: string; - minChar: number; - limChar: number; - isWriteAccess: boolean; + export class NavigationBarItem { + constructor(public text: string, + public kind: string, + public kindModifiers: string, + public spans: TypeScript.TextSpan[], + public childItems: NavigationBarItem[] = null, + public indent = 0, + public bolded = false, + public grayed = false) { + } } - export interface NavigateToItem { - name: string; - kind: string; // see ScriptElementKind - kindModifiers: string; // see ScriptElementKindModifier, comma separated - matchKind: string; - fileName: string; - minChar: number; - limChar: number; - additionalSpans?: SpanInfo[]; - containerName: string; - containerKind: string; // see ScriptElementKind + export class TodoCommentDescriptor { + constructor(public text: string, + public priority: number) { + } } - export interface TextEdit { - minChar: number; - limChar: number; - text: string; + export class TodoComment { + constructor(public descriptor: TodoCommentDescriptor, + public message: string, + public position: number) { + } + } + + export class TextChange { + constructor(public span: TypeScript.TextSpan, public newText: string) { + } + + static createInsert(pos: number, newText: string): TextChange { + return new TextChange(new TypeScript.TextSpan(pos, 0), newText); + } + static createDelete(start: number, end: number): TextChange { + return new TextChange(TypeScript.TextSpan.fromBounds(start, end), ""); + } + static createReplace(start: number, end: number, newText: string): TextChange { + return new TextChange(TypeScript.TextSpan.fromBounds(start, end), newText); + } + } + + export class ReferenceEntry { + public fileName: string = ""; + public textSpan: TypeScript.TextSpan; + public isWriteAccess: boolean = false; + + constructor(fileName: string, textSpan: TypeScript.TextSpan, isWriteAccess: boolean) { + this.fileName = fileName; + this.textSpan = textSpan; + this.isWriteAccess = isWriteAccess; + } + } + + export class NavigateToItem { + constructor(public name: string, + public kind: string, + public kindModifiers: string, + public matchKind: string, + public fileName: string, + public textSpan: TypeScript.TextSpan, + public containerName: string, + public containerKind: string) { + } } export interface EditorOptions { @@ -546,14 +583,14 @@ module ts { PlaceOpenBraceOnNewLineForControlBlocks: boolean; } - export interface DefinitionInfo { - fileName: string; - minChar: number; - limChar: number; - kind: string; - name: string; - containerKind: string; - containerName: string; + export class DefinitionInfo { + constructor(public fileName: string, + public textSpan: TypeScript.TextSpan, + public kind: string, + public name: string, + public containerKind: string, + public containerName: string) { + } } export interface MemberName { @@ -562,54 +599,80 @@ module ts { text: string; } - export interface TypeInfo { - memberName: MemberName; - docComment: string; - fullSymbolName: string; - kind: string; - minChar: number; - limChar: number; + export class TypeInfo { + constructor( + public memberName: TypeScript.MemberName, + public docComment: string, + public fullSymbolName: string, + public kind: string, + public textSpan: TypeScript.TextSpan) { + } } - export interface SpanInfo { - minChar: number; - limChar: number; - // text?: string; + export class RenameInfo { + constructor(public canRename: boolean, + public localizedErrorMessage: string, + public displayName: string, + public fullDisplayName: string, + public kind: string, + public kindModifiers: string, + public triggerSpan: TypeScript.TextSpan) { + } + + public static CreateError(localizedErrorMessage: string) { + return new RenameInfo(/*canRename:*/ false, localizedErrorMessage, + /*displayName:*/ null, /*fullDisplayName:*/ null, + /*kind:*/ null, /*kindModifiers:*/ null, /*triggerSpan:*/ null); + } + + public static Create(displayName: string, + fullDisplayName: string, + kind: string, + kindModifiers: string, + triggerSpan: TypeScript.TextSpan) { + return new RenameInfo(/*canRename:*/ true, /*localizedErrorMessage:*/ null, displayName, fullDisplayName, kind, kindModifiers, triggerSpan); + } } - export interface SignatureInfo { - actual: ActualSignatureInfo; - formal: FormalSignatureItemInfo[]; // Formal signatures - activeFormal: number; // Index of the "best match" formal signature + export class SignatureHelpParameter { + constructor(public name: string, + public documentation: string, + public display: string, + public isOptional: boolean) { + } } - export interface FormalSignatureItemInfo { - signatureInfo: string; - typeParameters: FormalTypeParameterInfo[]; - parameters: FormalParameterInfo[]; // Array of parameters - docComment: string; // Help for the signature + /** + * Represents a single signature to show in signature help. + * The id is used for subsequent calls into the language service to ask questions about the + * signature help item in the context of any documents that have been updated. i.e. after + * an edit has happened, while signature help is still active, the host can ask important + * questions like 'what parameter is the user currently contained within?'. + */ + export class SignatureHelpItem { + constructor(public isVariadic: boolean, + public prefix: string, + public suffix: string, + public separator: string, + public parameters: SignatureHelpParameter[], + public documentation: string) { + } } - export interface FormalTypeParameterInfo { - name: string; // Type parameter name - docComment: string; // Comments that contain help for the parameter - minChar: number; // minChar for parameter info in the formal signature info string - limChar: number; // lim char for parameter info in the formal signature info string + /** + * Represents a set of signature help items, and the preferred item that should be selected. + */ + export class SignatureHelpItems { + constructor(public items: SignatureHelpItem[], + public applicableSpan: TypeScript.TextSpan, + public selectedItemIndex: number) { + } } - export interface FormalParameterInfo { - name: string; // Parameter name - isVariable: boolean; // true if parameter is var args - docComment: string; // Comments that contain help for the parameter - minChar: number; // minChar for parameter info in the formal signature info string - limChar: number; // lim char for parameter info in the formal signature info string - } - - export interface ActualSignatureInfo { - parameterMinChar: number; - parameterLimChar: number; - currentParameterIsTypeParameter: boolean; // current parameter is a type argument or a normal argument - currentParameter: number; // Index of active parameter in "parameters" or "typeParamters" array + export class SignatureHelpState { + constructor(public argumentIndex: number, + public argumentCount: number) { + } } export interface CompletionInfo { @@ -1877,25 +1940,21 @@ module ts { } /// QuickInfo - function getTypeAtPosition(filename: string, position: number): TypeInfo { + function getTypeAtPosition(fileName: string, position: number): TypeInfo { synchronizeHostData(); - filename = TypeScript.switchToForwardSlashes(filename); - var sourceFile = getSourceFile(filename); + fileName = TypeScript.switchToForwardSlashes(fileName); + var sourceFile = getSourceFile(fileName); var node = getNodeAtPosition(sourceFile.getSourceFile(), position); if (!node) return undefined; var symbol = typeChecker.getSymbolInfo(node); var type = symbol && typeChecker.getTypeOfSymbol(symbol); if (type) { - return { - memberName: new TypeScript.MemberNameString(typeChecker.typeToString(type)), - docComment: "", - fullSymbolName: typeChecker.symbolToString(symbol, getContainerNode(node)), - kind: getSymbolKind(symbol), - minChar: node.pos, - limChar: node.end - }; + return new TypeInfo( + new TypeScript.MemberNameString(typeChecker.typeToString(type)), + "", typeChecker.symbolToString(symbol, getContainerNode(node)), + getSymbolKind(symbol), TypeScript.TextSpan.fromBounds(node.pos, node.end)); } return undefined; @@ -1950,15 +2009,13 @@ module ts { } function getDefinitionInfo(node: Node, symbolKind: string, symbolName: string, containerName: string): DefinitionInfo { - return { - fileName: node.getSourceFile().filename, - minChar: node.getStart(), - limChar: node.getEnd(), - kind: symbolKind, - name: symbolName, - containerName: containerName, - containerKind: undefined - }; + return new DefinitionInfo( + node.getSourceFile().filename, + TypeScript.TextSpan.fromBounds(node.getStart(), node.getEnd()), + symbolKind, + symbolName, + undefined, + containerName); } function tryAddSignature(signatureDeclarations: Declaration[], selectConstructors: boolean, symbolKind: string, symbolName: string, containerName: string, result: DefinitionInfo[]) { @@ -2028,11 +2085,10 @@ module ts { if (comment) { var targetFilename = normalizePath(combinePaths(getDirectoryPath(filename), comment.filename)); if (program.getSourceFile(targetFilename)) { - return [{ - fileName: targetFilename, minChar: 0, limChar: 0, - kind: ScriptElementKind.scriptElement, - name: comment.filename, containerName: undefined, containerKind: undefined - }]; + return [new DefinitionInfo( + targetFilename, TypeScript.TextSpan.fromBounds(0, 0), + ScriptElementKind.scriptElement, + comment.filename, undefined, undefined)]; } return undefined; } @@ -2077,7 +2133,7 @@ module ts { return currentSourceFile; } - function getNameOrDottedNameSpan(filename: string, startPos: number, endPos: number): SpanInfo { + function getNameOrDottedNameSpan(filename: string, startPos: number, endPos: number): TypeScript.TextSpan { function getTypeInfoEligiblePath(filename: string, position: number, isConstructorValidPosition: boolean) { var sourceUnit = syntaxTreeCache.getCurrentFileSyntaxTree(filename).sourceUnit(); @@ -2128,10 +2184,9 @@ module ts { } } - return { - minChar: TypeScript.start(node), - limChar: TypeScript.end(node) - }; + return TypeScript.TextSpan.fromBounds( + TypeScript.start(node), + TypeScript.end(node)); } function getBreakpointStatementAtPosition(filename: string, position: number) { @@ -2142,15 +2197,13 @@ module ts { return TypeScript.Services.Breakpoints.getBreakpointLocation(syntaxtree, position); } - function getScriptLexicalStructure(filename: string) { + function getNavigationBarItems(filename: string) { filename = TypeScript.switchToForwardSlashes(filename); var syntaxTree = getSyntaxTree(filename); - var items: NavigateToItem[] = []; - TypeScript.Services.GetScriptLexicalStructureWalker.getListsOfAllScriptLexicalStructure(items, filename, syntaxTree.sourceUnit()); - return items; + return new TypeScript.Services.NavigationBarItemGetter().getItems(syntaxTree.sourceUnit()); } - function getOutliningRegions(filename: string): OutliningSpan[] { + function getOutliningSpans(filename: string): OutliningSpan[] { // doesn't use compiler - no need to synchronize with host filename = TypeScript.switchToForwardSlashes(filename); var sourceFile = getCurrentSourceFile(filename); @@ -2197,41 +2250,191 @@ module ts { return manager; } - function getFormattingEditsForRange(filename: string, minChar: number, limChar: number, options: FormatCodeOptions): TextEdit[] { - filename = TypeScript.switchToForwardSlashes(filename); + function getFormattingEditsForRange(fileName: string, start: number, end: number, options: FormatCodeOptions): TextChange[] { + fileName = TypeScript.switchToForwardSlashes(fileName); - var manager = getFormattingManager(filename, options); - return manager.formatSelection(minChar, limChar); + var manager = getFormattingManager(fileName, options); + return manager.formatSelection(start, end); } - function getFormattingEditsForDocument(filename: string, minChar: number, limChar: number, options: FormatCodeOptions): TextEdit[] { - filename = TypeScript.switchToForwardSlashes(filename); + function getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions): TextChange[] { + fileName = TypeScript.switchToForwardSlashes(fileName); - var manager = getFormattingManager(filename, options); - return manager.formatDocument(minChar, limChar); + var manager = getFormattingManager(fileName, options); + return manager.formatDocument(); } - function getFormattingEditsOnPaste(filename: string, minChar: number, limChar: number, options: FormatCodeOptions): TextEdit[] { - filename = TypeScript.switchToForwardSlashes(filename); + function getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions): TextChange[] { + fileName = TypeScript.switchToForwardSlashes(fileName); - var manager = getFormattingManager(filename, options); - return manager.formatOnPaste(minChar, limChar); + var manager = getFormattingManager(fileName, options); + + if (key === "}") { + return manager.formatOnClosingCurlyBrace(position); + } + else if (key === ";") { + return manager.formatOnSemicolon(position); + } + else if (key === "\n") { + return manager.formatOnEnter(position); + } + + return []; } - function getFormattingEditsAfterKeystroke(filename: string, position: number, key: string, options: FormatCodeOptions): TextEdit[] { - filename = TypeScript.switchToForwardSlashes(filename); + function getTodoCommentsRegExp(descriptors: TodoCommentDescriptor[]): RegExp { + // NOTE: ?: means 'non-capture group'. It allows us to have groups without having to + // filter them out later in the final result array. - var manager = getFormattingManager(filename, options); - if (key === "}") return manager.formatOnClosingCurlyBrace(position); - else if (key === ";") return manager.formatOnSemicolon(position); - else if (key === "\n") return manager.formatOnEnter(position); - else return []; + // TODO comments can appear in one of the following forms: + // + // 1) // TODO or /////////// TODO + // + // 2) /* TODO or /********** TODO + // + // 3) /* + // * TODO + // */ + // + // The following three regexps are used to match the start of the text up to the TODO + // comment portion. + var singleLineCommentStart = /(?:\/\/+\s*)/.source; + var multiLineCommentStart = /(?:\/\*+\s*)/.source; + var anyNumberOfSpacesAndAsterixesAtStartOfLine = /(?:^(?:\s|\*)*)/.source; + + // Match any of the above three TODO comment start regexps. + // Note that the outermost group *is* a capture group. We want to capture the preamble + // so that we can determine the starting position of the TODO comment match. + var preamble = "(" + anyNumberOfSpacesAndAsterixesAtStartOfLine + "|" + singleLineCommentStart + "|" + multiLineCommentStart + ")"; + + // Takes the descriptors and forms a regexp that matches them as if they were literals. + // For example, if the descriptors are "TODO(jason)" and "HACK", then this will be: + // + // (?:(TODO\(jason\))|(HACK)) + // + // Note that the outermost group is *not* a capture group, but the innermost groups + // *are* capture groups. By capturing the inner literals we can determine after + // matching which descriptor we are dealing with. + var literals = "(?:" + descriptors.map(d => "(" + this.escapeRegExp(d.text) + ")").join("|") + ")"; + + // After matching a descriptor literal, the following regexp matches the rest of the + // text up to the end of the line (or */). + var endOfLineOrEndOfComment = /(?:$|\*\/)/.source + var messageRemainder = /(?:.*?)/.source + + // This is the portion of the match we'll return as part of the TODO comment result. We + // match the literal portion up to the end of the line or end of comment. + var messagePortion = "(" + literals + messageRemainder + ")"; + var regExpString = preamble + messagePortion + endOfLineOrEndOfComment; + + // The final regexp will look like this: + // /((?:\/\/+\s*)|(?:\/\*+\s*)|(?:^(?:\s|\*)*))((?:(TODO\(jason\))|(HACK))(?:.*?))(?:$|\*\/)/gim + + // The flags of the regexp are important here. + // 'g' is so that we are doing a global search and can find matches several times + // in the input. + // + // 'i' is for case insensitivity (We do this to match C# TODO comment code). + // + // 'm' is so we can find matches in a multiline input. + return new RegExp(regExpString, "gim"); } + function getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[] { + fileName = TypeScript.switchToForwardSlashes(fileName); + + var syntaxTree = this.compiler.getDocument(fileName).syntaxTree(); + this.cancellationToken.throwIfCancellationRequested(); + + var text = syntaxTree.text; + var fileContents = text.substr(0, text.length()); + this.cancellationToken.throwIfCancellationRequested(); + + var result: TodoComment[] = []; + + if (descriptors.length > 0) { + var regExp = this.getTodoCommentsRegExp(descriptors); + + var matchArray: RegExpExecArray; + while (matchArray = regExp.exec(fileContents)) { + this.cancellationToken.throwIfCancellationRequested(); + + // If we got a match, here is what the match array will look like. Say the source text is: + // + // " // hack 1" + // + // The result array with the regexp: will be: + // + // ["// hack 1", "// ", "hack 1", undefined, "hack"] + // + // Here are the relevant capture groups: + // 0) The full match for hte entire regex. + // 1) The preamble to the message portion. + // 2) The message portion. + // 3...N) The descriptor that was matched - by index. 'undefined' for each + // descriptor that didn't match. an actual value if it did match. + // + // i.e. 'undefined' in position 3 above means TODO(jason) didn't match. + // "hack" in position 4 means HACK did match. + var firstDescriptorCaptureIndex = 3; + Debug.assert(matchArray.length === descriptors.length + firstDescriptorCaptureIndex); + + var preamble = matchArray[1]; + var matchPosition = matchArray.index + preamble.length; + + // Ok, we have found a match in the file. This is ony an acceptable match if + // it is contained within a comment. + var token = TypeScript.findToken(syntaxTree.sourceUnit(), matchPosition); + + if (matchPosition >= TypeScript.start(token) && matchPosition < TypeScript.end(token)) { + // match was within the token itself. Not in the comment. Keep searching + // descriptor. + continue; + } + + // Looks to be within the trivia. See if we can find hte comment containing it. + var triviaList = matchPosition < TypeScript.start(token) ? token.leadingTrivia(syntaxTree.text) : token.trailingTrivia(syntaxTree.text); + var trivia = this.findContainingComment(triviaList, matchPosition); + if (trivia === null) { + continue; + } + + var descriptor: TodoCommentDescriptor = undefined; + for (var i = 0, n = descriptors.length; i < n; i++) { + if (matchArray[i + firstDescriptorCaptureIndex]) { + descriptor = descriptors[i]; + } + } + Debug.assert(descriptor); + + // We don't want to match something like 'TODOBY', so we make sure a non + // letter/digit follows the match. + if (this.isLetterOrDigit(fileContents.charCodeAt(matchPosition + descriptor.text.length))) { + continue; + } + + var message = matchArray[2]; + result.push(new TodoComment(descriptor, message, matchPosition)); + } + } + + return result; + } + + function findContainingComment(triviaList: TypeScript.ISyntaxTriviaList, position: number): TypeScript.ISyntaxTrivia { + for (var i = 0, n = triviaList.count(); i < n; i++) { + var trivia = triviaList.syntaxTriviaAt(i); + var fullEnd = trivia.fullStart() + trivia.fullWidth(); + if (trivia.isComment() && trivia.fullStart() <= position && position < fullEnd) { + return trivia; + } + } + + return null; + } return { dispose: dispose, - refresh: () => { }, cleanupSemanticCache: cleanupSemanticCache, getSyntacticDiagnostics: getSyntacticDiagnostics, getSemanticDiagnostics: getSemanticDiagnostics, @@ -2239,7 +2442,8 @@ module ts { getCompletionsAtPosition: getCompletionsAtPosition, getCompletionEntryDetails: getCompletionEntryDetails, getTypeAtPosition: getTypeAtPosition, - getSignatureAtPosition: (filename, position): SignatureInfo => undefined, + getSignatureHelpItems: (filename, position): SignatureHelpItems => null, + getSignatureHelpCurrentArgumentState: (fileName, position, applicableSpanStart): SignatureHelpState => null, getDefinitionAtPosition: getDefinitionAtPosition, getReferencesAtPosition: (filename, position) => [], getOccurrencesAtPosition: (filename, position) => [], @@ -2247,15 +2451,16 @@ module ts { getNameOrDottedNameSpan: getNameOrDottedNameSpan, getBreakpointStatementAtPosition: getBreakpointStatementAtPosition, getNavigateToItems: (searchValue) => [], - getScriptLexicalStructure: getScriptLexicalStructure, - getOutliningRegions: getOutliningRegions, + getRenameInfo: (fileName, position): RenameInfo => null, + getNavigationBarItems: getNavigationBarItems, + getOutliningSpans: getOutliningSpans, + getTodoComments: getTodoComments, getBraceMatchingAtPosition: getBraceMatchingAtPosition, getIndentationAtPosition: getIndentationAtPosition, getFormattingEditsForRange: getFormattingEditsForRange, getFormattingEditsForDocument: getFormattingEditsForDocument, - getFormattingEditsOnPaste: getFormattingEditsOnPaste, getFormattingEditsAfterKeystroke: getFormattingEditsAfterKeystroke, - getEmitOutput: (filename): EmitOutput => undefined, + getEmitOutput: (filename): EmitOutput => null, }; } diff --git a/src/services/shims.ts b/src/services/shims.ts index d473a6979ee..d62b894f5a5 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -86,44 +86,51 @@ module ts { getTypeAtPosition(fileName: string, position: number): string; getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): string; getBreakpointStatementAtPosition(fileName: string, position: number): string; - getSignatureAtPosition(fileName: string, position: number): string; + + getSignatureHelpItems(fileName: string, position: number): string; + getSignatureHelpCurrentArgumentState(fileName: string, position: number, applicableSpanStart: number): string; // Returns a JSON encoded value of the type: - // { fileName: string; minChar: number; limChar: number; kind: string; name: string; containerKind: string; containerName: string } + // { canRename: boolean, localizedErrorMessage: string, displayName: string, fullDisplayName: string, kind: string, kindModifiers: string, triggerSpan: { start; length } } + getRenameInfo(fileName: string, position: number): string; + + // Returns a JSON encoded value of the type: + // { fileName: string; textSpan: { start: number; length: number}; kind: string; name: string; containerKind: string; containerName: string } // // Or null value if no definition can be found. getDefinitionAtPosition(fileName: string, position: number): string; // Returns a JSON encoded value of the type: - // { fileName: string; minChar: number; limChar: number; isWriteAccess: boolean }[] + // { fileName: string; textSpan: { start: number; length: number}; isWriteAccess: boolean }[] getReferencesAtPosition(fileName: string, position: number): string; // Returns a JSON encoded value of the type: - // { fileName: string; minChar: number; limChar: number; isWriteAccess: boolean }[] + // { fileName: string; textSpan: { start: number; length: number}; isWriteAccess: boolean }[] getOccurrencesAtPosition(fileName: string, position: number): string; // Returns a JSON encoded value of the type: - // { fileName: string; minChar: number; limChar: number; isWriteAccess: boolean }[] + // { fileName: string; textSpan: { start: number; length: number}; isWriteAccess: boolean }[] getImplementorsAtPosition(fileName: string, position: number): string; // Returns a JSON encoded value of the type: - // { name: string; kind: string; kindModifiers: string; containerName: string; containerKind: string; matchKind: string; fileName: string; minChar: number; limChar: number; } [] = []; + // { name: string; kind: string; kindModifiers: string; containerName: string; containerKind: string; matchKind: string; fileName: string; textSpan: { start: number; length: number}; } [] = []; getNavigateToItems(searchValue: string): string; // Returns a JSON encoded value of the type: - // { name: string; kind: string; kindModifiers: string; containerName: string; containerKind: string; matchKind: string; fileName: string; minChar: number; limChar: number; } [] = []; - getScriptLexicalStructure(fileName: string): string; + // { text: string; kind: string; kindModifiers: string; bolded: boolean; grayed: boolean; indent: number; spans: { start: number; length: number; }[]; childItems: [] } [] = []; + getNavigationBarItems(fileName: string): string; // Returns a JSON encoded value of the type: - // { name: string; kind: string; kindModifiers: string; containerName: string; containerKind: string; matchKind: string; fileName: string; minChar: number; limChar: number; } [] = []; - getOutliningRegions(fileName: string): string; + // { textSpan: { start: number, length: number }; hintSpan: { start: number, length: number }; bannerText: string; autoCollapse: boolean } [] = []; + getOutliningSpans(fileName: string): string; + + getTodoComments(fileName: string, todoCommentDescriptors: string): string; getBraceMatchingAtPosition(fileName: string, position: number): string; getIndentationAtPosition(fileName: string, position: number, options: string/*Services.EditorOptions*/): string; - getFormattingEditsForRange(fileName: string, minChar: number, limChar: number, options: string/*Services.FormatCodeOptions*/): string; - getFormattingEditsForDocument(fileName: string, minChar: number, limChar: number, options: string/*Services.FormatCodeOptions*/): string; - getFormattingEditsOnPaste(fileName: string, minChar: number, limChar: number, options: string/*Services.FormatCodeOptions*/): string; + getFormattingEditsForRange(fileName: string, start: number, end: number, options: string/*Services.FormatCodeOptions*/): string; + getFormattingEditsForDocument(fileName: string, options: string/*Services.FormatCodeOptions*/): string; getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: string/*Services.FormatCodeOptions*/): string; getEmitOutput(fileName: string): string; @@ -456,7 +463,6 @@ module ts { this.forwardJSONCall( "refresh(" + throwOnError + ")", () => { - this.languageService.refresh(); return null; }); } @@ -469,8 +475,6 @@ module ts { return null; }); } - /// SQUIGGLES - /// private static realizeDiagnostic(diagnostic: Diagnostic): { message: string; start: number; length: number; category: string; } { return { @@ -495,7 +499,7 @@ module ts { public getSyntacticDiagnostics(fileName: string): string { return this.forwardJSONCall( - "getSyntacticDiagnostics(\"" + fileName + "\")", + "getSyntacticDiagnostics('" + fileName + "')", () => { var errors = this.languageService.getSyntacticDiagnostics(fileName); return errors.map(LanguageServiceShimObject.realizeDiagnostic); @@ -504,7 +508,7 @@ module ts { public getSemanticDiagnostics(fileName: string): string { return this.forwardJSONCall( - "getSemanticDiagnostics(\"" + fileName + "\")", + "getSemanticDiagnostics('" + fileName + "')", () => { var errors = this.languageService.getSemanticDiagnostics(fileName); return errors.map(LanguageServiceShimObject.realizeDiagnostic); @@ -525,7 +529,7 @@ module ts { /// in the active file. public getTypeAtPosition(fileName: string, position: number): string { return this.forwardJSONCall( - "getTypeAtPosition(\"" + fileName + "\", " + position + ")", + "getTypeAtPosition('" + fileName + "', " + position + ")", () => { var typeInfo = this.languageService.getTypeAtPosition(fileName, position); return typeInfo; @@ -537,7 +541,7 @@ module ts { // in the active file. public getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): string { return this.forwardJSONCall( - "getNameOrDottedNameSpan(\"" + fileName + "\", " + startPos + ", " + endPos + ")", + "getNameOrDottedNameSpan('" + fileName + "', " + startPos + ", " + endPos + ")", () => { var spanInfo = this.languageService.getNameOrDottedNameSpan(fileName, startPos, endPos); return spanInfo; @@ -548,7 +552,7 @@ module ts { /// Computes span information of statement at the requested position in the active file. public getBreakpointStatementAtPosition(fileName: string, position: number): string { return this.forwardJSONCall( - "getBreakpointStatementAtPosition(\"" + fileName + "\", " + position + ")", + "getBreakpointStatementAtPosition('" + fileName + "', " + position + ")", () => { var spanInfo = this.languageService.getBreakpointStatementAtPosition(fileName, position); return spanInfo; @@ -556,32 +560,49 @@ module ts { } /// SIGNATUREHELP - /// Computes a string representation of the signatures at the requested position - /// in the active file. - public getSignatureAtPosition(fileName: string, position: number): string { + + public getSignatureHelpItems(fileName: string, position: number): string { return this.forwardJSONCall( - "getSignatureAtPosition(\"" + fileName + "\", " + position + ")", + "getSignatureHelpItems('" + fileName + "', " + position + ")", () => { - var signatureInfo = this.languageService.getSignatureAtPosition(fileName, position); + var signatureInfo = this.languageService.getSignatureHelpItems(fileName, position); return signatureInfo; }); } + public getSignatureHelpCurrentArgumentState(fileName: string, position: number, applicableSpanStart: number): string { + return this.forwardJSONCall( + "getSignatureHelpCurrentArgumentState('" + fileName + "', " + position + ", " + applicableSpanStart + ")", + () => { + var signatureInfo = this.languageService.getSignatureHelpItems(fileName, position); + return signatureInfo; + }); + } + + /// GOTO DEFINITION /// Computes the definition location and file for the symbol /// at the requested position. public getDefinitionAtPosition(fileName: string, position: number): string { return this.forwardJSONCall( - "getDefinitionAtPosition(\"" + fileName + "\", " + position + ")", + "getDefinitionAtPosition('" + fileName + "', " + position + ")", () => { return this.languageService.getDefinitionAtPosition(fileName, position); }); } + public getRenameInfo(fileName: string, position: number): string { + return this.forwardJSONCall( + "getRenameInfo('" + fileName + "', " + position + ")", + () => { + return this.languageService.getRenameInfo(fileName, position); + }); + } + /// GET BRACE MATCHING public getBraceMatchingAtPosition(fileName: string, position: number): string { return this.forwardJSONCall( - "getBraceMatchingAtPosition(\"" + fileName + "\", " + position + ")", + "getBraceMatchingAtPosition('" + fileName + "', " + position + ")", () => { var textRanges = this.languageService.getBraceMatchingAtPosition(fileName, position); return textRanges; @@ -591,7 +612,7 @@ module ts { /// GET SMART INDENT public getIndentationAtPosition(fileName: string, position: number, options: string /*Services.EditorOptions*/): string { return this.forwardJSONCall( - "getIndentationAtPosition(\"" + fileName + "\", " + position + ")", + "getIndentationAtPosition('" + fileName + "', " + position + ")", () => { var localOptions: EditorOptions = JSON.parse(options); var columnOffset = this.languageService.getIndentationAtPosition(fileName, position, localOptions); @@ -605,7 +626,7 @@ module ts { /// Each reference is a "fileindex min lim" sub-string. public getReferencesAtPosition(fileName: string, position: number): string { return this.forwardJSONCall( - "getReferencesAtPosition(\"" + fileName + "\", " + position + ")", + "getReferencesAtPosition('" + fileName + "', " + position + ")", () => { return this.languageService.getReferencesAtPosition(fileName, position); }); @@ -613,7 +634,7 @@ module ts { public getOccurrencesAtPosition(fileName: string, position: number): string { return this.forwardJSONCall( - "getOccurrencesAtPosition(\"" + fileName + "\", " + position + ")", + "getOccurrencesAtPosition('" + fileName + "', " + position + ")", () => { return this.languageService.getOccurrencesAtPosition(fileName, position); }); @@ -622,7 +643,7 @@ module ts { /// GET IMPLEMENTORS public getImplementorsAtPosition(fileName: string, position: number): string { return this.forwardJSONCall( - "getImplementorsAtPosition(\"" + fileName + "\", " + position + ")", + "getImplementorsAtPosition('" + fileName + "', " + position + ")", () => { return this.languageService.getImplementorsAtPosition(fileName, position); }); @@ -635,7 +656,7 @@ module ts { /// list if requested. public getCompletionsAtPosition(fileName: string, position: number, isMemberCompletion: boolean) { return this.forwardJSONCall( - "getCompletionsAtPosition(\"" + fileName + "\", " + position + ", " + isMemberCompletion + ")", + "getCompletionsAtPosition('" + fileName + "', " + position + ", " + isMemberCompletion + ")", () => { var completion = this.languageService.getCompletionsAtPosition(fileName, position, isMemberCompletion); return completion; @@ -645,52 +666,38 @@ module ts { /// Get a string based representation of a completion list entry details public getCompletionEntryDetails(fileName: string, position: number, entryName: string) { return this.forwardJSONCall( - "getCompletionEntryDetails(\"" + fileName + "\", " + position + ", " + entryName + ")", + "getCompletionEntryDetails('" + fileName + "', " + position + ", " + entryName + ")", () => { var details = this.languageService.getCompletionEntryDetails(fileName, position, entryName); return details; }); } - /// FORMAT SELECTION - public getFormattingEditsForRange(fileName: string, minChar: number, limChar: number, options: string/*Services.FormatCodeOptions*/): string { + public getFormattingEditsForRange(fileName: string, start: number, end: number, options: string/*Services.FormatCodeOptions*/): string { return this.forwardJSONCall( - "getFormattingEditsForRange(\"" + fileName + "\", " + minChar + ", " + limChar + ")", + "getFormattingEditsForRange('" + fileName + "', " + start + ", " + end + ")", () => { - var localOptions: FormatCodeOptions = JSON.parse(options); - var edits = this.languageService.getFormattingEditsForRange(fileName, minChar, limChar, localOptions); + var localOptions: ts.FormatCodeOptions = JSON.parse(options); + var edits = this.languageService.getFormattingEditsForRange(fileName, start, end, localOptions); return edits; }); } - /// FORMAT DOCUMENT - public getFormattingEditsForDocument(fileName: string, minChar: number, limChar: number, options: string/*Services.FormatCodeOptions*/): string { + public getFormattingEditsForDocument(fileName: string, options: string/*Services.FormatCodeOptions*/): string { return this.forwardJSONCall( - "getFormattingEditsForDocument(\"" + fileName + "\", " + minChar + ", " + limChar + ")", + "getFormattingEditsForDocument('" + fileName + "')", () => { - var localOptions: FormatCodeOptions = JSON.parse(options); - var edits = this.languageService.getFormattingEditsForDocument(fileName, minChar, limChar, localOptions); + var localOptions: ts.FormatCodeOptions = JSON.parse(options); + var edits = this.languageService.getFormattingEditsForDocument(fileName, localOptions); return edits; }); } - /// FORMAT ON PASTE - public getFormattingEditsOnPaste(fileName: string, minChar: number, limChar: number, options: string/*Services.FormatCodeOptions*/): string { - return this.forwardJSONCall( - "getFormattingEditsOnPaste(\"" + fileName + "\", " + minChar + ", " + limChar + ")", - () => { - var localOptions: FormatCodeOptions = JSON.parse(options); - var edits = this.languageService.getFormattingEditsOnPaste(fileName, minChar, limChar, localOptions); - return edits; - }); - } - - /// FORMAT public getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: string/*Services.FormatCodeOptions*/): string { return this.forwardJSONCall( - "getFormattingEditsAfterKeystroke(\"" + fileName + "\", " + position + ", \"" + key + "\")", + "getFormattingEditsAfterKeystroke('" + fileName + "', " + position + ", '" + key + "')", () => { - var localOptions: FormatCodeOptions = JSON.parse(options); + var localOptions: ts.FormatCodeOptions = JSON.parse(options); var edits = this.languageService.getFormattingEditsAfterKeystroke(fileName, position, key, localOptions); return edits; }); @@ -700,83 +707,49 @@ module ts { /// Return a list of symbols that are interesting to navigate to public getNavigateToItems(searchValue: string): string { return this.forwardJSONCall( - "getNavigateToItems(\"" + searchValue + "\")", + "getNavigateToItems('" + searchValue + "')", () => { var items = this.languageService.getNavigateToItems(searchValue); - var result = this._navigateToItemsToString(items); - return result; + return items; }); } - // GET SCRIPT LEXICAL STRUCTURE - // - public getScriptLexicalStructure(fileName: string): string { + public getNavigationBarItems(fileName: string): string { return this.forwardJSONCall( - "getScriptLexicalStructure(\"" + fileName + "\")", + "getNavigationBarItems('" + fileName + "')", () => { - var items = this.languageService.getScriptLexicalStructure(fileName); - var result = this._navigateToItemsToString(items); - return result; + var items = this.languageService.getNavigationBarItems(fileName); + return items; }); } - // GET OUTLINING REGIONS - // - public getOutliningRegions(fileName: string): string { + public getOutliningSpans(fileName: string): string { return this.forwardJSONCall( - "getOutliningRegions(\"" + fileName + "\")", + "getOutliningSpans('" + fileName + "')", () => { - var items = this.languageService.getOutliningRegions(fileName); - // return just the part of data that language service v2 can understand - // language service v2 will use the entire OutliningSpan - var spans = map(items, i => i.textSpan); - return spans; + var items = this.languageService.getOutliningSpans(fileName); + return items; + }); + } + + public getTodoComments(fileName: string, descriptors: string): string { + return this.forwardJSONCall( + "getTodoComments('" + fileName + "')", + () => { + var items = this.languageService.getTodoComments(fileName, JSON.parse(descriptors)); + return items; }); } /// Emit public getEmitOutput(fileName: string): string { return this.forwardJSONCall( - "getEmitOutput(\"" + fileName + "\")", + "getEmitOutput('" + fileName + "')", () => { var output = this.languageService.getEmitOutput(fileName); return output; }); } - - private _navigateToItemsToString(items: NavigateToItem[]): any { - var result: { - name: string; - kind: string; - kindModifiers: string; - containerName: string; - containerKind: string; - matchKind: string; - fileName: string; - minChar: number; - limChar: number; - additionalSpans?: { start: number; end: number; }[]; - }[] = []; - - for (var i = 0; i < items.length; i++) { - var item = items[i]; - - result.push({ - name: item.name, - kind: item.kind, - kindModifiers: item.kindModifiers, - containerName: item.containerName, - containerKind: item.containerKind, - matchKind: item.matchKind, - fileName: item.fileName, - minChar: item.minChar, - limChar: item.limChar, - additionalSpans: item.additionalSpans ? item.additionalSpans.map(i => { return { start: i.minChar, end: i.limChar }; }) : undefined - }); - } - - return result; - } } class ClassifierShimObject extends ShimBase implements ClassifierShim { @@ -815,7 +788,7 @@ module ts { /// public getPreProcessedFileInfo(fileName: string, sourceText: TypeScript.IScriptSnapshot): string { return this.forwardJSONCall( - "getPreProcessedFileInfo(\"" + fileName + "\")", + "getPreProcessedFileInfo('" + fileName + "')", () => { var result = TypeScript.preProcessFile(fileName, sourceText); return result;