From d7beb3b2ef4d05cdef1484f069761e6d144027c3 Mon Sep 17 00:00:00 2001 From: gwyllion1873 Date: Sat, 31 Aug 2019 21:58:33 +0200 Subject: [PATCH] First cut of refactorings chnages --- package.json | 71 ++++++--- package.nls.de.json | 3 + package.nls.es.json | 3 + package.nls.fr.json | 3 + package.nls.it.json | 3 + package.nls.ja.json | 3 + package.nls.json | 3 + package.nls.ko-kr.json | 3 + package.nls.nl.json | 3 + package.nls.pl.json | 9 +- package.nls.pt-br.json | 3 + package.nls.ru.json | 3 + package.nls.zh-cn.json | 3 + package.nls.zh-tw.json | 3 + pythonFiles/refactor.py | 136 +++++++++++++++++- src/client/common/constants.ts | 3 + .../providers/simpleRefactorProvider.ts | 102 +++++++++++++ src/client/refactor/proxy.ts | 51 +++++++ src/client/telemetry/constants.ts | 3 + src/client/telemetry/index.ts | 23 ++- 20 files changed, 410 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index 879f50f15088..4a7c93d396ef 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,8 @@ "name": "python", "displayName": "Python", "description": "Linting, Debugging (multi-threaded, remote), Intellisense, code formatting, refactoring, unit tests, snippets, and more.", - "version": "2019.10.0-dev", - "languageServerVersion": "0.3.66", + "version": "2019.9.0-dev", + "languageServerVersion": "0.3.43", "publisher": "ms-python", "author": { "name": "Microsoft Corporation" @@ -237,6 +237,21 @@ "title": "%python.command.python.refactorExtractMethod.title%", "category": "Python Refactor" }, + { + "command": "python.refactorUseFunction", + "title": "%python.command.python.refactorUseFunction.title%", + "category": "Python Refactor" + }, + { + "command": "python.refactorInline", + "title": "%python.command.python.refactorInline.title%", + "category": "Python Refactor" + }, + { + "command": "python.refactorLocalToField", + "title": "%python.command.python.refactorLocalToField.title%", + "category": "Python Refactor" + }, { "command": "python.viewTestOutput", "title": "%python.command.python.viewTestOutput.title%", @@ -527,6 +542,24 @@ "group": "Refactor", "when": "editorHasSelection && editorLangId == python" }, + { + "command": "python.refactorUseFunction", + "title": "Refactor: Use Function", + "group": "Refactor", + "when": "editorFocus && editorLangId == python" + }, + { + "command": "python.refactorInline", + "title": "Refactor: Inline", + "group": "Refactor", + "when": "editorFocus && editorLangId == python" + }, + { + "command": "python.refactorLocalToField", + "title": "Refactor: Local To Field", + "group": "Refactor", + "when": "editorFocus && editorLangId == python" + }, { "command": "python.sortImports", "title": "Refactor: Sort Imports", @@ -1669,10 +1702,15 @@ "description": "Whether to install Python modules globally when not using an environment.", "scope": "resource" }, - "python.jediEnabled": { - "type": "boolean", - "default": true, - "description": "Enables Jedi as IntelliSense engine instead of Microsoft Python Analysis Engine.", + "python.languageServer": { + "type": "string", + "enum": [ + "jedi", + "microsoft", + "none" + ], + "default": "jedi", + "description": "Specifies which language server to use for IntelliSense engine.", "scope": "resource" }, "python.jediMemoryLimit": { @@ -1936,8 +1974,7 @@ "description": "Path to mypy, you can use a custom version of mypy by modifying this setting to include the full path.", "scope": "resource" }, - - "python.linting.pycodestyleArgs": { + "python.linting.pep8Args": { "type": "array", "description": "Arguments passed in. Each argument is a separate item in the array.", "default": [], @@ -1946,10 +1983,10 @@ }, "scope": "resource" }, - "python.linting.pycodestyleCategorySeverity.E": { + "python.linting.pep8CategorySeverity.E": { "type": "string", "default": "Error", - "description": "Severity of pycodestyle message type 'E'.", + "description": "Severity of Pep8 message type 'E'.", "enum": [ "Hint", "Error", @@ -1958,10 +1995,10 @@ ], "scope": "resource" }, - "python.linting.pycodestyleCategorySeverity.W": { + "python.linting.pep8CategorySeverity.W": { "type": "string", "default": "Warning", - "description": "Severity of pycodestyle message type 'W'.", + "description": "Severity of Pep8 message type 'W'.", "enum": [ "Hint", "Error", @@ -1970,16 +2007,16 @@ ], "scope": "resource" }, - "python.linting.pycodestyleEnabled": { + "python.linting.pep8Enabled": { "type": "boolean", "default": false, - "description": "Whether to lint Python files using pycodestyle", + "description": "Whether to lint Python files using pep8", "scope": "resource" }, - "python.linting.pycodestylePath": { + "python.linting.pep8Path": { "type": "string", - "default": "pycodestyle", - "description": "Path to pycodestyle, you can use a custom version of pycodestyle by modifying this setting to include the full path.", + "default": "pep8", + "description": "Path to pep8, you can use a custom version of pep8 by modifying this setting to include the full path.", "scope": "resource" }, "python.linting.prospectorArgs": { diff --git a/package.nls.de.json b/package.nls.de.json index 63bf670ea03c..e1991990129b 100644 --- a/package.nls.de.json +++ b/package.nls.de.json @@ -10,6 +10,9 @@ "python.command.python.updateSparkLibrary.title": "PySpark Arbeitsplatz-Bibliotheken aktualisieren", "python.command.python.refactorExtractVariable.title": "Variable extrahieren", "python.command.python.refactorExtractMethod.title": "Methode extrahieren", + "python.command.python.refactorUseFunction.title": "Use Function", + "python.command.python.refactorInline.title": "Inline", + "python.command.python.refactorLocalToField.title": "Local To Field", "python.command.python.viewTestOutput.title": "Unittest-Ausgabe anzeigen", "python.command.python.selectAndRunTestMethod.title": "Unittest-Methode ausführen ...", "python.command.python.selectAndDebugTestMethod.title": "Unittest-Debug-Methode ausführen ...", diff --git a/package.nls.es.json b/package.nls.es.json index 3117226a4c34..95490c3b732f 100644 --- a/package.nls.es.json +++ b/package.nls.es.json @@ -10,6 +10,9 @@ "python.command.python.updateSparkLibrary.title": "Actualizar las librerías PySpark del area de trabajo", "python.command.python.refactorExtractVariable.title": "Extraer variable", "python.command.python.refactorExtractMethod.title": "Extraer método", + "python.command.python.refactorUseFunction.title": "Use Function", + "python.command.python.refactorInline.title": "Inline", + "python.command.python.refactorLocalToField.title": "Local To Field", "python.command.python.viewTestOutput.title": "Mostrar resultados de la prueba unitaria", "python.command.python.selectAndRunTestMethod.title": "Método de ejecución de pruebas unitarias ...", "python.command.python.selectAndDebugTestMethod.title": "Método de depuración de pruebas unitarias ...", diff --git a/package.nls.fr.json b/package.nls.fr.json index 14e4cea23758..76d018b36173 100644 --- a/package.nls.fr.json +++ b/package.nls.fr.json @@ -10,6 +10,9 @@ "python.command.python.updateSparkLibrary.title": "Mettre à jour les librairies de l'espace de travail PySpark", "python.command.python.refactorExtractVariable.title": "Extraire la variable", "python.command.python.refactorExtractMethod.title": "Extraire la méthode", + "python.command.python.refactorUseFunction.title": "Use Function", + "python.command.python.refactorInline.title": "Inline", + "python.command.python.refactorLocalToField.title": "Local To Field", "python.command.python.viewTestOutput.title": "Afficher la sortie des tests unitaires", "python.command.python.selectAndRunTestMethod.title": "Exécuter la méthode de test unitaire ...", "python.command.python.selectAndDebugTestMethod.title": "Déboguer la méthode de test unitaire ...", diff --git a/package.nls.it.json b/package.nls.it.json index 75888a537ee8..e2895e4cc3fa 100644 --- a/package.nls.it.json +++ b/package.nls.it.json @@ -10,6 +10,9 @@ "python.command.python.updateSparkLibrary.title": "Aggiorna librerie PySpark dello spazio di lavoro", "python.command.python.refactorExtractVariable.title": "Estrai variable", "python.command.python.refactorExtractMethod.title": "Estrai metodo", + "python.command.python.refactorUseFunction.title": "Use Function", + "python.command.python.refactorInline.title": "Inline", + "python.command.python.refactorLocalToField.title": "Local To Field", "python.command.python.viewTestOutput.title": "Mostra output dei test", "python.command.python.selectAndRunTestMethod.title": "Esegui metodo di test ...", "python.command.python.selectAndDebugTestMethod.title": "Esegui debug del metodo di test ...", diff --git a/package.nls.ja.json b/package.nls.ja.json index 9815d74cb093..e95527893f2f 100644 --- a/package.nls.ja.json +++ b/package.nls.ja.json @@ -9,6 +9,9 @@ "python.command.python.updateSparkLibrary.title": "ワークスペース PySpark ライブラリを更新", "python.command.python.refactorExtractVariable.title": "変数を抽出", "python.command.python.refactorExtractMethod.title": "メソッドを抽出", + "python.command.python.refactorUseFunction.title": "Use Function", + "python.command.python.refactorInline.title": "Inline", + "python.command.python.refactorLocalToField.title": "Local To Field", "python.command.python.viewTestOutput.title": "単体テストの出力を表示", "python.command.python.selectAndRunTestMethod.title": "単体テストメソッドを実行...", "python.command.python.selectAndDebugTestMethod.title": "単体テストメソッドをデバッグ...", diff --git a/package.nls.json b/package.nls.json index 98d92a320433..9668c3efa51a 100644 --- a/package.nls.json +++ b/package.nls.json @@ -13,6 +13,9 @@ "python.command.python.updateSparkLibrary.title": "Update Workspace PySpark Libraries", "python.command.python.refactorExtractVariable.title": "Extract Variable", "python.command.python.refactorExtractMethod.title": "Extract Method", + "python.command.python.refactorUseFunction.title": "Use Function", + "python.command.python.refactorInline.title": "Inline", + "python.command.python.refactorLocalToField.title": "Local To Field", "python.command.python.viewOutput.title": "Show Output", "python.command.python.viewTestOutput.title": "Show Test Output", "python.command.python.selectAndRunTestMethod.title": "Run Test Method ...", diff --git a/package.nls.ko-kr.json b/package.nls.ko-kr.json index b83de82bc4f5..0e3e5d1283d9 100644 --- a/package.nls.ko-kr.json +++ b/package.nls.ko-kr.json @@ -9,6 +9,9 @@ "python.command.python.updateSparkLibrary.title": "PySpark 작업 영역 라이브러리 업데이트", "python.command.python.refactorExtractVariable.title": "변수 추출", "python.command.python.refactorExtractMethod.title": "메서드 추출", + "python.command.python.refactorUseFunction.title": "Use Function", + "python.command.python.refactorInline.title": "Inline", + "python.command.python.refactorLocalToField.title": "Local To Field", "python.command.python.viewTestOutput.title": "단위 테스트 결과 보기", "python.command.python.selectAndRunTestMethod.title": "단위 테스트 메서드 실행 ...", "python.command.python.selectAndDebugTestMethod.title": "단위 테스트 메서드 디버그 ...", diff --git a/package.nls.nl.json b/package.nls.nl.json index a74fc17959dc..5c5a878bb482 100644 --- a/package.nls.nl.json +++ b/package.nls.nl.json @@ -10,6 +10,9 @@ "python.command.python.updateSparkLibrary.title": "Pyspark-werkruimtebibliotheken updaten", "python.command.python.refactorExtractVariable.title": "Variabelen selecteren", "python.command.python.refactorExtractMethod.title": "Methode selecteren", + "python.command.python.refactorUseFunction.title": "Use Function", + "python.command.python.refactorInline.title": "Inline", + "python.command.python.refactorLocalToField.title": "Local To Field", "python.command.python.viewTestOutput.title": "Unittest-resultaat laten zien", "python.command.python.selectAndRunTestMethod.title": "Unittest-methode uitvoeren ...", "python.command.python.selectAndDebugTestMethod.title": "Unittest-methode debuggen ...", diff --git a/package.nls.pl.json b/package.nls.pl.json index 0d9c2704edaa..7ad9d4b3f823 100644 --- a/package.nls.pl.json +++ b/package.nls.pl.json @@ -2,18 +2,21 @@ "python.command.python.sortImports.title": "Sortuj importy", "python.command.python.startREPL.title": "Uruchom REPL", "python.command.python.createTerminal.title": "Otwórz Terminal", - "python.command.python.buildWorkspaceSymbols.title": "Zbuduj symbole dla przestrzeni roboczej", + "python.command.python.buildWorkspaceSymbols.title": "Zbuduj symbole dla przestrzeni roboczej", "python.command.python.runtests.title": "Uruchom wszystkie testy jednostkowe", "python.command.python.debugtests.title": "Debuguj wszystkie testy jednostkowe", "python.command.python.execInTerminal.title": "Uruchom plik pythonowy w terminalu", "python.command.python.setInterpreter.title": "Wybierz wersję interpretera", - "python.command.python.updateSparkLibrary.title": "Zaktualizuj biblioteki w przestrzeni roboczej PySpark", + "python.command.python.updateSparkLibrary.title": "Zaktualizuj biblioteki w przestrzeni roboczej PySpark", "python.command.python.refactorExtractVariable.title": "Wyodrębnij zmienną", "python.command.python.refactorExtractMethod.title": "Wyodrębnij metodę", + "python.command.python.refactorUseFunction.title": "Use Function", + "python.command.python.refactorInline.title": "Inline", + "python.command.python.refactorLocalToField.title": "Local To Field", "python.command.python.viewOutput.title": "Pokaż wyniki", "python.command.python.viewTestOutput.title": "Pokaż wyniki testów jednostkowych", "python.command.python.selectAndRunTestMethod.title": "Uruchom metodę testów jednostkowych ...", - "python.command.python.selectAndDebugTestMethod.title": "Debuguj metodę testów jednostkowych ...", + "python.command.python.selectAndDebugTestMethod.title": "Debuguj metodę testów jednostkowych ...", "python.command.python.selectAndRunTestFile.title": "Uruchom plik z testami jednostkowymi ...", "python.command.python.runCurrentTestFile.title": "Uruchom bieżący plik z testami jednostkowymi", "python.command.python.runFailedTests.title": "Uruchom testy jednostkowe, które się nie powiodły", diff --git a/package.nls.pt-br.json b/package.nls.pt-br.json index cba1df0761b4..49f4f8c95f4f 100644 --- a/package.nls.pt-br.json +++ b/package.nls.pt-br.json @@ -10,6 +10,9 @@ "python.command.python.updateSparkLibrary.title": "Atualizar Área de Trabalho da Biblioteca PySpark", "python.command.python.refactorExtractVariable.title": "Extrair Variável", "python.command.python.refactorExtractMethod.title": "Extrair Método", + "python.command.python.refactorUseFunction.title": "Use Function", + "python.command.python.refactorInline.title": "Inline", + "python.command.python.refactorLocalToField.title": "Local To Field", "python.command.python.viewTestOutput.title": "Exibir Resultados dos Testes Unitários", "python.command.python.selectAndRunTestMethod.title": "Executar Testes Unitários do Método ...", "python.command.python.selectAndDebugTestMethod.title": "Depurar Testes Unitários do Método ...", diff --git a/package.nls.ru.json b/package.nls.ru.json index 95975e8b4e5f..90d25e695d2e 100644 --- a/package.nls.ru.json +++ b/package.nls.ru.json @@ -9,6 +9,9 @@ "python.command.python.updateSparkLibrary.title": "Обновить библиотеки PySpark", "python.command.python.refactorExtractVariable.title": "Извлечь в переменную", "python.command.python.refactorExtractMethod.title": "Извлечь в метод", + "python.command.python.refactorUseFunction.title": "Use Function", + "python.command.python.refactorInline.title": "Inline", + "python.command.python.refactorLocalToField.title": "Local To Field", "python.command.python.viewTestOutput.title": "Показать вывод теста", "python.command.python.selectAndRunTestMethod.title": "Запусть тестовый метод...", "python.command.python.selectAndDebugTestMethod.title": "Отладить тестовый метод...", diff --git a/package.nls.zh-cn.json b/package.nls.zh-cn.json index 983827d0ca6f..e32d88db30b3 100644 --- a/package.nls.zh-cn.json +++ b/package.nls.zh-cn.json @@ -10,6 +10,9 @@ "python.command.python.updateSparkLibrary.title": "更新工作区 PySpark 库", "python.command.python.refactorExtractVariable.title": "提取变量", "python.command.python.refactorExtractMethod.title": "提取方法", + "python.command.python.refactorUseFunction.title": "Use Function", + "python.command.python.refactorInline.title": "Inline", + "python.command.python.refactorLocalToField.title": "Local To Field", "python.command.python.viewTestOutput.title": "显示单元测试输出", "python.command.python.selectAndRunTestMethod.title": "运行单元测试方法...", "python.command.python.selectAndDebugTestMethod.title": "调试单元测试方法...", diff --git a/package.nls.zh-tw.json b/package.nls.zh-tw.json index 45e813c5fff2..a4616f1ce4e0 100644 --- a/package.nls.zh-tw.json +++ b/package.nls.zh-tw.json @@ -10,6 +10,9 @@ "python.command.python.updateSparkLibrary.title": "更新工作區 PySpark 函式庫", "python.command.python.refactorExtractVariable.title": "提取變數", "python.command.python.refactorExtractMethod.title": "提取方法", + "python.command.python.refactorUseFunction.title": "Use Function", + "python.command.python.refactorInline.title": "Inline", + "python.command.python.refactorLocalToField.title": "Local To Field", "python.command.python.viewTestOutput.title": "顯示單元測試輸出", "python.command.python.selectAndRunTestMethod.title": "執行單元測試方法…", "python.command.python.selectAndDebugTestMethod.title": "偵錯單元測試方法…", diff --git a/pythonFiles/refactor.py b/pythonFiles/refactor.py index d8def8a95df8..8087a50574fa 100644 --- a/pythonFiles/refactor.py +++ b/pythonFiles/refactor.py @@ -14,10 +14,14 @@ from rope.base import libutils from rope.refactor.rename import Rename from rope.refactor.extract import ExtractMethod, ExtractVariable + from rope.refactor.usefunction import UseFunction + from rope.refactor.inline import create_inline + from rope.refactor.localtofield import LocalToField import rope.base.project import rope.base.taskhandle except: - jsonMessage = {'error': True, 'message': 'Rope not installed', 'traceback': '', 'type': 'ModuleNotFoundError'} + jsonMessage = {'error': True, 'message': 'Rope not installed', + 'traceback': '', 'type': 'ModuleNotFoundError'} sys.stderr.write(json.dumps(jsonMessage)) sys.stderr.flush() @@ -57,6 +61,7 @@ def __init__(self, filePath, fileMode=ChangeType.EDIT, diff=""): self.diff = diff self.fileMode = fileMode + def get_diff(changeset): """This is a copy of the code form the ChangeSet.get_description method found in Rope.""" new = changeset.new_contents @@ -73,12 +78,13 @@ def get_diff(changeset): if not old_lines[-1].endswith('\n'): old_lines[-1] = old_lines[-1] + os.linesep new = new + os.linesep - + result = difflib.unified_diff( old_lines, new.splitlines(True), 'a/' + changeset.resource.path, 'b/' + changeset.resource.path) return ''.join(list(result)) + class BaseRefactoring(object): """ Base class for refactorings @@ -188,6 +194,68 @@ def onRefactor(self): raise Exception('Unknown Change') +class UseFunctionRefactor(BaseRefactoring): + """ This class implements the Use Function refactoring, but does it only on the current + resource (file). + """ + + def __init__(self, project, resource, name="Use Function", progressCallback=None, startOffset=None): + BaseRefactoring.__init__(self, project, resource, + name, progressCallback) + self._startOffset = startOffset + + def onRefactor(self): + renamed = UseFunction( + self.project, self.resource, self._startOffset) + changes = renamed.get_changes([renamed.resource], task_handle=self._handle) + for item in changes.changes: + if isinstance(item, rope.base.change.ChangeContents): + self.changes.append( + Change(item.resource.real_path, ChangeType.EDIT, get_diff(item))) + else: + raise Exception('Unknown Change') + + +class InlineRefactor(BaseRefactoring): + """ This class implements the Inline refactoring. + """ + + def __init__(self, project, resource, name="Inline", progressCallback=None, startOffset=None): + BaseRefactoring.__init__(self, project, resource, + name, progressCallback) + self._startOffset = startOffset + + def onRefactor(self): + renamed = create_inline(self.project, self.resource, self._startOffset) + changes = renamed.get_changes(resources=[self.resource], task_handle=self._handle) + for item in changes.changes: + if isinstance(item, rope.base.change.ChangeContents): + self.changes.append( + Change(item.resource.real_path, ChangeType.EDIT, get_diff(item))) + else: + raise Exception('Unknown Change') + + +class LocalToFieldRefactor(BaseRefactoring): + """ This class implements the Local To Field refactoring. + """ + + def __init__(self, project, resource, name="Local To Field", progressCallback=None, startOffset=None): + BaseRefactoring.__init__(self, project, resource, + name, progressCallback) + self._startOffset = startOffset + + def onRefactor(self): + renamed = LocalToField(self.project, self.resource, self._startOffset) + changes = renamed.get_changes() + for item in changes.changes: + if isinstance(item, rope.base.change.ChangeContents): + self.changes.append( + Change(item.resource.real_path, ChangeType.EDIT, get_diff(item))) + else: + raise Exception('Unknown Change') + + class RopeRefactoring(object): def __init__(self): @@ -245,6 +313,54 @@ def _extractMethod(self, filePath, start, end, newName, indent_size): valueToReturn.append({'diff': change.diff}) return valueToReturn + def _useFunction(self, filePath, start, indent_size): + """ + Use Function + """ + project = rope.base.project.Project( + WORKSPACE_ROOT, ropefolder=ROPE_PROJECT_FOLDER, save_history=False, indent_size=indent_size) + resourceToRefactor = libutils.path_to_resource(project, filePath) + refactor = UseFunctionRefactor(project, resourceToRefactor, startOffset=start) + refactor.refactor() + changes = refactor.changes + project.close() + valueToReturn = [] + for change in changes: + valueToReturn.append({'diff': change.diff}) + return valueToReturn + + def _inline(self, filePath, start, indent_size): + """ + Inline + """ + project = rope.base.project.Project( + WORKSPACE_ROOT, ropefolder=ROPE_PROJECT_FOLDER, save_history=False, indent_size=indent_size) + resourceToRefactor = libutils.path_to_resource(project, filePath) + refactor = InlineRefactor(project, resourceToRefactor, startOffset=start) + refactor.refactor() + changes = refactor.changes + project.close() + valueToReturn = [] + for change in changes: + valueToReturn.append({'diff': change.diff}) + return valueToReturn + + def _localToField(self, filePath, start, indent_size): + """ + Local To Field + """ + project = rope.base.project.Project( + WORKSPACE_ROOT, ropefolder=ROPE_PROJECT_FOLDER, save_history=False, indent_size=indent_size) + resourceToRefactor = libutils.path_to_resource(project, filePath) + refactor = LocalToFieldRefactor(project, resourceToRefactor, startOffset=start) + refactor.refactor() + changes = refactor.changes + project.close() + valueToReturn = [] + for change in changes: + valueToReturn.append({'diff': change.diff}) + return valueToReturn + def _serialize(self, identifier, results): """ Serializes the refactor results @@ -282,6 +398,18 @@ def _process_request(self, request): changes = self._extractMethod(request['file'], int( request['start']), int(request['end']), request['name'], int(request['indent_size'])) return self._write_response(self._serialize(request['id'], changes)) + elif lookup == 'use_function': + changes = self._useFunction(request['file'], int( + request['start']), int(request['indent_size'])) + return self._write_response(self._serialize(request['id'], changes)) + elif lookup == 'inline': + changes = self._inline(request['file'], int( + request['start']), int(request['indent_size'])) + return self._write_response(self._serialize(request['id'], changes)) + elif lookup == 'local_to_field': + changes = self._localToField(request['file'], int( + request['start']), int(request['indent_size'])) + return self._write_response(self._serialize(request['id'], changes)) def _write_response(self, response): sys.stdout.write(response + '\n') @@ -295,9 +423,11 @@ def watch(self): except: exc_type, exc_value, exc_tb = sys.exc_info() tb_info = traceback.extract_tb(exc_tb) - jsonMessage = {'error': True, 'message': str(exc_value), 'traceback': str(tb_info), 'type': str(exc_type)} + jsonMessage = {'error': True, 'message': str( + exc_value), 'traceback': str(tb_info), 'type': str(exc_type)} sys.stderr.write(json.dumps(jsonMessage)) sys.stderr.flush() + if __name__ == '__main__': RopeRefactoring().watch() diff --git a/src/client/common/constants.ts b/src/client/common/constants.ts index fce024edb363..7608b0430c61 100644 --- a/src/client/common/constants.ts +++ b/src/client/common/constants.ts @@ -42,6 +42,9 @@ export namespace Commands { export const Tests_Run_Current_File = 'python.runCurrentTestFile'; export const Refactor_Extract_Variable = 'python.refactorExtractVariable'; export const Refactor_Extract_Method = 'python.refactorExtractMethod'; + export const Refactor_Use_Function = 'python.refactorUseFunction'; + export const Refactor_Inline = 'python.refactorInline'; + export const Refactor_Local_To_Field = 'python.refactorLocalToField'; export const Update_SparkLibrary = 'python.updateSparkLibrary'; export const Build_Workspace_Symbols = 'python.buildWorkspaceSymbols'; export const Start_REPL = 'python.startREPL'; diff --git a/src/client/providers/simpleRefactorProvider.ts b/src/client/providers/simpleRefactorProvider.ts index 6fca847da6c4..941490e9c251 100644 --- a/src/client/providers/simpleRefactorProvider.ts +++ b/src/client/providers/simpleRefactorProvider.ts @@ -38,6 +38,39 @@ export function activateSimplePythonRefactorProvider(context: vscode.ExtensionCo sendTelemetryWhenDone(EventName.REFACTOR_EXTRACT_FUNCTION, promise, stopWatch); }); context.subscriptions.push(disposable); + + disposable = vscode.commands.registerCommand(Commands.Refactor_Use_Function, () => { + const stopWatch = new StopWatch(); + const promise = useFunction(context.extensionPath, + vscode.window.activeTextEditor!, + vscode.window.activeTextEditor!.selection, + // tslint:disable-next-line:no-empty + outputChannel, serviceContainer).catch(() => { }); + sendTelemetryWhenDone(EventName.REFACTOR_USE_FUNCTION, promise, stopWatch); + }); + context.subscriptions.push(disposable); + + disposable = vscode.commands.registerCommand(Commands.Refactor_Inline, () => { + const stopWatch = new StopWatch(); + const promise = inline(context.extensionPath, + vscode.window.activeTextEditor!, + vscode.window.activeTextEditor!.selection, + // tslint:disable-next-line:no-empty + outputChannel, serviceContainer).catch(() => { }); + sendTelemetryWhenDone(EventName.REFACTOR_INLINE, promise, stopWatch); + }); + context.subscriptions.push(disposable); + + disposable = vscode.commands.registerCommand(Commands.Refactor_Local_To_Field, () => { + const stopWatch = new StopWatch(); + const promise = localToField(context.extensionPath, + vscode.window.activeTextEditor!, + vscode.window.activeTextEditor!.selection, + // tslint:disable-next-line:no-empty + outputChannel, serviceContainer).catch(() => { }); + sendTelemetryWhenDone(EventName.REFACTOR_LOCAL_TO_FIELD, promise, stopWatch); + }); + context.subscriptions.push(disposable); } // Exported for unit testing @@ -86,6 +119,75 @@ export function extractMethod(extensionDir: string, textEditor: vscode.TextEdito }); } +// Exported for unit testing +export function useFunction(extensionDir: string, textEditor: vscode.TextEditor, range: vscode.Range, + // tslint:disable-next-line:no-any + outputChannel: vscode.OutputChannel, serviceContainer: IServiceContainer): Promise { + + let workspaceFolder = vscode.workspace.getWorkspaceFolder(textEditor.document.uri); + if (!workspaceFolder && Array.isArray(vscode.workspace.workspaceFolders) && vscode.workspace.workspaceFolders.length > 0) { + workspaceFolder = vscode.workspace.workspaceFolders[0]; + } + const workspaceRoot = workspaceFolder ? workspaceFolder.uri.fsPath : __dirname; + const pythonSettings = serviceContainer.get(IConfigurationService).getSettings(workspaceFolder ? workspaceFolder.uri : undefined); + + return validateDocumentForRefactor(textEditor).then(() => { + const newName = `newmethod${new Date().getMilliseconds().toString()}`; + const proxy = new RefactorProxy(extensionDir, pythonSettings, workspaceRoot, serviceContainer); + const rename = proxy.useFunction(textEditor.document, textEditor.document.uri.fsPath, range, textEditor.options).then(response => { + return response.results[0].diff; + }); + + return extractName(textEditor, newName, rename, outputChannel); + }); +} + +// Exported for unit testing +export function inline(extensionDir: string, textEditor: vscode.TextEditor, range: vscode.Range, + // tslint:disable-next-line:no-any + outputChannel: vscode.OutputChannel, serviceContainer: IServiceContainer): Promise { + + let workspaceFolder = vscode.workspace.getWorkspaceFolder(textEditor.document.uri); + if (!workspaceFolder && Array.isArray(vscode.workspace.workspaceFolders) && vscode.workspace.workspaceFolders.length > 0) { + workspaceFolder = vscode.workspace.workspaceFolders[0]; + } + const workspaceRoot = workspaceFolder ? workspaceFolder.uri.fsPath : __dirname; + const pythonSettings = serviceContainer.get(IConfigurationService).getSettings(workspaceFolder ? workspaceFolder.uri : undefined); + + return validateDocumentForRefactor(textEditor).then(() => { + const newName = `newmethod${new Date().getMilliseconds().toString()}`; + const proxy = new RefactorProxy(extensionDir, pythonSettings, workspaceRoot, serviceContainer); + const rename = proxy.inline(textEditor.document, textEditor.document.uri.fsPath, range, textEditor.options).then(response => { + return response.results[0].diff; + }); + + return extractName(textEditor, newName, rename, outputChannel); + }); +} + +// Exported for unit testing +export function localToField(extensionDir: string, textEditor: vscode.TextEditor, range: vscode.Range, + // tslint:disable-next-line:no-any + outputChannel: vscode.OutputChannel, serviceContainer: IServiceContainer): Promise { + + let workspaceFolder = vscode.workspace.getWorkspaceFolder(textEditor.document.uri); + if (!workspaceFolder && Array.isArray(vscode.workspace.workspaceFolders) && vscode.workspace.workspaceFolders.length > 0) { + workspaceFolder = vscode.workspace.workspaceFolders[0]; + } + const workspaceRoot = workspaceFolder ? workspaceFolder.uri.fsPath : __dirname; + const pythonSettings = serviceContainer.get(IConfigurationService).getSettings(workspaceFolder ? workspaceFolder.uri : undefined); + + return validateDocumentForRefactor(textEditor).then(() => { + const newName = `newmethod${new Date().getMilliseconds().toString()}`; + const proxy = new RefactorProxy(extensionDir, pythonSettings, workspaceRoot, serviceContainer); + const rename = proxy.localToField(textEditor.document, textEditor.document.uri.fsPath, range, textEditor.options).then(response => { + return response.results[0].diff; + }); + + return extractName(textEditor, newName, rename, outputChannel); + }); +} + // tslint:disable-next-line:no-any function validateDocumentForRefactor(textEditor: vscode.TextEditor): Promise { if (!textEditor.document.isDirty) { diff --git a/src/client/refactor/proxy.ts b/src/client/refactor/proxy.ts index 32034083e9b9..3de70ecbc23c 100644 --- a/src/client/refactor/proxy.ts +++ b/src/client/refactor/proxy.ts @@ -97,6 +97,57 @@ export class RefactorProxy extends Disposable { }; return this.sendCommand(JSON.stringify(command)); } + public useFunction(document: TextDocument, filePath: string, range: Range, options?: TextEditorOptions): Promise { + if (!options) { + options = window.activeTextEditor!.options; + } + // Ensure last line is an empty line + if (!document.lineAt(document.lineCount - 1).isEmptyOrWhitespace && range.start.line === document.lineCount - 1) { + return Promise.reject('Missing blank line at the end of document (PEP8).'); + } + const command = { + lookup: 'use_function', + file: filePath, + start: this.getOffsetAt(document, range.start).toString(), + id: '1', + indent_size: options.tabSize + }; + return this.sendCommand(JSON.stringify(command)); + } + public inline(document: TextDocument, filePath: string, range: Range, options?: TextEditorOptions): Promise { + if (!options) { + options = window.activeTextEditor!.options; + } + // Ensure last line is an empty line + if (!document.lineAt(document.lineCount - 1).isEmptyOrWhitespace && range.start.line === document.lineCount - 1) { + return Promise.reject('Missing blank line at the end of document (PEP8).'); + } + const command = { + lookup: 'inline', + file: filePath, + start: this.getOffsetAt(document, range.start).toString(), + id: '1', + indent_size: options.tabSize + }; + return this.sendCommand(JSON.stringify(command)); + } + public localToField(document: TextDocument, filePath: string, range: Range, options?: TextEditorOptions): Promise { + if (!options) { + options = window.activeTextEditor!.options; + } + // Ensure last line is an empty line + if (!document.lineAt(document.lineCount - 1).isEmptyOrWhitespace && range.start.line === document.lineCount - 1) { + return Promise.reject('Missing blank line at the end of document (PEP8).'); + } + const command = { + lookup: 'local_to_field', + file: filePath, + start: this.getOffsetAt(document, range.start).toString(), + id: '1', + indent_size: options.tabSize + }; + return this.sendCommand(JSON.stringify(command)); + } private sendCommand(command: string): Promise { return this.initialize().then(() => { // tslint:disable-next-line:promise-must-complete diff --git a/src/client/telemetry/constants.ts b/src/client/telemetry/constants.ts index 8b97329d0b32..f6996afc23d9 100644 --- a/src/client/telemetry/constants.ts +++ b/src/client/telemetry/constants.ts @@ -21,6 +21,9 @@ export enum EventName { REFACTOR_RENAME = 'REFACTOR_RENAME', REFACTOR_EXTRACT_VAR = 'REFACTOR_EXTRACT_VAR', REFACTOR_EXTRACT_FUNCTION = 'REFACTOR_EXTRACT_FUNCTION', + REFACTOR_USE_FUNCTION = 'REFACTOR_USE_FUNCTION', + REFACTOR_INLINE = 'REFACTOR_INLINE', + REFACTOR_LOCAL_TO_FIELD = 'REFACTOR_LOCAL_TO_FIELD', REPL = 'REPL', PYTHON_INTERPRETER = 'PYTHON_INTERPRETER', PYTHON_INSTALL_PACKAGE = 'PYTHON_INSTALL_PACKAGE', diff --git a/src/client/telemetry/index.ts b/src/client/telemetry/index.ts index c9e01e9d35f0..a24a420b2ded 100644 --- a/src/client/telemetry/index.ts +++ b/src/client/telemetry/index.ts @@ -877,7 +877,16 @@ export interface IEventNamePropertyMapping { /** * Telemetry tracking switching between LS and Jedi */ - [EventName.PYTHON_LANGUAGE_SERVER_SWITCHED]: { change: 'Switch to Jedi from LS' | 'Switch to LS from Jedi' }; + [EventName.PYTHON_LANGUAGE_SERVER_SWITCHED]: { + /** + * Value of LS setting prior to switch. + */ + oldValue: string; + /** + * Value of LS setting after switch. + */ + newValue: string; + }; /** * Telemetry event sent with details after attempting to download LS */ @@ -1003,6 +1012,18 @@ export interface IEventNamePropertyMapping { /** * Telemetry event sent when providing an edit that describes changes to rename a symbol to a different name */ + [EventName.REFACTOR_USE_FUNCTION]: never | undefined; + /** + * Telemetry event sent when providing an edit that describes changes to use a function + */ + [EventName.REFACTOR_INLINE]: never | undefined; + /** + * Telemetry event sent when providing an edit that describes changes to inline something + */ + [EventName.REFACTOR_LOCAL_TO_FIELD]: never | undefined; + /** + * Telemetry event sent when providing an edit that describes changes to change a local to a field + */ [EventName.REFACTOR_RENAME]: never | undefined; /** * Telemetry event sent when providing a set of project-wide references for the given position and document