Skip to content
Draft
Show file tree
Hide file tree
Changes from 7 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
8 changes: 4 additions & 4 deletions src/LanguageServer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1554,7 +1554,7 @@ describe('LanguageServer', () => {
});

expect(locations.length).to.equal(1);
const location: Location = locations[0];
const location: Location = locations[0] as Location;
expect(location.uri).to.equal(functionDocument.uri);
expect(location.range.start.line).to.equal(5);
expect(location.range.start.character).to.equal(16);
Expand All @@ -1569,7 +1569,7 @@ describe('LanguageServer', () => {
});

expect(locations.length).to.equal(1);
const location: Location = locations[0];
const location: Location = locations[0] as Location;
expect(location.uri).to.equal(functionDocument.uri);
expect(location.range.start.line).to.equal(5);
expect(location.range.start.character).to.equal(16);
Expand All @@ -1594,7 +1594,7 @@ describe('LanguageServer', () => {
position: util.createPosition(3, 36)
});
expect(locations.length).to.equal(1);
const location: Location = locations[0];
const location: Location = locations[0] as Location;
expect(location.uri).to.equal(referenceDocument.uri);
expect(location.range.start.line).to.equal(2);
expect(location.range.start.character).to.equal(20);
Expand Down Expand Up @@ -1629,7 +1629,7 @@ describe('LanguageServer', () => {
position: util.createPosition(3, 30)
});
expect(locations.length).to.equal(1);
const location: Location = locations[0];
const location: Location = locations[0] as Location;
expect(location.uri).to.equal(functionDocument.uri);
expect(location.range.start.line).to.equal(2);
expect(location.range.start.character).to.equal(20);
Expand Down
2 changes: 1 addition & 1 deletion src/LanguageServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,7 @@ export class LanguageServer {

const srcPath = util.uriToPath(params.textDocument.uri);

const result = this.projectManager.getDefinition({ srcPath: srcPath, position: params.position });
const result = await this.projectManager.getDefinition({ srcPath: srcPath, position: params.position });
return result;
}

Expand Down
8 changes: 3 additions & 5 deletions src/Program.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as assert from 'assert';
import * as fsExtra from 'fs-extra';
import * as path from 'path';
import type { CodeAction, CompletionItem, Position, Range, SignatureInformation, Location, DocumentSymbol, CancellationToken } from 'vscode-languageserver';
import type { CodeAction, CompletionItem, Position, Range, SignatureInformation, Location, LocationLink, DocumentSymbol, CancellationToken } from 'vscode-languageserver';
import { CancellationTokenSource, CompletionItemKind } from 'vscode-languageserver';
import type { BsConfig, FinalizedBsConfig } from './BsConfig';
import { Scope } from './Scope';
Expand Down Expand Up @@ -1001,7 +1001,7 @@ export class Program {
* Given a position in a file, if the position is sitting on some type of identifier,
* go to the definition of that identifier (where this thing was first defined)
*/
public getDefinition(srcPath: string, position: Position): Location[] {
public getDefinition(srcPath: string, position: Position): Array<Location | LocationLink> {
let file = this.getFile(srcPath);
if (!file) {
return [];
Expand All @@ -1017,12 +1017,10 @@ export class Program {
this.plugins.emit('beforeProvideDefinition', event);
this.plugins.emit('provideDefinition', event);
this.plugins.emit('afterProvideDefinition', event);

return event.definitions;
}

/**
* Get hover information for a file and position
*/
public getHover(srcPath: string, position: Position): Hover[] {
let file = this.getFile(srcPath);
let result: Hover[];
Expand Down
4 changes: 2 additions & 2 deletions src/Scope.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { CompletionItem, Position, Range, Location } from 'vscode-languageserver';
import type { CompletionItem, Position, Range, Location, LocationLink } from 'vscode-languageserver';
import * as path from 'path';
import { CompletionItemKind } from 'vscode-languageserver';
import chalk from 'chalk';
Expand Down Expand Up @@ -1261,7 +1261,7 @@ export class Scope {
* Get the definition (where was this thing first defined) of the symbol under the position
* @deprecated use `DefinitionProvider.process()`
*/
public getDefinition(file: BscFile, position: Position): Location[] {
public getDefinition(file: BscFile, position: Position): Array<Location | LocationLink> {
// Overridden in XMLScope. Brs files use implementation in BrsFile
return [];
}
Expand Down
2 changes: 1 addition & 1 deletion src/XmlScope.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ describe('XmlScope', () => {
`);
const definition = program.getDefinition(childXmlFile.srcPath, Position.create(1, 48));
expect(definition).to.be.lengthOf(1);
expect(definition[0].uri).to.equal(util.pathToUri(parentXmlFile.srcPath));
expect((definition[0] as any).uri).to.equal(util.pathToUri(parentXmlFile.srcPath));
});
});

Expand Down
4 changes: 2 additions & 2 deletions src/XmlScope.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Location, Position } from 'vscode-languageserver';
import type { Location, LocationLink, Position } from 'vscode-languageserver';
import { Scope } from './Scope';
import { DiagnosticMessages } from './DiagnosticMessages';
import type { XmlFile } from './files/XmlFile';
Expand Down Expand Up @@ -169,7 +169,7 @@ export class XmlScope extends Scope {
* Get the definition (where was this thing first defined) of the symbol under the position
* @deprecated use `DefinitionProvider.process()`
*/
public getDefinition(file: BscFile, position: Position): Location[] {
public getDefinition(file: BscFile, position: Position): Array<Location | LocationLink> {
return new DefinitionProvider({
program: this.program,
file: file,
Expand Down
53 changes: 53 additions & 0 deletions src/bscPlugin/definition/DefinitionProvider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,4 +194,57 @@ describe('DefinitionProvider', () => {
range: util.createRange(1, 0, 1, 0)
}]);
});

it('handles script tag uri go-to-definition', () => {
const brsFile = program.setFile('components/MainScene.brs', `
sub main()
end sub
`);
const xmlFile = program.setFile('components/MainScene.xml', `
<component name="MainScene" extends="Scene">
<script type="text/brightscript" uri="pkg:/components/MainScene.brs" />
</component>
`);
// Line 2 (0-indexed): ` <script type="text/brightscript" uri="pkg:/components/MainScene.brs" />`
// The uri value range starts at the opening `"` for `pkg:/components/MainScene.brs`
const result = program.getDefinition(xmlFile.srcPath, util.createPosition(2, 60));
expect(result).to.be.lengthOf(1);
expect(result[0]).to.include({
targetUri: URI.file(brsFile.srcPath).toString()
});
// originSelectionRange should cover the full URI value (the entire filePathRange)
expect((result[0] as any).originSelectionRange).to.exist;
});

it('handles script tag uri go-to-definition with relative path', () => {
const brsFile = program.setFile('components/MainScene.brs', `
sub main()
end sub
`);
const xmlFile = program.setFile('components/MainScene.xml', `
<component name="MainScene" extends="Scene">
<script type="text/brightscript" uri="MainScene.brs" />
</component>
`);
// Line 2 (0-indexed): ` <script type="text/brightscript" uri="MainScene.brs" />`
// The uri value range starts at the opening `"` for `MainScene.brs`
const result = program.getDefinition(xmlFile.srcPath, util.createPosition(2, 54));
expect(result).to.be.lengthOf(1);
expect(result[0]).to.include({
targetUri: URI.file(brsFile.srcPath).toString()
});
expect((result[0] as any).originSelectionRange).to.exist;
});

it('returns empty array when script tag uri file is not found', () => {
const xmlFile = program.setFile('components/MainScene.xml', `
<component name="MainScene" extends="Scene">
<script type="text/brightscript" uri="pkg:/components/NotFound.brs" />
</component>
`);
// click within "pkg:/components/NotFound.brs" uri value
expect(
program.getDefinition(xmlFile.srcPath, util.createPosition(2, 60))
).to.eql([]);
});
});
22 changes: 20 additions & 2 deletions src/bscPlugin/definition/DefinitionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { isBrsFile, isClassStatement, isDottedGetExpression, isImportStatement,
import type { BrsFile } from '../../files/BrsFile';
import type { ProvideDefinitionEvent } from '../../interfaces';
import { TokenKind } from '../../lexer/TokenKind';
import type { Location } from 'vscode-languageserver-protocol';
import type { Location, LocationLink } from 'vscode-languageserver-protocol';
import type { ClassStatement, FunctionStatement, NamespaceStatement } from '../../parser/Statement';
import { ParseMode } from '../../parser/Parser';
import util from '../../util';
Expand All @@ -16,7 +16,7 @@ export class DefinitionProvider {
private event: ProvideDefinitionEvent
) { }

public process(): Location[] {
public process(): Array<Location | LocationLink> {
if (isBrsFile(this.event.file)) {
this.brsFileGetDefinition(this.event.file);
} else if (isXmlFile(this.event.file)) {
Expand Down Expand Up @@ -258,5 +258,23 @@ export class DefinitionProvider {
uri: util.pathToUri(file.parentComponent.srcPath)
});
}

//if the position is within a script tag's uri attribute
for (const scriptImport of file.scriptTagImports) {
if (scriptImport.filePathRange && util.rangeContains(scriptImport.filePathRange, this.event.position)) {
const scriptFile = this.event.program.getFile(scriptImport.pkgPath);
if (scriptFile) {
// Return a LocationLink so VS Code uses `originSelectionRange` to underline the
// entire URI path as a single unit on Ctrl+hover (rather than per path-segment).
this.event.definitions.push({
originSelectionRange: scriptImport.filePathRange,
targetUri: util.pathToUri(scriptFile.srcPath),
targetRange: util.createRange(0, 0, 0, 0),
targetSelectionRange: util.createRange(0, 0, 0, 0)
});
}
break;
}
}
}
}
4 changes: 2 additions & 2 deletions src/files/BrsFile.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { CodeWithSourceMap } from 'source-map';
import { SourceNode } from 'source-map';
import type { CompletionItem, Position, Location, Diagnostic } from 'vscode-languageserver';
import type { CompletionItem, Position, Location, LocationLink, Diagnostic } from 'vscode-languageserver';
import { CancellationTokenSource } from 'vscode-languageserver';
import { CompletionItemKind, TextEdit } from 'vscode-languageserver';
import chalk from 'chalk';
Expand Down Expand Up @@ -1309,7 +1309,7 @@ export class BrsFile {
* go to the definition of that identifier (where this thing was first defined)
* @deprecated use `DefinitionProvider.process()` instead
*/
public getDefinition(position: Position): Location[] {
public getDefinition(position: Position): Array<Location | LocationLink> {
return new DefinitionProvider({
program: this.program,
file: this,
Expand Down
9 changes: 6 additions & 3 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Range, Diagnostic, CodeAction, Position, CompletionItem, Location, DocumentSymbol, WorkspaceSymbol, Disposable, FileChangeType } from 'vscode-languageserver-protocol';
import type { Range, Diagnostic, CodeAction, Position, CompletionItem, Location, LocationLink, DocumentSymbol, WorkspaceSymbol, Disposable, FileChangeType } from 'vscode-languageserver-protocol';
import type { Scope } from './Scope';
import type { BrsFile } from './files/BrsFile';
import type { XmlFile } from './files/XmlFile';
Expand Down Expand Up @@ -394,9 +394,12 @@ export interface ProvideDefinitionEvent<TFile = BscFile> {
*/
position: Position;
/**
* The list of locations for where the item at the file and position was defined
* The list of locations for where the item at the file and position was defined.
* Plugins may push either `Location` or `LocationLink` objects.
* When a `LocationLink` is pushed, VS Code will use `originSelectionRange` to highlight
* the source range of the link (e.g. the full URI of a script tag attribute).
*/
definitions: Location[];
definitions: Array<Location | LocationLink>;
}
export type BeforeProvideDefinitionEvent<TFile = BscFile> = ProvideDefinitionEvent<TFile>;
export type AfterProvideDefinitionEvent<TFile = BscFile> = ProvideDefinitionEvent<TFile>;
Expand Down
4 changes: 2 additions & 2 deletions src/lsp/LspProject.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Diagnostic, Position, Range, Location, DocumentSymbol, WorkspaceSymbol, CodeAction, CompletionList } from 'vscode-languageserver-protocol';
import type { Diagnostic, Position, Range, Location, LocationLink, DocumentSymbol, WorkspaceSymbol, CodeAction, CompletionList } from 'vscode-languageserver-protocol';
import type { Hover, MaybePromise, SemanticToken } from '../interfaces';
import type { DocumentAction, DocumentActionWithStatus } from './DocumentManager';
import type { FileTranspileResult, SignatureInfoObj } from '../Program';
Expand Down Expand Up @@ -114,7 +114,7 @@ export interface LspProject {
* Get the locations where the symbol at the specified position is defined
* @param options the file path and position to get the definition for
*/
getDefinition(options: { srcPath: string; position: Position }): MaybePromise<Location[]>;
getDefinition(options: { srcPath: string; position: Position }): MaybePromise<Array<Location | LocationLink>>;

/**
* Get the locations where the symbol at the specified position is defined
Expand Down
4 changes: 2 additions & 2 deletions src/lsp/Project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { URI } from 'vscode-uri';
import { Deferred } from '../deferred';
import type { StandardizedFileEntry } from 'roku-deploy';
import { rokuDeploy } from 'roku-deploy';
import type { DocumentSymbol, Position, Range, Location, WorkspaceSymbol } from 'vscode-languageserver-protocol';
import type { DocumentSymbol, Position, Range, Location, LocationLink, WorkspaceSymbol } from 'vscode-languageserver-protocol';
import { CompletionList } from 'vscode-languageserver-protocol';
import { CancellationTokenSource } from 'vscode-languageserver-protocol';
import type { DocumentAction, DocumentActionWithStatus } from './DocumentManager';
Expand Down Expand Up @@ -383,7 +383,7 @@ export class Project implements LspProject {
}
}

public async getDefinition(options: { srcPath: string; position: Position }): Promise<Location[]> {
public async getDefinition(options: { srcPath: string; position: Position }): Promise<Array<Location | LocationLink>> {
await this.onIdle();
if (this.builder.program.hasFile(options.srcPath)) {
return this.builder.program.getDefinition(options.srcPath, options.position);
Expand Down
4 changes: 2 additions & 2 deletions src/lsp/ProjectManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { LspDiagnostic, LspProject, ProjectConfig } from './LspProject';
import { Project } from './Project';
import { WorkerThreadProject } from './worker/WorkerThreadProject';
import { FileChangeType } from 'vscode-languageserver-protocol';
import type { Hover, Position, Range, Location, SignatureHelp, DocumentSymbol, SymbolInformation, WorkspaceSymbol, CompletionList, CancellationToken } from 'vscode-languageserver-protocol';
import type { Hover, Position, Range, Location, LocationLink, SignatureHelp, DocumentSymbol, SymbolInformation, WorkspaceSymbol, CompletionList, CancellationToken } from 'vscode-languageserver-protocol';
import { Deferred } from '../deferred';
import type { DocumentActionWithStatus, FlushEvent } from './DocumentManager';
import { DocumentManager } from './DocumentManager';
Expand Down Expand Up @@ -554,7 +554,7 @@ export class ProjectManager {
* @returns a list of locations where the symbol under the position is defined in the project
*/
@TrackBusyStatus
public async getDefinition(options: { srcPath: string; position: Position }): Promise<Location[]> {
public async getDefinition(options: { srcPath: string; position: Position }): Promise<Array<Location | LocationLink>> {
//wait for all pending syncs to finish
await this.onIdle();

Expand Down
6 changes: 3 additions & 3 deletions src/lsp/worker/WorkerThreadProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type { Hover, MaybePromise, SemanticToken } from '../../interfaces';
import type { DocumentAction, DocumentActionWithStatus } from '../DocumentManager';
import { Deferred } from '../../deferred';
import type { FileTranspileResult, SignatureInfoObj } from '../../Program';
import type { Position, Range, Location, DocumentSymbol, WorkspaceSymbol, CodeAction, CompletionList } from 'vscode-languageserver-protocol';
import type { Position, Range, Location, LocationLink, DocumentSymbol, WorkspaceSymbol, CodeAction, CompletionList } from 'vscode-languageserver-protocol';
import type { Logger } from '../../logging';
import { createLogger } from '../../logging';
import * as fsExtra from 'fs-extra';
Expand Down Expand Up @@ -220,8 +220,8 @@ export class WorkerThreadProject implements LspProject {
return this.sendStandardRequest<Hover[]>('getHover', options);
}

public async getDefinition(options: { srcPath: string; position: Position }): Promise<Location[]> {
return this.sendStandardRequest<Location[]>('getDefinition', options);
public async getDefinition(options: { srcPath: string; position: Position }): Promise<Array<Location | LocationLink>> {
return this.sendStandardRequest<Array<Location | LocationLink>>('getDefinition', options);
}

public async getSignatureHelp(options: { srcPath: string; position: Position }): Promise<SignatureInfoObj[]> {
Expand Down
Loading