diff --git a/package.nls.ru.json b/package.nls.ru.json new file mode 100644 index 000000000000..e0e22abbc06d --- /dev/null +++ b/package.nls.ru.json @@ -0,0 +1,50 @@ +{ + "python.command.python.sortImports.title": "Отсортировать Imports", + "python.command.python.startREPL.title": "Открыть REPL", + "python.command.python.buildWorkspaceSymbols.title": "Собрать символы рабочего пространства", + "python.command.python.runtests.title": "Запустить все тесты", + "python.command.python.debugtests.title": "Запустить все тесты под отладчиком", + "python.command.python.execInTerminal.title": "Выполнить файл в консоли", + "python.command.python.setInterpreter.title": "Выбрать интерпретатор", + "python.command.python.updateSparkLibrary.title": "Обновить библиотеки PySpark", + "python.command.python.refactorExtractVariable.title": "Извлечь в переменную", + "python.command.python.refactorExtractMethod.title": "Извлечь в метод", + "python.command.python.viewTestOutput.title": "Показать вывод теста", + "python.command.python.selectAndRunTestMethod.title": "Запусть тестовый метод...", + "python.command.python.selectAndDebugTestMethod.title": "Отладить тестовый метод...", + "python.command.python.selectAndRunTestFile.title": "Запустить тестовый файл...", + "python.command.python.runCurrentTestFile.title": "Запустить текущий тестовый файл", + "python.command.python.runFailedTests.title": "Запустить непрошедшие тесты", + "python.command.python.execSelectionInTerminal.title": "Выполнить выбранный текст или текущую строку в консоли", + "python.command.python.execSelectionInDjangoShell.title": "Выполнить выбранный текст или текущую строку в оболочке Django", + "python.command.jupyter.runSelectionLine.title": "Выполнить выбранный текст или текущую строку", + "python.command.jupyter.execCurrentCell.title": "Выполнить ячейку", + "python.command.jupyter.execCurrentCellAndAdvance.title": "Выполнить ячейку и перейти к следующей", + "python.command.jupyter.gotToPreviousCell.title": "Перейти к предыдущей ячейке", + "python.command.jupyter.gotToNextCell.title": "Перейти к следующей ячейке", + "python.command.python.goToPythonObject.title": "Перейти к объекту Python", + "python.snippet.launch.standard.label": "Python", + "python.snippet.launch.standard.description": "Отладить программу Python со стандартным выводом", + "python.snippet.launch.pyspark.label": "Python: PySpark", + "python.snippet.launch.pyspark.description": "Отладка PySpark", + "python.snippet.launch.module.label": "Python: Модуль", + "python.snippet.launch.module.description": "Отладка модуля", + "python.snippet.launch.terminal.label": "Python: Интегрированная консоль", + "python.snippet.launch.terminal.description": "Отладка программы Python в интегрированной консоли", + "python.snippet.launch.externalTerminal.label": "Python: Внешний терминал", + "python.snippet.launch.externalTerminal.description": "Отладка программы Python во внешней консоли", + "python.snippet.launch.django.label": "Python: Django", + "python.snippet.launch.django.description": "Отладка приложения Django", + "python.snippet.launch.flask.label": "Python: Flask (0.11.x или новее)", + "python.snippet.launch.flask.description": "Отладка приложения Flask", + "python.snippet.launch.flaskOld.label": "Python: Flask (0.10.x или старее)", + "python.snippet.launch.flaskOld.description": "Отладка приложения Flask (старый стиль)", + "python.snippet.launch.pyramid.label": "Python: Приложение Pyramid", + "python.snippet.launch.pyramid.description": "Отладка приложения Pyramid", + "python.snippet.launch.watson.label": "Python: Приложение Watson", + "python.snippet.launch.watson.description": "Отладка приложения Watson", + "python.snippet.launch.attach.label": "Python: Подключить отладчик", + "python.snippet.launch.attach.description": "Подключить отладчик для удаленной отладки", + "python.snippet.launch.scrapy.label": "Python: Scrapy", + "python.snippet.launch.scrapy.description": "Scrapy в интегрированной консоли" +} diff --git a/pythonFiles/completion.py b/pythonFiles/completion.py index f072349d0999..ce798d246bab 100644 --- a/pythonFiles/completion.py +++ b/pythonFiles/completion.py @@ -6,7 +6,6 @@ import traceback import platform -WORD_RE = re.compile(r'\w') jediPreview = False class RedirectStdout(object): @@ -111,8 +110,6 @@ def _get_call_signatures(self, script): continue if param.name == 'self' and pos == 0: continue - if WORD_RE.match(param.name) is None: - continue try: name, value = param.description.split('=') except ValueError: @@ -155,8 +152,6 @@ def _get_call_signatures_with_args(self, script): continue if param.name == 'self' and pos == 0: continue - if WORD_RE.match(param.name) is None: - continue try: name, value = param.description.split('=') except ValueError: diff --git a/src/client/common/utils.ts b/src/client/common/utils.ts index 34cefb118342..25e9a720ca01 100644 --- a/src/client/common/utils.ts +++ b/src/client/common/utils.ts @@ -340,8 +340,8 @@ export function getSubDirectories(rootDir: string): Promise { subDirs.push(fullPath); } } - catch (ex) { - } + // tslint:disable-next-line:no-empty + catch (ex) {} }); resolve(subDirs); }); diff --git a/src/client/providers/signatureProvider.ts b/src/client/providers/signatureProvider.ts index bf6480a4d3a2..12dad261c39b 100644 --- a/src/client/providers/signatureProvider.ts +++ b/src/client/providers/signatureProvider.ts @@ -1,5 +1,6 @@ 'use strict'; +import { EOL } from 'os'; import * as vscode from 'vscode'; import { CancellationToken, Position, SignatureHelp, TextDocument } from 'vscode'; import { JediFactory } from '../languageServices/jediProxyFactory'; @@ -53,21 +54,40 @@ export class PythonSignatureProvider implements vscode.SignatureHelpProvider { data.definitions.forEach(def => { signature.activeParameter = def.paramindex; - // Don't display the documentation, as vs code doesn't format the docmentation. + // Don't display the documentation, as vs code doesn't format the documentation. // i.e. line feeds are not respected, long content is stripped. + + // Some functions do not come with parameter docs + let label: string; + let documentation: string; + const validParamInfo = def.params && def.params.length > 0 && def.docstring.startsWith(`${def.name}(`); + + if (validParamInfo) { + const docLines = def.docstring.splitLines(); + label = docLines.shift().trim(); + documentation = docLines.join(EOL).trim(); + } else { + label = def.description; + documentation = def.docstring; + } + const sig = { - label: def.description, + label, + documentation, parameters: [] }; - sig.parameters = def.params.map(arg => { - if (arg.docstring.length === 0) { - arg.docstring = extractParamDocString(arg.name, def.docstring); - } - return { - documentation: arg.docstring.length > 0 ? arg.docstring : arg.description, - label: arg.description.length > 0 ? arg.description : arg.name - }; - }); + + if (validParamInfo) { + sig.parameters = def.params.map(arg => { + if (arg.docstring.length === 0) { + arg.docstring = extractParamDocString(arg.name, def.docstring); + } + return { + documentation: arg.docstring.length > 0 ? arg.docstring : arg.description, + label: arg.name.trim() + }; + }); + } signature.signatures.push(sig); }); return signature; @@ -85,7 +105,7 @@ export class PythonSignatureProvider implements vscode.SignatureHelpProvider { source: document.getText() }; return this.jediFactory.getJediProxyHandler(document.uri).sendCommand(cmd, token).then(data => { - return PythonSignatureProvider.parseData(data); + return data ? PythonSignatureProvider.parseData(data) : new SignatureHelp(); }); } } diff --git a/src/test/pythonFiles/signature/basicSig.py b/src/test/pythonFiles/signature/basicSig.py new file mode 100644 index 000000000000..66ad4cbd0483 --- /dev/null +++ b/src/test/pythonFiles/signature/basicSig.py @@ -0,0 +1,2 @@ +range(c, 1, + diff --git a/src/test/pythonFiles/signature/classCtor.py b/src/test/pythonFiles/signature/classCtor.py new file mode 100644 index 000000000000..baa4045489e7 --- /dev/null +++ b/src/test/pythonFiles/signature/classCtor.py @@ -0,0 +1,6 @@ +class Person: + def __init__(self, name, age = 23): + self.name = name + self.age = age + +p1 = Person('Bob', ) diff --git a/src/test/pythonFiles/signature/ellipsis.py b/src/test/pythonFiles/signature/ellipsis.py new file mode 100644 index 000000000000..c34faa6d231a --- /dev/null +++ b/src/test/pythonFiles/signature/ellipsis.py @@ -0,0 +1 @@ +print(a, b, c) diff --git a/src/test/pythonFiles/signature/noSigPy3.py b/src/test/pythonFiles/signature/noSigPy3.py new file mode 100644 index 000000000000..3d814698b7fe --- /dev/null +++ b/src/test/pythonFiles/signature/noSigPy3.py @@ -0,0 +1 @@ +pow() diff --git a/src/test/signature/signature.test.ts b/src/test/signature/signature.test.ts new file mode 100644 index 000000000000..b42ecd115b16 --- /dev/null +++ b/src/test/signature/signature.test.ts @@ -0,0 +1,130 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +'use strict'; + +import * as assert from 'assert'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { PythonSettings } from '../../client/common/configSettings'; +import { execPythonFile } from '../../client/common/utils'; +import { rootWorkspaceUri } from '../common'; +import { closeActiveWindows, initialize, initializeTest } from '../initialize'; + +const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'signature'); + +class SignatureHelpResult { + constructor( + public line: number, + public index: number, + public signaturesCount: number, + public activeParameter: number, + public parameterName: string | null) { } +} + +// tslint:disable-next-line:max-func-body-length +suite('Signatures', () => { + let isPython3: Promise; + suiteSetup(async () => { + await initialize(); + const version = await execPythonFile(rootWorkspaceUri, PythonSettings.getInstance(rootWorkspaceUri).pythonPath, ['--version'], __dirname, true); + isPython3 = Promise.resolve(version.indexOf('3.') >= 0); + }); + setup(initializeTest); + suiteTeardown(closeActiveWindows); + teardown(closeActiveWindows); + + test('For ctor', async () => { + const expected = [ + new SignatureHelpResult(5, 11, 0, 0, null), + new SignatureHelpResult(5, 12, 1, 0, 'name'), + new SignatureHelpResult(5, 13, 1, 0, 'name'), + new SignatureHelpResult(5, 14, 1, 0, 'name'), + new SignatureHelpResult(5, 15, 1, 0, 'name'), + new SignatureHelpResult(5, 16, 1, 0, 'name'), + new SignatureHelpResult(5, 17, 1, 0, 'name'), + new SignatureHelpResult(5, 18, 1, 1, 'age'), + new SignatureHelpResult(5, 19, 1, 1, 'age'), + new SignatureHelpResult(5, 20, 0, 0, null) + ]; + + const document = await openDocument(path.join(autoCompPath, 'classCtor.py')); + for (let i = 0; i < expected.length; i += 1) { + await checkSignature(expected[i], document!.uri, i); + } + }); + + test('For intrinsic', async () => { + const expected = [ + new SignatureHelpResult(0, 0, 0, 0, null), + new SignatureHelpResult(0, 1, 0, 0, null), + new SignatureHelpResult(0, 2, 0, 0, null), + new SignatureHelpResult(0, 3, 0, 0, null), + new SignatureHelpResult(0, 4, 0, 0, null), + new SignatureHelpResult(0, 5, 0, 0, null), + new SignatureHelpResult(0, 6, 1, 0, 'start'), + new SignatureHelpResult(0, 7, 1, 0, 'start'), + new SignatureHelpResult(0, 8, 1, 1, 'stop'), + new SignatureHelpResult(0, 9, 1, 1, 'stop'), + new SignatureHelpResult(0, 10, 1, 1, 'stop'), + new SignatureHelpResult(0, 11, 1, 2, 'step'), + new SignatureHelpResult(1, 0, 1, 2, 'step') + ]; + + const document = await openDocument(path.join(autoCompPath, 'basicSig.py')); + for (let i = 0; i < expected.length; i += 1) { + await checkSignature(expected[i], document!.uri, i); + } + }); + + test('For ellipsis', async () => { + if (!await isPython3) { + return; + } + const expected = [ + new SignatureHelpResult(0, 5, 0, 0, null), + new SignatureHelpResult(0, 6, 1, 0, 'value'), + new SignatureHelpResult(0, 7, 1, 0, 'value'), + new SignatureHelpResult(0, 8, 1, 1, '...'), + new SignatureHelpResult(0, 9, 1, 1, '...'), + new SignatureHelpResult(0, 10, 1, 1, '...'), + new SignatureHelpResult(0, 11, 1, 2, 'sep'), + new SignatureHelpResult(0, 12, 1, 2, 'sep') + ]; + + const document = await openDocument(path.join(autoCompPath, 'ellipsis.py')); + for (let i = 0; i < expected.length; i += 1) { + await checkSignature(expected[i], document!.uri, i); + } + }); + + test('For pow', async () => { + let expected: SignatureHelpResult; + if (await isPython3) { + expected = new SignatureHelpResult(0, 4, 1, 0, null); + } else { + expected = new SignatureHelpResult(0, 4, 1, 0, 'x'); + } + + const document = await openDocument(path.join(autoCompPath, 'noSigPy3.py')); + await checkSignature(expected, document!.uri, 0); + }); +}); + +async function openDocument(documentPath: string): Promise { + const document = await vscode.workspace.openTextDocument(documentPath); + await vscode.window.showTextDocument(document!); + return document; +} + +async function checkSignature(expected: SignatureHelpResult, uri: vscode.Uri, caseIndex: number) { + const position = new vscode.Position(expected.line, expected.index); + const actual = await vscode.commands.executeCommand('vscode.executeSignatureHelpProvider', uri, position); + assert.equal(actual!.signatures.length, expected.signaturesCount, `Signature count does not match, case ${caseIndex}`); + if (expected.signaturesCount > 0) { + assert.equal(actual!.activeParameter, expected.activeParameter, `Parameter index does not match, case ${caseIndex}`); + if (expected.parameterName) { + const parameter = actual!.signatures[0].parameters[expected.activeParameter]; + assert.equal(parameter.label, expected.parameterName, `Parameter name is incorrect, case ${caseIndex}`); + } + } +}