Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat: support LocationLink[] for textDocument/definition response #563

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions src/calls.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@

import type tsp from 'typescript/lib/protocol.d.js';
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 { CommandTypes } from './tsp-command-types.js';
import { uriToPath, toLocation, asRange, Range, toSymbolKind, pathToUri } from './protocol-translation.js';
import { TextDocument } from 'vscode-languageserver-textdocument';
import { uriToPath, toLocation, toSymbolKind, pathToUri } from './protocol-translation.js';
import { Range } from './utils/typeConverters.js';

export async function computeCallers(tspClient: TspClient, args: lsp.TextDocumentPositionParams): Promise<lspcalls.CallsResult> {
const nullResult = { calls: [] };
Expand Down Expand Up @@ -140,7 +141,7 @@ async function findEnclosingSymbol(tspClient: TspClient, args: tsp.FileSpan): Pr
return undefined;
}
const pos = lsp.Position.create(args.start.line - 1, args.start.offset - 1);
const symbol = await findEnclosingSymbolInTree(tree, lsp.Range.create(pos, pos));
const symbol = findEnclosingSymbolInTree(tree, lsp.Range.create(pos, pos));
if (!symbol) {
return undefined;
}
Expand All @@ -149,7 +150,7 @@ async function findEnclosingSymbol(tspClient: TspClient, args: tsp.FileSpan): Pr
}

function findEnclosingSymbolInTree(parent: tsp.NavigationTree, range: lsp.Range): lsp.DocumentSymbol | undefined {
const inSpan = (span: tsp.TextSpan) => !!Range.intersection(asRange(span), range);
const inSpan = (span: tsp.TextSpan) => !!Range.intersection(Range.fromTextSpan(span), range);
const inTree = (tree: tsp.NavigationTree) => tree.spans.some(span => inSpan(span));

let candidate = inTree(parent) ? parent : undefined;
Expand All @@ -167,10 +168,10 @@ function findEnclosingSymbolInTree(parent: tsp.NavigationTree, range: lsp.Range)
return 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)) {
selectionRange = nameRange;
}
Expand Down
10 changes: 5 additions & 5 deletions src/completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import * as lsp from 'vscode-languageserver';
import type tsp from 'typescript/lib/protocol.js';
import { LspDocument } from './document.js';
import { CommandTypes, 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 { TspClient } from './tsp-client.js';
import { CompletionOptions, DisplayPartKind, SupportedFeatures } from './ts-protocol.js';
import SnippetString from './utils/SnippetString.js';
import * as typeConverters from './utils/typeConverters.js';
import { Range, Position } from './utils/typeConverters.js';

interface ParameterListParts {
readonly parts: ReadonlyArray<tsp.SymbolDisplayPart>;
Expand Down Expand Up @@ -62,7 +62,7 @@ export function asCompletionItem(entry: tsp.CompletionEntry, file: string, posit
}

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
if (replacementRange && replacementRange.start.line !== replacementRange.end.line) {
replacementRange = lsp.Range.create(replacementRange.start, document.getLineEnd(replacementRange.start.line));
Expand Down Expand Up @@ -202,7 +202,7 @@ export async function asResolvedCompletionItem(
}
if (features.completionSnippets && options.completeFunctionCalls && (item.kind === lsp.CompletionItemKind.Function || item.kind === lsp.CompletionItemKind.Method)) {
const { line, offset } = item.data;
const position = typeConverters.Position.fromLocation({ line, offset });
const position = Position.fromLocation({ line, offset });
const shouldCompleteFunction = await isValidFunctionCompletionContext(filepath, position, client);
if (shouldCompleteFunction) {
createSnippetOfFunctionCall(item, details);
Expand All @@ -216,7 +216,7 @@ export async function isValidFunctionCompletionContext(filepath: string, positio
// Workaround for https://github.com/Microsoft/TypeScript/issues/12677
// Don't complete function calls inside of destructive assigments or imports
try {
const args: tsp.FileLocationRequestArgs = typeConverters.Position.toFileLocationRequestArgs(filepath, position);
const args: tsp.FileLocationRequestArgs = Position.toFileLocationRequestArgs(filepath, position);
const response = await client.request(CommandTypes.Quickinfo, args);
if (response.type !== 'response') {
return true;
Expand Down
15 changes: 8 additions & 7 deletions src/document-symbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,35 @@

import * as lsp from 'vscode-languageserver';
import type tsp from 'typescript/lib/protocol.d.js';
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: tsp.NavigationTree, symbols: lsp.DocumentSymbol[]): boolean {
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 });
}

function collectDocumentSymbolsInRange(parent: tsp.NavigationTree, symbols: lsp.DocumentSymbol[], range: lsp.Range): boolean {
let shouldInclude = shouldIncludeEntry(parent);

for (const span of parent.spans) {
const spanRange = asRange(span);
const spanRange = Range.fromTextSpan(span);
if (!Range.intersection(range, spanRange)) {
continue;
}

const children: lsp.DocumentSymbol[] = [];
if (parent.childItems) {
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);
shouldInclude = shouldInclude || includedChild;
}
}
}
let selectionRange = spanRange;
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.
if (Range.intersection(spanRange, nameRange)) {
selectionRange = nameRange;
Expand All @@ -59,11 +60,11 @@ export function collectSymbolInformation(uri: string, current: tsp.NavigationTre
let shouldInclude = shouldIncludeEntry(current);
const name = current.text;
for (const span of current.spans) {
const range = asRange(span);
const range = Range.fromTextSpan(span);
const children: lsp.SymbolInformation[] = [];
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);
shouldInclude = shouldInclude || includedChild;
}
Expand Down
7 changes: 4 additions & 3 deletions src/features/fix-all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
import type tsp from 'typescript/lib/protocol.d.js';
import * as lsp from 'vscode-languageserver';
import { LspDocuments } from '../document.js';
import { toFileRangeRequestArgs, toTextDocumentEdit } from '../protocol-translation.js';
import { toTextDocumentEdit } from '../protocol-translation.js';
import { TspClient } from '../tsp-client.js';
import { CommandTypes } from '../tsp-command-types.js';
import * as errorCodes from '../utils/errorCodes.js';
import * as fixNames from '../utils/fixNames.js';
import { CodeActionKind } from '../utils/types.js';
import { Range } from '../utils/typeConverters.js';

interface AutoFix {
readonly codes: Set<number>;
Expand All @@ -33,7 +34,7 @@ async function buildIndividualFixes(
}

const args: tsp.CodeFixRequestArgs = {
...toFileRangeRequestArgs(file, diagnostic.range),
...Range.toFileRangeRequestArgs(file, diagnostic.range),
errorCodes: [+diagnostic.code!]
};

Expand Down Expand Up @@ -67,7 +68,7 @@ async function buildCombinedFix(
}

const args: tsp.CodeFixRequestArgs = {
...toFileRangeRequestArgs(file, diagnostic.range),
...Range.toFileRangeRequestArgs(file, diagnostic.range),
errorCodes: [+diagnostic.code!]
};

Expand Down
4 changes: 2 additions & 2 deletions src/features/source-definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import * as lsp from 'vscode-languageserver';
import API from '../utils/api.js';
import * as typeConverters from '../utils/typeConverters.js';
import { Position } from '../utils/typeConverters.js';
import { toLocation, uriToPath } from '../protocol-translation.js';
import type { LspDocuments } from '../document.js';
import type { TspClient } from '../tsp-client.js';
Expand Down Expand Up @@ -54,7 +54,7 @@ export class SourceDefinitionCommand {
return;
}

const args = typeConverters.Position.toFileLocationRequestArgs(file, position);
const args = Position.toFileLocationRequestArgs(file, position);
return await lspClient.withProgress<lsp.Location[] | void>({
message: 'Finding source definitions…',
reporter
Expand Down
115 changes: 115 additions & 0 deletions src/lsp-server.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,121 @@ describe('completion', () => {
});
});

describe('definition', () => {
it('goes to definition', async () => {
// NOTE: This test needs to reference files that physically exist for the feature to work.
const indexUri = uri('source-definition', 'index.ts');
const indexDoc = {
uri: indexUri,
languageId: 'typescript',
version: 1,
text: readContents(filePath('source-definition', 'index.ts'))
};
server.didOpenTextDocument({ textDocument: indexDoc });
const definitions = await server.definition({
textDocument: indexDoc,
position: position(indexDoc, 'a/*identifier*/')
}) as lsp.Location[];
assert.isArray(definitions);
assert.equal(definitions!.length, 1);
assert.deepEqual(definitions![0], {
uri: uri('source-definition', 'a.d.ts'),
range: {
start: {
line: 0,
character: 21
},
end: {
line: 0,
character: 22
}
}
});
});
});

describe('definition (definition link supported)', () => {
let localServer: TestLspServer;

before(async () => {
const clientCapabilitiesOverride: lsp.ClientCapabilities = {
textDocument: {
definition: {
linkSupport: true
}
}
};
localServer = await createServer({
rootUri: uri('source-definition'),
publishDiagnostics: args => diagnostics.set(args.uri, args),
clientCapabilitiesOverride
});
});

beforeEach(() => {
localServer.closeAll();
// "closeAll" triggers final publishDiagnostics with an empty list so clear last.
diagnostics.clear();
localServer.workspaceEdits = [];
});

after(() => {
localServer.closeAll();
localServer.shutdown();
});

it('goes to definition', async () => {
// NOTE: This test needs to reference files that physically exist for the feature to work.
const indexUri = uri('source-definition', 'index.ts');
const indexDoc = {
uri: indexUri,
languageId: 'typescript',
version: 1,
text: readContents(filePath('source-definition', 'index.ts'))
};
localServer.didOpenTextDocument({ textDocument: indexDoc });
const definitions = await localServer.definition({
textDocument: indexDoc,
position: position(indexDoc, 'a/*identifier*/')
}) as lsp.DefinitionLink[];
assert.isArray(definitions);
assert.equal(definitions!.length, 1);
assert.deepEqual(definitions![0], {
originSelectionRange: {
start: {
line: 1,
character: 0
},
end: {
line: 1,
character: 1
}
},
targetRange: {
start: {
line: 0,
character: 0
},
end: {
line: 0,
character: 30
}
},
targetUri: uri('source-definition', 'a.d.ts'),
targetSelectionRange: {
start: {
line: 0,
character: 21
},
end: {
line: 0,
character: 22
}
}
});
});
});

describe('diagnostics', () => {
it('simple test', async () => {
const doc = {
Expand Down
Loading