Skip to content

Commit 372d9b7

Browse files
author
Mikhail Arkhipov
authored
Provide full signature help with highlighted parameter (#416)
* Basic tokenizer * Fixed property names * Tests, round I * Tests, round II * tokenizer test * Remove temorary change * Fix merge issue * Merge conflict * Merge conflict * Completion test * Fix last line * Fix javascript math * Make test await for results * Add license headers * Rename definitions to types * License headers * Fix typo in completion details (typo) * Fix hover test * Russian translations * Update to better translation * Fix typo * #70 How to get all parameter info when filling in a function param list * Fix #70 How to get all parameter info when filling in a function param list * Clean up * Clean imports * CR feedback * Trim whitespace for test stability * More tests * Better handle no-parameters documentation * Better handle ellipsis and Python3 * Remove test change
1 parent 992b654 commit 372d9b7

File tree

8 files changed

+174
-19
lines changed

8 files changed

+174
-19
lines changed

pythonFiles/completion.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import traceback
77
import platform
88

9-
WORD_RE = re.compile(r'\w')
109
jediPreview = False
1110

1211
class RedirectStdout(object):
@@ -111,8 +110,6 @@ def _get_call_signatures(self, script):
111110
continue
112111
if param.name == 'self' and pos == 0:
113112
continue
114-
if WORD_RE.match(param.name) is None:
115-
continue
116113
try:
117114
name, value = param.description.split('=')
118115
except ValueError:
@@ -155,8 +152,6 @@ def _get_call_signatures_with_args(self, script):
155152
continue
156153
if param.name == 'self' and pos == 0:
157154
continue
158-
if WORD_RE.match(param.name) is None:
159-
continue
160155
try:
161156
name, value = param.description.split('=')
162157
except ValueError:

src/client/common/utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -340,8 +340,8 @@ export function getSubDirectories(rootDir: string): Promise<string[]> {
340340
subDirs.push(fullPath);
341341
}
342342
}
343-
catch (ex) {
344-
}
343+
// tslint:disable-next-line:no-empty
344+
catch (ex) {}
345345
});
346346
resolve(subDirs);
347347
});

src/client/providers/signatureProvider.ts

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use strict';
22

3+
import { EOL } from 'os';
34
import * as vscode from 'vscode';
45
import { CancellationToken, Position, SignatureHelp, TextDocument } from 'vscode';
56
import { JediFactory } from '../languageServices/jediProxyFactory';
@@ -53,21 +54,40 @@ export class PythonSignatureProvider implements vscode.SignatureHelpProvider {
5354

5455
data.definitions.forEach(def => {
5556
signature.activeParameter = def.paramindex;
56-
// Don't display the documentation, as vs code doesn't format the docmentation.
57+
// Don't display the documentation, as vs code doesn't format the documentation.
5758
// i.e. line feeds are not respected, long content is stripped.
59+
60+
// Some functions do not come with parameter docs
61+
let label: string;
62+
let documentation: string;
63+
const validParamInfo = def.params && def.params.length > 0 && def.docstring.startsWith(`${def.name}(`);
64+
65+
if (validParamInfo) {
66+
const docLines = def.docstring.splitLines();
67+
label = docLines.shift().trim();
68+
documentation = docLines.join(EOL).trim();
69+
} else {
70+
label = def.description;
71+
documentation = def.docstring;
72+
}
73+
5874
const sig = <vscode.SignatureInformation>{
59-
label: def.description,
75+
label,
76+
documentation,
6077
parameters: []
6178
};
62-
sig.parameters = def.params.map(arg => {
63-
if (arg.docstring.length === 0) {
64-
arg.docstring = extractParamDocString(arg.name, def.docstring);
65-
}
66-
return <vscode.ParameterInformation>{
67-
documentation: arg.docstring.length > 0 ? arg.docstring : arg.description,
68-
label: arg.description.length > 0 ? arg.description : arg.name
69-
};
70-
});
79+
80+
if (validParamInfo) {
81+
sig.parameters = def.params.map(arg => {
82+
if (arg.docstring.length === 0) {
83+
arg.docstring = extractParamDocString(arg.name, def.docstring);
84+
}
85+
return <vscode.ParameterInformation>{
86+
documentation: arg.docstring.length > 0 ? arg.docstring : arg.description,
87+
label: arg.name.trim()
88+
};
89+
});
90+
}
7191
signature.signatures.push(sig);
7292
});
7393
return signature;
@@ -85,7 +105,7 @@ export class PythonSignatureProvider implements vscode.SignatureHelpProvider {
85105
source: document.getText()
86106
};
87107
return this.jediFactory.getJediProxyHandler<proxy.IArgumentsResult>(document.uri).sendCommand(cmd, token).then(data => {
88-
return PythonSignatureProvider.parseData(data);
108+
return data ? PythonSignatureProvider.parseData(data) : new SignatureHelp();
89109
});
90110
}
91111
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
range(c, 1,
2+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class Person:
2+
def __init__(self, name, age = 23):
3+
self.name = name
4+
self.age = age
5+
6+
p1 = Person('Bob', )
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
print(a, b, c)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pow()

src/test/signature/signature.test.ts

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
'use strict';
4+
5+
import * as assert from 'assert';
6+
import * as path from 'path';
7+
import * as vscode from 'vscode';
8+
import { PythonSettings } from '../../client/common/configSettings';
9+
import { execPythonFile } from '../../client/common/utils';
10+
import { rootWorkspaceUri } from '../common';
11+
import { closeActiveWindows, initialize, initializeTest } from '../initialize';
12+
13+
const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'signature');
14+
15+
class SignatureHelpResult {
16+
constructor(
17+
public line: number,
18+
public index: number,
19+
public signaturesCount: number,
20+
public activeParameter: number,
21+
public parameterName: string | null) { }
22+
}
23+
24+
// tslint:disable-next-line:max-func-body-length
25+
suite('Signatures', () => {
26+
let isPython3: Promise<boolean>;
27+
suiteSetup(async () => {
28+
await initialize();
29+
const version = await execPythonFile(rootWorkspaceUri, PythonSettings.getInstance(rootWorkspaceUri).pythonPath, ['--version'], __dirname, true);
30+
isPython3 = Promise.resolve(version.indexOf('3.') >= 0);
31+
});
32+
setup(initializeTest);
33+
suiteTeardown(closeActiveWindows);
34+
teardown(closeActiveWindows);
35+
36+
test('For ctor', async () => {
37+
const expected = [
38+
new SignatureHelpResult(5, 11, 0, 0, null),
39+
new SignatureHelpResult(5, 12, 1, 0, 'name'),
40+
new SignatureHelpResult(5, 13, 1, 0, 'name'),
41+
new SignatureHelpResult(5, 14, 1, 0, 'name'),
42+
new SignatureHelpResult(5, 15, 1, 0, 'name'),
43+
new SignatureHelpResult(5, 16, 1, 0, 'name'),
44+
new SignatureHelpResult(5, 17, 1, 0, 'name'),
45+
new SignatureHelpResult(5, 18, 1, 1, 'age'),
46+
new SignatureHelpResult(5, 19, 1, 1, 'age'),
47+
new SignatureHelpResult(5, 20, 0, 0, null)
48+
];
49+
50+
const document = await openDocument(path.join(autoCompPath, 'classCtor.py'));
51+
for (let i = 0; i < expected.length; i += 1) {
52+
await checkSignature(expected[i], document!.uri, i);
53+
}
54+
});
55+
56+
test('For intrinsic', async () => {
57+
const expected = [
58+
new SignatureHelpResult(0, 0, 0, 0, null),
59+
new SignatureHelpResult(0, 1, 0, 0, null),
60+
new SignatureHelpResult(0, 2, 0, 0, null),
61+
new SignatureHelpResult(0, 3, 0, 0, null),
62+
new SignatureHelpResult(0, 4, 0, 0, null),
63+
new SignatureHelpResult(0, 5, 0, 0, null),
64+
new SignatureHelpResult(0, 6, 1, 0, 'start'),
65+
new SignatureHelpResult(0, 7, 1, 0, 'start'),
66+
new SignatureHelpResult(0, 8, 1, 1, 'stop'),
67+
new SignatureHelpResult(0, 9, 1, 1, 'stop'),
68+
new SignatureHelpResult(0, 10, 1, 1, 'stop'),
69+
new SignatureHelpResult(0, 11, 1, 2, 'step'),
70+
new SignatureHelpResult(1, 0, 1, 2, 'step')
71+
];
72+
73+
const document = await openDocument(path.join(autoCompPath, 'basicSig.py'));
74+
for (let i = 0; i < expected.length; i += 1) {
75+
await checkSignature(expected[i], document!.uri, i);
76+
}
77+
});
78+
79+
test('For ellipsis', async () => {
80+
if (!await isPython3) {
81+
return;
82+
}
83+
const expected = [
84+
new SignatureHelpResult(0, 5, 0, 0, null),
85+
new SignatureHelpResult(0, 6, 1, 0, 'value'),
86+
new SignatureHelpResult(0, 7, 1, 0, 'value'),
87+
new SignatureHelpResult(0, 8, 1, 1, '...'),
88+
new SignatureHelpResult(0, 9, 1, 1, '...'),
89+
new SignatureHelpResult(0, 10, 1, 1, '...'),
90+
new SignatureHelpResult(0, 11, 1, 2, 'sep'),
91+
new SignatureHelpResult(0, 12, 1, 2, 'sep')
92+
];
93+
94+
const document = await openDocument(path.join(autoCompPath, 'ellipsis.py'));
95+
for (let i = 0; i < expected.length; i += 1) {
96+
await checkSignature(expected[i], document!.uri, i);
97+
}
98+
});
99+
100+
test('For pow', async () => {
101+
let expected: SignatureHelpResult;
102+
if (await isPython3) {
103+
expected = new SignatureHelpResult(0, 4, 1, 0, null);
104+
} else {
105+
expected = new SignatureHelpResult(0, 4, 1, 0, 'x');
106+
}
107+
108+
const document = await openDocument(path.join(autoCompPath, 'noSigPy3.py'));
109+
await checkSignature(expected, document!.uri, 0);
110+
});
111+
});
112+
113+
async function openDocument(documentPath: string): Promise<vscode.TextDocument | undefined> {
114+
const document = await vscode.workspace.openTextDocument(documentPath);
115+
await vscode.window.showTextDocument(document!);
116+
return document;
117+
}
118+
119+
async function checkSignature(expected: SignatureHelpResult, uri: vscode.Uri, caseIndex: number) {
120+
const position = new vscode.Position(expected.line, expected.index);
121+
const actual = await vscode.commands.executeCommand<vscode.SignatureHelp>('vscode.executeSignatureHelpProvider', uri, position);
122+
assert.equal(actual!.signatures.length, expected.signaturesCount, `Signature count does not match, case ${caseIndex}`);
123+
if (expected.signaturesCount > 0) {
124+
assert.equal(actual!.activeParameter, expected.activeParameter, `Parameter index does not match, case ${caseIndex}`);
125+
if (expected.parameterName) {
126+
const parameter = actual!.signatures[0].parameters[expected.activeParameter];
127+
assert.equal(parameter.label, expected.parameterName, `Parameter name is incorrect, case ${caseIndex}`);
128+
}
129+
}
130+
}

0 commit comments

Comments
 (0)