typescript-language-server
Advanced tools
Comparing version 1.0.0 to 1.1.0
# Changelog | ||
All notable changes to this project will be documented in this file. | ||
## [1.1.0](https://github.com/typescript-language-server/typescript-language-server/compare/v1.0.0...v1.1.0) (2022-08-21) | ||
### Features | ||
* add "Go To Source Definition" command ([#560](https://github.com/typescript-language-server/typescript-language-server/issues/560)) ([9bcdaf2](https://github.com/typescript-language-server/typescript-language-server/commit/9bcdaf2b0b09da9aa4d7e6ed79bdcd742b3cfc17)) | ||
* support `textDocument/inlayHint` request from 3.17.0 spec ([#566](https://github.com/typescript-language-server/typescript-language-server/issues/566)) ([9a2fd4e](https://github.com/typescript-language-server/typescript-language-server/commit/9a2fd4e34b6c50c57b974f617018dcefdb469788)) | ||
* support LocationLink[] for textDocument/definition response ([#563](https://github.com/typescript-language-server/typescript-language-server/issues/563)) ([196f328](https://github.com/typescript-language-server/typescript-language-server/commit/196f328cd9fd7a06998151d59bed0b945cc68b40)) | ||
### Bug Fixes | ||
* don't trigger error on empty Source Definition response ([#568](https://github.com/typescript-language-server/typescript-language-server/issues/568)) ([146a6ba](https://github.com/typescript-language-server/typescript-language-server/commit/146a6ba97f0792701ff8afcc431d3a1dfdb978a6)) | ||
* make wording in the typescript lookup error more generic ([585a05e](https://github.com/typescript-language-server/typescript-language-server/commit/585a05e43a0b530f10e488aed634fac0436109ae)), closes [#554](https://github.com/typescript-language-server/typescript-language-server/issues/554) | ||
* snippet completions returned to clients that don't support them ([#556](https://github.com/typescript-language-server/typescript-language-server/issues/556)) ([050d335](https://github.com/typescript-language-server/typescript-language-server/commit/050d3350e16fe78b7c60d7443ed3ad6d2cc4730d)) | ||
* update signature help feature to v3.15.0 LSP spec ([#555](https://github.com/typescript-language-server/typescript-language-server/issues/555)) ([da074a6](https://github.com/typescript-language-server/typescript-language-server/commit/da074a618ca6c29819834a0344682094d6ff08f6)) | ||
## [1.0.0](https://github.com/typescript-language-server/typescript-language-server/compare/v0.11.2...v1.0.0) (2022-08-06) | ||
@@ -5,0 +22,0 @@ |
import * as lsp from 'vscode-languageserver'; | ||
import { TextDocument } from 'vscode-languageserver-textdocument'; | ||
import * as lspcalls from './lsp-protocol.calls.proposed.js'; | ||
import { TspClient } from './tsp-client.js'; | ||
import { TextDocument } from 'vscode-languageserver-textdocument'; | ||
export declare function computeCallers(tspClient: TspClient, args: lsp.TextDocumentPositionParams): Promise<lspcalls.CallsResult>; | ||
@@ -6,0 +6,0 @@ export declare type DocumentProvider = (file: string) => TextDocument | undefined; |
304
lib/calls.js
@@ -1,159 +0,141 @@ | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
import * as lsp from 'vscode-languageserver'; | ||
import * as lspcalls from './lsp-protocol.calls.proposed.js'; | ||
import { uriToPath, toLocation, asRange, Range, toSymbolKind, pathToUri } from './protocol-translation.js'; | ||
export function computeCallers(tspClient, args) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const nullResult = { calls: [] }; | ||
const contextDefinition = yield getDefinition(tspClient, args); | ||
if (!contextDefinition) { | ||
return nullResult; | ||
import { uriToPath, toLocation, toSymbolKind, pathToUri } from './protocol-translation.js'; | ||
import { Range } from './utils/typeConverters.js'; | ||
export async function computeCallers(tspClient, args) { | ||
const nullResult = { calls: [] }; | ||
const contextDefinition = await getDefinition(tspClient, args); | ||
if (!contextDefinition) { | ||
return nullResult; | ||
} | ||
const contextSymbol = await findEnclosingSymbol(tspClient, contextDefinition); | ||
if (!contextSymbol) { | ||
return nullResult; | ||
} | ||
const callerReferences = await findNonDefinitionReferences(tspClient, contextDefinition); | ||
const calls = []; | ||
for (const callerReference of callerReferences) { | ||
const symbol = await findEnclosingSymbol(tspClient, callerReference); | ||
if (!symbol) { | ||
continue; | ||
} | ||
const contextSymbol = yield findEnclosingSymbol(tspClient, contextDefinition); | ||
if (!contextSymbol) { | ||
return nullResult; | ||
const location = toLocation(callerReference, undefined); | ||
calls.push({ | ||
location, | ||
symbol, | ||
}); | ||
} | ||
return { calls, symbol: contextSymbol }; | ||
} | ||
export async function computeCallees(tspClient, args, documentProvider) { | ||
const nullResult = { calls: [] }; | ||
const contextDefinition = await getDefinition(tspClient, args); | ||
if (!contextDefinition) { | ||
return nullResult; | ||
} | ||
const contextSymbol = await findEnclosingSymbol(tspClient, contextDefinition); | ||
if (!contextSymbol) { | ||
return nullResult; | ||
} | ||
const outgoingCallReferences = await findOutgoingCalls(tspClient, contextSymbol, documentProvider); | ||
const calls = []; | ||
for (const reference of outgoingCallReferences) { | ||
const definitionReferences = await findDefinitionReferences(tspClient, reference); | ||
const definitionReference = definitionReferences[0]; | ||
if (!definitionReference) { | ||
continue; | ||
} | ||
const callerReferences = yield findNonDefinitionReferences(tspClient, contextDefinition); | ||
const calls = []; | ||
for (const callerReference of callerReferences) { | ||
const symbol = yield findEnclosingSymbol(tspClient, callerReference); | ||
if (!symbol) { | ||
continue; | ||
} | ||
const location = toLocation(callerReference, undefined); | ||
calls.push({ | ||
location, | ||
symbol | ||
}); | ||
const definitionSymbol = await findEnclosingSymbol(tspClient, definitionReference); | ||
if (!definitionSymbol) { | ||
continue; | ||
} | ||
return { calls, symbol: contextSymbol }; | ||
}); | ||
const location = toLocation(reference, undefined); | ||
calls.push({ | ||
location, | ||
symbol: definitionSymbol, | ||
}); | ||
} | ||
return { calls, symbol: contextSymbol }; | ||
} | ||
export function computeCallees(tspClient, args, documentProvider) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const nullResult = { calls: [] }; | ||
const contextDefinition = yield getDefinition(tspClient, args); | ||
if (!contextDefinition) { | ||
return nullResult; | ||
} | ||
const contextSymbol = yield findEnclosingSymbol(tspClient, contextDefinition); | ||
if (!contextSymbol) { | ||
return nullResult; | ||
} | ||
const outgoingCallReferences = yield findOutgoingCalls(tspClient, contextSymbol, documentProvider); | ||
const calls = []; | ||
for (const reference of outgoingCallReferences) { | ||
const definitionReferences = yield findDefinitionReferences(tspClient, reference); | ||
const definitionReference = definitionReferences[0]; | ||
if (!definitionReference) { | ||
continue; | ||
async function findOutgoingCalls(tspClient, contextSymbol, documentProvider) { | ||
/** | ||
* The TSP does not provide call references. | ||
* As long as we are not able to access the AST in a tsserver plugin and return the information necessary as metadata to the reponse, | ||
* we need to test possible calls. | ||
*/ | ||
const computeCallCandidates = (document, range) => { | ||
const symbolText = document.getText(range); | ||
const regex = /\W([$_a-zA-Z0-9\u{00C0}-\u{E007F}]+)(<.*>)?\(/gmu; // Example: matches `candidate` in " candidate()", "Foo.candidate<T>()", etc. | ||
let match = regex.exec(symbolText); | ||
const candidates = []; | ||
while (match) { | ||
const identifier = match[1]; | ||
if (identifier) { | ||
const start = match.index + match[0].indexOf(identifier); | ||
const end = start + identifier.length; | ||
candidates.push({ identifier, start, end }); | ||
} | ||
const definitionSymbol = yield findEnclosingSymbol(tspClient, definitionReference); | ||
if (!definitionSymbol) { | ||
continue; | ||
} | ||
const location = toLocation(reference, undefined); | ||
calls.push({ | ||
location, | ||
symbol: definitionSymbol | ||
}); | ||
match = regex.exec(symbolText); | ||
} | ||
return { calls, symbol: contextSymbol }; | ||
}); | ||
} | ||
function findOutgoingCalls(tspClient, contextSymbol, documentProvider) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
/** | ||
* The TSP does not provide call references. | ||
* As long as we are not able to access the AST in a tsserver plugin and return the information necessary as metadata to the reponse, | ||
* we need to test possible calls. | ||
*/ | ||
const computeCallCandidates = (document, range) => { | ||
const symbolText = document.getText(range); | ||
const regex = /\W([$_a-zA-Z0-9\u{00C0}-\u{E007F}]+)(<.*>)?\(/gmu; // Example: matches `candidate` in " candidate()", "Foo.candidate<T>()", etc. | ||
let match = regex.exec(symbolText); | ||
const candidates = []; | ||
while (match) { | ||
const identifier = match[1]; | ||
if (identifier) { | ||
const start = match.index + match[0].indexOf(identifier); | ||
const end = start + identifier.length; | ||
candidates.push({ identifier, start, end }); | ||
} | ||
match = regex.exec(symbolText); | ||
} | ||
const offset = document.offsetAt(range.start); | ||
const candidateRanges = candidates.map(c => lsp.Range.create(document.positionAt(offset + c.start), document.positionAt(offset + c.end))); | ||
return candidateRanges; | ||
}; | ||
/** | ||
* This function tests a candidate and returns a locaion for a valid call. | ||
*/ | ||
const validateCall = (file, candidateRange) => __awaiter(this, void 0, void 0, function* () { | ||
const offset = document.offsetAt(range.start); | ||
const candidateRanges = candidates.map(c => lsp.Range.create(document.positionAt(offset + c.start), document.positionAt(offset + c.end))); | ||
return candidateRanges; | ||
}; | ||
/** | ||
* This function tests a candidate and returns a locaion for a valid call. | ||
*/ | ||
const validateCall = async (file, candidateRange) => { | ||
const tspPosition = { line: candidateRange.start.line + 1, offset: candidateRange.start.character + 1 }; | ||
const references = await findNonDefinitionReferences(tspClient, { file, start: tspPosition, end: tspPosition }); | ||
for (const reference of references) { | ||
const tspPosition = { line: candidateRange.start.line + 1, offset: candidateRange.start.character + 1 }; | ||
const references = yield findNonDefinitionReferences(tspClient, { file, start: tspPosition, end: tspPosition }); | ||
for (const reference of references) { | ||
const tspPosition = { line: candidateRange.start.line + 1, offset: candidateRange.start.character + 1 }; | ||
if (tspPosition.line === reference.start.line) { | ||
return reference; | ||
} | ||
if (tspPosition.line === reference.start.line) { | ||
return reference; | ||
} | ||
}); | ||
const calls = []; | ||
const file = uriToPath(contextSymbol.location.uri); | ||
const document = documentProvider(file); | ||
if (!document) { | ||
return calls; | ||
} | ||
const candidateRanges = computeCallCandidates(document, contextSymbol.location.range); | ||
for (const candidateRange of candidateRanges) { | ||
const call = yield validateCall(file, candidateRange); | ||
if (call) { | ||
calls.push(call); | ||
} | ||
} | ||
}; | ||
const calls = []; | ||
const file = uriToPath(contextSymbol.location.uri); | ||
const document = documentProvider(file); | ||
if (!document) { | ||
return calls; | ||
}); | ||
} | ||
function getDefinition(tspClient, args) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const file = uriToPath(args.textDocument.uri); | ||
if (!file) { | ||
return undefined; | ||
} | ||
const candidateRanges = computeCallCandidates(document, contextSymbol.location.range); | ||
for (const candidateRange of candidateRanges) { | ||
const call = await validateCall(file, candidateRange); | ||
if (call) { | ||
calls.push(call); | ||
} | ||
const definitionResult = yield tspClient.request("definition" /* CommandTypes.Definition */, { | ||
file, | ||
line: args.position.line + 1, | ||
offset: args.position.character + 1 | ||
}); | ||
return definitionResult.body ? definitionResult.body[0] : undefined; | ||
}); | ||
} | ||
return calls; | ||
} | ||
function findEnclosingSymbol(tspClient, args) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const file = args.file; | ||
const response = yield tspClient.request("navtree" /* CommandTypes.NavTree */, { file }); | ||
const tree = response.body; | ||
if (!tree || !tree.childItems) { | ||
return undefined; | ||
} | ||
const pos = lsp.Position.create(args.start.line - 1, args.start.offset - 1); | ||
const symbol = yield findEnclosingSymbolInTree(tree, lsp.Range.create(pos, pos)); | ||
if (!symbol) { | ||
return undefined; | ||
} | ||
const uri = pathToUri(file, undefined); | ||
return lspcalls.DefinitionSymbol.create(uri, symbol); | ||
async function getDefinition(tspClient, args) { | ||
const file = uriToPath(args.textDocument.uri); | ||
if (!file) { | ||
return undefined; | ||
} | ||
const definitionResult = await tspClient.request("definition" /* CommandTypes.Definition */, { | ||
file, | ||
line: args.position.line + 1, | ||
offset: args.position.character + 1, | ||
}); | ||
return definitionResult.body ? definitionResult.body[0] : undefined; | ||
} | ||
async function findEnclosingSymbol(tspClient, args) { | ||
const file = args.file; | ||
const response = await tspClient.request("navtree" /* CommandTypes.NavTree */, { file }); | ||
const tree = response.body; | ||
if (!tree || !tree.childItems) { | ||
return undefined; | ||
} | ||
const pos = lsp.Position.create(args.start.line - 1, args.start.offset - 1); | ||
const symbol = findEnclosingSymbolInTree(tree, lsp.Range.create(pos, pos)); | ||
if (!symbol) { | ||
return undefined; | ||
} | ||
const uri = pathToUri(file, undefined); | ||
return lspcalls.DefinitionSymbol.create(uri, symbol); | ||
} | ||
function findEnclosingSymbolInTree(parent, range) { | ||
const inSpan = (span) => !!Range.intersection(asRange(span), range); | ||
const inSpan = (span) => !!Range.intersection(Range.fromTextSpan(span), range); | ||
const inTree = (tree) => tree.spans.some(span => inSpan(span)); | ||
@@ -175,6 +157,6 @@ let candidate = inTree(parent) ? parent : undefined; | ||
const span = candidate.spans.find(span => inSpan(span)); | ||
const spanRange = asRange(span); | ||
const spanRange = Range.fromTextSpan(span); | ||
let selectionRange = spanRange; | ||
if (candidate.nameSpan) { | ||
const nameRange = asRange(candidate.nameSpan); | ||
const nameRange = Range.fromTextSpan(candidate.nameSpan); | ||
if (Range.intersection(spanRange, nameRange)) { | ||
@@ -188,29 +170,23 @@ selectionRange = nameRange; | ||
range: spanRange, | ||
selectionRange: selectionRange | ||
selectionRange: selectionRange, | ||
}; | ||
} | ||
function findDefinitionReferences(tspClient, args) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
return (yield findReferences(tspClient, args)).filter(ref => ref.isDefinition); | ||
}); | ||
async function findDefinitionReferences(tspClient, args) { | ||
return (await findReferences(tspClient, args)).filter(ref => ref.isDefinition); | ||
} | ||
function findNonDefinitionReferences(tspClient, args) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
return (yield findReferences(tspClient, args)).filter(ref => !ref.isDefinition); | ||
}); | ||
async function findNonDefinitionReferences(tspClient, args) { | ||
return (await findReferences(tspClient, args)).filter(ref => !ref.isDefinition); | ||
} | ||
function findReferences(tspClient, args) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const file = args.file; | ||
const result = yield tspClient.request("references" /* CommandTypes.References */, { | ||
file, | ||
line: args.start.line, | ||
offset: args.start.offset | ||
}); | ||
if (!result.body) { | ||
return []; | ||
} | ||
return result.body.refs; | ||
async function findReferences(tspClient, args) { | ||
const file = args.file; | ||
const result = await tspClient.request("references" /* CommandTypes.References */, { | ||
file, | ||
line: args.start.line, | ||
offset: args.start.offset, | ||
}); | ||
if (!result.body) { | ||
return []; | ||
} | ||
return result.body.refs; | ||
} | ||
//# sourceMappingURL=calls.js.map |
@@ -39,4 +39,4 @@ #!/usr/bin/env node | ||
tsserverLogVerbosity: options.tsserverLogVerbosity, | ||
showMessageLevel: logLevel | ||
showMessageLevel: logLevel, | ||
}).listen(); | ||
//# sourceMappingURL=cli.js.map |
@@ -10,3 +10,4 @@ export declare const Commands: { | ||
SELECT_REFACTORING: string; | ||
SOURCE_DEFINITION: string; | ||
}; | ||
//# sourceMappingURL=commands.d.ts.map |
@@ -7,2 +7,3 @@ /* | ||
*/ | ||
import { SourceDefinitionCommand } from './features/source-definition.js'; | ||
export const Commands = { | ||
@@ -16,4 +17,5 @@ APPLY_WORKSPACE_EDIT: '_typescript.applyWorkspaceEdit', | ||
/** Commands below should be implemented by the client */ | ||
SELECT_REFACTORING: '_typescript.selectRefactoring' | ||
SELECT_REFACTORING: '_typescript.selectRefactoring', | ||
SOURCE_DEFINITION: SourceDefinitionCommand.id, | ||
}; | ||
//# sourceMappingURL=commands.js.map |
@@ -5,11 +5,8 @@ import * as lsp from 'vscode-languageserver'; | ||
import { TspClient } from './tsp-client.js'; | ||
import { CompletionOptions, SupportedFeatures } from './ts-protocol.js'; | ||
interface TSCompletionItem extends lsp.CompletionItem { | ||
data: tsp.CompletionDetailsRequestArgs; | ||
} | ||
export declare function asCompletionItem(entry: tsp.CompletionEntry, file: string, position: lsp.Position, document: LspDocument, features: SupportedFeatures): TSCompletionItem; | ||
export declare function asResolvedCompletionItem(item: lsp.CompletionItem, details: tsp.CompletionEntryDetails, client: TspClient, options: CompletionOptions): Promise<lsp.CompletionItem>; | ||
import { SupportedFeatures } from './ts-protocol.js'; | ||
import type { WorkspaceConfigurationCompletionOptions } from './configuration-manager.js'; | ||
export declare function asCompletionItem(entry: tsp.CompletionEntry, file: string, position: lsp.Position, document: LspDocument, features: SupportedFeatures): lsp.CompletionItem | null; | ||
export declare function asResolvedCompletionItem(item: lsp.CompletionItem, details: tsp.CompletionEntryDetails, client: TspClient, options: WorkspaceConfigurationCompletionOptions, features: SupportedFeatures): Promise<lsp.CompletionItem>; | ||
export declare function isValidFunctionCompletionContext(filepath: string, position: lsp.Position, client: TspClient): Promise<boolean>; | ||
export declare function getCompletionTriggerCharacter(character: string | undefined): tsp.CompletionsTriggerCharacter | undefined; | ||
export {}; | ||
//# sourceMappingURL=completion.d.ts.map |
@@ -7,20 +7,18 @@ /* | ||
*/ | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
import * as lsp from 'vscode-languageserver'; | ||
import { KindModifiers, ScriptElementKind } from './tsp-command-types.js'; | ||
import { asRange, toTextEdit, asPlainText, asDocumentation, normalizePath } from './protocol-translation.js'; | ||
import { toTextEdit, asPlainText, asDocumentation, normalizePath } from './protocol-translation.js'; | ||
import { Commands } from './commands.js'; | ||
import { DisplayPartKind } from './ts-protocol.js'; | ||
import SnippetString from './utils/SnippetString.js'; | ||
import * as typeConverters from './utils/typeConverters.js'; | ||
import { Range, Position } from './utils/typeConverters.js'; | ||
export function asCompletionItem(entry, file, position, document, features) { | ||
const item = Object.assign(Object.assign({ label: entry.name }, features.labelDetails ? { labelDetails: entry.labelDetails } : {}), { kind: asCompletionItemKind(entry.kind), sortText: entry.sortText, commitCharacters: asCommitCharacters(entry.kind), preselect: entry.isRecommended, data: { | ||
const item = { | ||
label: entry.name, | ||
...features.completionLabelDetails ? { labelDetails: entry.labelDetails } : {}, | ||
kind: asCompletionItemKind(entry.kind), | ||
sortText: entry.sortText, | ||
commitCharacters: asCommitCharacters(entry.kind), | ||
preselect: entry.isRecommended, | ||
data: { | ||
file, | ||
@@ -33,6 +31,7 @@ line: position.line + 1, | ||
source: entry.source, | ||
data: entry.data | ||
} : entry.name | ||
] | ||
} }); | ||
data: entry.data, | ||
} : entry.name, | ||
], | ||
}, | ||
}; | ||
if (entry.source && entry.hasAction) { | ||
@@ -43,11 +42,15 @@ // De-prioritze auto-imports | ||
} | ||
const { sourceDisplay, isSnippet } = entry; | ||
const { isSnippet, sourceDisplay } = entry; | ||
if (isSnippet && !features.completionSnippets) { | ||
return null; | ||
} | ||
if (features.completionSnippets && (isSnippet || entry.isImportStatementCompletion || item.kind === lsp.CompletionItemKind.Function || item.kind === lsp.CompletionItemKind.Method)) { | ||
// Import statements, Functions and Methods can result in a snippet completion when resolved. | ||
item.insertTextFormat = lsp.InsertTextFormat.Snippet; | ||
} | ||
if (sourceDisplay) { | ||
item.detail = asPlainText(sourceDisplay); | ||
} | ||
if (entry.isImportStatementCompletion || isSnippet || item.kind === lsp.CompletionItemKind.Function || item.kind === lsp.CompletionItemKind.Method) { | ||
item.insertTextFormat = lsp.InsertTextFormat.Snippet; | ||
} | ||
let insertText = entry.insertText; | ||
let replacementRange = entry.replacementSpan && asRange(entry.replacementSpan); | ||
let replacementRange = entry.replacementSpan && Range.fromTextSpan(entry.replacementSpan); | ||
// Make sure we only replace a single line at most | ||
@@ -173,49 +176,43 @@ if (replacementRange && replacementRange.start.line !== replacementRange.end.line) { | ||
} | ||
export function asResolvedCompletionItem(item, details, client, options) { | ||
var _a; | ||
return __awaiter(this, void 0, void 0, function* () { | ||
item.detail = asDetail(details); | ||
item.documentation = asDocumentation(details); | ||
const filepath = normalizePath(item.data.file); | ||
if ((_a = details.codeActions) === null || _a === void 0 ? void 0 : _a.length) { | ||
item.additionalTextEdits = asAdditionalTextEdits(details.codeActions, filepath); | ||
item.command = asCommand(details.codeActions, item.data.file); | ||
export async function asResolvedCompletionItem(item, details, client, options, features) { | ||
item.detail = asDetail(details); | ||
item.documentation = asDocumentation(details); | ||
const filepath = normalizePath(item.data.file); | ||
if (details.codeActions?.length) { | ||
item.additionalTextEdits = asAdditionalTextEdits(details.codeActions, filepath); | ||
item.command = asCommand(details.codeActions, item.data.file); | ||
} | ||
if (features.completionSnippets && options.completeFunctionCalls && (item.kind === lsp.CompletionItemKind.Function || item.kind === lsp.CompletionItemKind.Method)) { | ||
const { line, offset } = item.data; | ||
const position = Position.fromLocation({ line, offset }); | ||
const shouldCompleteFunction = await isValidFunctionCompletionContext(filepath, position, client); | ||
if (shouldCompleteFunction) { | ||
createSnippetOfFunctionCall(item, details); | ||
} | ||
if (options.completeFunctionCalls && item.insertTextFormat === lsp.InsertTextFormat.Snippet | ||
&& (item.kind === lsp.CompletionItemKind.Function || item.kind === lsp.CompletionItemKind.Method)) { | ||
const { line, offset } = item.data; | ||
const position = typeConverters.Position.fromLocation({ line, offset }); | ||
const shouldCompleteFunction = yield isValidFunctionCompletionContext(filepath, position, client); | ||
if (shouldCompleteFunction) { | ||
createSnippetOfFunctionCall(item, details); | ||
} | ||
} | ||
return item; | ||
} | ||
export async function isValidFunctionCompletionContext(filepath, position, client) { | ||
// Workaround for https://github.com/Microsoft/TypeScript/issues/12677 | ||
// Don't complete function calls inside of destructive assigments or imports | ||
try { | ||
const args = Position.toFileLocationRequestArgs(filepath, position); | ||
const response = await client.request("quickinfo" /* CommandTypes.Quickinfo */, args); | ||
if (response.type !== 'response') { | ||
return true; | ||
} | ||
return item; | ||
}); | ||
} | ||
export function isValidFunctionCompletionContext(filepath, position, client) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
// Workaround for https://github.com/Microsoft/TypeScript/issues/12677 | ||
// Don't complete function calls inside of destructive assigments or imports | ||
try { | ||
const args = typeConverters.Position.toFileLocationRequestArgs(filepath, position); | ||
const response = yield client.request("quickinfo" /* CommandTypes.Quickinfo */, args); | ||
if (response.type !== 'response') { | ||
const { body } = response; | ||
switch (body?.kind) { | ||
case 'var': | ||
case 'let': | ||
case 'const': | ||
case 'alias': | ||
return false; | ||
default: | ||
return true; | ||
} | ||
const { body } = response; | ||
switch (body === null || body === void 0 ? void 0 : body.kind) { | ||
case 'var': | ||
case 'let': | ||
case 'const': | ||
case 'alias': | ||
return false; | ||
default: | ||
return true; | ||
} | ||
} | ||
catch (_a) { | ||
return true; | ||
} | ||
}); | ||
} | ||
catch { | ||
return true; | ||
} | ||
} | ||
@@ -234,2 +231,3 @@ function createSnippetOfFunctionCall(item, detail) { | ||
item.insertText = snippet.value; | ||
item.insertTextFormat = lsp.InsertTextFormat.Snippet; | ||
} | ||
@@ -343,4 +341,4 @@ function getParameterListParts(displayParts) { | ||
description: codeAction.description, | ||
changes: codeAction.changes.filter(x => x.fileName !== filepath) | ||
}))] | ||
changes: codeAction.changes.filter(x => x.fileName !== filepath), | ||
}))], | ||
}; | ||
@@ -347,0 +345,0 @@ } |
@@ -6,2 +6,3 @@ import type tsp from 'typescript/lib/protocol.d.js'; | ||
import { LspDocuments } from './document.js'; | ||
import { SupportedFeatures } from './ts-protocol.js'; | ||
declare class FileDiagnostics { | ||
@@ -11,5 +12,5 @@ protected readonly uri: string; | ||
protected readonly documents: LspDocuments; | ||
protected readonly publishDiagnosticsCapabilities: lsp.TextDocumentClientCapabilities['publishDiagnostics']; | ||
protected readonly features: SupportedFeatures; | ||
private readonly diagnosticsPerKind; | ||
constructor(uri: string, publishDiagnostics: (params: lsp.PublishDiagnosticsParams) => void, documents: LspDocuments, publishDiagnosticsCapabilities: lsp.TextDocumentClientCapabilities['publishDiagnostics']); | ||
constructor(uri: string, publishDiagnostics: (params: lsp.PublishDiagnosticsParams) => void, documents: LspDocuments, features: SupportedFeatures); | ||
update(kind: EventTypes, diagnostics: tsp.Diagnostic[]): void; | ||
@@ -22,7 +23,7 @@ protected readonly firePublishDiagnostics: () => Promise<void>; | ||
protected readonly documents: LspDocuments; | ||
protected readonly publishDiagnosticsCapabilities: lsp.TextDocumentClientCapabilities['publishDiagnostics']; | ||
protected readonly features: SupportedFeatures; | ||
protected readonly logger: Logger; | ||
protected readonly diagnostics: Map<string, FileDiagnostics>; | ||
private ignoredDiagnosticCodes; | ||
constructor(publishDiagnostics: (params: lsp.PublishDiagnosticsParams) => void, documents: LspDocuments, publishDiagnosticsCapabilities: lsp.TextDocumentClientCapabilities['publishDiagnostics'], logger: Logger); | ||
constructor(publishDiagnostics: (params: lsp.PublishDiagnosticsParams) => void, documents: LspDocuments, features: SupportedFeatures, logger: Logger); | ||
updateDiagnostics(kind: EventTypes, event: tsp.DiagnosticEvent): void; | ||
@@ -29,0 +30,0 @@ updateIgnoredDiagnosticCodes(ignoredCodes: readonly number[]): void; |
@@ -10,7 +10,7 @@ /* | ||
class FileDiagnostics { | ||
constructor(uri, publishDiagnostics, documents, publishDiagnosticsCapabilities) { | ||
constructor(uri, publishDiagnostics, documents, features) { | ||
this.uri = uri; | ||
this.publishDiagnostics = publishDiagnostics; | ||
this.documents = documents; | ||
this.publishDiagnosticsCapabilities = publishDiagnosticsCapabilities; | ||
this.features = features; | ||
this.diagnosticsPerKind = new Map(); | ||
@@ -30,3 +30,3 @@ this.firePublishDiagnostics = debounce(() => { | ||
for (const diagnostic of diagnostics) { | ||
result.push(toDiagnostic(diagnostic, this.documents, this.publishDiagnosticsCapabilities)); | ||
result.push(toDiagnostic(diagnostic, this.documents, this.features)); | ||
} | ||
@@ -38,6 +38,6 @@ } | ||
export class DiagnosticEventQueue { | ||
constructor(publishDiagnostics, documents, publishDiagnosticsCapabilities, logger) { | ||
constructor(publishDiagnostics, documents, features, logger) { | ||
this.publishDiagnostics = publishDiagnostics; | ||
this.documents = documents; | ||
this.publishDiagnosticsCapabilities = publishDiagnosticsCapabilities; | ||
this.features = features; | ||
this.logger = logger; | ||
@@ -58,3 +58,3 @@ this.diagnostics = new Map(); | ||
const uri = pathToUri(file, this.documents); | ||
const diagnosticsForFile = this.diagnostics.get(uri) || new FileDiagnostics(uri, this.publishDiagnostics, this.documents, this.publishDiagnosticsCapabilities); | ||
const diagnosticsForFile = this.diagnostics.get(uri) || new FileDiagnostics(uri, this.publishDiagnostics, this.documents, this.features); | ||
diagnosticsForFile.update(kind, diagnostics); | ||
@@ -67,5 +67,4 @@ this.diagnostics.set(uri, diagnosticsForFile); | ||
getDiagnosticsForFile(file) { | ||
var _a; | ||
const uri = pathToUri(file, this.documents); | ||
return ((_a = this.diagnostics.get(uri)) === null || _a === void 0 ? void 0 : _a.getDiagnostics()) || []; | ||
return this.diagnostics.get(uri)?.getDiagnostics() || []; | ||
} | ||
@@ -72,0 +71,0 @@ isDiagnosticIgnored(diagnostic) { |
@@ -7,6 +7,7 @@ /* | ||
*/ | ||
import { asRange, toSymbolKind, Range } from './protocol-translation.js'; | ||
import { toSymbolKind } from './protocol-translation.js'; | ||
import { ScriptElementKind } from './tsp-command-types.js'; | ||
import { Range } from './utils/typeConverters.js'; | ||
export function collectDocumentSymbols(parent, symbols) { | ||
return collectDocumentSymbolsInRange(parent, symbols, { start: asRange(parent.spans[0]).start, end: asRange(parent.spans[parent.spans.length - 1]).end }); | ||
return collectDocumentSymbolsInRange(parent, symbols, { start: Range.fromTextSpan(parent.spans[0]).start, end: Range.fromTextSpan(parent.spans[parent.spans.length - 1]).end }); | ||
} | ||
@@ -16,3 +17,3 @@ function collectDocumentSymbolsInRange(parent, symbols, range) { | ||
for (const span of parent.spans) { | ||
const spanRange = asRange(span); | ||
const spanRange = Range.fromTextSpan(span); | ||
if (!Range.intersection(range, spanRange)) { | ||
@@ -24,3 +25,3 @@ continue; | ||
for (const child of parent.childItems) { | ||
if (child.spans.some(childSpan => !!Range.intersection(spanRange, asRange(childSpan)))) { | ||
if (child.spans.some(childSpan => !!Range.intersection(spanRange, Range.fromTextSpan(childSpan)))) { | ||
const includedChild = collectDocumentSymbolsInRange(child, children, spanRange); | ||
@@ -33,3 +34,3 @@ shouldInclude = shouldInclude || includedChild; | ||
if (parent.nameSpan) { | ||
const nameRange = asRange(parent.nameSpan); | ||
const nameRange = Range.fromTextSpan(parent.nameSpan); | ||
// In the case of mergeable definitions, the nameSpan is only correct for the first definition. | ||
@@ -47,3 +48,3 @@ if (Range.intersection(spanRange, nameRange)) { | ||
selectionRange: selectionRange, | ||
children | ||
children, | ||
}); | ||
@@ -58,7 +59,7 @@ } | ||
for (const span of current.spans) { | ||
const range = asRange(span); | ||
const range = Range.fromTextSpan(span); | ||
const children = []; | ||
if (current.childItems) { | ||
for (const child of current.childItems) { | ||
if (child.spans.some(span => !!Range.intersection(range, asRange(span)))) { | ||
if (child.spans.some(span => !!Range.intersection(range, Range.fromTextSpan(span)))) { | ||
const includedChild = collectSymbolInformation(uri, child, children, name); | ||
@@ -75,5 +76,5 @@ shouldInclude = shouldInclude || includedChild; | ||
uri, | ||
range | ||
range, | ||
}, | ||
containerName | ||
containerName, | ||
}); | ||
@@ -80,0 +81,0 @@ symbols.push(...children); |
@@ -5,79 +5,71 @@ /*--------------------------------------------------------------------------------------------- | ||
*--------------------------------------------------------------------------------------------*/ | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
import * as lsp from 'vscode-languageserver'; | ||
import { toFileRangeRequestArgs, toTextDocumentEdit } from '../protocol-translation.js'; | ||
import { toTextDocumentEdit } from '../protocol-translation.js'; | ||
import * as errorCodes from '../utils/errorCodes.js'; | ||
import * as fixNames from '../utils/fixNames.js'; | ||
import { CodeActionKind } from '../utils/types.js'; | ||
function buildIndividualFixes(fixes, client, file, documents, diagnostics) { | ||
var _a; | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const edits = []; | ||
for (const diagnostic of diagnostics) { | ||
for (const { codes, fixName } of fixes) { | ||
if (!codes.has(diagnostic.code)) { | ||
continue; | ||
} | ||
const args = Object.assign(Object.assign({}, toFileRangeRequestArgs(file, diagnostic.range)), { errorCodes: [+diagnostic.code] }); | ||
const response = yield client.request("getCodeFixes" /* CommandTypes.GetCodeFixes */, args); | ||
if (response.type !== 'response') { | ||
continue; | ||
} | ||
const fix = (_a = response.body) === null || _a === void 0 ? void 0 : _a.find(fix => fix.fixName === fixName); | ||
if (fix) { | ||
edits.push(...fix.changes.map(change => toTextDocumentEdit(change, documents))); | ||
break; | ||
} | ||
import { Range } from '../utils/typeConverters.js'; | ||
async function buildIndividualFixes(fixes, client, file, documents, diagnostics) { | ||
const edits = []; | ||
for (const diagnostic of diagnostics) { | ||
for (const { codes, fixName } of fixes) { | ||
if (!codes.has(diagnostic.code)) { | ||
continue; | ||
} | ||
const args = { | ||
...Range.toFileRangeRequestArgs(file, diagnostic.range), | ||
errorCodes: [+diagnostic.code], | ||
}; | ||
const response = await client.request("getCodeFixes" /* CommandTypes.GetCodeFixes */, args); | ||
if (response.type !== 'response') { | ||
continue; | ||
} | ||
const fix = response.body?.find(fix => fix.fixName === fixName); | ||
if (fix) { | ||
edits.push(...fix.changes.map(change => toTextDocumentEdit(change, documents))); | ||
break; | ||
} | ||
} | ||
return edits; | ||
}); | ||
} | ||
return edits; | ||
} | ||
function buildCombinedFix(fixes, client, file, documents, diagnostics) { | ||
var _a, _b; | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const edits = []; | ||
for (const diagnostic of diagnostics) { | ||
for (const { codes, fixName } of fixes) { | ||
if (!codes.has(diagnostic.code)) { | ||
continue; | ||
} | ||
const args = Object.assign(Object.assign({}, toFileRangeRequestArgs(file, diagnostic.range)), { errorCodes: [+diagnostic.code] }); | ||
const response = yield client.request("getCodeFixes" /* CommandTypes.GetCodeFixes */, args); | ||
if (response.type !== 'response' || !((_a = response.body) === null || _a === void 0 ? void 0 : _a.length)) { | ||
continue; | ||
} | ||
const fix = (_b = response.body) === null || _b === void 0 ? void 0 : _b.find(fix => fix.fixName === fixName); | ||
if (!fix) { | ||
continue; | ||
} | ||
if (!fix.fixId) { | ||
edits.push(...fix.changes.map(change => toTextDocumentEdit(change, documents))); | ||
return edits; | ||
} | ||
const combinedArgs = { | ||
scope: { | ||
type: 'file', | ||
args: { file } | ||
}, | ||
fixId: fix.fixId | ||
}; | ||
const combinedResponse = yield client.request("getCombinedCodeFix" /* CommandTypes.GetCombinedCodeFix */, combinedArgs); | ||
if (combinedResponse.type !== 'response' || !combinedResponse.body) { | ||
return edits; | ||
} | ||
edits.push(...combinedResponse.body.changes.map(change => toTextDocumentEdit(change, documents))); | ||
async function buildCombinedFix(fixes, client, file, documents, diagnostics) { | ||
const edits = []; | ||
for (const diagnostic of diagnostics) { | ||
for (const { codes, fixName } of fixes) { | ||
if (!codes.has(diagnostic.code)) { | ||
continue; | ||
} | ||
const args = { | ||
...Range.toFileRangeRequestArgs(file, diagnostic.range), | ||
errorCodes: [+diagnostic.code], | ||
}; | ||
const response = await client.request("getCodeFixes" /* CommandTypes.GetCodeFixes */, args); | ||
if (response.type !== 'response' || !response.body?.length) { | ||
continue; | ||
} | ||
const fix = response.body?.find(fix => fix.fixName === fixName); | ||
if (!fix) { | ||
continue; | ||
} | ||
if (!fix.fixId) { | ||
edits.push(...fix.changes.map(change => toTextDocumentEdit(change, documents))); | ||
return edits; | ||
} | ||
const combinedArgs = { | ||
scope: { | ||
type: 'file', | ||
args: { file }, | ||
}, | ||
fixId: fix.fixId, | ||
}; | ||
const combinedResponse = await client.request("getCombinedCodeFix" /* CommandTypes.GetCombinedCodeFix */, combinedArgs); | ||
if (combinedResponse.type !== 'response' || !combinedResponse.body) { | ||
return edits; | ||
} | ||
edits.push(...combinedResponse.body.changes.map(change => toTextDocumentEdit(change, documents))); | ||
return edits; | ||
} | ||
return edits; | ||
}); | ||
} | ||
return edits; | ||
} | ||
@@ -92,17 +84,15 @@ // #region Source Actions | ||
} | ||
build(client, file, documents, diagnostics) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const edits = []; | ||
edits.push(...yield buildIndividualFixes([ | ||
{ codes: errorCodes.incorrectlyImplementsInterface, fixName: fixNames.classIncorrectlyImplementsInterface }, | ||
{ codes: errorCodes.asyncOnlyAllowedInAsyncFunctions, fixName: fixNames.awaitInSyncFunction } | ||
], client, file, documents, diagnostics)); | ||
edits.push(...yield buildCombinedFix([ | ||
{ codes: errorCodes.unreachableCode, fixName: fixNames.unreachableCode } | ||
], client, file, documents, diagnostics)); | ||
if (!edits.length) { | ||
return null; | ||
} | ||
return lsp.CodeAction.create(this.title, { documentChanges: edits }, SourceFixAll.kind.value); | ||
}); | ||
async build(client, file, documents, diagnostics) { | ||
const edits = []; | ||
edits.push(...await buildIndividualFixes([ | ||
{ codes: errorCodes.incorrectlyImplementsInterface, fixName: fixNames.classIncorrectlyImplementsInterface }, | ||
{ codes: errorCodes.asyncOnlyAllowedInAsyncFunctions, fixName: fixNames.awaitInSyncFunction }, | ||
], client, file, documents, diagnostics)); | ||
edits.push(...await buildCombinedFix([ | ||
{ codes: errorCodes.unreachableCode, fixName: fixNames.unreachableCode }, | ||
], client, file, documents, diagnostics)); | ||
if (!edits.length) { | ||
return null; | ||
} | ||
return lsp.CodeAction.create(this.title, { documentChanges: edits }, SourceFixAll.kind.value); | ||
} | ||
@@ -116,12 +106,10 @@ } | ||
} | ||
build(client, file, documents, diagnostics) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const edits = yield buildCombinedFix([ | ||
{ codes: errorCodes.variableDeclaredButNeverUsed, fixName: fixNames.unusedIdentifier } | ||
], client, file, documents, diagnostics); | ||
if (!edits.length) { | ||
return null; | ||
} | ||
return lsp.CodeAction.create(this.title, { documentChanges: edits }, SourceRemoveUnused.kind.value); | ||
}); | ||
async build(client, file, documents, diagnostics) { | ||
const edits = await buildCombinedFix([ | ||
{ codes: errorCodes.variableDeclaredButNeverUsed, fixName: fixNames.unusedIdentifier }, | ||
], client, file, documents, diagnostics); | ||
if (!edits.length) { | ||
return null; | ||
} | ||
return lsp.CodeAction.create(this.title, { documentChanges: edits }, SourceRemoveUnused.kind.value); | ||
} | ||
@@ -135,12 +123,10 @@ } | ||
} | ||
build(client, file, documents, diagnostics) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const edits = yield buildCombinedFix([ | ||
{ codes: errorCodes.cannotFindName, fixName: fixNames.fixImport } | ||
], client, file, documents, diagnostics); | ||
if (!edits.length) { | ||
return null; | ||
} | ||
return lsp.CodeAction.create(this.title, { documentChanges: edits }, SourceAddMissingImports.kind.value); | ||
}); | ||
async build(client, file, documents, diagnostics) { | ||
const edits = await buildCombinedFix([ | ||
{ codes: errorCodes.cannotFindName, fixName: fixNames.fixImport }, | ||
], client, file, documents, diagnostics); | ||
if (!edits.length) { | ||
return null; | ||
} | ||
return lsp.CodeAction.create(this.title, { documentChanges: edits }, SourceAddMissingImports.kind.value); | ||
} | ||
@@ -157,12 +143,10 @@ } | ||
} | ||
provideCodeActions(kinds, file, diagnostics, documents) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const results = []; | ||
for (const provider of TypeScriptAutoFixProvider.kindProviders) { | ||
if (kinds.some(kind => kind.contains(provider.kind))) { | ||
results.push((new provider).build(this.client, file, documents, diagnostics)); | ||
} | ||
async provideCodeActions(kinds, file, diagnostics, documents) { | ||
const results = []; | ||
for (const provider of TypeScriptAutoFixProvider.kindProviders) { | ||
if (kinds.some(kind => kind.contains(provider.kind))) { | ||
results.push((new provider).build(this.client, file, documents, diagnostics)); | ||
} | ||
return (yield Promise.all(results)).flatMap(result => result || []); | ||
}); | ||
} | ||
return (await Promise.all(results)).flatMap(result => result || []); | ||
} | ||
@@ -173,4 +157,4 @@ } | ||
SourceRemoveUnused, | ||
SourceAddMissingImports | ||
SourceAddMissingImports, | ||
]; | ||
//# sourceMappingURL=fix-all.js.map |
@@ -7,11 +7,2 @@ /* | ||
*/ | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
import chai from 'chai'; | ||
@@ -21,8 +12,8 @@ import { uri, createServer, lastPosition, filePath, readContents, positionAfter } from './test-utils.js'; | ||
let server; | ||
before(() => __awaiter(void 0, void 0, void 0, function* () { | ||
server = yield createServer({ | ||
before(async () => { | ||
server = await createServer({ | ||
rootUri: uri(), | ||
publishDiagnostics: () => { } | ||
publishDiagnostics: () => { }, | ||
}); | ||
})); | ||
}); | ||
beforeEach(() => { | ||
@@ -36,3 +27,3 @@ server.closeAll(); | ||
describe('documentHighlight', () => { | ||
it('simple test', () => __awaiter(void 0, void 0, void 0, function* () { | ||
it('simple test', async () => { | ||
const doc = { | ||
@@ -42,16 +33,16 @@ uri: uri('module2.ts'), | ||
version: 1, | ||
text: readContents(filePath('module2.ts')) | ||
text: readContents(filePath('module2.ts')), | ||
}; | ||
server.didOpenTextDocument({ | ||
textDocument: doc | ||
textDocument: doc, | ||
}); | ||
const result = yield server.documentHighlight({ | ||
const result = await server.documentHighlight({ | ||
textDocument: doc, | ||
position: lastPosition(doc, 'doStuff') | ||
position: lastPosition(doc, 'doStuff'), | ||
}); | ||
assert.equal(2, result.length, JSON.stringify(result, undefined, 2)); | ||
})); | ||
}); | ||
}); | ||
describe('completions', () => { | ||
it('receives completion that auto-imports from another module', () => __awaiter(void 0, void 0, void 0, function* () { | ||
it('receives completion that auto-imports from another module', async () => { | ||
const doc = { | ||
@@ -61,8 +52,8 @@ uri: uri('completion.ts'), | ||
version: 1, | ||
text: readContents(filePath('completion.ts')) | ||
text: readContents(filePath('completion.ts')), | ||
}; | ||
server.didOpenTextDocument({ textDocument: doc }); | ||
const proposals = yield server.completion({ | ||
const proposals = await server.completion({ | ||
textDocument: doc, | ||
position: positionAfter(doc, 'doStuff') | ||
position: positionAfter(doc, 'doStuff'), | ||
}); | ||
@@ -72,8 +63,8 @@ assert.isNotNull(proposals); | ||
assert.isDefined(completion); | ||
const resolvedCompletion = yield server.completionResolve(completion); | ||
const resolvedCompletion = await server.completionResolve(completion); | ||
assert.isDefined(resolvedCompletion.additionalTextEdits); | ||
assert.isUndefined(resolvedCompletion.command); | ||
server.didCloseTextDocument({ textDocument: doc }); | ||
})); | ||
}); | ||
}); | ||
//# sourceMappingURL=file-lsp-server.spec.js.map |
import * as lsp from 'vscode-languageserver'; | ||
import type tsp from 'typescript/lib/protocol.d.js'; | ||
export declare function asSignatureHelp(info: tsp.SignatureHelpItems): lsp.SignatureHelp; | ||
export declare function asSignatureHelp(info: tsp.SignatureHelpItems, context?: lsp.SignatureHelpContext): lsp.SignatureHelp; | ||
export declare function toTsTriggerReason(context: lsp.SignatureHelpContext): tsp.SignatureHelpTriggerReason; | ||
//# sourceMappingURL=hover.d.ts.map |
@@ -7,13 +7,32 @@ /* | ||
*/ | ||
/*--------------------------------------------------------------------------------------------- | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. See License.txt in the project root for license information. | ||
*--------------------------------------------------------------------------------------------*/ | ||
import * as lsp from 'vscode-languageserver'; | ||
import { asDocumentation, asPlainText } from './protocol-translation.js'; | ||
export function asSignatureHelp(info) { | ||
export function asSignatureHelp(info, context) { | ||
const signatures = info.items.map(asSignatureInformation); | ||
return { | ||
activeSignature: info.selectedItemIndex, | ||
activeSignature: getActiveSignature(info, signatures, context), | ||
activeParameter: getActiveParameter(info), | ||
signatures: info.items.map(asSignatureInformation) | ||
signatures, | ||
}; | ||
} | ||
function getActiveSignature(info, signatures, context) { | ||
// Try matching the previous active signature's label to keep it selected | ||
if (context?.activeSignatureHelp?.activeSignature !== undefined) { | ||
const previouslyActiveSignature = context.activeSignatureHelp.signatures[context.activeSignatureHelp.activeSignature]; | ||
if (previouslyActiveSignature && context.isRetrigger) { | ||
const existingIndex = signatures.findIndex(other => other.label === previouslyActiveSignature.label); | ||
if (existingIndex !== -1) { | ||
return existingIndex; | ||
} | ||
} | ||
} | ||
return info.selectedItemIndex; | ||
} | ||
function getActiveParameter(info) { | ||
const activeSignature = info.items[info.selectedItemIndex]; | ||
if (activeSignature && activeSignature.isVariadic) { | ||
if (activeSignature?.isVariadic) { | ||
return Math.min(info.argumentIndex, activeSignature.parameters.length - 1); | ||
@@ -29,5 +48,5 @@ } | ||
documentation: item.documentation, | ||
tags: item.tags.filter(x => x.name !== 'param') | ||
tags: item.tags.filter(x => x.name !== 'param'), | ||
}), | ||
parameters | ||
parameters, | ||
}; | ||
@@ -41,5 +60,26 @@ signature.label += parameters.map(parameter => parameter.label).join(asPlainText(item.separatorDisplayParts)); | ||
label: asPlainText(parameter.displayParts), | ||
documentation: asDocumentation(parameter) | ||
documentation: asDocumentation(parameter), | ||
}; | ||
} | ||
export function toTsTriggerReason(context) { | ||
switch (context.triggerKind) { | ||
case lsp.SignatureHelpTriggerKind.TriggerCharacter: | ||
if (context.triggerCharacter) { | ||
if (context.isRetrigger) { | ||
return { kind: 'retrigger', triggerCharacter: context.triggerCharacter }; | ||
} | ||
else { | ||
return { kind: 'characterTyped', triggerCharacter: context.triggerCharacter }; | ||
} | ||
} | ||
else { | ||
return { kind: 'invoked' }; | ||
} | ||
case lsp.SignatureHelpTriggerKind.ContentChange: | ||
return context.isRetrigger ? { kind: 'retrigger' } : { kind: 'invoked' }; | ||
case lsp.SignatureHelpTriggerKind.Invoked: | ||
default: | ||
return { kind: 'invoked' }; | ||
} | ||
} | ||
//# sourceMappingURL=hover.js.map |
@@ -25,3 +25,3 @@ /* | ||
type: severity, | ||
message: message | ||
message: message, | ||
}); | ||
@@ -28,0 +28,0 @@ } |
import * as lsp from 'vscode-languageserver'; | ||
export interface ProgressReporter { | ||
begin(message?: string): void; | ||
report(message: string): void; | ||
end(): void; | ||
export interface WithProgressOptions { | ||
message: string; | ||
reporter: lsp.WorkDoneProgressReporter; | ||
} | ||
export interface LspClient { | ||
setClientCapabilites(capabilites: lsp.ClientCapabilities): void; | ||
createProgressReporter(): ProgressReporter; | ||
createProgressReporter(token?: lsp.CancellationToken, workDoneProgress?: lsp.WorkDoneProgressReporter): Promise<lsp.WorkDoneProgressReporter>; | ||
withProgress<R>(options: WithProgressOptions, task: (progress: lsp.WorkDoneProgressReporter) => Promise<R>): Promise<R>; | ||
publishDiagnostics(args: lsp.PublishDiagnosticsParams): void; | ||
showMessage(args: lsp.ShowMessageParams): void; | ||
showErrorMessage(message: string): void; | ||
logMessage(args: lsp.LogMessageParams): void; | ||
applyWorkspaceEdit(args: lsp.ApplyWorkspaceEditParams): Promise<lsp.ApplyWorkspaceEditResult>; | ||
telemetry(args: any): void; | ||
rename(args: lsp.TextDocumentPositionParams): Promise<any>; | ||
@@ -19,13 +17,11 @@ } | ||
protected connection: lsp.Connection; | ||
private clientCapabilities?; | ||
constructor(connection: lsp.Connection); | ||
setClientCapabilites(capabilites: lsp.ClientCapabilities): void; | ||
createProgressReporter(): ProgressReporter; | ||
publishDiagnostics(args: lsp.PublishDiagnosticsParams): void; | ||
showMessage(args: lsp.ShowMessageParams): void; | ||
createProgressReporter(_?: lsp.CancellationToken, workDoneProgress?: lsp.WorkDoneProgressReporter): Promise<lsp.WorkDoneProgressReporter>; | ||
withProgress<R = unknown>(options: WithProgressOptions, task: (progress: lsp.WorkDoneProgressReporter) => Promise<R>): Promise<R>; | ||
publishDiagnostics(params: lsp.PublishDiagnosticsParams): void; | ||
showErrorMessage(message: string): void; | ||
logMessage(args: lsp.LogMessageParams): void; | ||
telemetry(args: any): void; | ||
applyWorkspaceEdit(args: lsp.ApplyWorkspaceEditParams): Promise<lsp.ApplyWorkspaceEditResult>; | ||
applyWorkspaceEdit(params: lsp.ApplyWorkspaceEditParams): Promise<lsp.ApplyWorkspaceEditResult>; | ||
rename(args: lsp.TextDocumentPositionParams): Promise<any>; | ||
} | ||
//# sourceMappingURL=lsp-client.d.ts.map |
@@ -7,13 +7,8 @@ /* | ||
*/ | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
import * as lsp from 'vscode-languageserver'; | ||
import { MessageType } from 'vscode-languageserver'; | ||
import { attachWorkDone } from 'vscode-languageserver/lib/common/progress.js'; | ||
import { TypeScriptRenameRequest } from './ts-protocol.js'; | ||
// Hack around the LSP library that makes it otherwise impossible to differentiate between Null and Client-initiated reporter. | ||
const nullProgressReporter = attachWorkDone(undefined, /* params */ undefined); | ||
export class LspClientImpl { | ||
@@ -23,45 +18,25 @@ constructor(connection) { | ||
} | ||
setClientCapabilites(capabilites) { | ||
this.clientCapabilities = capabilites; | ||
async createProgressReporter(_, workDoneProgress) { | ||
let reporter; | ||
if (workDoneProgress && workDoneProgress.constructor !== nullProgressReporter.constructor) { | ||
reporter = workDoneProgress; | ||
} | ||
else { | ||
reporter = workDoneProgress || await this.connection.window.createWorkDoneProgress(); | ||
} | ||
return reporter; | ||
} | ||
createProgressReporter() { | ||
let workDoneProgress; | ||
return { | ||
begin: (message = '') => { | ||
var _a, _b; | ||
if ((_b = (_a = this.clientCapabilities) === null || _a === void 0 ? void 0 : _a.window) === null || _b === void 0 ? void 0 : _b.workDoneProgress) { | ||
workDoneProgress = this.connection.window.createWorkDoneProgress(); | ||
workDoneProgress | ||
.then((progress) => { | ||
progress.begin(message); | ||
}) | ||
.catch(() => { }); | ||
} | ||
}, | ||
report: (message) => { | ||
if (workDoneProgress) { | ||
workDoneProgress | ||
.then((progress) => { | ||
progress.report(message); | ||
}) | ||
.catch(() => { }); | ||
} | ||
}, | ||
end: () => { | ||
if (workDoneProgress) { | ||
workDoneProgress | ||
.then((progress) => { | ||
progress.done(); | ||
}) | ||
.catch(() => { }); | ||
workDoneProgress = undefined; | ||
} | ||
} | ||
}; | ||
async withProgress(options, task) { | ||
const { message, reporter } = options; | ||
reporter.begin(message); | ||
return task(reporter).then(result => { | ||
reporter.done(); | ||
return result; | ||
}); | ||
} | ||
publishDiagnostics(args) { | ||
this.connection.sendNotification(lsp.PublishDiagnosticsNotification.type, args); | ||
publishDiagnostics(params) { | ||
this.connection.sendDiagnostics(params); | ||
} | ||
showMessage(args) { | ||
this.connection.sendNotification(lsp.ShowMessageNotification.type, args); | ||
showErrorMessage(message) { | ||
this.connection.sendNotification(lsp.ShowMessageNotification.type, { type: MessageType.Error, message }); | ||
} | ||
@@ -71,16 +46,9 @@ logMessage(args) { | ||
} | ||
telemetry(args) { | ||
this.connection.sendNotification(lsp.TelemetryEventNotification.type, args); | ||
async applyWorkspaceEdit(params) { | ||
return this.connection.workspace.applyEdit(params); | ||
} | ||
applyWorkspaceEdit(args) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
return this.connection.sendRequest(lsp.ApplyWorkspaceEditRequest.type, args); | ||
}); | ||
async rename(args) { | ||
return this.connection.sendRequest(TypeScriptRenameRequest.type, args); | ||
} | ||
rename(args) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
return this.connection.sendRequest(TypeScriptRenameRequest.type, args); | ||
}); | ||
} | ||
} | ||
//# sourceMappingURL=lsp-client.js.map |
@@ -22,3 +22,3 @@ /* | ||
tsserverLogFile: options.tsserverLogFile, | ||
tsserverLogVerbosity: options.tsserverLogVerbosity | ||
tsserverLogVerbosity: options.tsserverLogVerbosity, | ||
}); | ||
@@ -48,5 +48,6 @@ connection.onInitialize(server.initialize.bind(server)); | ||
connection.onFoldingRanges(server.foldingRanges.bind(server)); | ||
connection.languages.inlayHint.on(server.inlayHints.bind(server)); | ||
// proposed `textDocument/calls` request | ||
connection.onRequest(lspcalls.CallsRequest.type, server.calls.bind(server)); | ||
connection.onRequest(lspinlayHints.type, server.inlayHints.bind(server)); | ||
connection.onRequest(lspinlayHints.type, server.inlayHintsLegacy.bind(server)); | ||
connection.onRequest(lsp.SemanticTokensRequest.type, server.semanticTokensFull.bind(server)); | ||
@@ -53,0 +54,0 @@ connection.onRequest(lsp.SemanticTokensRangeRequest.type, server.semanticTokensRange.bind(server)); |
@@ -26,12 +26,3 @@ import * as lsp from 'vscode-languageserver'; | ||
export declare type HandlerSignature = RequestHandler<InlayHintsParams, InlayHintsResult | null, void>; | ||
export interface InlayHintsOptions { | ||
includeInlayParameterNameHints?: 'none' | 'literals' | 'all'; | ||
includeInlayParameterNameHintsWhenArgumentMatchesName?: boolean; | ||
includeInlayFunctionParameterTypeHints?: boolean; | ||
includeInlayVariableTypeHints?: boolean; | ||
includeInlayPropertyDeclarationTypeHints?: boolean; | ||
includeInlayFunctionLikeReturnTypeHints?: boolean; | ||
includeInlayEnumMemberValueHints?: boolean; | ||
} | ||
export {}; | ||
//# sourceMappingURL=lsp-protocol.inlayHints.proposed.d.ts.map |
@@ -6,3 +6,3 @@ import * as lsp from 'vscode-languageserver'; | ||
import { LspDocument } from './document.js'; | ||
import { TypeScriptInitializeParams, TypeScriptInitializeResult, TypeScriptWorkspaceSettingsLanguageSettings } from './ts-protocol.js'; | ||
import { TypeScriptInitializeParams, TypeScriptInitializeResult } from './ts-protocol.js'; | ||
import { IServerOptions } from './utils/configuration.js'; | ||
@@ -14,6 +14,5 @@ export declare class LspServer { | ||
private initializeParams; | ||
private initializeResult; | ||
private diagnosticQueue?; | ||
private configurationManager; | ||
private logger; | ||
private workspaceConfiguration; | ||
private workspaceRoot; | ||
@@ -33,3 +32,2 @@ private typeScriptAutoFixProvider; | ||
didChangeConfiguration(params: lsp.DidChangeConfigurationParams): void; | ||
getWorkspacePreferencesForDocument(file: string): TypeScriptWorkspaceSettingsLanguageSettings; | ||
protected diagnosticsTokenSource: lsp.CancellationTokenSource | undefined; | ||
@@ -47,11 +45,9 @@ protected interuptDiagnostics<R>(f: () => R): R; | ||
didChangeTextDocument(params: lsp.DidChangeTextDocumentParams): void; | ||
didSaveTextDocument(_params: lsp.DidChangeTextDocumentParams): void; | ||
definition(params: lsp.TextDocumentPositionParams): Promise<lsp.Definition>; | ||
implementation(params: lsp.TextDocumentPositionParams): Promise<lsp.Definition>; | ||
typeDefinition(params: lsp.TextDocumentPositionParams): Promise<lsp.Definition>; | ||
protected getDefinition({ type, params }: { | ||
type: 'definition' | 'implementation' | 'typeDefinition'; | ||
params: lsp.TextDocumentPositionParams; | ||
}): Promise<lsp.Definition>; | ||
documentSymbol(params: lsp.TextDocumentPositionParams): Promise<lsp.DocumentSymbol[] | lsp.SymbolInformation[]>; | ||
didSaveTextDocument(_params: lsp.DidSaveTextDocumentParams): void; | ||
definition(params: lsp.DefinitionParams): Promise<lsp.Definition | lsp.DefinitionLink[] | undefined>; | ||
implementation(params: lsp.TextDocumentPositionParams): Promise<lsp.Definition | undefined>; | ||
typeDefinition(params: lsp.TextDocumentPositionParams): Promise<lsp.Definition | undefined>; | ||
private getDefinition; | ||
private getSymbolLocations; | ||
documentSymbol(params: lsp.DocumentSymbolParams): Promise<lsp.DocumentSymbol[] | lsp.SymbolInformation[]>; | ||
protected get supportHierarchicalDocumentSymbol(): boolean; | ||
@@ -66,5 +62,4 @@ completion(params: lsp.CompletionParams): Promise<lsp.CompletionList | null>; | ||
documentRangeFormatting(params: lsp.DocumentRangeFormattingParams): Promise<lsp.TextEdit[]>; | ||
private getFormattingOptions; | ||
signatureHelp(params: lsp.TextDocumentPositionParams): Promise<lsp.SignatureHelp | undefined>; | ||
protected getSignatureHelp(file: string, position: lsp.Position): Promise<tsp.SignatureHelpResponse | undefined>; | ||
signatureHelp(params: lsp.SignatureHelpParams): Promise<lsp.SignatureHelp | undefined>; | ||
protected getSignatureHelp(file: string, params: lsp.SignatureHelpParams): Promise<tsp.SignatureHelpResponse | undefined>; | ||
codeAction(params: lsp.CodeActionParams): Promise<lsp.CodeAction[]>; | ||
@@ -74,3 +69,3 @@ protected getCodeFixes(args: tsp.CodeFixRequestArgs): Promise<tsp.GetCodeFixesResponse | undefined>; | ||
protected getOrganizeImports(args: tsp.OrganizeImportsRequestArgs): Promise<tsp.OrganizeImportsResponse | undefined>; | ||
executeCommand(arg: lsp.ExecuteCommandParams): Promise<void>; | ||
executeCommand(arg: lsp.ExecuteCommandParams, token?: lsp.CancellationToken, workDoneProgress?: lsp.WorkDoneProgressReporter): Promise<any>; | ||
protected applyFileCodeEdits(edits: ReadonlyArray<tsp.FileCodeEdits>): Promise<boolean>; | ||
@@ -88,6 +83,6 @@ protected applyRenameFile(sourceUri: string, targetUri: string): Promise<void>; | ||
protected asFoldingRangeKind(span: tsp.OutliningSpan): lsp.FoldingRangeKind | undefined; | ||
protected onTsEvent(event: protocol.Event): void; | ||
protected onTsEvent(event: protocol.Event): Promise<void>; | ||
calls(params: lspcalls.CallsParams): Promise<lspcalls.CallsResult>; | ||
inlayHints(params: lspinlayHints.InlayHintsParams): Promise<lspinlayHints.InlayHintsResult>; | ||
private getInlayHintsOptions; | ||
inlayHints(params: lsp.InlayHintParams): Promise<lsp.InlayHint[] | undefined>; | ||
inlayHintsLegacy(params: lspinlayHints.InlayHintsParams): Promise<lspinlayHints.InlayHintsResult>; | ||
semanticTokensFull(params: lsp.SemanticTokensParams): Promise<lsp.SemanticTokens>; | ||
@@ -94,0 +89,0 @@ semanticTokensRange(params: lsp.SemanticTokensRangeParams): Promise<lsp.SemanticTokens>; |
@@ -7,11 +7,2 @@ /* | ||
*/ | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
import * as path from 'node:path'; | ||
@@ -28,6 +19,6 @@ import fs from 'fs-extra'; | ||
import { DiagnosticEventQueue } from './diagnostic-queue.js'; | ||
import { toDocumentHighlight, asRange, asTagsDocumentation, uriToPath, toSymbolKind, toLocation, toPosition, pathToUri, toTextEdit, toFileRangeRequestArgs, asPlainText, normalizePath } from './protocol-translation.js'; | ||
import { toDocumentHighlight, asTagsDocumentation, uriToPath, toSymbolKind, toLocation, pathToUri, toTextEdit, asPlainText, normalizePath } from './protocol-translation.js'; | ||
import { LspDocuments } from './document.js'; | ||
import { asCompletionItem, asResolvedCompletionItem, getCompletionTriggerCharacter } from './completion.js'; | ||
import { asSignatureHelp } from './hover.js'; | ||
import { asSignatureHelp, toTsTriggerReason } from './hover.js'; | ||
import { Commands } from './commands.js'; | ||
@@ -41,34 +32,7 @@ import { provideQuickFix } from './quickfix.js'; | ||
import { TypeScriptAutoFixProvider } from './features/fix-all.js'; | ||
import { TypeScriptInlayHintsProvider } from './features/inlay-hints.js'; | ||
import { SourceDefinitionCommand } from './features/source-definition.js'; | ||
import { Position, Range } from './utils/typeConverters.js'; | ||
import { CodeActionKind } from './utils/types.js'; | ||
const DEFAULT_TSSERVER_PREFERENCES = { | ||
allowIncompleteCompletions: true, | ||
allowRenameOfImportPath: true, | ||
allowTextChangesInNewFiles: true, | ||
disableSuggestions: false, | ||
displayPartsForJSDoc: true, | ||
generateReturnInDocTemplate: true, | ||
importModuleSpecifierEnding: 'auto', | ||
importModuleSpecifierPreference: 'shortest', | ||
includeAutomaticOptionalChainCompletions: true, | ||
includeCompletionsForImportStatements: true, | ||
includeCompletionsForModuleExports: true, | ||
includeCompletionsWithClassMemberSnippets: true, | ||
includeCompletionsWithInsertText: true, | ||
includeCompletionsWithObjectLiteralMethodSnippets: true, | ||
includeCompletionsWithSnippetText: true, | ||
includeInlayEnumMemberValueHints: false, | ||
includeInlayFunctionLikeReturnTypeHints: false, | ||
includeInlayFunctionParameterTypeHints: false, | ||
includeInlayParameterNameHints: 'none', | ||
includeInlayParameterNameHintsWhenArgumentMatchesName: false, | ||
includeInlayPropertyDeclarationTypeHints: false, | ||
includeInlayVariableTypeHints: false, | ||
includePackageJsonAutoImports: 'auto', | ||
jsxAttributeCompletionStyle: 'auto', | ||
lazyConfiguredProjectsFromExternalProject: false, | ||
providePrefixAndSuffixTextForRename: true, | ||
provideRefactorNotApplicableReason: false, | ||
quotePreference: 'auto', | ||
useLabelDetailsInCompletionEntries: true | ||
}; | ||
import { ConfigurationManager } from './configuration-manager.js'; | ||
class ServerInitializingIndicator { | ||
@@ -82,3 +46,3 @@ constructor(lspClient) { | ||
if (this._progressReporter) { | ||
this._progressReporter.end(); | ||
this._progressReporter.done(); | ||
this._progressReporter = undefined; | ||
@@ -88,3 +52,3 @@ } | ||
} | ||
startedLoadingProject(projectName) { | ||
async startedLoadingProject(projectName) { | ||
// TS projects are loaded sequentially. Cancel existing task because it should always be resolved before | ||
@@ -94,3 +58,3 @@ // the incoming project loading task is. | ||
this._loadingProjectName = projectName; | ||
this._progressReporter = this.lspClient.createProgressReporter(); | ||
this._progressReporter = await this.lspClient.createProgressReporter(); | ||
this._progressReporter.begin('Initializing JS/TS language features…'); | ||
@@ -102,3 +66,3 @@ } | ||
if (this._progressReporter) { | ||
this._progressReporter.end(); | ||
this._progressReporter.done(); | ||
this._progressReporter = undefined; | ||
@@ -112,2 +76,6 @@ } | ||
this.options = options; | ||
this._tspClient = null; | ||
this._loadingIndicator = null; | ||
this.initializeParams = null; | ||
this.typeScriptAutoFixProvider = null; | ||
this.features = {}; | ||
@@ -119,4 +87,4 @@ this.documents = new LspDocuments(); | ||
this.doRequestDiagnosticsDebounced = debounce(() => this.doRequestDiagnostics(), 200); | ||
this.configurationManager = new ConfigurationManager(this.documents); | ||
this.logger = new PrefixingLogger(options.logger, '[lspserver]'); | ||
this.workspaceConfiguration = {}; | ||
} | ||
@@ -174,165 +142,176 @@ closeAll() { | ||
} | ||
initialize(params) { | ||
var _a, _b, _c, _d, _e, _f, _g; | ||
return __awaiter(this, void 0, void 0, function* () { | ||
this.logger.log('initialize', params); | ||
if (this._tspClient) { | ||
throw new Error('The "initialize" request has already called before.'); | ||
async initialize(params) { | ||
this.logger.log('initialize', params); | ||
if (this._tspClient) { | ||
throw new Error('The "initialize" request has already called before.'); | ||
} | ||
this.initializeParams = params; | ||
const clientCapabilities = this.initializeParams.capabilities; | ||
this._loadingIndicator = new ServerInitializingIndicator(this.options.lspClient); | ||
this.workspaceRoot = this.initializeParams.rootUri ? uriToPath(this.initializeParams.rootUri) : this.initializeParams.rootPath || undefined; | ||
const userInitializationOptions = this.initializeParams.initializationOptions || {}; | ||
const { disableAutomaticTypingAcquisition, hostInfo, maxTsServerMemory, npmLocation, locale } = userInitializationOptions; | ||
const { logVerbosity, plugins } = { | ||
logVerbosity: userInitializationOptions.logVerbosity || this.options.tsserverLogVerbosity, | ||
plugins: userInitializationOptions.plugins || [], | ||
}; | ||
const logFile = this.getLogFile(logVerbosity); | ||
const globalPlugins = []; | ||
const pluginProbeLocations = []; | ||
for (const plugin of plugins) { | ||
globalPlugins.push(plugin.name); | ||
pluginProbeLocations.push(plugin.location); | ||
} | ||
const typescriptVersion = this.findTypescriptVersion(); | ||
if (typescriptVersion) { | ||
this.logger.info(`Using Typescript version (${typescriptVersion.source}) ${typescriptVersion.versionString} from path "${typescriptVersion.tsServerPath}"`); | ||
} | ||
else { | ||
throw Error('Could not find a valid TypeScript installation. Please ensure that the "typescript" dependency is installed in the workspace or that a valid --tsserver-path is specified. Exiting.'); | ||
} | ||
this.configurationManager.mergeTsPreferences(userInitializationOptions.preferences || {}); | ||
// Setup supported features. | ||
const { textDocument } = clientCapabilities; | ||
this.features.definitionLinkSupport = textDocument?.definition?.linkSupport && typescriptVersion.version?.gte(API.v270); | ||
const completionCapabilities = textDocument?.completion; | ||
if (completionCapabilities?.completionItem) { | ||
if (this.configurationManager.tsPreferences.useLabelDetailsInCompletionEntries | ||
&& completionCapabilities.completionItem.labelDetailsSupport | ||
&& typescriptVersion.version?.gte(API.v470)) { | ||
this.features.completionLabelDetails = true; | ||
} | ||
this.initializeParams = params; | ||
const clientCapabilities = this.initializeParams.capabilities; | ||
this.options.lspClient.setClientCapabilites(clientCapabilities); | ||
this._loadingIndicator = new ServerInitializingIndicator(this.options.lspClient); | ||
this.workspaceRoot = this.initializeParams.rootUri ? uriToPath(this.initializeParams.rootUri) : this.initializeParams.rootPath || undefined; | ||
this.diagnosticQueue = new DiagnosticEventQueue(diagnostics => this.options.lspClient.publishDiagnostics(diagnostics), this.documents, (_a = clientCapabilities.textDocument) === null || _a === void 0 ? void 0 : _a.publishDiagnostics, this.logger); | ||
const userInitializationOptions = this.initializeParams.initializationOptions || {}; | ||
const { disableAutomaticTypingAcquisition, hostInfo, maxTsServerMemory, npmLocation, locale } = userInitializationOptions; | ||
const { logVerbosity, plugins } = { | ||
logVerbosity: userInitializationOptions.logVerbosity || this.options.tsserverLogVerbosity, | ||
plugins: userInitializationOptions.plugins || [] | ||
}; | ||
const logFile = this.getLogFile(logVerbosity); | ||
const globalPlugins = []; | ||
const pluginProbeLocations = []; | ||
for (const plugin of plugins) { | ||
globalPlugins.push(plugin.name); | ||
pluginProbeLocations.push(plugin.location); | ||
if (completionCapabilities.completionItem.snippetSupport) { | ||
this.features.completionSnippets = true; | ||
} | ||
const typescriptVersion = this.findTypescriptVersion(); | ||
if (typescriptVersion) { | ||
this.logger.info(`Using Typescript version (${typescriptVersion.source}) ${typescriptVersion.versionString} from path "${typescriptVersion.tsServerPath}"`); | ||
if (textDocument?.publishDiagnostics?.tagSupport) { | ||
this.features.diagnosticsTagSupport = true; | ||
} | ||
else { | ||
throw Error('Could not find a valid tsserver executable in the workspace or in the $PATH. Please ensure that the "typescript" dependency is installed in either location. Exiting.'); | ||
} | ||
const userPreferences = Object.assign(Object.assign({}, DEFAULT_TSSERVER_PREFERENCES), userInitializationOptions.preferences); | ||
if (userPreferences.useLabelDetailsInCompletionEntries | ||
&& ((_d = (_c = (_b = clientCapabilities.textDocument) === null || _b === void 0 ? void 0 : _b.completion) === null || _c === void 0 ? void 0 : _c.completionItem) === null || _d === void 0 ? void 0 : _d.labelDetailsSupport) | ||
&& ((_e = typescriptVersion.version) === null || _e === void 0 ? void 0 : _e.gte(API.v470))) { | ||
this.features.labelDetails = true; | ||
} | ||
const finalPreferences = Object.assign(Object.assign({}, userPreferences), { useLabelDetailsInCompletionEntries: this.features.labelDetails }); | ||
this._tspClient = new TspClient({ | ||
tsserverPath: typescriptVersion.tsServerPath, | ||
logFile, | ||
logVerbosity, | ||
disableAutomaticTypingAcquisition, | ||
maxTsServerMemory, | ||
npmLocation, | ||
locale, | ||
globalPlugins, | ||
pluginProbeLocations, | ||
logger: this.options.logger, | ||
onEvent: this.onTsEvent.bind(this), | ||
onExit: (exitCode, signal) => { | ||
if (exitCode) { | ||
this.logger.error(`tsserver process has exited (exit code: ${exitCode}, signal: ${signal}). Stopping the server.`); | ||
} | ||
this.shutdown(); | ||
} | ||
this.configurationManager.mergeTsPreferences({ | ||
useLabelDetailsInCompletionEntries: this.features.completionLabelDetails, | ||
}); | ||
this.diagnosticQueue = new DiagnosticEventQueue(diagnostics => this.options.lspClient.publishDiagnostics(diagnostics), this.documents, this.features, this.logger); | ||
this._tspClient = new TspClient({ | ||
apiVersion: typescriptVersion.version || API.defaultVersion, | ||
tsserverPath: typescriptVersion.tsServerPath, | ||
logFile, | ||
logVerbosity, | ||
disableAutomaticTypingAcquisition, | ||
maxTsServerMemory, | ||
npmLocation, | ||
locale, | ||
globalPlugins, | ||
pluginProbeLocations, | ||
logger: this.options.logger, | ||
onEvent: this.onTsEvent.bind(this), | ||
onExit: (exitCode, signal) => { | ||
if (exitCode) { | ||
this.logger.error(`tsserver process has exited (exit code: ${exitCode}, signal: ${signal}). Stopping the server.`); | ||
} | ||
}); | ||
const started = this.tspClient.start(); | ||
if (!started) { | ||
throw new Error('tsserver process has failed to start.'); | ||
} | ||
process.on('exit', () => { | ||
this.shutdown(); | ||
}); | ||
process.on('SIGINT', () => { | ||
process.exit(); | ||
}); | ||
this.typeScriptAutoFixProvider = new TypeScriptAutoFixProvider(this.tspClient); | ||
yield Promise.all([ | ||
this.tspClient.request("configure" /* CommandTypes.Configure */, Object.assign(Object.assign({}, hostInfo ? { hostInfo } : {}), { formatOptions: { | ||
// We can use \n here since the editor should normalize later on to its line endings. | ||
newLineCharacter: '\n' | ||
}, preferences: finalPreferences })), | ||
this.tspClient.request("compilerOptionsForInferredProjects" /* CommandTypes.CompilerOptionsForInferredProjects */, { | ||
options: { | ||
module: "CommonJS" /* tsp.ModuleKind.CommonJS */, | ||
target: "ES2016" /* tsp.ScriptTarget.ES2016 */, | ||
jsx: "Preserve" /* tsp.JsxEmit.Preserve */, | ||
allowJs: true, | ||
allowSyntheticDefaultImports: true, | ||
allowNonTsExtensions: true | ||
} | ||
}) | ||
]); | ||
const logFileUri = logFile && pathToUri(logFile, undefined); | ||
this.initializeResult = { | ||
capabilities: { | ||
textDocumentSync: lsp.TextDocumentSyncKind.Incremental, | ||
completionProvider: { | ||
triggerCharacters: ['.', '"', '\'', '/', '@', '<'], | ||
resolveProvider: true | ||
}, | ||
}); | ||
const started = this.tspClient.start(); | ||
if (!started) { | ||
throw new Error('tsserver process has failed to start.'); | ||
} | ||
process.on('exit', () => { | ||
this.shutdown(); | ||
}); | ||
process.on('SIGINT', () => { | ||
process.exit(); | ||
}); | ||
this.typeScriptAutoFixProvider = new TypeScriptAutoFixProvider(this.tspClient); | ||
await Promise.all([ | ||
this.configurationManager.setAndConfigureTspClient(this._tspClient, hostInfo), | ||
this.tspClient.request("compilerOptionsForInferredProjects" /* CommandTypes.CompilerOptionsForInferredProjects */, { | ||
options: { | ||
module: "CommonJS" /* tsp.ModuleKind.CommonJS */, | ||
target: "ES2016" /* tsp.ScriptTarget.ES2016 */, | ||
jsx: "Preserve" /* tsp.JsxEmit.Preserve */, | ||
allowJs: true, | ||
allowSyntheticDefaultImports: true, | ||
allowNonTsExtensions: true, | ||
}, | ||
}), | ||
]); | ||
const logFileUri = logFile && pathToUri(logFile, undefined); | ||
const initializeResult = { | ||
capabilities: { | ||
textDocumentSync: lsp.TextDocumentSyncKind.Incremental, | ||
completionProvider: { | ||
triggerCharacters: ['.', '"', '\'', '/', '@', '<'], | ||
resolveProvider: true, | ||
}, | ||
codeActionProvider: clientCapabilities.textDocument?.codeAction?.codeActionLiteralSupport | ||
? { codeActionKinds: [ | ||
...TypeScriptAutoFixProvider.kinds.map(kind => kind.value), | ||
CodeActionKind.SourceOrganizeImportsTs.value, | ||
CodeActionKind.QuickFix.value, | ||
CodeActionKind.Refactor.value, | ||
] } : true, | ||
definitionProvider: true, | ||
documentFormattingProvider: true, | ||
documentRangeFormattingProvider: true, | ||
documentHighlightProvider: true, | ||
documentSymbolProvider: true, | ||
executeCommandProvider: { | ||
commands: [ | ||
Commands.APPLY_WORKSPACE_EDIT, | ||
Commands.APPLY_CODE_ACTION, | ||
Commands.APPLY_REFACTORING, | ||
Commands.ORGANIZE_IMPORTS, | ||
Commands.APPLY_RENAME_FILE, | ||
Commands.SOURCE_DEFINITION, | ||
], | ||
}, | ||
hoverProvider: true, | ||
inlayHintProvider: true, | ||
renameProvider: true, | ||
referencesProvider: true, | ||
signatureHelpProvider: { | ||
triggerCharacters: ['(', ',', '<'], | ||
retriggerCharacters: [')'], | ||
}, | ||
workspaceSymbolProvider: true, | ||
implementationProvider: true, | ||
typeDefinitionProvider: true, | ||
foldingRangeProvider: true, | ||
semanticTokensProvider: { | ||
documentSelector: null, | ||
legend: { | ||
// list taken from: https://github.com/microsoft/TypeScript/blob/main/src/services/classifier2020.ts#L10 | ||
tokenTypes: [ | ||
'class', | ||
'enum', | ||
'interface', | ||
'namespace', | ||
'typeParameter', | ||
'type', | ||
'parameter', | ||
'variable', | ||
'enumMember', | ||
'property', | ||
'function', | ||
'member', | ||
], | ||
// token from: https://github.com/microsoft/TypeScript/blob/main/src/services/classifier2020.ts#L14 | ||
tokenModifiers: [ | ||
'declaration', | ||
'static', | ||
'async', | ||
'readonly', | ||
'defaultLibrary', | ||
'local', | ||
], | ||
}, | ||
codeActionProvider: ((_g = (_f = clientCapabilities.textDocument) === null || _f === void 0 ? void 0 : _f.codeAction) === null || _g === void 0 ? void 0 : _g.codeActionLiteralSupport) | ||
? { codeActionKinds: [ | ||
...TypeScriptAutoFixProvider.kinds.map(kind => kind.value), | ||
CodeActionKind.SourceOrganizeImportsTs.value, | ||
CodeActionKind.QuickFix.value, | ||
CodeActionKind.Refactor.value | ||
] } : true, | ||
definitionProvider: true, | ||
documentFormattingProvider: true, | ||
documentRangeFormattingProvider: true, | ||
documentHighlightProvider: true, | ||
documentSymbolProvider: true, | ||
executeCommandProvider: { | ||
commands: [ | ||
Commands.APPLY_WORKSPACE_EDIT, | ||
Commands.APPLY_CODE_ACTION, | ||
Commands.APPLY_REFACTORING, | ||
Commands.ORGANIZE_IMPORTS, | ||
Commands.APPLY_RENAME_FILE | ||
] | ||
}, | ||
hoverProvider: true, | ||
renameProvider: true, | ||
referencesProvider: true, | ||
signatureHelpProvider: { | ||
triggerCharacters: ['(', ',', '<'] | ||
}, | ||
workspaceSymbolProvider: true, | ||
implementationProvider: true, | ||
typeDefinitionProvider: true, | ||
foldingRangeProvider: true, | ||
semanticTokensProvider: { | ||
documentSelector: null, | ||
legend: { | ||
// list taken from: https://github.com/microsoft/TypeScript/blob/main/src/services/classifier2020.ts#L10 | ||
tokenTypes: [ | ||
'class', | ||
'enum', | ||
'interface', | ||
'namespace', | ||
'typeParameter', | ||
'type', | ||
'parameter', | ||
'variable', | ||
'enumMember', | ||
'property', | ||
'function', | ||
'member' | ||
], | ||
// token from: https://github.com/microsoft/TypeScript/blob/main/src/services/classifier2020.ts#L14 | ||
tokenModifiers: [ | ||
'declaration', | ||
'static', | ||
'async', | ||
'readonly', | ||
'defaultLibrary', | ||
'local' | ||
] | ||
}, | ||
full: true, | ||
range: true | ||
} | ||
full: true, | ||
range: true, | ||
}, | ||
logFileUri | ||
}; | ||
this.initializeResult.capabilities.callsProvider = true; | ||
this.logger.log('onInitialize result', this.initializeResult); | ||
return this.initializeResult; | ||
}); | ||
}, | ||
logFileUri, | ||
}; | ||
initializeResult.capabilities.callsProvider = true; | ||
this.logger.log('onInitialize result', initializeResult); | ||
return initializeResult; | ||
} | ||
@@ -363,16 +342,6 @@ getLogFile(logVerbosity) { | ||
didChangeConfiguration(params) { | ||
var _a, _b; | ||
this.workspaceConfiguration = params.settings || {}; | ||
const ignoredDiagnosticCodes = ((_a = this.workspaceConfiguration.diagnostics) === null || _a === void 0 ? void 0 : _a.ignoredCodes) || []; | ||
(_b = this.diagnosticQueue) === null || _b === void 0 ? void 0 : _b.updateIgnoredDiagnosticCodes(ignoredDiagnosticCodes); | ||
this.configurationManager.setWorkspaceConfiguration(params.settings || {}); | ||
const ignoredDiagnosticCodes = this.configurationManager.workspaceConfiguration.diagnostics?.ignoredCodes || []; | ||
this.diagnosticQueue?.updateIgnoredDiagnosticCodes(ignoredDiagnosticCodes); | ||
} | ||
getWorkspacePreferencesForDocument(file) { | ||
var _a; | ||
const doc = this.documents.get(file); | ||
if (!doc) { | ||
return {}; | ||
} | ||
const preferencesKey = doc.languageId.startsWith('typescript') ? 'typescript' : 'javascript'; | ||
return (_a = this.workspaceConfiguration[preferencesKey]) !== null && _a !== void 0 ? _a : {}; | ||
} | ||
interuptDiagnostics(f) { | ||
@@ -387,24 +356,20 @@ if (!this.diagnosticsTokenSource) { | ||
} | ||
requestDiagnostics() { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
this.pendingDebouncedRequest = true; | ||
yield this.doRequestDiagnosticsDebounced(); | ||
}); | ||
async requestDiagnostics() { | ||
this.pendingDebouncedRequest = true; | ||
await this.doRequestDiagnosticsDebounced(); | ||
} | ||
doRequestDiagnostics() { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
this.cancelDiagnostics(); | ||
const geterrTokenSource = new lsp.CancellationTokenSource(); | ||
this.diagnosticsTokenSource = geterrTokenSource; | ||
const { files } = this.documents; | ||
try { | ||
return yield this.tspClient.request("geterr" /* CommandTypes.Geterr */, { delay: 0, files }, this.diagnosticsTokenSource.token); | ||
async doRequestDiagnostics() { | ||
this.cancelDiagnostics(); | ||
const geterrTokenSource = new lsp.CancellationTokenSource(); | ||
this.diagnosticsTokenSource = geterrTokenSource; | ||
const { files } = this.documents; | ||
try { | ||
return await this.tspClient.request("geterr" /* CommandTypes.Geterr */, { delay: 0, files }, this.diagnosticsTokenSource.token); | ||
} | ||
finally { | ||
if (this.diagnosticsTokenSource === geterrTokenSource) { | ||
this.diagnosticsTokenSource = undefined; | ||
this.pendingDebouncedRequest = false; | ||
} | ||
finally { | ||
if (this.diagnosticsTokenSource === geterrTokenSource) { | ||
this.diagnosticsTokenSource = undefined; | ||
this.pendingDebouncedRequest = false; | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
@@ -427,3 +392,3 @@ cancelDiagnostics() { | ||
scriptKindName: this.getScriptKindName(params.textDocument.languageId), | ||
projectRootPath: this.workspaceRoot | ||
projectRootPath: this.workspaceRoot, | ||
}); | ||
@@ -438,5 +403,5 @@ this.requestDiagnostics(); | ||
{ | ||
text: params.textDocument.text | ||
} | ||
] | ||
text: params.textDocument.text, | ||
}, | ||
], | ||
}); | ||
@@ -472,3 +437,3 @@ } | ||
uri: document.uri, | ||
diagnostics: [] | ||
diagnostics: [], | ||
}); | ||
@@ -515,3 +480,3 @@ } | ||
endOffset, | ||
insertString: change.text | ||
insertString: change.text, | ||
}); | ||
@@ -525,72 +490,90 @@ document.applyEdit(textDocument.version, change); | ||
} | ||
definition(params) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
// TODO: implement version checking and if semver.gte(version, 270) use `definitionAndBoundSpan` instead | ||
return this.getDefinition({ | ||
type: 'definition', | ||
params | ||
}); | ||
async definition(params) { | ||
return this.getDefinition({ | ||
type: this.features.definitionLinkSupport ? "definitionAndBoundSpan" /* CommandTypes.DefinitionAndBoundSpan */ : "definition" /* CommandTypes.Definition */, | ||
params, | ||
}); | ||
} | ||
implementation(params) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
return this.getDefinition({ | ||
type: 'implementation', | ||
params | ||
}); | ||
async implementation(params) { | ||
return this.getSymbolLocations({ | ||
type: "implementation" /* CommandTypes.Implementation */, | ||
params, | ||
}); | ||
} | ||
typeDefinition(params) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
return this.getDefinition({ | ||
type: 'typeDefinition', | ||
params | ||
}); | ||
async typeDefinition(params) { | ||
return this.getSymbolLocations({ | ||
type: "typeDefinition" /* CommandTypes.TypeDefinition */, | ||
params, | ||
}); | ||
} | ||
getDefinition({ type, params }) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log(type, params, file); | ||
if (!file) { | ||
return []; | ||
async getDefinition({ type, params }) { | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log(type, params, file); | ||
if (!file) { | ||
return undefined; | ||
} | ||
if (type === "definitionAndBoundSpan" /* CommandTypes.DefinitionAndBoundSpan */) { | ||
const args = Position.toFileLocationRequestArgs(file, params.position); | ||
const response = await this.tspClient.request(type, args); | ||
if (response.type !== 'response' || !response.body) { | ||
return undefined; | ||
} | ||
const result = yield this.tspClient.request(type, { | ||
file, | ||
line: params.position.line + 1, | ||
offset: params.position.character + 1 | ||
const span = Range.fromTextSpan(response.body.textSpan); | ||
return response.body.definitions | ||
.map((location) => { | ||
const target = toLocation(location, this.documents); | ||
const targetRange = location.contextStart && location.contextEnd | ||
? Range.fromLocations(location.contextStart, location.contextEnd) | ||
: target.range; | ||
return { | ||
originSelectionRange: span, | ||
targetRange, | ||
targetUri: target.uri, | ||
targetSelectionRange: target.range, | ||
}; | ||
}); | ||
return result.body ? result.body.map(fileSpan => toLocation(fileSpan, this.documents)) : []; | ||
} | ||
return this.getSymbolLocations({ type: "definition" /* CommandTypes.Definition */, params }); | ||
} | ||
async getSymbolLocations({ type, params }) { | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log(type, params, file); | ||
if (!file) { | ||
return []; | ||
} | ||
const args = Position.toFileLocationRequestArgs(file, params.position); | ||
const response = await this.tspClient.request(type, args); | ||
if (response.type !== 'response' || !response.body) { | ||
return undefined; | ||
} | ||
return response.body.map(fileSpan => toLocation(fileSpan, this.documents)); | ||
} | ||
async documentSymbol(params) { | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log('symbol', params, file); | ||
if (!file) { | ||
return []; | ||
} | ||
const response = await this.tspClient.request("navtree" /* CommandTypes.NavTree */, { | ||
file, | ||
}); | ||
} | ||
documentSymbol(params) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log('symbol', params, file); | ||
if (!file) { | ||
return []; | ||
} | ||
const response = yield this.tspClient.request("navtree" /* CommandTypes.NavTree */, { | ||
file | ||
}); | ||
const tree = response.body; | ||
if (!tree || !tree.childItems) { | ||
return []; | ||
} | ||
if (this.supportHierarchicalDocumentSymbol) { | ||
const symbols = []; | ||
for (const item of tree.childItems) { | ||
collectDocumentSymbols(item, symbols); | ||
} | ||
return symbols; | ||
} | ||
const tree = response.body; | ||
if (!tree || !tree.childItems) { | ||
return []; | ||
} | ||
if (this.supportHierarchicalDocumentSymbol) { | ||
const symbols = []; | ||
for (const item of tree.childItems) { | ||
collectSymbolInformation(params.textDocument.uri, item, symbols); | ||
collectDocumentSymbols(item, symbols); | ||
} | ||
return symbols; | ||
}); | ||
} | ||
const symbols = []; | ||
for (const item of tree.childItems) { | ||
collectSymbolInformation(params.textDocument.uri, item, symbols); | ||
} | ||
return symbols; | ||
} | ||
get supportHierarchicalDocumentSymbol() { | ||
const textDocument = this.initializeParams.capabilities.textDocument; | ||
const textDocument = this.initializeParams?.capabilities.textDocument; | ||
const documentSymbol = textDocument && textDocument.documentSymbol; | ||
@@ -603,479 +586,419 @@ return !!documentSymbol && !!documentSymbol.hierarchicalDocumentSymbolSupport; | ||
*/ | ||
completion(params) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log('completion', params, file); | ||
if (!file) { | ||
return lsp.CompletionList.create([]); | ||
} | ||
const document = this.documents.get(file); | ||
if (!document) { | ||
throw new Error('The document should be opened for completion, file: ' + file); | ||
} | ||
try { | ||
const result = yield this.interuptDiagnostics(() => { | ||
var _a, _b; | ||
return this.tspClient.request("completionInfo" /* CommandTypes.CompletionInfo */, { | ||
file, | ||
line: params.position.line + 1, | ||
offset: params.position.character + 1, | ||
triggerCharacter: getCompletionTriggerCharacter((_a = params.context) === null || _a === void 0 ? void 0 : _a.triggerCharacter), | ||
triggerKind: (_b = params.context) === null || _b === void 0 ? void 0 : _b.triggerKind | ||
}); | ||
}); | ||
const { body } = result; | ||
const completions = (body ? body.entries : []) | ||
.filter(entry => entry.kind !== 'warning') | ||
.map(entry => asCompletionItem(entry, file, params.position, document, this.features)); | ||
return lsp.CompletionList.create(completions, body === null || body === void 0 ? void 0 : body.isIncomplete); | ||
} | ||
catch (error) { | ||
if (error.message === 'No content available.') { | ||
this.logger.info('No content was available for completion request'); | ||
return null; | ||
async completion(params) { | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log('completion', params, file); | ||
if (!file) { | ||
return lsp.CompletionList.create([]); | ||
} | ||
const document = this.documents.get(file); | ||
if (!document) { | ||
throw new Error('The document should be opened for completion, file: ' + file); | ||
} | ||
try { | ||
const result = await this.interuptDiagnostics(() => this.tspClient.request("completionInfo" /* CommandTypes.CompletionInfo */, { | ||
file, | ||
line: params.position.line + 1, | ||
offset: params.position.character + 1, | ||
triggerCharacter: getCompletionTriggerCharacter(params.context?.triggerCharacter), | ||
triggerKind: params.context?.triggerKind, | ||
})); | ||
const { body } = result; | ||
const completions = []; | ||
for (const entry of body?.entries ?? []) { | ||
if (entry.kind === 'warning') { | ||
continue; | ||
} | ||
else { | ||
throw error; | ||
const completion = asCompletionItem(entry, file, params.position, document, this.features); | ||
if (!completion) { | ||
continue; | ||
} | ||
completions.push(completion); | ||
} | ||
}); | ||
} | ||
completionResolve(item) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
this.logger.log('completion/resolve', item); | ||
yield this.tspClient.request("configure" /* CommandTypes.Configure */, { | ||
formatOptions: this.getWorkspacePreferencesForDocument(item.data.file).format | ||
}); | ||
const { body } = yield this.interuptDiagnostics(() => this.tspClient.request("completionEntryDetails" /* CommandTypes.CompletionDetails */, item.data)); | ||
const details = body && body.length && body[0]; | ||
if (!details) { | ||
return item; | ||
return lsp.CompletionList.create(completions, body?.isIncomplete); | ||
} | ||
catch (error) { | ||
if (error.message === 'No content available.') { | ||
this.logger.info('No content was available for completion request'); | ||
return null; | ||
} | ||
return asResolvedCompletionItem(item, details, this.tspClient, this.workspaceConfiguration.completions || {}); | ||
}); | ||
} | ||
hover(params) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log('hover', params, file); | ||
if (!file) { | ||
return { contents: [] }; | ||
else { | ||
throw error; | ||
} | ||
const result = yield this.interuptDiagnostics(() => this.getQuickInfo(file, params.position)); | ||
if (!result || !result.body) { | ||
return { contents: [] }; | ||
} | ||
const range = asRange(result.body); | ||
const contents = []; | ||
if (result.body.displayString) { | ||
contents.push({ language: 'typescript', value: result.body.displayString }); | ||
} | ||
const tags = asTagsDocumentation(result.body.tags); | ||
const documentation = asPlainText(result.body.documentation); | ||
contents.push(documentation + (tags ? '\n\n' + tags : '')); | ||
return { | ||
contents, | ||
range | ||
}; | ||
}); | ||
} | ||
} | ||
getQuickInfo(file, position) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
try { | ||
return yield this.tspClient.request("quickinfo" /* CommandTypes.Quickinfo */, { | ||
file, | ||
line: position.line + 1, | ||
offset: position.character + 1 | ||
}); | ||
} | ||
catch (err) { | ||
return undefined; | ||
} | ||
}); | ||
async completionResolve(item) { | ||
this.logger.log('completion/resolve', item); | ||
await this.configurationManager.configureGloballyFromDocument(item.data.file); | ||
const { body } = await this.interuptDiagnostics(() => this.tspClient.request("completionEntryDetails" /* CommandTypes.CompletionDetails */, item.data)); | ||
const details = body && body.length && body[0]; | ||
if (!details) { | ||
return item; | ||
} | ||
return asResolvedCompletionItem(item, details, this.tspClient, this.configurationManager.workspaceConfiguration.completions || {}, this.features); | ||
} | ||
rename(params) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log('onRename', params, file); | ||
if (!file) { | ||
return undefined; | ||
} | ||
const result = yield this.tspClient.request("rename" /* CommandTypes.Rename */, { | ||
async hover(params) { | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log('hover', params, file); | ||
if (!file) { | ||
return { contents: [] }; | ||
} | ||
const result = await this.interuptDiagnostics(() => this.getQuickInfo(file, params.position)); | ||
if (!result || !result.body) { | ||
return { contents: [] }; | ||
} | ||
const range = Range.fromTextSpan(result.body); | ||
const contents = []; | ||
if (result.body.displayString) { | ||
contents.push({ language: 'typescript', value: result.body.displayString }); | ||
} | ||
const tags = asTagsDocumentation(result.body.tags); | ||
const documentation = asPlainText(result.body.documentation); | ||
contents.push(documentation + (tags ? '\n\n' + tags : '')); | ||
return { | ||
contents, | ||
range, | ||
}; | ||
} | ||
async getQuickInfo(file, position) { | ||
try { | ||
return await this.tspClient.request("quickinfo" /* CommandTypes.Quickinfo */, { | ||
file, | ||
line: params.position.line + 1, | ||
offset: params.position.character + 1 | ||
line: position.line + 1, | ||
offset: position.character + 1, | ||
}); | ||
if (!result.body || !result.body.info.canRename || result.body.locs.length === 0) { | ||
return undefined; | ||
} | ||
catch (err) { | ||
return undefined; | ||
} | ||
} | ||
async rename(params) { | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log('onRename', params, file); | ||
if (!file) { | ||
return undefined; | ||
} | ||
const result = await this.tspClient.request("rename" /* CommandTypes.Rename */, { | ||
file, | ||
line: params.position.line + 1, | ||
offset: params.position.character + 1, | ||
}); | ||
if (!result.body || !result.body.info.canRename || result.body.locs.length === 0) { | ||
return undefined; | ||
} | ||
const workspaceEdit = {}; | ||
result.body.locs | ||
.forEach((spanGroup) => { | ||
const uri = pathToUri(spanGroup.file, this.documents); | ||
if (!workspaceEdit.changes) { | ||
workspaceEdit.changes = {}; | ||
} | ||
const workspaceEdit = { | ||
changes: {} | ||
}; | ||
result.body.locs | ||
.forEach((spanGroup) => { | ||
const uri = pathToUri(spanGroup.file, this.documents), textEdits = workspaceEdit.changes[uri] || (workspaceEdit.changes[uri] = []); | ||
spanGroup.locs.forEach((textSpan) => { | ||
textEdits.push({ | ||
newText: `${textSpan.prefixText || ''}${params.newName}${textSpan.suffixText || ''}`, | ||
range: { | ||
start: toPosition(textSpan.start), | ||
end: toPosition(textSpan.end) | ||
} | ||
}); | ||
const textEdits = workspaceEdit.changes[uri] || (workspaceEdit.changes[uri] = []); | ||
spanGroup.locs.forEach((textSpan) => { | ||
textEdits.push({ | ||
newText: `${textSpan.prefixText || ''}${params.newName}${textSpan.suffixText || ''}`, | ||
range: { | ||
start: Position.fromLocation(textSpan.start), | ||
end: Position.fromLocation(textSpan.end), | ||
}, | ||
}); | ||
}); | ||
return workspaceEdit; | ||
}); | ||
return workspaceEdit; | ||
} | ||
references(params) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log('onReferences', params, file); | ||
if (!file) { | ||
return []; | ||
} | ||
const result = yield this.tspClient.request("references" /* CommandTypes.References */, { | ||
file, | ||
line: params.position.line + 1, | ||
offset: params.position.character + 1 | ||
}); | ||
if (!result.body) { | ||
return []; | ||
} | ||
return result.body.refs | ||
.filter(fileSpan => params.context.includeDeclaration || !fileSpan.isDefinition) | ||
.map(fileSpan => toLocation(fileSpan, this.documents)); | ||
async references(params) { | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log('onReferences', params, file); | ||
if (!file) { | ||
return []; | ||
} | ||
const result = await this.tspClient.request("references" /* CommandTypes.References */, { | ||
file, | ||
line: params.position.line + 1, | ||
offset: params.position.character + 1, | ||
}); | ||
if (!result.body) { | ||
return []; | ||
} | ||
return result.body.refs | ||
.filter(fileSpan => params.context.includeDeclaration || !fileSpan.isDefinition) | ||
.map(fileSpan => toLocation(fileSpan, this.documents)); | ||
} | ||
documentFormatting(params) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log('documentFormatting', params, file); | ||
if (!file) { | ||
return []; | ||
} | ||
const formatOptions = this.getFormattingOptions(file, params.options); | ||
// options are not yet supported in tsserver, but we can send a configure request first | ||
yield this.tspClient.request("configure" /* CommandTypes.Configure */, { | ||
formatOptions | ||
}); | ||
const response = yield this.tspClient.request("format" /* CommandTypes.Format */, { | ||
file, | ||
line: 1, | ||
offset: 1, | ||
endLine: Number.MAX_SAFE_INTEGER, | ||
endOffset: Number.MAX_SAFE_INTEGER, | ||
options: formatOptions | ||
}); | ||
if (response.body) { | ||
return response.body.map(e => toTextEdit(e)); | ||
} | ||
async documentFormatting(params) { | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log('documentFormatting', params, file); | ||
if (!file) { | ||
return []; | ||
} | ||
const formatOptions = params.options; | ||
await this.configurationManager.configureGloballyFromDocument(file, formatOptions); | ||
const response = await this.tspClient.request("format" /* CommandTypes.Format */, { | ||
file, | ||
line: 1, | ||
offset: 1, | ||
endLine: Number.MAX_SAFE_INTEGER, | ||
endOffset: Number.MAX_SAFE_INTEGER, | ||
options: formatOptions, | ||
}); | ||
if (response.body) { | ||
return response.body.map(e => toTextEdit(e)); | ||
} | ||
return []; | ||
} | ||
documentRangeFormatting(params) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log('documentRangeFormatting', params, file); | ||
if (!file) { | ||
return []; | ||
} | ||
const formatOptions = this.getFormattingOptions(file, params.options); | ||
// options are not yet supported in tsserver, but we can send a configure request first | ||
yield this.tspClient.request("configure" /* CommandTypes.Configure */, { | ||
formatOptions | ||
}); | ||
const response = yield this.tspClient.request("format" /* CommandTypes.Format */, { | ||
file, | ||
line: params.range.start.line + 1, | ||
offset: params.range.start.character + 1, | ||
endLine: params.range.end.line + 1, | ||
endOffset: params.range.end.character + 1, | ||
options: formatOptions | ||
}); | ||
if (response.body) { | ||
return response.body.map(e => toTextEdit(e)); | ||
} | ||
async documentRangeFormatting(params) { | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log('documentRangeFormatting', params, file); | ||
if (!file) { | ||
return []; | ||
} | ||
const formatOptions = params.options; | ||
await this.configurationManager.configureGloballyFromDocument(file, formatOptions); | ||
const response = await this.tspClient.request("format" /* CommandTypes.Format */, { | ||
file, | ||
line: params.range.start.line + 1, | ||
offset: params.range.start.character + 1, | ||
endLine: params.range.end.line + 1, | ||
endOffset: params.range.end.character + 1, | ||
options: formatOptions, | ||
}); | ||
if (response.body) { | ||
return response.body.map(e => toTextEdit(e)); | ||
} | ||
return []; | ||
} | ||
getFormattingOptions(file, requestOptions) { | ||
const workspacePreference = this.getWorkspacePreferencesForDocument(file); | ||
let opts = Object.assign(Object.assign({}, (workspacePreference === null || workspacePreference === void 0 ? void 0 : workspacePreference.format) || {}), requestOptions); | ||
// translate | ||
if (opts.convertTabsToSpaces === undefined) { | ||
opts.convertTabsToSpaces = requestOptions.insertSpaces; | ||
async signatureHelp(params) { | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log('signatureHelp', params, file); | ||
if (!file) { | ||
return undefined; | ||
} | ||
if (opts.indentSize === undefined) { | ||
opts.indentSize = requestOptions.tabSize; | ||
const response = await this.interuptDiagnostics(() => this.getSignatureHelp(file, params)); | ||
if (!response || !response.body) { | ||
return undefined; | ||
} | ||
if (this.workspaceRoot) { | ||
try { | ||
opts = JSON.parse(fs.readFileSync(this.workspaceRoot + '/tsfmt.json', 'utf-8')); | ||
} | ||
catch (err) { | ||
this.logger.log(`No formatting options found ${err}`); | ||
} | ||
return asSignatureHelp(response.body, params.context); | ||
} | ||
async getSignatureHelp(file, params) { | ||
try { | ||
const { position, context } = params; | ||
return await this.tspClient.request("signatureHelp" /* CommandTypes.SignatureHelp */, { | ||
file, | ||
line: position.line + 1, | ||
offset: position.character + 1, | ||
triggerReason: context ? toTsTriggerReason(context) : undefined, | ||
}); | ||
} | ||
return opts; | ||
catch (err) { | ||
return undefined; | ||
} | ||
} | ||
signatureHelp(params) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log('signatureHelp', params, file); | ||
if (!file) { | ||
return undefined; | ||
async codeAction(params) { | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log('codeAction', params, file); | ||
if (!file) { | ||
return []; | ||
} | ||
const args = Range.toFileRangeRequestArgs(file, params.range); | ||
const actions = []; | ||
const kinds = params.context.only?.map(kind => new CodeActionKind(kind)); | ||
if (!kinds || kinds.some(kind => kind.contains(CodeActionKind.QuickFix))) { | ||
const errorCodes = params.context.diagnostics.map(diagnostic => Number(diagnostic.code)); | ||
actions.push(...provideQuickFix(await this.getCodeFixes({ ...args, errorCodes }), this.documents)); | ||
} | ||
if (!kinds || kinds.some(kind => kind.contains(CodeActionKind.Refactor))) { | ||
actions.push(...provideRefactors(await this.getRefactors(args), args)); | ||
} | ||
// organize import is provided by tsserver for any line, so we only get it if explicitly requested | ||
if (kinds?.some(kind => kind.contains(CodeActionKind.SourceOrganizeImportsTs))) { | ||
// see this issue for more context about how this argument is used | ||
// https://github.com/microsoft/TypeScript/issues/43051 | ||
const skipDestructiveCodeActions = params.context.diagnostics.some( | ||
// assume no severity is an error | ||
d => (d.severity ?? 0) <= 2); | ||
const response = await this.getOrganizeImports({ | ||
scope: { type: 'file', args }, | ||
skipDestructiveCodeActions, | ||
}); | ||
actions.push(...provideOrganizeImports(response, this.documents)); | ||
} | ||
// TODO: Since we rely on diagnostics pointing at errors in the correct places, we can't proceed if we are not | ||
// sure that diagnostics are up-to-date. Thus we check `pendingDebouncedRequest` to see if there are *any* | ||
// pending diagnostic requests (regardless of for which file). | ||
// In general would be better to replace the whole diagnostics handling logic with the one from | ||
// bufferSyncSupport.ts in VSCode's typescript language features. | ||
if (kinds && !this.pendingDebouncedRequest) { | ||
const diagnostics = this.diagnosticQueue?.getDiagnosticsForFile(file) || []; | ||
if (diagnostics.length) { | ||
actions.push(...await this.typeScriptAutoFixProvider.provideCodeActions(kinds, file, diagnostics, this.documents)); | ||
} | ||
const response = yield this.interuptDiagnostics(() => this.getSignatureHelp(file, params.position)); | ||
if (!response || !response.body) { | ||
return undefined; | ||
} | ||
return asSignatureHelp(response.body); | ||
}); | ||
} | ||
return actions; | ||
} | ||
getSignatureHelp(file, position) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
try { | ||
return yield this.tspClient.request("signatureHelp" /* CommandTypes.SignatureHelp */, { | ||
file, | ||
line: position.line + 1, | ||
offset: position.character + 1 | ||
}); | ||
} | ||
catch (err) { | ||
return undefined; | ||
} | ||
}); | ||
async getCodeFixes(args) { | ||
try { | ||
return await this.tspClient.request("getCodeFixes" /* CommandTypes.GetCodeFixes */, args); | ||
} | ||
catch (err) { | ||
return undefined; | ||
} | ||
} | ||
codeAction(params) { | ||
var _a, _b; | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log('codeAction', params, file); | ||
if (!file) { | ||
return []; | ||
async getRefactors(args) { | ||
try { | ||
return await this.tspClient.request("getApplicableRefactors" /* CommandTypes.GetApplicableRefactors */, args); | ||
} | ||
catch (err) { | ||
return undefined; | ||
} | ||
} | ||
async getOrganizeImports(args) { | ||
try { | ||
await this.configurationManager.configureGloballyFromDocument(args.scope.args.file); | ||
return await this.tspClient.request("organizeImports" /* CommandTypes.OrganizeImports */, args); | ||
} | ||
catch (err) { | ||
return undefined; | ||
} | ||
} | ||
async executeCommand(arg, token, workDoneProgress) { | ||
this.logger.log('executeCommand', arg); | ||
if (arg.command === Commands.APPLY_WORKSPACE_EDIT && arg.arguments) { | ||
const edit = arg.arguments[0]; | ||
await this.options.lspClient.applyWorkspaceEdit({ edit }); | ||
} | ||
else if (arg.command === Commands.APPLY_CODE_ACTION && arg.arguments) { | ||
const codeAction = arg.arguments[0]; | ||
if (!await this.applyFileCodeEdits(codeAction.changes)) { | ||
return; | ||
} | ||
const args = toFileRangeRequestArgs(file, params.range); | ||
const actions = []; | ||
const kinds = (_a = params.context.only) === null || _a === void 0 ? void 0 : _a.map(kind => new CodeActionKind(kind)); | ||
if (!kinds || kinds.some(kind => kind.contains(CodeActionKind.QuickFix))) { | ||
const errorCodes = params.context.diagnostics.map(diagnostic => Number(diagnostic.code)); | ||
actions.push(...provideQuickFix(yield this.getCodeFixes(Object.assign(Object.assign({}, args), { errorCodes })), this.documents)); | ||
} | ||
if (!kinds || kinds.some(kind => kind.contains(CodeActionKind.Refactor))) { | ||
actions.push(...provideRefactors(yield this.getRefactors(args), args)); | ||
} | ||
// organize import is provided by tsserver for any line, so we only get it if explicitly requested | ||
if (kinds === null || kinds === void 0 ? void 0 : kinds.some(kind => kind.contains(CodeActionKind.SourceOrganizeImportsTs))) { | ||
// see this issue for more context about how this argument is used | ||
// https://github.com/microsoft/TypeScript/issues/43051 | ||
const skipDestructiveCodeActions = params.context.diagnostics.some( | ||
// assume no severity is an error | ||
d => { var _a; return ((_a = d.severity) !== null && _a !== void 0 ? _a : 0) <= 2; }); | ||
const response = yield this.getOrganizeImports({ | ||
scope: { type: 'file', args }, | ||
skipDestructiveCodeActions | ||
}); | ||
actions.push(...provideOrganizeImports(response, this.documents)); | ||
} | ||
// TODO: Since we rely on diagnostics pointing at errors in the correct places, we can't proceed if we are not | ||
// sure that diagnostics are up-to-date. Thus we check `pendingDebouncedRequest` to see if there are *any* | ||
// pending diagnostic requests (regardless of for which file). | ||
// In general would be better to replace the whole diagnostics handling logic with the one from | ||
// bufferSyncSupport.ts in VSCode's typescript language features. | ||
if (kinds && !this.pendingDebouncedRequest) { | ||
const diagnostics = ((_b = this.diagnosticQueue) === null || _b === void 0 ? void 0 : _b.getDiagnosticsForFile(file)) || []; | ||
if (diagnostics.length) { | ||
actions.push(...yield this.typeScriptAutoFixProvider.provideCodeActions(kinds, file, diagnostics, this.documents)); | ||
if (codeAction.commands && codeAction.commands.length) { | ||
for (const command of codeAction.commands) { | ||
await this.tspClient.request("applyCodeActionCommand" /* CommandTypes.ApplyCodeActionCommand */, { command }); | ||
} | ||
} | ||
return actions; | ||
}); | ||
} | ||
getCodeFixes(args) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
try { | ||
return yield this.tspClient.request("getCodeFixes" /* CommandTypes.GetCodeFixes */, args); | ||
} | ||
else if (arg.command === Commands.APPLY_REFACTORING && arg.arguments) { | ||
const args = arg.arguments[0]; | ||
const { body } = await this.tspClient.request("getEditsForRefactor" /* CommandTypes.GetEditsForRefactor */, args); | ||
if (!body || !body.edits.length) { | ||
return; | ||
} | ||
catch (err) { | ||
return undefined; | ||
for (const edit of body.edits) { | ||
await fs.ensureFile(edit.fileName); | ||
} | ||
}); | ||
} | ||
getRefactors(args) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
try { | ||
return yield this.tspClient.request("getApplicableRefactors" /* CommandTypes.GetApplicableRefactors */, args); | ||
if (!await this.applyFileCodeEdits(body.edits)) { | ||
return; | ||
} | ||
catch (err) { | ||
return undefined; | ||
} | ||
}); | ||
} | ||
getOrganizeImports(args) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
try { | ||
// Pass format options to organize imports | ||
yield this.tspClient.request("configure" /* CommandTypes.Configure */, { | ||
formatOptions: this.getWorkspacePreferencesForDocument(args.scope.args.file).format | ||
const renameLocation = body.renameLocation; | ||
if (renameLocation) { | ||
await this.options.lspClient.rename({ | ||
textDocument: { | ||
uri: pathToUri(args.file, this.documents), | ||
}, | ||
position: Position.fromLocation(renameLocation), | ||
}); | ||
return yield this.tspClient.request("organizeImports" /* CommandTypes.OrganizeImports */, args); | ||
} | ||
catch (err) { | ||
return undefined; | ||
} | ||
}); | ||
} | ||
executeCommand(arg) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
this.logger.log('executeCommand', arg); | ||
if (arg.command === Commands.APPLY_WORKSPACE_EDIT && arg.arguments) { | ||
const edit = arg.arguments[0]; | ||
yield this.options.lspClient.applyWorkspaceEdit({ | ||
edit | ||
}); | ||
} | ||
else if (arg.command === Commands.APPLY_CODE_ACTION && arg.arguments) { | ||
const codeAction = arg.arguments[0]; | ||
if (!(yield this.applyFileCodeEdits(codeAction.changes))) { | ||
return; | ||
} | ||
} | ||
else if (arg.command === Commands.ORGANIZE_IMPORTS && arg.arguments) { | ||
const file = arg.arguments[0]; | ||
const additionalArguments = arg.arguments[1] || {}; | ||
await this.configurationManager.configureGloballyFromDocument(file); | ||
const { body } = await this.tspClient.request("organizeImports" /* CommandTypes.OrganizeImports */, { | ||
scope: { | ||
type: 'file', | ||
args: { file }, | ||
}, | ||
skipDestructiveCodeActions: additionalArguments.skipDestructiveCodeActions, | ||
}); | ||
await this.applyFileCodeEdits(body); | ||
} | ||
else if (arg.command === Commands.APPLY_RENAME_FILE && arg.arguments) { | ||
const { sourceUri, targetUri } = arg.arguments[0]; | ||
this.applyRenameFile(sourceUri, targetUri); | ||
} | ||
else if (arg.command === Commands.APPLY_COMPLETION_CODE_ACTION && arg.arguments) { | ||
const [_, codeActions] = arg.arguments; | ||
for (const codeAction of codeActions) { | ||
await this.applyFileCodeEdits(codeAction.changes); | ||
if (codeAction.commands && codeAction.commands.length) { | ||
for (const command of codeAction.commands) { | ||
yield this.tspClient.request("applyCodeActionCommand" /* CommandTypes.ApplyCodeActionCommand */, { command }); | ||
await this.tspClient.request("applyCodeActionCommand" /* CommandTypes.ApplyCodeActionCommand */, { command }); | ||
} | ||
} | ||
// Execute only the first code action. | ||
break; | ||
} | ||
else if (arg.command === Commands.APPLY_REFACTORING && arg.arguments) { | ||
const args = arg.arguments[0]; | ||
const { body } = yield this.tspClient.request("getEditsForRefactor" /* CommandTypes.GetEditsForRefactor */, args); | ||
if (!body || !body.edits.length) { | ||
return; | ||
} | ||
for (const edit of body.edits) { | ||
yield fs.ensureFile(edit.fileName); | ||
} | ||
if (!(yield this.applyFileCodeEdits(body.edits))) { | ||
return; | ||
} | ||
const renameLocation = body.renameLocation; | ||
if (renameLocation) { | ||
yield this.options.lspClient.rename({ | ||
textDocument: { | ||
uri: pathToUri(args.file, this.documents) | ||
}, | ||
position: toPosition(renameLocation) | ||
}); | ||
} | ||
} | ||
else if (arg.command === Commands.ORGANIZE_IMPORTS && arg.arguments) { | ||
const file = arg.arguments[0]; | ||
const additionalArguments = arg.arguments[1] || {}; | ||
yield this.tspClient.request("configure" /* CommandTypes.Configure */, { | ||
formatOptions: this.getWorkspacePreferencesForDocument(file).format | ||
}); | ||
const { body } = yield this.tspClient.request("organizeImports" /* CommandTypes.OrganizeImports */, { | ||
scope: { | ||
type: 'file', | ||
args: { file } | ||
}, | ||
skipDestructiveCodeActions: additionalArguments.skipDestructiveCodeActions | ||
}); | ||
yield this.applyFileCodeEdits(body); | ||
} | ||
else if (arg.command === Commands.APPLY_RENAME_FILE && arg.arguments) { | ||
const { sourceUri, targetUri } = arg.arguments[0]; | ||
this.applyRenameFile(sourceUri, targetUri); | ||
} | ||
else if (arg.command === Commands.APPLY_COMPLETION_CODE_ACTION && arg.arguments) { | ||
const [_, codeActions] = arg.arguments; | ||
for (const codeAction of codeActions) { | ||
yield this.applyFileCodeEdits(codeAction.changes); | ||
if (codeAction.commands && codeAction.commands.length) { | ||
for (const command of codeAction.commands) { | ||
yield this.tspClient.request("applyCodeActionCommand" /* CommandTypes.ApplyCodeActionCommand */, { command }); | ||
} | ||
} | ||
// Execute only the first code action. | ||
break; | ||
} | ||
} | ||
else { | ||
this.logger.error(`Unknown command ${arg.command}.`); | ||
} | ||
}); | ||
} | ||
else if (arg.command === Commands.SOURCE_DEFINITION) { | ||
const [uri, position] = (arg.arguments || []); | ||
const reporter = await this.options.lspClient.createProgressReporter(token, workDoneProgress); | ||
return SourceDefinitionCommand.execute(uri, position, this.documents, this.tspClient, this.options.lspClient, reporter); | ||
} | ||
else { | ||
this.logger.error(`Unknown command ${arg.command}.`); | ||
} | ||
} | ||
applyFileCodeEdits(edits) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
if (!edits.length) { | ||
return false; | ||
} | ||
const changes = {}; | ||
for (const edit of edits) { | ||
changes[pathToUri(edit.fileName, this.documents)] = edit.textChanges.map(toTextEdit); | ||
} | ||
const { applied } = yield this.options.lspClient.applyWorkspaceEdit({ | ||
edit: { changes } | ||
}); | ||
return applied; | ||
async applyFileCodeEdits(edits) { | ||
if (!edits.length) { | ||
return false; | ||
} | ||
const changes = {}; | ||
for (const edit of edits) { | ||
changes[pathToUri(edit.fileName, this.documents)] = edit.textChanges.map(toTextEdit); | ||
} | ||
const { applied } = await this.options.lspClient.applyWorkspaceEdit({ | ||
edit: { changes }, | ||
}); | ||
return applied; | ||
} | ||
applyRenameFile(sourceUri, targetUri) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const edits = yield this.getEditsForFileRename(sourceUri, targetUri); | ||
this.applyFileCodeEdits(edits); | ||
}); | ||
async applyRenameFile(sourceUri, targetUri) { | ||
const edits = await this.getEditsForFileRename(sourceUri, targetUri); | ||
this.applyFileCodeEdits(edits); | ||
} | ||
getEditsForFileRename(sourceUri, targetUri) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const newFilePath = uriToPath(targetUri); | ||
const oldFilePath = uriToPath(sourceUri); | ||
if (!newFilePath || !oldFilePath) { | ||
return []; | ||
} | ||
try { | ||
const { body } = yield this.tspClient.request("getEditsForFileRename" /* CommandTypes.GetEditsForFileRename */, { | ||
oldFilePath, | ||
newFilePath | ||
}); | ||
return body; | ||
} | ||
catch (err) { | ||
return []; | ||
} | ||
}); | ||
async getEditsForFileRename(sourceUri, targetUri) { | ||
const newFilePath = uriToPath(targetUri); | ||
const oldFilePath = uriToPath(sourceUri); | ||
if (!newFilePath || !oldFilePath) { | ||
return []; | ||
} | ||
try { | ||
const { body } = await this.tspClient.request("getEditsForFileRename" /* CommandTypes.GetEditsForFileRename */, { | ||
oldFilePath, | ||
newFilePath, | ||
}); | ||
return body; | ||
} | ||
catch (err) { | ||
return []; | ||
} | ||
} | ||
documentHighlight(arg) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const file = uriToPath(arg.textDocument.uri); | ||
this.logger.log('documentHighlight', arg, file); | ||
if (!file) { | ||
return []; | ||
async documentHighlight(arg) { | ||
const file = uriToPath(arg.textDocument.uri); | ||
this.logger.log('documentHighlight', arg, file); | ||
if (!file) { | ||
return []; | ||
} | ||
let response; | ||
try { | ||
response = await this.tspClient.request("documentHighlights" /* CommandTypes.DocumentHighlights */, { | ||
file, | ||
line: arg.position.line + 1, | ||
offset: arg.position.character + 1, | ||
filesToSearch: [file], | ||
}); | ||
} | ||
catch (err) { | ||
return []; | ||
} | ||
if (!response.body) { | ||
return []; | ||
} | ||
const result = []; | ||
for (const item of response.body) { | ||
// tsp returns item.file with POSIX path delimiters, whereas file is platform specific. | ||
// Converting to a URI and back to a path ensures consistency. | ||
if (normalizePath(item.file) === file) { | ||
const highlights = toDocumentHighlight(item); | ||
result.push(...highlights); | ||
} | ||
let response; | ||
try { | ||
response = yield this.tspClient.request("documentHighlights" /* CommandTypes.DocumentHighlights */, { | ||
file, | ||
line: arg.position.line + 1, | ||
offset: arg.position.character + 1, | ||
filesToSearch: [file] | ||
}); | ||
} | ||
catch (err) { | ||
return []; | ||
} | ||
if (!response.body) { | ||
return []; | ||
} | ||
const result = []; | ||
for (const item of response.body) { | ||
// tsp returns item.file with POSIX path delimiters, whereas file is platform specific. | ||
// Converting to a URI and back to a path ensures consistency. | ||
if (normalizePath(item.file) === file) { | ||
const highlights = toDocumentHighlight(item); | ||
result.push(...highlights); | ||
} | ||
} | ||
return result; | ||
}); | ||
} | ||
return result; | ||
} | ||
@@ -1085,24 +1008,22 @@ lastFileOrDummy() { | ||
} | ||
workspaceSymbol(params) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const result = yield this.tspClient.request("navto" /* CommandTypes.Navto */, { | ||
file: this.lastFileOrDummy(), | ||
searchValue: params.query | ||
}); | ||
if (!result.body) { | ||
return []; | ||
} | ||
return result.body.map(item => { | ||
return { | ||
location: { | ||
uri: pathToUri(item.file, this.documents), | ||
range: { | ||
start: toPosition(item.start), | ||
end: toPosition(item.end) | ||
} | ||
async workspaceSymbol(params) { | ||
const result = await this.tspClient.request("navto" /* CommandTypes.Navto */, { | ||
file: this.lastFileOrDummy(), | ||
searchValue: params.query, | ||
}); | ||
if (!result.body) { | ||
return []; | ||
} | ||
return result.body.map(item => { | ||
return { | ||
location: { | ||
uri: pathToUri(item.file, this.documents), | ||
range: { | ||
start: Position.fromLocation(item.start), | ||
end: Position.fromLocation(item.end), | ||
}, | ||
kind: toSymbolKind(item.kind), | ||
name: item.name | ||
}; | ||
}); | ||
}, | ||
kind: toSymbolKind(item.kind), | ||
name: item.name, | ||
}; | ||
}); | ||
@@ -1113,29 +1034,27 @@ } | ||
*/ | ||
foldingRanges(params) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log('foldingRanges', params, file); | ||
if (!file) { | ||
return undefined; | ||
async foldingRanges(params) { | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log('foldingRanges', params, file); | ||
if (!file) { | ||
return undefined; | ||
} | ||
const document = this.documents.get(file); | ||
if (!document) { | ||
throw new Error("The document should be opened for foldingRanges', file: " + file); | ||
} | ||
const { body } = await this.tspClient.request("getOutliningSpans" /* CommandTypes.GetOutliningSpans */, { file }); | ||
if (!body) { | ||
return undefined; | ||
} | ||
const foldingRanges = []; | ||
for (const span of body) { | ||
const foldingRange = this.asFoldingRange(span, document); | ||
if (foldingRange) { | ||
foldingRanges.push(foldingRange); | ||
} | ||
const document = this.documents.get(file); | ||
if (!document) { | ||
throw new Error("The document should be opened for foldingRanges', file: " + file); | ||
} | ||
const { body } = yield this.tspClient.request("getOutliningSpans" /* CommandTypes.GetOutliningSpans */, { file }); | ||
if (!body) { | ||
return undefined; | ||
} | ||
const foldingRanges = []; | ||
for (const span of body) { | ||
const foldingRange = this.asFoldingRange(span, document); | ||
if (foldingRange) { | ||
foldingRanges.push(foldingRange); | ||
} | ||
} | ||
return foldingRanges; | ||
}); | ||
} | ||
return foldingRanges; | ||
} | ||
asFoldingRange(span, document) { | ||
const range = asRange(span.textSpan); | ||
const range = Range.fromTextSpan(span.textSpan); | ||
const kind = this.asFoldingRangeKind(span); | ||
@@ -1155,3 +1074,3 @@ // workaround for https://github.com/Microsoft/vscode/issues/49904 | ||
endLine, | ||
kind | ||
kind, | ||
}; | ||
@@ -1168,11 +1087,10 @@ } | ||
} | ||
onTsEvent(event) { | ||
var _a; | ||
async onTsEvent(event) { | ||
if (event.event === "semanticDiag" /* EventTypes.SementicDiag */ || | ||
event.event === "syntaxDiag" /* EventTypes.SyntaxDiag */ || | ||
event.event === "suggestionDiag" /* EventTypes.SuggestionDiag */) { | ||
(_a = this.diagnosticQueue) === null || _a === void 0 ? void 0 : _a.updateDiagnostics(event.event, event); | ||
this.diagnosticQueue?.updateDiagnostics(event.event, event); | ||
} | ||
else if (event.event === "projectLoadingStart" /* EventTypes.ProjectLoadingStart */) { | ||
this.loadingIndicator.startedLoadingProject(event.body.projectName); | ||
await this.loadingIndicator.startedLoadingProject(event.body.projectName); | ||
} | ||
@@ -1184,133 +1102,120 @@ else if (event.event === "projectLoadingFinish" /* EventTypes.ProjectLoadingFinish */) { | ||
this.logger.log('Ignored event', { | ||
event: event.event | ||
event: event.event, | ||
}); | ||
} | ||
} | ||
calls(params) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
let callsResult = { calls: [] }; | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log('calls', params, file); | ||
if (!file) { | ||
return callsResult; | ||
} | ||
if (params.direction === lspcalls.CallDirection.Outgoing) { | ||
const documentProvider = (file) => this.documents.get(file); | ||
callsResult = yield computeCallees(this.tspClient, params, documentProvider); | ||
} | ||
else { | ||
callsResult = yield computeCallers(this.tspClient, params); | ||
} | ||
async calls(params) { | ||
let callsResult = { calls: [] }; | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log('calls', params, file); | ||
if (!file) { | ||
return callsResult; | ||
} | ||
if (params.direction === lspcalls.CallDirection.Outgoing) { | ||
const documentProvider = (file) => this.documents.get(file); | ||
callsResult = await computeCallees(this.tspClient, params, documentProvider); | ||
} | ||
else { | ||
callsResult = await computeCallers(this.tspClient, params); | ||
} | ||
return callsResult; | ||
} | ||
async inlayHints(params) { | ||
return await TypeScriptInlayHintsProvider.provideInlayHints(params.textDocument.uri, params.range, this.documents, this.tspClient, this.options.lspClient, this.configurationManager); | ||
} | ||
async inlayHintsLegacy(params) { | ||
this.options.lspClient.logMessage({ | ||
message: 'Support for experimental "typescript/inlayHints" request is deprecated. Use spec-compliant "textDocument/inlayHint" instead.', | ||
type: lsp.MessageType.Warning, | ||
}); | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log('inlayHints', params, file); | ||
if (!file) { | ||
return { inlayHints: [] }; | ||
} | ||
await this.configurationManager.configureGloballyFromDocument(file); | ||
const doc = this.documents.get(file); | ||
if (!doc) { | ||
return { inlayHints: [] }; | ||
} | ||
const start = doc.offsetAt(params.range?.start ?? { | ||
line: 0, | ||
character: 0, | ||
}); | ||
const end = doc.offsetAt(params.range?.end ?? { | ||
line: doc.lineCount + 1, | ||
character: 0, | ||
}); | ||
try { | ||
const result = await this.tspClient.request("provideInlayHints" /* CommandTypes.ProvideInlayHints */, { | ||
file, | ||
start: start, | ||
length: end - start, | ||
}); | ||
return { | ||
inlayHints: result.body?.map((item) => ({ | ||
text: item.text, | ||
position: Position.fromLocation(item.position), | ||
whitespaceAfter: item.whitespaceAfter, | ||
whitespaceBefore: item.whitespaceBefore, | ||
kind: item.kind, | ||
})) ?? [], | ||
}; | ||
} | ||
catch { | ||
return { | ||
inlayHints: [], | ||
}; | ||
} | ||
} | ||
inlayHints(params) { | ||
var _a, _b, _c, _d, _e, _f; | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log('inlayHints', params, file); | ||
if (!file) { | ||
return { inlayHints: [] }; | ||
} | ||
yield this.tspClient.request("configure" /* CommandTypes.Configure */, { | ||
preferences: this.getInlayHintsOptions(file) | ||
}); | ||
const doc = this.documents.get(file); | ||
if (!doc) { | ||
return { inlayHints: [] }; | ||
} | ||
const start = doc.offsetAt((_b = (_a = params.range) === null || _a === void 0 ? void 0 : _a.start) !== null && _b !== void 0 ? _b : { | ||
line: 0, | ||
character: 0 | ||
}); | ||
const end = doc.offsetAt((_d = (_c = params.range) === null || _c === void 0 ? void 0 : _c.end) !== null && _d !== void 0 ? _d : { | ||
line: doc.lineCount + 1, | ||
character: 0 | ||
}); | ||
try { | ||
const result = yield this.tspClient.request("provideInlayHints" /* CommandTypes.ProvideInlayHints */, { | ||
file, | ||
start: start, | ||
length: end - start | ||
}); | ||
return { | ||
inlayHints: (_f = (_e = result.body) === null || _e === void 0 ? void 0 : _e.map((item) => ({ | ||
text: item.text, | ||
position: toPosition(item.position), | ||
whitespaceAfter: item.whitespaceAfter, | ||
whitespaceBefore: item.whitespaceBefore, | ||
kind: item.kind | ||
}))) !== null && _f !== void 0 ? _f : [] | ||
}; | ||
} | ||
catch (_g) { | ||
return { | ||
inlayHints: [] | ||
}; | ||
} | ||
async semanticTokensFull(params) { | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log('semanticTokensFull', params, file); | ||
if (!file) { | ||
return { data: [] }; | ||
} | ||
const doc = this.documents.get(file); | ||
if (!doc) { | ||
return { data: [] }; | ||
} | ||
const start = doc.offsetAt({ | ||
line: 0, | ||
character: 0, | ||
}); | ||
const end = doc.offsetAt({ | ||
line: doc.lineCount, | ||
character: 0, | ||
}); | ||
return this.getSemanticTokens(doc, file, start, end); | ||
} | ||
getInlayHintsOptions(file) { | ||
var _a, _b; | ||
const workspacePreference = this.getWorkspacePreferencesForDocument(file); | ||
const userPreferences = ((_a = this.initializeParams.initializationOptions) === null || _a === void 0 ? void 0 : _a.preferences) || {}; | ||
return Object.assign(Object.assign({}, userPreferences), (_b = workspacePreference.inlayHints) !== null && _b !== void 0 ? _b : {}); | ||
async semanticTokensRange(params) { | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log('semanticTokensRange', params, file); | ||
if (!file) { | ||
return { data: [] }; | ||
} | ||
const doc = this.documents.get(file); | ||
if (!doc) { | ||
return { data: [] }; | ||
} | ||
const start = doc.offsetAt(params.range.start); | ||
const end = doc.offsetAt(params.range.end); | ||
return this.getSemanticTokens(doc, file, start, end); | ||
} | ||
semanticTokensFull(params) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log('semanticTokensFull', params, file); | ||
if (!file) { | ||
return { data: [] }; | ||
} | ||
const doc = this.documents.get(file); | ||
if (!doc) { | ||
return { data: [] }; | ||
} | ||
const start = doc.offsetAt({ | ||
line: 0, | ||
character: 0 | ||
async getSemanticTokens(doc, file, startOffset, endOffset) { | ||
try { | ||
const result = await this.tspClient.request("encodedSemanticClassifications-full" /* CommandTypes.EncodedSemanticClassificationsFull */, { | ||
file, | ||
start: startOffset, | ||
length: endOffset - startOffset, | ||
format: '2020', | ||
}); | ||
const end = doc.offsetAt({ | ||
line: doc.lineCount, | ||
character: 0 | ||
}); | ||
return this.getSemanticTokens(doc, file, start, end); | ||
}); | ||
const spans = result.body?.spans ?? []; | ||
return { data: lspsemanticTokens.transformSpans(doc, spans) }; | ||
} | ||
catch { | ||
return { data: [] }; | ||
} | ||
} | ||
semanticTokensRange(params) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const file = uriToPath(params.textDocument.uri); | ||
this.logger.log('semanticTokensRange', params, file); | ||
if (!file) { | ||
return { data: [] }; | ||
} | ||
const doc = this.documents.get(file); | ||
if (!doc) { | ||
return { data: [] }; | ||
} | ||
const start = doc.offsetAt(params.range.start); | ||
const end = doc.offsetAt(params.range.end); | ||
return this.getSemanticTokens(doc, file, start, end); | ||
}); | ||
} | ||
getSemanticTokens(doc, file, startOffset, endOffset) { | ||
var _a, _b; | ||
return __awaiter(this, void 0, void 0, function* () { | ||
try { | ||
const result = yield this.tspClient.request("encodedSemanticClassifications-full" /* CommandTypes.EncodedSemanticClassificationsFull */, { | ||
file, | ||
start: startOffset, | ||
length: endOffset - startOffset, | ||
format: '2020' | ||
}); | ||
const spans = (_b = (_a = result.body) === null || _a === void 0 ? void 0 : _a.spans) !== null && _b !== void 0 ? _b : []; | ||
return { data: lspsemanticTokens.transformSpans(doc, spans) }; | ||
} | ||
catch (_c) { | ||
return { data: [] }; | ||
} | ||
}); | ||
} | ||
} | ||
//# sourceMappingURL=lsp-server.js.map |
@@ -12,5 +12,5 @@ import * as chai from 'chai'; | ||
fileName, | ||
textChanges: [] | ||
} | ||
] | ||
textChanges: [], | ||
}, | ||
], | ||
}; | ||
@@ -27,7 +27,7 @@ const actual = provideOrganizeImports(response, undefined); | ||
uri: uri('file'), | ||
version: null | ||
} | ||
} | ||
] | ||
} | ||
version: null, | ||
}, | ||
}, | ||
], | ||
}, | ||
}]; | ||
@@ -34,0 +34,0 @@ chai.assert.deepEqual(actual, expected); |
import * as lsp from 'vscode-languageserver'; | ||
import type tsp from 'typescript/lib/protocol.d.js'; | ||
import { LspDocuments } from './document.js'; | ||
import { SupportedFeatures } from './ts-protocol.js'; | ||
export declare function uriToPath(stringUri: string): string | undefined; | ||
@@ -19,11 +20,8 @@ export declare function pathToUri(filepath: string, documents: LspDocuments | undefined): string; | ||
export declare function normalizeFsPath(fsPath: string): string; | ||
export declare function toPosition(location: tsp.Location): lsp.Position; | ||
export declare function toLocation(fileSpan: tsp.FileSpan, documents: LspDocuments | undefined): lsp.Location; | ||
export declare function toFileRangeRequestArgs(file: string, range: lsp.Range): tsp.FileRangeRequestArgs; | ||
export declare function toSymbolKind(tspKind: string): lsp.SymbolKind; | ||
export declare function toDiagnostic(diagnostic: tsp.Diagnostic, documents: LspDocuments | undefined, publishDiagnosticsCapabilities: lsp.TextDocumentClientCapabilities['publishDiagnostics']): lsp.Diagnostic; | ||
export declare function toDiagnostic(diagnostic: tsp.Diagnostic, documents: LspDocuments | undefined, features: SupportedFeatures): lsp.Diagnostic; | ||
export declare function toTextEdit(edit: tsp.CodeEdit): lsp.TextEdit; | ||
export declare function toTextDocumentEdit(change: tsp.FileCodeEdits, documents: LspDocuments | undefined): lsp.TextDocumentEdit; | ||
export declare function toDocumentHighlight(item: tsp.DocumentHighlightsItem): lsp.DocumentHighlight[]; | ||
export declare function asRange(span: tsp.TextSpan): lsp.Range; | ||
export declare function asDocumentation(data: { | ||
@@ -37,5 +35,2 @@ documentation?: tsp.SymbolDisplayPart[]; | ||
export declare function asPlainText(parts: string | tsp.SymbolDisplayPart[]): string; | ||
export declare namespace Range { | ||
function intersection(one: lsp.Range, other: lsp.Range): lsp.Range | undefined; | ||
} | ||
//# sourceMappingURL=protocol-translation.d.ts.map |
@@ -9,2 +9,3 @@ /* | ||
import vscodeUri from 'vscode-uri'; | ||
import { Position } from './utils/typeConverters.js'; | ||
const RE_PATHSEP_WINDOWS = /\\/g; | ||
@@ -58,10 +59,2 @@ export function uriToPath(stringUri) { | ||
} | ||
export function toPosition(location) { | ||
// Clamping on the low side to 0 since Typescript returns 0, 0 when creating new file | ||
// even though position is suppoed to be 1-based. | ||
return { | ||
line: Math.max(0, location.line - 1), | ||
character: Math.max(0, location.offset - 1) | ||
}; | ||
} | ||
export function toLocation(fileSpan, documents) { | ||
@@ -71,16 +64,7 @@ return { | ||
range: { | ||
start: toPosition(fileSpan.start), | ||
end: toPosition(fileSpan.end) | ||
} | ||
start: Position.fromLocation(fileSpan.start), | ||
end: Position.fromLocation(fileSpan.end), | ||
}, | ||
}; | ||
} | ||
export function toFileRangeRequestArgs(file, range) { | ||
return { | ||
file, | ||
startLine: range.start.line + 1, | ||
startOffset: range.start.character + 1, | ||
endLine: range.end.line + 1, | ||
endOffset: range.end.character + 1 | ||
}; | ||
} | ||
const symbolKindsMapping = { | ||
@@ -109,3 +93,3 @@ 'enum member': lsp.SymbolKind.Constant, | ||
setter: lsp.SymbolKind.Method, | ||
var: lsp.SymbolKind.Variable | ||
var: lsp.SymbolKind.Variable, | ||
}; | ||
@@ -123,7 +107,7 @@ export function toSymbolKind(tspKind) { | ||
} | ||
export function toDiagnostic(diagnostic, documents, publishDiagnosticsCapabilities) { | ||
export function toDiagnostic(diagnostic, documents, features) { | ||
const lspDiagnostic = { | ||
range: { | ||
start: toPosition(diagnostic.start), | ||
end: toPosition(diagnostic.end) | ||
start: Position.fromLocation(diagnostic.start), | ||
end: Position.fromLocation(diagnostic.end), | ||
}, | ||
@@ -134,5 +118,5 @@ message: diagnostic.text, | ||
source: diagnostic.source || 'typescript', | ||
relatedInformation: asRelatedInformation(diagnostic.relatedInformation, documents) | ||
relatedInformation: asRelatedInformation(diagnostic.relatedInformation, documents), | ||
}; | ||
if (publishDiagnosticsCapabilities === null || publishDiagnosticsCapabilities === void 0 ? void 0 : publishDiagnosticsCapabilities.tagSupport) { | ||
if (features.diagnosticsTagSupport) { | ||
lspDiagnostic.tags = getDiagnosticTags(diagnostic); | ||
@@ -168,6 +152,6 @@ } | ||
range: { | ||
start: toPosition(edit.start), | ||
end: toPosition(edit.end) | ||
start: Position.fromLocation(edit.start), | ||
end: Position.fromLocation(edit.end), | ||
}, | ||
newText: edit.newText | ||
newText: edit.newText, | ||
}; | ||
@@ -179,5 +163,5 @@ } | ||
uri: pathToUri(change.fileName, documents), | ||
version: currentVersion(change.fileName, documents) | ||
version: currentVersion(change.fileName, documents), | ||
}, | ||
edits: change.textChanges.map(c => toTextEdit(c)) | ||
edits: change.textChanges.map(c => toTextEdit(c)), | ||
}; | ||
@@ -190,5 +174,5 @@ } | ||
range: { | ||
start: toPosition(i.start), | ||
end: toPosition(i.end) | ||
} | ||
start: Position.fromLocation(i.start), | ||
end: Position.fromLocation(i.end), | ||
}, | ||
}; | ||
@@ -213,5 +197,2 @@ }); | ||
} | ||
export function asRange(span) { | ||
return lsp.Range.create(Math.max(0, span.start.line - 1), Math.max(0, span.start.offset - 1), Math.max(0, span.end.line - 1), Math.max(0, span.end.offset - 1)); | ||
} | ||
export function asDocumentation(data) { | ||
@@ -230,3 +211,3 @@ let value = ''; | ||
kind: lsp.MarkupKind.Markdown, | ||
value | ||
value, | ||
} : undefined; | ||
@@ -287,70 +268,2 @@ } | ||
} | ||
var Position; | ||
(function (Position) { | ||
function Min(...positions) { | ||
if (!positions.length) { | ||
return undefined; | ||
} | ||
let result = positions.pop(); | ||
for (const p of positions) { | ||
if (isBefore(p, result)) { | ||
result = p; | ||
} | ||
} | ||
return result; | ||
} | ||
Position.Min = Min; | ||
function isBefore(one, other) { | ||
if (one.line < other.line) { | ||
return true; | ||
} | ||
if (other.line < one.line) { | ||
return false; | ||
} | ||
return one.character < other.character; | ||
} | ||
Position.isBefore = isBefore; | ||
function Max(...positions) { | ||
if (!positions.length) { | ||
return undefined; | ||
} | ||
let result = positions.pop(); | ||
for (const p of positions) { | ||
if (isAfter(p, result)) { | ||
result = p; | ||
} | ||
} | ||
return result; | ||
} | ||
Position.Max = Max; | ||
function isAfter(one, other) { | ||
return !isBeforeOrEqual(one, other); | ||
} | ||
Position.isAfter = isAfter; | ||
function isBeforeOrEqual(one, other) { | ||
if (one.line < other.line) { | ||
return true; | ||
} | ||
if (other.line < one.line) { | ||
return false; | ||
} | ||
return one.character <= other.character; | ||
} | ||
Position.isBeforeOrEqual = isBeforeOrEqual; | ||
})(Position || (Position = {})); | ||
export var Range; | ||
(function (Range) { | ||
function intersection(one, other) { | ||
const start = Position.Max(other.start, one.start); | ||
const end = Position.Min(other.end, one.end); | ||
if (Position.isAfter(start, end)) { | ||
// this happens when there is no overlap: | ||
// |-----| | ||
// |----| | ||
return undefined; | ||
} | ||
return lsp.Range.create(start, end); | ||
} | ||
Range.intersection = intersection; | ||
})(Range = Range || (Range = {})); | ||
//# sourceMappingURL=protocol-translation.js.map |
@@ -17,5 +17,5 @@ /* | ||
command: Commands.APPLY_WORKSPACE_EDIT, | ||
arguments: [{ documentChanges: fix.changes.map(c => toTextDocumentEdit(c, documents)) }] | ||
arguments: [{ documentChanges: fix.changes.map(c => toTextDocumentEdit(c, documents)) }], | ||
}, lsp.CodeActionKind.QuickFix)); | ||
} | ||
//# sourceMappingURL=quickfix.js.map |
@@ -30,3 +30,7 @@ /* | ||
export function asApplyRefactoring(action, info, args) { | ||
return lsp.CodeAction.create(action.description, lsp.Command.create(action.description, Commands.APPLY_REFACTORING, Object.assign(Object.assign({}, args), { refactor: info.name, action: action.name })), asKind(info)); | ||
return lsp.CodeAction.create(action.description, lsp.Command.create(action.description, Commands.APPLY_REFACTORING, { | ||
...args, | ||
refactor: info.name, | ||
action: action.name, | ||
}), asKind(info)); | ||
} | ||
@@ -33,0 +37,0 @@ function asKind(refactor) { |
import * as lsp from 'vscode-languageserver'; | ||
import { LspServer } from './lsp-server.js'; | ||
export declare const PACKAGE_ROOT: string; | ||
export declare function getDefaultClientCapabilities(): lsp.ClientCapabilities; | ||
export declare function uri(...components: string[]): string; | ||
@@ -16,3 +15,3 @@ export declare function filePath(...components: string[]): string; | ||
} | ||
export declare function createServer(options: { | ||
interface TestLspServerOptions { | ||
rootUri: string | null; | ||
@@ -22,3 +21,5 @@ tsserverLogVerbosity?: string; | ||
clientCapabilitiesOverride?: lsp.ClientCapabilities; | ||
}): Promise<TestLspServer>; | ||
} | ||
export declare function createServer(options: TestLspServerOptions): Promise<TestLspServer>; | ||
export {}; | ||
//# sourceMappingURL=test-utils.d.ts.map |
@@ -7,11 +7,2 @@ /* | ||
*/ | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
import { platform } from 'node:os'; | ||
@@ -21,2 +12,3 @@ import * as path from 'node:path'; | ||
import { fileURLToPath } from 'node:url'; | ||
import deepmerge from 'deepmerge'; | ||
import * as lsp from 'vscode-languageserver'; | ||
@@ -30,25 +22,42 @@ import { normalizePath, pathToUri } from './protocol-translation.js'; | ||
export const PACKAGE_ROOT = fileURLToPath(new URL('..', import.meta.url)); | ||
export function getDefaultClientCapabilities() { | ||
return { | ||
textDocument: { | ||
completion: { | ||
completionItem: { | ||
labelDetailsSupport: true | ||
} | ||
const DEFAULT_TEST_CLIENT_CAPABILITIES = { | ||
textDocument: { | ||
completion: { | ||
completionItem: { | ||
snippetSupport: true, | ||
labelDetailsSupport: true, | ||
}, | ||
documentSymbol: { | ||
hierarchicalDocumentSymbolSupport: true | ||
}, | ||
documentSymbol: { | ||
hierarchicalDocumentSymbolSupport: true, | ||
}, | ||
publishDiagnostics: { | ||
tagSupport: { | ||
valueSet: [ | ||
lsp.DiagnosticTag.Unnecessary, | ||
lsp.DiagnosticTag.Deprecated, | ||
], | ||
}, | ||
publishDiagnostics: { | ||
tagSupport: { | ||
valueSet: [ | ||
lsp.DiagnosticTag.Unnecessary, | ||
lsp.DiagnosticTag.Deprecated | ||
] | ||
} | ||
}, | ||
moniker: {} | ||
} | ||
}; | ||
} | ||
}, | ||
moniker: {}, | ||
}, | ||
}; | ||
const DEFAULT_TEST_CLIENT_INITIALIZATION_OPTIONS = { | ||
plugins: [], | ||
preferences: { | ||
allowIncompleteCompletions: true, | ||
allowRenameOfImportPath: true, | ||
allowTextChangesInNewFiles: true, | ||
displayPartsForJSDoc: true, | ||
generateReturnInDocTemplate: true, | ||
includeAutomaticOptionalChainCompletions: true, | ||
includeCompletionsForImportStatements: true, | ||
includeCompletionsForModuleExports: true, | ||
includeCompletionsWithClassMemberSnippets: true, | ||
includeCompletionsWithInsertText: true, | ||
includeCompletionsWithSnippetText: true, | ||
jsxAttributeCompletionStyle: 'auto', | ||
providePrefixAndSuffixTextForRename: true, | ||
}, | ||
}; | ||
export function uri(...components) { | ||
@@ -69,3 +78,3 @@ const resolved = filePath(...components); | ||
line: pos.line, | ||
character: pos.character | ||
character: pos.character, | ||
}; | ||
@@ -88,2 +97,42 @@ } | ||
} | ||
class TestLspClient { | ||
constructor(options, logger) { | ||
this.options = options; | ||
this.logger = logger; | ||
this.workspaceEditsListener = null; | ||
} | ||
async createProgressReporter(_token, _workDoneProgress) { | ||
const reporter = new class { | ||
begin(_title, _percentage, _message, _cancellable) { } | ||
report(_message) { } | ||
done() { } | ||
}; | ||
return reporter; | ||
} | ||
async withProgress(_options, task) { | ||
const progress = await this.createProgressReporter(); | ||
return await task(progress); | ||
} | ||
publishDiagnostics(args) { | ||
return this.options.publishDiagnostics(args); | ||
} | ||
showErrorMessage(message) { | ||
this.logger.error(`[showErrorMessage] ${message}`); | ||
} | ||
logMessage(args) { | ||
this.logger.log('logMessage', JSON.stringify(args)); | ||
} | ||
addApplyWorkspaceEditListener(listener) { | ||
this.workspaceEditsListener = listener; | ||
} | ||
async applyWorkspaceEdit(args) { | ||
if (this.workspaceEditsListener) { | ||
this.workspaceEditsListener(args); | ||
} | ||
return { applied: true }; | ||
} | ||
async rename() { | ||
throw new Error('unsupported'); | ||
} | ||
} | ||
export class TestLspServer extends LspServer { | ||
@@ -95,50 +144,27 @@ constructor() { | ||
} | ||
export function createServer(options) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const typescriptVersionProvider = new TypeScriptVersionProvider(); | ||
const bundled = typescriptVersionProvider.bundledVersion(); | ||
const logger = new ConsoleLogger(CONSOLE_LOG_LEVEL); | ||
const server = new TestLspServer({ | ||
logger, | ||
tsserverPath: bundled.tsServerPath, | ||
tsserverLogVerbosity: options.tsserverLogVerbosity, | ||
tsserverLogFile: path.resolve(PACKAGE_ROOT, 'tsserver.log'), | ||
lspClient: { | ||
setClientCapabilites() { }, | ||
createProgressReporter() { | ||
return { | ||
begin() { }, | ||
report() { }, | ||
end() { } | ||
}; | ||
}, | ||
publishDiagnostics: options.publishDiagnostics, | ||
showMessage(args) { | ||
throw args; // should not be called. | ||
}, | ||
logMessage(args) { | ||
logger.log('logMessage', JSON.stringify(args)); | ||
}, | ||
telemetry(args) { | ||
logger.log('telemetry', JSON.stringify(args)); | ||
}, | ||
applyWorkspaceEdit(args) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
server.workspaceEdits.push(args); | ||
return { applied: true }; | ||
}); | ||
}, | ||
rename: () => Promise.reject(new Error('unsupported')) | ||
} | ||
}); | ||
yield server.initialize({ | ||
rootPath: undefined, | ||
rootUri: options.rootUri, | ||
processId: 42, | ||
capabilities: options.clientCapabilitiesOverride || getDefaultClientCapabilities(), | ||
workspaceFolders: null | ||
}); | ||
return server; | ||
export async function createServer(options) { | ||
const typescriptVersionProvider = new TypeScriptVersionProvider(); | ||
const bundled = typescriptVersionProvider.bundledVersion(); | ||
const logger = new ConsoleLogger(CONSOLE_LOG_LEVEL); | ||
const lspClient = new TestLspClient(options, logger); | ||
const server = new TestLspServer({ | ||
logger, | ||
tsserverPath: bundled.tsServerPath, | ||
tsserverLogVerbosity: options.tsserverLogVerbosity, | ||
tsserverLogFile: path.resolve(PACKAGE_ROOT, 'tsserver.log'), | ||
lspClient, | ||
}); | ||
lspClient.addApplyWorkspaceEditListener(args => { | ||
server.workspaceEdits.push(args); | ||
}); | ||
await server.initialize({ | ||
rootPath: undefined, | ||
rootUri: options.rootUri, | ||
processId: 42, | ||
capabilities: deepmerge(DEFAULT_TEST_CLIENT_CAPABILITIES, options.clientCapabilitiesOverride || {}), | ||
initializationOptions: DEFAULT_TEST_CLIENT_INITIALIZATION_OPTIONS, | ||
workspaceFolders: null, | ||
}); | ||
return server; | ||
} | ||
//# sourceMappingURL=test-utils.js.map |
@@ -6,3 +6,2 @@ /** | ||
import type tsp from 'typescript/lib/protocol.d.js'; | ||
import { InlayHintsOptions } from './lsp-protocol.inlayHints.proposed.js'; | ||
export declare namespace TypeScriptRenameRequest { | ||
@@ -20,3 +19,6 @@ const type: lsp.RequestType<lsp.TextDocumentPositionParams, void, void>; | ||
export interface SupportedFeatures { | ||
labelDetails?: boolean; | ||
completionLabelDetails?: boolean; | ||
completionSnippets?: boolean; | ||
diagnosticsTagSupport?: boolean; | ||
definitionLinkSupport?: boolean; | ||
} | ||
@@ -43,19 +45,2 @@ export interface TypeScriptPlugin { | ||
} | ||
export interface TypeScriptWorkspaceSettingsLanguageSettings { | ||
format?: tsp.FormatCodeSettings; | ||
inlayHints?: InlayHintsOptions; | ||
} | ||
interface TypeScriptWorkspaceSettingsDiagnostics { | ||
ignoredCodes?: number[]; | ||
} | ||
export interface TypeScriptWorkspaceSettings { | ||
javascript?: TypeScriptWorkspaceSettingsLanguageSettings; | ||
typescript?: TypeScriptWorkspaceSettingsLanguageSettings; | ||
completions?: CompletionOptions; | ||
diagnostics?: TypeScriptWorkspaceSettingsDiagnostics; | ||
} | ||
export interface CompletionOptions { | ||
completeFunctionCalls?: boolean; | ||
} | ||
export {}; | ||
//# sourceMappingURL=ts-protocol.d.ts.map |
@@ -6,3 +6,5 @@ /// <reference types="node" resolution-mode="require"/> | ||
import { Logger } from './logger.js'; | ||
import API from './utils/api.js'; | ||
export interface TspClientOptions { | ||
apiVersion: API; | ||
logger: Logger; | ||
@@ -22,37 +24,39 @@ tsserverPath: string; | ||
interface TypeScriptRequestTypes { | ||
'geterr': [tsp.GeterrRequestArgs, any]; | ||
'compilerOptionsForInferredProjects': [tsp.SetCompilerOptionsForInferredProjectsArgs, tsp.SetCompilerOptionsForInferredProjectsResponse]; | ||
'documentHighlights': [tsp.DocumentHighlightsRequestArgs, tsp.DocumentHighlightsResponse]; | ||
'applyCodeActionCommand': [tsp.ApplyCodeActionCommandRequestArgs, tsp.ApplyCodeActionCommandResponse]; | ||
'completionEntryDetails': [tsp.CompletionDetailsRequestArgs, tsp.CompletionDetailsResponse]; | ||
'completionInfo': [tsp.CompletionsRequestArgs, tsp.CompletionInfoResponse]; | ||
'configure': [tsp.ConfigureRequestArguments, tsp.ConfigureResponse]; | ||
'definition': [tsp.FileLocationRequestArgs, tsp.DefinitionResponse]; | ||
'definitionAndBoundSpan': [tsp.FileLocationRequestArgs, tsp.DefinitionInfoAndBoundSpanResponse]; | ||
'docCommentTemplate': [tsp.FileLocationRequestArgs, tsp.DocCommandTemplateResponse]; | ||
'format': [tsp.FormatRequestArgs, tsp.FormatResponse]; | ||
'formatonkey': [tsp.FormatOnKeyRequestArgs, tsp.FormatResponse]; | ||
'getApplicableRefactors': [tsp.GetApplicableRefactorsRequestArgs, tsp.GetApplicableRefactorsResponse]; | ||
'getCodeFixes': [tsp.CodeFixRequestArgs, tsp.CodeFixResponse]; | ||
'getCombinedCodeFix': [tsp.GetCombinedCodeFixRequestArgs, tsp.GetCombinedCodeFixResponse]; | ||
'getEditsForFileRename': [tsp.GetEditsForFileRenameRequestArgs, tsp.GetEditsForFileRenameResponse]; | ||
'getEditsForRefactor': [tsp.GetEditsForRefactorRequestArgs, tsp.GetEditsForRefactorResponse]; | ||
'getOutliningSpans': [tsp.FileRequestArgs, tsp.OutliningSpansResponse]; | ||
'getSupportedCodeFixes': [null, tsp.GetSupportedCodeFixesResponse]; | ||
'implementation': [tsp.FileLocationRequestArgs, tsp.ImplementationResponse]; | ||
'jsxClosingTag': [tsp.JsxClosingTagRequestArgs, tsp.JsxClosingTagResponse]; | ||
'navto': [tsp.NavtoRequestArgs, tsp.NavtoResponse]; | ||
'navtree': [tsp.FileRequestArgs, tsp.NavTreeResponse]; | ||
'organizeImports': [tsp.OrganizeImportsRequestArgs, tsp.OrganizeImportsResponse]; | ||
'projectInfo': [tsp.ProjectInfoRequestArgs, tsp.ProjectInfoResponse]; | ||
'quickinfo': [tsp.FileLocationRequestArgs, tsp.QuickInfoResponse]; | ||
'references': [tsp.FileLocationRequestArgs, tsp.ReferencesResponse]; | ||
'rename': [tsp.RenameRequestArgs, tsp.RenameResponse]; | ||
'signatureHelp': [tsp.SignatureHelpRequestArgs, tsp.SignatureHelpResponse]; | ||
'typeDefinition': [tsp.FileLocationRequestArgs, tsp.TypeDefinitionResponse]; | ||
'provideInlayHints': [tsp.InlayHintsRequestArgs, tsp.InlayHintsResponse]; | ||
'encodedSemanticClassifications-full': [tsp.EncodedSemanticClassificationsRequestArgs, tsp.EncodedSemanticClassificationsResponse]; | ||
[CommandTypes.ApplyCodeActionCommand]: [tsp.ApplyCodeActionCommandRequestArgs, tsp.ApplyCodeActionCommandResponse]; | ||
[CommandTypes.CompilerOptionsForInferredProjects]: [tsp.SetCompilerOptionsForInferredProjectsArgs, tsp.SetCompilerOptionsForInferredProjectsResponse]; | ||
[CommandTypes.CompletionDetails]: [tsp.CompletionDetailsRequestArgs, tsp.CompletionDetailsResponse]; | ||
[CommandTypes.CompletionInfo]: [tsp.CompletionsRequestArgs, tsp.CompletionInfoResponse]; | ||
[CommandTypes.Configure]: [tsp.ConfigureRequestArguments, tsp.ConfigureResponse]; | ||
[CommandTypes.Definition]: [tsp.FileLocationRequestArgs, tsp.DefinitionResponse]; | ||
[CommandTypes.DefinitionAndBoundSpan]: [tsp.FileLocationRequestArgs, tsp.DefinitionInfoAndBoundSpanResponse]; | ||
[CommandTypes.DocCommentTemplate]: [tsp.FileLocationRequestArgs, tsp.DocCommandTemplateResponse]; | ||
[CommandTypes.DocumentHighlights]: [tsp.DocumentHighlightsRequestArgs, tsp.DocumentHighlightsResponse]; | ||
[CommandTypes.EncodedSemanticClassificationsFull]: [tsp.EncodedSemanticClassificationsRequestArgs, tsp.EncodedSemanticClassificationsResponse]; | ||
[CommandTypes.FindSourceDefinition]: [tsp.FileLocationRequestArgs, tsp.DefinitionResponse]; | ||
[CommandTypes.Format]: [tsp.FormatRequestArgs, tsp.FormatResponse]; | ||
[CommandTypes.Formatonkey]: [tsp.FormatOnKeyRequestArgs, tsp.FormatResponse]; | ||
[CommandTypes.GetApplicableRefactors]: [tsp.GetApplicableRefactorsRequestArgs, tsp.GetApplicableRefactorsResponse]; | ||
[CommandTypes.GetCodeFixes]: [tsp.CodeFixRequestArgs, tsp.CodeFixResponse]; | ||
[CommandTypes.GetCombinedCodeFix]: [tsp.GetCombinedCodeFixRequestArgs, tsp.GetCombinedCodeFixResponse]; | ||
[CommandTypes.GetEditsForFileRename]: [tsp.GetEditsForFileRenameRequestArgs, tsp.GetEditsForFileRenameResponse]; | ||
[CommandTypes.GetEditsForRefactor]: [tsp.GetEditsForRefactorRequestArgs, tsp.GetEditsForRefactorResponse]; | ||
[CommandTypes.Geterr]: [tsp.GeterrRequestArgs, any]; | ||
[CommandTypes.GetOutliningSpans]: [tsp.FileRequestArgs, tsp.OutliningSpansResponse]; | ||
[CommandTypes.GetSupportedCodeFixes]: [null, tsp.GetSupportedCodeFixesResponse]; | ||
[CommandTypes.Implementation]: [tsp.FileLocationRequestArgs, tsp.ImplementationResponse]; | ||
[CommandTypes.JsxClosingTag]: [tsp.JsxClosingTagRequestArgs, tsp.JsxClosingTagResponse]; | ||
[CommandTypes.Navto]: [tsp.NavtoRequestArgs, tsp.NavtoResponse]; | ||
[CommandTypes.NavTree]: [tsp.FileRequestArgs, tsp.NavTreeResponse]; | ||
[CommandTypes.OrganizeImports]: [tsp.OrganizeImportsRequestArgs, tsp.OrganizeImportsResponse]; | ||
[CommandTypes.ProjectInfo]: [tsp.ProjectInfoRequestArgs, tsp.ProjectInfoResponse]; | ||
[CommandTypes.ProvideInlayHints]: [tsp.InlayHintsRequestArgs, tsp.InlayHintsResponse]; | ||
[CommandTypes.Quickinfo]: [tsp.FileLocationRequestArgs, tsp.QuickInfoResponse]; | ||
[CommandTypes.References]: [tsp.FileLocationRequestArgs, tsp.ReferencesResponse]; | ||
[CommandTypes.Rename]: [tsp.RenameRequestArgs, tsp.RenameResponse]; | ||
[CommandTypes.SignatureHelp]: [tsp.SignatureHelpRequestArgs, tsp.SignatureHelpResponse]; | ||
[CommandTypes.TypeDefinition]: [tsp.FileLocationRequestArgs, tsp.TypeDefinitionResponse]; | ||
} | ||
export declare class TspClient { | ||
private options; | ||
apiVersion: API; | ||
private tsserverProc; | ||
@@ -59,0 +63,0 @@ private readlineInterface; |
@@ -17,4 +17,7 @@ /* | ||
this.options = options; | ||
this.tsserverProc = null; | ||
this.readlineInterface = null; | ||
this.seq = 0; | ||
this.deferreds = {}; | ||
this.apiVersion = options.apiVersion; | ||
this.logger = new PrefixingLogger(options.logger, '[tsclient]'); | ||
@@ -27,3 +30,3 @@ this.tsserverLogger = new PrefixingLogger(options.logger, '[tsserver]'); | ||
} | ||
const { tsserverPath, logFile, logVerbosity, disableAutomaticTypingAcquisition, maxTsServerMemory, npmLocation, locale, globalPlugins, pluginProbeLocations } = this.options; | ||
const { tsserverPath, logFile, logVerbosity, disableAutomaticTypingAcquisition, maxTsServerMemory, npmLocation, locale, globalPlugins, pluginProbeLocations, } = this.options; | ||
const args = []; | ||
@@ -58,4 +61,4 @@ if (logFile) { | ||
execArgv: [ | ||
...maxTsServerMemory ? [`--max-old-space-size=${maxTsServerMemory}`] : [] | ||
] | ||
...maxTsServerMemory ? [`--max-old-space-size=${maxTsServerMemory}`] : [], | ||
], | ||
}; | ||
@@ -84,6 +87,5 @@ this.tsserverProc = cp.fork(tsserverPath, args, options); | ||
shutdown() { | ||
var _a, _b; | ||
(_a = this.readlineInterface) === null || _a === void 0 ? void 0 : _a.close(); | ||
this.readlineInterface?.close(); | ||
if (this.tsserverProc) { | ||
(_b = this.tsserverProc.stdin) === null || _b === void 0 ? void 0 : _b.destroy(); | ||
this.tsserverProc.stdin?.destroy(); | ||
this.tsserverProc.kill('SIGTERM'); | ||
@@ -121,3 +123,3 @@ } | ||
seq: this.seq, | ||
type: 'request' | ||
type: 'request', | ||
}; | ||
@@ -124,0 +126,0 @@ if (args) { |
@@ -7,11 +7,2 @@ /* | ||
*/ | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
import * as chai from 'chai'; | ||
@@ -21,2 +12,3 @@ import { TspClient } from './tsp-client.js'; | ||
import { filePath, readContents } from './test-utils.js'; | ||
import API from './utils/api.js'; | ||
import { TypeScriptVersionProvider } from './utils/versionProvider.js'; | ||
@@ -29,4 +21,5 @@ const assert = chai.assert; | ||
server = new TspClient({ | ||
apiVersion: API.defaultVersion, | ||
logger: new ConsoleLogger(), | ||
tsserverPath: bundled.tsServerPath | ||
tsserverPath: bundled.tsServerPath, | ||
}); | ||
@@ -41,61 +34,61 @@ }); | ||
}); | ||
it('completion', () => __awaiter(void 0, void 0, void 0, function* () { | ||
it('completion', async () => { | ||
const f = filePath('module2.ts'); | ||
server.notify("open" /* CommandTypes.Open */, { | ||
file: f, | ||
fileContent: readContents(f) | ||
fileContent: readContents(f), | ||
}); | ||
const completions = yield server.request("completionInfo" /* CommandTypes.CompletionInfo */, { | ||
const completions = await server.request("completionInfo" /* CommandTypes.CompletionInfo */, { | ||
file: f, | ||
line: 1, | ||
offset: 0, | ||
prefix: 'im' | ||
prefix: 'im', | ||
}); | ||
assert.isDefined(completions.body); | ||
assert.equal(completions.body.entries[1].name, 'ImageBitmap'); | ||
})); | ||
it('references', () => __awaiter(void 0, void 0, void 0, function* () { | ||
}); | ||
it('references', async () => { | ||
const f = filePath('module2.ts'); | ||
server.notify("open" /* CommandTypes.Open */, { | ||
file: f, | ||
fileContent: readContents(f) | ||
fileContent: readContents(f), | ||
}); | ||
const references = yield server.request("references" /* CommandTypes.References */, { | ||
const references = await server.request("references" /* CommandTypes.References */, { | ||
file: f, | ||
line: 8, | ||
offset: 16 | ||
offset: 16, | ||
}); | ||
assert.isDefined(references.body); | ||
assert.equal(references.body.symbolName, 'doStuff'); | ||
})); | ||
it('inlayHints', () => __awaiter(void 0, void 0, void 0, function* () { | ||
}); | ||
it('inlayHints', async () => { | ||
const f = filePath('module2.ts'); | ||
server.notify("open" /* CommandTypes.Open */, { | ||
file: f, | ||
fileContent: readContents(f) | ||
fileContent: readContents(f), | ||
}); | ||
yield server.request("configure" /* CommandTypes.Configure */, { | ||
await server.request("configure" /* CommandTypes.Configure */, { | ||
preferences: { | ||
includeInlayFunctionLikeReturnTypeHints: true | ||
} | ||
includeInlayFunctionLikeReturnTypeHints: true, | ||
}, | ||
}); | ||
const inlayHints = yield server.request("provideInlayHints" /* CommandTypes.ProvideInlayHints */, { | ||
const inlayHints = await server.request("provideInlayHints" /* CommandTypes.ProvideInlayHints */, { | ||
file: f, | ||
start: 0, | ||
length: 1000 | ||
length: 1000, | ||
}); | ||
assert.isDefined(inlayHints.body); | ||
assert.equal(inlayHints.body[0].text, ': boolean'); | ||
})); | ||
it('documentHighlight', () => __awaiter(void 0, void 0, void 0, function* () { | ||
}); | ||
it('documentHighlight', async () => { | ||
const f = filePath('module2.ts'); | ||
server.notify("open" /* CommandTypes.Open */, { | ||
file: f, | ||
fileContent: readContents(f) | ||
fileContent: readContents(f), | ||
}); | ||
const response = yield server.request("documentHighlights" /* CommandTypes.DocumentHighlights */, { | ||
const response = await server.request("documentHighlights" /* CommandTypes.DocumentHighlights */, { | ||
file: f, | ||
line: 8, | ||
offset: 16, | ||
filesToSearch: [f] | ||
filesToSearch: [f], | ||
}); | ||
@@ -105,4 +98,4 @@ assert.isDefined(response.body); | ||
assert.isFalse(response.body.some(({ file: file_1 }) => file_1.endsWith('module1.ts')), JSON.stringify(response.body, undefined, 2)); | ||
})); | ||
}); | ||
}); | ||
//# sourceMappingURL=tsp-client.spec.js.map |
@@ -35,2 +35,3 @@ /** | ||
FileReferences = "fileReferences", | ||
FindSourceDefinition = "findSourceDefinition", | ||
Format = "format", | ||
@@ -37,0 +38,0 @@ Formatonkey = "formatonkey", |
@@ -97,4 +97,4 @@ /** | ||
KindModifiers.jsxFile, | ||
KindModifiers.jsonFile | ||
KindModifiers.jsonFile, | ||
]; | ||
//# sourceMappingURL=tsp-command-types.js.map |
@@ -9,2 +9,4 @@ /* | ||
constructor() { | ||
this.resolve = (_) => { }; | ||
this.reject = (_) => { }; | ||
this.promise = new Promise((resolve, reject) => { | ||
@@ -11,0 +13,0 @@ this.resolve = resolve; |
/** | ||
* Helpers for converting FROM LanguageServer types language-server ts types | ||
*/ | ||
import type * as lsp from 'vscode-languageserver-protocol'; | ||
import * as lsp from 'vscode-languageserver-protocol'; | ||
import type tsp from 'typescript/lib/protocol.d.js'; | ||
export declare namespace Range { | ||
const fromTextSpan: (span: tsp.TextSpan) => lsp.Range; | ||
const toTextSpan: (range: lsp.Range) => tsp.TextSpan; | ||
const fromLocations: (start: tsp.Location, end: tsp.Location) => lsp.Range; | ||
const toFileRangeRequestArgs: (file: string, range: lsp.Range) => tsp.FileRangeRequestArgs; | ||
const toFormattingRequestArgs: (file: string, range: lsp.Range) => tsp.FormatRequestArgs; | ||
function intersection(one: lsp.Range, other: lsp.Range): lsp.Range | undefined; | ||
} | ||
export declare namespace Position { | ||
@@ -10,3 +18,13 @@ const fromLocation: (tslocation: tsp.Location) => lsp.Position; | ||
const toFileLocationRequestArgs: (file: string, position: lsp.Position) => tsp.FileLocationRequestArgs; | ||
function Min(): undefined; | ||
function Min(...positions: lsp.Position[]): lsp.Position; | ||
function isBefore(one: lsp.Position, other: lsp.Position): boolean; | ||
function Max(): undefined; | ||
function Max(...positions: lsp.Position[]): lsp.Position; | ||
function isAfter(one: lsp.Position, other: lsp.Position): boolean; | ||
function isBeforeOrEqual(one: lsp.Position, other: lsp.Position): boolean; | ||
} | ||
export declare namespace Location { | ||
const fromTextSpan: (resource: lsp.DocumentUri, tsTextSpan: tsp.TextSpan) => lsp.Location; | ||
} | ||
//# sourceMappingURL=typeConverters.d.ts.map |
@@ -0,7 +1,52 @@ | ||
/*--------------------------------------------------------------------------------------------- | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. See License.txt in the project root for license information. | ||
*--------------------------------------------------------------------------------------------*/ | ||
/** | ||
* Helpers for converting FROM LanguageServer types language-server ts types | ||
*/ | ||
import * as lsp from 'vscode-languageserver-protocol'; | ||
export var Range; | ||
(function (Range) { | ||
Range.fromTextSpan = (span) => Range.fromLocations(span.start, span.end); | ||
Range.toTextSpan = (range) => ({ | ||
start: Position.toLocation(range.start), | ||
end: Position.toLocation(range.end), | ||
}); | ||
Range.fromLocations = (start, end) => lsp.Range.create(Math.max(0, start.line - 1), Math.max(start.offset - 1, 0), Math.max(0, end.line - 1), Math.max(0, end.offset - 1)); | ||
Range.toFileRangeRequestArgs = (file, range) => ({ | ||
file, | ||
startLine: range.start.line + 1, | ||
startOffset: range.start.character + 1, | ||
endLine: range.end.line + 1, | ||
endOffset: range.end.character + 1, | ||
}); | ||
Range.toFormattingRequestArgs = (file, range) => ({ | ||
file, | ||
line: range.start.line + 1, | ||
offset: range.start.character + 1, | ||
endLine: range.end.line + 1, | ||
endOffset: range.end.character + 1, | ||
}); | ||
function intersection(one, other) { | ||
const start = Position.Max(other.start, one.start); | ||
const end = Position.Min(other.end, one.end); | ||
if (Position.isAfter(start, end)) { | ||
// this happens when there is no overlap: | ||
// |-----| | ||
// |----| | ||
return undefined; | ||
} | ||
return lsp.Range.create(start, end); | ||
} | ||
Range.intersection = intersection; | ||
})(Range = Range || (Range = {})); | ||
export var Position; | ||
(function (Position) { | ||
Position.fromLocation = (tslocation) => { | ||
// Clamping on the low side to 0 since Typescript returns 0, 0 when creating new file | ||
// even though position is supposed to be 1-based. | ||
return { | ||
line: tslocation.line - 1, | ||
character: tslocation.offset - 1 | ||
line: Math.max(tslocation.line - 1, 0), | ||
character: Math.max(tslocation.offset - 1, 0), | ||
}; | ||
@@ -11,3 +56,3 @@ }; | ||
line: position.line + 1, | ||
offset: position.character + 1 | ||
offset: position.character + 1, | ||
}); | ||
@@ -17,5 +62,59 @@ Position.toFileLocationRequestArgs = (file, position) => ({ | ||
line: position.line + 1, | ||
offset: position.character + 1 | ||
offset: position.character + 1, | ||
}); | ||
function Min(...positions) { | ||
if (!positions.length) { | ||
return undefined; | ||
} | ||
let result = positions.pop(); | ||
for (const p of positions) { | ||
if (isBefore(p, result)) { | ||
result = p; | ||
} | ||
} | ||
return result; | ||
} | ||
Position.Min = Min; | ||
function isBefore(one, other) { | ||
if (one.line < other.line) { | ||
return true; | ||
} | ||
if (other.line < one.line) { | ||
return false; | ||
} | ||
return one.character < other.character; | ||
} | ||
Position.isBefore = isBefore; | ||
function Max(...positions) { | ||
if (!positions.length) { | ||
return undefined; | ||
} | ||
let result = positions.pop(); | ||
for (const p of positions) { | ||
if (isAfter(p, result)) { | ||
result = p; | ||
} | ||
} | ||
return result; | ||
} | ||
Position.Max = Max; | ||
function isAfter(one, other) { | ||
return !isBeforeOrEqual(one, other); | ||
} | ||
Position.isAfter = isAfter; | ||
function isBeforeOrEqual(one, other) { | ||
if (one.line < other.line) { | ||
return true; | ||
} | ||
if (other.line < one.line) { | ||
return false; | ||
} | ||
return one.character <= other.character; | ||
} | ||
Position.isBeforeOrEqual = isBeforeOrEqual; | ||
})(Position = Position || (Position = {})); | ||
export var Location; | ||
(function (Location) { | ||
Location.fromTextSpan = (resource, tsTextSpan) => lsp.Location.create(resource, Range.fromTextSpan(tsTextSpan)); | ||
})(Location = Location || (Location = {})); | ||
//# sourceMappingURL=typeConverters.js.map |
@@ -44,6 +44,5 @@ /*--------------------------------------------------------------------------------------------- | ||
getTypeScriptVersion(serverPath) { | ||
var _a, _b, _c, _d, _e, _f, _g, _h; | ||
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.info(`Resolving TypeScript version from path "${serverPath}"...`); | ||
this.logger?.info(`Resolving TypeScript version from path "${serverPath}"...`); | ||
if (!fs.existsSync(serverPath)) { | ||
(_b = this.logger) === null || _b === void 0 ? void 0 : _b.info('Server path does not exist on disk'); | ||
this.logger?.info('Server path does not exist on disk'); | ||
return null; | ||
@@ -53,3 +52,3 @@ } | ||
if (p.length <= 2) { | ||
(_c = this.logger) === null || _c === void 0 ? void 0 : _c.info('Server path is invalid (has less than two path components).'); | ||
this.logger?.info('Server path is invalid (has less than two path components).'); | ||
return null; | ||
@@ -67,6 +66,6 @@ } | ||
if (!fs.existsSync(fileName)) { | ||
(_d = this.logger) === null || _d === void 0 ? void 0 : _d.info(`Failed to find package.json at path "${fileName}"`); | ||
this.logger?.info(`Failed to find package.json at path "${fileName}"`); | ||
return null; | ||
} | ||
(_e = this.logger) === null || _e === void 0 ? void 0 : _e.info(`Reading version from package.json at "${fileName}"`); | ||
this.logger?.info(`Reading version from package.json at "${fileName}"`); | ||
const contents = fs.readFileSync(fileName).toString(); | ||
@@ -78,10 +77,10 @@ let desc = null; | ||
catch (err) { | ||
(_f = this.logger) === null || _f === void 0 ? void 0 : _f.info('Failed parsing contents of package.json.'); | ||
this.logger?.info('Failed parsing contents of package.json.'); | ||
return null; | ||
} | ||
if (!desc || !desc.version) { | ||
(_g = this.logger) === null || _g === void 0 ? void 0 : _g.info('Failed reading version number from package.json.'); | ||
this.logger?.info('Failed reading version number from package.json.'); | ||
return null; | ||
} | ||
(_h = this.logger) === null || _h === void 0 ? void 0 : _h.info(`Resolved TypeScript version to "${desc.version}"`); | ||
this.logger?.info(`Resolved TypeScript version to "${desc.version}"`); | ||
return API.fromVersionString(desc.version); | ||
@@ -97,3 +96,2 @@ } | ||
getUserSettingVersion() { | ||
var _a, _b, _c, _d, _e, _f; | ||
const { tsserverPath } = this.configuration || {}; | ||
@@ -103,3 +101,3 @@ if (!tsserverPath) { | ||
} | ||
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.info(`Resolving user-provided tsserver path "${tsserverPath}"...`); | ||
this.logger?.info(`Resolving user-provided tsserver path "${tsserverPath}"...`); | ||
let resolvedPath = tsserverPath; | ||
@@ -112,15 +110,15 @@ // Resolve full path to the binary if path is not absolute. | ||
} | ||
(_b = this.logger) === null || _b === void 0 ? void 0 : _b.info(`Non-absolute tsserver path resolved to "${binaryPath ? resolvedPath : '<failed>'}"`); | ||
this.logger?.info(`Non-absolute tsserver path resolved to "${binaryPath ? resolvedPath : '<failed>'}"`); | ||
} | ||
// Resolve symbolic link. | ||
let stat = fs.lstatSync(resolvedPath, { throwIfNoEntry: false }); | ||
if (stat === null || stat === void 0 ? void 0 : stat.isSymbolicLink()) { | ||
if (stat?.isSymbolicLink()) { | ||
resolvedPath = fs.realpathSync(resolvedPath); | ||
(_c = this.logger) === null || _c === void 0 ? void 0 : _c.info(`Symbolic link tsserver path resolved to "${resolvedPath}"`); | ||
this.logger?.info(`Symbolic link tsserver path resolved to "${resolvedPath}"`); | ||
} | ||
// Get directory path | ||
stat = fs.lstatSync(resolvedPath, { throwIfNoEntry: false }); | ||
if (stat === null || stat === void 0 ? void 0 : stat.isFile()) { | ||
if (stat?.isFile()) { | ||
resolvedPath = path.dirname(resolvedPath); | ||
(_d = this.logger) === null || _d === void 0 ? void 0 : _d.info(`Resolved directory path from a file path: ${resolvedPath}`); | ||
this.logger?.info(`Resolved directory path from a file path: ${resolvedPath}`); | ||
} | ||
@@ -130,9 +128,9 @@ // Resolve path to the "lib" dir. | ||
const packageJsonPath = pkgUpSync({ cwd: resolvedPath }); | ||
(_e = this.logger) === null || _e === void 0 ? void 0 : _e.info(`Resolved package.json location: "${packageJsonPath}"`); | ||
this.logger?.info(`Resolved package.json location: "${packageJsonPath}"`); | ||
if (packageJsonPath) { | ||
resolvedPath = path.join(path.dirname(packageJsonPath), 'lib'); | ||
(_f = this.logger) === null || _f === void 0 ? void 0 : _f.info(`Assumed tsserver lib location: "${resolvedPath}"`); | ||
this.logger?.info(`Assumed tsserver lib location: "${resolvedPath}"`); | ||
} | ||
} | ||
catch (_g) { | ||
catch { | ||
// ignore | ||
@@ -139,0 +137,0 @@ } |
{ | ||
"name": "typescript-language-server", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"description": "Language Server Protocol (LSP) implementation for TypeScript using tsserver", | ||
@@ -27,2 +27,3 @@ "author": "TypeFox and others", | ||
"lint": "eslint --ext \".js,.ts\" src", | ||
"fix": "eslint --ext \".js,.ts\" --fix src", | ||
"build": "concurrently -n compile,lint -c blue,green \"yarn compile\" \"yarn lint\"", | ||
@@ -57,14 +58,16 @@ "compile": "tsc -b", | ||
"devDependencies": { | ||
"@types/chai": "^4.3.1", | ||
"@types/chai": "^4.3.3", | ||
"@types/deepmerge": "^2.2.0", | ||
"@types/fs-extra": "^9.0.13", | ||
"@types/mocha": "^9.1.1", | ||
"@types/node": "^16.11.47", | ||
"@types/semver": "^7.3.10", | ||
"@types/node": "^16.11.52", | ||
"@types/semver": "^7.3.12", | ||
"@types/which": "^2.0.1", | ||
"@typescript-eslint/eslint-plugin": "^5.32.0", | ||
"@typescript-eslint/parser": "^5.32.0", | ||
"@typescript-eslint/eslint-plugin": "^5.33.1", | ||
"@typescript-eslint/parser": "^5.33.1", | ||
"chai": "^4.3.6", | ||
"concurrently": "^7.3.0", | ||
"cross-env": "^7.0.3", | ||
"eslint": "^8.21.0", | ||
"deepmerge": "^4.2.2", | ||
"eslint": "^8.22.0", | ||
"husky": "4.x", | ||
@@ -71,0 +74,0 @@ "mocha": "^10.0.0", |
149
README.md
@@ -14,2 +14,29 @@ [![Build Status](https://travis-ci.org/theia-ide/typescript-language-server.svg?branch=master)](https://travis-ci.org/theia-ide/typescript-language-server) | ||
<!-- MarkdownTOC --> | ||
- [Installing](#installing) | ||
- [Running the language server](#running-the-language-server) | ||
- [CLI Options](#cli-options) | ||
- [initializationOptions](#initializationoptions) | ||
- [workspace/didChangeConfiguration](#workspacedidchangeconfiguration) | ||
- [Code actions on save](#code-actions-on-save) | ||
- [Workspace commands \(`workspace/executeCommand`\)](#workspace-commands-workspaceexecutecommand) | ||
- [Go to Source Definition](#go-to-source-definition) | ||
- [Apply Workspace Edits](#apply-workspace-edits) | ||
- [Apply Code Action](#apply-code-action) | ||
- [Apply Refactoring](#apply-refactoring) | ||
- [Organize Imports](#organize-imports) | ||
- [Rename File](#rename-file) | ||
- [Inlay hints \(`typescript/inlayHints`\) \(experimental\)](#inlay-hints-typescriptinlayhints-experimental) | ||
- [Callers and callees \(`textDocument/calls`\) \(experimental\)](#callers-and-callees-textdocumentcalls-experimental) | ||
- [Supported Protocol features](#supported-protocol-features) | ||
- [Experimental](#experimental) | ||
- [Development](#development) | ||
- [Build](#build) | ||
- [Test](#test) | ||
- [Watch](#watch) | ||
- [Publishing](#publishing) | ||
<!-- /MarkdownTOC --> | ||
## Installing | ||
@@ -251,34 +278,110 @@ | ||
See [LSP specification](https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#workspace_executeCommand). | ||
See [LSP specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_executeCommand). | ||
Most of the time, you'll execute commands with arguments retrieved from another request like `textDocument/codeAction`. There are some use cases for calling them manually. | ||
Supported commands: | ||
`lsp` refers to the language server protocol types, `tsp` refers to the typescript server protocol types. | ||
`lsp` refers to the language server protocol, `tsp` refers to the typescript server protocol. | ||
### Go to Source Definition | ||
* `_typescript.applyWorkspaceEdit` | ||
- Request: | ||
```ts | ||
type Arguments = [lsp.WorkspaceEdit] | ||
{ | ||
command: `_typescript.goToSourceDefinition` | ||
arguments: [ | ||
lsp.DocumentUri, // String URI of the document | ||
lsp.Position, // Line and character position (zero-based) | ||
] | ||
} | ||
``` | ||
* `_typescript.applyCodeAction` | ||
- Response: | ||
```ts | ||
type Arguments = [tsp.CodeAction] | ||
lsp.Location[] | null | ||
``` | ||
* `_typescript.applyRefactoring` | ||
(This command is supported from Typescript 4.7.) | ||
### Apply Workspace Edits | ||
- Request: | ||
```ts | ||
type Arguments = [tsp.GetEditsForRefactorRequestArgs] | ||
{ | ||
command: `_typescript.applyWorkspaceEdit` | ||
arguments: [lsp.WorkspaceEdit] | ||
} | ||
``` | ||
* `_typescript.organizeImports` | ||
- Response: | ||
```ts | ||
// The "skipDestructiveCodeActions" argument is supported from Typescript 4.4+ | ||
type Arguments = [string] | [string, { skipDestructiveCodeActions?: boolean }] | ||
lsp.ApplyWorkspaceEditResult | ||
``` | ||
* `_typescript.applyRenameFile` | ||
### Apply Code Action | ||
- Request: | ||
```ts | ||
type Arguments = [{ sourceUri: string; targetUri: string; }] | ||
{ | ||
command: `_typescript.applyCodeAction` | ||
arguments: [ | ||
tsp.CodeAction, // TypeScript Code Action object | ||
] | ||
} | ||
``` | ||
- Response: | ||
```ts | ||
void | ||
``` | ||
### Apply Refactoring | ||
- Request: | ||
```ts | ||
{ | ||
command: `_typescript.applyRefactoring` | ||
arguments: [ | ||
tsp.GetEditsForRefactorRequestArgs, | ||
] | ||
} | ||
``` | ||
- Response: | ||
```ts | ||
void | ||
``` | ||
### Organize Imports | ||
- Request: | ||
```ts | ||
{ | ||
command: `_typescript.organizeImports` | ||
arguments: [ | ||
// The "skipDestructiveCodeActions" argument is supported from Typescript 4.4+ | ||
[string] | [string, { skipDestructiveCodeActions?: boolean }], | ||
] | ||
} | ||
``` | ||
- Response: | ||
```ts | ||
void | ||
``` | ||
### Rename File | ||
- Request: | ||
```ts | ||
{ | ||
command: `_typescript.applyRenameFile` | ||
arguments: [ | ||
{ sourceUri: string; targetUri: string; }, | ||
] | ||
} | ||
``` | ||
- Response: | ||
```ts | ||
void | ||
``` | ||
## Inlay hints (`typescript/inlayHints`) (experimental) | ||
> !!! This implementation is deprecated. Use the spec-compliant `textDocument/inlayHint` request instead. !!! | ||
Supports experimental inline hints. | ||
@@ -406,2 +509,5 @@ | ||
- [x] textDocument/codeAction | ||
- [x] textDocument/completion (incl. `completion/resolve`) | ||
- [x] textDocument/definition | ||
- [x] textDocument/didChange (incremental) | ||
@@ -411,5 +517,2 @@ - [x] textDocument/didClose | ||
- [x] textDocument/didSave | ||
- [x] textDocument/codeAction | ||
- [x] textDocument/completion (incl. completion/resolve) | ||
- [x] textDocument/definition | ||
- [x] textDocument/documentHighlight | ||
@@ -419,9 +522,8 @@ - [x] textDocument/documentSymbol | ||
- [x] textDocument/formatting | ||
- [x] textDocument/hover | ||
- [x] textDocument/inlayHint (no support for `inlayHint/resolve` or `workspace/inlayHint/refresh`) | ||
- [x] textDocument/rangeFormatting | ||
- [x] textDocument/hover | ||
- [x] textDocument/references | ||
- [x] textDocument/rename | ||
- [x] textDocument/references | ||
- [x] textDocument/signatureHelp | ||
- [x] textDocument/calls (experimental) | ||
- [x] typescript/inlayHints (experimental, supported from Typescript v4.4.2) | ||
- [x] workspace/symbol | ||
@@ -431,2 +533,7 @@ - [x] workspace/didChangeConfiguration | ||
### Experimental | ||
- [x] textDocument/calls (experimental) | ||
- [x] typescript/inlayHints (experimental, supported from Typescript v4.4.2) DEPRECATED (use `textDocument/inlayHint` instead) | ||
## Development | ||
@@ -433,0 +540,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
588254
172
7355
557
20