From 04e0b3441647a894d73ba5bb36e45550b79de46f Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Tue, 5 May 2020 21:15:32 -0700 Subject: [PATCH 01/27] Fix path --- src/client/activation/node/languageClientFactory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/activation/node/languageClientFactory.ts b/src/client/activation/node/languageClientFactory.ts index 8490c4910041..d607afdd5d31 100644 --- a/src/client/activation/node/languageClientFactory.ts +++ b/src/client/activation/node/languageClientFactory.ts @@ -20,7 +20,7 @@ export class NodeLanguageClientFactory implements ILanguageClientFactory { constructor( @inject(IFileSystem) private readonly fs: IFileSystem, @inject(ILanguageServerFolderService) private readonly languageServerFolderService: ILanguageServerFolderService - ) {} + ) { } public async createLanguageClient( resource: Resource, From 4477900742b0dea66a9c1219a8e16b5254f2f69d Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Thu, 18 Jun 2020 11:40:57 -0700 Subject: [PATCH 02/27] Actually fix settings --- src/client/testing/common/updateTestSettings.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/client/testing/common/updateTestSettings.ts b/src/client/testing/common/updateTestSettings.ts index 1f5c79faf32c..1a309a66c1b5 100644 --- a/src/client/testing/common/updateTestSettings.ts +++ b/src/client/testing/common/updateTestSettings.ts @@ -31,7 +31,7 @@ export class UpdateTestSettingService implements IExtensionActivationService { const filesToBeFixed = await this.getFilesToBeFixed(resource); await Promise.all(filesToBeFixed.map((file) => this.fixSettingInFile(file))); } - public getSettingsFiles(resource: Resource) { + public getSettingsFiles(resource: Resource): string[] { const settingsFiles: string[] = []; if (this.application.userSettingsFile) { settingsFiles.push(this.application.userSettingsFile); @@ -42,7 +42,7 @@ export class UpdateTestSettingService implements IExtensionActivationService { } return settingsFiles; } - public async getFilesToBeFixed(resource: Resource) { + public async getFilesToBeFixed(resource: Resource): Promise { const files = this.getSettingsFiles(resource); const result = await Promise.all( files.map(async (file) => { @@ -87,10 +87,11 @@ export class UpdateTestSettingService implements IExtensionActivationService { return fileContents; } - public async doesFileNeedToBeFixed(filePath: string) { + public async doesFileNeedToBeFixed(filePath: string): Promise { try { const contents = await this.fs.readFile(filePath); return ( + contents.indexOf('python.jediEnabled') > 0 || contents.indexOf('python.unitTest.') > 0 || contents.indexOf('.pyTest') > 0 || contents.indexOf('.pep8') > 0 From d0d50dedd938bb5380c1413c25b6150dc420a938 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Thu, 18 Jun 2020 11:45:41 -0700 Subject: [PATCH 03/27] Add news --- news/2 Fixes/12429.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/2 Fixes/12429.md diff --git a/news/2 Fixes/12429.md b/news/2 Fixes/12429.md new file mode 100644 index 000000000000..4fea2a4f8f5c --- /dev/null +++ b/news/2 Fixes/12429.md @@ -0,0 +1 @@ +Fixed issue when `python.jediEnabled` setting was not removed and `python.languageServer` setting was not updated. From 03aa5f92820ac00a443d1b0cec174f8b0ad5e98c Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Thu, 18 Jun 2020 11:53:02 -0700 Subject: [PATCH 04/27] Add test --- .../diagnostics/checks/updateTestSettings.unit.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/test/application/diagnostics/checks/updateTestSettings.unit.test.ts b/src/test/application/diagnostics/checks/updateTestSettings.unit.test.ts index 819b27b9f8cb..c9a5bd704ee0 100644 --- a/src/test/application/diagnostics/checks/updateTestSettings.unit.test.ts +++ b/src/test/application/diagnostics/checks/updateTestSettings.unit.test.ts @@ -168,6 +168,12 @@ suite('Application Diagnostics - Check Test Settings', () => { assert.ok(!needsToBeFixed); verify(fs.readFile(__filename)).once(); }); + test('Verify `python.jediEnabled` is found in user settings', async () => { + when(fs.readFile(__filename)).thenResolve('"python.jediEnabled": false'); + const needsToBeFixed = await diagnosticService.doesFileNeedToBeFixed(__filename); + assert.ok(needsToBeFixed); + verify(fs.readFile(__filename)).once(); + }); [ { From e4a032f43211c9082e5ae4f0db96deee6160471f Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Thu, 18 Jun 2020 11:54:54 -0700 Subject: [PATCH 05/27] Format --- src/client/activation/node/languageClientFactory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/activation/node/languageClientFactory.ts b/src/client/activation/node/languageClientFactory.ts index 8bd72f7d60b0..a8d9cbdab41d 100644 --- a/src/client/activation/node/languageClientFactory.ts +++ b/src/client/activation/node/languageClientFactory.ts @@ -20,7 +20,7 @@ export class NodeLanguageClientFactory implements ILanguageClientFactory { constructor( @inject(IFileSystem) private readonly fs: IFileSystem, @inject(ILanguageServerFolderService) private readonly languageServerFolderService: ILanguageServerFolderService - ) { } + ) {} public async createLanguageClient( resource: Resource, From 27eeec76f8519ed0beb45274db2cd9200bde1f26 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Thu, 18 Jun 2020 16:29:44 -0700 Subject: [PATCH 06/27] Suppress 'jediEnabled' removal --- src/client/testing/common/updateTestSettings.ts | 13 +++---------- .../checks/updateTestSettings.unit.test.ts | 12 ++++++------ 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/client/testing/common/updateTestSettings.ts b/src/client/testing/common/updateTestSettings.ts index 1a309a66c1b5..b7b60aad8ad8 100644 --- a/src/client/testing/common/updateTestSettings.ts +++ b/src/client/testing/common/updateTestSettings.ts @@ -107,13 +107,13 @@ export class UpdateTestSettingService implements IExtensionActivationService { // - `true` or missing then set to `languageServer: Jedi`. // - `false` and `languageServer` is present, do nothing. // - `false` and `languageServer` is NOT present, set `languageServer` to `Microsoft`. - // `jediEnabled` is then removed. + // `jediEnabled` is NOT removed since JSONC parser may also remove comments. const jediEnabledPath = ['python.jediEnabled']; const languageServerPath = ['python.languageServer']; try { - let ast = parseTree(fileContent); - let jediEnabledNode = findNodeAtLocation(ast, jediEnabledPath); + const ast = parseTree(fileContent); + const jediEnabledNode = findNodeAtLocation(ast, jediEnabledPath); const jediEnabled = jediEnabledNode ? getNodeValue(jediEnabledNode) : true; const languageServerNode = findNodeAtLocation(ast, languageServerPath); const formattingOptions: FormattingOptions = { @@ -135,13 +135,6 @@ export class UpdateTestSettingService implements IExtensionActivationService { } fileContent = applyEdits(fileContent, edits); - // Remove jediEnabled - ast = parseTree(fileContent); - jediEnabledNode = findNodeAtLocation(ast, jediEnabledPath); - if (jediEnabledNode) { - edits = modify(fileContent, jediEnabledPath, undefined, { formattingOptions }); - fileContent = applyEdits(fileContent, edits); - } // tslint:disable-next-line:no-empty } catch {} return fileContent; diff --git a/src/test/application/diagnostics/checks/updateTestSettings.unit.test.ts b/src/test/application/diagnostics/checks/updateTestSettings.unit.test.ts index c9a5bd704ee0..d70dd05728ef 100644 --- a/src/test/application/diagnostics/checks/updateTestSettings.unit.test.ts +++ b/src/test/application/diagnostics/checks/updateTestSettings.unit.test.ts @@ -224,32 +224,32 @@ suite('Application Diagnostics - Check Test Settings', () => { { testTitle: 'jediEnabled: true, no languageServer setting', contents: '{ "python.jediEnabled": true }', - expectedContent: '{"python.languageServer": "Jedi"}' + expectedContent: '{ "python.jediEnabled": true, "python.languageServer": "Jedi"}' }, { testTitle: 'jediEnabled: true, languageServer setting present', contents: '{ "python.jediEnabled": true }', - expectedContent: '{"python.languageServer": "Jedi"}' + expectedContent: '{ "python.jediEnabled": true, "python.languageServer": "Jedi"}' }, { testTitle: 'jediEnabled: false, no languageServer setting', contents: '{ "python.jediEnabled": false }', - expectedContent: '{"python.languageServer": "Microsoft"}' + expectedContent: '{ "python.jediEnabled": false, "python.languageServer": "Microsoft"}' }, { testTitle: 'jediEnabled: false, languageServer is Microsoft', contents: '{ "python.jediEnabled": false, "python.languageServer": "Microsoft" }', - expectedContent: '{"python.languageServer": "Microsoft"}' + expectedContent: '{ "python.jediEnabled": false, "python.languageServer": "Microsoft"}' }, { testTitle: 'jediEnabled: false, languageServer is None', contents: '{ "python.jediEnabled": false, "python.languageServer": "None" }', - expectedContent: '{"python.languageServer": "None"}' + expectedContent: '{ "python.jediEnabled": false, "python.languageServer": "None"}' }, { testTitle: 'jediEnabled: false, languageServer is Jedi', contents: '{ "python.jediEnabled": false, "python.languageServer": "Jedi" }', - expectedContent: '{"python.languageServer": "Jedi"}' + expectedContent: '{ "python.jediEnabled": false, "python.languageServer": "Jedi"}' } ].forEach((item) => { test(item.testTitle, async () => { From 3ed655b7f239d923598226cbc7ae9461f98ae266 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Fri, 26 Jun 2020 08:31:45 -0700 Subject: [PATCH 07/27] Drop survey first launch threshold --- src/client/languageServices/languageServerSurveyBanner.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/languageServices/languageServerSurveyBanner.ts b/src/client/languageServices/languageServerSurveyBanner.ts index 3d58991a6094..721b9d05d965 100644 --- a/src/client/languageServices/languageServerSurveyBanner.ts +++ b/src/client/languageServices/languageServerSurveyBanner.ts @@ -44,8 +44,8 @@ export class LanguageServerSurveyBanner implements IPythonExtensionBanner { @inject(IPersistentStateFactory) private persistentState: IPersistentStateFactory, @inject(IBrowserService) private browserService: IBrowserService, @inject(ILanguageServerFolderService) private lsService: ILanguageServerFolderService, - showAfterMinimumEventsCount: number = 100, - showBeforeMaximumEventsCount: number = 500 + showAfterMinimumEventsCount: number = 30, + showBeforeMaximumEventsCount: number = 200 ) { this.minCompletionsBeforeShow = showAfterMinimumEventsCount; this.maxCompletionsBeforeShow = showBeforeMaximumEventsCount; From c8e67e6c31d48ae9fc15c5cf27032c9f30429ba5 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Wed, 22 Jul 2020 11:21:10 -0700 Subject: [PATCH 08/27] Initial --- package.json | 1 + package.nls.it.json | 2 +- package.nls.json | 24 ++++++++----- package.nls.nl.json | 4 +-- package.nls.ru.json | 11 ++++-- package.nls.zh-tw.json | 4 +-- src/client/activation/activationService.ts | 11 +++--- src/client/activation/common/activatorBase.ts | 19 ---------- src/client/activation/extensionSurvey.ts | 10 ++++-- .../activation/languageServer/activator.ts | 22 ++++++++++-- src/client/activation/node/activator.ts | 36 +++++++++++++++---- src/client/common/utils/localize.ts | 28 +++++++++++++-- .../proposeLanguageServerBanner.ts | 14 ++++---- .../activation/extensionSurvey.unit.test.ts | 28 +++++++++++---- .../common/utils/localize.functional.test.ts | 2 +- ...roposeNewLanguageServerBanner.unit.test.ts | 10 +++--- 16 files changed, 152 insertions(+), 74 deletions(-) diff --git a/package.json b/package.json index 5292fca5fa28..8af01765162f 100644 --- a/package.json +++ b/package.json @@ -2320,6 +2320,7 @@ "enum": [ "Jedi", "Microsoft", + "Pylance", "None" ], "default": "Jedi", diff --git a/package.nls.it.json b/package.nls.it.json index 864c50099f70..c16d6ce74241 100644 --- a/package.nls.it.json +++ b/package.nls.it.json @@ -27,5 +27,5 @@ "python.snippet.launch.flask.label": "Python: Flask", "python.snippet.launch.pyramid.label": "Python: Applicazione Pyramid", "python.snippet.launch.attach.label": "Python: Allega", - "LanguageService.bannerLabelYes": "Sì, prenderò il sondaggio ora" + "ExtensionSurveyBanner.bannerLabelYes": "Sì, prenderò il sondaggio ora" } diff --git a/package.nls.json b/package.nls.json index f4963ea49b25..7f4d02f60c3e 100644 --- a/package.nls.json +++ b/package.nls.json @@ -109,14 +109,11 @@ "python.snippet.launch.django.label": "Python: Django", "python.snippet.launch.flask.label": "Python: Flask", "python.snippet.launch.pyramid.label": "Python: Pyramid Application", - "LanguageService.proposePylanceMessage": "Try out a new faster, feature-rich language server for Python by Microsoft, Pylance! Install the extension now.", - "LanguageService.tryItNow": "Try it now", - "LanguageService.remindMeLater": "Remind me later", - "LanguageService.bannerLabelYes": "Yes, take survey now", - "LanguageService.bannerLabelNo": "No, thanks", - "LanguageService.lsFailedToStart": "We encountered an issue starting the Language Server. Reverting to the alternative, Jedi. Check the Python output panel for details.", - "LanguageService.lsFailedToDownload": "We encountered an issue downloading the Language Server. Reverting to the alternative, Jedi. Check the Python output panel for details.", - "LanguageService.lsFailedToExtract": "We encountered an issue extracting the Language Server. Reverting to the alternative, Jedi. Check the Python output panel for details.", + "Pylance.proposePylanceMessage": "Try out a new faster, feature-rich language server for Python by Microsoft, Pylance! Install the extension now.", + "Pylance.tryItNow": "Try it now", + "Pylance.remindMeLater": "Remind me later", + "Pylance.installPylanceMessage": "Pylance extension is not installed. Click Yes to open Pylance installation page.", + "Pylance.pylanceNotInstalledMessage": "Pylance extension is not installed.", "DataScience.unknownMimeTypeFormat": "Mime type {0} is not currently supported.", "DataScience.historyTitle": "Python Interactive", "DataScience.dataExplorerTitle": "Data Viewer", @@ -198,6 +195,8 @@ "OutputChannelNames.pythonTest": "Python Test Log", "OutputChannelNames.jupyter": "Jupyter", "ExtensionSurveyBanner.bannerMessage": "Can you please take 2 minutes to tell us how the Python extension is working for you?", + "ExtensionSurveyBanner.bannerLabelYes": "Yes, take survey now", + "ExtensionSurveyBanner.bannerLabelNo": "No, thanks", "ExtensionSurveyBanner.maybeLater": "Maybe later", "ExtensionChannels.installingInsidersMessage": "Installing Insiders... ", "ExtensionChannels.installingStableMessage": "Installing Stable... ", @@ -354,11 +353,18 @@ "Testing.configureTests": "Configure Test Framework", "Testing.disableTests": "Disable Tests", "Common.openOutputPanel": "Show output", + "LanguageService.lsFailedToStart": "We encountered an issue starting the Language Server. Reverting to the alternative, Jedi. Check the Python output panel for details.", + "LanguageService.lsFailedToDownload": "We encountered an issue downloading the Language Server. Reverting to the alternative, Jedi. Check the Python output panel for details.", + "LanguageService.lsFailedToExtract": "We encountered an issue extracting the Language Server. Reverting to the alternative, Jedi. Check the Python output panel for details.", "LanguageService.downloadFailedOutputMessage": "Language server download failed", "LanguageService.extractionFailedOutputMessage": "Language server extraction failed", "LanguageService.extractionCompletedOutputMessage": "Language server download complete", "LanguageService.extractionDoneOutputMessage": "done", "LanguageService.reloadVSCodeIfSeachPathHasChanged": "Search paths have changed for this Python interpreter. Please reload the extension to ensure that the IntelliSense works correctly", + "LanguageService.startingJedi": "Starting Jedi Python language engine.", + "LanguageService.startingMicrosoft": "Starting Microsoft Python language server.", + "LanguageService.startingPylance": "Starting Pylance language server.", + "LanguageService.startingNone": "Editor support is inactive since language server is set to None.", "AttachProcess.unsupportedOS": "Operating system '{0}' not supported.", "AttachProcess.attachTitle": "Attach to process", "AttachProcess.selectProcessPlaceholder": "Select the process to attach to", @@ -564,5 +570,5 @@ "DataScience.interactiveWindowModeBannerTitle" : "Do you want to open a new Python Interactive window for this file? [More Information](command:workbench.action.openSettings?%5B%22python.dataScience.interactiveWindowMode%22%5D).", "DataScience.interactiveWindowModeBannerSwitchYes" : "Yes", "DataScience.interactiveWindowModeBannerSwitchAlways" : "Always", - "DataScience.interactiveWindowModeBannerSwitchNo" : "No" + "DataScience.interactiveWindowModeBannerSwitchNo" : "No" } diff --git a/package.nls.nl.json b/package.nls.nl.json index 6329f4089f0f..ba4968e78535 100644 --- a/package.nls.nl.json +++ b/package.nls.nl.json @@ -46,8 +46,8 @@ "python.snippet.launch.flask.label": "Python: Flask", "python.snippet.launch.pyramid.label": "Python: Pyramid-applicatie", "python.snippet.launch.attach.label": "Python: aankoppelen", - "LanguageService.bannerLabelYes": "Ja, neem nu deel aan het onderzoek", - "LanguageService.bannerLabelNo": "Nee, bedankt", + "ExtensionSurveyBanner.bannerLabelYes": "Ja, neem nu deel aan het onderzoek", + "ExtensionSurveyBanner.bannerLabelNo": "Nee, bedankt", "LanguageService.lsFailedToStart": "We zijn een probleem tegengekomen bij het starten van de language server. Aan het terugschakelen naar het alternatief, Jedi. Bekijk het weergavepaneel voor details.", "LanguageService.lsFailedToDownload": "We zijn een probleem tegengekomen bij het downloaden van de language server. Aan het terugschakelen naar het alternatief, Jedi. Bekijk het weergavepaneel voor details.", "LanguageService.lsFailedToExtract": "We zijn een probleem tegengekomen bij het uitpakken van de language server. Aan het terugschakelen naar het alternatief, Jedi. Bekijk het weergavepaneel voor details.", diff --git a/package.nls.ru.json b/package.nls.ru.json index 35da3900fe8d..4f4b17086c12 100644 --- a/package.nls.ru.json +++ b/package.nls.ru.json @@ -27,6 +27,13 @@ "python.snippet.launch.flask.label": "Python: Flask", "python.snippet.launch.pyramid.label": "Python: Приложение Pyramid", "python.snippet.launch.attach.label": "Python: Подключить отладчик", - "LanguageService.bannerLabelYes": "Да, пройти опрос сейчас", - "LanguageService.bannerLabelNo": "Нет, спасибо" + "ExtensionSurveyBanner.bannerLabelYes": "Да, открыть опрос сейчас", + "ExtensionSurveyBanner.bannerLabelNo": "Нет, спасибо", + "ExtensionSurveyBanner.maybeLater": "Может быть, позже", + "ExtensionSurveyBanner.bannerMessage": "Не могли бы вы потратить пару минут на опрос о языковом сервере Pylance?", + "Pylance.proposePylanceMessage": "Попробуйте новый языковый сервер для Python от Microsoft: Pylance! Установите расширение Pylance.", + "Pylance.tryItNow": "Да, хочу", + "Pylance.remindMeLater": "Напомните позже", + "Pylance.installPylanceMessage": "Расширение Pylance не установлено. Нажмите Да чтобы открыть страницу установки Pylance.", + "Pylance.pylanceNotInstalledMessage": "Расширение Pylance не установлено." } diff --git a/package.nls.zh-tw.json b/package.nls.zh-tw.json index 4b69b11400c4..96eb10983a9a 100644 --- a/package.nls.zh-tw.json +++ b/package.nls.zh-tw.json @@ -41,8 +41,6 @@ "python.command.python.analysis.clearCache.title": "清除模組分析快取", "python.snippet.launch.module.default": "請輸入-模組-名稱", "python.snippet.launch.attachpid.label": "Python: 使用處理程序 ID 連結", - "LanguageService.bannerLabelYes": "是,現在填寫調查", - "LanguageService.bannerLabelNo": "不了,謝謝", "LanguageService.lsFailedToStart": "啟動語言伺服器時遇到問題。改回使用替代方案 \"Jedi\"。請檢查 Python 輸出面板以取得更多資訊。", "LanguageService.lsFailedToDownload": "下載語言伺服器時遇到問題。改回使用替代方案 \"Jedi\"。請檢查 Python 輸出面板以取得更多資訊。", "LanguageService.lsFailedToExtract": "擷取語言伺服器時遇到問題。改回使用替代方案 \"Jedi\"。請檢查 Python 輸出面板以取得更多資訊。", @@ -59,6 +57,8 @@ "OutputChannelNames.pythonTest": "Python 測試記錄", "OutputChannelNames.jupyter": "Jupyter", "ExtensionSurveyBanner.bannerMessage": "請問您是否可以用兩分鐘的時間,告訴我們 Python 延伸模組在您環境中的運作情況?", + "ExtensionSurveyBanner.bannerLabelYes": "是,現在填寫調查", + "ExtensionSurveyBanner.bannerLabelNo": "不了,謝謝", "ExtensionSurveyBanner.maybeLater": "等一下", "ExtensionChannels.installingInsidersMessage": "正在安裝 Insiders... ", "ExtensionChannels.installingStableMessage": "正在安裝穩定版... ", diff --git a/src/client/activation/activationService.ts b/src/client/activation/activationService.ts index 872c38c81b27..8b2223e47ad7 100644 --- a/src/client/activation/activationService.ts +++ b/src/client/activation/activationService.ts @@ -19,6 +19,7 @@ import { Resource } from '../common/types'; import { swallowExceptions } from '../common/utils/decorators'; +import { LanguageService } from '../common/utils/localize'; import { noop } from '../common/utils/misc'; import { IInterpreterService } from '../interpreter/contracts'; import { IServiceContainer } from '../ioc/types'; @@ -248,7 +249,7 @@ export class LanguageServerExtensionActivationService if (serverType === LanguageServerType.Jedi) { throw ex; } - await this.logStartup(serverType); + this.output.appendLine(LanguageService.lsFailedToStart()); serverType = LanguageServerType.Jedi; server = this.serviceContainer.get(ILanguageServerActivator, serverType); await server.start(resource, interpreter); @@ -268,16 +269,16 @@ export class LanguageServerExtensionActivationService let outputLine; switch (serverType) { case LanguageServerType.Jedi: - outputLine = 'Starting Jedi Python language engine.'; + outputLine = LanguageService.startingJedi(); break; case LanguageServerType.Microsoft: - outputLine = 'Starting Microsoft Python language server.'; + outputLine = LanguageService.startingMicrosoft(); break; case LanguageServerType.Node: - outputLine = 'Starting Pylance language server.'; + outputLine = LanguageService.startingPylance(); break; case LanguageServerType.None: - outputLine = 'Editor support is inactive since language server is set to None.'; + outputLine = LanguageService.startingNone(); break; default: throw new Error('Unknown langauge server type in activator.'); diff --git a/src/client/activation/common/activatorBase.ts b/src/client/activation/common/activatorBase.ts index a5ad15f521c0..e9b84284c4b9 100644 --- a/src/client/activation/common/activatorBase.ts +++ b/src/client/activation/common/activatorBase.ts @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import * as path from 'path'; import { CancellationToken, CodeLens, @@ -29,7 +28,6 @@ import { IWorkspaceService } from '../../common/application/types'; import { traceDecorators } from '../../common/logger'; import { IFileSystem } from '../../common/platform/types'; import { IConfigurationService, Resource } from '../../common/types'; -import { EXTENSION_ROOT_DIR } from '../../constants'; import { PythonInterpreter } from '../../pythonEnvironments/info'; import { ILanguageServerActivator, @@ -171,23 +169,6 @@ export abstract class LanguageServerActivatorBase implements ILanguageServerActi return this.handleProvideSignatureHelp(document, position, token, context); } - protected async ensureLanguageServerFileIsAvailable( - resource: Resource, - fileName: string - ): Promise { - const settings = this.configurationService.getSettings(resource); - if (!settings.downloadLanguageServer) { - return; - } - const languageServerFolder = await this.languageServerFolderService.getLanguageServerFolderName(resource); - const languageServerFolderPath = path.join(EXTENSION_ROOT_DIR, languageServerFolder); - const mscorlib = path.join(languageServerFolderPath, fileName); - if (!(await this.fs.fileExists(mscorlib))) { - await this.lsDownloader.downloadLanguageServer(languageServerFolderPath, resource); - } - return languageServerFolderPath; - } - protected getLanguageClient(): vscodeLanguageClient.LanguageClient | undefined { const proxy = this.manager.languageProxy; if (proxy) { diff --git a/src/client/activation/extensionSurvey.ts b/src/client/activation/extensionSurvey.ts index d9ca8551b91d..45750a4c9b4d 100644 --- a/src/client/activation/extensionSurvey.ts +++ b/src/client/activation/extensionSurvey.ts @@ -11,7 +11,7 @@ import '../common/extensions'; import { traceDecorators } from '../common/logger'; import { IPlatformService } from '../common/platform/types'; import { IBrowserService, IExperimentsManager, IPersistentStateFactory, IRandom } from '../common/types'; -import { Common, ExtensionSurveyBanner, LanguageService } from '../common/utils/localize'; +import { Common, ExtensionSurveyBanner } from '../common/utils/localize'; import { sendTelemetryEvent } from '../telemetry'; import { EventName } from '../telemetry/constants'; import { IExtensionSingleActivationService } from './types'; @@ -78,7 +78,11 @@ export class ExtensionSurveyPrompt implements IExtensionSingleActivationService @traceDecorators.error('Failed to display prompt for extension survey') public async showSurvey() { - const prompts = [LanguageService.bannerLabelYes(), ExtensionSurveyBanner.maybeLater(), Common.doNotShowAgain()]; + const prompts = [ + ExtensionSurveyBanner.bannerLabelYes(), + ExtensionSurveyBanner.maybeLater(), + Common.doNotShowAgain() + ]; const telemetrySelections: ['Yes', 'Maybe later', 'Do not show again'] = [ 'Yes', 'Maybe later', @@ -91,7 +95,7 @@ export class ExtensionSurveyPrompt implements IExtensionSingleActivationService if (!selection) { return; } - if (selection === LanguageService.bannerLabelYes()) { + if (selection === ExtensionSurveyBanner.bannerLabelYes()) { this.launchSurvey(); // Disable survey for a few weeks await this.persistentState diff --git a/src/client/activation/languageServer/activator.ts b/src/client/activation/languageServer/activator.ts index cc4bb9be9678..9e9c02f0c57c 100644 --- a/src/client/activation/languageServer/activator.ts +++ b/src/client/activation/languageServer/activator.ts @@ -5,8 +5,7 @@ import { inject, injectable, named } from 'inversify'; import * as path from 'path'; import { IWorkspaceService } from '../../common/application/types'; -import { isTestExecution } from '../../common/constants'; -import { traceDecorators } from '../../common/logger'; +import { EXTENSION_ROOT_DIR, isTestExecution } from '../../common/constants'; import { IFileSystem } from '../../common/platform/types'; import { BANNER_NAME_PROPOSE_LS, IConfigurationService, IPythonExtensionBanner, Resource } from '../../common/types'; import { PythonInterpreter } from '../../pythonEnvironments/info'; @@ -44,7 +43,6 @@ export class DotNetLanguageServerActivator extends LanguageServerActivatorBase { return super.start(resource, interpreter); } - @traceDecorators.error('Failed to ensure language server is available') public async ensureLanguageServerIsAvailable(resource: Resource): Promise { const languageServerFolderPath = await this.ensureLanguageServerFileIsAvailable(resource, 'mscorlib.dll'); if (languageServerFolderPath) { @@ -78,4 +76,22 @@ export class DotNetLanguageServerActivator extends LanguageServerActivatorBase { content.runtimeOptions.configProperties['System.Globalization.Invariant'] = true; await this.fs.writeFile(targetJsonFile, JSON.stringify(content)); } + + private async ensureLanguageServerFileIsAvailable( + resource: Resource, + fileName: string + ): Promise { + const settings = this.configurationService.getSettings(resource); + if (settings.downloadLanguageServer === false) { + // Development mde + return; + } + const languageServerFolder = await this.languageServerFolderService.getLanguageServerFolderName(resource); + const languageServerFolderPath = path.join(EXTENSION_ROOT_DIR, languageServerFolder); + const mscorlib = path.join(languageServerFolderPath, fileName); + if (!(await this.fs.fileExists(mscorlib))) { + await this.lsDownloader.downloadLanguageServer(languageServerFolderPath, resource); + } + return languageServerFolderPath; + } } diff --git a/src/client/activation/node/activator.ts b/src/client/activation/node/activator.ts index 21bcebc07991..0bddad7517e9 100644 --- a/src/client/activation/node/activator.ts +++ b/src/client/activation/node/activator.ts @@ -5,10 +5,12 @@ import { CancellationToken, CompletionItem, ProviderResult } from 'vscode'; // tslint:disable-next-line: import-name import ProtocolCompletionItem from 'vscode-languageclient/lib/common/protocolCompletionItem'; import { CompletionResolveRequest } from 'vscode-languageclient/node'; -import { IWorkspaceService } from '../../common/application/types'; -import { traceDecorators } from '../../common/logger'; +import { IApplicationEnvironment, IApplicationShell, IWorkspaceService } from '../../common/application/types'; +import { PYLANCE_EXTENSION_ID } from '../../common/constants'; import { IFileSystem } from '../../common/platform/types'; -import { IConfigurationService, Resource } from '../../common/types'; +import { IConfigurationService, IExtensions, Resource } from '../../common/types'; +import { Common, Pylance } from '../../common/utils/localize'; +import { getPylanceExtensionUri } from '../../languageServices/proposeLanguageServerBanner'; import { LanguageServerActivatorBase } from '../common/activatorBase'; import { ILanguageServerDownloader, ILanguageServerFolderService, ILanguageServerManager } from '../types'; @@ -28,14 +30,36 @@ export class NodeLanguageServerActivator extends LanguageServerActivatorBase { @inject(IFileSystem) fs: IFileSystem, @inject(ILanguageServerDownloader) lsDownloader: ILanguageServerDownloader, @inject(ILanguageServerFolderService) languageServerFolderService: ILanguageServerFolderService, - @inject(IConfigurationService) configurationService: IConfigurationService + @inject(IConfigurationService) configurationService: IConfigurationService, + @inject(IExtensions) private readonly extensions: IExtensions, + @inject(IApplicationShell) private readonly appShell: IApplicationShell, + @inject(IApplicationEnvironment) private readonly appEnv: IApplicationEnvironment ) { super(manager, workspace, fs, lsDownloader, languageServerFolderService, configurationService); } - @traceDecorators.error('Failed to ensure language server is available') public async ensureLanguageServerIsAvailable(resource: Resource): Promise { - await this.ensureLanguageServerFileIsAvailable(resource, 'server.bundle.js'); + const settings = this.configurationService.getSettings(resource); + if (settings.downloadLanguageServer === false) { + // Development mode. + return; + } + // Check if Pylance extension is installed. + if (this.extensions.getExtension(PYLANCE_EXTENSION_ID)) { + return; + } + // Point user to Pylance at the store. + const response = await this.appShell.showErrorMessage( + Pylance.installPylanceMessage(), + Common.bannerLabelYes(), + Common.bannerLabelNo() + ); + if (response === Common.bannerLabelYes()) { + this.appShell.openUrl(getPylanceExtensionUri(this.appEnv)); + } + // At this time there is no Pylance installed yet. + // throwing will cause activator to use Jedi temporarily. + throw new Error(Pylance.pylanceNotInstalledMessage()); } public resolveCompletionItem(item: CompletionItem, token: CancellationToken): ProviderResult { diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index b22c61576890..f462b64c9fab 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -100,7 +100,7 @@ export namespace AttachProcess { export const refreshList = localize('AttachProcess.refreshList', 'Refresh process list'); } -export namespace LanguageService { +export namespace Pylance { export const proposePylanceMessage = localize( 'LanguageService.proposePylanceMessage', 'Try out a new faster, feature-rich language server for Python by Microsoft, Pylance! Install the extension now.' @@ -108,8 +108,28 @@ export namespace LanguageService { export const tryItNow = localize('LanguageService.tryItNow', 'Try it now'); export const remindMeLater = localize('LanguageService.remindMeLater', 'Remind me later'); - export const bannerLabelYes = localize('LanguageService.bannerLabelYes', 'Yes, take survey now'); - export const bannerLabelNo = localize('LanguageService.bannerLabelNo', 'No, thanks'); + export const installPylanceMessage = localize( + 'LanguageService.installPylanceMessage', + 'Pylance extension is not installed. Click Yes to open Pylance installation page.' + ); + export const pylanceNotInstalledMessage = localize( + 'LanguageService.pylanceNotInstalledMessage', + 'Pylance extension is not installed.' + ); +} + +export namespace LanguageService { + export const startingJedi = localize('LanguageService.startingJedi', 'Starting Jedi Python language engine.'); + export const startingMicrosoft = localize( + 'LanguageService.startingMicrosoft', + 'Starting Microsoft Python language server.' + ); + export const startingPylance = localize('LanguageService.startingPylance', 'Starting Pylance language server.'); + export const startingNone = localize( + 'LanguageService.startingNone', + 'Editor support is inactive since language server is set to None.' + ); + export const lsFailedToStart = localize( 'LanguageService.lsFailedToStart', 'We encountered an issue starting the Language Server. Reverting to the alternative, Jedi. Check the Python output panel for details.' @@ -307,6 +327,8 @@ export namespace ExtensionSurveyBanner { 'ExtensionSurveyBanner.bannerMessage', 'Can you please take 2 minutes to tell us how the Python extension is working for you?' ); + export const bannerLabelYes = localize('ExtensionSurveyBanner.bannerLabelYes', 'Yes, take survey now'); + export const bannerLabelNo = localize('ExtensionSurveyBanner.bannerLabelNo', 'No, thanks'); export const maybeLater = localize('ExtensionSurveyBanner.maybeLater', 'Maybe later'); } diff --git a/src/client/languageServices/proposeLanguageServerBanner.ts b/src/client/languageServices/proposeLanguageServerBanner.ts index dfc792c48e47..13acc4527005 100644 --- a/src/client/languageServices/proposeLanguageServerBanner.ts +++ b/src/client/languageServices/proposeLanguageServerBanner.ts @@ -16,7 +16,7 @@ import { IPersistentStateFactory, IPythonExtensionBanner } from '../common/types'; -import { LanguageService } from '../common/utils/localize'; +import { Common, Pylance } from '../common/utils/localize'; export function getPylanceExtensionUri(appEnv: IApplicationEnvironment): string { return `${appEnv.uriScheme}:extension/${PYLANCE_EXTENSION_ID}`; @@ -66,16 +66,16 @@ export class ProposePylanceBanner implements IPythonExtensionBanner { } const response = await this.appShell.showInformationMessage( - LanguageService.proposePylanceMessage(), - LanguageService.tryItNow(), - LanguageService.bannerLabelNo(), - LanguageService.remindMeLater() + Pylance.proposePylanceMessage(), + Pylance.tryItNow(), + Common.bannerLabelNo(), + Pylance.remindMeLater() ); - if (response === LanguageService.tryItNow()) { + if (response === Pylance.tryItNow()) { this.appShell.openUrl(getPylanceExtensionUri(this.appEnv)); await this.disable(); - } else if (response === LanguageService.bannerLabelNo()) { + } else if (response === Common.bannerLabelNo()) { await this.disable(); } else { this.disabledInCurrentSession = true; diff --git a/src/test/activation/extensionSurvey.unit.test.ts b/src/test/activation/extensionSurvey.unit.test.ts index 6668914144a6..d49c566df0bf 100644 --- a/src/test/activation/extensionSurvey.unit.test.ts +++ b/src/test/activation/extensionSurvey.unit.test.ts @@ -20,7 +20,7 @@ import { IRandom } from '../../client/common/types'; import { createDeferred } from '../../client/common/utils/async'; -import { Common, ExtensionSurveyBanner, LanguageService } from '../../client/common/utils/localize'; +import { Common, ExtensionSurveyBanner } from '../../client/common/utils/localize'; import { OSType } from '../../client/common/utils/platform'; import { sleep } from '../core'; @@ -226,7 +226,11 @@ suite('Extension survey prompt - showSurvey()', () => { const packageJson = { version: 'extensionVersion' }; - const prompts = [LanguageService.bannerLabelYes(), ExtensionSurveyBanner.maybeLater(), Common.doNotShowAgain()]; + const prompts = [ + ExtensionSurveyBanner.bannerLabelYes(), + ExtensionSurveyBanner.maybeLater(), + Common.doNotShowAgain() + ]; const expectedUrl = `https://aka.ms/AA5rjx5?o=Windows&v=vscodeVersion&e=extensionVersion&m=sessionId`; appEnvironment .setup((a) => a.packageJson) @@ -246,7 +250,7 @@ suite('Extension survey prompt - showSurvey()', () => { .verifiable(TypeMoq.Times.once()); appShell .setup((a) => a.showInformationMessage(ExtensionSurveyBanner.bannerMessage(), ...prompts)) - .returns(() => Promise.resolve(LanguageService.bannerLabelYes())) + .returns(() => Promise.resolve(ExtensionSurveyBanner.bannerLabelYes())) .verifiable(TypeMoq.Times.once()); browserService .setup((s) => s.launch(expectedUrl)) @@ -282,7 +286,11 @@ suite('Extension survey prompt - showSurvey()', () => { }); test("Do nothing if 'Maybe later' option is clicked", async () => { - const prompts = [LanguageService.bannerLabelYes(), ExtensionSurveyBanner.maybeLater(), Common.doNotShowAgain()]; + const prompts = [ + ExtensionSurveyBanner.bannerLabelYes(), + ExtensionSurveyBanner.maybeLater(), + Common.doNotShowAgain() + ]; platformService.setup((p) => p.osType).verifiable(TypeMoq.Times.never()); appShell .setup((a) => a.showInformationMessage(ExtensionSurveyBanner.bannerMessage(), ...prompts)) @@ -321,7 +329,11 @@ suite('Extension survey prompt - showSurvey()', () => { }); test('Do nothing if no option is clicked', async () => { - const prompts = [LanguageService.bannerLabelYes(), ExtensionSurveyBanner.maybeLater(), Common.doNotShowAgain()]; + const prompts = [ + ExtensionSurveyBanner.bannerLabelYes(), + ExtensionSurveyBanner.maybeLater(), + Common.doNotShowAgain() + ]; platformService.setup((p) => p.osType).verifiable(TypeMoq.Times.never()); appShell .setup((a) => a.showInformationMessage(ExtensionSurveyBanner.bannerMessage(), ...prompts)) @@ -360,7 +372,11 @@ suite('Extension survey prompt - showSurvey()', () => { }); test("Disable prompt if 'Do not show again' option is clicked", async () => { - const prompts = [LanguageService.bannerLabelYes(), ExtensionSurveyBanner.maybeLater(), Common.doNotShowAgain()]; + const prompts = [ + ExtensionSurveyBanner.bannerLabelYes(), + ExtensionSurveyBanner.maybeLater(), + Common.doNotShowAgain() + ]; platformService.setup((p) => p.osType).verifiable(TypeMoq.Times.never()); appShell .setup((a) => a.showInformationMessage(ExtensionSurveyBanner.bannerMessage(), ...prompts)) diff --git a/src/test/common/utils/localize.functional.test.ts b/src/test/common/utils/localize.functional.test.ts index 89461b0ae606..8ccd3cf58926 100644 --- a/src/test/common/utils/localize.functional.test.ts +++ b/src/test/common/utils/localize.functional.test.ts @@ -64,7 +64,7 @@ suite('Localization', () => { // Force a config change setLocale('it'); - const val = localize.LanguageService.bannerLabelYes(); + const val = localize.ExtensionSurveyBanner.bannerLabelYes(); assert.equal(val, 'Sì, prenderò il sondaggio ora', 'bannerLabelYes is not being translated'); done(); }); diff --git a/src/test/testing/banners/proposeNewLanguageServerBanner.unit.test.ts b/src/test/testing/banners/proposeNewLanguageServerBanner.unit.test.ts index db4911814901..b7e9c56cd7c2 100644 --- a/src/test/testing/banners/proposeNewLanguageServerBanner.unit.test.ts +++ b/src/test/testing/banners/proposeNewLanguageServerBanner.unit.test.ts @@ -18,7 +18,7 @@ import { IPersistentStateFactory, IPythonSettings } from '../../../client/common/types'; -import { LanguageService } from '../../../client/common/utils/localize'; +import { Common, Pylance } from '../../../client/common/utils/localize'; import { getPylanceExtensionUri, ProposeLSStateKeys, @@ -47,10 +47,10 @@ suite('Propose Pylance Banner', () => { let appEnv: typemoq.IMock; let settings: typemoq.IMock; - const message = LanguageService.proposePylanceMessage(); - const yes = LanguageService.tryItNow(); - const no = LanguageService.bannerLabelNo(); - const later = LanguageService.remindMeLater(); + const message = Pylance.proposePylanceMessage(); + const yes = Pylance.tryItNow(); + const no = Common.bannerLabelNo(); + const later = Pylance.remindMeLater(); setup(() => { config = typemoq.Mock.ofType(); From 94a3d614b888139fc4717b0c726b9b1a78bb606c Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Wed, 22 Jul 2020 15:49:32 -0700 Subject: [PATCH 09/27] Pylance activation tests --- src/client/activation/common/activatorBase.ts | 9 +- .../activation/languageServer/activator.ts | 21 ++- src/client/activation/node/activator.ts | 6 +- .../languageServer/activator.unit.test.ts | 29 +-- .../activation/node/activator.unit.test.ts | 178 ++++++++++++++++++ 5 files changed, 201 insertions(+), 42 deletions(-) create mode 100644 src/test/activation/node/activator.unit.test.ts diff --git a/src/client/activation/common/activatorBase.ts b/src/client/activation/common/activatorBase.ts index e9b84284c4b9..c267d0c0bc59 100644 --- a/src/client/activation/common/activatorBase.ts +++ b/src/client/activation/common/activatorBase.ts @@ -29,12 +29,7 @@ import { traceDecorators } from '../../common/logger'; import { IFileSystem } from '../../common/platform/types'; import { IConfigurationService, Resource } from '../../common/types'; import { PythonInterpreter } from '../../pythonEnvironments/info'; -import { - ILanguageServerActivator, - ILanguageServerDownloader, - ILanguageServerFolderService, - ILanguageServerManager -} from '../types'; +import { ILanguageServerActivator, ILanguageServerManager } from '../types'; /** * Starts the language server managers per workspaces (currently one for first workspace). @@ -50,8 +45,6 @@ export abstract class LanguageServerActivatorBase implements ILanguageServerActi protected readonly manager: ILanguageServerManager, private readonly workspace: IWorkspaceService, protected readonly fs: IFileSystem, - protected readonly lsDownloader: ILanguageServerDownloader, - protected readonly languageServerFolderService: ILanguageServerFolderService, protected readonly configurationService: IConfigurationService ) {} diff --git a/src/client/activation/languageServer/activator.ts b/src/client/activation/languageServer/activator.ts index 9e9c02f0c57c..68becd50ea66 100644 --- a/src/client/activation/languageServer/activator.ts +++ b/src/client/activation/languageServer/activator.ts @@ -26,14 +26,15 @@ export class DotNetLanguageServerActivator extends LanguageServerActivatorBase { @inject(ILanguageServerManager) manager: ILanguageServerManager, @inject(IWorkspaceService) workspace: IWorkspaceService, @inject(IFileSystem) fs: IFileSystem, - @inject(ILanguageServerDownloader) lsDownloader: ILanguageServerDownloader, - @inject(ILanguageServerFolderService) languageServerFolderService: ILanguageServerFolderService, + @inject(ILanguageServerDownloader) private readonly lsDownloader: ILanguageServerDownloader, + @inject(ILanguageServerFolderService) + private readonly languageServerFolderService: ILanguageServerFolderService, @inject(IConfigurationService) configurationService: IConfigurationService, @inject(IPythonExtensionBanner) @named(BANNER_NAME_PROPOSE_LS) private proposePylancePopup: IPythonExtensionBanner ) { - super(manager, workspace, fs, lsDownloader, languageServerFolderService, configurationService); + super(manager, workspace, fs, configurationService); } public async start(resource: Resource, interpreter?: PythonInterpreter): Promise { @@ -83,15 +84,17 @@ export class DotNetLanguageServerActivator extends LanguageServerActivatorBase { ): Promise { const settings = this.configurationService.getSettings(resource); if (settings.downloadLanguageServer === false) { - // Development mde + // Development mode return; } const languageServerFolder = await this.languageServerFolderService.getLanguageServerFolderName(resource); - const languageServerFolderPath = path.join(EXTENSION_ROOT_DIR, languageServerFolder); - const mscorlib = path.join(languageServerFolderPath, fileName); - if (!(await this.fs.fileExists(mscorlib))) { - await this.lsDownloader.downloadLanguageServer(languageServerFolderPath, resource); + if (languageServerFolder) { + const languageServerFolderPath = path.join(EXTENSION_ROOT_DIR, languageServerFolder); + const mscorlib = path.join(languageServerFolderPath, fileName); + if (!(await this.fs.fileExists(mscorlib))) { + await this.lsDownloader.downloadLanguageServer(languageServerFolderPath, resource); + } + return languageServerFolderPath; } - return languageServerFolderPath; } } diff --git a/src/client/activation/node/activator.ts b/src/client/activation/node/activator.ts index 0bddad7517e9..0d42ef08665f 100644 --- a/src/client/activation/node/activator.ts +++ b/src/client/activation/node/activator.ts @@ -12,7 +12,7 @@ import { IConfigurationService, IExtensions, Resource } from '../../common/types import { Common, Pylance } from '../../common/utils/localize'; import { getPylanceExtensionUri } from '../../languageServices/proposeLanguageServerBanner'; import { LanguageServerActivatorBase } from '../common/activatorBase'; -import { ILanguageServerDownloader, ILanguageServerFolderService, ILanguageServerManager } from '../types'; +import { ILanguageServerManager } from '../types'; /** * Starts the Node.js-based language server managers per workspaces (currently one for first workspace). @@ -28,14 +28,12 @@ export class NodeLanguageServerActivator extends LanguageServerActivatorBase { @inject(ILanguageServerManager) manager: ILanguageServerManager, @inject(IWorkspaceService) workspace: IWorkspaceService, @inject(IFileSystem) fs: IFileSystem, - @inject(ILanguageServerDownloader) lsDownloader: ILanguageServerDownloader, - @inject(ILanguageServerFolderService) languageServerFolderService: ILanguageServerFolderService, @inject(IConfigurationService) configurationService: IConfigurationService, @inject(IExtensions) private readonly extensions: IExtensions, @inject(IApplicationShell) private readonly appShell: IApplicationShell, @inject(IApplicationEnvironment) private readonly appEnv: IApplicationEnvironment ) { - super(manager, workspace, fs, lsDownloader, languageServerFolderService, configurationService); + super(manager, workspace, fs, configurationService); } public async ensureLanguageServerIsAvailable(resource: Resource): Promise { diff --git a/src/test/activation/languageServer/activator.unit.test.ts b/src/test/activation/languageServer/activator.unit.test.ts index 1b393fa95488..01bc06ba31f9 100644 --- a/src/test/activation/languageServer/activator.unit.test.ts +++ b/src/test/activation/languageServer/activator.unit.test.ts @@ -6,9 +6,7 @@ import * as path from 'path'; import { anything, instance, mock, verify, when } from 'ts-mockito'; import { Uri } from 'vscode'; -import { LanguageServerDownloader } from '../../../client/activation/common/downloader'; import { DotNetLanguageServerActivator } from '../../../client/activation/languageServer/activator'; -import { DotNetLanguageServerFolderService } from '../../../client/activation/languageServer/languageServerFolderService'; import { DotNetLanguageServerManager } from '../../../client/activation/languageServer/manager'; import { ILanguageServerDownloader, @@ -16,20 +14,15 @@ import { ILanguageServerManager } from '../../../client/activation/types'; import { IWorkspaceService } from '../../../client/common/application/types'; -import { WorkspaceService } from '../../../client/common/application/workspace'; -import { PythonSettings } from '../../../client/common/configSettings'; -import { ConfigurationService } from '../../../client/common/configuration/service'; -import { FileSystem } from '../../../client/common/platform/fileSystem'; import { IFileSystem } from '../../../client/common/platform/types'; import { IConfigurationService, IPythonExtensionBanner, IPythonSettings } from '../../../client/common/types'; import { createDeferred } from '../../../client/common/utils/async'; import { EXTENSION_ROOT_DIR } from '../../../client/constants'; -import { ProposePylanceBanner } from '../../../client/languageServices/proposeLanguageServerBanner'; import { sleep } from '../../core'; // tslint:disable:max-func-body-length -suite('Language Server - Activator', () => { +suite('Microsoft Language Server - Activator', () => { let activator: DotNetLanguageServerActivator; let workspaceService: IWorkspaceService; let manager: ILanguageServerManager; @@ -41,13 +34,13 @@ suite('Language Server - Activator', () => { let banner: IPythonExtensionBanner; setup(() => { manager = mock(DotNetLanguageServerManager); - workspaceService = mock(WorkspaceService); - fs = mock(FileSystem); - lsDownloader = mock(LanguageServerDownloader); - lsFolderService = mock(DotNetLanguageServerFolderService); - configuration = mock(ConfigurationService); - settings = mock(PythonSettings); - banner = mock(ProposePylanceBanner); + workspaceService = mock(); + fs = mock(); + lsDownloader = mock(); + lsFolderService = mock(); + configuration = mock(); + settings = mock(); + banner = mock(); when(configuration.getSettings(anything())).thenReturn(instance(settings)); activator = new DotNetLanguageServerActivator( instance(manager), @@ -71,7 +64,6 @@ suite('Language Server - Activator', () => { }); test('Manager must be disposed', async () => { activator.dispose(); - verify(manager.dispose()).once(); }); test('Server should be disconnected but be started', async () => { @@ -151,11 +143,6 @@ suite('Language Server - Activator', () => { verify(workspaceService.workspaceFolders).once(); }); - test('Manager must be disposed', async () => { - activator.dispose(); - - verify(manager.dispose()).once(); - }); test('Download and check if ICU config exists', async () => { const languageServerFolder = 'Some folder name'; const languageServerFolderPath = path.join(EXTENSION_ROOT_DIR, languageServerFolder); diff --git a/src/test/activation/node/activator.unit.test.ts b/src/test/activation/node/activator.unit.test.ts new file mode 100644 index 000000000000..b28676e02cf3 --- /dev/null +++ b/src/test/activation/node/activator.unit.test.ts @@ -0,0 +1,178 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { expect } from 'chai'; +import { anyString, anything, instance, mock, verify, when } from 'ts-mockito'; +import { Extension, Uri } from 'vscode'; +import { NodeLanguageServerActivator } from '../../../client/activation/node/activator'; +import { NodeLanguageServerManager } from '../../../client/activation/node/manager'; +import { ILanguageServerManager } from '../../../client/activation/types'; +import { + IApplicationEnvironment, + IApplicationShell, + IWorkspaceService +} from '../../../client/common/application/types'; +import { WorkspaceService } from '../../../client/common/application/workspace'; +import { PythonSettings } from '../../../client/common/configSettings'; +import { ConfigurationService } from '../../../client/common/configuration/service'; +import { PYLANCE_EXTENSION_ID } from '../../../client/common/constants'; +import { FileSystem } from '../../../client/common/platform/fileSystem'; +import { IFileSystem } from '../../../client/common/platform/types'; +import { IConfigurationService, IExtensions, IPythonSettings } from '../../../client/common/types'; +import { Common, Pylance } from '../../../client/common/utils/localize'; + +// tslint:disable:max-func-body-length + +suite('Pylance Language Server - Activator', () => { + let activator: NodeLanguageServerActivator; + let workspaceService: IWorkspaceService; + let manager: ILanguageServerManager; + let fs: IFileSystem; + let configuration: IConfigurationService; + let settings: IPythonSettings; + let extensions: IExtensions; + let appShell: IApplicationShell; + let appEnv: IApplicationEnvironment; + // tslint:disable-next-line: no-any + let pylanceExtension: Extension; + setup(() => { + manager = mock(NodeLanguageServerManager); + workspaceService = mock(WorkspaceService); + fs = mock(FileSystem); + configuration = mock(ConfigurationService); + settings = mock(PythonSettings); + extensions = mock(); + appShell = mock(); + appEnv = mock(); + // tslint:disable-next-line: no-any + pylanceExtension = mock>(); + when(configuration.getSettings(anything())).thenReturn(instance(settings)); + when(appEnv.uriScheme).thenReturn('scheme'); + activator = new NodeLanguageServerActivator( + instance(manager), + instance(workspaceService), + instance(fs), + instance(configuration), + instance(extensions), + instance(appShell), + instance(appEnv) + ); + }); + + test('Manager must be started without any workspace', async () => { + when(extensions.getExtension(PYLANCE_EXTENSION_ID)).thenReturn(instance(pylanceExtension)); + when(workspaceService.hasWorkspaceFolders).thenReturn(false); + when(manager.start(undefined, undefined)).thenResolve(); + + await activator.start(undefined); + verify(manager.start(undefined, undefined)).once(); + verify(workspaceService.hasWorkspaceFolders).once(); + }); + + test('Manager must be disposed', async () => { + activator.dispose(); + verify(manager.dispose()).once(); + }); + + test('Activator should check if Pylance is installed', async () => { + when(extensions.getExtension(PYLANCE_EXTENSION_ID)).thenReturn(instance(pylanceExtension)); + await activator.start(undefined); + verify(extensions.getExtension(PYLANCE_EXTENSION_ID)).once(); + }); + + test('Activator should not check if Pylance is installed in development mode', async () => { + when(settings.downloadLanguageServer).thenReturn(false); + await activator.start(undefined); + verify(extensions.getExtension(PYLANCE_EXTENSION_ID)).never(); + }); + + test('If Pylance is not installed, user should get prompt to install it', async () => { + expect(activator.start(undefined)) + .to.eventually.be.rejectedWith(Pylance.pylanceNotInstalledMessage()) + .and.be.an.instanceOf(Error); + verify( + appShell.showErrorMessage(Pylance.installPylanceMessage(), Common.bannerLabelYes(), Common.bannerLabelNo()) + ).once(); + }); + + test('If Pylance is installed, user should not get prompt to install it', async () => { + when(extensions.getExtension(PYLANCE_EXTENSION_ID)).thenReturn(instance(pylanceExtension)); + await activator.start(undefined); + verify( + appShell.showErrorMessage(Pylance.installPylanceMessage(), Common.bannerLabelYes(), Common.bannerLabelNo()) + ).never(); + }); + + test('If Pylance is not installed and user responded Yes, Pylance install page should be opened', async () => { + when( + appShell.showErrorMessage(Pylance.installPylanceMessage(), Common.bannerLabelYes(), Common.bannerLabelNo()) + ).thenReturn(Promise.resolve(Common.bannerLabelYes())); + + try { + await activator.start(undefined); + // tslint:disable-next-line: no-empty + } catch {} + verify(appShell.openUrl(`scheme:extension/${PYLANCE_EXTENSION_ID}`)).once(); + verify(manager.connect()).never(); + }); + + test('If Pylance is not installed and user responded Yes, Pylance activator should throw', async () => { + when( + appShell.showErrorMessage(Pylance.installPylanceMessage(), Common.bannerLabelYes(), Common.bannerLabelNo()) + ).thenReturn(Promise.resolve(Common.bannerLabelYes())); + + expect(activator.start(undefined)) + .to.eventually.be.rejectedWith(Pylance.pylanceNotInstalledMessage()) + .and.be.an.instanceOf(Error); + + verify(manager.connect()).never(); + }); + + test('If Pylance is not installed and user responded No, Pylance install page should not be opened', async () => { + when( + appShell.showErrorMessage(Pylance.installPylanceMessage(), Common.bannerLabelYes(), Common.bannerLabelNo()) + ).thenReturn(Promise.resolve(Common.bannerLabelNo())); + + try { + await activator.start(undefined); + // tslint:disable-next-line: no-empty + } catch {} + verify(appShell.openUrl(anyString())).never(); + verify(manager.connect()).never(); + }); + + test('If Pylance is not installed and user responded No, Pylance activator should throw', async () => { + when( + appShell.showErrorMessage(Pylance.installPylanceMessage(), Common.bannerLabelYes(), Common.bannerLabelNo()) + ).thenReturn(Promise.resolve(Common.bannerLabelNo())); + + expect(activator.start(undefined)) + .to.eventually.be.rejectedWith(Pylance.pylanceNotInstalledMessage()) + .and.be.an.instanceOf(Error); + verify(manager.connect()).never(); + }); + + test('Server should be disconnected but be started', async () => { + when(extensions.getExtension(PYLANCE_EXTENSION_ID)).thenReturn(instance(pylanceExtension)); + await activator.start(undefined); + + verify(manager.start(undefined, undefined)).once(); + verify(manager.connect()).never(); + }); + + test('Manager must be started with resource for first available workspace', async () => { + const uri = Uri.file(__filename); + when(workspaceService.hasWorkspaceFolders).thenReturn(true); + when(workspaceService.workspaceFolders).thenReturn([{ index: 0, name: '', uri }]); + when(manager.start(uri, undefined)).thenResolve(); + when(settings.downloadLanguageServer).thenReturn(false); + + await activator.start(undefined); + + verify(manager.start(uri, undefined)).once(); + verify(workspaceService.hasWorkspaceFolders).once(); + verify(workspaceService.workspaceFolders).once(); + }); +}); From 7518be33ae4dc00750111f657462dc60566db5ee Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Wed, 22 Jul 2020 16:24:31 -0700 Subject: [PATCH 10/27] Fix loc tests --- src/client/common/utils/localize.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index f462b64c9fab..9870bf43e6e6 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -102,18 +102,18 @@ export namespace AttachProcess { export namespace Pylance { export const proposePylanceMessage = localize( - 'LanguageService.proposePylanceMessage', + 'Pylance.proposePylanceMessage', 'Try out a new faster, feature-rich language server for Python by Microsoft, Pylance! Install the extension now.' ); - export const tryItNow = localize('LanguageService.tryItNow', 'Try it now'); - export const remindMeLater = localize('LanguageService.remindMeLater', 'Remind me later'); + export const tryItNow = localize('Pylance.tryItNow', 'Try it now'); + export const remindMeLater = localize('Pylance.remindMeLater', 'Remind me later'); export const installPylanceMessage = localize( - 'LanguageService.installPylanceMessage', + 'Pylance.installPylanceMessage', 'Pylance extension is not installed. Click Yes to open Pylance installation page.' ); export const pylanceNotInstalledMessage = localize( - 'LanguageService.pylanceNotInstalledMessage', + 'Pylance.pylanceNotInstalledMessage', 'Pylance extension is not installed.' ); } From 32d3626d8cba42e63352e4dcc70b06ea51ce9d5a Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Thu, 23 Jul 2020 10:16:46 -0700 Subject: [PATCH 11/27] Add news --- news/1 Enhancements/13122.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/1 Enhancements/13122.md diff --git a/news/1 Enhancements/13122.md b/news/1 Enhancements/13122.md new file mode 100644 index 000000000000..2d36013ce0d7 --- /dev/null +++ b/news/1 Enhancements/13122.md @@ -0,0 +1 @@ +Expose `Pylance` setting in `python.languageServer`. If [Pylance extension](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance) is not installed, prompt user to install it. From d7f106a429d71007e127ffd27327deb350f329f0 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Thu, 23 Jul 2020 13:24:46 -0700 Subject: [PATCH 12/27] Fix test --- src/test/activation/languageServer/activator.unit.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/activation/languageServer/activator.unit.test.ts b/src/test/activation/languageServer/activator.unit.test.ts index 01bc06ba31f9..13452eac1356 100644 --- a/src/test/activation/languageServer/activator.unit.test.ts +++ b/src/test/activation/languageServer/activator.unit.test.ts @@ -67,6 +67,7 @@ suite('Microsoft Language Server - Activator', () => { verify(manager.dispose()).once(); }); test('Server should be disconnected but be started', async () => { + when(workspaceService.hasWorkspaceFolders).thenReturn(false); await activator.start(undefined); verify(manager.start(undefined, undefined)).once(); From 07ea40f74b319d48f596b83c8c01970552942f38 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Thu, 23 Jul 2020 14:26:47 -0700 Subject: [PATCH 13/27] Add reload on extension install --- src/client/activation/node/activator.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/client/activation/node/activator.ts b/src/client/activation/node/activator.ts index 0d42ef08665f..98aeb421fe92 100644 --- a/src/client/activation/node/activator.ts +++ b/src/client/activation/node/activator.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. import { inject, injectable } from 'inversify'; -import { CancellationToken, CompletionItem, ProviderResult } from 'vscode'; +import { CancellationToken, commands, CompletionItem, ProviderResult } from 'vscode'; // tslint:disable-next-line: import-name import ProtocolCompletionItem from 'vscode-languageclient/lib/common/protocolCompletionItem'; import { CompletionResolveRequest } from 'vscode-languageclient/node'; @@ -55,6 +55,13 @@ export class NodeLanguageServerActivator extends LanguageServerActivatorBase { if (response === Common.bannerLabelYes()) { this.appShell.openUrl(getPylanceExtensionUri(this.appEnv)); } + + this.extensions.onDidChange(() => { + if (this.extensions.getExtension(PYLANCE_EXTENSION_ID)) { + commands.executeCommand('workbench.action.reloadWindow'); + } + }); + // At this time there is no Pylance installed yet. // throwing will cause activator to use Jedi temporarily. throw new Error(Pylance.pylanceNotInstalledMessage()); From 4b45562abde16964e943e84d012d74c89a35ffd0 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Thu, 23 Jul 2020 21:34:28 -0700 Subject: [PATCH 14/27] Limit reload to once --- src/client/activation/node/activator.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/client/activation/node/activator.ts b/src/client/activation/node/activator.ts index 98aeb421fe92..e5ad0aeb2db4 100644 --- a/src/client/activation/node/activator.ts +++ b/src/client/activation/node/activator.ts @@ -24,6 +24,8 @@ import { ILanguageServerManager } from '../types'; */ @injectable() export class NodeLanguageServerActivator extends LanguageServerActivatorBase { + private pylanceInstalled = false; + constructor( @inject(ILanguageServerManager) manager: ILanguageServerManager, @inject(IWorkspaceService) workspace: IWorkspaceService, @@ -57,8 +59,9 @@ export class NodeLanguageServerActivator extends LanguageServerActivatorBase { } this.extensions.onDidChange(() => { - if (this.extensions.getExtension(PYLANCE_EXTENSION_ID)) { + if (this.extensions.getExtension(PYLANCE_EXTENSION_ID) && !this.pylanceInstalled) { commands.executeCommand('workbench.action.reloadWindow'); + this.pylanceInstalled = true; } }); From b1a8b8bf947eff0fd2b99ade3916bcf8bce0c27e Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Mon, 27 Jul 2020 13:29:50 -0700 Subject: [PATCH 15/27] Use command manager + add test --- src/client/activation/node/activator.ts | 14 +++++--- .../activation/node/activator.unit.test.ts | 33 +++++++++++++++++-- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/client/activation/node/activator.ts b/src/client/activation/node/activator.ts index e5ad0aeb2db4..a61e786302e8 100644 --- a/src/client/activation/node/activator.ts +++ b/src/client/activation/node/activator.ts @@ -1,11 +1,16 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. import { inject, injectable } from 'inversify'; -import { CancellationToken, commands, CompletionItem, ProviderResult } from 'vscode'; +import { CancellationToken, CompletionItem, ProviderResult } from 'vscode'; // tslint:disable-next-line: import-name import ProtocolCompletionItem from 'vscode-languageclient/lib/common/protocolCompletionItem'; import { CompletionResolveRequest } from 'vscode-languageclient/node'; -import { IApplicationEnvironment, IApplicationShell, IWorkspaceService } from '../../common/application/types'; +import { + IApplicationEnvironment, + IApplicationShell, + ICommandManager, + IWorkspaceService +} from '../../common/application/types'; import { PYLANCE_EXTENSION_ID } from '../../common/constants'; import { IFileSystem } from '../../common/platform/types'; import { IConfigurationService, IExtensions, Resource } from '../../common/types'; @@ -33,7 +38,8 @@ export class NodeLanguageServerActivator extends LanguageServerActivatorBase { @inject(IConfigurationService) configurationService: IConfigurationService, @inject(IExtensions) private readonly extensions: IExtensions, @inject(IApplicationShell) private readonly appShell: IApplicationShell, - @inject(IApplicationEnvironment) private readonly appEnv: IApplicationEnvironment + @inject(IApplicationEnvironment) private readonly appEnv: IApplicationEnvironment, + @inject(ICommandManager) private readonly commands: ICommandManager ) { super(manager, workspace, fs, configurationService); } @@ -60,7 +66,7 @@ export class NodeLanguageServerActivator extends LanguageServerActivatorBase { this.extensions.onDidChange(() => { if (this.extensions.getExtension(PYLANCE_EXTENSION_ID) && !this.pylanceInstalled) { - commands.executeCommand('workbench.action.reloadWindow'); + this.commands.executeCommand('workbench.action.reloadWindow'); this.pylanceInstalled = true; } }); diff --git a/src/test/activation/node/activator.unit.test.ts b/src/test/activation/node/activator.unit.test.ts index b28676e02cf3..15c59a33b4c0 100644 --- a/src/test/activation/node/activator.unit.test.ts +++ b/src/test/activation/node/activator.unit.test.ts @@ -5,13 +5,14 @@ import { expect } from 'chai'; import { anyString, anything, instance, mock, verify, when } from 'ts-mockito'; -import { Extension, Uri } from 'vscode'; +import { EventEmitter, Extension, Uri } from 'vscode'; import { NodeLanguageServerActivator } from '../../../client/activation/node/activator'; import { NodeLanguageServerManager } from '../../../client/activation/node/manager'; import { ILanguageServerManager } from '../../../client/activation/types'; import { IApplicationEnvironment, IApplicationShell, + ICommandManager, IWorkspaceService } from '../../../client/common/application/types'; import { WorkspaceService } from '../../../client/common/application/workspace'; @@ -35,6 +36,9 @@ suite('Pylance Language Server - Activator', () => { let extensions: IExtensions; let appShell: IApplicationShell; let appEnv: IApplicationEnvironment; + let commands: ICommandManager; + let extensionsChangedEvent: EventEmitter; + // tslint:disable-next-line: no-any let pylanceExtension: Extension; setup(() => { @@ -46,10 +50,16 @@ suite('Pylance Language Server - Activator', () => { extensions = mock(); appShell = mock(); appEnv = mock(); + commands = mock(); + // tslint:disable-next-line: no-any pylanceExtension = mock>(); when(configuration.getSettings(anything())).thenReturn(instance(settings)); when(appEnv.uriScheme).thenReturn('scheme'); + + extensionsChangedEvent = new EventEmitter(); + when(extensions.onDidChange).thenReturn(extensionsChangedEvent.event); + activator = new NodeLanguageServerActivator( instance(manager), instance(workspaceService), @@ -57,9 +67,13 @@ suite('Pylance Language Server - Activator', () => { instance(configuration), instance(extensions), instance(appShell), - instance(appEnv) + instance(appEnv), + instance(commands) ); }); + teardown(() => { + extensionsChangedEvent.dispose(); + }); test('Manager must be started without any workspace', async () => { when(extensions.getExtension(PYLANCE_EXTENSION_ID)).thenReturn(instance(pylanceExtension)); @@ -127,9 +141,24 @@ suite('Pylance Language Server - Activator', () => { .to.eventually.be.rejectedWith(Pylance.pylanceNotInstalledMessage()) .and.be.an.instanceOf(Error); + extensionsChangedEvent.fire(); verify(manager.connect()).never(); }); + test('If Pylance is not installed and user responded Yes, reload should be called after installation', async () => { + when( + appShell.showErrorMessage(Pylance.installPylanceMessage(), Common.bannerLabelYes(), Common.bannerLabelNo()) + ).thenReturn(Promise.resolve(Common.bannerLabelYes())); + + try { + await activator.start(undefined); + // tslint:disable-next-line: no-empty + } catch {} + when(extensions.getExtension(PYLANCE_EXTENSION_ID)).thenReturn(pylanceExtension); + extensionsChangedEvent.fire(); + verify(commands.executeCommand('workbench.action.reloadWindow')).once(); + }); + test('If Pylance is not installed and user responded No, Pylance install page should not be opened', async () => { when( appShell.showErrorMessage(Pylance.installPylanceMessage(), Common.bannerLabelYes(), Common.bannerLabelNo()) From fc99438872836e0a1b046b6a56e06cb78c1b9d63 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Mon, 27 Jul 2020 13:46:34 -0700 Subject: [PATCH 16/27] Capilatization of `Language Server` --- package.nls.json | 8 ++++---- src/client/activation/activationService.ts | 2 +- .../activation/common/languageServerFolderService.ts | 2 +- .../activation/languageServer/languageServerProxy.ts | 8 ++++---- src/client/activation/languageServer/manager.ts | 10 +++++----- src/client/activation/node/languageServerProxy.ts | 6 +++--- src/client/activation/node/manager.ts | 10 +++++----- src/client/common/featureDeprecationManager.ts | 2 +- src/client/common/utils/localize.ts | 6 +++--- .../intellisense/intellisenseProvider.ts | 2 +- src/client/providers/jediProxy.ts | 2 +- src/client/telemetry/index.ts | 4 ++-- .../languageServer/downloadChannelRules.unit.test.ts | 4 ++-- .../languageServerPackageService.test.ts | 2 +- .../activation/languageServer/manager.unit.test.ts | 2 +- src/test/analysisEngineTest.ts | 2 +- src/test/linters/linterinfo.unit.test.ts | 4 ++-- src/test/performance/load.perf.test.ts | 2 +- src/test/performanceTest.ts | 2 +- 19 files changed, 40 insertions(+), 40 deletions(-) diff --git a/package.nls.json b/package.nls.json index 7f4d02f60c3e..15853f25d4f8 100644 --- a/package.nls.json +++ b/package.nls.json @@ -38,7 +38,7 @@ "DataScience.launchNotebookTrustPrompt.trustAllNotebooks": "Trust all notebooks", "DataScience.insecureSessionMessage": "Connecting over HTTP without a token may be an insecure connection. Do you want to connect to a possibly insecure server?", "DataScience.insecureSessionDenied": "Denied connection to insecure server.", - "python.command.python.viewLanguageServerOutput.title": "Show Language Server Output", + "python.command.python.viewLanguageServerOutput.title": "Show language server Output", "python.command.python.selectAndRunTestMethod.title": "Run Test Method ...", "python.command.python.selectAndDebugTestMethod.title": "Debug Test Method ...", "python.command.python.selectAndRunTestFile.title": "Run Test File ...", @@ -353,9 +353,9 @@ "Testing.configureTests": "Configure Test Framework", "Testing.disableTests": "Disable Tests", "Common.openOutputPanel": "Show output", - "LanguageService.lsFailedToStart": "We encountered an issue starting the Language Server. Reverting to the alternative, Jedi. Check the Python output panel for details.", - "LanguageService.lsFailedToDownload": "We encountered an issue downloading the Language Server. Reverting to the alternative, Jedi. Check the Python output panel for details.", - "LanguageService.lsFailedToExtract": "We encountered an issue extracting the Language Server. Reverting to the alternative, Jedi. Check the Python output panel for details.", + "LanguageService.lsFailedToStart": "We encountered an issue starting the language server. Reverting to Jedi language engine. Check the Python output panel for details.", + "LanguageService.lsFailedToDownload": "We encountered an issue downloading the language server. Reverting to Jedi language engine. Check the Python output panel for details.", + "LanguageService.lsFailedToExtract": "We encountered an issue extracting the language server. Reverting to Jedi language engine. Check the Python output panel for details.", "LanguageService.downloadFailedOutputMessage": "Language server download failed", "LanguageService.extractionFailedOutputMessage": "Language server extraction failed", "LanguageService.extractionCompletedOutputMessage": "Language server download complete", diff --git a/src/client/activation/activationService.ts b/src/client/activation/activationService.ts index 8b2223e47ad7..8e04b4b65427 100644 --- a/src/client/activation/activationService.ts +++ b/src/client/activation/activationService.ts @@ -127,7 +127,7 @@ export class LanguageServerExtensionActivationService this.activatedServer.server.dispose(); } } - @swallowExceptions('Send telemetry for Language Server current selection') + @swallowExceptions('Send telemetry for language server current selection') public async sendTelemetryForChosenLanguageServer(languageServer: LanguageServerType): Promise { const state = this.stateFactory.createGlobalPersistentState( 'SWITCH_LS', diff --git a/src/client/activation/common/languageServerFolderService.ts b/src/client/activation/common/languageServerFolderService.ts index bcf34b70aa7f..ae501d8b709e 100644 --- a/src/client/activation/common/languageServerFolderService.ts +++ b/src/client/activation/common/languageServerFolderService.ts @@ -53,7 +53,7 @@ export abstract class LanguageServerFolderService implements ILanguageServerFold return `${this.languageServerFolder}.${serverVersion!.version.raw}`; } - @traceDecorators.verbose('Get latest version of Language Server') + @traceDecorators.verbose('Get latest version of language server') public getLatestLanguageServerVersion(resource: Resource): Promise { const minVersion = this.getMinimalLanguageServerVersion(); const lsPackageService = this.serviceContainer.get( diff --git a/src/client/activation/languageServer/languageServerProxy.ts b/src/client/activation/languageServer/languageServerProxy.ts index 1d7c0cf8d33a..96dff89c8dbe 100644 --- a/src/client/activation/languageServer/languageServerProxy.ts +++ b/src/client/activation/languageServer/languageServerProxy.ts @@ -33,7 +33,7 @@ export class DotNetLanguageServerProxy implements ILanguageServerProxy { ) { this.startupCompleted = createDeferred(); } - @traceDecorators.verbose('Stopping Language Server') + @traceDecorators.verbose('Stopping language server') public dispose() { if (this.languageClient) { // Do not await on this. @@ -45,7 +45,7 @@ export class DotNetLanguageServerProxy implements ILanguageServerProxy { d.dispose(); } if (this.startupCompleted.completed) { - this.startupCompleted.reject(new Error('Disposed Language Server')); + this.startupCompleted.reject(new Error('Disposed language server')); this.startupCompleted = createDeferred(); } this.disposed = true; @@ -86,7 +86,7 @@ export class DotNetLanguageServerProxy implements ILanguageServerProxy { await this.startupCompleted.promise; } } - @traceDecorators.error('Failed to load Language Server extension') + @traceDecorators.error('Failed to load language server extension') public loadExtension(args?: {}) { if (this.extensionLoadedArgs.has(args || '')) { return; @@ -111,7 +111,7 @@ export class DotNetLanguageServerProxy implements ILanguageServerProxy { } this.startupCompleted.resolve(); } - @swallowExceptions('Activating Unit Tests Manager for Language Server') + @swallowExceptions('Activating Unit Tests Manager for Microssoft Python Language Server') protected async registerTestServices() { if (!this.languageClient) { throw new Error('languageClient not initialized'); diff --git a/src/client/activation/languageServer/manager.ts b/src/client/activation/languageServer/manager.ts index 9421d4f4099a..6055a6ea35ad 100644 --- a/src/client/activation/languageServer/manager.ts +++ b/src/client/activation/languageServer/manager.ts @@ -58,10 +58,10 @@ export class DotNetLanguageServerManager implements ILanguageServerManager { public get languageProxy() { return this.languageServerProxy; } - @traceDecorators.error('Failed to start Language Server') + @traceDecorators.error('Failed to start language server') public async start(resource: Resource, interpreter: PythonInterpreter | undefined): Promise { if (this.languageProxy) { - throw new Error('Language Server already started'); + throw new Error('Language server already started'); } this.registerCommandHandler(); this.resource = resource; @@ -94,8 +94,8 @@ export class DotNetLanguageServerManager implements ILanguageServerManager { protected restartLanguageServerDebounced(): void { this.restartLanguageServer().ignoreErrors(); } - @traceDecorators.error('Failed to restart Language Server') - @traceDecorators.verbose('Restarting Language Server') + @traceDecorators.error('Failed to restart language server') + @traceDecorators.verbose('Restarting language server') protected async restartLanguageServer(): Promise { if (this.languageProxy) { this.languageProxy.dispose(); @@ -109,7 +109,7 @@ export class DotNetLanguageServerManager implements ILanguageServerManager { undefined, DotNetLanguageServerManager.versionTelemetryProps ) - @traceDecorators.verbose('Starting Language Server') + @traceDecorators.verbose('Starting language server') protected async startLanguageServer(): Promise { this.languageServerProxy = this.serviceContainer.get(ILanguageServerProxy); diff --git a/src/client/activation/node/languageServerProxy.ts b/src/client/activation/node/languageServerProxy.ts index 4d5bfc9429cd..88ec6661db2c 100644 --- a/src/client/activation/node/languageServerProxy.ts +++ b/src/client/activation/node/languageServerProxy.ts @@ -51,7 +51,7 @@ export class NodeLanguageServerProxy implements ILanguageServerProxy { }; } - @traceDecorators.verbose('Stopping Language Server') + @traceDecorators.verbose('Stopping language server') public dispose() { if (this.languageClient) { // Do not await on this. @@ -67,7 +67,7 @@ export class NodeLanguageServerProxy implements ILanguageServerProxy { d.dispose(); } if (this.startupCompleted.completed) { - this.startupCompleted.reject(new Error('Disposed Language Server')); + this.startupCompleted.reject(new Error('Disposed language server')); this.startupCompleted = createDeferred(); } this.disposed = true; @@ -155,7 +155,7 @@ export class NodeLanguageServerProxy implements ILanguageServerProxy { this.startupCompleted.resolve(); } - @swallowExceptions('Activating Unit Tests Manager for Language Server') + @swallowExceptions('Activating Unit Tests Manager for Pylance language server') protected async registerTestServices() { if (!this.languageClient) { throw new Error('languageClient not initialized'); diff --git a/src/client/activation/node/manager.ts b/src/client/activation/node/manager.ts index 6f83029a3a64..14d64d9c1d44 100644 --- a/src/client/activation/node/manager.ts +++ b/src/client/activation/node/manager.ts @@ -58,10 +58,10 @@ export class NodeLanguageServerManager implements ILanguageServerManager { return this.languageServerProxy; } - @traceDecorators.error('Failed to start Language Server') + @traceDecorators.error('Failed to start language server') public async start(resource: Resource, interpreter: PythonInterpreter | undefined): Promise { if (this.languageProxy) { - throw new Error('Language Server already started'); + throw new Error('Language server already started'); } this.resource = resource; this.interpreter = interpreter; @@ -89,8 +89,8 @@ export class NodeLanguageServerManager implements ILanguageServerManager { this.restartLanguageServer().ignoreErrors(); } - @traceDecorators.error('Failed to restart Language Server') - @traceDecorators.verbose('Restarting Language Server') + @traceDecorators.error('Failed to restart language server') + @traceDecorators.verbose('Restarting language server') protected async restartLanguageServer(): Promise { if (this.languageProxy) { this.languageProxy.dispose(); @@ -105,7 +105,7 @@ export class NodeLanguageServerManager implements ILanguageServerManager { undefined, NodeLanguageServerManager.versionTelemetryProps ) - @traceDecorators.verbose('Starting Language Server') + @traceDecorators.verbose('Starting language server') protected async startLanguageServer(): Promise { this.languageServerProxy = this.serviceContainer.get(ILanguageServerProxy); diff --git a/src/client/common/featureDeprecationManager.ts b/src/client/common/featureDeprecationManager.ts index 870743ce0723..79a4af0cd566 100644 --- a/src/client/common/featureDeprecationManager.ts +++ b/src/client/common/featureDeprecationManager.ts @@ -30,7 +30,7 @@ const deprecatedFeatures: DeprecatedFeatureInfo[] = [ { doNotDisplayPromptStateKey: 'SHOW_DEPRECATED_FEATURE_PROMPT_FOR_AUTO_COMPLETE_PRELOAD_MODULES', message: - "The setting 'python.autoComplete.preloadModules' is deprecated, please consider using Microsoft Language Server ('python.languageServer' setting).", + "The setting 'python.autoComplete.preloadModules' is deprecated, please consider using Pylance Language Server ('python.languageServer' setting).", moreInfoUrl: 'https://github.com/Microsoft/vscode-python/issues/1704', setting: { setting: 'autoComplete.preloadModules' } } diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index 9870bf43e6e6..89462f860920 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -132,15 +132,15 @@ export namespace LanguageService { export const lsFailedToStart = localize( 'LanguageService.lsFailedToStart', - 'We encountered an issue starting the Language Server. Reverting to the alternative, Jedi. Check the Python output panel for details.' + 'We encountered an issue starting the language server. Reverting to Jedi language engine. Check the Python output panel for details.' ); export const lsFailedToDownload = localize( 'LanguageService.lsFailedToDownload', - 'We encountered an issue downloading the Language Server. Reverting to the alternative, Jedi. Check the Python output panel for details.' + 'We encountered an issue downloading the language server. Reverting to Jedi language engine. Check the Python output panel for details.' ); export const lsFailedToExtract = localize( 'LanguageService.lsFailedToExtract', - 'We encountered an issue extracting the Language Server. Reverting to the alternative, Jedi. Check the Python output panel for details.' + 'We encountered an issue extracting the language server. Reverting to Jedi language engine. Check the Python output panel for details.' ); export const downloadFailedOutputMessage = localize( 'LanguageService.downloadFailedOutputMessage', diff --git a/src/client/datascience/interactive-common/intellisense/intellisenseProvider.ts b/src/client/datascience/interactive-common/intellisense/intellisenseProvider.ts index 71cb79ecc845..c0d5ef24b7d1 100644 --- a/src/client/datascience/interactive-common/intellisense/intellisenseProvider.ts +++ b/src/client/datascience/interactive-common/intellisense/intellisenseProvider.ts @@ -413,7 +413,7 @@ export class IntellisenseProvider implements IInteractiveWindowListener { return this.combineCompletions( await Promise.all([ - // Ensure we wait for a result from Language Server (assumption is LS is faster). + // Ensure we wait for a result from language server (assumption is LS is faster). // Telemetry will prove/disprove this assumption and we'll change this code accordingly. lsCompletions, // Wait for a max of n ms before ignoring results from jupyter (jupyter completion is generally slower). diff --git a/src/client/providers/jediProxy.ts b/src/client/providers/jediProxy.ts index add675bb2e76..85e719a96796 100644 --- a/src/client/providers/jediProxy.ts +++ b/src/client/providers/jediProxy.ts @@ -354,7 +354,7 @@ export class JediProxy implements Disposable { // tslint:disable-next-line:max-func-body-length private async spawnProcess() { if (this.languageServerStarted && !this.languageServerStarted.completed) { - this.languageServerStarted.reject(new Error('Language Server not started.')); + this.languageServerStarted.reject(new Error('Language server not started.')); } this.languageServerStarted = createDeferred(); const pythonProcess = await this.serviceContainer diff --git a/src/client/telemetry/index.ts b/src/client/telemetry/index.ts index 9b0071c21894..cec850d7defb 100644 --- a/src/client/telemetry/index.ts +++ b/src/client/telemetry/index.ts @@ -1291,11 +1291,11 @@ export interface IEventNamePropertyMapping { */ [EventName.PYTHON_LANGUAGE_SERVER_NONE]: never | undefined; /** - * Telemetry sent from Language Server (details of telemetry sent can be provided by LS team) + * Telemetry sent from language server (details of telemetry sent can be provided by LS team) */ [EventName.PYTHON_LANGUAGE_SERVER_TELEMETRY]: any; /** - * Telemetry sent when the client makes a request to the Language Server + * Telemetry sent when the client makes a request to the language server */ [EventName.PYTHON_LANGUAGE_SERVER_REQUEST]: any; /** diff --git a/src/test/activation/languageServer/downloadChannelRules.unit.test.ts b/src/test/activation/languageServer/downloadChannelRules.unit.test.ts index 656f19c75f70..2b7c09eddb9e 100644 --- a/src/test/activation/languageServer/downloadChannelRules.unit.test.ts +++ b/src/test/activation/languageServer/downloadChannelRules.unit.test.ts @@ -15,10 +15,10 @@ import { import { IPersistentState, IPersistentStateFactory } from '../../../client/common/types'; import { IServiceContainer } from '../../../client/ioc/types'; -suite('Language Server Download Channel Rules', () => { +suite('Microsoft Language Server Download Channel Rules', () => { [undefined, path.join('a', 'b')].forEach((currentFolderPath) => { const currentFolder = currentFolderPath ? { path: currentFolderPath, version: new SemVer('0.0.0') } : undefined; - const testSuffix = ` (${currentFolderPath ? 'with' : 'without'} an existing Language Server Folder`; + const testSuffix = ` (${currentFolderPath ? 'with' : 'without'} an existing language server Folder`; test(`Daily channel should always download ${testSuffix}`, async () => { const rule = new DownloadDailyChannelRule(); diff --git a/src/test/activation/languageServer/languageServerPackageService.test.ts b/src/test/activation/languageServer/languageServerPackageService.test.ts index e401d524e428..1101876781e2 100644 --- a/src/test/activation/languageServer/languageServerPackageService.test.ts +++ b/src/test/activation/languageServer/languageServerPackageService.test.ts @@ -26,7 +26,7 @@ suite('Language Server Package Service', () => { setup(() => { serviceContainer = typeMoq.Mock.ofType(); }); - test('Ensure new Major versions of Language Server is accounted for (azure blob)', async () => { + test('Ensure new Major versions of language server is accounted for (azure blob)', async () => { const nugetService = new NugetService(); serviceContainer.setup((c) => c.get(typeMoq.It.isValue(INugetService))).returns(() => nugetService); const platformService = new PlatformService(); diff --git a/src/test/activation/languageServer/manager.unit.test.ts b/src/test/activation/languageServer/manager.unit.test.ts index 003595e47ad4..5d8f3ac37ab8 100644 --- a/src/test/activation/languageServer/manager.unit.test.ts +++ b/src/test/activation/languageServer/manager.unit.test.ts @@ -96,7 +96,7 @@ suite('Language Server - Manager', () => { await startLanguageServer(); await expect(manager.start(resource, undefined)).to.eventually.be.rejectedWith( - 'Language Server already started' + 'Language server already started' ); }); test('Changes in analysis options must restart LS', async () => { diff --git a/src/test/analysisEngineTest.ts b/src/test/analysisEngineTest.ts index 53044c724c5c..5a68406b9f32 100644 --- a/src/test/analysisEngineTest.ts +++ b/src/test/analysisEngineTest.ts @@ -13,7 +13,7 @@ process.env.TEST_FILES_SUFFIX = 'ls.test'; function start() { console.log('*'.repeat(100)); - console.log('Start Language Server tests'); + console.log('Start language server tests'); require('../../node_modules/vscode/bin/test'); } start(); diff --git a/src/test/linters/linterinfo.unit.test.ts b/src/test/linters/linterinfo.unit.test.ts index 4ec1f5f5e03f..a648c1949d46 100644 --- a/src/test/linters/linterinfo.unit.test.ts +++ b/src/test/linters/linterinfo.unit.test.ts @@ -47,7 +47,7 @@ suite('Linter Info - Pylint', () => { expect(linterInfo.isEnabled()).to.be.true; }); - test('Test disabled when using Language Server and Pylint is not configured', async () => { + test('Test disabled when using language server and Pylint is not configured', async () => { const linterInfo = new PylintLinterInfo(instance(config), instance(workspace), []); when(config.getSettings(anything())).thenReturn({ @@ -63,7 +63,7 @@ suite('Linter Info - Pylint', () => { expect(linterInfo.isEnabled()).to.be.false; }); - test('Should inspect the value of linting.pylintEnabled when using Language Server', async () => { + test('Should inspect the value of linting.pylintEnabled when using language server', async () => { const linterInfo = new PylintLinterInfo(instance(config), instance(workspace), []); const inspectStub = sinon.stub(); const pythonConfig = { diff --git a/src/test/performance/load.perf.test.ts b/src/test/performance/load.perf.test.ts index 5521cb11566f..05e3ff90627a 100644 --- a/src/test/performance/load.perf.test.ts +++ b/src/test/performance/load.perf.test.ts @@ -77,7 +77,7 @@ suite('Activation Times', () => { console.log(`Dev version loaded in ${devActivationAvgTime}ms`); console.log(`Release version loaded in ${releaseActivationAvgTime}ms`); - console.log(`Language Server loaded in ${languageServerActivationAvgTime}ms`); + console.log(`Language server loaded in ${languageServerActivationAvgTime}ms`); expect(devActivationAvgTime - releaseActivationAvgTime).to.be.lessThan( AllowedIncreaseInActivationDelayInMS, diff --git a/src/test/performanceTest.ts b/src/test/performanceTest.ts index 938630943570..df2208e19481 100644 --- a/src/test/performanceTest.ts +++ b/src/test/performanceTest.ts @@ -67,7 +67,7 @@ class TestRunner { // Language server. await this.enableLanguageServer(true); const languageServerLogFile = path.join(logFilesPath, `languageServer_loadtimes${i}.txt`); - console.log(`Start Performance Tests: Counter ${i}, for Release version with Language Server`); + console.log(`Start Performance Tests: Counter ${i}, for Release version with language server`); await this.capturePerfTimes(Version.Release, languageServerLogFile); languageServerLogFiles.push(languageServerLogFile); } From 2f4c12f9919ec34524471582479befb5ff970bfb Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Mon, 27 Jul 2020 15:31:46 -0700 Subject: [PATCH 17/27] Fix typos --- package.nls.json | 2 +- src/client/activation/languageServer/languageServerProxy.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.nls.json b/package.nls.json index 15853f25d4f8..1f5cb19c43dc 100644 --- a/package.nls.json +++ b/package.nls.json @@ -38,7 +38,7 @@ "DataScience.launchNotebookTrustPrompt.trustAllNotebooks": "Trust all notebooks", "DataScience.insecureSessionMessage": "Connecting over HTTP without a token may be an insecure connection. Do you want to connect to a possibly insecure server?", "DataScience.insecureSessionDenied": "Denied connection to insecure server.", - "python.command.python.viewLanguageServerOutput.title": "Show language server Output", + "python.command.python.viewLanguageServerOutput.title": "Show Language Server Output", "python.command.python.selectAndRunTestMethod.title": "Run Test Method ...", "python.command.python.selectAndDebugTestMethod.title": "Debug Test Method ...", "python.command.python.selectAndRunTestFile.title": "Run Test File ...", diff --git a/src/client/activation/languageServer/languageServerProxy.ts b/src/client/activation/languageServer/languageServerProxy.ts index 96dff89c8dbe..bc89929cfa38 100644 --- a/src/client/activation/languageServer/languageServerProxy.ts +++ b/src/client/activation/languageServer/languageServerProxy.ts @@ -111,7 +111,7 @@ export class DotNetLanguageServerProxy implements ILanguageServerProxy { } this.startupCompleted.resolve(); } - @swallowExceptions('Activating Unit Tests Manager for Microssoft Python Language Server') + @swallowExceptions('Activating Unit Tests Manager for Microsoft Python Language Server') protected async registerTestServices() { if (!this.languageClient) { throw new Error('languageClient not initialized'); From b11463dfaded84e95e3bcef9fc3f72e0a6efee4e Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Mon, 27 Jul 2020 16:36:34 -0700 Subject: [PATCH 18/27] Prompt to reload --- package.nls.json | 1 + package.nls.ru.json | 3 +- src/client/activation/node/activator.ts | 20 +++++++++-- src/client/common/utils/localize.ts | 4 +++ .../activation/node/activator.unit.test.ts | 36 ++++++++++++++++++- 5 files changed, 59 insertions(+), 5 deletions(-) diff --git a/package.nls.json b/package.nls.json index 1f5cb19c43dc..0bfe84e704e8 100644 --- a/package.nls.json +++ b/package.nls.json @@ -114,6 +114,7 @@ "Pylance.remindMeLater": "Remind me later", "Pylance.installPylanceMessage": "Pylance extension is not installed. Click Yes to open Pylance installation page.", "Pylance.pylanceNotInstalledMessage": "Pylance extension is not installed.", + "Pylance.pylanceInstalledReloadPromptMessage": "Pylance extension is now installed. Reload window to activate?.", "DataScience.unknownMimeTypeFormat": "Mime type {0} is not currently supported.", "DataScience.historyTitle": "Python Interactive", "DataScience.dataExplorerTitle": "Data Viewer", diff --git a/package.nls.ru.json b/package.nls.ru.json index 4f4b17086c12..1440729d543f 100644 --- a/package.nls.ru.json +++ b/package.nls.ru.json @@ -35,5 +35,6 @@ "Pylance.tryItNow": "Да, хочу", "Pylance.remindMeLater": "Напомните позже", "Pylance.installPylanceMessage": "Расширение Pylance не установлено. Нажмите Да чтобы открыть страницу установки Pylance.", - "Pylance.pylanceNotInstalledMessage": "Расширение Pylance не установлено." + "Pylance.pylanceNotInstalledMessage": "Расширение Pylance не установлено.", + "Pylance.pylanceInstalledReloadPromptMessage": "Расширение Pylance установлено. Перезагрузить окно для его активации?" } diff --git a/src/client/activation/node/activator.ts b/src/client/activation/node/activator.ts index a61e786302e8..8351c465072b 100644 --- a/src/client/activation/node/activator.ts +++ b/src/client/activation/node/activator.ts @@ -14,6 +14,7 @@ import { import { PYLANCE_EXTENSION_ID } from '../../common/constants'; import { IFileSystem } from '../../common/platform/types'; import { IConfigurationService, IExtensions, Resource } from '../../common/types'; +import { createDeferred } from '../../common/utils/async'; import { Common, Pylance } from '../../common/utils/localize'; import { getPylanceExtensionUri } from '../../languageServices/proposeLanguageServerBanner'; import { LanguageServerActivatorBase } from '../common/activatorBase'; @@ -29,6 +30,7 @@ import { ILanguageServerManager } from '../types'; */ @injectable() export class NodeLanguageServerActivator extends LanguageServerActivatorBase { + private readonly pylanceInstallCompletedDeferred = createDeferred(); // For tests to track Pylance install completion. private pylanceInstalled = false; constructor( @@ -44,6 +46,10 @@ export class NodeLanguageServerActivator extends LanguageServerActivatorBase { super(manager, workspace, fs, configurationService); } + get pylanceInstallCompleted(): Promise { + return this.pylanceInstallCompletedDeferred.promise; + } + public async ensureLanguageServerIsAvailable(resource: Resource): Promise { const settings = this.configurationService.getSettings(resource); if (settings.downloadLanguageServer === false) { @@ -55,7 +61,7 @@ export class NodeLanguageServerActivator extends LanguageServerActivatorBase { return; } // Point user to Pylance at the store. - const response = await this.appShell.showErrorMessage( + let response = await this.appShell.showErrorMessage( Pylance.installPylanceMessage(), Common.bannerLabelYes(), Common.bannerLabelNo() @@ -64,10 +70,18 @@ export class NodeLanguageServerActivator extends LanguageServerActivatorBase { this.appShell.openUrl(getPylanceExtensionUri(this.appEnv)); } - this.extensions.onDidChange(() => { + this.extensions.onDidChange(async () => { if (this.extensions.getExtension(PYLANCE_EXTENSION_ID) && !this.pylanceInstalled) { - this.commands.executeCommand('workbench.action.reloadWindow'); this.pylanceInstalled = true; + response = await this.appShell.showWarningMessage( + Pylance.pylanceInstalledReloadPromptMessage(), + Common.bannerLabelYes(), + Common.bannerLabelNo() + ); + this.pylanceInstallCompletedDeferred.resolve(); + if (response === Common.bannerLabelYes()) { + this.commands.executeCommand('workbench.action.reloadWindow'); + } } }); diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index 89462f860920..482cbb0227c3 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -116,6 +116,10 @@ export namespace Pylance { 'Pylance.pylanceNotInstalledMessage', 'Pylance extension is not installed.' ); + export const pylanceInstalledReloadPromptMessage = localize( + 'Pylance.pylanceInstalledReloadPromptMessage', + 'Pylance extension is now installed. Reload window to activate?' + ); } export namespace LanguageService { diff --git a/src/test/activation/node/activator.unit.test.ts b/src/test/activation/node/activator.unit.test.ts index 15c59a33b4c0..4476e3a1348f 100644 --- a/src/test/activation/node/activator.unit.test.ts +++ b/src/test/activation/node/activator.unit.test.ts @@ -145,7 +145,7 @@ suite('Pylance Language Server - Activator', () => { verify(manager.connect()).never(); }); - test('If Pylance is not installed and user responded Yes, reload should be called after installation', async () => { + test('If Pylance is not installed and user responded Yes, reload should be called if user agreed to it', async () => { when( appShell.showErrorMessage(Pylance.installPylanceMessage(), Common.bannerLabelYes(), Common.bannerLabelNo()) ).thenReturn(Promise.resolve(Common.bannerLabelYes())); @@ -154,11 +154,45 @@ suite('Pylance Language Server - Activator', () => { await activator.start(undefined); // tslint:disable-next-line: no-empty } catch {} + when(extensions.getExtension(PYLANCE_EXTENSION_ID)).thenReturn(pylanceExtension); + when( + appShell.showWarningMessage( + Pylance.pylanceInstalledReloadPromptMessage(), + Common.bannerLabelYes(), + Common.bannerLabelNo() + ) + ).thenReturn(Promise.resolve(Common.bannerLabelYes())); + extensionsChangedEvent.fire(); + await activator.pylanceInstallCompleted; verify(commands.executeCommand('workbench.action.reloadWindow')).once(); }); + test('If Pylance is not installed and user responded Yes, reload should not be called if user refused it', async () => { + when( + appShell.showErrorMessage(Pylance.installPylanceMessage(), Common.bannerLabelYes(), Common.bannerLabelNo()) + ).thenReturn(Promise.resolve(Common.bannerLabelYes())); + + try { + await activator.start(undefined); + // tslint:disable-next-line: no-empty + } catch {} + + when(extensions.getExtension(PYLANCE_EXTENSION_ID)).thenReturn(pylanceExtension); + when( + appShell.showWarningMessage( + Pylance.pylanceInstalledReloadPromptMessage(), + Common.bannerLabelYes(), + Common.bannerLabelNo() + ) + ).thenReturn(Promise.resolve(Common.bannerLabelNo())); + + extensionsChangedEvent.fire(); + await activator.pylanceInstallCompleted; + verify(commands.executeCommand('workbench.action.reloadWindow')).never(); + }); + test('If Pylance is not installed and user responded No, Pylance install page should not be opened', async () => { when( appShell.showErrorMessage(Pylance.installPylanceMessage(), Common.bannerLabelYes(), Common.bannerLabelNo()) From 513021fd0c3cc3931f609a89340736e59f5c2689 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Mon, 27 Jul 2020 18:07:49 -0700 Subject: [PATCH 19/27] Typo --- package.nls.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.nls.json b/package.nls.json index 0bfe84e704e8..27b739ffab11 100644 --- a/package.nls.json +++ b/package.nls.json @@ -114,7 +114,7 @@ "Pylance.remindMeLater": "Remind me later", "Pylance.installPylanceMessage": "Pylance extension is not installed. Click Yes to open Pylance installation page.", "Pylance.pylanceNotInstalledMessage": "Pylance extension is not installed.", - "Pylance.pylanceInstalledReloadPromptMessage": "Pylance extension is now installed. Reload window to activate?.", + "Pylance.pylanceInstalledReloadPromptMessage": "Pylance extension is now installed. Reload window to activate?", "DataScience.unknownMimeTypeFormat": "Mime type {0} is not currently supported.", "DataScience.historyTitle": "Python Interactive", "DataScience.dataExplorerTitle": "Data Viewer", From 3a1c41c056030e5fe30bd67205b1eca7fe57e109 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Tue, 28 Jul 2020 13:43:19 -0700 Subject: [PATCH 20/27] Common Pylance check --- package.nls.json | 1 + package.nls.ru.json | 3 +- src/client/activation/activationService.ts | 76 +++---- .../common/languageServerChangeHandler.ts | 114 +++++++++++ src/client/activation/node/activator.ts | 62 ++---- src/client/common/utils/localize.ts | 5 + src/client/telemetry/constants.ts | 1 + src/client/telemetry/index.ts | 4 + .../activation/activationService.unit.test.ts | 11 +- .../activation/node/activator.unit.test.ts | 118 ++--------- .../languageServerChangeHandler.unit.test.ts | 189 ++++++++++++++++++ 11 files changed, 391 insertions(+), 193 deletions(-) create mode 100644 src/client/activation/common/languageServerChangeHandler.ts create mode 100644 src/test/activation/node/languageServerChangeHandler.unit.test.ts diff --git a/package.nls.json b/package.nls.json index 27b739ffab11..04201a6c0cc5 100644 --- a/package.nls.json +++ b/package.nls.json @@ -366,6 +366,7 @@ "LanguageService.startingMicrosoft": "Starting Microsoft Python language server.", "LanguageService.startingPylance": "Starting Pylance language server.", "LanguageService.startingNone": "Editor support is inactive since language server is set to None.", + "LanguageService.reloadAfterLanguageServerChange": "Please reload the window switching between language servers.", "AttachProcess.unsupportedOS": "Operating system '{0}' not supported.", "AttachProcess.attachTitle": "Attach to process", "AttachProcess.selectProcessPlaceholder": "Select the process to attach to", diff --git a/package.nls.ru.json b/package.nls.ru.json index 1440729d543f..9b6b0a5661da 100644 --- a/package.nls.ru.json +++ b/package.nls.ru.json @@ -36,5 +36,6 @@ "Pylance.remindMeLater": "Напомните позже", "Pylance.installPylanceMessage": "Расширение Pylance не установлено. Нажмите Да чтобы открыть страницу установки Pylance.", "Pylance.pylanceNotInstalledMessage": "Расширение Pylance не установлено.", - "Pylance.pylanceInstalledReloadPromptMessage": "Расширение Pylance установлено. Перезагрузить окно для его активации?" + "Pylance.pylanceInstalledReloadPromptMessage": "Расширение Pylance установлено. Перезагрузить окно для его активации?", + "LanguageService.reloadAfterLanguageServerChange": "Пожалуйста, перезагрузите окно после смены типа языкового сервера." } diff --git a/src/client/activation/activationService.ts b/src/client/activation/activationService.ts index 8e04b4b65427..698ca2d265bf 100644 --- a/src/client/activation/activationService.ts +++ b/src/client/activation/activationService.ts @@ -7,12 +7,18 @@ import { ConfigurationChangeEvent, Disposable, OutputChannel, Uri } from 'vscode import { LSNotSupportedDiagnosticServiceId } from '../application/diagnostics/checks/lsNotSupported'; import { IDiagnosticsService } from '../application/diagnostics/types'; -import { IApplicationShell, ICommandManager, IWorkspaceService } from '../common/application/types'; +import { + IApplicationEnvironment, + IApplicationShell, + ICommandManager, + IWorkspaceService +} from '../common/application/types'; import { STANDARD_OUTPUT_CHANNEL } from '../common/constants'; import { traceError } from '../common/logger'; import { IConfigurationService, IDisposableRegistry, + IExtensions, IOutputChannel, IPersistentStateFactory, IPythonSettings, @@ -26,6 +32,7 @@ import { IServiceContainer } from '../ioc/types'; import { PythonInterpreter } from '../pythonEnvironments/info'; import { sendTelemetryEvent } from '../telemetry'; import { EventName } from '../telemetry/constants'; +import { LanguageServerChangeHandler } from './common/languageServerChangeHandler'; import { Commands } from './languageServer/constants'; import { RefCountedLanguageServer } from './refCountedLanguageServer'; import { @@ -51,8 +58,8 @@ export class LanguageServerExtensionActivationService private activatedServer?: IActivatedServer; private readonly workspaceService: IWorkspaceService; private readonly output: OutputChannel; - private readonly appShell: IApplicationShell; private readonly interpreterService: IInterpreterService; + private readonly languageServerChangeHandler: LanguageServerChangeHandler; private resource!: Resource; constructor( @@ -62,7 +69,7 @@ export class LanguageServerExtensionActivationService this.workspaceService = this.serviceContainer.get(IWorkspaceService); this.interpreterService = this.serviceContainer.get(IInterpreterService); this.output = this.serviceContainer.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); - this.appShell = this.serviceContainer.get(IApplicationShell); + const commandManager = this.serviceContainer.get(ICommandManager); const disposables = serviceContainer.get(IDisposableRegistry); disposables.push(this); @@ -72,6 +79,15 @@ export class LanguageServerExtensionActivationService disposables.push( commandManager.registerCommand(Commands.ClearAnalyisCache, this.onClearAnalysisCaches.bind(this)) ); + + this.languageServerChangeHandler = new LanguageServerChangeHandler( + this.getCurrentLanguageServerType(), + this.serviceContainer.get(IExtensions), + this.serviceContainer.get(IApplicationShell), + this.serviceContainer.get(IApplicationEnvironment), + this.serviceContainer.get(ICommandManager) + ); + disposables.push(this.languageServerChangeHandler); } public async activate(resource: Resource): Promise { @@ -127,6 +143,7 @@ export class LanguageServerExtensionActivationService this.activatedServer.server.dispose(); } } + @swallowExceptions('Send telemetry for language server current selection') public async sendTelemetryForChosenLanguageServer(languageServer: LanguageServerType): Promise { const state = this.stateFactory.createGlobalPersistentState( @@ -168,16 +185,6 @@ export class LanguageServerExtensionActivationService ); } - /** - * Checks if user is using Jedi as intellisense - * @returns `true` if user is using jedi, `false` if user is using language server - */ - public useJedi(): boolean { - const configurationService = this.serviceContainer.get(IConfigurationService); - const lstType = configurationService.getSettings(this.resource).languageServer; - return lstType === LanguageServerType.Jedi; - } - protected async onWorkspaceFoldersChanged() { //If an activated workspace folder was removed, dispose its activator const workspaceKeys = await Promise.all( @@ -198,29 +205,26 @@ export class LanguageServerExtensionActivationService return this.activate(this.resource); } + private getCurrentLanguageServerType(): LanguageServerType { + const configurationService = this.serviceContainer.get(IConfigurationService); + const serverType = configurationService.getSettings(this.resource).languageServer; + return serverType ?? LanguageServerType.Jedi; + } + private async createRefCountedServer( resource: Resource, interpreter: PythonInterpreter | undefined, key: string ): Promise { - const configurationService = this.serviceContainer.get(IConfigurationService); - let serverType = configurationService.getSettings(this.resource).languageServer; - if (!serverType) { - serverType = LanguageServerType.Jedi; - } - + let serverType = this.getCurrentLanguageServerType(); switch (serverType) { case LanguageServerType.None: sendTelemetryEvent(EventName.PYTHON_LANGUAGE_SERVER_NONE, undefined, undefined); break; case LanguageServerType.Node: - // No telemetry in development phase. + sendTelemetryEvent(EventName.PYTHON_LANGUAGE_SERVER_PYLANCE, undefined, undefined); break; case LanguageServerType.Microsoft: - if (this.useJedi()) { - serverType = LanguageServerType.Jedi; - break; - } const lsNotSupportedDiagnosticService = this.serviceContainer.get( IDiagnosticsService, LSNotSupportedDiagnosticServiceId @@ -286,7 +290,7 @@ export class LanguageServerExtensionActivationService this.output.appendLine(outputLine); } - private async onDidChangeConfiguration(event: ConfigurationChangeEvent) { + private async onDidChangeConfiguration(event: ConfigurationChangeEvent): Promise { const workspacesUris: (Uri | undefined)[] = this.workspaceService.hasWorkspaceFolders ? this.workspaceService.workspaceFolders!.map((workspace) => workspace.uri) : [undefined]; @@ -295,26 +299,12 @@ export class LanguageServerExtensionActivationService ) { return; } - const jedi = this.useJedi(); - if (this.activatedServer) { - if (this.activatedServer.jedi === jedi) { - return; - } - const configurationService = this.serviceContainer.get(IConfigurationService); - const lsType = configurationService.getSettings(this.resource).languageServer; - if (this.activatedServer.key === lsType) { - return; - } - } - - const item = await this.appShell.showInformationMessage( - 'Please reload the window switching between language engines.', - 'Reload' - ); - if (item === 'Reload') { - this.serviceContainer.get(ICommandManager).executeCommand('workbench.action.reloadWindow'); + const lsType = this.getCurrentLanguageServerType(); + if (this.activatedServer?.key !== lsType) { + await this.languageServerChangeHandler.handleLanguageServerChange(lsType); } } + private async getKey(resource: Resource, interpreter?: PythonInterpreter): Promise { const configurationService = this.serviceContainer.get(IConfigurationService); const serverType = configurationService.getSettings(this.resource).languageServer; diff --git a/src/client/activation/common/languageServerChangeHandler.ts b/src/client/activation/common/languageServerChangeHandler.ts new file mode 100644 index 000000000000..9b312a7cf7f1 --- /dev/null +++ b/src/client/activation/common/languageServerChangeHandler.ts @@ -0,0 +1,114 @@ +import { Disposable } from 'vscode'; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { IApplicationEnvironment, IApplicationShell, ICommandManager } from '../../common/application/types'; +import { PYLANCE_EXTENSION_ID } from '../../common/constants'; +import { IExtensions } from '../../common/types'; +import { createDeferred } from '../../common/utils/async'; +import { Common, LanguageService, Pylance } from '../../common/utils/localize'; +import { getPylanceExtensionUri } from '../../languageServices/proposeLanguageServerBanner'; +import { LanguageServerType } from '../types'; + +export async function promptForPylanceInstall( + appShell: IApplicationShell, + appEnv: IApplicationEnvironment +): Promise { + // If not installed, point user to Pylance at the store. + const response = await appShell.showWarningMessage( + Pylance.installPylanceMessage(), + Common.bannerLabelYes(), + Common.bannerLabelNo() + ); + + if (response === Common.bannerLabelYes()) { + appShell.openUrl(getPylanceExtensionUri(appEnv)); + } +} + +// Tracks language server type and issues appropriate reload or install prompts. +export class LanguageServerChangeHandler implements Disposable { + // For tests that need to track Pylance install completion. + private readonly pylanceInstallCompletedDeferred = createDeferred(); + private readonly disposables: Disposable[] = []; + private pylanceInstalled = false; + + constructor( + private currentLsType: LanguageServerType | undefined, + private readonly extensions: IExtensions, + private readonly appShell: IApplicationShell, + private readonly appEnv: IApplicationEnvironment, + private readonly commands: ICommandManager + ) { + this.pylanceInstalled = this.isPylanceInstalled(); + this.disposables.push( + extensions.onDidChange(async () => { + await this.extensionsChangeHandler(); + }) + ); + } + + public dispose(): void { + for (const d of this.disposables) { + d.dispose(); + } + } + + // For tests that need to track Pylance install completion. + get pylanceInstallCompleted(): Promise { + return this.pylanceInstallCompletedDeferred.promise; + } + + public async handleLanguageServerChange(lsType: LanguageServerType | undefined): Promise { + if (this.currentLsType === lsType) { + return; + } + // VS Code has to be reloaded when language server type changes. In case of Pylance + // it also has to be installed manually by the user. We avoid prompting to reload + // if target changes to Pylance when Pylance is not installed since otherwise user + // may get one reload prompt now and then another when Pylance is finally installed. + // Instead, check the installation and suppress prompt if Pylance is not there. + // Extensions change event handler will then show its own prompt. + let response: string | undefined; + if (lsType === LanguageServerType.Node && !this.isPylanceInstalled()) { + // If not installed, point user to Pylance at the store. + await promptForPylanceInstall(this.appShell, this.appEnv); + // At this point Pylance is not yet installed. Skip reload prompt + // since we are going to show it when Pylance becomes available. + } else { + response = await this.appShell.showInformationMessage( + LanguageService.reloadAfterLanguageServerChange(), + Common.reload() + ); + if (response === Common.reload()) { + this.commands.executeCommand('workbench.action.reloadWindow'); + } + } + this.currentLsType = lsType; + } + + private async extensionsChangeHandler(): Promise { + // Track Pylance extension installation state and prompt to reload when it becomes available. + const oldInstallState = this.pylanceInstalled; + this.pylanceInstalled = this.isPylanceInstalled(); + if (oldInstallState === this.pylanceInstalled) { + this.pylanceInstallCompletedDeferred.resolve(); + return; + } + + const response = await this.appShell.showWarningMessage( + Pylance.pylanceInstalledReloadPromptMessage(), + Common.bannerLabelYes(), + Common.bannerLabelNo() + ); + + this.pylanceInstallCompletedDeferred.resolve(); + if (response === Common.bannerLabelYes()) { + this.commands.executeCommand('workbench.action.reloadWindow'); + } + } + + private isPylanceInstalled(): boolean { + return this.extensions.getExtension(PYLANCE_EXTENSION_ID) ? true : false; + } +} diff --git a/src/client/activation/node/activator.ts b/src/client/activation/node/activator.ts index 8351c465072b..fc438bece25b 100644 --- a/src/client/activation/node/activator.ts +++ b/src/client/activation/node/activator.ts @@ -1,27 +1,22 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. + import { inject, injectable } from 'inversify'; import { CancellationToken, CompletionItem, ProviderResult } from 'vscode'; // tslint:disable-next-line: import-name import ProtocolCompletionItem from 'vscode-languageclient/lib/common/protocolCompletionItem'; import { CompletionResolveRequest } from 'vscode-languageclient/node'; -import { - IApplicationEnvironment, - IApplicationShell, - ICommandManager, - IWorkspaceService -} from '../../common/application/types'; +import { IApplicationEnvironment, IApplicationShell, IWorkspaceService } from '../../common/application/types'; import { PYLANCE_EXTENSION_ID } from '../../common/constants'; import { IFileSystem } from '../../common/platform/types'; import { IConfigurationService, IExtensions, Resource } from '../../common/types'; -import { createDeferred } from '../../common/utils/async'; -import { Common, Pylance } from '../../common/utils/localize'; -import { getPylanceExtensionUri } from '../../languageServices/proposeLanguageServerBanner'; +import { Pylance } from '../../common/utils/localize'; import { LanguageServerActivatorBase } from '../common/activatorBase'; +import { promptForPylanceInstall } from '../common/languageServerChangeHandler'; import { ILanguageServerManager } from '../types'; /** - * Starts the Node.js-based language server managers per workspaces (currently one for first workspace). + * Starts Pylance language server manager. * * @export * @class NodeLanguageServerActivator @@ -30,9 +25,6 @@ import { ILanguageServerManager } from '../types'; */ @injectable() export class NodeLanguageServerActivator extends LanguageServerActivatorBase { - private readonly pylanceInstallCompletedDeferred = createDeferred(); // For tests to track Pylance install completion. - private pylanceInstalled = false; - constructor( @inject(ILanguageServerManager) manager: ILanguageServerManager, @inject(IWorkspaceService) workspace: IWorkspaceService, @@ -40,54 +32,24 @@ export class NodeLanguageServerActivator extends LanguageServerActivatorBase { @inject(IConfigurationService) configurationService: IConfigurationService, @inject(IExtensions) private readonly extensions: IExtensions, @inject(IApplicationShell) private readonly appShell: IApplicationShell, - @inject(IApplicationEnvironment) private readonly appEnv: IApplicationEnvironment, - @inject(ICommandManager) private readonly commands: ICommandManager + @inject(IApplicationEnvironment) private readonly appEnv: IApplicationEnvironment ) { super(manager, workspace, fs, configurationService); } - get pylanceInstallCompleted(): Promise { - return this.pylanceInstallCompletedDeferred.promise; - } - public async ensureLanguageServerIsAvailable(resource: Resource): Promise { const settings = this.configurationService.getSettings(resource); if (settings.downloadLanguageServer === false) { // Development mode. return; } - // Check if Pylance extension is installed. - if (this.extensions.getExtension(PYLANCE_EXTENSION_ID)) { - return; + if (!this.extensions.getExtension(PYLANCE_EXTENSION_ID)) { + // Pylance is not yet installed. Throw will cause activator to use Jedi + // temporarily. Language server installation tracker will prompt for window + // reload when Pylance becomes available. + await promptForPylanceInstall(this.appShell, this.appEnv); + throw new Error(Pylance.pylanceNotInstalledMessage()); } - // Point user to Pylance at the store. - let response = await this.appShell.showErrorMessage( - Pylance.installPylanceMessage(), - Common.bannerLabelYes(), - Common.bannerLabelNo() - ); - if (response === Common.bannerLabelYes()) { - this.appShell.openUrl(getPylanceExtensionUri(this.appEnv)); - } - - this.extensions.onDidChange(async () => { - if (this.extensions.getExtension(PYLANCE_EXTENSION_ID) && !this.pylanceInstalled) { - this.pylanceInstalled = true; - response = await this.appShell.showWarningMessage( - Pylance.pylanceInstalledReloadPromptMessage(), - Common.bannerLabelYes(), - Common.bannerLabelNo() - ); - this.pylanceInstallCompletedDeferred.resolve(); - if (response === Common.bannerLabelYes()) { - this.commands.executeCommand('workbench.action.reloadWindow'); - } - } - }); - - // At this time there is no Pylance installed yet. - // throwing will cause activator to use Jedi temporarily. - throw new Error(Pylance.pylanceNotInstalledMessage()); } public resolveCompletionItem(item: CompletionItem, token: CancellationToken): ProviderResult { diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index 482cbb0227c3..0e8356651e97 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -134,6 +134,11 @@ export namespace LanguageService { 'Editor support is inactive since language server is set to None.' ); + export const reloadAfterLanguageServerChange = localize( + 'LanguageService.reloadAfterLanguageServerChange', + 'Please reload the window switching between language servers.' + ); + export const lsFailedToStart = localize( 'LanguageService.lsFailedToStart', 'We encountered an issue starting the language server. Reverting to Jedi language engine. Check the Python output panel for details.' diff --git a/src/client/telemetry/constants.ts b/src/client/telemetry/constants.ts index 42fb49092b33..c3160073c632 100644 --- a/src/client/telemetry/constants.ts +++ b/src/client/telemetry/constants.ts @@ -77,6 +77,7 @@ export enum EventName { PYTHON_LANGUAGE_SERVER_CURRENT_SELECTION = 'PYTHON_LANGUAGE_SERVER_CURRENT_SELECTION', PYTHON_LANGUAGE_SERVER_NONE = 'PYTHON_LANGUAGE_SERVER.NONE', + PYTHON_LANGUAGE_SERVER_PYLANCE = 'PYTHON_LANGUAGE_SERVER.PYLANCE', PYTHON_LANGUAGE_SERVER_LIST_BLOB_STORE_PACKAGES = 'PYTHON_LANGUAGE_SERVER.LIST_BLOB_PACKAGES', PYTHON_LANGUAGE_SERVER_EXTRACTED = 'PYTHON_LANGUAGE_SERVER.EXTRACTED', PYTHON_LANGUAGE_SERVER_DOWNLOADED = 'PYTHON_LANGUAGE_SERVER.DOWNLOADED', diff --git a/src/client/telemetry/index.ts b/src/client/telemetry/index.ts index cec850d7defb..29b571cbbc48 100644 --- a/src/client/telemetry/index.ts +++ b/src/client/telemetry/index.ts @@ -1290,6 +1290,10 @@ export interface IEventNamePropertyMapping { * Telemetry event sent when user specified None to the language server. */ [EventName.PYTHON_LANGUAGE_SERVER_NONE]: never | undefined; + /** + * Telemetry event sent when user specified Pylance to the language server. + */ + [EventName.PYTHON_LANGUAGE_SERVER_PYLANCE]: never | undefined; /** * Telemetry sent from language server (details of telemetry sent can be provided by LS team) */ diff --git a/src/test/activation/activationService.unit.test.ts b/src/test/activation/activationService.unit.test.ts index fea8949e050e..6bc984879488 100644 --- a/src/test/activation/activationService.unit.test.ts +++ b/src/test/activation/activationService.unit.test.ts @@ -21,6 +21,7 @@ import { IConfigurationService, IDisposable, IDisposableRegistry, + IExtensions, IOutputChannel, IPersistentState, IPersistentStateFactory, @@ -65,6 +66,7 @@ suite('Language Server Activation - ActivationService', () => { const configService = TypeMoq.Mock.ofType(); pythonSettings = TypeMoq.Mock.ofType(); const langFolderServiceMock = TypeMoq.Mock.ofType(); + const extensionsMock = TypeMoq.Mock.ofType(); const folderVer: FolderVersionPair = { path: '', version: new SemVer('1.2.3') @@ -125,6 +127,9 @@ suite('Language Server Activation - ActivationService', () => { serviceContainer .setup((c) => c.get(TypeMoq.It.isValue(ILanguageServerFolderService))) .returns(() => langFolderServiceMock.object); + serviceContainer + .setup((c) => c.get(TypeMoq.It.isValue(IExtensions))) + .returns(() => extensionsMock.object); serviceContainer .setup((s) => s.get( @@ -191,7 +196,7 @@ suite('Language Server Activation - ActivationService', () => { .returns(() => TypeMoq.Mock.ofType().object) .verifiable(TypeMoq.Times.once()); - pythonSettings.setup((p) => p.languageServer).returns(() => LanguageServerType.Microsoft); + pythonSettings.setup((p) => p.languageServer).returns(() => languageServerType); const activator = TypeMoq.Mock.ofType(); const activationService = new LanguageServerExtensionActivationService( serviceContainer.object, @@ -672,6 +677,7 @@ suite('Language Server Activation - ActivationService', () => { const e = new EventEmitter(); interpreterService.setup((i) => i.onDidChangeInterpreter).returns(() => e.event); const langFolderServiceMock = TypeMoq.Mock.ofType(); + const extensionsMock = TypeMoq.Mock.ofType(); const folderVer: FolderVersionPair = { path: '', version: new SemVer('1.2.3') @@ -720,6 +726,7 @@ suite('Language Server Activation - ActivationService', () => { serviceContainer .setup((c) => c.get(TypeMoq.It.isValue(ILanguageServerFolderService))) .returns(() => langFolderServiceMock.object); + serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(IExtensions))).returns(() => extensionsMock.object); serviceContainer .setup((s) => s.get( @@ -837,6 +844,7 @@ suite('Language Server Activation - ActivationService', () => { const e = new EventEmitter(); interpreterService.setup((i) => i.onDidChangeInterpreter).returns(() => e.event); const langFolderServiceMock = TypeMoq.Mock.ofType(); + const extensionsMock = TypeMoq.Mock.ofType(); const folderVer: FolderVersionPair = { path: '', version: new SemVer('1.2.3') @@ -885,6 +893,7 @@ suite('Language Server Activation - ActivationService', () => { serviceContainer .setup((c) => c.get(TypeMoq.It.isValue(ILanguageServerFolderService))) .returns(() => langFolderServiceMock.object); + serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(IExtensions))).returns(() => extensionsMock.object); serviceContainer .setup((s) => s.get( diff --git a/src/test/activation/node/activator.unit.test.ts b/src/test/activation/node/activator.unit.test.ts index 4476e3a1348f..796ab8240d8b 100644 --- a/src/test/activation/node/activator.unit.test.ts +++ b/src/test/activation/node/activator.unit.test.ts @@ -4,7 +4,7 @@ 'use strict'; import { expect } from 'chai'; -import { anyString, anything, instance, mock, verify, when } from 'ts-mockito'; +import { anything, instance, mock, verify, when } from 'ts-mockito'; import { EventEmitter, Extension, Uri } from 'vscode'; import { NodeLanguageServerActivator } from '../../../client/activation/node/activator'; import { NodeLanguageServerManager } from '../../../client/activation/node/manager'; @@ -48,9 +48,10 @@ suite('Pylance Language Server - Activator', () => { configuration = mock(ConfigurationService); settings = mock(PythonSettings); extensions = mock(); + commands = mock(); appShell = mock(); appEnv = mock(); - commands = mock(); + when(appEnv.uriScheme).thenReturn('scheme'); // tslint:disable-next-line: no-any pylanceExtension = mock>(); @@ -67,8 +68,7 @@ suite('Pylance Language Server - Activator', () => { instance(configuration), instance(extensions), instance(appShell), - instance(appEnv), - instance(commands) + instance(appEnv) ); }); teardown(() => { @@ -102,127 +102,49 @@ suite('Pylance Language Server - Activator', () => { verify(extensions.getExtension(PYLANCE_EXTENSION_ID)).never(); }); - test('If Pylance is not installed, user should get prompt to install it', async () => { - expect(activator.start(undefined)) - .to.eventually.be.rejectedWith(Pylance.pylanceNotInstalledMessage()) - .and.be.an.instanceOf(Error); - verify( - appShell.showErrorMessage(Pylance.installPylanceMessage(), Common.bannerLabelYes(), Common.bannerLabelNo()) - ).once(); - }); - - test('If Pylance is installed, user should not get prompt to install it', async () => { - when(extensions.getExtension(PYLANCE_EXTENSION_ID)).thenReturn(instance(pylanceExtension)); - await activator.start(undefined); - verify( - appShell.showErrorMessage(Pylance.installPylanceMessage(), Common.bannerLabelYes(), Common.bannerLabelNo()) - ).never(); - }); - - test('If Pylance is not installed and user responded Yes, Pylance install page should be opened', async () => { - when( - appShell.showErrorMessage(Pylance.installPylanceMessage(), Common.bannerLabelYes(), Common.bannerLabelNo()) - ).thenReturn(Promise.resolve(Common.bannerLabelYes())); - - try { - await activator.start(undefined); - // tslint:disable-next-line: no-empty - } catch {} - verify(appShell.openUrl(`scheme:extension/${PYLANCE_EXTENSION_ID}`)).once(); - verify(manager.connect()).never(); - }); - - test('If Pylance is not installed and user responded Yes, Pylance activator should throw', async () => { - when( - appShell.showErrorMessage(Pylance.installPylanceMessage(), Common.bannerLabelYes(), Common.bannerLabelNo()) - ).thenReturn(Promise.resolve(Common.bannerLabelYes())); - - expect(activator.start(undefined)) - .to.eventually.be.rejectedWith(Pylance.pylanceNotInstalledMessage()) - .and.be.an.instanceOf(Error); - - extensionsChangedEvent.fire(); - verify(manager.connect()).never(); - }); - - test('If Pylance is not installed and user responded Yes, reload should be called if user agreed to it', async () => { - when( - appShell.showErrorMessage(Pylance.installPylanceMessage(), Common.bannerLabelYes(), Common.bannerLabelNo()) - ).thenReturn(Promise.resolve(Common.bannerLabelYes())); - - try { - await activator.start(undefined); - // tslint:disable-next-line: no-empty - } catch {} - - when(extensions.getExtension(PYLANCE_EXTENSION_ID)).thenReturn(pylanceExtension); + test('When Pylance is not installed activator should show install prompt ', async () => { when( appShell.showWarningMessage( - Pylance.pylanceInstalledReloadPromptMessage(), + Pylance.installPylanceMessage(), Common.bannerLabelYes(), Common.bannerLabelNo() ) - ).thenReturn(Promise.resolve(Common.bannerLabelYes())); - - extensionsChangedEvent.fire(); - await activator.pylanceInstallCompleted; - verify(commands.executeCommand('workbench.action.reloadWindow')).once(); - }); - - test('If Pylance is not installed and user responded Yes, reload should not be called if user refused it', async () => { - when( - appShell.showErrorMessage(Pylance.installPylanceMessage(), Common.bannerLabelYes(), Common.bannerLabelNo()) - ).thenReturn(Promise.resolve(Common.bannerLabelYes())); + ).thenReturn(Promise.resolve(Common.bannerLabelNo())); try { await activator.start(undefined); // tslint:disable-next-line: no-empty } catch {} - - when(extensions.getExtension(PYLANCE_EXTENSION_ID)).thenReturn(pylanceExtension); - when( + verify( appShell.showWarningMessage( - Pylance.pylanceInstalledReloadPromptMessage(), + Pylance.installPylanceMessage(), Common.bannerLabelYes(), Common.bannerLabelNo() ) - ).thenReturn(Promise.resolve(Common.bannerLabelNo())); - - extensionsChangedEvent.fire(); - await activator.pylanceInstallCompleted; - verify(commands.executeCommand('workbench.action.reloadWindow')).never(); + ).once(); + verify(appShell.openUrl(`scheme:extension/${PYLANCE_EXTENSION_ID}`)).never(); }); - test('If Pylance is not installed and user responded No, Pylance install page should not be opened', async () => { + test('When Pylance is not installed activator should open Pylance install page if users clicks Yes', async () => { when( - appShell.showErrorMessage(Pylance.installPylanceMessage(), Common.bannerLabelYes(), Common.bannerLabelNo()) - ).thenReturn(Promise.resolve(Common.bannerLabelNo())); + appShell.showWarningMessage( + Pylance.installPylanceMessage(), + Common.bannerLabelYes(), + Common.bannerLabelNo() + ) + ).thenReturn(Promise.resolve(Common.bannerLabelYes())); try { await activator.start(undefined); // tslint:disable-next-line: no-empty } catch {} - verify(appShell.openUrl(anyString())).never(); - verify(manager.connect()).never(); + verify(appShell.openUrl(`scheme:extension/${PYLANCE_EXTENSION_ID}`)).once(); }); - test('If Pylance is not installed and user responded No, Pylance activator should throw', async () => { - when( - appShell.showErrorMessage(Pylance.installPylanceMessage(), Common.bannerLabelYes(), Common.bannerLabelNo()) - ).thenReturn(Promise.resolve(Common.bannerLabelNo())); - + test('Activator should throw if Pylance is not installed', async () => { expect(activator.start(undefined)) .to.eventually.be.rejectedWith(Pylance.pylanceNotInstalledMessage()) .and.be.an.instanceOf(Error); - verify(manager.connect()).never(); - }); - - test('Server should be disconnected but be started', async () => { - when(extensions.getExtension(PYLANCE_EXTENSION_ID)).thenReturn(instance(pylanceExtension)); - await activator.start(undefined); - - verify(manager.start(undefined, undefined)).once(); - verify(manager.connect()).never(); }); test('Manager must be started with resource for first available workspace', async () => { diff --git a/src/test/activation/node/languageServerChangeHandler.unit.test.ts b/src/test/activation/node/languageServerChangeHandler.unit.test.ts new file mode 100644 index 000000000000..94b7ef180311 --- /dev/null +++ b/src/test/activation/node/languageServerChangeHandler.unit.test.ts @@ -0,0 +1,189 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { anyString, instance, mock, verify, when } from 'ts-mockito'; +import { EventEmitter, Extension } from 'vscode'; +import { LanguageServerChangeHandler } from '../../../client/activation/common/languageServerChangeHandler'; +import { LanguageServerType } from '../../../client/activation/types'; +import { IApplicationEnvironment, IApplicationShell, ICommandManager } from '../../../client/common/application/types'; +import { PYLANCE_EXTENSION_ID } from '../../../client/common/constants'; +import { IExtensions } from '../../../client/common/types'; +import { Common, LanguageService, Pylance } from '../../../client/common/utils/localize'; + +suite('Language Server - Change Handler', () => { + let extensions: IExtensions; + let appShell: IApplicationShell; + let appEnv: IApplicationEnvironment; + let commands: ICommandManager; + let extensionsChangedEvent: EventEmitter; + let handler: LanguageServerChangeHandler; + + // tslint:disable-next-line: no-any + let pylanceExtension: Extension; + setup(() => { + extensions = mock(); + appShell = mock(); + appEnv = mock(); + commands = mock(); + + // tslint:disable-next-line: no-any + pylanceExtension = mock>(); + when(appEnv.uriScheme).thenReturn('scheme'); + + extensionsChangedEvent = new EventEmitter(); + when(extensions.onDidChange).thenReturn(extensionsChangedEvent.event); + }); + teardown(() => { + extensionsChangedEvent.dispose(); + handler?.dispose(); + }); + + [undefined, LanguageServerType.None, LanguageServerType.Microsoft, LanguageServerType.Node].forEach(async (t) => { + test(`Handler should do nothing if language server is ${t} and did not change`, async () => { + handler = makeHandler(t); + await handler.handleLanguageServerChange(t); + + verify(extensions.getExtension(anyString())).once(); + verify(appShell.openUrl(anyString())).never(); + verify(appShell.showInformationMessage(anyString(), anyString())).never(); + verify(appShell.showWarningMessage(anyString(), anyString())).never(); + verify(commands.executeCommand(anyString())).never(); + }); + }); + + [LanguageServerType.None, LanguageServerType.Microsoft, LanguageServerType.Node].forEach(async (t) => { + test(`Handler should prompt for reload when language server type changes to ${t}, Pylance is installed ans user clicks Reload`, async () => { + when(extensions.getExtension(PYLANCE_EXTENSION_ID)).thenReturn(instance(pylanceExtension)); + when( + appShell.showInformationMessage(LanguageService.reloadAfterLanguageServerChange(), Common.reload()) + ).thenReturn(Promise.resolve(Common.reload())); + + handler = makeHandler(undefined); + await handler.handleLanguageServerChange(t); + + verify( + appShell.showInformationMessage(LanguageService.reloadAfterLanguageServerChange(), Common.reload()) + ).once(); + verify(commands.executeCommand('workbench.action.reloadWindow')).once(); + }); + }); + + [LanguageServerType.None, LanguageServerType.Microsoft, LanguageServerType.Node].forEach(async (t) => { + test(`Handler should not prompt for reload when language server type changes to ${t}, Pylance is installed ans user does not clicks Reload`, async () => { + when(extensions.getExtension(PYLANCE_EXTENSION_ID)).thenReturn(instance(pylanceExtension)); + when( + appShell.showInformationMessage(LanguageService.reloadAfterLanguageServerChange(), Common.reload()) + ).thenReturn(Promise.resolve(undefined)); + + handler = makeHandler(undefined); + await handler.handleLanguageServerChange(t); + + verify( + appShell.showInformationMessage(LanguageService.reloadAfterLanguageServerChange(), Common.reload()) + ).once(); + verify(commands.executeCommand('workbench.action.reloadWindow')).never(); + }); + }); + + test('Handler should prompt for install when language server changes to Pylance and Pylance is not installed', async () => { + when( + appShell.showWarningMessage( + Pylance.installPylanceMessage(), + Common.bannerLabelYes(), + Common.bannerLabelNo() + ) + ).thenReturn(Promise.resolve(undefined)); + + handler = makeHandler(undefined); + await handler.handleLanguageServerChange(LanguageServerType.Node); + + verify( + appShell.showInformationMessage(LanguageService.reloadAfterLanguageServerChange(), Common.reload()) + ).never(); + verify( + appShell.showWarningMessage( + Pylance.installPylanceMessage(), + Common.bannerLabelYes(), + Common.bannerLabelNo() + ) + ).once(); + }); + + test('Handler should open Pylance store page when language server changes to Pylance, Pylance is not installed and user clicks Yes', async () => { + when( + appShell.showWarningMessage( + Pylance.installPylanceMessage(), + Common.bannerLabelYes(), + Common.bannerLabelNo() + ) + ).thenReturn(Promise.resolve(Common.bannerLabelYes())); + + handler = makeHandler(undefined); + await handler.handleLanguageServerChange(LanguageServerType.Node); + + verify(appShell.openUrl(`scheme:extension/${PYLANCE_EXTENSION_ID}`)).once(); + verify(commands.executeCommand('workbench.action.reloadWindow')).never(); + }); + + test('Handler should not open Pylance store page when language server changes to Pylance, Pylance is not installed and user clicks No', async () => { + when( + appShell.showWarningMessage( + Pylance.installPylanceMessage(), + Common.bannerLabelYes(), + Common.bannerLabelNo() + ) + ).thenReturn(Promise.resolve(Common.bannerLabelNo())); + + handler = makeHandler(undefined); + await handler.handleLanguageServerChange(LanguageServerType.Node); + + verify(appShell.openUrl(`scheme:extension/${PYLANCE_EXTENSION_ID}`)).never(); + verify(commands.executeCommand('workbench.action.reloadWindow')).never(); + }); + + test('If Pylance was not installed and now it is, reload should be called if user agreed to it', async () => { + when( + appShell.showWarningMessage( + Pylance.pylanceInstalledReloadPromptMessage(), + Common.bannerLabelYes(), + Common.bannerLabelNo() + ) + ).thenReturn(Promise.resolve(Common.bannerLabelYes())); + handler = makeHandler(LanguageServerType.Node); + + when(extensions.getExtension(PYLANCE_EXTENSION_ID)).thenReturn(pylanceExtension); + extensionsChangedEvent.fire(); + + await handler.pylanceInstallCompleted; + verify(commands.executeCommand('workbench.action.reloadWindow')).once(); + }); + + test('If Pylance was not installed and now it is, reload should not be called if user refused it', async () => { + when( + appShell.showWarningMessage( + Pylance.pylanceInstalledReloadPromptMessage(), + Common.bannerLabelYes(), + Common.bannerLabelNo() + ) + ).thenReturn(Promise.resolve(Common.bannerLabelNo())); + handler = makeHandler(LanguageServerType.Node); + + when(extensions.getExtension(PYLANCE_EXTENSION_ID)).thenReturn(pylanceExtension); + extensionsChangedEvent.fire(); + + await handler.pylanceInstallCompleted; + verify(commands.executeCommand('workbench.action.reloadWindow')).never(); + }); + + function makeHandler(initialLSType: LanguageServerType | undefined): LanguageServerChangeHandler { + return new LanguageServerChangeHandler( + initialLSType, + instance(extensions), + instance(appShell), + instance(appEnv), + instance(commands) + ); + } +}); From c26a54f772b297bc9af26676c598b2aca188a470 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Tue, 28 Jul 2020 13:51:14 -0700 Subject: [PATCH 21/27] Remove unused --- src/test/activation/node/activator.unit.test.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/test/activation/node/activator.unit.test.ts b/src/test/activation/node/activator.unit.test.ts index 796ab8240d8b..3b87588774eb 100644 --- a/src/test/activation/node/activator.unit.test.ts +++ b/src/test/activation/node/activator.unit.test.ts @@ -12,7 +12,6 @@ import { ILanguageServerManager } from '../../../client/activation/types'; import { IApplicationEnvironment, IApplicationShell, - ICommandManager, IWorkspaceService } from '../../../client/common/application/types'; import { WorkspaceService } from '../../../client/common/application/workspace'; @@ -36,7 +35,6 @@ suite('Pylance Language Server - Activator', () => { let extensions: IExtensions; let appShell: IApplicationShell; let appEnv: IApplicationEnvironment; - let commands: ICommandManager; let extensionsChangedEvent: EventEmitter; // tslint:disable-next-line: no-any @@ -48,7 +46,6 @@ suite('Pylance Language Server - Activator', () => { configuration = mock(ConfigurationService); settings = mock(PythonSettings); extensions = mock(); - commands = mock(); appShell = mock(); appEnv = mock(); when(appEnv.uriScheme).thenReturn('scheme'); From adc3f3cbbae59448523283141fa902b6ab0ad3cd Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Tue, 28 Jul 2020 13:58:53 -0700 Subject: [PATCH 22/27] Remove duplicate telemetry --- src/client/activation/activationService.ts | 6 ------ .../activation/common/languageServerChangeHandler.ts | 2 +- src/client/telemetry/constants.ts | 2 -- src/client/telemetry/index.ts | 8 -------- 4 files changed, 1 insertion(+), 17 deletions(-) diff --git a/src/client/activation/activationService.ts b/src/client/activation/activationService.ts index 698ca2d265bf..39c52208db7f 100644 --- a/src/client/activation/activationService.ts +++ b/src/client/activation/activationService.ts @@ -218,12 +218,6 @@ export class LanguageServerExtensionActivationService ): Promise { let serverType = this.getCurrentLanguageServerType(); switch (serverType) { - case LanguageServerType.None: - sendTelemetryEvent(EventName.PYTHON_LANGUAGE_SERVER_NONE, undefined, undefined); - break; - case LanguageServerType.Node: - sendTelemetryEvent(EventName.PYTHON_LANGUAGE_SERVER_PYLANCE, undefined, undefined); - break; case LanguageServerType.Microsoft: const lsNotSupportedDiagnosticService = this.serviceContainer.get( IDiagnosticsService, diff --git a/src/client/activation/common/languageServerChangeHandler.ts b/src/client/activation/common/languageServerChangeHandler.ts index 9b312a7cf7f1..abbd422c68c6 100644 --- a/src/client/activation/common/languageServerChangeHandler.ts +++ b/src/client/activation/common/languageServerChangeHandler.ts @@ -109,6 +109,6 @@ export class LanguageServerChangeHandler implements Disposable { } private isPylanceInstalled(): boolean { - return this.extensions.getExtension(PYLANCE_EXTENSION_ID) ? true : false; + return !!this.extensions.getExtension(PYLANCE_EXTENSION_ID); } } diff --git a/src/client/telemetry/constants.ts b/src/client/telemetry/constants.ts index c3160073c632..f0f38e6ac69a 100644 --- a/src/client/telemetry/constants.ts +++ b/src/client/telemetry/constants.ts @@ -76,8 +76,6 @@ export enum EventName { EXTENSION_SURVEY_PROMPT = 'EXTENSION_SURVEY_PROMPT', PYTHON_LANGUAGE_SERVER_CURRENT_SELECTION = 'PYTHON_LANGUAGE_SERVER_CURRENT_SELECTION', - PYTHON_LANGUAGE_SERVER_NONE = 'PYTHON_LANGUAGE_SERVER.NONE', - PYTHON_LANGUAGE_SERVER_PYLANCE = 'PYTHON_LANGUAGE_SERVER.PYLANCE', PYTHON_LANGUAGE_SERVER_LIST_BLOB_STORE_PACKAGES = 'PYTHON_LANGUAGE_SERVER.LIST_BLOB_PACKAGES', PYTHON_LANGUAGE_SERVER_EXTRACTED = 'PYTHON_LANGUAGE_SERVER.EXTRACTED', PYTHON_LANGUAGE_SERVER_DOWNLOADED = 'PYTHON_LANGUAGE_SERVER.DOWNLOADED', diff --git a/src/client/telemetry/index.ts b/src/client/telemetry/index.ts index 29b571cbbc48..e0a855190983 100644 --- a/src/client/telemetry/index.ts +++ b/src/client/telemetry/index.ts @@ -1286,14 +1286,6 @@ export interface IEventNamePropertyMapping { [EventName.PYTHON_LANGUAGE_SERVER_STARTUP]: { lsVersion?: string; }; - /** - * Telemetry event sent when user specified None to the language server. - */ - [EventName.PYTHON_LANGUAGE_SERVER_NONE]: never | undefined; - /** - * Telemetry event sent when user specified Pylance to the language server. - */ - [EventName.PYTHON_LANGUAGE_SERVER_PYLANCE]: never | undefined; /** * Telemetry sent from language server (details of telemetry sent can be provided by LS team) */ From 488b02c0ec50978d880d6e21b3220edd931c0d68 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Tue, 28 Jul 2020 13:59:44 -0700 Subject: [PATCH 23/27] formatting --- src/client/activation/common/languageServerChangeHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/activation/common/languageServerChangeHandler.ts b/src/client/activation/common/languageServerChangeHandler.ts index abbd422c68c6..980e21f571ca 100644 --- a/src/client/activation/common/languageServerChangeHandler.ts +++ b/src/client/activation/common/languageServerChangeHandler.ts @@ -1,7 +1,7 @@ -import { Disposable } from 'vscode'; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import { Disposable } from 'vscode'; import { IApplicationEnvironment, IApplicationShell, ICommandManager } from '../../common/application/types'; import { PYLANCE_EXTENSION_ID } from '../../common/constants'; import { IExtensions } from '../../common/types'; From 88e7a2f3acc2f7492b06328c45292e7d7f5b0041 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Tue, 28 Jul 2020 14:35:17 -0700 Subject: [PATCH 24/27] Remove switch --- src/client/activation/activationService.ts | 29 +++++++++------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/client/activation/activationService.ts b/src/client/activation/activationService.ts index 39c52208db7f..31228ecca017 100644 --- a/src/client/activation/activationService.ts +++ b/src/client/activation/activationService.ts @@ -217,24 +217,19 @@ export class LanguageServerExtensionActivationService key: string ): Promise { let serverType = this.getCurrentLanguageServerType(); - switch (serverType) { - case LanguageServerType.Microsoft: - const lsNotSupportedDiagnosticService = this.serviceContainer.get( - IDiagnosticsService, - LSNotSupportedDiagnosticServiceId - ); - const diagnostic = await lsNotSupportedDiagnosticService.diagnose(undefined); - lsNotSupportedDiagnosticService.handle(diagnostic).ignoreErrors(); - if (diagnostic.length) { - sendTelemetryEvent(EventName.PYTHON_LANGUAGE_SERVER_PLATFORM_SUPPORTED, undefined, { - supported: false - }); - serverType = LanguageServerType.Jedi; - } - break; - default: + if (serverType === LanguageServerType.Microsoft) { + const lsNotSupportedDiagnosticService = this.serviceContainer.get( + IDiagnosticsService, + LSNotSupportedDiagnosticServiceId + ); + const diagnostic = await lsNotSupportedDiagnosticService.diagnose(undefined); + lsNotSupportedDiagnosticService.handle(diagnostic).ignoreErrors(); + if (diagnostic.length) { + sendTelemetryEvent(EventName.PYTHON_LANGUAGE_SERVER_PLATFORM_SUPPORTED, undefined, { + supported: false + }); serverType = LanguageServerType.Jedi; - break; + } } this.sendTelemetryForChosenLanguageServer(serverType).ignoreErrors(); From 32f7cc38bb270721a245fb7cde820ff4ac722fc4 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Tue, 28 Jul 2020 14:58:04 -0700 Subject: [PATCH 25/27] Safer dispose --- src/client/activation/common/languageServerChangeHandler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/activation/common/languageServerChangeHandler.ts b/src/client/activation/common/languageServerChangeHandler.ts index 980e21f571ca..c154ba83b1fb 100644 --- a/src/client/activation/common/languageServerChangeHandler.ts +++ b/src/client/activation/common/languageServerChangeHandler.ts @@ -49,8 +49,8 @@ export class LanguageServerChangeHandler implements Disposable { } public dispose(): void { - for (const d of this.disposables) { - d.dispose(); + while (this.disposables.length) { + this.disposables.pop()?.dispose(); } } From af97a75049299680731038fb6872c03d99bc53ee Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Wed, 29 Jul 2020 15:54:45 -0700 Subject: [PATCH 26/27] Remove unnecessary ?? --- src/client/activation/activationService.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/client/activation/activationService.ts b/src/client/activation/activationService.ts index 31228ecca017..1633cf4b96e0 100644 --- a/src/client/activation/activationService.ts +++ b/src/client/activation/activationService.ts @@ -207,8 +207,7 @@ export class LanguageServerExtensionActivationService private getCurrentLanguageServerType(): LanguageServerType { const configurationService = this.serviceContainer.get(IConfigurationService); - const serverType = configurationService.getSettings(this.resource).languageServer; - return serverType ?? LanguageServerType.Jedi; + return configurationService.getSettings(this.resource).languageServer; } private async createRefCountedServer( From 9e05807e34931edd6cdaceac0df96689e1aa1cb2 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Wed, 29 Jul 2020 16:16:11 -0700 Subject: [PATCH 27/27] Change order --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8af01765162f..04cdec81a26c 100644 --- a/package.json +++ b/package.json @@ -2319,8 +2319,8 @@ "type": "string", "enum": [ "Jedi", - "Microsoft", "Pylance", + "Microsoft", "None" ], "default": "Jedi",