Skip to content

Commit f4d1733

Browse files
committed
fixed #436
1 parent 55a6129 commit f4d1733

6 files changed

Lines changed: 2061 additions & 21 deletions

File tree

pythonFiles/completion.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ def __init__(self):
2626

2727
def _get_definition_type(self, definition):
2828
is_built_in = definition.in_builtin_module
29-
if definition.type not in ['import', 'keyword'] and is_built_in():
30-
return 'builtin'
29+
#if definition.type not in ['import', 'keyword'] and is_built_in():
30+
# return 'builtin'
3131
if definition.type in ['statement'] and definition.name.isupper():
3232
return 'constant'
3333
return self.basic_types.get(definition.type, definition.type)
@@ -111,6 +111,7 @@ def _get_call_signatures_with_args(self, script):
111111
"paramindex": 0, "params": [], "bracketstart": []}
112112
sig["description"] = signature.description
113113
sig["docstring"] = signature.docstring()
114+
sig["raw_docstring"] = signature.docstring(raw=True)
114115
sig["name"] = signature.name
115116
sig["paramindex"] = signature.index
116117
sig["bracketstart"].append(signature.index)
@@ -154,6 +155,7 @@ def _serialize_completions(self, script, identifier=None, prefix=''):
154155
continue
155156
_completion = {
156157
'type': 'property',
158+
'raw_type':'',
157159
'rightLabel': self._additional_info(signature)
158160
}
159161
# we pass 'text' here only for fuzzy matcher
@@ -166,6 +168,7 @@ def _serialize_completions(self, script, identifier=None, prefix=''):
166168
_completion['displayText'] = name
167169
if self.show_doc_strings:
168170
_completion['description'] = signature.docstring()
171+
_completion['raw_docstring'] = signature.docstring(raw=True)
169172
else:
170173
_completion['description'] = self._generate_signature(
171174
signature)
@@ -183,7 +186,9 @@ def _serialize_completions(self, script, identifier=None, prefix=''):
183186
_completion = {
184187
'text': completion.name,
185188
'type': self._get_definition_type(completion),
189+
'raw_type': completion.type,
186190
'description': description,
191+
'raw_docstring': completion.docstring(raw=True),
187192
'rightLabel': self._additional_info(completion)
188193
}
189194
if any([c['text'].split('=')[0] == _completion['text']
@@ -236,6 +241,7 @@ def _top_definition(definition):
236241
_definition = {
237242
'text': definition.name,
238243
'type': self._get_definition_type(definition),
244+
'raw_type': definition.type,
239245
'fileName': definition.module_path,
240246
'line': definition.line - 1,
241247
'column': definition.column

src/client/providers/hoverProvider.ts

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,48 @@ export class PythonHoverProvider implements vscode.HoverProvider {
5454
if (wordUnderCursor === txt) {
5555
return;
5656
}
57-
const lines = txt.split(EOL);
58-
if (lines.length > 2 && lines[1].trim().length === 0) {
59-
const line1 = lines[0];
60-
lines.shift();
61-
return new vscode.Hover([{ language: 'python', value: line1 }, lines.join(EOL)]);
62-
}
63-
return new vscode.Hover(txt);
57+
58+
return extractHoverInfo(definition);
6459
});
6560
}
6661
}
62+
63+
function extractHoverInfo(definition: proxy.IAutoCompleteItem): vscode.Hover {
64+
// Somtimes the signature of the function, class (whatever) is broken into multiple lines
65+
// Here's an example
66+
// ```python
67+
// def __init__(self, group=None, target=None, name=None,
68+
// args=(), kwargs=None, verbose=None):
69+
// """This constructor should always be called with keyword arguments. Arguments are:
70+
71+
// *group* should be None; reserved for future extension when a ThreadGroup
72+
// class is implemented.
73+
/// """
74+
/// ```
75+
const txt = definition.description || definition.text;
76+
const rawDocString = typeof definition.raw_docstring === 'string' ? definition.raw_docstring.trim() : '';
77+
const firstLineOfRawDocString = rawDocString.length > 0 ? rawDocString.split(EOL)[0] : '';
78+
const lines = txt.split(EOL);
79+
const startIndexOfDocString = lines.findIndex(line => line.indexOf(firstLineOfRawDocString) === 0);
80+
81+
let signatureLines = startIndexOfDocString === -1 ? [lines.shift()] : lines.splice(0, startIndexOfDocString);
82+
let signature = signatureLines.filter(line=>line.trim().length > 0).join(EOL);
83+
84+
switch (definition.type) {
85+
case vscode.CompletionItemKind.Constructor:
86+
case vscode.CompletionItemKind.Function:
87+
case vscode.CompletionItemKind.Method: {
88+
signature = 'def ' + signature;
89+
break;
90+
}
91+
case vscode.CompletionItemKind.Class: {
92+
signature = 'class ' + signature;
93+
break;
94+
}
95+
}
96+
const hoverInfo: vscode.MarkedString[] = [{ language: 'python', value: signature }];
97+
if (lines.some(line => line.trim().length > 0)) {
98+
hoverInfo.push(lines.join(EOL));
99+
}
100+
return new vscode.Hover(hoverInfo);
101+
}

src/client/providers/jediProxy.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -222,9 +222,9 @@ function spawnProcess(dir: string) {
222222
catch (ex) {
223223
// Possible we've only received part of the data, hence don't clear previousData
224224
// Don't log errors when we haven't received the entire response
225-
if (ex.message.indexOf('Unexpected end of input') === -1 &&
226-
ex.message.indexOf('Unexpected end of JSON input') === -1 &&
227-
ex.message.indexOf('Unexpected token d in JSON at position') === -1) {
225+
if (ex.message.indexOf('Unexpected end of input') === -1 &&
226+
ex.message.indexOf('Unexpected end of JSON input') === -1 &&
227+
ex.message.indexOf('Unexpected token') === -1) {
228228
handleError("stdout", ex.message);
229229
}
230230
return;
@@ -269,6 +269,7 @@ function spawnProcess(dir: string) {
269269
const originalType = <string><any>item.type;
270270
item.type = getMappedVSCodeType(originalType);
271271
item.kind = getMappedVSCodeSymbol(originalType);
272+
item.raw_type = getMappedVSCodeType(originalType);
272273
});
273274

274275
let completionResult: ICompletionResult = {
@@ -557,9 +558,11 @@ export interface IReference {
557558

558559
export interface IAutoCompleteItem {
559560
type: vscode.CompletionItemKind;
561+
raw_type: vscode.CompletionItemKind;
560562
kind: vscode.SymbolKind;
561563
text: string;
562564
description: string;
565+
raw_docstring: string;
563566
rightLabel: string;
564567
}
565568
export interface IDefinition {

src/test/extension.autocomplete.test.ts

Lines changed: 92 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -235,8 +235,8 @@ suite('Hover Definition', () => {
235235
assert.equal(`${def[0].range.start.line},${def[0].range.start.character}`, '30,4', 'Start position is incorrect');
236236
assert.equal(`${def[0].range.end.line},${def[0].range.end.character}`, '30,11', 'End position is incorrect');
237237
assert.equal(def[0].contents.length, 2, 'Invalid content items');
238-
assert.equal(def[0].contents[0].value, 'method1(self)', 'function signature incorrect');
239-
assert.equal(def[0].contents[1], `${EOL}This is method1`, 'Invalid conents');
238+
assert.equal(def[0].contents[0].value, 'def method1(self)', 'function signature incorrect');
239+
assert.equal(def[0].contents[1], `This is method1`, 'Invalid conents');
240240
}).then(done, done);
241241
});
242242

@@ -255,8 +255,8 @@ suite('Hover Definition', () => {
255255
assert.equal(def.length, 1, 'Definition lenght is incorrect');
256256
assert.equal(`${def[0].range.start.line},${def[0].range.start.character}`, '1,9', 'Start position is incorrect');
257257
assert.equal(`${def[0].range.end.line},${def[0].range.end.character}`, '1,12', 'End position is incorrect');
258-
assert.equal(def[0].contents[0].value, 'fun()', 'function signature incorrect');
259-
assert.equal(def[0].contents[1], `${EOL}This is fun`, 'Invalid conents');
258+
assert.equal(def[0].contents[0].value, 'def fun()', 'function signature incorrect');
259+
assert.equal(def[0].contents[1], `This is fun`, 'Invalid conents');
260260
}).then(done, done);
261261
});
262262

@@ -275,8 +275,8 @@ suite('Hover Definition', () => {
275275
assert.equal(def.length, 1, 'Definition lenght is incorrect');
276276
assert.equal(`${def[0].range.start.line},${def[0].range.start.character}`, '25,4', 'Start position is incorrect');
277277
assert.equal(`${def[0].range.end.line},${def[0].range.end.character}`, '25,7', 'End position is incorrect');
278-
assert.equal(def[0].contents[0].value, 'bar()', 'function signature incorrect');
279-
const documentation = `${EOL}说明 - keep this line, it works${EOL}delete following line, it works${EOL}如果存在需要等待审批或正在执行的任务,将不刷新页面`;
278+
assert.equal(def[0].contents[0].value, 'def bar()', 'function signature incorrect');
279+
const documentation = `说明 - keep this line, it works${EOL}delete following line, it works${EOL}如果存在需要等待审批或正在执行的任务,将不刷新页面`;
280280
assert.equal(def[0].contents[1], documentation, 'Invalid conents');
281281
}).then(done, done);
282282
});
@@ -296,8 +296,8 @@ suite('Hover Definition', () => {
296296
assert.equal(def.length, 1, 'Definition lenght is incorrect');
297297
assert.equal(`${def[0].range.start.line},${def[0].range.start.character}`, '1,5', 'Start position is incorrect');
298298
assert.equal(`${def[0].range.end.line},${def[0].range.end.character}`, '1,16', 'End position is incorrect');
299-
assert.equal(def[0].contents[0].value, 'showMessage()', 'Invalid content items');
300-
const documentation = `${EOL}Кюм ут жэмпэр пошжим льаборэж, коммюны янтэрэсщэт нам ед, декта игнота ныморэ жят эи. ${EOL}Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку.`;
299+
assert.equal(def[0].contents[0].value, 'def showMessage()', 'Invalid content items');
300+
const documentation = `Кюм ут жэмпэр пошжим льаборэж, коммюны янтэрэсщэт нам ед, декта игнота ныморэ жят эи. ${EOL}Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку.`;
301301
assert.equal(def[0].contents[1], documentation, 'Invalid conents');
302302
}).then(done, done);
303303
});
@@ -333,4 +333,88 @@ suite('Hover Definition', () => {
333333
assert.equal(def.length, 0, 'Definition lenght is incorrect');
334334
}).then(done, done);
335335
});
336+
337+
test('Highlighting Class', done => {
338+
let textEditor: vscode.TextEditor;
339+
let textDocument: vscode.TextDocument;
340+
return vscode.workspace.openTextDocument(fileHover).then(document => {
341+
textDocument = document;
342+
return vscode.window.showTextDocument(textDocument);
343+
}).then(editor => {
344+
assert(vscode.window.activeTextEditor, 'No active editor');
345+
textEditor = editor;
346+
const position = new vscode.Position(11, 15);
347+
return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position);
348+
}).then((def: [{ range: vscode.Range, contents: { language: string, value: string }[] }]) => {
349+
assert.equal(def.length, 1, 'Definition lenght is incorrect');
350+
assert.equal(`${def[0].range.start.line},${def[0].range.start.character}`, '11,12', 'Start position is incorrect');
351+
assert.equal(`${def[0].range.end.line},${def[0].range.end.character}`, '11,18', 'End position is incorrect');
352+
assert.equal(def[0].contents[0].value, 'class Random(self, x=None)', 'Invalid content items');
353+
const documentation = `Random number generator base class used by bound module functions.${EOL}${EOL}` +
354+
`Used to instantiate instances of Random to get generators that don't${EOL}` +
355+
`share state.${EOL}${EOL}` +
356+
`Class Random can also be subclassed if you want to use a different basic${EOL}` +
357+
`generator of your own devising: in that case, override the following${EOL}` +
358+
`methods: random(), seed(), getstate(), and setstate().${EOL}` +
359+
`Optionally, implement a getrandbits() method so that randrange()${EOL}` +
360+
`can cover arbitrarily large ranges.`
361+
362+
assert.equal(def[0].contents[1], documentation, 'Invalid conents');
363+
}).then(done, done);
364+
});
365+
366+
test('Highlight Method', done => {
367+
let textEditor: vscode.TextEditor;
368+
let textDocument: vscode.TextDocument;
369+
return vscode.workspace.openTextDocument(fileHover).then(document => {
370+
textDocument = document;
371+
return vscode.window.showTextDocument(textDocument);
372+
}).then(editor => {
373+
assert(vscode.window.activeTextEditor, 'No active editor');
374+
textEditor = editor;
375+
const position = new vscode.Position(12, 10);
376+
return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position);
377+
}).then((def: [{ range: vscode.Range, contents: { language: string, value: string }[] }]) => {
378+
assert.equal(def.length, 1, 'Definition lenght is incorrect');
379+
assert.equal(`${def[0].range.start.line},${def[0].range.start.character}`, '12,5', 'Start position is incorrect');
380+
assert.equal(`${def[0].range.end.line},${def[0].range.end.character}`, '12,12', 'End position is incorrect');
381+
assert.equal(def[0].contents[0].value, 'def randint(self, a, b)', 'Invalid content items');
382+
const documentation = `Return random integer in range [a, b], including both end points.${EOL} `;
383+
assert.equal(def[0].contents[1], documentation, 'Invalid conents');
384+
}).then(done, done);
385+
});
386+
387+
test('Highlight Multiline Method Signature', done => {
388+
let textEditor: vscode.TextEditor;
389+
let textDocument: vscode.TextDocument;
390+
return vscode.workspace.openTextDocument(fileHover).then(document => {
391+
textDocument = document;
392+
return vscode.window.showTextDocument(textDocument);
393+
}).then(editor => {
394+
assert(vscode.window.activeTextEditor, 'No active editor');
395+
textEditor = editor;
396+
const position = new vscode.Position(15, 10);
397+
return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position);
398+
}).then((def: [{ range: vscode.Range, contents: { language: string, value: string }[] }]) => {
399+
assert.equal(def.length, 1, 'Definition lenght is incorrect');
400+
assert.equal(`${def[0].range.start.line},${def[0].range.start.character}`, '15,2', 'Start position is incorrect');
401+
assert.equal(`${def[0].range.end.line},${def[0].range.end.character}`, '15,10', 'End position is incorrect');
402+
const signature = `def __init__(self, group=None, target=None, name=None,${EOL}args=(), kwargs=None, verbose=None)`;
403+
assert.equal(def[0].contents[0].value, signature, 'Invalid content items');
404+
const documentation = `This constructor should always be called with keyword arguments. Arguments are:${EOL}${EOL}` +
405+
`*group* should be None; reserved for future extension when a ThreadGroup${EOL}` +
406+
`class is implemented.${EOL}${EOL}` +
407+
`*target* is the callable object to be invoked by the run()${EOL}` +
408+
`method. Defaults to None, meaning nothing is called.${EOL}${EOL}` +
409+
`*name* is the thread name. By default, a unique name is constructed of${EOL}` +
410+
`the form "Thread-N" where N is a small decimal number.${EOL}${EOL}` +
411+
`*args* is the argument tuple for the target invocation. Defaults to ().${EOL}${EOL}` +
412+
`*kwargs* is a dictionary of keyword arguments for the target${EOL}` +
413+
`invocation. Defaults to {}.${EOL}${EOL}` +
414+
`If a subclass overrides the constructor, it must make sure to invoke${EOL}` +
415+
`the base class constructor (Thread.__init__()) before doing anything${EOL}` +
416+
`else to the thread.`;
417+
assert.equal(def[0].contents[1], documentation, 'Invalid conents');
418+
}).then(done, done);
419+
});
336420
});

src/test/pythonFiles/autocomp/hoverTest.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,11 @@
66

77
rnd = random.Random()
88
print(rnd.randint(0,5))
9-
print(math.acos(90))
9+
print(math.acos(90))
10+
11+
import misc
12+
rnd2 = misc.Random()
13+
rnd2.randint()
14+
15+
t = misc.Thread()
16+
t.__init__()

0 commit comments

Comments
 (0)