From be29178053ddcd61f87dfc1f6a042d5283e0634b Mon Sep 17 00:00:00 2001 From: bschley <3937642+bschley@users.noreply.github.com> Date: Mon, 16 Jul 2018 20:36:20 +0200 Subject: [PATCH 01/27] german translation v1 --- .gitignore | 1 + package.nls.de.json | 52 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 package.nls.de.json diff --git a/.gitignore b/.gitignore index c6cd087c91ce..2d32c02d7a84 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ obj/** .pytest_cache tmp/** .python-version +vscode-python.code-workspace \ No newline at end of file diff --git a/package.nls.de.json b/package.nls.de.json new file mode 100644 index 000000000000..c70a01d1531d --- /dev/null +++ b/package.nls.de.json @@ -0,0 +1,52 @@ +{ + "python.command.python.sortImports.title": "Importe sortieren", + "python.command.python.startREPL.title": "REPL starten", + "python.command.python.createTerminal.title": "Terminal erstellen", + "python.command.python.buildWorkspaceSymbols.title": "Arbeitsplatz-Symbole Erstellen", + "python.command.python.runtests.title": "Alle Unittests ausführen", + "python.command.python.debugtests.title": "Alle Unittests debuggen", + "python.command.python.execInTerminal.title": "Python Datei in Terminal ausfrühren", + "python.command.python.setInterpreter.title": "Interpreter auswählen", + "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.viewTestOutput.title": "Unittest Ausgabe anzeigen", + "python.command.python.selectAndRunTestMethod.title": "Unittest Methode ausführen ...", + "python.command.python.selectAndDebugTestMethod.title": "Unittest Debug Methode ausführen ...", + "python.command.python.selectAndRunTestFile.title": "Unittest Datei ausführen ...", + "python.command.python.runCurrentTestFile.title": "Ausgewählte Unittest Datei ausführen", + "python.command.python.runFailedTests.title": "Fehlerhafte Unittests ausführen", + "python.command.python.discoverTests.title": "Unittest untersuchen", + "python.command.python.execSelectionInTerminal.title": "Run Selection/Line in Python Terminal", + "python.command.python.execSelectionInDjangoShell.title": "TODO Run Selection/Line in Django Shell", + "python.command.python.goToPythonObject.title": "Gehe zu Python Objekt", + "python.command.python.setLinter.title": "Linter auswählen", + "python.command.python.enableLinting.title": "Linting aktivieren", + "python.command.python.runLinting.title": "Linting ausführen", + "python.snippet.launch.standard.label": "Python: Aktuelle Datei", + "python.snippet.launch.standard.description": "Python Programm debuggen mit Standard Ausgabe", + "python.snippet.launch.pyspark.label": "Python: PySpark", + "python.snippet.launch.pyspark.description": "PySpark debuggen", + "python.snippet.launch.module.label": "Python: Modul", + "python.snippet.launch.module.description": "Python Modul debuggen", + "python.snippet.launch.terminal.label": "Python: Terminal (integriert)", + "python.snippet.launch.terminal.description": "Python Programm debuggen mit integrierten Terminal/Konsole", + "python.snippet.launch.externalTerminal.label": "Python: Terminal (extern)", + "python.snippet.launch.externalTerminal.description": "Python Programm mit externen Terminal/Konsole", + "python.snippet.launch.django.label": "Python: Django", + "python.snippet.launch.django.description": "Django Applikation debuggen", + "python.snippet.launch.flask.label": "Python: Flask (0.11.x oder neuer)", + "python.snippet.launch.flask.description": "Flask Applikation debuggen", + "python.snippet.launch.flaskOld.label": "Python: Flask (0.10.x or früher)", + "python.snippet.launch.flaskOld.description": "Ältere Flask Applikation debuggen", + "python.snippet.launch.gevent.label": "Python: Gevent", + "python.snippet.launch.gevent.description": "Gevent Applikation debuggen", + "python.snippet.launch.pyramid.label": "Python: Pyramid Applikation", + "python.snippet.launch.pyramid.description": "Pyramid Applikation debuggen", + "python.snippet.launch.watson.label": "Python: Watson Applikation", + "python.snippet.launch.watson.description": "Watson Applikation debuggen", + "python.snippet.launch.attach.label": "Python: anhängen", + "python.snippet.launch.attach.description": "Debugger anhängen für Remote Debugging", + "python.snippet.launch.scrapy.label": "Python: Scrapy", + "python.snippet.launch.scrapy.description": "Scrapy mit integierten Terminal/Konsole" +} From 305cf1ed391f88ee22cfe276d077ed7ae2f66e14 Mon Sep 17 00:00:00 2001 From: bschley <3937642+bschley@users.noreply.github.com> Date: Fri, 20 Jul 2018 00:22:22 +0200 Subject: [PATCH 02/27] added and updated german translation --- package.nls.de.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.nls.de.json b/package.nls.de.json index c70a01d1531d..4046a1618ead 100644 --- a/package.nls.de.json +++ b/package.nls.de.json @@ -17,8 +17,8 @@ "python.command.python.runCurrentTestFile.title": "Ausgewählte Unittest Datei ausführen", "python.command.python.runFailedTests.title": "Fehlerhafte Unittests ausführen", "python.command.python.discoverTests.title": "Unittest untersuchen", - "python.command.python.execSelectionInTerminal.title": "Run Selection/Line in Python Terminal", - "python.command.python.execSelectionInDjangoShell.title": "TODO Run Selection/Line in Django Shell", + "python.command.python.execSelectionInTerminal.title": "Selektion/Reihe in Python Terminal ausführen", + "python.command.python.execSelectionInDjangoShell.title": "Selektion/Reihe in Django Shell ausführen", "python.command.python.goToPythonObject.title": "Gehe zu Python Objekt", "python.command.python.setLinter.title": "Linter auswählen", "python.command.python.enableLinting.title": "Linting aktivieren", From cc679e737bef9f6b1ec1bab7830f85bc55d78d1d Mon Sep 17 00:00:00 2001 From: bschley <3937642+bschley@users.noreply.github.com> Date: Fri, 20 Jul 2018 00:27:06 +0200 Subject: [PATCH 03/27] Documented the German translation --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index aac7bfd8d044..7a4343811aee 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ The extension is available in multiple languages thanks to external contributors (if you would like to contribute a translation, see the [pull request which added Italian](https://github.com/Microsoft/vscode-python/pull/1152)): +* `de` * `en` * `es` * `fr` From b6a3098878143d800a683103e526fc8a0c350d99 Mon Sep 17 00:00:00 2001 From: bschley <3937642+bschley@users.noreply.github.com> Date: Fri, 20 Jul 2018 00:54:02 +0200 Subject: [PATCH 04/27] added new line --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 2d32c02d7a84..8ccacc7b77dc 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,4 @@ obj/** .pytest_cache tmp/** .python-version -vscode-python.code-workspace \ No newline at end of file +vscode-python.code-workspace From 97cf98a6d06437c3868a25afc5ecfcc35163632a Mon Sep 17 00:00:00 2001 From: bschley <3937642+bschley@users.noreply.github.com> Date: Tue, 24 Jul 2018 17:43:10 +0200 Subject: [PATCH 05/27] Typo and Grammar fixes --- package.nls.de.json | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/package.nls.de.json b/package.nls.de.json index 4046a1618ead..e8f5cb14a98f 100644 --- a/package.nls.de.json +++ b/package.nls.de.json @@ -1,13 +1,13 @@ { - "python.command.python.sortImports.title": "Importe sortieren", - "python.command.python.startREPL.title": "REPL starten", + "python.command.python.sortImports.title": "Sortieren der Importe", + "python.command.python.startREPL.title": "Starten des REPL", "python.command.python.createTerminal.title": "Terminal erstellen", - "python.command.python.buildWorkspaceSymbols.title": "Arbeitsplatz-Symbole Erstellen", + "python.command.python.buildWorkspaceSymbols.title": "Arbeitsplatz-Symbole erstellen", "python.command.python.runtests.title": "Alle Unittests ausführen", "python.command.python.debugtests.title": "Alle Unittests debuggen", - "python.command.python.execInTerminal.title": "Python Datei in Terminal ausfrühren", + "python.command.python.execInTerminal.title": "Python-Datei im Terminal ausführen", "python.command.python.setInterpreter.title": "Interpreter auswählen", - "python.command.python.updateSparkLibrary.title": "PySpark Arbeitsplatz Bibliotheken aktualisieren", + "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.viewTestOutput.title": "Unittest Ausgabe anzeigen", @@ -16,7 +16,7 @@ "python.command.python.selectAndRunTestFile.title": "Unittest Datei ausführen ...", "python.command.python.runCurrentTestFile.title": "Ausgewählte Unittest Datei ausführen", "python.command.python.runFailedTests.title": "Fehlerhafte Unittests ausführen", - "python.command.python.discoverTests.title": "Unittest untersuchen", + "python.command.python.discoverTests.title": "Unittests untersuchen", "python.command.python.execSelectionInTerminal.title": "Selektion/Reihe in Python Terminal ausführen", "python.command.python.execSelectionInDjangoShell.title": "Selektion/Reihe in Django Shell ausführen", "python.command.python.goToPythonObject.title": "Gehe zu Python Objekt", @@ -30,23 +30,23 @@ "python.snippet.launch.module.label": "Python: Modul", "python.snippet.launch.module.description": "Python Modul debuggen", "python.snippet.launch.terminal.label": "Python: Terminal (integriert)", - "python.snippet.launch.terminal.description": "Python Programm debuggen mit integrierten Terminal/Konsole", + "python.snippet.launch.terminal.description": "Python Programm mit integriertem Terminal/Konsole debuggen", "python.snippet.launch.externalTerminal.label": "Python: Terminal (extern)", - "python.snippet.launch.externalTerminal.description": "Python Programm mit externen Terminal/Konsole", + "python.snippet.launch.externalTerminal.description": "Python Programm mit externem Terminal/Konsole debuggen", "python.snippet.launch.django.label": "Python: Django", - "python.snippet.launch.django.description": "Django Applikation debuggen", + "python.snippet.launch.django.description": "Django Anwendung debuggen", "python.snippet.launch.flask.label": "Python: Flask (0.11.x oder neuer)", - "python.snippet.launch.flask.description": "Flask Applikation debuggen", - "python.snippet.launch.flaskOld.label": "Python: Flask (0.10.x or früher)", - "python.snippet.launch.flaskOld.description": "Ältere Flask Applikation debuggen", + "python.snippet.launch.flask.description": "Flask Anwendung debuggen", + "python.snippet.launch.flaskOld.label": "Python: Flask (0.10.x oder früher)", + "python.snippet.launch.flaskOld.description": "Ältere Flask Anwendung debuggen", "python.snippet.launch.gevent.label": "Python: Gevent", - "python.snippet.launch.gevent.description": "Gevent Applikation debuggen", - "python.snippet.launch.pyramid.label": "Python: Pyramid Applikation", - "python.snippet.launch.pyramid.description": "Pyramid Applikation debuggen", - "python.snippet.launch.watson.label": "Python: Watson Applikation", - "python.snippet.launch.watson.description": "Watson Applikation debuggen", - "python.snippet.launch.attach.label": "Python: anhängen", - "python.snippet.launch.attach.description": "Debugger anhängen für Remote Debugging", + "python.snippet.launch.gevent.description": "Gevent Anwendung debuggen", + "python.snippet.launch.pyramid.label": "Python: Pyramid Anwendung", + "python.snippet.launch.pyramid.description": "Pyramid Anwendung debuggen", + "python.snippet.launch.watson.label": "Python: Watson Anwendung", + "python.snippet.launch.watson.description": "Watson Anwendung debuggen", + "python.snippet.launch.attach.label": "Python: Anfügen", + "python.snippet.launch.attach.description": "Debugger anfügen für Remote Debugging", "python.snippet.launch.scrapy.label": "Python: Scrapy", - "python.snippet.launch.scrapy.description": "Scrapy mit integierten Terminal/Konsole" + "python.snippet.launch.scrapy.description": "Scrapy mit integiertem Terminal/Konsole" } From 43e5505bb4d9b96894a8aaa373e6a475cdf58e13 Mon Sep 17 00:00:00 2001 From: bschley <3937642+bschley@users.noreply.github.com> Date: Tue, 24 Jul 2018 18:01:21 +0200 Subject: [PATCH 06/27] typo and grammar fixes --- package.nls.de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.nls.de.json b/package.nls.de.json index e8f5cb14a98f..8b6d7c4bf2d0 100644 --- a/package.nls.de.json +++ b/package.nls.de.json @@ -16,7 +16,7 @@ "python.command.python.selectAndRunTestFile.title": "Unittest Datei ausführen ...", "python.command.python.runCurrentTestFile.title": "Ausgewählte Unittest Datei ausführen", "python.command.python.runFailedTests.title": "Fehlerhafte Unittests ausführen", - "python.command.python.discoverTests.title": "Unittests untersuchen", + "python.command.python.discoverTests.title": "Unittests durchsuchen", "python.command.python.execSelectionInTerminal.title": "Selektion/Reihe in Python Terminal ausführen", "python.command.python.execSelectionInDjangoShell.title": "Selektion/Reihe in Django Shell ausführen", "python.command.python.goToPythonObject.title": "Gehe zu Python Objekt", From 245c7b97c4bf75d3485cfc60f1f93f24425446fa Mon Sep 17 00:00:00 2001 From: bschley <3937642+bschley@users.noreply.github.com> Date: Tue, 24 Jul 2018 18:13:49 +0200 Subject: [PATCH 07/27] typo and grammar fixes --- package.nls.de.json | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/package.nls.de.json b/package.nls.de.json index 8b6d7c4bf2d0..6744ee1cf235 100644 --- a/package.nls.de.json +++ b/package.nls.de.json @@ -10,41 +10,41 @@ "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.viewTestOutput.title": "Unittest Ausgabe anzeigen", - "python.command.python.selectAndRunTestMethod.title": "Unittest Methode ausführen ...", - "python.command.python.selectAndDebugTestMethod.title": "Unittest Debug Methode ausführen ...", - "python.command.python.selectAndRunTestFile.title": "Unittest Datei ausführen ...", - "python.command.python.runCurrentTestFile.title": "Ausgewählte Unittest Datei ausführen", + "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 ...", + "python.command.python.selectAndRunTestFile.title": "Unittest-Datei ausführen ...", + "python.command.python.runCurrentTestFile.title": "Ausgewählte Unittest-Datei ausführen", "python.command.python.runFailedTests.title": "Fehlerhafte Unittests ausführen", "python.command.python.discoverTests.title": "Unittests durchsuchen", - "python.command.python.execSelectionInTerminal.title": "Selektion/Reihe in Python Terminal ausführen", - "python.command.python.execSelectionInDjangoShell.title": "Selektion/Reihe in Django Shell ausführen", - "python.command.python.goToPythonObject.title": "Gehe zu Python Objekt", + "python.command.python.execSelectionInTerminal.title": "Selektion/Reihe in Python-Terminal ausführen", + "python.command.python.execSelectionInDjangoShell.title": "Selektion/Reihe in Django-Shell ausführen", + "python.command.python.goToPythonObject.title": "Gehe zu Python-Objekt", "python.command.python.setLinter.title": "Linter auswählen", "python.command.python.enableLinting.title": "Linting aktivieren", "python.command.python.runLinting.title": "Linting ausführen", "python.snippet.launch.standard.label": "Python: Aktuelle Datei", - "python.snippet.launch.standard.description": "Python Programm debuggen mit Standard Ausgabe", + "python.snippet.launch.standard.description": "Python-Programm debuggen mit Standardausgabe", "python.snippet.launch.pyspark.label": "Python: PySpark", "python.snippet.launch.pyspark.description": "PySpark debuggen", "python.snippet.launch.module.label": "Python: Modul", - "python.snippet.launch.module.description": "Python Modul debuggen", + "python.snippet.launch.module.description": "Python-Modul debuggen", "python.snippet.launch.terminal.label": "Python: Terminal (integriert)", - "python.snippet.launch.terminal.description": "Python Programm mit integriertem Terminal/Konsole debuggen", + "python.snippet.launch.terminal.description": "Python-Programm mit integriertem Terminal/Konsole debuggen", "python.snippet.launch.externalTerminal.label": "Python: Terminal (extern)", - "python.snippet.launch.externalTerminal.description": "Python Programm mit externem Terminal/Konsole debuggen", + "python.snippet.launch.externalTerminal.description": "Python-Programm mit externem Terminal/Konsole debuggen", "python.snippet.launch.django.label": "Python: Django", - "python.snippet.launch.django.description": "Django Anwendung debuggen", + "python.snippet.launch.django.description": "Django-Anwendung debuggen", "python.snippet.launch.flask.label": "Python: Flask (0.11.x oder neuer)", - "python.snippet.launch.flask.description": "Flask Anwendung debuggen", + "python.snippet.launch.flask.description": "Flask-Anwendung debuggen", "python.snippet.launch.flaskOld.label": "Python: Flask (0.10.x oder früher)", - "python.snippet.launch.flaskOld.description": "Ältere Flask Anwendung debuggen", + "python.snippet.launch.flaskOld.description": "Ältere Flask-Anwendung debuggen", "python.snippet.launch.gevent.label": "Python: Gevent", - "python.snippet.launch.gevent.description": "Gevent Anwendung debuggen", - "python.snippet.launch.pyramid.label": "Python: Pyramid Anwendung", - "python.snippet.launch.pyramid.description": "Pyramid Anwendung debuggen", - "python.snippet.launch.watson.label": "Python: Watson Anwendung", - "python.snippet.launch.watson.description": "Watson Anwendung debuggen", + "python.snippet.launch.gevent.description": "Gevent-Anwendung debuggen", + "python.snippet.launch.pyramid.label": "Python: Pyramid-Anwendung", + "python.snippet.launch.pyramid.description": "Pyramid-Anwendung debuggen", + "python.snippet.launch.watson.label": "Python: Watson-Anwendung", + "python.snippet.launch.watson.description": "Watson-Anwendung debuggen", "python.snippet.launch.attach.label": "Python: Anfügen", "python.snippet.launch.attach.description": "Debugger anfügen für Remote Debugging", "python.snippet.launch.scrapy.label": "Python: Scrapy", From b6fceea65d482103a3d39b0c1105574940081538 Mon Sep 17 00:00:00 2001 From: Derek Keeler Date: Mon, 16 Jul 2018 17:05:25 -0600 Subject: [PATCH 08/27] Upgrade to latest LS protocol (#2164) * LS symbol providers * Different ready wait * Upgrade dependencies to latest LS * Make open files only default * Turn off hash checks * Fix double progress display * Update packages * Anchor dependencies * Add missing mock from vscode-mock * Downgrade pylint to < 2.0.0 to mirror prospector requirements --- news/1 Enhancements/2000.md | 1 + news/1 Enhancements/2113.md | 1 + package-lock.json | 47 ++++++++++++++----------- package.json | 11 +++--- requirements.txt | 2 +- src/client/activation/downloader.ts | 15 +------- src/client/activation/languageServer.ts | 2 +- src/test/vscode-mock.ts | 1 + 8 files changed, 39 insertions(+), 41 deletions(-) create mode 100644 news/1 Enhancements/2000.md create mode 100644 news/1 Enhancements/2113.md diff --git a/news/1 Enhancements/2000.md b/news/1 Enhancements/2000.md new file mode 100644 index 000000000000..319691492f78 --- /dev/null +++ b/news/1 Enhancements/2000.md @@ -0,0 +1 @@ +Only report Language Server download progress once. (Thanks @MikhailArkhipov) diff --git a/news/1 Enhancements/2113.md b/news/1 Enhancements/2113.md new file mode 100644 index 000000000000..9455e9f6077d --- /dev/null +++ b/news/1 Enhancements/2113.md @@ -0,0 +1 @@ +Set default analysis for language server to open files only. (Thanks @MikhailArkhipov) diff --git a/package-lock.json b/package-lock.json index e42cc4b1a58e..ee9849eb35e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9652,40 +9652,47 @@ } }, "vscode-jsonrpc": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0.tgz", - "integrity": "sha1-hyOdnhZrLXNSJFuKgTWXgEwdY6o=" + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.6.2.tgz", + "integrity": "sha512-T24Jb5V48e4VgYliUXMnZ379ItbrXgOimweKaJshD84z+8q7ZOZjJan0MeDe+Ugb+uqERDVV8SBmemaGMSMugA==" }, "vscode-languageclient": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-3.5.1.tgz", - "integrity": "sha512-GTQ+hSq/o4c/y6GYmyP9XNrVoIu0NFZ67KltSkqN+tO0eUNDIlrVNX+3DJzzyLhSsrctuGzuYWm3t87mNAcBmQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-4.3.0.tgz", + "integrity": "sha512-vDpsmYfYpfuyDKZ46pCJEFBOCNHepRBSmlBGA0fczEbYghYm059BiFo3SmT4MK1r8NvYrFEem4k5TYNW3wommg==", "requires": { - "vscode-languageserver-protocol": "3.5.1" + "vscode-languageserver-protocol": "^3.9.0" } }, "vscode-languageserver": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-3.5.1.tgz", - "integrity": "sha512-RYUKn0DgHTFcS8kS4VaNCjNMaQXYqiXdN9bKrFjXzu5RPKfjIYcoh47oVWwZj4L3R/DPB0Se7HPaDatvYY2XgQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-4.3.0.tgz", + "integrity": "sha512-4dTpnyTB6Q0HmMhxaG60rrpQthbTBlMtFX5cwJpPxcPzLZIFDWB3msR6TxGCzWpdYF11REIJihWByobpGkljdQ==", "requires": { - "vscode-languageserver-protocol": "3.5.1", - "vscode-uri": "^1.0.1" + "vscode-languageserver-protocol": "^3.9.0", + "vscode-uri": "^1.0.3" + }, + "dependencies": { + "vscode-uri": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-1.0.5.tgz", + "integrity": "sha1-O4majvccN/MFTXm9vdoxx7828g0=" + } } }, "vscode-languageserver-protocol": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.1.tgz", - "integrity": "sha512-1fPDIwsAv1difCV+8daOrJEGunClNJWqnUHq/ncWrjhitKWXgGmRCjlwZ3gDUTt54yRcvXz1PXJDaRNvNH6pYA==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.9.0.tgz", + "integrity": "sha512-i1sG5iU88Mocc7egTeh6dAow/yRWpPK5PLJaxsWsKiA+dspq1Yzr/R1bNLPc+6P/ab010lXhzdUHQY0CuIUyDw==", "requires": { - "vscode-jsonrpc": "3.5.0", - "vscode-languageserver-types": "3.5.0" + "vscode-jsonrpc": "^3.6.2", + "vscode-languageserver-types": "^3.9.0" } }, "vscode-languageserver-types": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz", - "integrity": "sha1-5I15li8LjgLelV4/UkkI4rGcA3Q=" + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.9.0.tgz", + "integrity": "sha512-Qzh3VsU3t0zhKtYl1revyax+4gGHl2ejNzYXeiZYQMF3i0vX4dtPohxGDFoZYfGFQI738aXYbSUQmhLeBckDlQ==" }, "vscode-uri": { "version": "1.0.1", diff --git a/package.json b/package.json index 4bc0621b780b..2795d022a5c4 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "theme": "dark" }, "engines": { - "vscode": "^1.23.0" + "vscode": "^1.25.0" }, "recommendations": [ "donjayamanne.jupyter" @@ -1277,7 +1277,7 @@ }, "python.analysis.openFilesOnly": { "type": "boolean", - "default": false, + "default": true, "description": "Only show errors and warnings for open files rather than for the entire workspace.", "scope": "resource" }, @@ -1964,7 +1964,7 @@ "fs-extra": "4.0.3", "fuzzy": "0.1.3", "get-port": "3.2.0", - "glob": "^7.1.2", + "glob": "7.1.2", "iconv-lite": "0.4.21", "inversify": "4.11.1", "line-by-line": "0.1.6", @@ -1990,8 +1990,9 @@ "vscode-debugadapter": "1.28.0", "vscode-debugprotocol": "1.28.0", "vscode-extension-telemetry": "0.0.15", - "vscode-languageclient": "3.5.1", - "vscode-languageserver": "3.5.1", + "vscode-languageclient": "4.3.0", + "vscode-languageserver": "4.3.0", + "vscode-languageserver-protocol": "3.9.0", "winreg": "1.2.4", "xml2js": "0.4.19" }, diff --git a/requirements.txt b/requirements.txt index 01c6059a10cb..cb71d94eb4e0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ flake8 autopep8 black ; python_version>='3.6' yapf -pylint +pylint<2.0.0 pep8 prospector pydocstyle diff --git a/src/client/activation/downloader.ts b/src/client/activation/downloader.ts index 59f9fd91a8db..c80b7dd40856 100644 --- a/src/client/activation/downloader.ts +++ b/src/client/activation/downloader.ts @@ -11,7 +11,6 @@ import { createDeferred } from '../common/helpers'; import { IFileSystem, IPlatformService } from '../common/platform/types'; import { IExtensionContext, IOutputChannel } from '../common/types'; import { IServiceContainer } from '../ioc/types'; -import { HashVerifier } from './hashVerifier'; import { PlatformData } from './platformData'; // tslint:disable-next-line:no-require-imports no-var-requires @@ -42,7 +41,6 @@ export class LanguageServerDownloader { let localTempFilePath = ''; try { localTempFilePath = await this.downloadFile(downloadUriPrefix, enginePackageFileName, 'Downloading Microsoft Python Language Server... '); - await this.verifyDownload(localTempFilePath, platformString); await this.unpackArchive(context.extensionPath, localTempFilePath); } catch (err) { this.output.appendLine('failed.'); @@ -70,8 +68,7 @@ export class LanguageServerDownloader { }); await window.withProgress({ - location: ProgressLocation.Window, - title + location: ProgressLocation.Window }, (progress) => { requestProgress(request(uri)) @@ -98,16 +95,6 @@ export class LanguageServerDownloader { return tempFile.filePath; } - private async verifyDownload(filePath: string, platformString: string): Promise { - this.output.appendLine(''); - this.output.append('Verifying download... '); - const verifier = new HashVerifier(); - if (!await verifier.verifyHash(filePath, platformString, await this.platformData.getExpectedHash())) { - throw new Error('Hash of the downloaded file does not match.'); - } - this.output.appendLine('valid.'); - } - private async unpackArchive(extensionPath: string, tempFilePath: string): Promise { this.output.append('Unpacking archive... '); diff --git a/src/client/activation/languageServer.ts b/src/client/activation/languageServer.ts index 0d4a6f20871b..5154b92a41af 100644 --- a/src/client/activation/languageServer.ts +++ b/src/client/activation/languageServer.ts @@ -232,7 +232,7 @@ export class LanguageServerExtensionActivator implements IExtensionActivator { properties }, displayOptions: { - preferredFormat: 1, // Markdown + preferredFormat: 'markdown', trimDocumentationLines: false, maxDocumentationLineLength: 0, trimDocumentationText: false, diff --git a/src/test/vscode-mock.ts b/src/test/vscode-mock.ts index 1500ca249eae..a1a35ed406c2 100644 --- a/src/test/vscode-mock.ts +++ b/src/test/vscode-mock.ts @@ -64,6 +64,7 @@ mockedVSCode.EventEmitter = vscodeMocks.vscMock.EventEmitter; mockedVSCode.ConfigurationTarget = vscodeMocks.vscMockExtHostedTypes.ConfigurationTarget; mockedVSCode.StatusBarAlignment = vscodeMocks.vscMockExtHostedTypes.StatusBarAlignment; mockedVSCode.SignatureHelp = vscodeMocks.vscMockExtHostedTypes.SignatureHelp; +mockedVSCode.DocumentLink = vscodeMocks.vscMockExtHostedTypes.DocumentLink; // This API is used in src/client/telemetry/telemetry.ts const extensions = TypeMoq.Mock.ofType(); From a7d46c4b568dae317196a6add198086639bde147 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 17 Jul 2018 10:52:54 -0700 Subject: [PATCH 09/27] Explicitly test against installing the language server --- .github/test_plan.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/test_plan.md b/.github/test_plan.md index c3d6ef79a7cf..8dc68cca76ce 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -15,6 +15,7 @@ - Check the `Output` window under `Python` for logged errors - Have `Developer Tools` open to detect any errors - Consider running the tests in a multi-folder workspace +- Focus on in-development features (i.e. experimental debugger and language server)
Scenarios @@ -48,6 +49,7 @@ - [ ] [`Create Terminal`](https://code.visualstudio.com/docs/python/environments#_activating-an-environment-in-the-terminal) works - [ ] Steals focus - [ ] `"python.terminal.activateEnvironment": false` deactivates automatically running the activation script in the terminal + - [ ] After the language server downloads it is able to complete its analysis of the environment w/o requiring a restart - [ ] Detect multiple virtual environments contained in the directory specified in `"python.venvPath"` - [ ] Detected all [conda environments created with an interpreter](https://code.visualstudio.com/docs/python/environments#_conda-environments) - [ ] Appropriate suffix label specified in status bar (e.g. `(condaenv)`) @@ -56,11 +58,13 @@ - [ ] Installs into environment - [ ] [`Create Terminal`](https://code.visualstudio.com/docs/python/environments#_activating-an-environment-in-the-terminal) works - [ ] `"python.terminal.activateEnvironment": false` deactivates automatically running the activation script in the terminal + - [ ] After the language server downloads it is able to complete its analysis of the environment w/o requiring a restart - [ ] (Linux/macOS until [`-m` is supported](https://github.com/Microsoft/vscode-python/issues/978)) Detected the virtual environment created by [pipenv](https://docs.pipenv.org/) - [ ] Appropriate suffix label specified in status bar (e.g. `(pipenv)`) - [ ] Prompt to install Pylint uses `pipenv install --dev` - [ ] [`Create Terminal`](https://code.visualstudio.com/docs/python/environments#_activating-an-environment-in-the-terminal) works - [ ] `"python.terminal.activateEnvironment": false` deactivates automatically running the activation script in the terminal + - [ ] After the language server downloads it is able to complete its analysis of the environment w/o requiring a restart - [ ] (Linux/macOS) Virtual environments created under `{workspaceFolder}/.direnv/python-{python_version}` are detected (for [direnv](https://direnv.net/) and its [`layout python3`](https://github.com/direnv/direnv/blob/master/stdlib.sh) support) - [ ] Appropriate suffix label specified in status bar (e.g. `(venv)`) From 27d6600450bc4e108ed5f2a976852439279e9146 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 17 Jul 2018 14:05:55 -0700 Subject: [PATCH 10/27] Specify what versions of Python we support --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7a4343811aee..3466e163ab1b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Python extension for Visual Studio Code -A [Visual Studio Code](https://code.visualstudio.com/) [extension](https://marketplace.visualstudio.com/VSCode) with rich support for the [Python language](https://www.python.org/) (_including Python 3.6_), including features such as linting, debugging, IntelliSense, code navigation, code formatting, refactoring, unit tests, snippets, and more! +A [Visual Studio Code](https://code.visualstudio.com/) [extension](https://marketplace.visualstudio.com/VSCode) with rich support for the [Python language](https://www.python.org/) (2.7, >=3.4), including features such as linting, debugging, IntelliSense, code navigation, code formatting, refactoring, unit tests, snippets, and more! ## Quick start From 4760b2bcc64afbbbadba90d4c8babc90715d458a Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 17 Jul 2018 15:45:09 -0700 Subject: [PATCH 11/27] Relax validation of environment path variable (#2172) --- news/2 Fixes/2076.md | 1 + .../diagnostics/checks/envPathVariable.ts | 6 +- .../checks/envPathVariable.unit.test.ts | 58 +++++++++---------- 3 files changed, 32 insertions(+), 33 deletions(-) create mode 100644 news/2 Fixes/2076.md diff --git a/news/2 Fixes/2076.md b/news/2 Fixes/2076.md new file mode 100644 index 000000000000..70ce03782a26 --- /dev/null +++ b/news/2 Fixes/2076.md @@ -0,0 +1 @@ +Relax validation of the environment `Path` variable. diff --git a/src/client/application/diagnostics/checks/envPathVariable.ts b/src/client/application/diagnostics/checks/envPathVariable.ts index 9b00ed1303f1..bccb3a29c042 100644 --- a/src/client/application/diagnostics/checks/envPathVariable.ts +++ b/src/client/application/diagnostics/checks/envPathVariable.ts @@ -16,8 +16,8 @@ import { DiagnosticCodes } from '../constants'; import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../promptHandler'; import { DiagnosticScope, IDiagnostic, IDiagnosticHandlerService } from '../types'; -const InvalidEnvPathVariableMessage = 'The environment variable \'{0}\' seems to have some paths containing characters (\';\', \'"\' or \';;\').' + - ' The existence of such characters are known to have caused the {1} extension to not load. If the extension fails to load please modify your paths to remove these characters.'; +const InvalidEnvPathVariableMessage = 'The environment variable \'{0}\' seems to have some paths containing the \'"\' character.' + + ' The existence of such a character is known to have caused the {1} extension to not load. If the extension fails to load please modify your paths to remove this \'"\' character.'; export class InvalidEnvironmentPathVariableDiagnostic extends BaseDiagnostic { constructor(message) { @@ -79,6 +79,6 @@ export class EnvironmentPathVariableDiagnosticsService extends BaseDiagnosticsSe const pathValue = currentProc.env[this.platform.pathVariableName]; const pathSeparator = this.serviceContainer.get(IPathUtils).delimiter; const paths = pathValue.split(pathSeparator); - return paths.filter((item, index) => item.indexOf('"') >= 0 || item.indexOf(';') >= 0 || (item.length === 0 && index !== paths.length - 1)).length > 0; + return paths.filter(item => item.indexOf('"') >= 0).length > 0; } } diff --git a/src/test/application/diagnostics/checks/envPathVariable.unit.test.ts b/src/test/application/diagnostics/checks/envPathVariable.unit.test.ts index e71efc0406cd..a2f678f1f142 100644 --- a/src/test/application/diagnostics/checks/envPathVariable.unit.test.ts +++ b/src/test/application/diagnostics/checks/envPathVariable.unit.test.ts @@ -115,36 +115,34 @@ suite('Application Diagnostics - Checks Env Path Variable', () => { expect(diagnostics).to.be.deep.equal([]); }); // Note: On windows, when a path contains a `;` then Windows encloses the path within `"`. - [';;', '"'].forEach(invalidCharacter => { - test(`Should return single diagnostics for Windows if path contains ${invalidCharacter}`, async () => { - platformService.setup(p => p.isWindows).returns(() => true); - const paths = [ - path.join('one', 'two', `three${invalidCharacter}`), - path.join('one', 'two', 'four') - ].join(pathDelimiter); - procEnv.setup(env => env[pathVariableName]).returns(() => paths); - - const diagnostics = await diagnosticService.diagnose(); - - expect(diagnostics).to.be.lengthOf(1); - expect(diagnostics[0].code).to.be.equal(DiagnosticCodes.InvalidEnvironmentPathVariableDiagnostic); - expect(diagnostics[0].message).to.contain(extensionName); - expect(diagnostics[0].message).to.contain(pathVariableName); - expect(diagnostics[0].severity).to.be.equal(DiagnosticSeverity.Warning); - expect(diagnostics[0].scope).to.be.equal(DiagnosticScope.Global); - }); - test('Should not return diagnostics for Windows if path ends with delimiter', async () => { - const paths = [ - path.join('one', 'two', 'three'), - path.join('one', 'two', 'four') - ].join(pathDelimiter) + pathDelimiter; - platformService.setup(p => p.isWindows).returns(() => true); - procEnv.setup(env => env[pathVariableName]).returns(() => paths); - - const diagnostics = await diagnosticService.diagnose(); - - expect(diagnostics).to.be.lengthOf(0); - }); + test('Should return single diagnostics for Windows if path contains \'"\'', async () => { + platformService.setup(p => p.isWindows).returns(() => true); + const paths = [ + path.join('one', 'two', 'three"'), + path.join('one', 'two', 'four') + ].join(pathDelimiter); + procEnv.setup(env => env[pathVariableName]).returns(() => paths); + + const diagnostics = await diagnosticService.diagnose(); + + expect(diagnostics).to.be.lengthOf(1); + expect(diagnostics[0].code).to.be.equal(DiagnosticCodes.InvalidEnvironmentPathVariableDiagnostic); + expect(diagnostics[0].message).to.contain(extensionName); + expect(diagnostics[0].message).to.contain(pathVariableName); + expect(diagnostics[0].severity).to.be.equal(DiagnosticSeverity.Warning); + expect(diagnostics[0].scope).to.be.equal(DiagnosticScope.Global); + }); + test('Should not return diagnostics for Windows if path ends with delimiter', async () => { + const paths = [ + path.join('one', 'two', 'three'), + path.join('one', 'two', 'four') + ].join(pathDelimiter) + pathDelimiter; + platformService.setup(p => p.isWindows).returns(() => true); + procEnv.setup(env => env[pathVariableName]).returns(() => paths); + + const diagnostics = await diagnosticService.diagnose(); + + expect(diagnostics).to.be.lengthOf(0); }); test('Should display three options in message displayed with 2 commands', async () => { platformService.setup(p => p.isWindows).returns(() => true); From de71813b9ca55f058a61c7da297d0374f2cea0f3 Mon Sep 17 00:00:00 2001 From: Derek Keeler Date: Tue, 17 Jul 2018 18:21:38 -0600 Subject: [PATCH 12/27] Update CONTRIBUTING - LANGUAGE SERVER.md (#2161) --- CONTRIBUTING - LANGUAGE SERVER.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING - LANGUAGE SERVER.md b/CONTRIBUTING - LANGUAGE SERVER.md index 116b668e1723..7c363ce23b81 100644 --- a/CONTRIBUTING - LANGUAGE SERVER.md +++ b/CONTRIBUTING - LANGUAGE SERVER.md @@ -22,7 +22,7 @@ ```shell git clone https://github.com/microsoft/ptvs -cd Python/Product/VsCode/AnalysisVsc +cd ptvs/Python/Product/VSCode/AnalysisVsc dotnet build ``` From 1ad96fd4c07340aa4f7e4687bbf07a15ddc4b0f8 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 18 Jul 2018 10:29:50 -0700 Subject: [PATCH 13/27] Ensure Pylint<2.0.0 on Python 2.x (#2178) * Ensure Pylint<=2.0.0 on Python 2.x * Python 2.7 requires older than 2.0.0 * tests * Add Tests * Fix broken tests * Redue nesting of suites --- src/client/common/installer/condaInstaller.ts | 3 +- .../common/installer/moduleInstaller.ts | 28 +- .../common/installer/pipEnvInstaller.ts | 22 +- src/client/common/installer/pipInstaller.ts | 7 +- .../common/installer/moduleInstaller.test.ts | 78 ----- .../installer/moduleInstaller.unit.test.ts | 269 ++++++++++++++++++ src/test/common/moduleInstaller.test.ts | 9 +- src/test/unittests.ts | 2 +- 8 files changed, 318 insertions(+), 100 deletions(-) delete mode 100644 src/test/common/installer/moduleInstaller.test.ts create mode 100644 src/test/common/installer/moduleInstaller.unit.test.ts diff --git a/src/client/common/installer/condaInstaller.ts b/src/client/common/installer/condaInstaller.ts index 10acd9f3bb64..83d519be9ac0 100644 --- a/src/client/common/installer/condaInstaller.ts +++ b/src/client/common/installer/condaInstaller.ts @@ -61,8 +61,7 @@ export class CondaInstaller extends ModuleInstaller implements IModuleInstaller args.push(moduleName); return { args, - execPath: condaFile, - moduleName: '' + execPath: condaFile }; } private async isCurrentEnvironmentACondaEnvironment(resource?: Uri): Promise { diff --git a/src/client/common/installer/moduleInstaller.ts b/src/client/common/installer/moduleInstaller.ts index 5fe19952bba9..5acfab18c9a1 100644 --- a/src/client/common/installer/moduleInstaller.ts +++ b/src/client/common/installer/moduleInstaller.ts @@ -10,11 +10,10 @@ import * as path from 'path'; import * as vscode from 'vscode'; import { IInterpreterService, InterpreterType } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; -import { PythonSettings } from '../configSettings'; import { STANDARD_OUTPUT_CHANNEL } from '../constants'; import { noop } from '../core.utils'; import { ITerminalServiceFactory } from '../terminal/types'; -import { ExecutionInfo, IOutputChannel } from '../types'; +import { ExecutionInfo, IConfigurationService, IOutputChannel } from '../types'; @injectable() export abstract class ModuleInstaller { @@ -23,9 +22,12 @@ export abstract class ModuleInstaller { const executionInfo = await this.getExecutionInfo(name, resource); const terminalService = this.serviceContainer.get(ITerminalServiceFactory).getTerminalService(resource); + const executionInfoArgs = await this.processInstallArgs(executionInfo.args, resource); if (executionInfo.moduleName) { - const settings = PythonSettings.getInstance(resource); - const args = ['-m', 'pip'].concat(executionInfo.args); + const configService = this.serviceContainer.get(IConfigurationService); + const settings = configService.getSettings(resource); + const args = ['-m', executionInfo.moduleName].concat(executionInfoArgs); + const pythonPath = settings.pythonPath; const interpreterService = this.serviceContainer.get(IInterpreterService); @@ -43,12 +45,28 @@ export abstract class ModuleInstaller { await terminalService.sendCommand(pythonPath, args.concat(['--user'])); } } else { - await terminalService.sendCommand(executionInfo.execPath!, executionInfo.args); + await terminalService.sendCommand(executionInfo.execPath!, executionInfoArgs); } } public abstract isSupported(resource?: vscode.Uri): Promise; protected abstract getExecutionInfo(moduleName: string, resource?: vscode.Uri): Promise; + private async processInstallArgs(args: string[], resource?: vscode.Uri): Promise { + const indexOfPylint = args.findIndex(arg => arg.toUpperCase() === 'PYLINT'); + if (indexOfPylint === -1) { + return args; + } + // If installing pylint on python 2.x, then use pylint~=1.9.0 + const interpreterService = this.serviceContainer.get(IInterpreterService); + const currentInterpreter = await interpreterService.getActiveInterpreter(resource); + if (currentInterpreter && currentInterpreter.version_info && currentInterpreter.version_info[0] === 2) { + const newArgs = [...args]; + // This command could be sent to the terminal, hence '<' needs to be escaped for UNIX. + newArgs[indexOfPylint] = '"pylint<2.0.0"'; + return newArgs; + } + return args; + } private async isPathWritableAsync(directoryPath: string): Promise { const filePath = `${directoryPath}${path.sep}___vscpTest___`; return new Promise(resolve => { diff --git a/src/client/common/installer/pipEnvInstaller.ts b/src/client/common/installer/pipEnvInstaller.ts index 4b01df9fd3e2..2833aa218324 100644 --- a/src/client/common/installer/pipEnvInstaller.ts +++ b/src/client/common/installer/pipEnvInstaller.ts @@ -5,13 +5,14 @@ import { inject, injectable } from 'inversify'; import { Uri } from 'vscode'; import { IInterpreterLocatorService, PIPENV_SERVICE } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; -import { ITerminalServiceFactory } from '../terminal/types'; +import { ExecutionInfo } from '../types'; +import { ModuleInstaller } from './moduleInstaller'; import { IModuleInstaller } from './types'; -const pipenvName = 'pipenv'; +export const pipenvName = 'pipenv'; @injectable() -export class PipEnvInstaller implements IModuleInstaller { +export class PipEnvInstaller extends ModuleInstaller implements IModuleInstaller { private readonly pipenv: IInterpreterLocatorService; public get displayName() { @@ -21,17 +22,18 @@ export class PipEnvInstaller implements IModuleInstaller { return 10; } - constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { + constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { + super(serviceContainer); this.pipenv = this.serviceContainer.get(IInterpreterLocatorService, PIPENV_SERVICE); } - - public installModule(name: string, resource?: Uri): Promise { - const terminalService = this.serviceContainer.get(ITerminalServiceFactory).getTerminalService(resource); - return terminalService.sendCommand(pipenvName, ['install', name, '--dev']); - } - public async isSupported(resource?: Uri): Promise { const interpreters = await this.pipenv.getInterpreters(resource); return interpreters && interpreters.length > 0; } + protected async getExecutionInfo(moduleName: string, resource?: Uri): Promise { + return { + args: ['install', moduleName, '--dev'], + execPath: pipenvName + }; + } } diff --git a/src/client/common/installer/pipInstaller.ts b/src/client/common/installer/pipInstaller.ts index 90a471b382b1..16f886f29e93 100644 --- a/src/client/common/installer/pipInstaller.ts +++ b/src/client/common/installer/pipInstaller.ts @@ -2,8 +2,9 @@ // Licensed under the MIT License. import { inject, injectable } from 'inversify'; -import { Uri, workspace } from 'vscode'; +import { Uri } from 'vscode'; import { IServiceContainer } from '../../ioc/types'; +import { IWorkspaceService } from '../application/types'; import { IPythonExecutionFactory } from '../process/types'; import { ExecutionInfo } from '../types'; import { ModuleInstaller } from './moduleInstaller'; @@ -25,14 +26,14 @@ export class PipInstaller extends ModuleInstaller implements IModuleInstaller { } protected async getExecutionInfo(moduleName: string, resource?: Uri): Promise { const proxyArgs: string[] = []; - const proxy = workspace.getConfiguration('http').get('proxy', ''); + const workspaceService = this.serviceContainer.get(IWorkspaceService); + const proxy = workspaceService.getConfiguration('http').get('proxy', ''); if (proxy.length > 0) { proxyArgs.push('--proxy'); proxyArgs.push(proxy); } return { args: [...proxyArgs, 'install', '-U', moduleName], - execPath: '', moduleName: 'pip' }; } diff --git a/src/test/common/installer/moduleInstaller.test.ts b/src/test/common/installer/moduleInstaller.test.ts deleted file mode 100644 index a7590f952896..000000000000 --- a/src/test/common/installer/moduleInstaller.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as path from 'path'; -import * as TypeMoq from 'typemoq'; -import { Disposable} from 'vscode'; -import { CondaInstaller } from '../../../client/common/installer/condaInstaller'; -import { PipInstaller } from '../../../client/common/installer/pipInstaller'; -import { IInstallationChannelManager, IModuleInstaller } from '../../../client/common/installer/types'; -import { ITerminalService, ITerminalServiceFactory } from '../../../client/common/terminal/types'; -import { IConfigurationService, IDisposableRegistry, IPythonSettings } from '../../../client/common/types'; -import { ICondaService, IInterpreterService } from '../../../client/interpreter/contracts'; -import { IServiceContainer } from '../../../client/ioc/types'; -import { initialize } from '../../initialize'; - -// tslint:disable-next-line:max-func-body-length -suite('Module Installerx', () => { - const pythonPath = path.join(__dirname, 'python'); - suiteSetup(initialize); - [CondaInstaller, PipInstaller].forEach(installerClass => { - let disposables: Disposable[] = []; - let installer: IModuleInstaller; - let installationChannel: TypeMoq.IMock; - let serviceContainer: TypeMoq.IMock; - let terminalService: TypeMoq.IMock; - let pythonSettings: TypeMoq.IMock; - let interpreterService: TypeMoq.IMock; - setup(() => { - serviceContainer = TypeMoq.Mock.ofType(); - - disposables = []; - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IDisposableRegistry), TypeMoq.It.isAny())).returns(() => disposables); - - installationChannel = TypeMoq.Mock.ofType(); - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInstallationChannelManager), TypeMoq.It.isAny())).returns(() => installationChannel.object); - - const condaService = TypeMoq.Mock.ofType(); - condaService.setup(c => c.getCondaFile()).returns(() => Promise.resolve('conda')); - condaService.setup(c => c.getCondaEnvironment(TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)); - - const configService = TypeMoq.Mock.ofType(); - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IConfigurationService), TypeMoq.It.isAny())).returns(() => configService); - pythonSettings = TypeMoq.Mock.ofType(); - pythonSettings.setup(p => p.pythonPath).returns(() => pythonPath); - configService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object); - - terminalService = TypeMoq.Mock.ofType(); - const terminalServiceFactory = TypeMoq.Mock.ofType(); - terminalServiceFactory.setup(f => f.getTerminalService(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => terminalService.object); - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ITerminalServiceFactory), TypeMoq.It.isAny())).returns(() => terminalServiceFactory.object); - - interpreterService = TypeMoq.Mock.ofType(); - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInterpreterService), TypeMoq.It.isAny())).returns(() => interpreterService.object); - - installer = new installerClass(serviceContainer.object); - }); - teardown(() => { - disposables.forEach(disposable => { - if (disposable) { - disposable.dispose(); - } - }); - }); - test(`Ensure getActiveInterperter is used (${installerClass.name})`, async () => { - if (installer.displayName !== 'Pip') { - return; - } - interpreterService.setup(i => i.getActiveInterpreter(TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)).verifiable(); - try { - await installer.installModule('xyz'); - // tslint:disable-next-line:no-empty - } catch { } - interpreterService.verifyAll(); - }); - }); -}); diff --git a/src/test/common/installer/moduleInstaller.unit.test.ts b/src/test/common/installer/moduleInstaller.unit.test.ts new file mode 100644 index 000000000000..c57c9befe149 --- /dev/null +++ b/src/test/common/installer/moduleInstaller.unit.test.ts @@ -0,0 +1,269 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:no-any max-func-body-length no-invalid-this + +import * as path from 'path'; +import * as TypeMoq from 'typemoq'; +import { Disposable, OutputChannel, Uri, WorkspaceConfiguration } from 'vscode'; +import { IWorkspaceService } from '../../../client/common/application/types'; +import { noop } from '../../../client/common/core.utils'; +import { EnumEx } from '../../../client/common/enumUtils'; +import { CondaInstaller } from '../../../client/common/installer/condaInstaller'; +import { PipEnvInstaller, pipenvName } from '../../../client/common/installer/pipEnvInstaller'; +import { PipInstaller } from '../../../client/common/installer/pipInstaller'; +import { ProductInstaller } from '../../../client/common/installer/productInstaller'; +import { IInstallationChannelManager, IModuleInstaller } from '../../../client/common/installer/types'; +import { PythonVersionInfo } from '../../../client/common/process/types'; +import { ITerminalService, ITerminalServiceFactory } from '../../../client/common/terminal/types'; +import { IConfigurationService, IDisposableRegistry, IPythonSettings, ModuleNamePurpose, Product } from '../../../client/common/types'; +import { ICondaService, IInterpreterService, InterpreterType, PythonInterpreter } from '../../../client/interpreter/contracts'; +import { IServiceContainer } from '../../../client/ioc/types'; + +/* Complex test to ensure we cover all combinations: +We could have written separate tests for each installer, but we'd be replicate code. +Both approachs have their benefits. + +Comnbinations of: +1. With and without a workspace. +2. Http Proxy configuration. +3. All products. +4. Different versions of Python. +5. With and without conda. +6. Conda environments with names and without names. +7. All installers. +*/ +suite('Module Installer', () => { + const pythonPath = path.join(__dirname, 'python'); + [CondaInstaller, PipInstaller, PipEnvInstaller].forEach(installerClass => { + // Proxy info is relevant only for PipInstaller. + const proxyServers = installerClass === PipInstaller ? ['', 'proxy:1234'] : ['']; + proxyServers.forEach(proxyServer => { + [undefined, Uri.file('/users/dev/xyz')].forEach(resource => { + // Conda info is relevant only for CondaInstaller. + const condaEnvs = installerClass === CondaInstaller ? [{ name: 'My-Env01', path: '' }, { name: '', path: '/conda/path' }] : []; + [undefined, ...condaEnvs].forEach(condaEnvInfo => { + const testProxySuffix = proxyServer.length === 0 ? 'without proxy info' : 'with proxy info'; + const testCondaEnv = condaEnvInfo ? (condaEnvInfo.name ? 'without conda name' : 'with conda path') : 'without conda'; + const testSuite = [testProxySuffix, testCondaEnv].filter(item => item.length > 0).join(', '); + suite(`${installerClass.name} (${testSuite})`, () => { + let disposables: Disposable[] = []; + let installer: IModuleInstaller; + let installationChannel: TypeMoq.IMock; + let serviceContainer: TypeMoq.IMock; + let terminalService: TypeMoq.IMock; + let pythonSettings: TypeMoq.IMock; + let interpreterService: TypeMoq.IMock; + const condaExecutable = 'my.exe'; + setup(() => { + serviceContainer = TypeMoq.Mock.ofType(); + + disposables = []; + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IDisposableRegistry), TypeMoq.It.isAny())).returns(() => disposables); + + installationChannel = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInstallationChannelManager), TypeMoq.It.isAny())).returns(() => installationChannel.object); + + const condaService = TypeMoq.Mock.ofType(); + condaService.setup(c => c.getCondaFile()).returns(() => Promise.resolve(condaExecutable)); + condaService.setup(c => c.getCondaEnvironment(TypeMoq.It.isAny())).returns(() => Promise.resolve(condaEnvInfo)); + + const configService = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IConfigurationService), TypeMoq.It.isAny())).returns(() => configService.object); + pythonSettings = TypeMoq.Mock.ofType(); + pythonSettings.setup(p => p.pythonPath).returns(() => pythonPath); + configService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object); + + terminalService = TypeMoq.Mock.ofType(); + const terminalServiceFactory = TypeMoq.Mock.ofType(); + terminalServiceFactory.setup(f => f.getTerminalService(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => terminalService.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ITerminalServiceFactory), TypeMoq.It.isAny())).returns(() => terminalServiceFactory.object); + + interpreterService = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInterpreterService), TypeMoq.It.isAny())).returns(() => interpreterService.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ICondaService), TypeMoq.It.isAny())).returns(() => condaService.object); + + const workspaceService = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IWorkspaceService), TypeMoq.It.isAny())).returns(() => workspaceService.object); + const http = TypeMoq.Mock.ofType(); + http.setup(h => h.get(TypeMoq.It.isValue('proxy'), TypeMoq.It.isAny())).returns(() => proxyServer); + workspaceService.setup(w => w.getConfiguration(TypeMoq.It.isValue('http'))).returns(() => http.object); + + installer = new installerClass(serviceContainer.object); + }); + teardown(() => { + disposables.forEach(disposable => { + if (disposable) { + disposable.dispose(); + } + }); + }); + function setActiveInterpreter(activeInterpreter?: PythonInterpreter) { + interpreterService + .setup(i => i.getActiveInterpreter(TypeMoq.It.isValue(resource))) + .returns(() => Promise.resolve(activeInterpreter)) + .verifiable(TypeMoq.Times.atLeastOnce()); + } + getModuleNamesForTesting().forEach(product => { + const moduleName = product.moduleName; + async function installModuleAndVerifyCommand(command: string, expectedArgs: string[]) { + terminalService.setup(t => t.sendCommand(TypeMoq.It.isValue(command), TypeMoq.It.isValue(expectedArgs))) + .returns(() => Promise.resolve()) + .verifiable(TypeMoq.Times.once()); + + await installer.installModule(moduleName, resource); + terminalService.verifyAll(); + } + + if (product.value === Product.pylint) { + // tslint:disable-next-line:no-shadowed-variable + generatePythonInterpreterVersions().forEach(interpreterInfo => { + const majorVersion = interpreterInfo.version_info[0]; + if (majorVersion === 2) { + const testTitle = `Ensure install arg is \'pylint<2.0.0\' in ${interpreterInfo.version_info.join('.')}`; + if (installerClass === PipInstaller) { + test(testTitle, async () => { + setActiveInterpreter(interpreterInfo); + const proxyArgs = proxyServer.length === 0 ? [] : ['--proxy', proxyServer]; + const expectedArgs = ['-m', 'pip', ...proxyArgs, 'install', '-U', '"pylint<2.0.0"']; + await installModuleAndVerifyCommand(pythonPath, expectedArgs); + }); + } + if (installerClass === PipEnvInstaller) { + test(testTitle, async () => { + setActiveInterpreter(interpreterInfo); + const expectedArgs = ['install', '"pylint<2.0.0"', '--dev']; + await installModuleAndVerifyCommand(pipenvName, expectedArgs); + }); + } + if (installerClass === CondaInstaller) { + test(testTitle, async () => { + setActiveInterpreter(interpreterInfo); + const expectedArgs = ['install']; + if (condaEnvInfo && condaEnvInfo.name) { + expectedArgs.push('--name'); + expectedArgs.push(condaEnvInfo.name); + } else if (condaEnvInfo && condaEnvInfo.path) { + expectedArgs.push('--prefix'); + expectedArgs.push(condaEnvInfo.path); + } + expectedArgs.push('"pylint<2.0.0"'); + await installModuleAndVerifyCommand(condaExecutable, expectedArgs); + }); + } + } else { + const testTitle = `Ensure install arg is \'pylint\' in ${interpreterInfo.version_info.join('.')}`; + if (installerClass === PipInstaller) { + test(testTitle, async () => { + setActiveInterpreter(interpreterInfo); + const proxyArgs = proxyServer.length === 0 ? [] : ['--proxy', proxyServer]; + const expectedArgs = ['-m', 'pip', ...proxyArgs, 'install', '-U', 'pylint']; + await installModuleAndVerifyCommand(pythonPath, expectedArgs); + }); + } + if (installerClass === PipEnvInstaller) { + test(testTitle, async () => { + setActiveInterpreter(interpreterInfo); + const expectedArgs = ['install', 'pylint', '--dev']; + await installModuleAndVerifyCommand(pipenvName, expectedArgs); + }); + } + if (installerClass === CondaInstaller) { + test(testTitle, async () => { + setActiveInterpreter(interpreterInfo); + const expectedArgs = ['install']; + if (condaEnvInfo && condaEnvInfo.name) { + expectedArgs.push('--name'); + expectedArgs.push(condaEnvInfo.name); + } else if (condaEnvInfo && condaEnvInfo.path) { + expectedArgs.push('--prefix'); + expectedArgs.push(condaEnvInfo.path); + } + expectedArgs.push('pylint'); + await installModuleAndVerifyCommand(condaExecutable, expectedArgs); + }); + } + } + }); + return; + } + + if (installerClass === PipInstaller) { + test(`Ensure getActiveInterperter is used in PipInstaller (${product.name})`, async () => { + setActiveInterpreter(); + try { + await installer.installModule(product.name, resource); + } catch { + noop(); + } + interpreterService.verifyAll(); + }); + } + if (installerClass === PipInstaller) { + test(`Test Args (${product.name})`, async () => { + setActiveInterpreter(); + const proxyArgs = proxyServer.length === 0 ? [] : ['--proxy', proxyServer]; + const expectedArgs = ['-m', 'pip', ...proxyArgs, 'install', '-U', moduleName]; + await installModuleAndVerifyCommand(pythonPath, expectedArgs); + interpreterService.verifyAll(); + }); + } + if (installerClass === PipEnvInstaller) { + test(`Test args (${product.name})`, async () => { + setActiveInterpreter(); + const expectedArgs = ['install', moduleName, '--dev']; + await installModuleAndVerifyCommand(pipenvName, expectedArgs); + }); + } + if (installerClass === CondaInstaller) { + test(`Test args (${product.name})`, async () => { + setActiveInterpreter(); + const expectedArgs = ['install']; + if (condaEnvInfo && condaEnvInfo.name) { + expectedArgs.push('--name'); + expectedArgs.push(condaEnvInfo.name); + } else if (condaEnvInfo && condaEnvInfo.path) { + expectedArgs.push('--prefix'); + expectedArgs.push(condaEnvInfo.path); + } + expectedArgs.push(moduleName); + await installModuleAndVerifyCommand(condaExecutable, expectedArgs); + }); + } + }); + }); + }); + }); + }); + }); +}); + +function generatePythonInterpreterVersions() { + const versions: PythonVersionInfo[] = [[2, 7, 0, 'final'], [3, 4, 0, 'final'], [3, 5, 0, 'final'], [3, 6, 0, 'final'], [3, 7, 0, 'final']]; + return versions.map(version => { + const info = TypeMoq.Mock.ofType(); + info.setup((t: any) => t.then).returns(() => undefined); + info.setup(t => t.type).returns(() => InterpreterType.VirtualEnv); + info.setup(t => t.version_info).returns(() => version); + return info.object; + }); +} + +function getModuleNamesForTesting(): { name: string; value: Product; moduleName: string }[] { + return EnumEx.getNamesAndValues(Product) + .map(product => { + let moduleName = ''; + const mockSvc = TypeMoq.Mock.ofType().object; + const mockOutChnl = TypeMoq.Mock.ofType().object; + try { + const prodInstaller = new ProductInstaller(mockSvc, mockOutChnl); + moduleName = prodInstaller.translateProductToModuleName(product.value, ModuleNamePurpose.install); + return { name: product.name, value: product.value, moduleName }; + } catch { + return; + } + }) + .filter(item => item !== undefined) as { name: string; value: Product; moduleName: string }[]; +} diff --git a/src/test/common/moduleInstaller.test.ts b/src/test/common/moduleInstaller.test.ts index 291cc6d3f534..0cf8698fc644 100644 --- a/src/test/common/moduleInstaller.test.ts +++ b/src/test/common/moduleInstaller.test.ts @@ -3,7 +3,8 @@ import { expect } from 'chai'; import * as path from 'path'; import * as TypeMoq from 'typemoq'; -import { ConfigurationTarget, Uri } from 'vscode'; +import { ConfigurationTarget, Uri, WorkspaceConfiguration } from 'vscode'; +import { IWorkspaceService } from '../../client/common/application/types'; import { PythonSettings } from '../../client/common/configSettings'; import { ConfigurationService } from '../../client/common/configuration/service'; import { CondaInstaller } from '../../client/common/installer/condaInstaller'; @@ -103,6 +104,12 @@ suite('Module Installer', () => { ioc.serviceManager.addSingleton(IPlatformService, PlatformService); ioc.serviceManager.addSingleton(IConfigurationService, ConfigurationService); + const workspaceService = TypeMoq.Mock.ofType(); + ioc.serviceManager.addSingletonInstance(IWorkspaceService, workspaceService.object); + const http = TypeMoq.Mock.ofType(); + http.setup(h => h.get(TypeMoq.It.isValue('proxy'), TypeMoq.It.isAny())).returns(() => ''); + workspaceService.setup(w => w.getConfiguration(TypeMoq.It.isValue('http'))).returns(() => http.object); + ioc.registerMockProcessTypes(); ioc.serviceManager.addSingletonInstance(IsWindows, false); } diff --git a/src/test/unittests.ts b/src/test/unittests.ts index 8a8c41bfcb69..f2c965417650 100644 --- a/src/test/unittests.ts +++ b/src/test/unittests.ts @@ -91,7 +91,7 @@ if (require.main === module) { const timeoutArgIndex = args.findIndex(arg => arg.startsWith('timeout=')); const grepArgIndex = args.findIndex(arg => arg.startsWith('grep=')); const timeout: number | undefined = timeoutArgIndex >= 0 ? parseInt(args[timeoutArgIndex].split('=')[1].trim(), 10) : undefined; - let grep: string | undefined = timeoutArgIndex >= 0 ? args[grepArgIndex].split('=')[1].trim() : undefined; + let grep: string | undefined = grepArgIndex >= 0 ? args[grepArgIndex].split('=')[1].trim() : undefined; grep = grep && grep.length > 0 ? grep : undefined; runTests({ grep, timeout }); From 4a9172ad03e35bf9b4b7e913ce015ad1995dbf61 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 18 Jul 2018 11:12:26 -0700 Subject: [PATCH 14/27] Change default port in experimental debugger to 5678 (#2174) --- news/2 Fixes/2146.md | 1 + package.json | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 news/2 Fixes/2146.md diff --git a/news/2 Fixes/2146.md b/news/2 Fixes/2146.md new file mode 100644 index 000000000000..93e660fa18d3 --- /dev/null +++ b/news/2 Fixes/2146.md @@ -0,0 +1 @@ +Change the default port used in remote debugging using `Experimental` debugger to `5678`. diff --git a/package.json b/package.json index 2795d022a5c4..167c1f3b3a93 100644 --- a/package.json +++ b/package.json @@ -901,7 +901,7 @@ "name": "Attach (Remote Debug)", "type": "pythonExperimental", "request": "attach", - "port": 3000, + "port": 5678, "host": "localhost" } } @@ -1101,7 +1101,7 @@ "name": "Python Experimental: Attach", "type": "pythonExperimental", "request": "attach", - "port": 3000, + "port": 5678, "host": "localhost" }, { From 8be9833d236322d31411084a511069bd4db87c98 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 18 Jul 2018 13:38:13 -0700 Subject: [PATCH 15/27] Change the download links of the language server files (#2181) --- news/3 Code Health/2180.md | 1 + src/client/activation/downloader.ts | 24 +++++-- src/client/activation/platformData.ts | 15 ++-- src/test/activation/downloader.unit.test.ts | 68 +++++++++++++++++++ ...Data.test.ts => platformData.unit.test.ts} | 13 ++-- 5 files changed, 103 insertions(+), 18 deletions(-) create mode 100644 news/3 Code Health/2180.md create mode 100644 src/test/activation/downloader.unit.test.ts rename src/test/activation/{platformData.test.ts => platformData.unit.test.ts} (90%) diff --git a/news/3 Code Health/2180.md b/news/3 Code Health/2180.md new file mode 100644 index 000000000000..d49e681aa8c7 --- /dev/null +++ b/news/3 Code Health/2180.md @@ -0,0 +1 @@ +Change the download links of the language server files. diff --git a/src/client/activation/downloader.ts b/src/client/activation/downloader.ts index c80b7dd40856..8b7ed686babe 100644 --- a/src/client/activation/downloader.ts +++ b/src/client/activation/downloader.ts @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +'use strict'; + import * as fileSystem from 'fs'; import * as path from 'path'; import * as request from 'request'; @@ -11,7 +13,7 @@ import { createDeferred } from '../common/helpers'; import { IFileSystem, IPlatformService } from '../common/platform/types'; import { IExtensionContext, IOutputChannel } from '../common/types'; import { IServiceContainer } from '../ioc/types'; -import { PlatformData } from './platformData'; +import { PlatformData, PlatformName } from './platformData'; // tslint:disable-next-line:no-require-imports no-var-requires const StreamZip = require('node-stream-zip'); @@ -21,6 +23,13 @@ const downloadBaseFileName = 'Python-Language-Server'; const downloadVersion = '0.1.0'; const downloadFileExtension = '.nupkg'; +const DownloadLinks = { + [PlatformName.Windows32Bit]: `${downloadUriPrefix}/${downloadBaseFileName}-${PlatformName.Windows32Bit}.${downloadVersion}${downloadFileExtension}`, + [PlatformName.Windows64Bit]: `${downloadUriPrefix}/${downloadBaseFileName}-${PlatformName.Windows64Bit}.${downloadVersion}${downloadFileExtension}`, + [PlatformName.Linux64Bit]: `${downloadUriPrefix}/${downloadBaseFileName}-${PlatformName.Linux64Bit}.${downloadVersion}${downloadFileExtension}`, + [PlatformName.Mac64Bit]: `${downloadUriPrefix}/${downloadBaseFileName}-${PlatformName.Mac64Bit}.${downloadVersion}${downloadFileExtension}` +}; + export class LanguageServerDownloader { private readonly output: OutputChannel; private readonly platform: IPlatformService; @@ -34,13 +43,17 @@ export class LanguageServerDownloader { this.platformData = new PlatformData(this.platform, this.fs); } - public async downloadLanguageServer(context: IExtensionContext): Promise { + public async getDownloadUri() { const platformString = await this.platformData.getPlatformName(); - const enginePackageFileName = `${downloadBaseFileName}-${platformString}.${downloadVersion}${downloadFileExtension}`; + return DownloadLinks[platformString]; + } + + public async downloadLanguageServer(context: IExtensionContext): Promise { + const downloadUri = await this.getDownloadUri(); let localTempFilePath = ''; try { - localTempFilePath = await this.downloadFile(downloadUriPrefix, enginePackageFileName, 'Downloading Microsoft Python Language Server... '); + localTempFilePath = await this.downloadFile(downloadUri, 'Downloading Microsoft Python Language Server... '); await this.unpackArchive(context.extensionPath, localTempFilePath); } catch (err) { this.output.appendLine('failed.'); @@ -53,8 +66,7 @@ export class LanguageServerDownloader { } } - private async downloadFile(location: string, fileName: string, title: string): Promise { - const uri = `${location}/${fileName}`; + private async downloadFile(uri: string, title: string): Promise { this.output.append(`Downloading ${uri}... `); const tempFile = await this.fs.createTemporaryFile(downloadFileExtension); diff --git a/src/client/activation/platformData.ts b/src/client/activation/platformData.ts index 60448dccc850..8564b8625b44 100644 --- a/src/client/activation/platformData.ts +++ b/src/client/activation/platformData.ts @@ -9,20 +9,27 @@ import { language_server_win_x86_sha512 } from './languageServerHashes'; +export enum PlatformName { + Windows32Bit = 'win-x86', + Windows64Bit = 'win-x64', + Mac64Bit = 'osx-x64', + Linux64Bit = 'linux-x64' +} + export class PlatformData { constructor(private platform: IPlatformService, fs: IFileSystem) { } - public async getPlatformName(): Promise { + public async getPlatformName(): Promise { if (this.platform.isWindows) { - return this.platform.is64bit ? 'win-x64' : 'win-x86'; + return this.platform.is64bit ? PlatformName.Windows64Bit : PlatformName.Windows32Bit; } if (this.platform.isMac) { - return 'osx-x64'; + return PlatformName.Mac64Bit; } if (this.platform.isLinux) { if (!this.platform.is64bit) { throw new Error('Microsoft Python Language Server does not support 32-bit Linux.'); } - return 'linux-x64'; + return PlatformName.Linux64Bit; } throw new Error('Unknown OS platform.'); } diff --git a/src/test/activation/downloader.unit.test.ts b/src/test/activation/downloader.unit.test.ts new file mode 100644 index 000000000000..c75b5e3b3d49 --- /dev/null +++ b/src/test/activation/downloader.unit.test.ts @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:no-unused-variable + +import * as assert from 'assert'; +import * as TypeMoq from 'typemoq'; +import { LanguageServerDownloader } from '../../client/activation/downloader'; +import { IFileSystem, IPlatformService } from '../../client/common/platform/types'; +import { IOutputChannel } from '../../client/common/types'; +import { IServiceContainer } from '../../client/ioc/types'; + +const downloadUriPrefix = 'https://pvsc.blob.core.windows.net/python-language-server'; +const downloadBaseFileName = 'Python-Language-Server'; +const downloadVersion = '0.1.0'; +const downloadFileExtension = '.nupkg'; + +suite('Activation - Downloader', () => { + let languageServerDownloader: LanguageServerDownloader; + let serviceContainer: TypeMoq.IMock; + let platformService: TypeMoq.IMock; + setup(() => { + serviceContainer = TypeMoq.Mock.ofType(); + platformService = TypeMoq.Mock.ofType(); + const fs = TypeMoq.Mock.ofType(); + const output = TypeMoq.Mock.ofType(); + + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IOutputChannel), TypeMoq.It.isAny())).returns(() => output.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPlatformService))).returns(() => platformService.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IFileSystem))).returns(() => fs.object); + + languageServerDownloader = new LanguageServerDownloader(serviceContainer.object, ''); + }); + type PlatformIdentifier = { + windows?: boolean; + mac?: boolean; + linux?: boolean; + is64Bit?: boolean; + }; + function setupPlatform(platform: PlatformIdentifier) { + platformService.setup(x => x.isWindows).returns(() => platform.windows === true); + platformService.setup(x => x.isMac).returns(() => platform.mac === true); + platformService.setup(x => x.isLinux).returns(() => platform.linux === true); + platformService.setup(x => x.is64bit).returns(() => platform.is64Bit === true); + } + test('Windows 32Bit', async () => { + setupPlatform({ windows: true }); + const link = await languageServerDownloader.getDownloadUri(); + assert.equal(link, `${downloadUriPrefix}/${downloadBaseFileName}-win-x86.${downloadVersion}${downloadFileExtension}`); + }); + test('Windows 64Bit', async () => { + setupPlatform({ windows: true, is64Bit: true }); + const link = await languageServerDownloader.getDownloadUri(); + assert.equal(link, `${downloadUriPrefix}/${downloadBaseFileName}-win-x64.${downloadVersion}${downloadFileExtension}`); + }); + test('Mac 64Bit', async () => { + setupPlatform({ mac: true, is64Bit: true }); + const link = await languageServerDownloader.getDownloadUri(); + assert.equal(link, `${downloadUriPrefix}/${downloadBaseFileName}-osx-x64.${downloadVersion}${downloadFileExtension}`); + }); + test('Linux 64Bit', async () => { + setupPlatform({ linux: true, is64Bit: true }); + const link = await languageServerDownloader.getDownloadUri(); + assert.equal(link, `${downloadUriPrefix}/${downloadBaseFileName}-linux-x64.${downloadVersion}${downloadFileExtension}`); + }); +}); diff --git a/src/test/activation/platformData.test.ts b/src/test/activation/platformData.unit.test.ts similarity index 90% rename from src/test/activation/platformData.test.ts rename to src/test/activation/platformData.unit.test.ts index 6d847253c05f..80ce2ce494be 100644 --- a/src/test/activation/platformData.test.ts +++ b/src/test/activation/platformData.unit.test.ts @@ -6,7 +6,6 @@ import * as assert from 'assert'; import * as TypeMoq from 'typemoq'; import { PlatformData } from '../../client/activation/platformData'; import { IFileSystem, IPlatformService } from '../../client/common/platform/types'; -import { initialize } from '../initialize'; const testDataWinMac = [ { isWindows: true, is64Bit: true, expectedName: 'win-x64' }, @@ -31,8 +30,6 @@ const testDataModuleName = [ // tslint:disable-next-line:max-func-body-length suite('Activation - platform data', () => { - suiteSetup(initialize); - test('Name and hash (Windows/Mac)', async () => { for (const t of testDataWinMac) { const platformService = TypeMoq.Mock.ofType(); @@ -43,11 +40,11 @@ suite('Activation - platform data', () => { const fs = TypeMoq.Mock.ofType(); const pd = new PlatformData(platformService.object, fs.object); - let actual = await pd.getPlatformName(); + const actual = await pd.getPlatformName(); assert.equal(actual, t.expectedName, `${actual} does not match ${t.expectedName}`); - actual = await pd.getExpectedHash(); - assert.equal(actual, t.expectedName, `${actual} hash not match ${t.expectedName}`); + const actualHash = await pd.getExpectedHash(); + assert.equal(actualHash, t.expectedName, `${actual} hash not match ${t.expectedName}`); } }); test('Name and hash (Linux)', async () => { @@ -62,10 +59,10 @@ suite('Activation - platform data', () => { fs.setup(x => x.readFile(TypeMoq.It.isAnyString())).returns(() => Promise.resolve(`NAME="name"\nID=${t.name}\nID_LIKE=debian`)); const pd = new PlatformData(platformService.object, fs.object); - let actual = await pd.getPlatformName(); + const actual = await pd.getPlatformName(); assert.equal(actual, t.expectedName, `${actual} does not match ${t.expectedName}`); - actual = await pd.getExpectedHash(); + const actualHash = await pd.getExpectedHash(); assert.equal(actual, t.expectedName, `${actual} hash not match ${t.expectedName}`); } }); From 0afd058e3e6fc2517d6e0dc7657f5402a4b2935a Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 18 Jul 2018 14:45:18 -0700 Subject: [PATCH 16/27] Change shortcut from ctrl+enter to shift+enter (#2189) --- .github/test_plan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/test_plan.md b/.github/test_plan.md index 8dc68cca76ce..a98b21fea0b6 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -33,7 +33,7 @@ - [ ] `Run Selection/Line in Python Terminal` - [ ] Right-click - [ ] Command - - [ ] `Ctrl-Enter` + - [ ] `Shift+Enter` #### Virtual environments From 28e5c60116296036dd7c7f1fbe703f30c70c77f0 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 18 Jul 2018 14:57:08 -0700 Subject: [PATCH 17/27] Register test manager when using the new language server (#2187) --- news/2 Fixes/2186.md | 1 + src/client/activation/languageServer.ts | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 news/2 Fixes/2186.md diff --git a/news/2 Fixes/2186.md b/news/2 Fixes/2186.md new file mode 100644 index 000000000000..a2f6df4f564a --- /dev/null +++ b/news/2 Fixes/2186.md @@ -0,0 +1 @@ +Register test manager when using the new language server. diff --git a/src/client/activation/languageServer.ts b/src/client/activation/languageServer.ts index 5154b92a41af..14e0d53ff0f1 100644 --- a/src/client/activation/languageServer.ts +++ b/src/client/activation/languageServer.ts @@ -11,7 +11,7 @@ import { isTestExecution, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; import { createDeferred, Deferred } from '../common/helpers'; import { IFileSystem, IPlatformService } from '../common/platform/types'; import { StopWatch } from '../common/stopWatch'; -import { IConfigurationService, IExtensionContext, IOutputChannel, IPythonSettings } from '../common/types'; +import { IConfigurationService, IExtensionContext, ILogger, IOutputChannel, IPythonSettings } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { PYTHON_LANGUAGE_SERVER_DOWNLOADED, @@ -19,6 +19,7 @@ import { PYTHON_LANGUAGE_SERVER_ERROR } from '../telemetry/constants'; import { getTelemetryReporter } from '../telemetry/telemetry'; +import { IUnitTestManagementService } from '../unittests/types'; import { LanguageServerDownloader } from './downloader'; import { InterpreterData, InterpreterDataService } from './interpreterDataService'; import { PlatformData } from './platformData'; @@ -89,6 +90,11 @@ export class LanguageServerExtensionActivator implements IExtensionActivator { if (!clientOptions) { return false; } + + const testManagementService = this.services.get(IUnitTestManagementService); + testManagementService.activate() + .catch(ex => this.services.get(ILogger).logError('Failed to activate Unit Tests', ex)); + return this.startLanguageServer(clientOptions); } From 6607c104b978727b45cc08a7da6b45ea3703f84a Mon Sep 17 00:00:00 2001 From: Derek Keeler Date: Wed, 18 Jul 2018 16:27:47 -0600 Subject: [PATCH 18/27] Add two new popups: One to switch to new LS, another to ask for feedback. (#2173) --- news/1 Enhancements/2127.md | 1 + src/client/activation/languageServer.ts | 21 ++- src/client/common/types.ts | 20 +++ src/client/common/utils.ts | 16 ++ src/client/debugger/banner.ts | 4 +- src/client/debugger/serviceRegistry.ts | 9 +- src/client/debugger/types.ts | 10 -- src/client/extension.ts | 6 +- .../languageServerSurveyBanner.ts | 143 ++++++++++++++++++ .../proposeLanguageServerBanner.ts | 124 +++++++++++++++ src/client/providers/jediProxy.ts | 12 +- .../unittests/pytest/services/argsService.ts | 7 +- src/test/debugger/banner.unit.test.ts | 4 +- .../banners/languageServerSurvey.unit.test.ts | 105 +++++++++++++ ...roposeNewLanguageServerBanner.unit.test.ts | 85 +++++++++++ 15 files changed, 542 insertions(+), 25 deletions(-) create mode 100644 news/1 Enhancements/2127.md create mode 100644 src/client/languageServices/languageServerSurveyBanner.ts create mode 100644 src/client/languageServices/proposeLanguageServerBanner.ts create mode 100644 src/test/unittests/banners/languageServerSurvey.unit.test.ts create mode 100644 src/test/unittests/banners/proposeNewLanguageServerBanner.unit.test.ts diff --git a/news/1 Enhancements/2127.md b/news/1 Enhancements/2127.md new file mode 100644 index 000000000000..04135259c073 --- /dev/null +++ b/news/1 Enhancements/2127.md @@ -0,0 +1 @@ +Add two popups to the extension: one to ask users to move to the new language server, the other to request feedback from users of that language server. diff --git a/src/client/activation/languageServer.ts b/src/client/activation/languageServer.ts index 14e0d53ff0f1..8c416206a7b1 100644 --- a/src/client/activation/languageServer.ts +++ b/src/client/activation/languageServer.ts @@ -3,15 +3,18 @@ import { inject, injectable } from 'inversify'; import * as path from 'path'; -import { OutputChannel, Uri } from 'vscode'; -import { Disposable, LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient'; +import { CancellationToken, CompletionContext, OutputChannel, Position, + TextDocument, Uri } from 'vscode'; +import { Disposable, LanguageClient, LanguageClientOptions, + ProvideCompletionItemsSignature, ServerOptions } from 'vscode-languageclient'; import { IApplicationShell, ICommandManager, IWorkspaceService } from '../common/application/types'; import { PythonSettings } from '../common/configSettings'; import { isTestExecution, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; import { createDeferred, Deferred } from '../common/helpers'; import { IFileSystem, IPlatformService } from '../common/platform/types'; import { StopWatch } from '../common/stopWatch'; -import { IConfigurationService, IExtensionContext, ILogger, IOutputChannel, IPythonSettings } from '../common/types'; +import { BANNER_NAME_LS_SURVEY, IConfigurationService, IExtensionContext, ILogger, + IOutputChannel, IPythonExtensionBanner, IPythonSettings } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { PYTHON_LANGUAGE_SERVER_DOWNLOADED, @@ -51,6 +54,7 @@ export class LanguageServerExtensionActivator implements IExtensionActivator { private excludedFiles: string[] = []; private typeshedPaths: string[] = []; private loadExtensionArgs: {} | undefined; + private surveyBanner: IPythonExtensionBanner; // tslint:disable-next-line:no-unused-variable private progressReporting: ProgressReporting | undefined; @@ -81,6 +85,8 @@ export class LanguageServerExtensionActivator implements IExtensionActivator { } )); + this.surveyBanner = services.get(IPythonExtensionBanner, BANNER_NAME_LS_SURVEY); + (this.configuration.getSettings() as PythonSettings).addListener('change', this.onSettingsChanged); } @@ -155,6 +161,7 @@ export class LanguageServerExtensionActivator implements IExtensionActivator { if (this.loadExtensionArgs) { this.languageClient!.sendRequest('python/loadExtension', this.loadExtensionArgs); } + this.startupCompleted.resolve(); } @@ -250,6 +257,14 @@ export class LanguageServerExtensionActivator implements IExtensionActivator { testEnvironment: isTestExecution(), analysisUpdates: true, traceLogging + }, + middleware: { + provideCompletionItem: (document: TextDocument, position: Position, context: CompletionContext, token: CancellationToken, next: ProvideCompletionItemsSignature) => { + if (this.surveyBanner) { + this.surveyBanner.showBanner().ignoreErrors(); + } + return next(document, position, context, token); + } } }; } diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 4e5c35215e63..83dee6718c57 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -267,3 +267,23 @@ export const IBrowserService = Symbol('IBrowserService'); export interface IBrowserService { launch(url: string): void; } + +export const IExperimentalDebuggerBanner = Symbol('IExperimentalDebuggerBanner'); +export interface IExperimentalDebuggerBanner { + enabled: boolean; + initialize(): void; + showBanner(): Promise; + shouldShowBanner(): Promise; + disable(): Promise; + launchSurvey(): Promise; +} + +export const IPythonExtensionBanner = Symbol('IPythonExtensionBanner'); +export interface IPythonExtensionBanner { + enabled: boolean; + shownCount: Promise; + optionLabels: string[]; + showBanner(): Promise; +} +export const BANNER_NAME_LS_SURVEY: string = 'LSSurveyBanner'; +export const BANNER_NAME_PROPOSE_LS: string = 'ProposeLS'; diff --git a/src/client/common/utils.ts b/src/client/common/utils.ts index 3e803b388466..4cd5f2c027e0 100644 --- a/src/client/common/utils.ts +++ b/src/client/common/utils.ts @@ -1,6 +1,7 @@ 'use strict'; // tslint:disable: no-any one-line no-suspicious-comment prefer-template prefer-const no-unnecessary-callback-wrapper no-function-expression no-string-literal no-control-regex no-shadowed-variable +import * as crypto from 'crypto'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; @@ -111,3 +112,18 @@ export function arePathsSame(path1: string, path2: string) { return path1 === path2; } } + +function getRandom(): number { + let num: number = 0; + + const buf: Buffer = crypto.randomBytes(2); + num = (buf.readUInt8(0) << 8) + buf.readUInt8(1); + + const maxValue: number = Math.pow(16, 4) - 1; + return (num / maxValue); +} + +export function getRandomBetween(min: number = 0, max: number = 10): number { + const randomVal: number = getRandom(); + return min + (randomVal * (max - min)); +} diff --git a/src/client/debugger/banner.ts b/src/client/debugger/banner.ts index a5971b7cfe36..480dc604b5e0 100644 --- a/src/client/debugger/banner.ts +++ b/src/client/debugger/banner.ts @@ -8,10 +8,10 @@ import { inject, injectable } from 'inversify'; import { Disposable } from 'vscode'; import { IApplicationEnvironment, IApplicationShell, IDebugService } from '../common/application/types'; import '../common/extensions'; -import { IBrowserService, IDisposableRegistry, ILogger, IPersistentStateFactory } from '../common/types'; +import { IBrowserService, IDisposableRegistry, IExperimentalDebuggerBanner, + ILogger, IPersistentStateFactory } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { ExperimentalDebuggerType } from './Common/constants'; -import { IExperimentalDebuggerBanner } from './types'; export enum PersistentStateKeys { ShowBanner = 'ShowBanner', diff --git a/src/client/debugger/serviceRegistry.ts b/src/client/debugger/serviceRegistry.ts index e634d828eed7..efb44040b4cf 100644 --- a/src/client/debugger/serviceRegistry.ts +++ b/src/client/debugger/serviceRegistry.ts @@ -9,16 +9,19 @@ import { FileSystem } from '../common/platform/fileSystem'; import { PlatformService } from '../common/platform/platformService'; import { IFileSystem, IPlatformService } from '../common/platform/types'; import { CurrentProcess } from '../common/process/currentProcess'; -import { ICurrentProcess, ISocketServer } from '../common/types'; +import { BANNER_NAME_LS_SURVEY, BANNER_NAME_PROPOSE_LS, ICurrentProcess, + IExperimentalDebuggerBanner, IPythonExtensionBanner, ISocketServer } from '../common/types'; import { ServiceContainer } from '../ioc/container'; import { ServiceManager } from '../ioc/serviceManager'; import { IServiceContainer, IServiceManager } from '../ioc/types'; +import { LanguageServerSurveyBanner } from '../languageServices/languageServerSurveyBanner'; +import { ProposeLanguageServerBanner } from '../languageServices/proposeLanguageServerBanner'; import { ExperimentalDebuggerBanner } from './banner'; import { DebugStreamProvider } from './Common/debugStreamProvider'; import { ProtocolLogger } from './Common/protocolLogger'; import { ProtocolParser } from './Common/protocolParser'; import { ProtocolMessageWriter } from './Common/protocolWriter'; -import { IDebugStreamProvider, IExperimentalDebuggerBanner, IProtocolLogger, IProtocolMessageWriter, IProtocolParser } from './types'; +import { IDebugStreamProvider, IProtocolLogger, IProtocolMessageWriter, IProtocolParser } from './types'; export function initializeIoc(): IServiceContainer { const cont = new Container(); @@ -42,4 +45,6 @@ function registerDebuggerTypes(serviceManager: IServiceManager) { export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(IExperimentalDebuggerBanner, ExperimentalDebuggerBanner); + serviceManager.addSingleton(IPythonExtensionBanner, LanguageServerSurveyBanner, BANNER_NAME_LS_SURVEY); + serviceManager.addSingleton(IPythonExtensionBanner, ProposeLanguageServerBanner, BANNER_NAME_PROPOSE_LS); } diff --git a/src/client/debugger/types.ts b/src/client/debugger/types.ts index 6a641a08e8ad..c34031b082af 100644 --- a/src/client/debugger/types.ts +++ b/src/client/debugger/types.ts @@ -38,13 +38,3 @@ export interface IProtocolMessageWriter { } export const IDebugConfigurationProvider = Symbol('DebugConfigurationProvider'); - -export const IExperimentalDebuggerBanner = Symbol('IExperimentalDebuggerBanner'); -export interface IExperimentalDebuggerBanner { - enabled: boolean; - initialize(): void; - showBanner(): Promise; - shouldShowBanner(): Promise; - disable(): Promise; - launchSurvey(): Promise; -} diff --git a/src/client/extension.ts b/src/client/extension.ts index d3528183e022..2761d4b6b863 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -26,13 +26,15 @@ import { registerTypes as platformRegisterTypes } from './common/platform/servic import { registerTypes as processRegisterTypes } from './common/process/serviceRegistry'; import { registerTypes as commonRegisterTypes } from './common/serviceRegistry'; import { ITerminalHelper } from './common/terminal/types'; -import { GLOBAL_MEMENTO, IConfigurationService, IDisposableRegistry, IExtensionContext, ILogger, IMemento, IOutputChannel, IPersistentStateFactory, WORKSPACE_MEMENTO } from './common/types'; +import { GLOBAL_MEMENTO, IConfigurationService, IDisposableRegistry, + IExperimentalDebuggerBanner, IExtensionContext, ILogger, IMemento, IOutputChannel, + IPersistentStateFactory, WORKSPACE_MEMENTO } from './common/types'; import { registerTypes as variableRegisterTypes } from './common/variables/serviceRegistry'; import { AttachRequestArguments, LaunchRequestArguments } from './debugger/Common/Contracts'; import { BaseConfigurationProvider } from './debugger/configProviders/baseProvider'; import { registerTypes as debugConfigurationRegisterTypes } from './debugger/configProviders/serviceRegistry'; import { registerTypes as debuggerRegisterTypes } from './debugger/serviceRegistry'; -import { IDebugConfigurationProvider, IExperimentalDebuggerBanner } from './debugger/types'; +import { IDebugConfigurationProvider } from './debugger/types'; import { registerTypes as formattersRegisterTypes } from './formatters/serviceRegistry'; import { IInterpreterSelector } from './interpreter/configuration/types'; import { ICondaService, IInterpreterService, PythonInterpreter } from './interpreter/contracts'; diff --git a/src/client/languageServices/languageServerSurveyBanner.ts b/src/client/languageServices/languageServerSurveyBanner.ts new file mode 100644 index 000000000000..7a375df9bb49 --- /dev/null +++ b/src/client/languageServices/languageServerSurveyBanner.ts @@ -0,0 +1,143 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable } from 'inversify'; +import { IApplicationShell } from '../common/application/types'; +import '../common/extensions'; +import { IBrowserService, IPersistentStateFactory, + IPythonExtensionBanner } from '../common/types'; +import { getRandomBetween } from '../common/utils'; + +// persistent state names, exported to make use of in testing +export enum LSSurveyStateKeys { + ShowBanner = 'ShowLSSurveyBanner', + ShowAttemptCounter = 'LSSurveyShowAttempt', + ShowAfterCompletionCount = 'LSSurveyShowCount' +} + +enum LSSurveyLabelIndex { + Yes, + No +} + +/* +This class represents a popup that will ask our users for some feedback after +a specific event occurs N times. +*/ +@injectable() +export class LanguageServerSurveyBanner implements IPythonExtensionBanner { + private disabledInCurrentSession: boolean = false; + private minCompletionsBeforeShow: number; + private maxCompletionsBeforeShow: number; + private isInitialized: boolean = false; + private bannerMessage: string = 'Can you please take 2 minutes to tell us how the Experimental Debugger is working for you?'; + private bannerLabels: string [] = [ 'Yes, take survey now', 'No, thanks']; + + constructor( + @inject(IApplicationShell) private appShell: IApplicationShell, + @inject(IPersistentStateFactory) private persistentState: IPersistentStateFactory, + @inject(IBrowserService) private browserService: IBrowserService, + showAfterMinimumEventsCount: number = 100, + showBeforeMaximumEventsCount: number = 500) + { + this.minCompletionsBeforeShow = showAfterMinimumEventsCount; + this.maxCompletionsBeforeShow = showBeforeMaximumEventsCount; + this.initialize(); + } + + public initialize(): void { + if (this.isInitialized) { + return; + } + this.isInitialized = true; + + if (this.minCompletionsBeforeShow >= this.maxCompletionsBeforeShow) { + this.disable().ignoreErrors(); + } + } + + public get optionLabels(): string[] { + return this.bannerLabels; + } + + public get shownCount(): Promise { + return this.getPythonLSLaunchCounter(); + } + + public get enabled(): boolean { + return this.persistentState.createGlobalPersistentState(LSSurveyStateKeys.ShowBanner, true).value; + } + + public async showBanner(): Promise { + if (!this.enabled || this.disabledInCurrentSession) { + return; + } + + const launchCounter: number = await this.incrementPythonLanguageServiceLaunchCounter(); + const show = await this.shouldShowBanner(launchCounter); + if (!show) { + return; + } + + const response = await this.appShell.showInformationMessage(this.bannerMessage, ...this.bannerLabels); + switch (response) { + case this.bannerLabels[LSSurveyLabelIndex.Yes]: + { + await this.launchSurvey(); + await this.disable(); + break; + } + case this.bannerLabels[LSSurveyLabelIndex.No]: { + await this.disable(); + break; + } + default: { + // Disable for the current session. + this.disabledInCurrentSession = true; + } + } + } + + public async shouldShowBanner(launchCounter?: number): Promise { + if (!this.enabled || this.disabledInCurrentSession) { + return false; + } + + if (! launchCounter) { + launchCounter = await this.getPythonLSLaunchCounter(); + } + const threshold: number = await this.getPythonLSLaunchThresholdCounter(); + + return launchCounter >= threshold; + } + + public async disable(): Promise { + await this.persistentState.createGlobalPersistentState(LSSurveyStateKeys.ShowBanner, false).updateValue(false); + } + + public async launchSurvey(): Promise { + const launchCounter = await this.getPythonLSLaunchCounter(); + this.browserService.launch(`https://www.research.net/r/LJZV9BZ?n=${launchCounter}`); + } + + private async incrementPythonLanguageServiceLaunchCounter(): Promise { + const state = this.persistentState.createGlobalPersistentState(LSSurveyStateKeys.ShowAttemptCounter, 0); + await state.updateValue(state.value + 1); + return state.value; + } + + private async getPythonLSLaunchCounter(): Promise { + const state = this.persistentState.createGlobalPersistentState(LSSurveyStateKeys.ShowAttemptCounter, 0); + return state.value; + } + + private async getPythonLSLaunchThresholdCounter(): Promise { + const state = this.persistentState.createGlobalPersistentState(LSSurveyStateKeys.ShowAfterCompletionCount, undefined); + if (state.value === undefined) { + await state.updateValue(getRandomBetween(this.minCompletionsBeforeShow, this.maxCompletionsBeforeShow)); + } + return state.value!; + } +} diff --git a/src/client/languageServices/proposeLanguageServerBanner.ts b/src/client/languageServices/proposeLanguageServerBanner.ts new file mode 100644 index 000000000000..7ae5dc6b5b98 --- /dev/null +++ b/src/client/languageServices/proposeLanguageServerBanner.ts @@ -0,0 +1,124 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable } from 'inversify'; +import { ConfigurationTarget } from 'vscode'; +import { IApplicationShell } from '../common/application/types'; +import '../common/extensions'; +import { IConfigurationService, IPersistentStateFactory, + IPythonExtensionBanner } from '../common/types'; +import { getRandomBetween } from '../common/utils'; + +// persistent state names, exported to make use of in testing +export enum ProposeLSStateKeys { + ShowBanner = 'ProposeLSBanner' +} + +enum ProposeLSLabelIndex { + Yes, + No, + Later +} + +/* +This class represents a popup that propose that the user try out a new +feature of the extension, and optionally enable that new feature if they +choose to do so. It is meant to be shown only to a subset of our users, +and will show as soon as it is instructed to do so, if a random sample +function enables the popup for this user. +*/ +@injectable() +export class ProposeLanguageServerBanner implements IPythonExtensionBanner { + private initialized?: boolean; + private disabledInCurrentSession: boolean = false; + private sampleSizePerHundred: number; + private bannerMessage: string = 'Try out Preview of our new Python Language Server to get richer and faster IntelliSense completions, and syntax errors as you type.'; + private bannerLabels: string[] = [ 'Try it now', 'No thanks', 'Remind me Later' ]; + + constructor( + @inject(IApplicationShell) private appShell: IApplicationShell, + @inject(IPersistentStateFactory) private persistentState: IPersistentStateFactory, + @inject(IConfigurationService) private configuration: IConfigurationService, + sampleSizePerOneHundredUsers: number = 10) + { + this.sampleSizePerHundred = sampleSizePerOneHundredUsers; + this.initialize(); + } + + public initialize() { + if (this.initialized) { + return; + } + this.initialized = true; + + // Don't even bother adding handlers if banner has been turned off. + if (!this.enabled) { + return; + } + + // we only want 10% of folks that use Jedi to see this survey. + const randomSample: number = getRandomBetween(0, 100); + if (randomSample >= this.sampleSizePerHundred) { + this.disable().ignoreErrors(); + return; + } + } + + public get shownCount(): Promise { + return Promise.resolve(-1); // we don't count this popup banner! + } + + public get optionLabels(): string[] { + return this.bannerLabels; + } + + public get enabled(): boolean { + return this.persistentState.createGlobalPersistentState(ProposeLSStateKeys.ShowBanner, true).value; + } + + public async showBanner(): Promise { + if (!this.enabled) { + return; + } + + const show = await this.shouldShowBanner(); + if (!show) { + return; + } + + const response = await this.appShell.showInformationMessage(this.bannerMessage, ...this.bannerLabels); + switch (response) { + case this.bannerLabels[ProposeLSLabelIndex.Yes]: { + await this.enableNewLanguageServer(); + await this.disable(); + break; + } + case this.bannerLabels[ProposeLSLabelIndex.No]: { + await this.disable(); + break; + } + case this.bannerLabels[ProposeLSLabelIndex.Later]: { + this.disabledInCurrentSession = true; + break; + } + default: { + // Disable for the current session. + this.disabledInCurrentSession = true; + } + } + } + + public async shouldShowBanner(): Promise { + return Promise.resolve(this.enabled && !this.disabledInCurrentSession); + } + + public async disable(): Promise { + await this.persistentState.createGlobalPersistentState(ProposeLSStateKeys.ShowBanner, false).updateValue(false); + } + + public async enableNewLanguageServer(): Promise { + await this.configuration.updateSettingAsync('jediEnabled', false, undefined, ConfigurationTarget.Global); + } +} diff --git a/src/client/providers/jediProxy.ts b/src/client/providers/jediProxy.ts index 73e1dbe44992..962b6dc8c0b0 100644 --- a/src/client/providers/jediProxy.ts +++ b/src/client/providers/jediProxy.ts @@ -6,14 +6,16 @@ import { ChildProcess } from 'child_process'; import * as fs from 'fs-extra'; import * as path from 'path'; import * as pidusage from 'pidusage'; -import { CancellationToken, CancellationTokenSource, CompletionItemKind, Disposable, SymbolKind, Uri } from 'vscode'; +import { CancellationToken, CancellationTokenSource, CompletionItemKind, + Disposable, SymbolKind, Uri } from 'vscode'; import { PythonSettings } from '../common/configSettings'; +import { isTestExecution } from '../common/constants'; import { debounce, swallowExceptions } from '../common/decorators'; import '../common/extensions'; import { createDeferred, Deferred } from '../common/helpers'; import { IPythonExecutionFactory } from '../common/process/types'; import { StopWatch } from '../common/stopWatch'; -import { ILogger } from '../common/types'; +import { BANNER_NAME_PROPOSE_LS, ILogger, IPythonExtensionBanner } from '../common/types'; import { IEnvironmentVariablesProvider } from '../common/variables/types'; import { IServiceContainer } from '../ioc/types'; import * as logger from './../common/logger'; @@ -148,6 +150,7 @@ export class JediProxy implements Disposable { private pidUsageFailures = { timer: new StopWatch(), counter: 0 }; private lastCmdIdProcessed?: number; private lastCmdIdProcessedForPidUsage?: number; + private proposeNewLanguageServerPopup: IPythonExtensionBanner; public constructor(private extensionRootDir: string, workspacePath: string, private serviceContainer: IServiceContainer) { this.workspacePath = workspacePath; @@ -158,6 +161,8 @@ export class JediProxy implements Disposable { this.initialized = createDeferred(); this.startLanguageServer().then(() => this.initialized.resolve()).ignoreErrors(); + this.proposeNewLanguageServerPopup = serviceContainer.get(IPythonExtensionBanner, BANNER_NAME_PROPOSE_LS); + this.checkJediMemoryFootprint().ignoreErrors(); } @@ -292,6 +297,9 @@ export class JediProxy implements Disposable { private async startLanguageServer(): Promise { const newAutoComletePaths = await this.buildAutoCompletePaths(); this.additionalAutoCompletePaths = newAutoComletePaths; + if (!isTestExecution()) { + await this.proposeNewLanguageServerPopup.showBanner(); + } return this.restartLanguageServer(); } private restartLanguageServer(): Promise { diff --git a/src/client/unittests/pytest/services/argsService.ts b/src/client/unittests/pytest/services/argsService.ts index c539fdbae265..e8942a840b9f 100644 --- a/src/client/unittests/pytest/services/argsService.ts +++ b/src/client/unittests/pytest/services/argsService.ts @@ -9,6 +9,7 @@ import { IArgumentsHelper, IArgumentsService, TestFilter } from '../../types'; const OptionsWithArguments = ['-c', '-k', '-m', '-o', '-p', '-r', '-W', '--assert', '--basetemp', '--capture', '--color', '--confcutdir', + '--cov', '--cov-config', '--cov-fail-under', '--cov-report', '--deselect', '--dist', '--doctest-glob', '--doctest-report', '--durations', '--ignore', '--import-mode', '--junit-prefix', '--junit-xml', '--last-failed-no-failures', @@ -21,12 +22,14 @@ const OptionsWithArguments = ['-c', '-k', '-m', '-o', '-p', '-r', '-W', '--numprocesses', '--rsyncdir', '--rsyncignore', '--tx']; const OptionsWithoutArguments = ['--cache-clear', '--cache-show', '--collect-in-virtualenv', - '--collect-only', '--continue-on-collection-errors', '--debug', '--disable-pytest-warnings', + '--collect-only', '--continue-on-collection-errors', + '--cov-append', '--cov-branch', '--debug', '--disable-pytest-warnings', '--disable-warnings', '--doctest-continue-on-failure', '--doctest-ignore-import-errors', '--doctest-modules', '--exitfirst', '--failed-first', '--ff', '--fixtures', '--fixtures-per-test', '--force-sugar', '--full-trace', '--funcargs', '--help', '--keep-duplicates', '--last-failed', '--lf', '--markers', '--new-first', '--nf', - '--no-print-logs', '--noconftest', '--old-summary', '--pdb', '--pyargs', + '--no-cov', '--no-cov-on-fail', + '--no-print-logs', '--noconftest', '--old-summary', '--pdb', '--pyargs', '-PyTest, Unittest-pyargs', '--quiet', '--runxfail', '--setup-only', '--setup-plan', '--setup-show', '--showlocals', '--strict', '--trace-config', '--verbose', '--version', '-h', '-l', '-q', '-s', '-v', '-x', '--boxed', '--forked', '--looponfail', '--tx', '-d']; diff --git a/src/test/debugger/banner.unit.test.ts b/src/test/debugger/banner.unit.test.ts index 22706fea11ae..59ac609c5c02 100644 --- a/src/test/debugger/banner.unit.test.ts +++ b/src/test/debugger/banner.unit.test.ts @@ -9,10 +9,10 @@ import { expect } from 'chai'; import * as typemoq from 'typemoq'; import { DebugSession } from 'vscode'; import { IApplicationShell, IDebugService } from '../../client/common/application/types'; -import { IBrowserService, IDisposableRegistry, ILogger, IPersistentState, IPersistentStateFactory } from '../../client/common/types'; +import { IBrowserService, IDisposableRegistry, IExperimentalDebuggerBanner, + ILogger, IPersistentState, IPersistentStateFactory } from '../../client/common/types'; import { ExperimentalDebuggerBanner, PersistentStateKeys } from '../../client/debugger/banner'; import { ExperimentalDebuggerType } from '../../client/debugger/Common/constants'; -import { IExperimentalDebuggerBanner } from '../../client/debugger/types'; import { IServiceContainer } from '../../client/ioc/types'; suite('Debugging - Banner', () => { diff --git a/src/test/unittests/banners/languageServerSurvey.unit.test.ts b/src/test/unittests/banners/languageServerSurvey.unit.test.ts new file mode 100644 index 000000000000..bf3b1660cab8 --- /dev/null +++ b/src/test/unittests/banners/languageServerSurvey.unit.test.ts @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:no-any max-func-body-length + +import { expect } from 'chai'; +import * as typemoq from 'typemoq'; +import { IApplicationShell } from '../../../client/common/application/types'; +import { IBrowserService, IConfigurationService, IPersistentState, IPersistentStateFactory } from '../../../client/common/types'; +import { LanguageServerSurveyBanner, LSSurveyStateKeys } from '../../../client/languageServices/languageServerSurveyBanner'; + +suite('Language Server Survey Banner', () => { + let config: typemoq.IMock; + let appShell: typemoq.IMock; + let browser: typemoq.IMock; + const message = 'Can you please take 2 minutes to tell us how the Experimental Debugger is working for you?'; + const yes = 'Yes, take survey now'; + const no = 'No, thanks'; + + setup(() => { + config = typemoq.Mock.ofType(); + appShell = typemoq.Mock.ofType(); + browser = typemoq.Mock.ofType(); + }); + test('Is debugger enabled upon creation?', () => { + const enabledValue: boolean = true; + const attemptCounter: number = 0; + const completionsCount: number = 0; + const testBanner: LanguageServerSurveyBanner = preparePopup(attemptCounter, completionsCount, enabledValue, 0, 100, appShell.object, browser.object); + expect(testBanner.enabled).to.be.equal(true, 'Sampling 100/100 should always enable the banner.'); + }); + test('Do not show banner when it is disabled', () => { + appShell.setup(a => a.showInformationMessage(typemoq.It.isValue(message), + typemoq.It.isValue(yes), + typemoq.It.isValue(no))) + .verifiable(typemoq.Times.never()); + const enabledValue: boolean = true; + const attemptCounter: number = 0; + const completionsCount: number = 0; + const testBanner: LanguageServerSurveyBanner = preparePopup(attemptCounter, completionsCount, enabledValue, 0, 0, appShell.object, browser.object); + testBanner.showBanner().ignoreErrors(); + }); + test('shouldShowBanner must return false when Banner is implicitly disabled by sampling', () => { + const enabledValue: boolean = true; + const attemptCounter: number = 0; + const completionsCount: number = 0; + const testBanner: LanguageServerSurveyBanner = preparePopup(attemptCounter, completionsCount, enabledValue, 0, 0, appShell.object, browser.object); + expect(testBanner.enabled).to.be.equal(false, 'We implicitly disabled the banner, it should never show.'); + }); +}); + +function preparePopup(attemptCounter: number, completionsCount: number, enabledValue: boolean, minCompletionCount: number, maxCompletionCount: number, appShell: IApplicationShell, browser: IBrowserService): LanguageServerSurveyBanner { + const myfactory: typemoq.IMock = typemoq.Mock.ofType(); + const enabledValState: typemoq.IMock> = typemoq.Mock.ofType>(); + const attemptCountState: typemoq.IMock> = typemoq.Mock.ofType>(); + const completionCountState: typemoq.IMock> = typemoq.Mock.ofType>(); + + enabledValState.setup(a => a.updateValue(typemoq.It.isValue(true))).returns(() => { + enabledValue = true; + return Promise.resolve(); + }); + enabledValState.setup(a => a.updateValue(typemoq.It.isValue(false))).returns(() => { + enabledValue = false; + return Promise.resolve(); + }); + + attemptCountState.setup(a => a.updateValue(typemoq.It.isAnyNumber())).returns(() => { + attemptCounter += 1; + return Promise.resolve(); + }); + + completionCountState.setup(a => a.updateValue(typemoq.It.isAnyNumber())).returns(() => { + completionsCount += 1; + return Promise.resolve(); + }); + + enabledValState.setup(a => a.value).returns(() => enabledValue); + attemptCountState.setup(a => a.value).returns(() => attemptCounter); + completionCountState.setup(a => a.value).returns(() => completionsCount); + + myfactory.setup(a => a.createGlobalPersistentState(typemoq.It.isValue(LSSurveyStateKeys.ShowBanner), + typemoq.It.isValue(true))).returns(() => { + return enabledValState.object; + }); + myfactory.setup(a => a.createGlobalPersistentState(typemoq.It.isValue(LSSurveyStateKeys.ShowBanner), + typemoq.It.isValue(false))).returns(() => { + return enabledValState.object; + }); + myfactory.setup(a => a.createGlobalPersistentState(typemoq.It.isValue(LSSurveyStateKeys.ShowAttemptCounter), + typemoq.It.isAnyNumber())).returns(() => { + return attemptCountState.object; + }); + myfactory.setup(a => a.createGlobalPersistentState(typemoq.It.isValue(LSSurveyStateKeys.ShowAfterCompletionCount), + typemoq.It.isAnyNumber())).returns(() => { + return completionCountState.object; + }); + return new LanguageServerSurveyBanner( + appShell, + myfactory.object, + browser, + minCompletionCount, + maxCompletionCount); +} diff --git a/src/test/unittests/banners/proposeNewLanguageServerBanner.unit.test.ts b/src/test/unittests/banners/proposeNewLanguageServerBanner.unit.test.ts new file mode 100644 index 000000000000..e11c7e75d637 --- /dev/null +++ b/src/test/unittests/banners/proposeNewLanguageServerBanner.unit.test.ts @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:no-any max-func-body-length + +import { expect } from 'chai'; +import * as typemoq from 'typemoq'; +import { IApplicationShell } from '../../../client/common/application/types'; +import { IConfigurationService, IPersistentState, IPersistentStateFactory } from '../../../client/common/types'; +import { ProposeLanguageServerBanner, ProposeLSStateKeys } from '../../../client/languageServices/proposeLanguageServerBanner'; + +suite('Propose New Language Server Banner', () => { + let config: typemoq.IMock; + let appShell: typemoq.IMock; + const message = 'Try out Preview of our new Python Language Server to get richer and faster IntelliSense completions, and syntax errors as you type.'; + const yes = 'Try it now'; + const no = 'No thanks'; + const later = 'Remind me Later'; + + setup(() => { + config = typemoq.Mock.ofType(); + appShell = typemoq.Mock.ofType(); + }); + test('Is debugger enabled upon creation?', () => { + const enabledValue: boolean = true; + const testBanner: ProposeLanguageServerBanner = preparePopup(enabledValue, 100, appShell.object, config.object); + expect(testBanner.enabled).to.be.equal(true, 'Sampling 100/100 should always enable the banner.'); + }); + test('Do not show banner when it is disabled', () => { + appShell.setup(a => a.showInformationMessage(typemoq.It.isValue(message), + typemoq.It.isValue(yes), + typemoq.It.isValue(no), + typemoq.It.isValue(later))) + .verifiable(typemoq.Times.never()); + const enabled: boolean = true; + const testBanner: ProposeLanguageServerBanner = preparePopup(enabled, 0, appShell.object, config.object); + testBanner.showBanner().ignoreErrors(); + }); + test('shouldShowBanner must return false when Banner is implicitly disabled by sampling', () => { + const enabled: boolean = true; + const testBanner: ProposeLanguageServerBanner = preparePopup(enabled, 0, appShell.object, config.object); + expect(testBanner.enabled).to.be.equal(false, 'We implicitly disabled the banner, it should never show.'); + }); + test('shouldShowBanner must return false when Banner is explicitly disabled', async () => { + const enabled: boolean = true; + const testBanner: ProposeLanguageServerBanner = preparePopup(enabled, 100, appShell.object, config.object); + + expect(await testBanner.shouldShowBanner()).to.be.equal(true, '100% sample size should always make the banner enabled.'); + await testBanner.disable(); + expect(await testBanner.shouldShowBanner()).to.be.equal(false, 'Explicitly disabled banner shouldShowBanner != false.'); + }); +}); + +function preparePopup(enabledValue: boolean, sampleValue: number, appShell: IApplicationShell, config: IConfigurationService): ProposeLanguageServerBanner { + const myfactory: typemoq.IMock = typemoq.Mock.ofType(); + const val: typemoq.IMock> = typemoq.Mock.ofType>(); + val.setup(a => a.updateValue(typemoq.It.isValue(true))).returns(() => { + enabledValue = true; + return Promise.resolve(); + }); + val.setup(a => a.updateValue(typemoq.It.isValue(false))).returns(() => { + enabledValue = false; + return Promise.resolve(); + }); + val.setup(a => a.value).returns(() => { + return enabledValue; + }); + myfactory.setup(a => a.createGlobalPersistentState(typemoq.It.isValue(ProposeLSStateKeys.ShowBanner), + typemoq.It.isValue(true))) + .returns(() => { + return val.object; + }); + myfactory.setup(a => a.createGlobalPersistentState(typemoq.It.isValue(ProposeLSStateKeys.ShowBanner), + typemoq.It.isValue(false))) + .returns(() => { + return val.object; + }); + return new ProposeLanguageServerBanner( + appShell, + myfactory.object, + config, + sampleValue); +} From 38ff12c359d75d1ce916e9f2e87454abe892b45a Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Wed, 18 Jul 2018 15:35:10 -0700 Subject: [PATCH 19/27] Final release prep (#2190) Part of #2023 --- CHANGELOG.md | 25 ++++++++++++++++++++++--- news/1 Enhancements/1591.md | 1 - news/1 Enhancements/2000.md | 1 - news/1 Enhancements/2107.md | 2 -- news/1 Enhancements/2113.md | 1 - news/1 Enhancements/2127.md | 1 - news/2 Fixes/2013.md | 1 - news/2 Fixes/2044.md | 1 - news/2 Fixes/2048.md | 1 - news/2 Fixes/2057.md | 1 - news/2 Fixes/2068.md | 2 -- news/2 Fixes/2076.md | 1 - news/2 Fixes/2079.md | 1 - news/2 Fixes/2146.md | 1 - news/2 Fixes/2186.md | 1 - news/3 Code Health/1986.md | 1 - news/3 Code Health/2128.md | 1 - news/3 Code Health/2150.md | 1 - news/3 Code Health/2180.md | 1 - package-lock.json | 2 +- package.json | 2 +- 21 files changed, 24 insertions(+), 25 deletions(-) delete mode 100644 news/1 Enhancements/1591.md delete mode 100644 news/1 Enhancements/2000.md delete mode 100644 news/1 Enhancements/2107.md delete mode 100644 news/1 Enhancements/2113.md delete mode 100644 news/1 Enhancements/2127.md delete mode 100644 news/2 Fixes/2013.md delete mode 100644 news/2 Fixes/2044.md delete mode 100644 news/2 Fixes/2048.md delete mode 100644 news/2 Fixes/2057.md delete mode 100644 news/2 Fixes/2068.md delete mode 100644 news/2 Fixes/2076.md delete mode 100644 news/2 Fixes/2079.md delete mode 100644 news/2 Fixes/2146.md delete mode 100644 news/2 Fixes/2186.md delete mode 100644 news/3 Code Health/1986.md delete mode 100644 news/3 Code Health/2128.md delete mode 100644 news/3 Code Health/2150.md delete mode 100644 news/3 Code Health/2180.md diff --git a/CHANGELOG.md b/CHANGELOG.md index d57d36b8f11d..f4fa900c30c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 2018.7.0-beta (10 July 2018) +## 2018.7.0 (18 July 2018) ### Thanks @@ -55,6 +55,15 @@ part of! 1. Language server now reports code analysis progress in the status bar. ([#1591](https://github.com/Microsoft/vscode-python/issues/1591)) +1. Only report Language Server download progress once. + ([#2000](https://github.com/Microsoft/vscode-python/issues/2000)) +1. Messages changes to reflect name of the language server: 'Microsoft Python Language Server'; + folder name changed from `analysis` to `languageServer`. + ([#2107](https://github.com/Microsoft/vscode-python/issues/2107)) +1. Set default analysis for language server to open files only. + ([#2113](https://github.com/Microsoft/vscode-python/issues/2113)) +1. Add two popups to the extension: one to ask users to move to the new language server, the other to request feedback from users of that language server. + ([#2127](https://github.com/Microsoft/vscode-python/issues/2127)) ### Fixes @@ -69,15 +78,25 @@ part of! 1. Change keyboard shortcut for `Run Selection/Line in Python Terminal` to not interfere with the Find/Replace dialog box. ([#2068](https://github.com/Microsoft/vscode-python/issues/2068)) +1. Relax validation of the environment `Path` variable. + ([#2076](https://github.com/Microsoft/vscode-python/issues/2076)) 1. `editor.formatOnType` is more reliable handling floating point numbers. ([#2079](https://github.com/Microsoft/vscode-python/issues/2079)) +1. Change the default port used in remote debugging using `Experimental` debugger to `5678`. + ([#2146](https://github.com/Microsoft/vscode-python/issues/2146)) +1. Register test manager when using the new language server. + ([#2186](https://github.com/Microsoft/vscode-python/issues/2186)) ### Code Health 1. Removed pre-commit hook that ran unit tests. ([#1986](https://github.com/Microsoft/vscode-python/issues/1986)) - - +1. Pass OS type to the debugger. + ([#2128](https://github.com/Microsoft/vscode-python/issues/2128)) +1. Ensure 'languageServer' directory is excluded from the build output. + ([#2150](https://github.com/Microsoft/vscode-python/issues/2150)) +1. Change the download links of the language server files. + ([#2180](https://github.com/Microsoft/vscode-python/issues/2180)) diff --git a/news/1 Enhancements/1591.md b/news/1 Enhancements/1591.md deleted file mode 100644 index 758c2b0b7234..000000000000 --- a/news/1 Enhancements/1591.md +++ /dev/null @@ -1 +0,0 @@ -Language server now reports code analysis progress in the status bar. diff --git a/news/1 Enhancements/2000.md b/news/1 Enhancements/2000.md deleted file mode 100644 index 319691492f78..000000000000 --- a/news/1 Enhancements/2000.md +++ /dev/null @@ -1 +0,0 @@ -Only report Language Server download progress once. (Thanks @MikhailArkhipov) diff --git a/news/1 Enhancements/2107.md b/news/1 Enhancements/2107.md deleted file mode 100644 index 22014105dda2..000000000000 --- a/news/1 Enhancements/2107.md +++ /dev/null @@ -1,2 +0,0 @@ -Messages changes to reflect name of the language server: 'Microsoft Python Language Server'. -Folder name changed from 'analysis' to 'languageServer'. \ No newline at end of file diff --git a/news/1 Enhancements/2113.md b/news/1 Enhancements/2113.md deleted file mode 100644 index 9455e9f6077d..000000000000 --- a/news/1 Enhancements/2113.md +++ /dev/null @@ -1 +0,0 @@ -Set default analysis for language server to open files only. (Thanks @MikhailArkhipov) diff --git a/news/1 Enhancements/2127.md b/news/1 Enhancements/2127.md deleted file mode 100644 index 04135259c073..000000000000 --- a/news/1 Enhancements/2127.md +++ /dev/null @@ -1 +0,0 @@ -Add two popups to the extension: one to ask users to move to the new language server, the other to request feedback from users of that language server. diff --git a/news/2 Fixes/2013.md b/news/2 Fixes/2013.md deleted file mode 100644 index 4524ae66a0ea..000000000000 --- a/news/2 Fixes/2013.md +++ /dev/null @@ -1 +0,0 @@ -Ensure dunder variables are always displayed in code completion when using the new language server. diff --git a/news/2 Fixes/2044.md b/news/2 Fixes/2044.md deleted file mode 100644 index 122287640547..000000000000 --- a/news/2 Fixes/2044.md +++ /dev/null @@ -1 +0,0 @@ -Store testId for files & suites during unittest discovery. diff --git a/news/2 Fixes/2048.md b/news/2 Fixes/2048.md deleted file mode 100644 index 1ef400d6de06..000000000000 --- a/news/2 Fixes/2048.md +++ /dev/null @@ -1 +0,0 @@ -`editor.formatOnType` no longer adds space after `*` in multi-line arguments. diff --git a/news/2 Fixes/2057.md b/news/2 Fixes/2057.md deleted file mode 100644 index 34db4fed4207..000000000000 --- a/news/2 Fixes/2057.md +++ /dev/null @@ -1 +0,0 @@ -Fix bug where tooltips would popup whenever a comma is typed within a string. diff --git a/news/2 Fixes/2068.md b/news/2 Fixes/2068.md deleted file mode 100644 index 062dfb4d3654..000000000000 --- a/news/2 Fixes/2068.md +++ /dev/null @@ -1,2 +0,0 @@ -Change keyboard shortcut for `Run Selection/Line in Python Terminal` to not -interfere with the Find/Replace dialog box. diff --git a/news/2 Fixes/2076.md b/news/2 Fixes/2076.md deleted file mode 100644 index 70ce03782a26..000000000000 --- a/news/2 Fixes/2076.md +++ /dev/null @@ -1 +0,0 @@ -Relax validation of the environment `Path` variable. diff --git a/news/2 Fixes/2079.md b/news/2 Fixes/2079.md deleted file mode 100644 index 6b0366702a18..000000000000 --- a/news/2 Fixes/2079.md +++ /dev/null @@ -1 +0,0 @@ -`editor.formatOnType` is more reliable handling floating point numbers. diff --git a/news/2 Fixes/2146.md b/news/2 Fixes/2146.md deleted file mode 100644 index 93e660fa18d3..000000000000 --- a/news/2 Fixes/2146.md +++ /dev/null @@ -1 +0,0 @@ -Change the default port used in remote debugging using `Experimental` debugger to `5678`. diff --git a/news/2 Fixes/2186.md b/news/2 Fixes/2186.md deleted file mode 100644 index a2f6df4f564a..000000000000 --- a/news/2 Fixes/2186.md +++ /dev/null @@ -1 +0,0 @@ -Register test manager when using the new language server. diff --git a/news/3 Code Health/1986.md b/news/3 Code Health/1986.md deleted file mode 100644 index e20a4c024e14..000000000000 --- a/news/3 Code Health/1986.md +++ /dev/null @@ -1 +0,0 @@ -Removed pre-commit hook that ran unit tests. diff --git a/news/3 Code Health/2128.md b/news/3 Code Health/2128.md deleted file mode 100644 index 22ac6aecbc7a..000000000000 --- a/news/3 Code Health/2128.md +++ /dev/null @@ -1 +0,0 @@ -Pass OS type to the debugger. diff --git a/news/3 Code Health/2150.md b/news/3 Code Health/2150.md deleted file mode 100644 index 77408e558502..000000000000 --- a/news/3 Code Health/2150.md +++ /dev/null @@ -1 +0,0 @@ -Ensure 'languageServer' directory is excluded from the build output. diff --git a/news/3 Code Health/2180.md b/news/3 Code Health/2180.md deleted file mode 100644 index d49e681aa8c7..000000000000 --- a/news/3 Code Health/2180.md +++ /dev/null @@ -1 +0,0 @@ -Change the download links of the language server files. diff --git a/package-lock.json b/package-lock.json index ee9849eb35e3..8bbc19bbb321 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "python", - "version": "2018.7.0-beta", + "version": "2018.7.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 167c1f3b3a93..d9bb9b5e83ef 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "Linting, Debugging (multi-threaded, remote), Intellisense, code formatting, refactoring, unit tests, snippets, and more.", - "version": "2018.7.0-beta", + "version": "2018.7.0", "publisher": "ms-python", "author": { "name": "Microsoft Corporation" From 1105199a0a130732a1b65337fea351812f908f15 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 18 Jul 2018 16:41:06 -0700 Subject: [PATCH 20/27] Move service registrations from debug adapter host to extension host (#2193) Fixes #2191 --- src/client/activation/serviceRegistry.ts | 5 +++++ src/client/debugger/serviceRegistry.ts | 7 +------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/client/activation/serviceRegistry.ts b/src/client/activation/serviceRegistry.ts index f9d8dfdf549d..23dffa6cd365 100644 --- a/src/client/activation/serviceRegistry.ts +++ b/src/client/activation/serviceRegistry.ts @@ -3,7 +3,10 @@ 'use strict'; +import { BANNER_NAME_LS_SURVEY, BANNER_NAME_PROPOSE_LS, IPythonExtensionBanner } from '../common/types'; import { IServiceManager } from '../ioc/types'; +import { LanguageServerSurveyBanner } from '../languageServices/languageServerSurveyBanner'; +import { ProposeLanguageServerBanner } from '../languageServices/proposeLanguageServerBanner'; import { ExtensionActivationService } from './activationService'; import { JediExtensionActivator } from './jedi'; import { LanguageServerExtensionActivator } from './languageServer'; @@ -13,4 +16,6 @@ export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(IExtensionActivationService, ExtensionActivationService); serviceManager.add(IExtensionActivator, JediExtensionActivator, ExtensionActivators.Jedi); serviceManager.add(IExtensionActivator, LanguageServerExtensionActivator, ExtensionActivators.DotNet); + serviceManager.addSingleton(IPythonExtensionBanner, LanguageServerSurveyBanner, BANNER_NAME_LS_SURVEY); + serviceManager.addSingleton(IPythonExtensionBanner, ProposeLanguageServerBanner, BANNER_NAME_PROPOSE_LS); } diff --git a/src/client/debugger/serviceRegistry.ts b/src/client/debugger/serviceRegistry.ts index efb44040b4cf..3030f77d91e7 100644 --- a/src/client/debugger/serviceRegistry.ts +++ b/src/client/debugger/serviceRegistry.ts @@ -9,13 +9,10 @@ import { FileSystem } from '../common/platform/fileSystem'; import { PlatformService } from '../common/platform/platformService'; import { IFileSystem, IPlatformService } from '../common/platform/types'; import { CurrentProcess } from '../common/process/currentProcess'; -import { BANNER_NAME_LS_SURVEY, BANNER_NAME_PROPOSE_LS, ICurrentProcess, - IExperimentalDebuggerBanner, IPythonExtensionBanner, ISocketServer } from '../common/types'; +import { ICurrentProcess, IExperimentalDebuggerBanner, ISocketServer } from '../common/types'; import { ServiceContainer } from '../ioc/container'; import { ServiceManager } from '../ioc/serviceManager'; import { IServiceContainer, IServiceManager } from '../ioc/types'; -import { LanguageServerSurveyBanner } from '../languageServices/languageServerSurveyBanner'; -import { ProposeLanguageServerBanner } from '../languageServices/proposeLanguageServerBanner'; import { ExperimentalDebuggerBanner } from './banner'; import { DebugStreamProvider } from './Common/debugStreamProvider'; import { ProtocolLogger } from './Common/protocolLogger'; @@ -45,6 +42,4 @@ function registerDebuggerTypes(serviceManager: IServiceManager) { export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(IExperimentalDebuggerBanner, ExperimentalDebuggerBanner); - serviceManager.addSingleton(IPythonExtensionBanner, LanguageServerSurveyBanner, BANNER_NAME_LS_SURVEY); - serviceManager.addSingleton(IPythonExtensionBanner, ProposeLanguageServerBanner, BANNER_NAME_PROPOSE_LS); } From 8e58152dca23b5697a3f40af0855862e2ef66a7a Mon Sep 17 00:00:00 2001 From: Derek Keeler Date: Mon, 23 Jul 2018 18:39:41 -0600 Subject: [PATCH 21/27] Update to latest Language Server (#2234) * Update to latest Language Server - change the download version to latest build - change the name of the executable bit of the LS * Fix unit tests - Remove hardcoded strings causing issues - Expose information from downloader/platform - Update tests for explicit OS types * Update PIP version regex - make it allow 2, or 3, version segments - handles old M.m.r or new Y.r version patterns * Remove hard-coded strings /and/ formats from tests. * Correction to the PIP_VERSION_REGEX - keep it simple, keeler. --- src/client/activation/downloader.ts | 4 ++-- src/client/activation/platformData.ts | 18 +++++++++++++++--- src/client/interpreter/interpreterVersion.ts | 2 +- src/test/activation/downloader.unit.test.ts | 16 ++++++---------- src/test/activation/platformData.unit.test.ts | 9 ++++++--- .../interpreters/interpreterVersion.test.ts | 4 ++-- 6 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/client/activation/downloader.ts b/src/client/activation/downloader.ts index 8b7ed686babe..0bb6b9dbcf20 100644 --- a/src/client/activation/downloader.ts +++ b/src/client/activation/downloader.ts @@ -20,10 +20,10 @@ const StreamZip = require('node-stream-zip'); const downloadUriPrefix = 'https://pvsc.blob.core.windows.net/python-language-server'; const downloadBaseFileName = 'Python-Language-Server'; -const downloadVersion = '0.1.0'; +const downloadVersion = '0.1.18204.3'; const downloadFileExtension = '.nupkg'; -const DownloadLinks = { +export const DownloadLinks = { [PlatformName.Windows32Bit]: `${downloadUriPrefix}/${downloadBaseFileName}-${PlatformName.Windows32Bit}.${downloadVersion}${downloadFileExtension}`, [PlatformName.Windows64Bit]: `${downloadUriPrefix}/${downloadBaseFileName}-${PlatformName.Windows64Bit}.${downloadVersion}${downloadFileExtension}`, [PlatformName.Linux64Bit]: `${downloadUriPrefix}/${downloadBaseFileName}-${PlatformName.Linux64Bit}.${downloadVersion}${downloadFileExtension}`, diff --git a/src/client/activation/platformData.ts b/src/client/activation/platformData.ts index 8564b8625b44..39cce79cc82f 100644 --- a/src/client/activation/platformData.ts +++ b/src/client/activation/platformData.ts @@ -16,6 +16,12 @@ export enum PlatformName { Linux64Bit = 'linux-x64' } +export enum PlatformLSExecutables { + Windows = 'Microsoft.Python.LanguageServer.exe', + MacOS = 'Microsoft.Python.LanguageServer', + Linux = 'Microsoft.Python.LanguageServer' +} + export class PlatformData { constructor(private platform: IPlatformService, fs: IFileSystem) { } public async getPlatformName(): Promise { @@ -39,9 +45,15 @@ export class PlatformData { } public getEngineExecutableName(): string { - return this.platform.isWindows - ? 'Microsoft.Python.LanguageServer.exe' - : 'Microsoft.Python.LanguageServer.LanguageServer'; + if (this.platform.isWindows) { + return PlatformLSExecutables.Windows; + } else if (this.platform.isLinux) { + return PlatformLSExecutables.Linux; + } else if (this.platform.isMac) { + return PlatformLSExecutables.MacOS; + } else { + return 'unknown-platform'; + } } public async getExpectedHash(): Promise { diff --git a/src/client/interpreter/interpreterVersion.ts b/src/client/interpreter/interpreterVersion.ts index 4b00ddadead2..2bfb72c4f98f 100644 --- a/src/client/interpreter/interpreterVersion.ts +++ b/src/client/interpreter/interpreterVersion.ts @@ -3,7 +3,7 @@ import '../common/extensions'; import { IProcessServiceFactory } from '../common/process/types'; import { IInterpreterVersionService } from './contracts'; -export const PIP_VERSION_REGEX = '\\d+\\.\\d+(\\.\\d+)'; +export const PIP_VERSION_REGEX = '\\d+\\.\\d+(\\.\\d+)?'; @injectable() export class InterpreterVersionService implements IInterpreterVersionService { diff --git a/src/test/activation/downloader.unit.test.ts b/src/test/activation/downloader.unit.test.ts index c75b5e3b3d49..edee2e828f3c 100644 --- a/src/test/activation/downloader.unit.test.ts +++ b/src/test/activation/downloader.unit.test.ts @@ -7,16 +7,12 @@ import * as assert from 'assert'; import * as TypeMoq from 'typemoq'; -import { LanguageServerDownloader } from '../../client/activation/downloader'; +import { DownloadLinks, LanguageServerDownloader } from '../../client/activation/downloader'; +import { PlatformName } from '../../client/activation/platformData'; import { IFileSystem, IPlatformService } from '../../client/common/platform/types'; import { IOutputChannel } from '../../client/common/types'; import { IServiceContainer } from '../../client/ioc/types'; -const downloadUriPrefix = 'https://pvsc.blob.core.windows.net/python-language-server'; -const downloadBaseFileName = 'Python-Language-Server'; -const downloadVersion = '0.1.0'; -const downloadFileExtension = '.nupkg'; - suite('Activation - Downloader', () => { let languageServerDownloader: LanguageServerDownloader; let serviceContainer: TypeMoq.IMock; @@ -48,21 +44,21 @@ suite('Activation - Downloader', () => { test('Windows 32Bit', async () => { setupPlatform({ windows: true }); const link = await languageServerDownloader.getDownloadUri(); - assert.equal(link, `${downloadUriPrefix}/${downloadBaseFileName}-win-x86.${downloadVersion}${downloadFileExtension}`); + assert.equal(link, DownloadLinks[PlatformName.Windows32Bit]); }); test('Windows 64Bit', async () => { setupPlatform({ windows: true, is64Bit: true }); const link = await languageServerDownloader.getDownloadUri(); - assert.equal(link, `${downloadUriPrefix}/${downloadBaseFileName}-win-x64.${downloadVersion}${downloadFileExtension}`); + assert.equal(link, DownloadLinks[PlatformName.Windows64Bit]); }); test('Mac 64Bit', async () => { setupPlatform({ mac: true, is64Bit: true }); const link = await languageServerDownloader.getDownloadUri(); - assert.equal(link, `${downloadUriPrefix}/${downloadBaseFileName}-osx-x64.${downloadVersion}${downloadFileExtension}`); + assert.equal(link, DownloadLinks[PlatformName.Mac64Bit]); }); test('Linux 64Bit', async () => { setupPlatform({ linux: true, is64Bit: true }); const link = await languageServerDownloader.getDownloadUri(); - assert.equal(link, `${downloadUriPrefix}/${downloadBaseFileName}-linux-x64.${downloadVersion}${downloadFileExtension}`); + assert.equal(link, DownloadLinks[PlatformName.Linux64Bit]); }); }); diff --git a/src/test/activation/platformData.unit.test.ts b/src/test/activation/platformData.unit.test.ts index 80ce2ce494be..0044c27b0182 100644 --- a/src/test/activation/platformData.unit.test.ts +++ b/src/test/activation/platformData.unit.test.ts @@ -4,7 +4,7 @@ // tslint:disable:no-unused-variable import * as assert from 'assert'; import * as TypeMoq from 'typemoq'; -import { PlatformData } from '../../client/activation/platformData'; +import { PlatformData, PlatformLSExecutables } from '../../client/activation/platformData'; import { IFileSystem, IPlatformService } from '../../client/common/platform/types'; const testDataWinMac = [ @@ -24,8 +24,9 @@ const testDataLinux = [ ]; const testDataModuleName = [ - { isWindows: true, expectedName: 'Microsoft.Python.LanguageServer.exe' }, - { isWindows: false, expectedName: 'Microsoft.Python.LanguageServer.LanguageServer' } + { isWindows: true, isMac: false, isLinux: false, expectedName: PlatformLSExecutables.Windows }, + { isWindows: false, isMac: true, isLinux: false, expectedName: PlatformLSExecutables.MacOS }, + { isWindows: false, isMac: false, isLinux: true, expectedName: PlatformLSExecutables.Linux } ]; // tslint:disable-next-line:max-func-body-length @@ -70,6 +71,8 @@ suite('Activation - platform data', () => { for (const t of testDataModuleName) { const platformService = TypeMoq.Mock.ofType(); platformService.setup(x => x.isWindows).returns(() => t.isWindows); + platformService.setup(x => x.isLinux).returns(() => t.isLinux); + platformService.setup(x => x.isMac).returns(() => t.isMac); const fs = TypeMoq.Mock.ofType(); const pd = new PlatformData(platformService.object, fs.object); diff --git a/src/test/interpreters/interpreterVersion.test.ts b/src/test/interpreters/interpreterVersion.test.ts index 6b9c9da81ad7..63b0dc45b3b9 100644 --- a/src/test/interpreters/interpreterVersion.test.ts +++ b/src/test/interpreters/interpreterVersion.test.ts @@ -43,7 +43,7 @@ suite('Interpreters display version', () => { const pyVersion = await interpreterVersion.getVersion('INVALID_INTERPRETER', 'DEFAULT_TEST_VALUE'); assert.equal(pyVersion, 'DEFAULT_TEST_VALUE', 'Incorrect version'); }); - test('Must return the pip Version', async () => { + test('Must return the pip Version.', async () => { const pythonProcess = await ioc.serviceContainer.get(IProcessServiceFactory).create(); const result = await pythonProcess.exec(PYTHON_PATH, ['-m', 'pip', '--version'], { cwd: __dirname, mergeStdOutErr: true }); const output = result.stdout.splitLines()[0]; @@ -60,7 +60,7 @@ suite('Interpreters display version', () => { // tslint:disable-next-line:no-non-null-assertion await expect(pipVersionPromise).to.eventually.equal(matches![0].trim()); }); - test('Must throw an exceptionn when pip version cannot be determine', async () => { + test('Must throw an exception when pip version cannot be determined', async () => { const interpreterVersion = ioc.serviceContainer.get(IInterpreterVersionService); const pipVersionPromise = interpreterVersion.getPipVersion('INVALID_INTERPRETER'); await expect(pipVersionPromise).to.be.rejectedWith(); From c87788e7aac6cd2a891e6796a65c0a68e43ce8f9 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 23 Jul 2018 17:40:22 -0700 Subject: [PATCH 22/27] 2018.7.1 release (#2235) * Bump version number * Touch up news entries * Update changelog * 2018.7.1 release for new language server --- CHANGELOG.md | 9 +++++++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4fa900c30c5..bc8c712281b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 2018.7.1 (23 July 2018) + +### Fixes + +1. Update the language server to code as of + [651468731500ec1cc644029c3666c57b82f77d76](https://github.com/Microsoft/PTVS/commit/651468731500ec1cc644029c3666c57b82f77d76). + ([#2233](https://github.com/Microsoft/vscode-python/issues/2233)) + + ## 2018.7.0 (18 July 2018) ### Thanks diff --git a/package-lock.json b/package-lock.json index 8bbc19bbb321..8f6914bd4808 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "python", - "version": "2018.7.0", + "version": "2018.7.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d9bb9b5e83ef..c502de7d7d68 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "Linting, Debugging (multi-threaded, remote), Intellisense, code formatting, refactoring, unit tests, snippets, and more.", - "version": "2018.7.0", + "version": "2018.7.1", "publisher": "ms-python", "author": { "name": "Microsoft Corporation" From 323ef7b6f61d91d13a40329841cdfd015f3b9ae6 Mon Sep 17 00:00:00 2001 From: bschley <3937642+bschley@users.noreply.github.com> Date: Tue, 24 Jul 2018 18:13:49 +0200 Subject: [PATCH 23/27] typo and grammar fixes --- package.nls.de.json | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/package.nls.de.json b/package.nls.de.json index 8b6d7c4bf2d0..6744ee1cf235 100644 --- a/package.nls.de.json +++ b/package.nls.de.json @@ -10,41 +10,41 @@ "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.viewTestOutput.title": "Unittest Ausgabe anzeigen", - "python.command.python.selectAndRunTestMethod.title": "Unittest Methode ausführen ...", - "python.command.python.selectAndDebugTestMethod.title": "Unittest Debug Methode ausführen ...", - "python.command.python.selectAndRunTestFile.title": "Unittest Datei ausführen ...", - "python.command.python.runCurrentTestFile.title": "Ausgewählte Unittest Datei ausführen", + "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 ...", + "python.command.python.selectAndRunTestFile.title": "Unittest-Datei ausführen ...", + "python.command.python.runCurrentTestFile.title": "Ausgewählte Unittest-Datei ausführen", "python.command.python.runFailedTests.title": "Fehlerhafte Unittests ausführen", "python.command.python.discoverTests.title": "Unittests durchsuchen", - "python.command.python.execSelectionInTerminal.title": "Selektion/Reihe in Python Terminal ausführen", - "python.command.python.execSelectionInDjangoShell.title": "Selektion/Reihe in Django Shell ausführen", - "python.command.python.goToPythonObject.title": "Gehe zu Python Objekt", + "python.command.python.execSelectionInTerminal.title": "Selektion/Reihe in Python-Terminal ausführen", + "python.command.python.execSelectionInDjangoShell.title": "Selektion/Reihe in Django-Shell ausführen", + "python.command.python.goToPythonObject.title": "Gehe zu Python-Objekt", "python.command.python.setLinter.title": "Linter auswählen", "python.command.python.enableLinting.title": "Linting aktivieren", "python.command.python.runLinting.title": "Linting ausführen", "python.snippet.launch.standard.label": "Python: Aktuelle Datei", - "python.snippet.launch.standard.description": "Python Programm debuggen mit Standard Ausgabe", + "python.snippet.launch.standard.description": "Python-Programm debuggen mit Standardausgabe", "python.snippet.launch.pyspark.label": "Python: PySpark", "python.snippet.launch.pyspark.description": "PySpark debuggen", "python.snippet.launch.module.label": "Python: Modul", - "python.snippet.launch.module.description": "Python Modul debuggen", + "python.snippet.launch.module.description": "Python-Modul debuggen", "python.snippet.launch.terminal.label": "Python: Terminal (integriert)", - "python.snippet.launch.terminal.description": "Python Programm mit integriertem Terminal/Konsole debuggen", + "python.snippet.launch.terminal.description": "Python-Programm mit integriertem Terminal/Konsole debuggen", "python.snippet.launch.externalTerminal.label": "Python: Terminal (extern)", - "python.snippet.launch.externalTerminal.description": "Python Programm mit externem Terminal/Konsole debuggen", + "python.snippet.launch.externalTerminal.description": "Python-Programm mit externem Terminal/Konsole debuggen", "python.snippet.launch.django.label": "Python: Django", - "python.snippet.launch.django.description": "Django Anwendung debuggen", + "python.snippet.launch.django.description": "Django-Anwendung debuggen", "python.snippet.launch.flask.label": "Python: Flask (0.11.x oder neuer)", - "python.snippet.launch.flask.description": "Flask Anwendung debuggen", + "python.snippet.launch.flask.description": "Flask-Anwendung debuggen", "python.snippet.launch.flaskOld.label": "Python: Flask (0.10.x oder früher)", - "python.snippet.launch.flaskOld.description": "Ältere Flask Anwendung debuggen", + "python.snippet.launch.flaskOld.description": "Ältere Flask-Anwendung debuggen", "python.snippet.launch.gevent.label": "Python: Gevent", - "python.snippet.launch.gevent.description": "Gevent Anwendung debuggen", - "python.snippet.launch.pyramid.label": "Python: Pyramid Anwendung", - "python.snippet.launch.pyramid.description": "Pyramid Anwendung debuggen", - "python.snippet.launch.watson.label": "Python: Watson Anwendung", - "python.snippet.launch.watson.description": "Watson Anwendung debuggen", + "python.snippet.launch.gevent.description": "Gevent-Anwendung debuggen", + "python.snippet.launch.pyramid.label": "Python: Pyramid-Anwendung", + "python.snippet.launch.pyramid.description": "Pyramid-Anwendung debuggen", + "python.snippet.launch.watson.label": "Python: Watson-Anwendung", + "python.snippet.launch.watson.description": "Watson-Anwendung debuggen", "python.snippet.launch.attach.label": "Python: Anfügen", "python.snippet.launch.attach.description": "Debugger anfügen für Remote Debugging", "python.snippet.launch.scrapy.label": "Python: Scrapy", From 95764f712d029b6896e3c70fa548922c7f63606c Mon Sep 17 00:00:00 2001 From: bschley <3937642+bschley@users.noreply.github.com> Date: Tue, 31 Jul 2018 08:01:52 +0200 Subject: [PATCH 24/27] Revert "added new line" This reverts commit b6a3098878143d800a683103e526fc8a0c350d99. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8ccacc7b77dc..2d32c02d7a84 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,4 @@ obj/** .pytest_cache tmp/** .python-version -vscode-python.code-workspace +vscode-python.code-workspace \ No newline at end of file From 33d51a696335c76fdf500b255ae141c16447883f Mon Sep 17 00:00:00 2001 From: Ben Schley <3937642+bschley@users.noreply.github.com> Date: Thu, 2 Aug 2018 22:24:18 +0200 Subject: [PATCH 25/27] Update .gitignore revert my .gitignore change --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8ccacc7b77dc..c6cd087c91ce 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,3 @@ obj/** .pytest_cache tmp/** .python-version -vscode-python.code-workspace From 9079b851815705ad596e10439a71d63557307b6e Mon Sep 17 00:00:00 2001 From: bschley <3937642+bschley@users.noreply.github.com> Date: Thu, 2 Aug 2018 22:56:56 +0200 Subject: [PATCH 26/27] reverted my .gitignore change --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8ccacc7b77dc..c6cd087c91ce 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,3 @@ obj/** .pytest_cache tmp/** .python-version -vscode-python.code-workspace From 67d1157bdea2bfd1e62ca6896a997cc6b3a98365 Mon Sep 17 00:00:00 2001 From: bschley <3937642+bschley@users.noreply.github.com> Date: Thu, 2 Aug 2018 23:40:39 +0200 Subject: [PATCH 27/27] added a news for the Changelog about the German translation. --- news/1 Enhancements/2203.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/1 Enhancements/2203.md diff --git a/news/1 Enhancements/2203.md b/news/1 Enhancements/2203.md new file mode 100644 index 000000000000..78353110deef --- /dev/null +++ b/news/1 Enhancements/2203.md @@ -0,0 +1 @@ +Added a German translation. (thanks to [bschley](https://github.com/bschley) and by means of [berndverst](https://github.com/berndverst) and [croth1](https://github.com/croth1) for the reviews) \ No newline at end of file