Skip to content

Commit 48ea4e4

Browse files
patrysDonJayamanne
authored andcommitted
Improve symbol provider (#622)
* Fix type annotations and make tests pass * Improve symbol provider to provide scope info
1 parent f0b3233 commit 48ea4e4

File tree

11 files changed

+187
-121
lines changed

11 files changed

+187
-121
lines changed

pythonFiles/completion.py

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,52 @@ def _top_definition(self, definition):
249249
return d
250250
return definition
251251

252+
def _extract_range(self, definition):
253+
"""Provides the definition range of a given definition
254+
255+
For regular symbols it returns the start and end location of the
256+
characters making up the symbol.
257+
258+
For scoped containers it will return the entire definition of the
259+
scope.
260+
261+
The scope that jedi provides ends with the first character of the next
262+
scope so it's not ideal. For vscode we need the scope to end with the
263+
last character of actual code. That's why we extract the lines that
264+
make up our scope and trim the trailing whitespace.
265+
"""
266+
from jedi import common
267+
from jedi.parser.utils import load_parser
268+
# get the scope range
269+
if definition.type in ['class', 'function', 'method']:
270+
scope = definition._name.get_parent_scope()
271+
start_line = scope.start_pos[0] - 1
272+
start_column = scope.start_pos[1]
273+
end_line = scope.end_pos[0] - 1
274+
end_column = scope.end_pos[1]
275+
# get the lines
276+
path = definition._name.get_parent_until().path
277+
parser = load_parser(path)
278+
lines = common.splitlines(parser.source)
279+
lines[end_line] = lines[end_line][:end_column]
280+
# trim the lines
281+
lines = lines[start_line:end_line + 1]
282+
lines = '\n'.join(lines).rstrip().split('\n')
283+
end_line = start_line + len(lines) - 1
284+
end_column = len(lines[-1]) - 1
285+
else:
286+
symbol = definition._name
287+
start_line = symbol.start_pos[0] - 1
288+
start_column = symbol.start_pos[1]
289+
end_line = symbol.end_pos[0] - 1
290+
end_column = symbol.end_pos[1]
291+
return {
292+
'start_line': start_line,
293+
'start_column': start_column,
294+
'end_line': end_line,
295+
'end_column': end_column
296+
}
297+
252298
def _serialize_definitions(self, definitions, identifier=None):
253299
"""Serialize response to be read from VSCode.
254300
@@ -267,13 +313,18 @@ def _serialize_definitions(self, definitions, identifier=None):
267313
definition = self._top_definition(definition)
268314
if not definition.module_path:
269315
continue
316+
try:
317+
parent = definition.parent()
318+
container = parent.name if parent.type != 'module' else ''
319+
except Exception:
320+
container = ''
270321
_definition = {
271322
'text': definition.name,
272323
'type': self._get_definition_type(definition),
273324
'raw_type': definition.type,
274325
'fileName': definition.module_path,
275-
'line': definition.line - 1,
276-
'column': definition.column
326+
'container': container,
327+
'range': self._extract_range(definition)
277328
}
278329
_definitions.append(_definition)
279330
except Exception as e:
@@ -419,7 +470,7 @@ def watch(self):
419470
else:
420471
jediPath = os.path.join(os.path.dirname(__file__), 'release')
421472
sys.path.insert(0, jediPath)
422-
import jedi
473+
import jedi
423474
if jediPreview:
424475
jedi.settings.cache_directory = os.path.join(
425476
jedi.settings.cache_directory, cachePrefix + jedi.__version__.replace('.', ''))

src/client/jupyter/jupyter_client/jupyterSocketClient.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ export class JupyterSocketClient extends SocketCallbackHandler {
187187
def.resolve();
188188
}
189189
public ping(message: string) {
190-
const [def, id] = this.createId<string[]>();
190+
const [def, id] = this.createId<string>();
191191
this.SendRawCommand(Commands.PingBytes);
192192
this.stream.WriteString(id);
193193
this.stream.WriteString(message);

src/client/jupyter/jupyter_client/main.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export class JupyterClientAdapter extends EventEmitter implements IJupyterClient
101101
// Ok everything has started, now test ping
102102
const msg1 = 'Hello world from Type Script - Функция проверки ИНН и КПП - 长城!1';
103103
const msg2 = 'Hello world from Type Script - Функция проверки ИНН и КПП - 长城!2';
104-
Promise.all<string>([this.ipythonAdapter.ping(msg1), this.ipythonAdapter.ping(msg2)]).then(msgs => {
104+
Promise.all<string, string>([this.ipythonAdapter.ping(msg1), this.ipythonAdapter.ping(msg2)]).then(msgs => {
105105
if (msgs.indexOf(msg1) === -1 || msgs.indexOf(msg2) === -1) {
106106
def.reject('msg1 or msg2 not returned');
107107
}
@@ -189,4 +189,4 @@ export class JupyterClientAdapter extends EventEmitter implements IJupyterClient
189189

190190
return subject;
191191
}
192-
}
192+
}

src/client/jupyter/kernel-manager.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -88,16 +88,16 @@ export class KernelManagerImpl extends EventEmitter {
8888
throw new Error('Start Existing Kernel not implemented');
8989
}
9090

91-
public startKernel(kernelSpec: KernelspecMetadata, language: string): Promise<Kernel> {
91+
public async startKernel(kernelSpec: KernelspecMetadata, language: string): Promise<Kernel> {
9292
this.destroyRunningKernelFor(language);
93-
return this.jupyterClient.startKernel(kernelSpec).then((kernelInfo: [string, any, string]) => {
94-
const kernelUUID = kernelInfo[0];
95-
const config = kernelInfo[1];
96-
const connectionFile = kernelInfo[2];
97-
const kernel = new JupyterClientKernel(kernelSpec, language, config, connectionFile, kernelUUID, this.jupyterClient);
98-
this.setRunningKernelFor(language, kernel);
99-
return this.executeStartupCode(kernel).then(() => kernel);
100-
});
93+
const kernelInfo = await this.jupyterClient.startKernel(kernelSpec);
94+
const kernelUUID = kernelInfo[0];
95+
const config = kernelInfo[1];
96+
const connectionFile = kernelInfo[2];
97+
const kernel = new JupyterClientKernel(kernelSpec, language, config, connectionFile, kernelUUID, this.jupyterClient);
98+
this.setRunningKernelFor(language, kernel);
99+
await this.executeStartupCode(kernel);
100+
return kernel;
101101
}
102102

103103
private executeStartupCode(kernel: Kernel): Promise<any> {
@@ -186,4 +186,4 @@ export class KernelManagerImpl extends EventEmitter {
186186
public getKernelSpecsFromJupyter(): Promise<any> {
187187
return this.jupyterClient.getAllKernelSpecs();
188188
}
189-
}
189+
}

src/client/providers/definitionProvider.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,22 @@ export class PythonDefinitionProvider implements vscode.DefinitionProvider {
1515
}
1616
private static parseData(data: proxy.IDefinitionResult): vscode.Definition {
1717
if (data && data.definition) {
18-
var definitionResource = vscode.Uri.file(data.definition.fileName);
19-
var range = new vscode.Range(data.definition.lineIndex, data.definition.columnIndex, data.definition.lineIndex, data.definition.columnIndex);
20-
18+
const definition = data.definition;
19+
const definitionResource = vscode.Uri.file(definition.fileName);
20+
const range = new vscode.Range(
21+
definition.range.startLine, definition.range.startColumn,
22+
definition.range.endLine, definition.range.endColumn);
2123
return new vscode.Location(definitionResource, range);
2224
}
2325
return null;
2426
}
2527
public provideDefinition(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Thenable<vscode.Definition> {
2628
var filename = document.fileName;
2729
if (document.lineAt(position.line).text.match(/^\s*\/\//)) {
28-
return Promise.resolve();
30+
return Promise.resolve(null);
2931
}
3032
if (position.character <= 0) {
31-
return Promise.resolve();
33+
return Promise.resolve(null);
3234
}
3335

3436
var range = document.getWordRangeAtPosition(position);

src/client/providers/hoverProvider.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ export class PythonHoverProvider implements vscode.HoverProvider {
1414
public provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Thenable<vscode.Hover> {
1515
var filename = document.fileName;
1616
if (document.lineAt(position.line).text.match(/^\s*\/\//)) {
17-
return Promise.resolve();
17+
return Promise.resolve(null);
1818
}
1919
if (position.character <= 0) {
20-
return Promise.resolve();
20+
return Promise.resolve(null);
2121
}
2222

2323
var range = document.getWordRangeAtPosition(position);
2424
if (!range || range.isEmpty) {
25-
return Promise.resolve();
25+
return Promise.resolve(null);
2626
}
2727
var columnIndex = range.start.character < range.end.character ? range.start.character + 2 : range.end.character;
2828
var cmd: proxy.ICommand<proxy.ICompletionResult> = {

src/client/providers/jediProxy.ts

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -311,12 +311,17 @@ function spawnProcess(dir: string) {
311311
let def = defs[0];
312312
const originalType = def.type as string;
313313
defResult.definition = {
314-
columnIndex: Number(def.column),
315314
fileName: def.fileName,
316-
lineIndex: Number(def.line),
317315
text: def.text,
318316
type: getMappedVSCodeType(originalType),
319-
kind: getMappedVSCodeSymbol(originalType)
317+
kind: getMappedVSCodeSymbol(originalType),
318+
container: def.container,
319+
range: {
320+
startLine: def.range.start_line,
321+
startColumn: def.range.start_column,
322+
endLine: def.range.end_line,
323+
endColumn: def.range.end_column
324+
}
320325
};
321326
}
322327

@@ -330,15 +335,20 @@ function spawnProcess(dir: string) {
330335
requestId: cmd.id,
331336
definitions: []
332337
};
333-
defResults.definitions = defs.map(def => {
338+
defResults.definitions = defs.map<IDefinition>(def => {
334339
const originalType = def.type as string;
335-
return <IDefinition>{
336-
columnIndex: <number>def.column,
337-
fileName: <string>def.fileName,
338-
lineIndex: <number>def.line,
339-
text: <string>def.text,
340+
return {
341+
fileName: def.fileName,
342+
text: def.text,
340343
type: getMappedVSCodeType(originalType),
341-
kind: getMappedVSCodeSymbol(originalType)
344+
kind: getMappedVSCodeSymbol(originalType),
345+
container: def.container,
346+
range: {
347+
startLine: def.range.start_line,
348+
startColumn: def.range.start_column,
349+
endLine: def.range.end_line,
350+
endColumn: def.range.end_column
351+
}
342352
};
343353
});
344354

@@ -597,13 +607,19 @@ export interface IAutoCompleteItem {
597607
raw_docstring: string;
598608
rightLabel: string;
599609
}
610+
interface IDefinitionRange {
611+
startLine: number;
612+
startColumn: number;
613+
endLine: number;
614+
endColumn: number;
615+
}
600616
export interface IDefinition {
601617
type: vscode.CompletionItemKind;
602618
kind: vscode.SymbolKind;
603619
text: string;
604620
fileName: string;
605-
columnIndex: number;
606-
lineIndex: number;
621+
container: string;
622+
range: IDefinitionRange;
607623
}
608624

609625
export class JediProxyHandler<R extends ICommandResult, T> {

src/client/providers/referenceProvider.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ export class PythonReferenceProvider implements vscode.ReferenceProvider {
3434
public provideReferences(document: vscode.TextDocument, position: vscode.Position, context: vscode.ReferenceContext, token: vscode.CancellationToken): Thenable<vscode.Location[]> {
3535
var filename = document.fileName;
3636
if (document.lineAt(position.line).text.match(/^\s*\/\//)) {
37-
return Promise.resolve();
37+
return Promise.resolve(null);
3838
}
3939
if (position.character <= 0) {
40-
return Promise.resolve();
40+
return Promise.resolve(null);
4141
}
4242

4343
var range = document.getWordRangeAtPosition(position);

src/client/providers/symbolProvider.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,18 @@ export class PythonSymbolProvider implements vscode.DocumentSymbolProvider {
1010
public constructor(context: vscode.ExtensionContext, jediProxy: proxy.JediProxy = null) {
1111
this.jediProxyHandler = new proxy.JediProxyHandler(context, jediProxy);
1212
}
13-
private static parseData(data: proxy.ISymbolResult): vscode.SymbolInformation[] {
13+
private static parseData(document: vscode.TextDocument, data: proxy.ISymbolResult): vscode.SymbolInformation[] {
1414
if (data) {
15-
var symbols = data.definitions.map(sym => {
16-
var symbol = sym.kind;
17-
var range = new vscode.Range(sym.lineIndex, sym.columnIndex, sym.lineIndex, sym.columnIndex);
18-
return new vscode.SymbolInformation(sym.text, symbol, range, vscode.Uri.file(sym.fileName));
15+
let symbols = data.definitions.filter(sym => sym.fileName === document.fileName);
16+
return symbols.map(sym => {
17+
const symbol = sym.kind;
18+
const range = new vscode.Range(
19+
sym.range.startLine, sym.range.startColumn,
20+
sym.range.endLine, sym.range.endColumn);
21+
const uri = vscode.Uri.file(sym.fileName);
22+
const location = new vscode.Location(uri, range);
23+
return new vscode.SymbolInformation(sym.text, symbol, sym.container, location);
1924
});
20-
21-
return symbols;
2225
}
2326
return [];
2427
}
@@ -37,7 +40,7 @@ export class PythonSymbolProvider implements vscode.DocumentSymbolProvider {
3740
}
3841

3942
return this.jediProxyHandler.sendCommand(cmd, token).then(data => {
40-
return PythonSymbolProvider.parseData(data);
43+
return PythonSymbolProvider.parseData(document, data);
4144
});
4245
}
4346
}

src/client/workspaceSymbols/provider.ts

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,29 +11,23 @@ export class WorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvider {
1111
public constructor(private tagGenerator: Generator, private outputChannel: vscode.OutputChannel) {
1212
}
1313

14-
provideWorkspaceSymbols(query: string, token: vscode.CancellationToken): Thenable<vscode.SymbolInformation[]> {
14+
async provideWorkspaceSymbols(query: string, token: vscode.CancellationToken): Promise<vscode.SymbolInformation[]> {
1515
if (!pythonSettings.workspaceSymbols.enabled) {
16-
return Promise.resolve([]);
16+
return [];
1717
}
18-
return fsExistsAsync(pythonSettings.workspaceSymbols.tagFilePath).then(exits => {
19-
let def = createDeferred<any>();
20-
if (exits) {
21-
def.resolve();
22-
}
23-
else {
24-
vscode.commands.executeCommand(Commands.Build_Workspace_Symbols, false, token).then(() => def.resolve(), reason => def.reject(reason));
25-
}
26-
27-
return def.promise
28-
.then(() => parseTags(query, token))
29-
.then(items => {
30-
if (!Array.isArray(items)) {
31-
return [];
32-
}
33-
return items.map(item => new vscode.SymbolInformation(item.symbolName,
34-
item.symbolKind, '',
35-
new vscode.Location(vscode.Uri.file(item.fileName), item.position)));
36-
});
37-
});
18+
// check whether tag file needs to be built
19+
const tagFileExists = await fsExistsAsync(pythonSettings.workspaceSymbols.tagFilePath);
20+
if (!tagFileExists) {
21+
await vscode.commands.executeCommand(Commands.Build_Workspace_Symbols, false, token);
22+
}
23+
// load tags
24+
const items = await parseTags(query, token);
25+
if (!Array.isArray(items)) {
26+
return [];
27+
}
28+
return items.map(item => new vscode.SymbolInformation(
29+
item.symbolName, item.symbolKind, '',
30+
new vscode.Location(vscode.Uri.file(item.fileName), item.position)
31+
));
3832
}
3933
}

0 commit comments

Comments
 (0)