diff --git a/.config/1espt/PipelineAutobaseliningConfig.yml b/.config/1espt/PipelineAutobaseliningConfig.yml index eec0d93737..3b670042d9 100644 --- a/.config/1espt/PipelineAutobaseliningConfig.yml +++ b/.config/1espt/PipelineAutobaseliningConfig.yml @@ -50,3 +50,21 @@ pipelines: lastModifiedDate: 2024-03-27 armory: lastModifiedDate: 2024-03-27 + 1424: + retail: + source: + credscan: + lastModifiedDate: 2025-02-05 + eslint: + lastModifiedDate: 2025-02-05 + psscriptanalyzer: + lastModifiedDate: 2025-02-05 + armory: + lastModifiedDate: 2025-02-05 + binary: + credscan: + lastModifiedDate: 2025-02-05 + binskim: + lastModifiedDate: 2025-02-05 + spotbugs: + lastModifiedDate: 2025-02-05 diff --git a/.config/guardian/.gdnbaselines b/.config/guardian/.gdnbaselines index 631aaa846f..9d323d1019 100644 --- a/.config/guardian/.gdnbaselines +++ b/.config/guardian/.gdnbaselines @@ -14,10 +14,16 @@ "26445e3e484940d2d58c2ffc32ab3895fca4b1589d66e2f2dee2fa01f2c479fb": { "signature": "26445e3e484940d2d58c2ffc32ab3895fca4b1589d66e2f2dee2fa01f2c479fb", "alternativeSignatures": [], + "target": "test/omnisharp/omnisharpUnitTests/testAssets/private.pem", + "line": 1, "memberOf": [ "default" ], - "createdDate": "2024-09-09 19:35:36Z" + "tool": "credscan", + "ruleId": "CSCAN-GENERAL0020", + "createdDate": "2025-02-05 00:31:10Z", + "expirationDate": "2025-07-25 00:50:30Z", + "justification": "This error is baselined with an expiration date of 180 days from 2025-02-05 00:50:30Z" } } -} +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 61e73cc16d..eb698f23a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,28 @@ - Diagnostics related feature requests and improvements [#5951](https://github.com/dotnet/vscode-csharp/issues/5951) - Debug from .csproj and .sln [#5876](https://github.com/dotnet/vscode-csharp/issues/5876) +# 2.65.x +* Update Roslyn LSP to run on .NET 9 (PR: [#7946](https://github.com/dotnet/vscode-csharp/pull/7946)) +* Update Roslyn to 4.14.0-2.25106.12 (PR: [#7969](https://github.com/dotnet/vscode-csharp/pull/7969)) + * Maintain whitespace when converting to switch expression (PR: [#77083](https://github.com/dotnet/roslyn/pull/77083)) + * Fix extra whitespace insertion for completion text edits (PR: [#77071](https://github.com/dotnet/roslyn/pull/77071)) + * Realize less of the syntax tree during AbstractSemanticModelReuseLanguageService.GetPreviousBodyNode (PR: [#77032](https://github.com/dotnet/roslyn/pull/77032)) + * Fix issue loading project with relative path globs (PR: [#76961](https://github.com/dotnet/roslyn/pull/76961)) + * Fix: Ensure DOTNET_ROOT is reset user defined value during test execution (PR: [#76819](https://github.com/dotnet/roslyn/pull/76819)) + * Update Roslyn LSP server to target .NET 9 (PR: [#76938](https://github.com/dotnet/roslyn/pull/76938)) + * Update ICSharpCode.Decompiler to 8.2.0.7535 (PR: [#71837](https://github.com/dotnet/roslyn/pull/71837)) + * Reduce CPU costs under AnalyzerExecutor.ExecuteSyntaxNodeActions (PR: [#76894](https://github.com/dotnet/roslyn/pull/76894)) +* Add code snippets for C# expression-bodied properties (PR: [#5683](https://github.com/dotnet/vscode-csharp/pull/5683)) +* Sync whitespace options even when detectIndentation is on (PR: [#7965](https://github.com/dotnet/vscode-csharp/pull/7965)) +* Bump Razor (PR: [#7940](https://github.com/dotnet/vscode-csharp/pull/7940)) +Bump xamltools to 17.14.35807.11(PR: [#7976]( https://github.com/dotnet/vscode-csharp/pull/7976)) + # 2.64.x +* Bump xamlTools to 17.14.35730.156 (PR: [#7932](https://github.com/dotnet/vscode-csharp/pull/7941)) # 2.63.x * Bump xamlTools to 17.14.35723.260 (PR: [#7932](https://github.com/dotnet/vscode-csharp/pull/7941)) + * CSS Hot Reload for MAUI Blazor Hybrid is now in preview. It's enabled when C# Hot Reload is enabled. * Update Roslyn to 4.14.0-1.25074.7 (PR: [#7942](https://github.com/dotnet/vscode-csharp/pull/7942)) * Enable extract refactorings in LSP (PR: [#76718](https://github.com/dotnet/roslyn/pull/76718)) * Speed up 'fix all' for 'use auto prop' by running in parallel (PR: [#76905](https://github.com/dotnet/roslyn/pull/76905)) @@ -58,12 +76,19 @@ * Update 'use nameof instead of typeof' to support generic types (PR: [#76780](https://github.com/dotnet/roslyn/pull/76780)) * Add feature to convert from an explicitly typed lambda to an implicitly typed one. (PR: [#76770](https://github.com/dotnet/roslyn/pull/76770)) * Support modifiers with simple lambda parameters. (PR: [#75400](https://github.com/dotnet/roslyn/pull/75400)) -* Update Razor to 9.0.0-preview.25064.4 (PR: [#7927](https://github.com/dotnet/vscode-csharp/pull/7927)) +* Update Razor to 9.0.0-preview.25073.1 (PR: [#7940](https://github.com/dotnet/vscode-csharp/pull/7940)) * Wire up the UseRoslynTokenizer feature properly (#11386) (PR: [#11386](https://github.com/dotnet/razor/pull/11386)) * New Razor document formatting engine (#11364) (PR: [#11364](https://github.com/dotnet/razor/pull/11364)) * Fix a couple of exceptions encountered when formatting documents with preprocessor directives (#11373) (PR: [#11373](https://github.com/dotnet/razor/pull/11373)) * Allow RazorProjectEngine.Process to be cancelled (#11334) (PR: [#11334](https://github.com/dotnet/razor/pull/11334)) * Further refactoring of Razor tooling project system (#11320) (PR: [#11320](https://github.com/dotnet/razor/pull/11320)) + * Don't create overlapping changes when doing additional formatting (#11413) (PR: [#11413](https://github.com/dotnet/razor/pull/11413)) + * Synchronize razor compiler assembly loading (#11394) (PR: [#11394](https://github.com/dotnet/razor/pull/11394)) + * Allow generate method to handle delegates (#11402) (PR: [#11402](https://github.com/dotnet/razor/pull/11402)) + * Fix bad completion commit in vs code (#11398) (PR: [#11398](https://github.com/dotnet/razor/pull/11398)) + * SourceTexts for Everyone! (#11404) (PR: [#11404](https://github.com/dotnet/razor/pull/11404)) + * Handful of performance fixes (#11399) (PR: [#11399](https://github.com/dotnet/razor/pull/11399)) + * Use the overload that takes an immutable array in serailization (#11393) (PR: [#11393](https://github.com/dotnet/razor/pull/11393)) # 2.62.x * Update Roslyn to 4.14.0-1.25060.2 (PR: [#7916](https://github.com/dotnet/vscode-csharp/pull/7916)) diff --git a/azure-pipelines-official.yml b/azure-pipelines-official.yml index d4ed8f2456..bea03e0afe 100644 --- a/azure-pipelines-official.yml +++ b/azure-pipelines-official.yml @@ -28,7 +28,7 @@ parameters: default: auto variables: - defaultDotnetVersion: '8.0.403' +- template: /azure-pipelines/dotnet-variables.yml@self resources: repositories: diff --git a/azure-pipelines.yml b/azure-pipelines.yml index aa44840e91..6dc7dee871 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -29,8 +29,7 @@ schedules: - main variables: -- name: defaultDotnetVersion - value: '8.0.403' +- template: /azure-pipelines/dotnet-variables.yml@self - name: testVSCodeVersion ${{ if eq( variables['Build.Reason'], 'Schedule' ) }}: value: insiders diff --git a/azure-pipelines/dotnet-variables.yml b/azure-pipelines/dotnet-variables.yml new file mode 100644 index 0000000000..9381ea25dc --- /dev/null +++ b/azure-pipelines/dotnet-variables.yml @@ -0,0 +1,3 @@ +variables: +- name: defaultDotnetVersion + value: '8.0.403' \ No newline at end of file diff --git a/azure-pipelines/green-insertion.yml b/azure-pipelines/green-insertion.yml index 22c3752d3c..e07723e7e5 100644 --- a/azure-pipelines/green-insertion.yml +++ b/azure-pipelines/green-insertion.yml @@ -1,12 +1,12 @@ trigger: none # We only want to trigger manually or based on resources pr: none -schedules: -- cron: "0 6 * * *" # Daily @ 10 PM PST - displayName: Daily vs-green insertion - branches: - include: - - feature/lsp_tools_host +#schedules: +#- cron: "0 6 * * *" # Daily @ 10 PM PST +# displayName: Daily vs-green insertion +# branches: +# include: +# - feature/lsp_tools_host parameters: - name: InsertTargetBranch diff --git a/azure-pipelines/profiling.yml b/azure-pipelines/profiling.yml new file mode 100644 index 0000000000..7171b16e38 --- /dev/null +++ b/azure-pipelines/profiling.yml @@ -0,0 +1,59 @@ +trigger: none +pr: none +schedules: +- cron: '18 1 * * *' # every day at 1:18 AM + displayName: Periodic profiling run + branches: + include: + - main + +variables: +- name: TRACE_DROP_LOCATION + value: $(Build.SourcesDirectory)/out/profiling/ +- name: MERGED_TRACE_LOCATION + value: $(Build.SourcesDirectory)/out/profiling/merged/ +- name: LOGS_DIRECTORY + value: $(Build.SourcesDirectory)/out/logs/ +- template: /azure-pipelines/dotnet-variables.yml@self + + +resources: + repositories: + - repository: 1ESPipelineTemplates + type: git + name: 1ESPipelineTemplates/1ESPipelineTemplates + ref: refs/tags/release +extends: + template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates + parameters: + sdl: + sourceAnalysisPool: + name: netcore1espool-internal + image: 1es-windows-2022 + os: windows + stages: + - stage: Run_Profiling + displayName: Run Profiling + jobs: + - job: Profile + pool: + name: netcore1espool-internal + image: 1es-ubuntu-2204 + os: linux + templateContext: + outputs: + - output: pipelineArtifact + targetPath: $(TRACE_DROP_LOCATION) + artifactName: traces + displayName: 📢 Publish intermediate trace files + condition: failed() + - output: pipelineArtifact + targetPath: $(MERGED_TRACE_LOCATION) + artifactName: merged mibc + displayName: 📢 Publish merged MIBC + steps: + - template: /azure-pipelines/test.yml@self + parameters: + dotnetVersion: $(defaultDotnetVersion) + npmCommand: profiling + testVSCodeVersion: stable \ No newline at end of file diff --git a/azure-pipelines/test.yml b/azure-pipelines/test.yml index 5f8cb471ac..6df8301926 100644 --- a/azure-pipelines/test.yml +++ b/azure-pipelines/test.yml @@ -44,9 +44,17 @@ steps: mergeTestResults: true testRunTitle: $(System.StageDisplayName) $(Agent.JobName) (Attempt $(System.JobAttempt)) -- task: PublishPipelineArtifact@1 - condition: failed() - displayName: 'Upload integration test logs' - inputs: - targetPath: '$(Build.SourcesDirectory)/out/logs' - artifactName: 'Test Logs ($(System.StageDisplayName)-$(Agent.JobName)-$(System.JobAttempt))' +- ${{ if eq(variables['System.TeamProject'], 'internal') }}: + - task: 1ES.PublishPipelineArtifact@1 + condition: failed() + displayName: 'Upload integration test logs' + inputs: + path: '$(Build.SourcesDirectory)/out/logs' + artifact: 'Test Logs ($(System.StageDisplayName)-$(Agent.JobName)-$(System.JobAttempt))' +- ${{ else }}: + - task: PublishPipelineArtifact@1 + condition: failed() + displayName: 'Upload integration test logs' + inputs: + targetPath: '$(Build.SourcesDirectory)/out/logs' + artifactName: 'Test Logs ($(System.StageDisplayName)-$(Agent.JobName)-$(System.JobAttempt))' diff --git a/gulpfile.ts b/gulpfile.ts index c75eb2a778..ef52553ced 100644 --- a/gulpfile.ts +++ b/gulpfile.ts @@ -11,3 +11,4 @@ require('./tasks/createTagsTasks'); require('./tasks/debuggerTasks'); require('./tasks/snapTasks'); require('./tasks/signingTasks'); +require('./tasks/profilingTasks'); diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index 6ad19dccdc..de04c7137e 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -162,8 +162,14 @@ "Text editor must be focused to fix all issues": "Text editor must be focused to fix all issues", "Fix all issues": "Fix all issues", "Select fix all action": "Select fix all action", + "C# LSP Trace Logs": "C# LSP Trace Logs", + "Open solution": "Open solution", + "Restart server": "Restart server", + "C# Workspace Status": "C# Workspace Status", + "The active document is not part of the open workspace. Not all language features will be available.": "The active document is not part of the open workspace. Not all language features will be available.", + "Dismiss": "Dismiss", + "Do not show for this workspace": "Do not show for this workspace", "Test run already in progress": "Test run already in progress", - "Generated document not found": "Generated document not found", "Server stopped": "Server stopped", "Workspace projects": "Workspace projects", "Your workspace has multiple Visual Studio Solution files; please select one to get full IntelliSense.": "Your workspace has multiple Visual Studio Solution files; please select one to get full IntelliSense.", @@ -174,21 +180,15 @@ "IntelliCode features will not be available, {0} failed to activate.": "IntelliCode features will not be available, {0} failed to activate.", "Go to output": "Go to output", "Suppress notification": "Suppress notification", - "C# LSP Trace Logs": "C# LSP Trace Logs", "Restore {0}": "Restore {0}", "Restore already in progress": "Restore already in progress", "Sending request": "Sending request", + "C# Project Context Status": "C# Project Context Status", + "Active File Context": "Active File Context", "C# configuration has changed. Would you like to reload the window to apply your changes?": "C# configuration has changed. Would you like to reload the window to apply your changes?", + "Generated document not found": "Generated document not found", "Nested Code Action": "Nested Code Action", "Fix All: ": "Fix All: ", - "The active document is not part of the open workspace. Not all language features will be available.": "The active document is not part of the open workspace. Not all language features will be available.", - "Dismiss": "Dismiss", - "Do not show for this workspace": "Do not show for this workspace", - "Open solution": "Open solution", - "Restart server": "Restart server", - "C# Workspace Status": "C# Workspace Status", - "C# Project Context Status": "C# Project Context Status", - "Active File Context": "Active File Context", "Pick a fix all scope": "Pick a fix all scope", "Fix All Code Action": "Fix All Code Action", "Failed to set extension directory": "Failed to set extension directory", diff --git a/package.json b/package.json index b5d4547cc9..f15dc76849 100644 --- a/package.json +++ b/package.json @@ -39,9 +39,9 @@ "defaults": { "roslyn": "4.14.0-2.25106.12", "omniSharp": "1.39.12", - "razor": "9.0.0-preview.25064.4", + "razor": "9.0.0-preview.25073.1", "razorOmnisharp": "7.0.0-preview.23363.1", - "xamlTools": "17.14.35723.260" + "xamlTools": "17.14.35807.11" }, "main": "./dist/extension", "l10n": "./l10n", @@ -73,6 +73,7 @@ "test:integration:devkit": "tsc -p ./ && gulp test:integration:devkit", "test:razor": "tsc -p ./ && npm run compile:razorTextMate && gulp test:razor", "test:razorintegration": "tsc -p ./ && gulp test:razorintegration", + "profiling": "tsc -p ./ && gulp profiling", "test:artifacts": "tsc -p ./ && gulp test:artifacts", "omnisharptest": "tsc -p ./ && gulp omnisharptest", "omnisharptest:unit": "tsc -p ./ && gulp omnisharptest:unit", diff --git a/package.nls.cs.json b/package.nls.cs.json index 141d5028e7..54ff647f70 100644 --- a/package.nls.cs.json +++ b/package.nls.cs.json @@ -45,7 +45,7 @@ "configuration.dotnet.completion.triggerCompletionInArgumentLists": "Automaticky zobrazovat seznam dokončení v seznamech argumentů", "configuration.dotnet.defaultSolution.description": "Cesta výchozího řešení, které se má otevřít v pracovním prostoru. Můžete přeskočit nastavením na „zakázat“. (Dříve omnisharp.defaultLaunchSolution)", "configuration.dotnet.enableXamlTools": "Povolí nástroje XAML při použití sady C# Dev Kit", - "configuration.dotnet.formatting.organizeImportsOnFormat": "Specifies whether 'using' directives should be grouped and sorted during document formatting. (Previously `omnisharp.organizeImportsOnFormat`)", + "configuration.dotnet.formatting.organizeImportsOnFormat": "Určuje, zda mají být během formátování dokumentu seskupeny a seřazeny direktivy using. (dříve omnisharp.organizeImportsOnFormat)", "configuration.dotnet.highlighting.highlightRelatedJsonComponents": "Zvýrazněte související komponenty JSON pod kurzorem.", "configuration.dotnet.highlighting.highlightRelatedRegexComponents": "Zvýraznit související komponenty regulárního výrazu pod kurzorem.", "configuration.dotnet.inlayHints.enableInlayHintsForLiteralParameters": "Zobrazit nápovědy pro literály", diff --git a/package.nls.de.json b/package.nls.de.json index 3573651f4f..ac7da73295 100644 --- a/package.nls.de.json +++ b/package.nls.de.json @@ -45,7 +45,7 @@ "configuration.dotnet.completion.triggerCompletionInArgumentLists": "Vervollständigungsliste in Argumentlisten automatisch anzeigen", "configuration.dotnet.defaultSolution.description": "Der Pfad der Standardlösung, die im Arbeitsbereich geöffnet werden soll, oder auf \"deaktivieren\" festlegen, um sie zu überspringen. (Zuvor \"omnisharp.defaultLaunchSolution\")", "configuration.dotnet.enableXamlTools": "Aktiviert XAML-Tools bei Verwendung des C#-Dev Kit", - "configuration.dotnet.formatting.organizeImportsOnFormat": "Specifies whether 'using' directives should be grouped and sorted during document formatting. (Previously `omnisharp.organizeImportsOnFormat`)", + "configuration.dotnet.formatting.organizeImportsOnFormat": "Gibt an, ob „using“-Anweisungen während der Dokumentformatierung gruppiert und sortiert werden sollen. (Zuvor „omnisharp.organizeImportsOnFormat“)", "configuration.dotnet.highlighting.highlightRelatedJsonComponents": "Zugehörige JSON-Komponenten unter dem Cursor markieren.", "configuration.dotnet.highlighting.highlightRelatedRegexComponents": "Zugehörige Komponenten regulärer Ausdrücke unter dem Cursor markieren.", "configuration.dotnet.inlayHints.enableInlayHintsForLiteralParameters": "Hinweise für Literale anzeigen", diff --git a/package.nls.es.json b/package.nls.es.json index ebf08dcbcd..3fd96c6618 100644 --- a/package.nls.es.json +++ b/package.nls.es.json @@ -45,7 +45,7 @@ "configuration.dotnet.completion.triggerCompletionInArgumentLists": "Mostrar automáticamente la lista de finalización en las listas de argumentos", "configuration.dotnet.defaultSolution.description": "Ruta de acceso de la solución predeterminada que se va a abrir en el área de trabajo o se establece en \"deshabilitar\" para omitirla. (Anteriormente \"omnisharp.defaultLaunchSolution\")", "configuration.dotnet.enableXamlTools": "Habilita las herramientas XAML al usar el Kit de desarrollo de C#", - "configuration.dotnet.formatting.organizeImportsOnFormat": "Specifies whether 'using' directives should be grouped and sorted during document formatting. (Previously `omnisharp.organizeImportsOnFormat`)", + "configuration.dotnet.formatting.organizeImportsOnFormat": "Especifica si las directivas \"using\" deben agruparse y ordenarse durante el formato del documento. (Anteriormente \"omnisharp.organizeImportsOnFormat\")", "configuration.dotnet.highlighting.highlightRelatedJsonComponents": "Resaltar los componentes JSON relacionados bajo el cursor.", "configuration.dotnet.highlighting.highlightRelatedRegexComponents": "Resaltar los componentes de expresiones regulares relacionados bajo el cursor.", "configuration.dotnet.inlayHints.enableInlayHintsForLiteralParameters": "Mostrar sugerencias para los literales", diff --git a/package.nls.fr.json b/package.nls.fr.json index 8e4530982d..7d63fe08b1 100644 --- a/package.nls.fr.json +++ b/package.nls.fr.json @@ -45,7 +45,7 @@ "configuration.dotnet.completion.triggerCompletionInArgumentLists": "Afficher automatiquement la liste de complétion dans les listes d'arguments", "configuration.dotnet.defaultSolution.description": "Le chemin d’accès de la solution par défaut à ouvrir dans l’espace de travail, ou la valeur ’disable’ pour l’ignorer. (Précédemment `omnisharp.defaultLaunchSolution`)", "configuration.dotnet.enableXamlTools": "Active les outils XAML lors de l’utilisation du Kit de développement C#", - "configuration.dotnet.formatting.organizeImportsOnFormat": "Specifies whether 'using' directives should be grouped and sorted during document formatting. (Previously `omnisharp.organizeImportsOnFormat`)", + "configuration.dotnet.formatting.organizeImportsOnFormat": "Spécifie si les directives « using » doivent être regroupées et triées lors de la mise en forme d’un document. (Anciennement `omnisharp.organizeImportsOnFormat`)", "configuration.dotnet.highlighting.highlightRelatedJsonComponents": "Mettez en surbrillance les composants JSON associés sous le curseur.", "configuration.dotnet.highlighting.highlightRelatedRegexComponents": "Mettre en surbrillance les composants d’expression régulière associés sous le curseur.", "configuration.dotnet.inlayHints.enableInlayHintsForLiteralParameters": "Afficher les indicateurs pour les littéraux", diff --git a/package.nls.it.json b/package.nls.it.json index b9b6c269f9..3159586835 100644 --- a/package.nls.it.json +++ b/package.nls.it.json @@ -45,7 +45,7 @@ "configuration.dotnet.completion.triggerCompletionInArgumentLists": "Mostra automaticamente l'elenco di completamento negli elenchi di argomenti", "configuration.dotnet.defaultSolution.description": "Percorso della soluzione predefinita da aprire nell'area di lavoro o impostare su 'disabilita' per ignorarla. (In precedenza “omnisharp.defaultLaunchSolution”)", "configuration.dotnet.enableXamlTools": "Abilita gli strumenti XAML quando si usa il kit di sviluppo C#", - "configuration.dotnet.formatting.organizeImportsOnFormat": "Specifies whether 'using' directives should be grouped and sorted during document formatting. (Previously `omnisharp.organizeImportsOnFormat`)", + "configuration.dotnet.formatting.organizeImportsOnFormat": "Specifica se le direttive \"using\" devono essere raggruppate e ordinate durante la formattazione del documento. (In precedenza `omnisharp.organizeImportsOnFormat`)", "configuration.dotnet.highlighting.highlightRelatedJsonComponents": "Evidenziare i componenti JSON correlati sotto il cursore.", "configuration.dotnet.highlighting.highlightRelatedRegexComponents": "Evidenzia i componenti dell'espressione regolare correlati sotto il cursore.", "configuration.dotnet.inlayHints.enableInlayHintsForLiteralParameters": "Mostra suggerimenti per i valori letterali", diff --git a/package.nls.ja.json b/package.nls.ja.json index 90bf3c09fa..adebfec105 100644 --- a/package.nls.ja.json +++ b/package.nls.ja.json @@ -45,7 +45,7 @@ "configuration.dotnet.completion.triggerCompletionInArgumentLists": "引数リストに入力候補一覧を自動的に表示する", "configuration.dotnet.defaultSolution.description": "ワークスペースで開く既定のソリューションのパス。スキップするには 'disable' に設定します。(以前の `omnisharp.defaultLaunchSolution`)", "configuration.dotnet.enableXamlTools": "C# 開発キットを使用するときに XAML ツールを有効にします", - "configuration.dotnet.formatting.organizeImportsOnFormat": "Specifies whether 'using' directives should be grouped and sorted during document formatting. (Previously `omnisharp.organizeImportsOnFormat`)", + "configuration.dotnet.formatting.organizeImportsOnFormat": "ドキュメントの書式設定中に 'using' ディレクティブをグループ化して並べ替える必要があるかどうかを指定します。(以前の 'omnisharp.organizeImportsOnFormat')", "configuration.dotnet.highlighting.highlightRelatedJsonComponents": "カーソルの下にある関連する JSON コンポーネントをハイライトします。", "configuration.dotnet.highlighting.highlightRelatedRegexComponents": "カーソルの下にある関連する正規表現コンポーネントをハイライトします。", "configuration.dotnet.inlayHints.enableInlayHintsForLiteralParameters": "リテラルのヒントを表示する", diff --git a/package.nls.ko.json b/package.nls.ko.json index 65140aca8c..04414b99fe 100644 --- a/package.nls.ko.json +++ b/package.nls.ko.json @@ -45,7 +45,7 @@ "configuration.dotnet.completion.triggerCompletionInArgumentLists": "인수 목록에 자동으로 완성 목록 표시", "configuration.dotnet.defaultSolution.description": "작업 영역에서 열릴 기본 솔루션의 경로, 건너뛰려면 '비활성화'로 설정하세요(이전 `omnisharp.defaultLaunchSolution`).", "configuration.dotnet.enableXamlTools": "C# 개발자 키트를 사용할 때 XAML 도구 사용", - "configuration.dotnet.formatting.organizeImportsOnFormat": "Specifies whether 'using' directives should be grouped and sorted during document formatting. (Previously `omnisharp.organizeImportsOnFormat`)", + "configuration.dotnet.formatting.organizeImportsOnFormat": "문서 서식을 지정하는 동안 'using' 지시문을 그룹화하고 정렬할지 여부를 지정합니다. (Previously `omnisharp.organizeImportsOnFormat`)", "configuration.dotnet.highlighting.highlightRelatedJsonComponents": "커서 아래에서 관련 JSON 구성 요소를 강조 표시합니다.", "configuration.dotnet.highlighting.highlightRelatedRegexComponents": "커서 아래의 관련 정규식 구성 요소를 강조 표시합니다.", "configuration.dotnet.inlayHints.enableInlayHintsForLiteralParameters": "리터럴에 대한 힌트 표시", diff --git a/package.nls.pl.json b/package.nls.pl.json index 6e71374824..0bf5b7e66c 100644 --- a/package.nls.pl.json +++ b/package.nls.pl.json @@ -45,7 +45,7 @@ "configuration.dotnet.completion.triggerCompletionInArgumentLists": "Automatycznie pokaż listę uzupełniania na listach argumentów", "configuration.dotnet.defaultSolution.description": "Ścieżka domyślnego rozwiązania, która ma zostać otwarta w obszarze roboczym, lub ustawiona na wartość „wyłącz”, aby je pominąć. (Poprzednio „omnisharp.defaultLaunchSolution”)", "configuration.dotnet.enableXamlTools": "Włącza narzędzia XAML podczas korzystania z zestawu deweloperskiego języka C#", - "configuration.dotnet.formatting.organizeImportsOnFormat": "Specifies whether 'using' directives should be grouped and sorted during document formatting. (Previously `omnisharp.organizeImportsOnFormat`)", + "configuration.dotnet.formatting.organizeImportsOnFormat": "Określa, czy dyrektywy „using” mają być grupowane i sortowane podczas formatowania dokumentu. (Poprzednio „omnisharp.organizeImportsOnFormat”)", "configuration.dotnet.highlighting.highlightRelatedJsonComponents": "Wyróżnij powiązane składniki JSON pod kursorem.", "configuration.dotnet.highlighting.highlightRelatedRegexComponents": "Wyróżnij powiązane składniki wyrażenia regularnego pod kursorem.", "configuration.dotnet.inlayHints.enableInlayHintsForLiteralParameters": "Pokaż wskazówki dla literałów", diff --git a/package.nls.pt-br.json b/package.nls.pt-br.json index 7234ed9312..b4da580dcf 100644 --- a/package.nls.pt-br.json +++ b/package.nls.pt-br.json @@ -45,7 +45,7 @@ "configuration.dotnet.completion.triggerCompletionInArgumentLists": "Mostrar automaticamente a lista de conclusão nas listas de argumentos", "configuration.dotnet.defaultSolution.description": "O caminho da solução padrão a ser aberta no workspace ou definido como 'desabilitado' para ignorá-la. (Anteriormente `omnisharp.defaultLaunchSolution`)", "configuration.dotnet.enableXamlTools": "Habilita ferramentas XAML ao usar o Kit de Desenvolvimento em C#", - "configuration.dotnet.formatting.organizeImportsOnFormat": "Specifies whether 'using' directives should be grouped and sorted during document formatting. (Previously `omnisharp.organizeImportsOnFormat`)", + "configuration.dotnet.formatting.organizeImportsOnFormat": "Especifica se as diretivas 'usando' devem ser agrupadas e classificadas durante a formatação do documento. (Anteriormente `omnisharp.organizeImportsOnFormat`)", "configuration.dotnet.highlighting.highlightRelatedJsonComponents": "Destaque os componentes JSON relacionados sob o cursor.", "configuration.dotnet.highlighting.highlightRelatedRegexComponents": "Destaque os componentes de expressão regular relacionados sob o cursor.", "configuration.dotnet.inlayHints.enableInlayHintsForLiteralParameters": "Mostrar as dicas para os literais", diff --git a/package.nls.ru.json b/package.nls.ru.json index c4af3a4fc0..394e74d1b8 100644 --- a/package.nls.ru.json +++ b/package.nls.ru.json @@ -45,7 +45,7 @@ "configuration.dotnet.completion.triggerCompletionInArgumentLists": "Автоматически показывать список завершения в списках аргументов", "configuration.dotnet.defaultSolution.description": "Путь к решению по умолчанию, которое будет открыто в рабочей области. Или задайте значение \"Отключить\", чтобы пропустить его. (Ранее — \"omnisharp.defaultLaunchSolution\")", "configuration.dotnet.enableXamlTools": "Включает инструменты XAML при использовании комплекта разработки C# Dev Kit.", - "configuration.dotnet.formatting.organizeImportsOnFormat": "Specifies whether 'using' directives should be grouped and sorted during document formatting. (Previously `omnisharp.organizeImportsOnFormat`)", + "configuration.dotnet.formatting.organizeImportsOnFormat": "Указывает, следует ли группировать и сортировать директивы \"using\" во время форматирования документов. (Ранее — \"omnisharp.organizeImportsOnFormat\")", "configuration.dotnet.highlighting.highlightRelatedJsonComponents": "Выделить связанные компоненты JSON под курсором.", "configuration.dotnet.highlighting.highlightRelatedRegexComponents": "Выделение связанных компонентов регулярных выражений под курсором.", "configuration.dotnet.inlayHints.enableInlayHintsForLiteralParameters": "Отображать подсказки для литералов", diff --git a/package.nls.tr.json b/package.nls.tr.json index 03d4f8a4f4..d3465c2f41 100644 --- a/package.nls.tr.json +++ b/package.nls.tr.json @@ -45,7 +45,7 @@ "configuration.dotnet.completion.triggerCompletionInArgumentLists": "Bağımsız değişken listelerinde tamamlama listesini otomatik olarak göster", "configuration.dotnet.defaultSolution.description": "Varsayılan çözümün yolu, çalışma alanında açılacak veya atlamak için 'devre dışı' olarak ayarlanacak. (Daha önce 'omnisharp.defaultLaunchSolution')", "configuration.dotnet.enableXamlTools": "C# Geliştirme Setini kullanırken XAML araçlarını etkinleştirir", - "configuration.dotnet.formatting.organizeImportsOnFormat": "Specifies whether 'using' directives should be grouped and sorted during document formatting. (Previously `omnisharp.organizeImportsOnFormat`)", + "configuration.dotnet.formatting.organizeImportsOnFormat": "'using' yönergelerinin belge biçimlendirmesi sırasında gruplandırılarak sıralanıp sıralanmayacağını belirtir. (Önceki adıyla `omnisharp.organizeImportsOnFormat`)", "configuration.dotnet.highlighting.highlightRelatedJsonComponents": "İmlecin altındaki ilgili JSON bileşenlerini vurgula.", "configuration.dotnet.highlighting.highlightRelatedRegexComponents": "İmleç altındaki ilgili normal ifade bileşenlerini vurgula.", "configuration.dotnet.inlayHints.enableInlayHintsForLiteralParameters": "Sabit değerler için ipuçlarını göster", diff --git a/package.nls.zh-cn.json b/package.nls.zh-cn.json index a898a9e86c..25b33b4e4b 100644 --- a/package.nls.zh-cn.json +++ b/package.nls.zh-cn.json @@ -45,7 +45,7 @@ "configuration.dotnet.completion.triggerCompletionInArgumentLists": "自动显示参数列表中的补全列表", "configuration.dotnet.defaultSolution.description": "要在工作区中打开的默认解决方案的路径,或者设置为“禁用”以跳过它。(之前为 \"omnisharp.defaultLaunchSolution\")", "configuration.dotnet.enableXamlTools": "使用 C# 开发工具包时启用 XAML 工具", - "configuration.dotnet.formatting.organizeImportsOnFormat": "Specifies whether 'using' directives should be grouped and sorted during document formatting. (Previously `omnisharp.organizeImportsOnFormat`)", + "configuration.dotnet.formatting.organizeImportsOnFormat": "指定在设置文档格式期间是否应对 “using” 指令进行分组和排序。(以前为 `omnisharp.organizeImportsOnFormat`)", "configuration.dotnet.highlighting.highlightRelatedJsonComponents": "突出显示光标下的相关 JSON 组件。", "configuration.dotnet.highlighting.highlightRelatedRegexComponents": "突出显示光标下的相关正则表达式组件。", "configuration.dotnet.inlayHints.enableInlayHintsForLiteralParameters": "显示文本提示", diff --git a/package.nls.zh-tw.json b/package.nls.zh-tw.json index 55edc8b0d9..e06f1b29a5 100644 --- a/package.nls.zh-tw.json +++ b/package.nls.zh-tw.json @@ -45,7 +45,7 @@ "configuration.dotnet.completion.triggerCompletionInArgumentLists": "自動在引數清單中顯示自動完成清單", "configuration.dotnet.defaultSolution.description": "要在工作區中開啟的預設解決方案路徑,或設為 [停用] 以略過它。(先前為 `omnisharp.defaultLaunchSolution`)", "configuration.dotnet.enableXamlTools": "使用 C# 開發套件時啟用 XAML 工具", - "configuration.dotnet.formatting.organizeImportsOnFormat": "Specifies whether 'using' directives should be grouped and sorted during document formatting. (Previously `omnisharp.organizeImportsOnFormat`)", + "configuration.dotnet.formatting.organizeImportsOnFormat": "指定在文件格式化期間,是否應該將 'using' 指示詞分組和排序。(先前為 'omnisharp.organizeImportsOnFormat')", "configuration.dotnet.highlighting.highlightRelatedJsonComponents": "反白資料指標下的相關 JSON 元件。", "configuration.dotnet.highlighting.highlightRelatedRegexComponents": "反白資料指標下的相關規則運算式元件。", "configuration.dotnet.inlayHints.enableInlayHintsForLiteralParameters": "顯示常值的提示", diff --git a/snippets/csharp.json b/snippets/csharp.json index bd3c63d537..e37bc6cfb4 100644 --- a/snippets/csharp.json +++ b/snippets/csharp.json @@ -354,6 +354,25 @@ ], "description": "An automatically implemented property. C# 3.0 or higher" }, + "propex": { + "prefix": "propex", + "body": [ + "public ${1:int} ${2:MyProperty} => ${3:myVar};$0" + ], + "description": "An expression-bodied property without a backing field. C# 6.0 or higher" + }, + "propexfull": { + "prefix": "propexfull", + "body": [ + "private ${1:int} ${2:myVar};", + "public ${1:int} ${3:MyProperty}", + "{", + "\tget => ${2:myVar};", + "\tset => ${2:myVar} = value;", + "}$0" + ], + "description": "An expression-bodied property with a private field. C# 6.0 or higher" + }, "sim": { "prefix": "sim", "body": [ diff --git a/src/csharpExtensionExports.ts b/src/csharpExtensionExports.ts index 99bc7e3725..13569b3faf 100644 --- a/src/csharpExtensionExports.ts +++ b/src/csharpExtensionExports.ts @@ -9,7 +9,7 @@ import { EventStream } from './eventStream'; import TestManager from './omnisharp/features/dotnetTest'; import { GlobalBrokeredServiceContainer } from '@microsoft/servicehub-framework'; import { RequestType } from 'vscode-languageclient/node'; -import { LanguageServerEvents } from './lsptoolshost/languageServerEvents'; +import { LanguageServerEvents } from './lsptoolshost/server/languageServerEvents'; export interface OmnisharpExtensionExports { initializationFinished: () => Promise; diff --git a/src/lsptoolshost/activate.ts b/src/lsptoolshost/activate.ts new file mode 100644 index 0000000000..08534079c4 --- /dev/null +++ b/src/lsptoolshost/activate.ts @@ -0,0 +1,175 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import * as fs from 'fs'; +import * as path from 'path'; +import { registerCommands } from './commands'; +import { registerDebugger } from './debugger/debugger'; +import { PlatformInformation } from '../shared/platform'; +import TelemetryReporter from '@vscode/extension-telemetry'; +import { getCSharpDevKit } from '../utils/getCSharpDevKit'; +import { DotnetRuntimeExtensionResolver } from './dotnetRuntime/dotnetRuntimeExtensionResolver'; +import { registerUnitTestingCommands } from './testing/unitTesting'; +import { registerLanguageServerOptionChanges } from './options/optionChanges'; +import { Observable } from 'rxjs'; +import { RoslynLanguageServerEvents } from './server/languageServerEvents'; +import { registerRazorCommands } from './razor/razorCommands'; +import { registerCodeActionFixAllCommands } from './diagnostics/fixAllCodeAction'; +import { commonOptions, languageServerOptions } from '../shared/options'; +import { registerNestedCodeActionCommands } from './diagnostics/nestedCodeAction'; +import { registerRestoreCommands } from './projectRestore/restore'; +import { registerCopilotExtension } from './copilot/copilot'; +import { registerSourceGeneratedFilesContentProvider } from './generators/sourceGeneratedFilesContentProvider'; +import { registerMiscellaneousFileNotifier } from './workspace/miscellaneousFileNotifier'; +import { TelemetryEventNames } from '../shared/telemetryEventNames'; +import { WorkspaceStatus } from './workspace/workspaceStatus'; +import { ProjectContextStatus } from './projectContext/projectContextStatus'; +import { RoslynLanguageServer } from './server/roslynLanguageServer'; + +let _channel: vscode.LogOutputChannel; +let _traceChannel: vscode.OutputChannel; + +/** + * Creates and activates the Roslyn language server. + * The returned promise will complete when the server starts. + */ +export async function activateRoslynLanguageServer( + context: vscode.ExtensionContext, + platformInfo: PlatformInformation, + optionObservable: Observable, + outputChannel: vscode.LogOutputChannel, + reporter: TelemetryReporter, + languageServerEvents: RoslynLanguageServerEvents +): Promise { + // Create a channel for outputting general logs from the language server. + _channel = outputChannel; + // Create a separate channel for outputting trace logs - these are incredibly verbose and make other logs very difficult to see. + // The trace channel verbosity is controlled by the _channel verbosity. + _traceChannel = vscode.window.createOutputChannel(vscode.l10n.t('C# LSP Trace Logs')); + + reporter.sendTelemetryEvent(TelemetryEventNames.ClientInitialize); + + const hostExecutableResolver = new DotnetRuntimeExtensionResolver( + platformInfo, + getServerPath, + outputChannel, + context.extensionPath + ); + const additionalExtensionPaths = scanExtensionPlugins(); + + const languageServer = await RoslynLanguageServer.initializeAsync( + platformInfo, + hostExecutableResolver, + context, + reporter, + additionalExtensionPaths, + languageServerEvents, + _channel, + _traceChannel + ); + + registerLanguageStatusItems(context, languageServer, languageServerEvents); + registerMiscellaneousFileNotifier(context, languageServer); + registerCopilotExtension(languageServer, _channel); + + // Register any commands that need to be handled by the extension. + registerCommands(context, languageServer, hostExecutableResolver, _channel); + registerNestedCodeActionCommands(context, languageServer, _channel); + registerCodeActionFixAllCommands(context, languageServer, _channel); + + registerRazorCommands(context, languageServer); + + registerUnitTestingCommands(context, languageServer); + + // Register any needed debugger components that need to communicate with the language server. + registerDebugger(context, languageServer, languageServerEvents, platformInfo, _channel); + + registerRestoreCommands(context, languageServer); + + registerSourceGeneratedFilesContentProvider(context, languageServer); + + context.subscriptions.push(registerLanguageServerOptionChanges(optionObservable)); + + return languageServer; + + function scanExtensionPlugins(): string[] { + const extensionsFromPackageJson = vscode.extensions.all.flatMap((extension) => { + let loadPaths = extension.packageJSON.contributes?.['csharpExtensionLoadPaths']; + if (loadPaths === undefined || loadPaths === null) { + _channel.debug(`Extension ${extension.id} does not contribute csharpExtensionLoadPaths`); + return []; + } + + if (!Array.isArray(loadPaths) || loadPaths.some((loadPath) => typeof loadPath !== 'string')) { + _channel.warn( + `Extension ${extension.id} has invalid csharpExtensionLoadPaths. Expected string array, found ${loadPaths}` + ); + return []; + } + + loadPaths = loadPaths.map((loadPath) => path.join(extension.extensionPath, loadPath)); + _channel.trace(`Extension ${extension.id} contributes csharpExtensionLoadPaths: ${loadPaths}`); + return loadPaths; + }); + const extensionsFromOptions = languageServerOptions.extensionsPaths ?? []; + return extensionsFromPackageJson.concat(extensionsFromOptions); + } +} + +function registerLanguageStatusItems( + context: vscode.ExtensionContext, + languageServer: RoslynLanguageServer, + languageServerEvents: RoslynLanguageServerEvents +) { + // DevKit will provide an equivalent workspace status item. + if (!getCSharpDevKit()) { + WorkspaceStatus.createStatusItem(context, languageServerEvents); + } + ProjectContextStatus.createStatusItem(context, languageServer); +} + +export function getServerPath(platformInfo: PlatformInformation) { + let serverPath = process.env.DOTNET_ROSLYN_SERVER_PATH; + + if (serverPath) { + _channel.appendLine(`Using server path override from DOTNET_ROSLYN_SERVER_PATH: ${serverPath}`); + } else { + serverPath = commonOptions.serverPath; + if (!serverPath) { + // Option not set, use the path from the extension. + serverPath = getInstalledServerPath(platformInfo); + } + } + + if (!fs.existsSync(serverPath)) { + throw new Error(`Cannot find language server in path '${serverPath}'`); + } + + return serverPath; +} + +function getInstalledServerPath(platformInfo: PlatformInformation): string { + const clientRoot = __dirname; + const serverFilePath = path.join(clientRoot, '..', '.roslyn', 'Microsoft.CodeAnalysis.LanguageServer'); + + let extension = ''; + if (platformInfo.isWindows()) { + extension = '.exe'; + } else if (platformInfo.isMacOS()) { + // MacOS executables must be signed with codesign. Currently all Roslyn server executables are built on windows + // and therefore dotnet publish does not automatically sign them. + // Tracking bug - https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1767519/ + extension = '.dll'; + } + + let pathWithExtension = `${serverFilePath}${extension}`; + if (!fs.existsSync(pathWithExtension)) { + // We might be running a platform neutral vsix which has no executable, instead we run the dll directly. + pathWithExtension = `${serverFilePath}.dll`; + } + + return pathWithExtension; +} diff --git a/src/lsptoolshost/onAutoInsert.ts b/src/lsptoolshost/autoInsert/onAutoInsert.ts similarity index 97% rename from src/lsptoolshost/onAutoInsert.ts rename to src/lsptoolshost/autoInsert/onAutoInsert.ts index 71ef4198a2..3ecac2e1c5 100644 --- a/src/lsptoolshost/onAutoInsert.ts +++ b/src/lsptoolshost/autoInsert/onAutoInsert.ts @@ -6,8 +6,8 @@ import * as vscode from 'vscode'; import { FormattingOptions, LanguageClient, TextDocumentIdentifier } from 'vscode-languageclient/node'; -import * as RoslynProtocol from './roslynProtocol'; -import { RoslynLanguageServer } from './roslynLanguageServer'; +import * as RoslynProtocol from '../server/roslynProtocol'; +import { RoslynLanguageServer } from '../server/roslynLanguageServer'; export function registerOnAutoInsert(languageServer: RoslynLanguageServer, languageClient: LanguageClient) { let source = new vscode.CancellationTokenSource(); diff --git a/src/lsptoolshost/onAutoInsertFeature.ts b/src/lsptoolshost/autoInsert/onAutoInsertFeature.ts similarity index 98% rename from src/lsptoolshost/onAutoInsertFeature.ts rename to src/lsptoolshost/autoInsert/onAutoInsertFeature.ts index 88a1537b2b..a8b48dbe74 100644 --- a/src/lsptoolshost/onAutoInsertFeature.ts +++ b/src/lsptoolshost/autoInsert/onAutoInsertFeature.ts @@ -21,7 +21,7 @@ import { ServerCapabilities, } from 'vscode-languageserver-protocol'; -import * as RoslynProtocol from './roslynProtocol'; +import * as RoslynProtocol from '../server/roslynProtocol'; import { generateUuid } from 'vscode-languageclient/lib/common/utils/uuid'; export class OnAutoInsertFeature implements DynamicFeature { diff --git a/src/lsptoolshost/commands.ts b/src/lsptoolshost/commands.ts index 5d8d1edf33..ad09e35fcf 100644 --- a/src/lsptoolshost/commands.ts +++ b/src/lsptoolshost/commands.ts @@ -4,14 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { UriConverter } from './uriConverter'; -import * as languageClient from 'vscode-languageclient/node'; -import { RoslynLanguageServer } from './roslynLanguageServer'; -import { createLaunchTargetForSolution } from '../shared/launchTarget'; +import { RoslynLanguageServer } from './server/roslynLanguageServer'; import reportIssue from '../shared/reportIssue'; import { getDotnetInfo } from '../shared/utils/getDotnetInfo'; import { IHostExecutableResolver } from '../shared/constants/IHostExecutableResolver'; -import { getCSharpDevKit } from '../utils/getCSharpDevKit'; +import { registerWorkspaceCommands } from './workspace/workspaceCommands'; +import { registerServerCommands } from './server/serverCommands'; export function registerCommands( context: vscode.ExtensionContext, @@ -19,31 +17,20 @@ export function registerCommands( hostExecutableResolver: IHostExecutableResolver, outputChannel: vscode.LogOutputChannel ) { - // It is very important to be careful about the types used as parameters for these command callbacks. - // If the arguments are coming from the server as json, it is NOT appropriate to use type definitions - // from the normal vscode API (e.g. vscode.Location) as input parameters. - // - // This is because at runtime the json objects do not contain the expected prototypes that the vscode types - // have and will fail 'instanceof' checks that are sprinkled throught the vscode APIs. - // - // Instead, we define inputs from the server using the LSP type definitions as those have no prototypes - // so we don't accidentally pass them directly into vscode APIs. - context.subscriptions.push(vscode.commands.registerCommand('roslyn.client.peekReferences', peekReferencesCallback)); - context.subscriptions.push( - vscode.commands.registerCommand( - 'roslyn.client.completionComplexEdit', - async (textDocument, textEdit, isSnippetString, newOffset) => - completionComplexEdit(textDocument, textEdit, isSnippetString, newOffset, outputChannel) - ) - ); - context.subscriptions.push( - vscode.commands.registerCommand('dotnet.restartServer', async () => restartServer(languageServer)) - ); - if (!getCSharpDevKit()) { - context.subscriptions.push( - vscode.commands.registerCommand('dotnet.openSolution', async () => openSolution(languageServer)) - ); - } + registerExtensionCommands(context, languageServer, hostExecutableResolver, outputChannel); + registerWorkspaceCommands(context, languageServer); + registerServerCommands(context, languageServer, outputChannel); +} + +/** + * Register commands that drive the C# extension. + */ +function registerExtensionCommands( + context: vscode.ExtensionContext, + languageServer: RoslynLanguageServer, + hostExecutableResolver: IHostExecutableResolver, + outputChannel: vscode.LogOutputChannel +) { context.subscriptions.push( vscode.commands.registerCommand('csharp.reportIssue', async () => reportIssue( @@ -58,139 +45,3 @@ export function registerCommands( vscode.commands.registerCommand('csharp.showOutputWindow', async () => outputChannel.show()) ); } - -/** - * Callback for code lens commands. Executes a references request via the VSCode command - * which will call into the LSP server to get the data. Then calls the VSCode command to display the result. - * @param uriStr The uri containing the location to find references for. - * @param serverPosition The position json object to execute the find references request. - */ -async function peekReferencesCallback(uriStr: string, serverPosition: languageClient.Position): Promise { - const uri = UriConverter.deserialize(uriStr); - - // Convert the json position object into the corresponding vscode position type. - const vscodeApiPosition = new vscode.Position(serverPosition.line, serverPosition.character); - const references: vscode.Location[] = await vscode.commands.executeCommand( - 'vscode.executeReferenceProvider', - uri, - vscodeApiPosition - ); - - if (references && Array.isArray(references)) { - // The references could come back after the document has moved to a new state (that may not even contain the position). - // This is fine - the VSCode API is resilient to that scenario and will not crash. - await vscode.commands.executeCommand('editor.action.showReferences', uri, vscodeApiPosition, references); - } -} - -async function restartServer(languageServer: RoslynLanguageServer): Promise { - await languageServer.restart(); -} - -/** - * Callback after a completion item with complex edit is committed. The change needs to be made outside completion resolve - * handling - * - * IMPORTANT: @see RazorCompletionItemProvider.resolveCompletionItem matches the arguments for this commands - * so it can remap correctly in razor files. Any updates to this function signature requires updates there as well. - * - * @param uriStr The uri containing the location of the document where the completion item was committed in. - * @param textEdits The additional complex edit for the committed completion item. - * @param isSnippetString Indicates if the TextEdit contains a snippet string. - * @param newPosition The offset for new cursor position. -1 if the edit has not specified one. - */ -async function completionComplexEdit( - textDocument: languageClient.TextDocumentIdentifier, - textEdit: vscode.TextEdit, - isSnippetString: boolean, - newOffset: number, - outputChannel: vscode.LogOutputChannel -): Promise { - const componentName = '[roslyn.client.completionComplexEdit]'; - - // Find TextDocument, opening if needed. - const uri = UriConverter.deserialize(textDocument.uri); - const document = await vscode.workspace.openTextDocument(uri); - if (document === undefined) { - outputAndThrow(outputChannel, `${componentName} Can't open document with path: '${textDocument.uri}'`); - } - - // Use editor if we need to deal with selection or snippets. - let editor: vscode.TextEditor | undefined = undefined; - if (isSnippetString || newOffset >= 0) { - editor = await vscode.window.showTextDocument(document); - if (editor === undefined) { - outputAndThrow( - outputChannel, - `${componentName} Editor unavailable for document with path: '${textDocument.uri}'` - ); - } - } - - const newRange = document.validateRange( - new vscode.Range( - textEdit.range.start.line, - textEdit.range.start.character, - textEdit.range.end.line, - textEdit.range.end.character - ) - ); - - // HACK: - // ApplyEdit would fail the first time it's called when an item was committed with text modifying commit char (e.g. space, '(', etc.) - // so we retry a couple time here as a tempory workaround. We need to either figure our the reason of the failure, and/or try the - // approach of sending another edit request to server with updated document. - let success = false; - for (let i = 0; i < 3; i++) { - if (isSnippetString) { - editor!.selection = new vscode.Selection(newRange.start, newRange.end); - success = await editor!.insertSnippet(new vscode.SnippetString(textEdit.newText)); - } else { - const edit = new vscode.WorkspaceEdit(); - const newTextEdit = vscode.TextEdit.replace(newRange, textEdit.newText); - edit.set(document.uri, [newTextEdit]); - success = await vscode.workspace.applyEdit(edit); - - if (success && newOffset >= 0) { - const newPosition = document.positionAt(newOffset); - editor!.selections = [new vscode.Selection(newPosition, newPosition)]; - } - } - - if (success) { - return; - } - } - - if (!success) { - outputAndThrow( - outputChannel, - `${componentName} ${isSnippetString ? 'TextEditor.insertSnippet' : 'workspace.applyEdit'} failed.` - ); - } -} - -function outputAndThrow(outputChannel: vscode.LogOutputChannel, message: string): void { - outputChannel.show(); - outputChannel.error(message); - throw new Error(message); -} - -async function openSolution(languageServer: RoslynLanguageServer): Promise { - if (!vscode.workspace.workspaceFolders) { - return undefined; - } - - const solutionFiles = await vscode.workspace.findFiles('**/*.{sln,slnf}'); - const launchTargets = solutionFiles.map(createLaunchTargetForSolution); - const launchTarget = await vscode.window.showQuickPick(launchTargets, { - matchOnDescription: true, - placeHolder: `Select solution file`, - }); - - if (launchTarget) { - const uri = vscode.Uri.file(launchTarget.target); - await languageServer.openSolution(uri); - return uri; - } -} diff --git a/src/lsptoolshost/copilot.ts b/src/lsptoolshost/copilot/copilot.ts similarity index 94% rename from src/lsptoolshost/copilot.ts rename to src/lsptoolshost/copilot/copilot.ts index 4915606094..651f671ece 100644 --- a/src/lsptoolshost/copilot.ts +++ b/src/lsptoolshost/copilot/copilot.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { CSharpExtensionId } from '../constants/csharpExtensionId'; -import { CopilotRelatedDocumentsReport, CopilotRelatedDocumentsRequest } from './roslynProtocol'; -import { RoslynLanguageServer } from './roslynLanguageServer'; -import { UriConverter } from './uriConverter'; +import { CSharpExtensionId } from '../../constants/csharpExtensionId'; +import { CopilotRelatedDocumentsReport, CopilotRelatedDocumentsRequest } from '../server/roslynProtocol'; +import { RoslynLanguageServer } from '../server/roslynLanguageServer'; +import { UriConverter } from '../utils/uriConverter'; import { TextDocumentIdentifier } from 'vscode-languageserver-protocol'; interface CopilotTrait { diff --git a/src/lsptoolshost/services/IDotnetDebugConfigurationService.ts b/src/lsptoolshost/debugger/IDotnetDebugConfigurationService.ts similarity index 100% rename from src/lsptoolshost/services/IDotnetDebugConfigurationService.ts rename to src/lsptoolshost/debugger/IDotnetDebugConfigurationService.ts diff --git a/src/lsptoolshost/debugger.ts b/src/lsptoolshost/debugger/debugger.ts similarity index 73% rename from src/lsptoolshost/debugger.ts rename to src/lsptoolshost/debugger/debugger.ts index c7d2a3bc0b..01c5b6d2c3 100644 --- a/src/lsptoolshost/debugger.ts +++ b/src/lsptoolshost/debugger/debugger.ts @@ -4,16 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { addAssetsIfNecessary, generateAssets } from '../shared/assets'; -import { DotnetWorkspaceConfigurationProvider } from '../shared/workspaceConfigurationProvider'; -import { IWorkspaceDebugInformationProvider } from '../shared/IWorkspaceDebugInformationProvider'; -import { RoslynLanguageServer } from './roslynLanguageServer'; -import { RoslynWorkspaceDebugInformationProvider } from './roslynWorkspaceDebugConfigurationProvider'; -import { PlatformInformation } from '../shared/platform'; -import { ServerState } from './serverStateChange'; -import { DotnetConfigurationResolver } from '../shared/dotnetConfigurationProvider'; -import { getCSharpDevKit } from '../utils/getCSharpDevKit'; -import { RoslynLanguageServerEvents } from './languageServerEvents'; +import { addAssetsIfNecessary, generateAssets } from '../../shared/assets'; +import { DotnetWorkspaceConfigurationProvider } from '../../shared/workspaceConfigurationProvider'; +import { IWorkspaceDebugInformationProvider } from '../../shared/IWorkspaceDebugInformationProvider'; +import { RoslynLanguageServer } from '../server/roslynLanguageServer'; +import { RoslynWorkspaceDebugInformationProvider } from '../debugger/roslynWorkspaceDebugConfigurationProvider'; +import { PlatformInformation } from '../../shared/platform'; +import { DotnetConfigurationResolver } from '../../shared/dotnetConfigurationProvider'; +import { getCSharpDevKit } from '../../utils/getCSharpDevKit'; +import { RoslynLanguageServerEvents, ServerState } from '../server/languageServerEvents'; export function registerDebugger( context: vscode.ExtensionContext, diff --git a/src/lsptoolshost/roslynWorkspaceDebugConfigurationProvider.ts b/src/lsptoolshost/debugger/roslynWorkspaceDebugConfigurationProvider.ts similarity index 90% rename from src/lsptoolshost/roslynWorkspaceDebugConfigurationProvider.ts rename to src/lsptoolshost/debugger/roslynWorkspaceDebugConfigurationProvider.ts index 2b5666c835..1c0b650c89 100644 --- a/src/lsptoolshost/roslynWorkspaceDebugConfigurationProvider.ts +++ b/src/lsptoolshost/debugger/roslynWorkspaceDebugConfigurationProvider.ts @@ -4,19 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { mapAsync } from '../common'; +import { mapAsync } from '../../common'; import { IWorkspaceDebugInformationProvider, ProjectDebugInformation, -} from '../shared/IWorkspaceDebugInformationProvider'; -import { isBlazorWebAssemblyHosted, isBlazorWebAssemblyProject, isWebProject } from '../shared/utils'; -import { RoslynLanguageServer } from './roslynLanguageServer'; +} from '../../shared/IWorkspaceDebugInformationProvider'; +import { isBlazorWebAssemblyHosted, isBlazorWebAssemblyProject, isWebProject } from '../../shared/utils'; +import { RoslynLanguageServer } from '../server/roslynLanguageServer'; import { ProjectDebugConfiguration, WorkspaceDebugConfigurationParams, WorkspaceDebugConfigurationRequest, -} from './roslynProtocol'; -import { UriConverter } from './uriConverter'; +} from '../server/roslynProtocol'; +import { UriConverter } from '../utils/uriConverter'; export class RoslynWorkspaceDebugInformationProvider implements IWorkspaceDebugInformationProvider { constructor(private server: RoslynLanguageServer, private outputChannel: vscode.LogOutputChannel) {} diff --git a/src/lsptoolshost/buildDiagnosticsService.ts b/src/lsptoolshost/diagnostics/buildDiagnosticsService.ts similarity index 99% rename from src/lsptoolshost/buildDiagnosticsService.ts rename to src/lsptoolshost/diagnostics/buildDiagnosticsService.ts index 14ebea6cb9..70458d3bd5 100644 --- a/src/lsptoolshost/buildDiagnosticsService.ts +++ b/src/lsptoolshost/diagnostics/buildDiagnosticsService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { languageServerOptions } from '../shared/options'; +import { languageServerOptions } from '../../shared/options'; export enum AnalysisSetting { FullSolution = 'fullSolution', diff --git a/src/lsptoolshost/services/buildResultReporterService.ts b/src/lsptoolshost/diagnostics/buildResultReporterService.ts similarity index 95% rename from src/lsptoolshost/services/buildResultReporterService.ts rename to src/lsptoolshost/diagnostics/buildResultReporterService.ts index 4b26a81963..8055f36c5b 100644 --- a/src/lsptoolshost/services/buildResultReporterService.ts +++ b/src/lsptoolshost/diagnostics/buildResultReporterService.ts @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { RoslynLanguageServer } from '../roslynLanguageServer'; +import { RoslynLanguageServer } from '../server/roslynLanguageServer'; import { CancellationToken } from 'vscode-jsonrpc'; import * as vscode from 'vscode'; diff --git a/src/lsptoolshost/fixAllCodeAction.ts b/src/lsptoolshost/diagnostics/fixAllCodeAction.ts similarity index 94% rename from src/lsptoolshost/fixAllCodeAction.ts rename to src/lsptoolshost/diagnostics/fixAllCodeAction.ts index 429b8ad9e6..05fe9ea5a3 100644 --- a/src/lsptoolshost/fixAllCodeAction.ts +++ b/src/lsptoolshost/diagnostics/fixAllCodeAction.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import * as RoslynProtocol from './roslynProtocol'; +import * as RoslynProtocol from '../server/roslynProtocol'; import { LSPAny } from 'vscode-languageserver-protocol'; -import { RoslynLanguageServer } from './roslynLanguageServer'; +import { RoslynLanguageServer } from '../server/roslynLanguageServer'; import { URIConverter, createConverter } from 'vscode-languageclient/lib/common/protocolConverter'; -import { UriConverter } from './uriConverter'; +import { UriConverter } from '../utils/uriConverter'; export function registerCodeActionFixAllCommands( context: vscode.ExtensionContext, diff --git a/src/lsptoolshost/nestedCodeAction.ts b/src/lsptoolshost/diagnostics/nestedCodeAction.ts similarity index 97% rename from src/lsptoolshost/nestedCodeAction.ts rename to src/lsptoolshost/diagnostics/nestedCodeAction.ts index 633d6f19c0..2cb148dc99 100644 --- a/src/lsptoolshost/nestedCodeAction.ts +++ b/src/lsptoolshost/diagnostics/nestedCodeAction.ts @@ -5,9 +5,9 @@ import * as vscode from 'vscode'; import { CodeAction, CodeActionResolveRequest, LSPAny } from 'vscode-languageserver-protocol'; -import { RoslynLanguageServer } from './roslynLanguageServer'; +import { RoslynLanguageServer } from '../server/roslynLanguageServer'; import { URIConverter, createConverter } from 'vscode-languageclient/lib/common/protocolConverter'; -import { UriConverter } from './uriConverter'; +import { UriConverter } from '../utils/uriConverter'; import { getFixAllResponse } from './fixAllCodeAction'; export function registerNestedCodeActionCommands( diff --git a/src/lsptoolshost/dotnetRuntimeExtensionApi.ts b/src/lsptoolshost/dotnetRuntime/dotnetRuntimeExtensionApi.ts similarity index 100% rename from src/lsptoolshost/dotnetRuntimeExtensionApi.ts rename to src/lsptoolshost/dotnetRuntime/dotnetRuntimeExtensionApi.ts diff --git a/src/lsptoolshost/dotnetRuntimeExtensionResolver.ts b/src/lsptoolshost/dotnetRuntime/dotnetRuntimeExtensionResolver.ts similarity index 95% rename from src/lsptoolshost/dotnetRuntimeExtensionResolver.ts rename to src/lsptoolshost/dotnetRuntime/dotnetRuntimeExtensionResolver.ts index a46c94f21d..a72b243901 100644 --- a/src/lsptoolshost/dotnetRuntimeExtensionResolver.ts +++ b/src/lsptoolshost/dotnetRuntime/dotnetRuntimeExtensionResolver.ts @@ -5,12 +5,12 @@ import * as path from 'path'; import * as vscode from 'vscode'; -import { HostExecutableInformation } from '../shared/constants/hostExecutableInformation'; -import { IHostExecutableResolver } from '../shared/constants/IHostExecutableResolver'; -import { PlatformInformation } from '../shared/platform'; -import { languageServerOptions } from '../shared/options'; +import { HostExecutableInformation } from '../../shared/constants/hostExecutableInformation'; +import { IHostExecutableResolver } from '../../shared/constants/IHostExecutableResolver'; +import { PlatformInformation } from '../../shared/platform'; +import { languageServerOptions } from '../../shared/options'; import { existsSync } from 'fs'; -import { CSharpExtensionId } from '../constants/csharpExtensionId'; +import { CSharpExtensionId } from '../../constants/csharpExtensionId'; import { readFile } from 'fs/promises'; import { IDotnetAcquireResult, IDotnetFindPathContext } from './dotnetRuntimeExtensionApi'; diff --git a/src/lsptoolshost/builtInComponents.ts b/src/lsptoolshost/extensions/builtInComponents.ts similarity index 97% rename from src/lsptoolshost/builtInComponents.ts rename to src/lsptoolshost/extensions/builtInComponents.ts index 4fa3007ccd..e0a96218bf 100644 --- a/src/lsptoolshost/builtInComponents.ts +++ b/src/lsptoolshost/extensions/builtInComponents.ts @@ -5,7 +5,7 @@ import * as fs from 'fs'; import * as path from 'path'; -import { LanguageServerOptions } from '../shared/options'; +import { LanguageServerOptions } from '../../shared/options'; interface ComponentInfo { defaultFolderName: string; diff --git a/src/lsptoolshost/roslynLanguageServerExportChannel.ts b/src/lsptoolshost/extensions/roslynLanguageServerExportChannel.ts similarity index 94% rename from src/lsptoolshost/roslynLanguageServerExportChannel.ts rename to src/lsptoolshost/extensions/roslynLanguageServerExportChannel.ts index be5372881f..992a7d232c 100644 --- a/src/lsptoolshost/roslynLanguageServerExportChannel.ts +++ b/src/lsptoolshost/extensions/roslynLanguageServerExportChannel.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; import { RequestType } from 'vscode-languageclient/node'; -import { RoslynLanguageServer } from './roslynLanguageServer'; +import { RoslynLanguageServer } from '../server/roslynLanguageServer'; export class RoslynLanguageServerExport { constructor(private _serverInitialized: Promise) {} diff --git a/src/lsptoolshost/sourceGeneratedFilesContentProvider.ts b/src/lsptoolshost/generators/sourceGeneratedFilesContentProvider.ts similarity index 95% rename from src/lsptoolshost/sourceGeneratedFilesContentProvider.ts rename to src/lsptoolshost/generators/sourceGeneratedFilesContentProvider.ts index 1e31e78c8d..fadd2d86b4 100644 --- a/src/lsptoolshost/sourceGeneratedFilesContentProvider.ts +++ b/src/lsptoolshost/generators/sourceGeneratedFilesContentProvider.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import * as RoslynProtocol from './roslynProtocol'; -import { RoslynLanguageServer } from './roslynLanguageServer'; -import { UriConverter } from './uriConverter'; +import * as RoslynProtocol from '../server/roslynProtocol'; +import { RoslynLanguageServer } from '../server/roslynLanguageServer'; import * as lsp from 'vscode-languageserver-protocol'; import { IDisposable } from '@microsoft/servicehub-framework'; +import { UriConverter } from '../utils/uriConverter'; export function registerSourceGeneratedFilesContentProvider( context: vscode.ExtensionContext, diff --git a/src/lsptoolshost/showToastNotification.ts b/src/lsptoolshost/handlers/showToastNotification.ts similarity index 88% rename from src/lsptoolshost/showToastNotification.ts rename to src/lsptoolshost/handlers/showToastNotification.ts index f2e2c972cc..3defcbe669 100644 --- a/src/lsptoolshost/showToastNotification.ts +++ b/src/lsptoolshost/handlers/showToastNotification.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { RoslynLanguageClient } from './roslynLanguageClient'; +import { RoslynLanguageClient } from '../server/roslynLanguageClient'; import { MessageType } from 'vscode-languageserver-protocol'; -import { ShowToastNotification } from './roslynProtocol'; -import { showErrorMessage, showInformationMessage, showWarningMessage } from '../shared/observers/utils/showMessage'; +import { ShowToastNotification } from '../server/roslynProtocol'; +import { showErrorMessage, showInformationMessage, showWarningMessage } from '../../shared/observers/utils/showMessage'; export function registerShowToastNotification(client: RoslynLanguageClient) { client.onNotification(ShowToastNotification.type, async (notification) => { diff --git a/src/lsptoolshost/languageStatusBar.ts b/src/lsptoolshost/languageStatusBar.ts deleted file mode 100644 index d0656e47fc..0000000000 --- a/src/lsptoolshost/languageStatusBar.ts +++ /dev/null @@ -1,99 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as vscode from 'vscode'; -import { RoslynLanguageServer } from './roslynLanguageServer'; -import { RoslynLanguageServerEvents } from './languageServerEvents'; -import { languageServerOptions } from '../shared/options'; -import { ServerState } from './serverStateChange'; -import { getCSharpDevKit } from '../utils/getCSharpDevKit'; -import { RazorLanguage } from '../razor/src/razorLanguage'; - -export function registerLanguageStatusItems( - context: vscode.ExtensionContext, - languageServer: RoslynLanguageServer, - languageServerEvents: RoslynLanguageServerEvents -) { - // DevKit will provide an equivalent workspace status item. - if (!getCSharpDevKit()) { - WorkspaceStatus.createStatusItem(context, languageServerEvents); - } - ProjectContextStatus.createStatusItem(context, languageServer); -} - -function combineDocumentSelectors(...selectors: vscode.DocumentSelector[]): vscode.DocumentSelector { - return selectors.reduce<(string | vscode.DocumentFilter)[]>((acc, selector) => acc.concat(selector), []); -} - -class WorkspaceStatus { - static createStatusItem(context: vscode.ExtensionContext, languageServerEvents: RoslynLanguageServerEvents) { - const documentSelector = combineDocumentSelectors(languageServerOptions.documentSelector); - const openSolutionCommand = { - command: 'dotnet.openSolution', - title: vscode.l10n.t('Open solution'), - }; - const restartServerCommand = { - command: 'dotnet.restartServer', - title: vscode.l10n.t('Restart server'), - }; - - const item = vscode.languages.createLanguageStatusItem('csharp.workspaceStatus', documentSelector); - item.name = vscode.l10n.t('C# Workspace Status'); - item.severity = vscode.LanguageStatusSeverity.Error; - item.command = openSolutionCommand; - context.subscriptions.push(item); - - languageServerEvents.onServerStateChange((e) => { - item.text = e.workspaceLabel; - item.busy = e.state === ServerState.ProjectInitializationStarted; - item.severity = - e.state === ServerState.Stopped - ? vscode.LanguageStatusSeverity.Error - : vscode.LanguageStatusSeverity.Information; - item.command = e.state === ServerState.Stopped ? restartServerCommand : openSolutionCommand; - }); - } -} - -class ProjectContextStatus { - static createStatusItem(context: vscode.ExtensionContext, languageServer: RoslynLanguageServer) { - const documentSelector = combineDocumentSelectors( - languageServerOptions.documentSelector, - RazorLanguage.documentSelector - ); - const projectContextService = languageServer._projectContextService; - - const item = vscode.languages.createLanguageStatusItem('csharp.projectContextStatus', documentSelector); - item.name = vscode.l10n.t('C# Project Context Status'); - item.detail = vscode.l10n.t('Active File Context'); - context.subscriptions.push(item); - - projectContextService.onActiveFileContextChanged((e) => { - item.text = e.context._vs_label; - - // Show a warning when the active file is part of the Miscellaneous File workspace and - // project initialization is complete. - if (languageServer.state === ServerState.ProjectInitializationComplete) { - item.severity = - e.context._vs_is_miscellaneous && e.isVerified - ? vscode.LanguageStatusSeverity.Warning - : vscode.LanguageStatusSeverity.Information; - } else { - item.severity = vscode.LanguageStatusSeverity.Information; - } - - item.detail = e.context._vs_is_miscellaneous - ? vscode.l10n.t( - 'The active document is not part of the open workspace. Not all language features will be available.' - ) - : vscode.l10n.t('Active File Context'); - }); - - // Trigger a refresh, but don't block creation on the refresh completing. - projectContextService.refresh().catch((e) => { - throw new Error(`Error refreshing project context status ${e}`); - }); - } -} diff --git a/src/lsptoolshost/configurationMiddleware.ts b/src/lsptoolshost/options/configurationMiddleware.ts similarity index 100% rename from src/lsptoolshost/configurationMiddleware.ts rename to src/lsptoolshost/options/configurationMiddleware.ts diff --git a/src/lsptoolshost/optionChanges.ts b/src/lsptoolshost/options/optionChanges.ts similarity index 91% rename from src/lsptoolshost/optionChanges.ts rename to src/lsptoolshost/options/optionChanges.ts index 4a66b22286..0bc5f878d4 100644 --- a/src/lsptoolshost/optionChanges.ts +++ b/src/lsptoolshost/options/optionChanges.ts @@ -5,10 +5,10 @@ import * as vscode from 'vscode'; import { Observable } from 'rxjs'; -import { CommonOptionsThatTriggerReload, LanguageServerOptionsThatTriggerReload } from '../shared/options'; -import { HandleOptionChanges, OptionChangeObserver, OptionChanges } from '../shared/observers/optionChangeObserver'; -import Disposable from '../disposable'; -import { CommandOption, showInformationMessage } from '../shared/observers/utils/showMessage'; +import { CommonOptionsThatTriggerReload, LanguageServerOptionsThatTriggerReload } from '../../shared/options'; +import { HandleOptionChanges, OptionChangeObserver, OptionChanges } from '../../shared/observers/optionChangeObserver'; +import Disposable from '../../disposable'; +import { CommandOption, showInformationMessage } from '../../shared/observers/utils/showMessage'; export function registerLanguageServerOptionChanges(optionObservable: Observable): Disposable { const optionChangeObserver: OptionChangeObserver = { diff --git a/src/lsptoolshost/optionNameConverter.ts b/src/lsptoolshost/options/optionNameConverter.ts similarity index 100% rename from src/lsptoolshost/optionNameConverter.ts rename to src/lsptoolshost/options/optionNameConverter.ts diff --git a/src/lsptoolshost/universalEditorConfigProvider.ts b/src/lsptoolshost/options/universalEditorConfigProvider.ts similarity index 74% rename from src/lsptoolshost/universalEditorConfigProvider.ts rename to src/lsptoolshost/options/universalEditorConfigProvider.ts index c65fc57e56..45ca26cf55 100644 --- a/src/lsptoolshost/universalEditorConfigProvider.ts +++ b/src/lsptoolshost/options/universalEditorConfigProvider.ts @@ -29,11 +29,11 @@ export function readEquivalentVsCodeConfiguration(serverSideOptionName: string): } function readTabSize(configuration: WorkspaceConfiguration): string { - return readValueIfDetectIndentationIsOff(configuration, 'editor.tabSize', '4'); + return readVsCodeConfigurations(configuration, 'editor.tabSize'); } function readIndentSize(configuration: WorkspaceConfiguration): string { - const indentSize = readValueIfDetectIndentationIsOff(configuration, 'editor.indentSize', '4'); + const indentSize = readVsCodeConfigurations(configuration, 'editor.indentSize'); // indent size could be a number or 'tabSize'. If it is 'tabSize', read the 'tabSize' section from config. if (indentSize == 'tabSize') { return readTabSize(configuration); @@ -43,7 +43,7 @@ function readIndentSize(configuration: WorkspaceConfiguration): string { } function readInsertSpaces(configuration: WorkspaceConfiguration): string { - const insertSpace = readValueIfDetectIndentationIsOff(configuration, 'editor.insertSpaces', true); + const insertSpace = readVsCodeConfigurations(configuration, 'editor.insertSpaces'); return insertSpace ? 'space' : 'tab'; } @@ -62,21 +62,6 @@ function readInsertFinalNewline(configuration: WorkspaceConfiguration): string { return readVsCodeConfigurations(configuration, 'files.insertFinalNewline'); } -function readValueIfDetectIndentationIsOff( - configuration: WorkspaceConfiguration, - vscodeConfigName: string, - defaultValue: T -): T { - // If detectIndentation is on, tabSize, indentSize and insertSpaces would be overridden by vscode based on the file's content. - // The values in settings become meaningless, so ask the server to fall back to default value. - // TODO: Both 'editor.detectIndentation' and.editorconfig provided the same functions here, we need to find a graceful way to handle them. - if (configuration.get('editor.detectIndentation')) { - return defaultValue; - } - - return readVsCodeConfigurations(configuration, vscodeConfigName); -} - function readVsCodeConfigurations(configuration: WorkspaceConfiguration, vscodeConfigName: string): T { const configValue = configuration.get(vscodeConfigName); if (configValue === undefined) { diff --git a/src/lsptoolshost/profiling/profiling.ts b/src/lsptoolshost/profiling/profiling.ts new file mode 100644 index 0000000000..dd5ac0ff48 --- /dev/null +++ b/src/lsptoolshost/profiling/profiling.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { EOL } from 'os'; +import { LogOutputChannel } from 'vscode'; + +export function getProfilingEnvVars(outputChannel: LogOutputChannel): NodeJS.ProcessEnv { + let profilingEnvVars = {}; + if (process.env.ROSLYN_DOTNET_EventPipeOutputPath) { + profilingEnvVars = { + DOTNET_EnableEventPipe: 1, + DOTNET_EventPipeConfig: 'Microsoft-Windows-DotNETRuntime:0x1F000080018:5', + DOTNET_EventPipeOutputPath: process.env.ROSLYN_DOTNET_EventPipeOutputPath, + DOTNET_ReadyToRun: 0, + DOTNET_TieredCompilation: 1, + DOTNET_TC_CallCounting: 0, + DOTNET_TC_QuickJitForLoops: 1, + DOTNET_JitCollect64BitCounts: 1, + }; + outputChannel.trace(`Profiling enabled with:${EOL}${JSON.stringify(profilingEnvVars)}`); + } + + return profilingEnvVars; +} diff --git a/src/lsptoolshost/services/projectContextService.ts b/src/lsptoolshost/projectContext/projectContextService.ts similarity index 94% rename from src/lsptoolshost/services/projectContextService.ts rename to src/lsptoolshost/projectContext/projectContextService.ts index 8dd9589b71..33e120f186 100644 --- a/src/lsptoolshost/services/projectContextService.ts +++ b/src/lsptoolshost/projectContext/projectContextService.ts @@ -4,12 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { RoslynLanguageServer } from '../roslynLanguageServer'; -import { VSGetProjectContextsRequest, VSProjectContext, VSProjectContextList } from '../roslynProtocol'; +import { RoslynLanguageServer } from '../server/roslynLanguageServer'; +import { VSGetProjectContextsRequest, VSProjectContext, VSProjectContextList } from '../server/roslynProtocol'; import { TextDocumentIdentifier } from 'vscode-languageserver-protocol'; -import { UriConverter } from '../uriConverter'; -import { LanguageServerEvents } from '../languageServerEvents'; -import { ServerState } from '../serverStateChange'; +import { UriConverter } from '../utils/uriConverter'; +import { LanguageServerEvents, ServerState } from '../server/languageServerEvents'; export interface ProjectContextChangeEvent { languageId: string; diff --git a/src/lsptoolshost/projectContext/projectContextStatus.ts b/src/lsptoolshost/projectContext/projectContextStatus.ts new file mode 100644 index 0000000000..7dcfd43b33 --- /dev/null +++ b/src/lsptoolshost/projectContext/projectContextStatus.ts @@ -0,0 +1,52 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { RoslynLanguageServer } from '../server/roslynLanguageServer'; +import { languageServerOptions } from '../../shared/options'; +import { RazorLanguage } from '../../razor/src/razorLanguage'; +import { ServerState } from '../server/languageServerEvents'; +import { combineDocumentSelectors } from '../utils/combineDocumentSelectors'; + +export class ProjectContextStatus { + static createStatusItem(context: vscode.ExtensionContext, languageServer: RoslynLanguageServer) { + const documentSelector = combineDocumentSelectors( + languageServerOptions.documentSelector, + RazorLanguage.documentSelector + ); + const projectContextService = languageServer._projectContextService; + + const item = vscode.languages.createLanguageStatusItem('csharp.projectContextStatus', documentSelector); + item.name = vscode.l10n.t('C# Project Context Status'); + item.detail = vscode.l10n.t('Active File Context'); + context.subscriptions.push(item); + + projectContextService.onActiveFileContextChanged((e) => { + item.text = e.context._vs_label; + + // Show a warning when the active file is part of the Miscellaneous File workspace and + // project initialization is complete. + if (languageServer.state === ServerState.ProjectInitializationComplete) { + item.severity = + e.context._vs_is_miscellaneous && e.isVerified + ? vscode.LanguageStatusSeverity.Warning + : vscode.LanguageStatusSeverity.Information; + } else { + item.severity = vscode.LanguageStatusSeverity.Information; + } + + item.detail = e.context._vs_is_miscellaneous + ? vscode.l10n.t( + 'The active document is not part of the open workspace. Not all language features will be available.' + ) + : vscode.l10n.t('Active File Context'); + }); + + // Trigger a refresh, but don't block creation on the refresh completing. + projectContextService.refresh().catch((e) => { + throw new Error(`Error refreshing project context status ${e}`); + }); + } +} diff --git a/src/lsptoolshost/restore.ts b/src/lsptoolshost/projectRestore/restore.ts similarity index 94% rename from src/lsptoolshost/restore.ts rename to src/lsptoolshost/projectRestore/restore.ts index 83f035707b..3b9e6c6c29 100644 --- a/src/lsptoolshost/restore.ts +++ b/src/lsptoolshost/projectRestore/restore.ts @@ -4,17 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { RoslynLanguageServer } from './roslynLanguageServer'; +import { RoslynLanguageServer } from '../server/roslynLanguageServer'; import { RestorableProjects, RestoreParams, RestorePartialResult, RestoreRequest, ProjectNeedsRestoreRequest, -} from './roslynProtocol'; +} from '../server/roslynProtocol'; import path from 'path'; -import { showErrorMessage } from '../shared/observers/utils/showMessage'; -import { getCSharpDevKit } from '../utils/getCSharpDevKit'; +import { showErrorMessage } from '../../shared/observers/utils/showMessage'; +import { getCSharpDevKit } from '../../utils/getCSharpDevKit'; let _restoreInProgress = false; diff --git a/src/lsptoolshost/razorCommands.ts b/src/lsptoolshost/razor/razorCommands.ts similarity index 96% rename from src/lsptoolshost/razorCommands.ts rename to src/lsptoolshost/razor/razorCommands.ts index 58416f606e..df4f4013ef 100644 --- a/src/lsptoolshost/razorCommands.ts +++ b/src/lsptoolshost/razor/razorCommands.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { RoslynLanguageServer } from './roslynLanguageServer'; +import { RoslynLanguageServer } from '../server/roslynLanguageServer'; import * as vscode from 'vscode'; import { DidChangeTextDocumentNotification, @@ -28,9 +28,9 @@ import { InlayHintParams, InlayHintRequest, } from 'vscode-languageclient/node'; -import SerializableSimplifyMethodParams from '../razor/src/simplify/serializableSimplifyMethodParams'; +import SerializableSimplifyMethodParams from '../../razor/src/simplify/serializableSimplifyMethodParams'; import { TextEdit } from 'vscode-html-languageservice'; -import { SerializableFormatNewFileParams } from '../razor/src/formatNewFile/serializableFormatNewFileParams'; +import { SerializableFormatNewFileParams } from '../../razor/src/formatNewFile/serializableFormatNewFileParams'; // These are commands that are invoked by the Razor extension, and are used to send LSP requests to the Roslyn LSP server export const roslynDidChangeCommand = 'roslyn.changeRazorCSharp'; diff --git a/src/lsptoolshost/languageServerEvents.ts b/src/lsptoolshost/server/languageServerEvents.ts similarity index 82% rename from src/lsptoolshost/languageServerEvents.ts rename to src/lsptoolshost/server/languageServerEvents.ts index 1cd623921c..3aa9756ef3 100644 --- a/src/lsptoolshost/languageServerEvents.ts +++ b/src/lsptoolshost/server/languageServerEvents.ts @@ -4,8 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { ServerStateChangeEvent } from './serverStateChange'; -import { IDisposable } from '../disposable'; +import { IDisposable } from '../../disposable'; + +export enum ServerState { + Stopped = 0, + Started = 1, + ProjectInitializationStarted = 2, + ProjectInitializationComplete = 3, +} + +export interface ServerStateChangeEvent { + state: ServerState; + workspaceLabel: string; +} /** * Defines events that are fired by the language server. diff --git a/src/lsptoolshost/roslynLanguageClient.ts b/src/lsptoolshost/server/roslynLanguageClient.ts similarity index 93% rename from src/lsptoolshost/roslynLanguageClient.ts rename to src/lsptoolshost/server/roslynLanguageClient.ts index 5dad3ca0aa..47266aedfd 100644 --- a/src/lsptoolshost/roslynLanguageClient.ts +++ b/src/lsptoolshost/server/roslynLanguageClient.ts @@ -10,9 +10,9 @@ import { MessageSignature, ServerOptions, } from 'vscode-languageclient/node'; -import CompositeDisposable from '../compositeDisposable'; -import { IDisposable } from '../disposable'; -import { languageServerOptions } from '../shared/options'; +import CompositeDisposable from '../../compositeDisposable'; +import { IDisposable } from '../../disposable'; +import { languageServerOptions } from '../../shared/options'; /** * Implementation of the base LanguageClient type that allows for additional items to be disposed of diff --git a/src/lsptoolshost/roslynLanguageServer.ts b/src/lsptoolshost/server/roslynLanguageServer.ts similarity index 80% rename from src/lsptoolshost/roslynLanguageServer.ts rename to src/lsptoolshost/server/roslynLanguageServer.ts index 9ab84c1b22..b29c9d73cd 100644 --- a/src/lsptoolshost/roslynLanguageServer.ts +++ b/src/lsptoolshost/server/roslynLanguageServer.ts @@ -4,15 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import * as fs from 'fs'; import * as path from 'path'; import * as cp from 'child_process'; import * as uuid from 'uuid'; import * as net from 'net'; -import { registerCommands } from './commands'; -import { registerDebugger } from './debugger'; -import { UriConverter } from './uriConverter'; - import { LanguageClientOptions, ServerOptions, @@ -31,57 +26,49 @@ import { ResponseError, NotificationHandler0, } from 'vscode-languageclient/node'; -import { PlatformInformation } from '../shared/platform'; -import { readConfigurations } from './configurationMiddleware'; -import { DynamicFileInfoHandler } from '../razor/src/dynamicFile/dynamicFileInfoHandler'; +import { PlatformInformation } from '../../shared/platform'; +import { readConfigurations } from '../options/configurationMiddleware'; +import { DynamicFileInfoHandler } from '../../razor/src/dynamicFile/dynamicFileInfoHandler'; import * as RoslynProtocol from './roslynProtocol'; -import { CSharpDevKitExports } from '../csharpDevKitExports'; -import { SolutionSnapshotId } from './services/ISolutionSnapshotProvider'; -import { ServerState } from './serverStateChange'; +import { CSharpDevKitExports } from '../../csharpDevKitExports'; +import { SolutionSnapshotId } from '../solutionSnapshot/ISolutionSnapshotProvider'; import TelemetryReporter from '@vscode/extension-telemetry'; -import CSharpIntelliCodeExports from '../csharpIntelliCodeExports'; -import { csharpDevkitExtensionId, csharpDevkitIntelliCodeExtensionId, getCSharpDevKit } from '../utils/getCSharpDevKit'; +import CSharpIntelliCodeExports from '../../csharpIntelliCodeExports'; +import { + csharpDevkitExtensionId, + csharpDevkitIntelliCodeExtensionId, + getCSharpDevKit, +} from '../../utils/getCSharpDevKit'; import { randomUUID } from 'crypto'; -import { DotnetRuntimeExtensionResolver } from './dotnetRuntimeExtensionResolver'; -import { IHostExecutableResolver } from '../shared/constants/IHostExecutableResolver'; +import { IHostExecutableResolver } from '../../shared/constants/IHostExecutableResolver'; import { RoslynLanguageClient } from './roslynLanguageClient'; -import { registerUnitTestingCommands } from './unitTesting'; -import { reportProjectConfigurationEvent } from '../shared/projectConfiguration'; -import { getDotnetInfo } from '../shared/utils/getDotnetInfo'; -import { registerLanguageServerOptionChanges } from './optionChanges'; -import { Observable } from 'rxjs'; -import { DotnetInfo } from '../shared/utils/dotnetInfo'; -import { RoslynLanguageServerEvents } from './languageServerEvents'; -import { registerShowToastNotification } from './showToastNotification'; -import { registerRazorCommands } from './razorCommands'; -import { registerOnAutoInsert } from './onAutoInsert'; -import { registerCodeActionFixAllCommands } from './fixAllCodeAction'; -import { commonOptions, languageServerOptions, omnisharpOptions, razorOptions } from '../shared/options'; +import { reportProjectConfigurationEvent } from '../../shared/projectConfiguration'; +import { getDotnetInfo } from '../../shared/utils/getDotnetInfo'; +import { DotnetInfo } from '../../shared/utils/dotnetInfo'; +import { RoslynLanguageServerEvents, ServerState } from './languageServerEvents'; +import { registerShowToastNotification } from '../handlers/showToastNotification'; +import { registerOnAutoInsert } from '../autoInsert/onAutoInsert'; +import { commonOptions, languageServerOptions, omnisharpOptions, razorOptions } from '../../shared/options'; import { NamedPipeInformation } from './roslynProtocol'; -import { IDisposable } from '../disposable'; -import { registerNestedCodeActionCommands } from './nestedCodeAction'; -import { registerRestoreCommands } from './restore'; -import { BuildDiagnosticsService } from './buildDiagnosticsService'; -import { getComponentPaths } from './builtInComponents'; -import { OnAutoInsertFeature } from './onAutoInsertFeature'; -import { registerLanguageStatusItems } from './languageStatusBar'; -import { ProjectContextService } from './services/projectContextService'; -import { ProvideDynamicFileResponse } from '../razor/src/dynamicFile/provideDynamicFileResponse'; -import { ProvideDynamicFileParams } from '../razor/src/dynamicFile/provideDynamicFileParams'; -import { registerCopilotExtension } from './copilot'; +import { IDisposable } from '../../disposable'; +import { BuildDiagnosticsService } from '../diagnostics/buildDiagnosticsService'; +import { getComponentPaths } from '../extensions/builtInComponents'; +import { OnAutoInsertFeature } from '../autoInsert/onAutoInsertFeature'; +import { ProjectContextService } from '../projectContext/projectContextService'; +import { ProvideDynamicFileResponse } from '../../razor/src/dynamicFile/provideDynamicFileResponse'; +import { ProvideDynamicFileParams } from '../../razor/src/dynamicFile/provideDynamicFileParams'; import { ActionOption, CommandOption, showErrorMessage, showInformationMessage, -} from '../shared/observers/utils/showMessage'; -import { registerSourceGeneratedFilesContentProvider } from './sourceGeneratedFilesContentProvider'; -import { registerMiscellaneousFileNotifier } from './miscellaneousFileNotifier'; -import { TelemetryEventNames } from '../shared/telemetryEventNames'; -import { RazorDynamicFileChangedParams } from '../razor/src/dynamicFile/dynamicFileUpdatedParams'; - -let _channel: vscode.LogOutputChannel; -let _traceChannel: vscode.OutputChannel; +} from '../../shared/observers/utils/showMessage'; +import { TelemetryEventNames } from '../../shared/telemetryEventNames'; +import { RazorDynamicFileChangedParams } from '../../razor/src/dynamicFile/dynamicFileUpdatedParams'; +import { getProfilingEnvVars } from '../profiling/profiling'; +import { isString } from '../utils/isString'; +import { getServerPath } from '../activate'; +import { UriConverter } from '../utils/uriConverter'; // Flag indicating if C# Devkit was installed the last time we activated. // Used to determine if we need to restart the server on extension changes. @@ -129,7 +116,8 @@ export class RoslynLanguageServer { private _platformInfo: PlatformInformation, private _context: vscode.ExtensionContext, private _telemetryReporter: TelemetryReporter, - private _languageServerEvents: RoslynLanguageServerEvents + private _languageServerEvents: RoslynLanguageServerEvents, + private _channel: vscode.LogOutputChannel ) { this.registerSetTrace(); this.registerSendOpenSolution(); @@ -171,17 +159,17 @@ export class RoslynLanguageServer { } }); // Register for changes to the log level. - _channel.onDidChangeLogLevel(async () => { + this._channel.onDidChangeLogLevel(async () => { await this.updateLogLevel(); }); } private async updateLogLevel(): Promise { if (this._languageClient.state === State.Running) { - const languageClientTraceLevel = RoslynLanguageServer.GetTraceLevel(_channel.logLevel); + const languageClientTraceLevel = RoslynLanguageServer.GetTraceLevel(this._channel.logLevel); // Update the server's log level. await this.sendNotification('roslyn/updateLogLevel', { - logLevel: RoslynLanguageServer.GetServerLogLevel(_channel.logLevel), + logLevel: RoslynLanguageServer.GetServerLogLevel(this._channel.logLevel), }); // Update the trace level that the client uses to log trace messages. await this._languageClient.setTrace(languageClientTraceLevel); @@ -260,7 +248,9 @@ export class RoslynLanguageServer { context: vscode.ExtensionContext, telemetryReporter: TelemetryReporter, additionalExtensionPaths: string[], - languageServerEvents: RoslynLanguageServerEvents + languageServerEvents: RoslynLanguageServerEvents, + channel: vscode.LogOutputChannel, + traceChannel: vscode.OutputChannel ): Promise { const serverOptions: ServerOptions = async () => { return await this.startServer( @@ -268,7 +258,8 @@ export class RoslynLanguageServer { hostExecutableResolver, context, telemetryReporter, - additionalExtensionPaths + additionalExtensionPaths, + channel ); }; @@ -281,8 +272,8 @@ export class RoslynLanguageServer { synchronize: { fileEvents: [], }, - traceOutputChannel: _traceChannel, - outputChannel: _channel, + traceOutputChannel: traceChannel, + outputChannel: channel, uriConverters: { // VSCode encodes the ":" as "%3A" in file paths, for example "file:///c%3A/Users/dabarbet/source/repos/ConsoleApp8/ConsoleApp8/Program.cs". // System.Uri does not decode the LocalPath property correctly into a valid windows path, instead you get something like @@ -309,7 +300,14 @@ export class RoslynLanguageServer { client.registerProposedFeatures(); - const server = new RoslynLanguageServer(client, platformInfo, context, telemetryReporter, languageServerEvents); + const server = new RoslynLanguageServer( + client, + platformInfo, + context, + telemetryReporter, + languageServerEvents, + channel + ); client.registerFeature(server._onAutoInsertFeature); @@ -488,7 +486,7 @@ export class RoslynLanguageServer { } if (!(error instanceof vscode.CancellationError)) { - _channel.error(`Error making ${request} request`, error); + this._channel.error(`Error making ${request} request`, error); } return error; } @@ -579,14 +577,15 @@ export class RoslynLanguageServer { hostExecutableResolver: IHostExecutableResolver, context: vscode.ExtensionContext, telemetryReporter: TelemetryReporter, - additionalExtensionPaths: string[] + additionalExtensionPaths: string[], + channel: vscode.LogOutputChannel ): Promise { telemetryReporter.sendTelemetryEvent(TelemetryEventNames.ClientServerStart); const serverPath = getServerPath(platformInfo); const dotnetInfo = await hostExecutableResolver.getHostExecutableInfo(); const dotnetExecutablePath = dotnetInfo.path; - _channel.info('Dotnet path: ' + dotnetExecutablePath); + channel.info('Dotnet path: ' + dotnetExecutablePath); telemetryReporter.sendTelemetryEvent(TelemetryEventNames.AcquiredRuntime); let args: string[] = []; @@ -598,7 +597,7 @@ export class RoslynLanguageServer { // Get the initial log level from the channel. // Changes to the channel log level will be picked up by the server after // LSP finishes initializing and we're able to pick up the new value. - const logLevel = this.GetServerLogLevel(_channel.logLevel); + const logLevel = this.GetServerLogLevel(channel.logLevel); if (logLevel) { args.push('--logLevel', logLevel); } @@ -627,14 +626,15 @@ export class RoslynLanguageServer { csharpDevkitIntelliCodeExtensionId ); if (csharpDevkitIntelliCodeExtension) { - _channel.info('Activating C# + C# Dev Kit + C# IntelliCode...'); + channel.info('Activating C# + C# Dev Kit + C# IntelliCode...'); const csharpDevkitIntelliCodeArgs = await this.getCSharpDevkitIntelliCodeExportArgs( csharpDevkitIntelliCodeExtension, - context + context, + channel ); args = args.concat(csharpDevkitIntelliCodeArgs); } else { - _channel.info('Activating C# + C# Dev Kit...'); + channel.info('Activating C# + C# Dev Kit...'); } // Set command enablement as soon as we know devkit is available. @@ -646,7 +646,7 @@ export class RoslynLanguageServer { await this.setupDevKitEnvironment(dotnetInfo.env, csharpDevkitExtension); } else { // C# Dev Kit is not installed - continue C#-only activation. - _channel.info('Activating C# standalone...'); + channel.info('Activating C# standalone...'); // Set command enablement to use roslyn standalone commands. await vscode.commands.executeCommand('setContext', 'dotnet.server.activationContext', 'Roslyn'); @@ -657,37 +657,42 @@ export class RoslynLanguageServer { args.push('--extension', extensionPath); } - _channel.debug(`Starting server at ${serverPath}`); + channel.debug(`Starting server at ${serverPath}`); // shouldn't this arg only be set if it's running with CSDevKit? args.push('--telemetryLevel', telemetryReporter.telemetryLevel); args.push('--extensionLogDirectory', context.logUri.fsPath); - const env = dotnetInfo.env; + let env = dotnetInfo.env; if (!languageServerOptions.useServerGC) { // The server by default uses serverGC, if the user opts out we need to set the environment variable to disable it. env.DOTNET_gcServer = '0'; - _channel.debug('ServerGC disabled'); + channel.debug('ServerGC disabled'); } + const profilingEnvVars = getProfilingEnvVars(channel); + env = { ...env, ...profilingEnvVars }; + + channel.trace(`Profiling environment variables: ${JSON.stringify(profilingEnvVars)}`); + let childProcess: cp.ChildProcessWithoutNullStreams; const cpOptions: cp.SpawnOptionsWithoutStdio = { detached: true, windowsHide: true, - env: dotnetInfo.env, + env: env, }; if (serverPath.endsWith('.dll')) { // If we were given a path to a dll, launch that via dotnet. const argsWithPath = [serverPath].concat(args); - _channel.debug(`Server arguments ${argsWithPath.join(' ')}`); + channel.debug(`Server arguments ${argsWithPath.join(' ')}`); childProcess = cp.spawn(dotnetExecutablePath, argsWithPath, cpOptions); } else { // Otherwise assume we were given a path to an executable. - _channel.debug(`Server arguments ${args.join(' ')}`); + channel.debug(`Server arguments ${args.join(' ')}`); childProcess = cp.spawn(serverPath, args, cpOptions); } @@ -697,14 +702,14 @@ export class RoslynLanguageServer { // Record the stdout and stderr streams from the server process. childProcess.stdout.on('data', (data: { toString: (arg0: any) => any }) => { const result: string = isString(data) ? data : data.toString(RoslynLanguageServer.encoding); - _channel.info('[stdout] ' + result); + channel.info('[stdout] ' + result); }); childProcess.stderr.on('data', (data: { toString: (arg0: any) => any }) => { const result: string = isString(data) ? data : data.toString(RoslynLanguageServer.encoding); - _channel.error('[stderr] ' + result); + channel.error('[stderr] ' + result); }); childProcess.on('exit', (code) => { - _channel.info(`Language server process exited with ${code}`); + channel.info(`Language server process exited with ${code}`); }); // Timeout promise used to time out the connection process if it takes too long. @@ -724,14 +729,14 @@ export class RoslynLanguageServer { // The server process will create the named pipe used for communication. Wait for it to be created, // and listen for the server to pass back the connection information via stdout. const namedPipePromise = new Promise((resolve) => { - _channel.debug('waiting for named pipe information from server...'); + channel.debug('waiting for named pipe information from server...'); childProcess.stdout.on('data', (data: { toString: (arg0: any) => any }) => { const result: string = isString(data) ? data : data.toString(RoslynLanguageServer.encoding); // Use the regular expression to find all JSON lines const jsonLines = result.match(RoslynLanguageServer.namedPipeKeyRegex); if (jsonLines) { const transmittedPipeNameInfo: NamedPipeInformation = JSON.parse(jsonLines[0]); - _channel.info('received named pipe information from server'); + channel.info('received named pipe information from server'); resolve(transmittedPipeNameInfo); } }); @@ -739,9 +744,9 @@ export class RoslynLanguageServer { const socketPromise = namedPipePromise.then(async (pipeConnectionInfo) => { return new Promise((resolve, reject) => { - _channel.debug('attempting to connect client to server...'); + channel.debug('attempting to connect client to server...'); const socket = net.createConnection(pipeConnectionInfo.pipeName, () => { - _channel.info('client has connected to server'); + channel.info('client has connected to server'); resolve(socket); }); @@ -799,7 +804,7 @@ export class RoslynLanguageServer { notification ); } else { - _channel.warn('Tried to send razor/dynamicFileInfoChanged while server is not running'); + this._channel.warn('Tried to send razor/dynamicFileInfoChanged while server is not running'); } } ); @@ -911,7 +916,7 @@ export class RoslynLanguageServer { if (csharpDevkitExtension && !_wasActivatedWithCSharpDevkit) { // We previously started without C# Dev Kit and its now installed. // Offer a prompt to restart the server to use C# Dev Kit. - _channel.info(`Detected new installation of ${csharpDevkitExtensionId}`); + this._channel.info(`Detected new installation of ${csharpDevkitExtensionId}`); const message = `Detected installation of ${csharpDevkitExtensionId}. Would you like to relaunch the language server for added features?`; showInformationMessage(vscode, message, title); } else { @@ -961,7 +966,8 @@ export class RoslynLanguageServer { private static async getCSharpDevkitIntelliCodeExportArgs( csharpDevkitIntelliCodeExtension: vscode.Extension, - extensionContext: vscode.ExtensionContext + extensionContext: vscode.ExtensionContext, + channel: vscode.LogOutputChannel ): Promise { try { const exports = await csharpDevkitIntelliCodeExtension.activate(); @@ -974,9 +980,9 @@ export class RoslynLanguageServer { ]; return csharpIntelliCodeArgs; } catch (e) { - _channel.error(`Activation of ${csharpDevkitIntelliCodeExtensionId} failed`, e); + channel.error(`Activation of ${csharpDevkitIntelliCodeExtensionId} failed`, e); if (e instanceof Error && e.stack) { - _channel.info(e.stack); + channel.info(e.stack); } const stateKey = 'disableIntellicodeFailedPopup'; @@ -991,7 +997,7 @@ export class RoslynLanguageServer { const showOutput: ActionOption = { title: vscode.l10n.t('Go to output'), action: async () => { - _channel.show(); + channel.show(); }, }; const suppressNotification: ActionOption = { @@ -1079,134 +1085,6 @@ export class RoslynLanguageServer { } } -/** - * Creates and activates the Roslyn language server. - * The returned promise will complete when the server starts. - */ -export async function activateRoslynLanguageServer( - context: vscode.ExtensionContext, - platformInfo: PlatformInformation, - optionObservable: Observable, - outputChannel: vscode.LogOutputChannel, - reporter: TelemetryReporter, - languageServerEvents: RoslynLanguageServerEvents -): Promise { - // Create a channel for outputting general logs from the language server. - _channel = outputChannel; - // Create a separate channel for outputting trace logs - these are incredibly verbose and make other logs very difficult to see. - // The trace channel verbosity is controlled by the _channel verbosity. - _traceChannel = vscode.window.createOutputChannel(vscode.l10n.t('C# LSP Trace Logs')); - - reporter.sendTelemetryEvent(TelemetryEventNames.ClientInitialize); - - const hostExecutableResolver = new DotnetRuntimeExtensionResolver( - platformInfo, - getServerPath, - outputChannel, - context.extensionPath - ); - const additionalExtensionPaths = scanExtensionPlugins(); - - const languageServer = await RoslynLanguageServer.initializeAsync( - platformInfo, - hostExecutableResolver, - context, - reporter, - additionalExtensionPaths, - languageServerEvents - ); - - registerLanguageStatusItems(context, languageServer, languageServerEvents); - registerMiscellaneousFileNotifier(context, languageServer); - registerCopilotExtension(languageServer, _channel); - - // Register any commands that need to be handled by the extension. - registerCommands(context, languageServer, hostExecutableResolver, _channel); - registerNestedCodeActionCommands(context, languageServer, _channel); - registerCodeActionFixAllCommands(context, languageServer, _channel); - - registerRazorCommands(context, languageServer); - - registerUnitTestingCommands(context, languageServer); - - // Register any needed debugger components that need to communicate with the language server. - registerDebugger(context, languageServer, languageServerEvents, platformInfo, _channel); - - registerRestoreCommands(context, languageServer); - - registerSourceGeneratedFilesContentProvider(context, languageServer); - - context.subscriptions.push(registerLanguageServerOptionChanges(optionObservable)); - - return languageServer; - - function scanExtensionPlugins(): string[] { - const extensionsFromPackageJson = vscode.extensions.all.flatMap((extension) => { - let loadPaths = extension.packageJSON.contributes?.['csharpExtensionLoadPaths']; - if (loadPaths === undefined || loadPaths === null) { - _channel.debug(`Extension ${extension.id} does not contribute csharpExtensionLoadPaths`); - return []; - } - - if (!Array.isArray(loadPaths) || loadPaths.some((loadPath) => typeof loadPath !== 'string')) { - _channel.warn( - `Extension ${extension.id} has invalid csharpExtensionLoadPaths. Expected string array, found ${loadPaths}` - ); - return []; - } - - loadPaths = loadPaths.map((loadPath) => path.join(extension.extensionPath, loadPath)); - _channel.trace(`Extension ${extension.id} contributes csharpExtensionLoadPaths: ${loadPaths}`); - return loadPaths; - }); - const extensionsFromOptions = languageServerOptions.extensionsPaths ?? []; - return extensionsFromPackageJson.concat(extensionsFromOptions); - } -} - -function getServerPath(platformInfo: PlatformInformation) { - let serverPath = process.env.DOTNET_ROSLYN_SERVER_PATH; - - if (serverPath) { - _channel.appendLine(`Using server path override from DOTNET_ROSLYN_SERVER_PATH: ${serverPath}`); - } else { - serverPath = commonOptions.serverPath; - if (!serverPath) { - // Option not set, use the path from the extension. - serverPath = getInstalledServerPath(platformInfo); - } - } - - if (!fs.existsSync(serverPath)) { - throw new Error(`Cannot find language server in path '${serverPath}'`); - } - - return serverPath; -} - -function getInstalledServerPath(platformInfo: PlatformInformation): string { - const clientRoot = __dirname; - const serverFilePath = path.join(clientRoot, '..', '.roslyn', 'Microsoft.CodeAnalysis.LanguageServer'); - - let extension = ''; - if (platformInfo.isWindows()) { - extension = '.exe'; - } else if (platformInfo.isMacOS()) { - // MacOS executables must be signed with codesign. Currently all Roslyn server executables are built on windows - // and therefore dotnet publish does not automatically sign them. - // Tracking bug - https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1767519/ - extension = '.dll'; - } - - let pathWithExtension = `${serverFilePath}${extension}`; - if (!fs.existsSync(pathWithExtension)) { - // We might be running a platform neutral vsix which has no executable, instead we run the dll directly. - pathWithExtension = `${serverFilePath}.dll`; - } - - return pathWithExtension; -} - // VS code will have a default session id when running under tests. Since we may still // report telemetry, we need to give a unique session id instead of the default value. function getSessionId(): string { @@ -1219,7 +1097,3 @@ function getSessionId(): string { return sessionId; } - -export function isString(value: any): value is string { - return typeof value === 'string' || value instanceof String; -} diff --git a/src/lsptoolshost/roslynProtocol.ts b/src/lsptoolshost/server/roslynProtocol.ts similarity index 99% rename from src/lsptoolshost/roslynProtocol.ts rename to src/lsptoolshost/server/roslynProtocol.ts index bea4640c8b..583d3b26ff 100644 --- a/src/lsptoolshost/roslynProtocol.ts +++ b/src/lsptoolshost/server/roslynProtocol.ts @@ -6,7 +6,7 @@ import { Command } from 'vscode'; import * as lsp from 'vscode-languageserver-protocol'; import { CodeAction, TextDocumentRegistrationOptions } from 'vscode-languageserver-protocol'; -import { ProjectConfigurationMessage } from '../shared/projectConfiguration'; +import { ProjectConfigurationMessage } from '../../shared/projectConfiguration'; export interface VSProjectContextList { _vs_projectContexts: VSProjectContext[]; diff --git a/src/lsptoolshost/server/serverCommands.ts b/src/lsptoolshost/server/serverCommands.ts new file mode 100644 index 0000000000..f9637211aa --- /dev/null +++ b/src/lsptoolshost/server/serverCommands.ts @@ -0,0 +1,156 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { RoslynLanguageServer } from '../server/roslynLanguageServer'; +import { UriConverter } from '../utils/uriConverter'; +import * as languageClient from 'vscode-languageclient/node'; + +/** + * Register server -> client commands as well as commands that drive the Roslyn Language Server. + */ +export function registerServerCommands( + context: vscode.ExtensionContext, + languageServer: RoslynLanguageServer, + outputChannel: vscode.LogOutputChannel +) { + // It is very important to be careful about the types used as parameters for these command callbacks. + // If the arguments are coming from the server as json, it is NOT appropriate to use type definitions + // from the normal vscode API (e.g. vscode.Location) as input parameters. + // + // This is because at runtime the json objects do not contain the expected prototypes that the vscode types + // have and will fail 'instanceof' checks that are sprinkled throught the vscode APIs. + // + // Instead, we define inputs from the server using the LSP type definitions as those have no prototypes + // so we don't accidentally pass them directly into vscode APIs. + context.subscriptions.push(vscode.commands.registerCommand('roslyn.client.peekReferences', peekReferencesCallback)); + context.subscriptions.push( + vscode.commands.registerCommand( + 'roslyn.client.completionComplexEdit', + async (textDocument, textEdit, isSnippetString, newOffset) => + completionComplexEdit(textDocument, textEdit, isSnippetString, newOffset, outputChannel) + ) + ); + context.subscriptions.push( + vscode.commands.registerCommand('dotnet.restartServer', async () => restartServer(languageServer)) + ); +} + +/** + * Callback for code lens commands. Executes a references request via the VSCode command + * which will call into the LSP server to get the data. Then calls the VSCode command to display the result. + * @param uriStr The uri containing the location to find references for. + * @param serverPosition The position json object to execute the find references request. + */ +async function peekReferencesCallback(uriStr: string, serverPosition: languageClient.Position): Promise { + const uri = UriConverter.deserialize(uriStr); + + // Convert the json position object into the corresponding vscode position type. + const vscodeApiPosition = new vscode.Position(serverPosition.line, serverPosition.character); + const references: vscode.Location[] = await vscode.commands.executeCommand( + 'vscode.executeReferenceProvider', + uri, + vscodeApiPosition + ); + + if (references && Array.isArray(references)) { + // The references could come back after the document has moved to a new state (that may not even contain the position). + // This is fine - the VSCode API is resilient to that scenario and will not crash. + await vscode.commands.executeCommand('editor.action.showReferences', uri, vscodeApiPosition, references); + } +} + +/** + * Callback after a completion item with complex edit is committed. The change needs to be made outside completion resolve + * handling + * + * IMPORTANT: @see RazorCompletionItemProvider.resolveCompletionItem matches the arguments for this commands + * so it can remap correctly in razor files. Any updates to this function signature requires updates there as well. + * + * @param uriStr The uri containing the location of the document where the completion item was committed in. + * @param textEdits The additional complex edit for the committed completion item. + * @param isSnippetString Indicates if the TextEdit contains a snippet string. + * @param newPosition The offset for new cursor position. -1 if the edit has not specified one. + */ +async function completionComplexEdit( + textDocument: languageClient.TextDocumentIdentifier, + textEdit: vscode.TextEdit, + isSnippetString: boolean, + newOffset: number, + outputChannel: vscode.LogOutputChannel +): Promise { + const componentName = '[roslyn.client.completionComplexEdit]'; + + // Find TextDocument, opening if needed. + const uri = UriConverter.deserialize(textDocument.uri); + const document = await vscode.workspace.openTextDocument(uri); + if (document === undefined) { + outputAndThrow(outputChannel, `${componentName} Can't open document with path: '${textDocument.uri}'`); + } + + // Use editor if we need to deal with selection or snippets. + let editor: vscode.TextEditor | undefined = undefined; + if (isSnippetString || newOffset >= 0) { + editor = await vscode.window.showTextDocument(document); + if (editor === undefined) { + outputAndThrow( + outputChannel, + `${componentName} Editor unavailable for document with path: '${textDocument.uri}'` + ); + } + } + + const newRange = document.validateRange( + new vscode.Range( + textEdit.range.start.line, + textEdit.range.start.character, + textEdit.range.end.line, + textEdit.range.end.character + ) + ); + + // HACK: + // ApplyEdit would fail the first time it's called when an item was committed with text modifying commit char (e.g. space, '(', etc.) + // so we retry a couple time here as a tempory workaround. We need to either figure our the reason of the failure, and/or try the + // approach of sending another edit request to server with updated document. + let success = false; + for (let i = 0; i < 3; i++) { + if (isSnippetString) { + editor!.selection = new vscode.Selection(newRange.start, newRange.end); + success = await editor!.insertSnippet(new vscode.SnippetString(textEdit.newText)); + } else { + const edit = new vscode.WorkspaceEdit(); + const newTextEdit = vscode.TextEdit.replace(newRange, textEdit.newText); + edit.set(document.uri, [newTextEdit]); + success = await vscode.workspace.applyEdit(edit); + + if (success && newOffset >= 0) { + const newPosition = document.positionAt(newOffset); + editor!.selections = [new vscode.Selection(newPosition, newPosition)]; + } + } + + if (success) { + return; + } + } + + if (!success) { + outputAndThrow( + outputChannel, + `${componentName} ${isSnippetString ? 'TextEditor.insertSnippet' : 'workspace.applyEdit'} failed.` + ); + } +} + +function outputAndThrow(outputChannel: vscode.LogOutputChannel, message: string): void { + outputChannel.show(); + outputChannel.error(message); + throw new Error(message); +} + +async function restartServer(languageServer: RoslynLanguageServer): Promise { + await languageServer.restart(); +} diff --git a/src/lsptoolshost/services/brokeredServicesHosting.ts b/src/lsptoolshost/serviceBroker/brokeredServicesHosting.ts similarity index 96% rename from src/lsptoolshost/services/brokeredServicesHosting.ts rename to src/lsptoolshost/serviceBroker/brokeredServicesHosting.ts index 5fcf8d3941..f0da662abc 100644 --- a/src/lsptoolshost/services/brokeredServicesHosting.ts +++ b/src/lsptoolshost/serviceBroker/brokeredServicesHosting.ts @@ -11,7 +11,7 @@ import { ServiceMoniker, ServiceRegistration, } from '@microsoft/servicehub-framework'; -import Descriptors from './descriptors'; +import Descriptors from '../solutionSnapshot/descriptors'; export class CSharpExtensionServiceBroker extends GlobalBrokeredServiceContainer { registerExternalServices(...monikers: ServiceMoniker[]) { diff --git a/src/lsptoolshost/services/ISolutionSnapshotProvider.ts b/src/lsptoolshost/solutionSnapshot/ISolutionSnapshotProvider.ts similarity index 100% rename from src/lsptoolshost/services/ISolutionSnapshotProvider.ts rename to src/lsptoolshost/solutionSnapshot/ISolutionSnapshotProvider.ts diff --git a/src/lsptoolshost/services/descriptors.ts b/src/lsptoolshost/solutionSnapshot/descriptors.ts similarity index 100% rename from src/lsptoolshost/services/descriptors.ts rename to src/lsptoolshost/solutionSnapshot/descriptors.ts diff --git a/src/lsptoolshost/services/solutionSnapshotProvider.ts b/src/lsptoolshost/solutionSnapshot/solutionSnapshotProvider.ts similarity index 92% rename from src/lsptoolshost/services/solutionSnapshotProvider.ts rename to src/lsptoolshost/solutionSnapshot/solutionSnapshotProvider.ts index 41908edb86..f346bed7ac 100644 --- a/src/lsptoolshost/services/solutionSnapshotProvider.ts +++ b/src/lsptoolshost/solutionSnapshot/solutionSnapshotProvider.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { RoslynLanguageServer } from '../roslynLanguageServer'; +import { RoslynLanguageServer } from '../server/roslynLanguageServer'; import { ISolutionSnapshotProvider, SolutionSnapshotId } from './ISolutionSnapshotProvider'; /** diff --git a/src/lsptoolshost/unitTesting.ts b/src/lsptoolshost/testing/unitTesting.ts similarity index 95% rename from src/lsptoolshost/unitTesting.ts rename to src/lsptoolshost/testing/unitTesting.ts index 24e812c656..d12e0ca47b 100644 --- a/src/lsptoolshost/unitTesting.ts +++ b/src/lsptoolshost/testing/unitTesting.ts @@ -7,12 +7,12 @@ import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; import * as languageClient from 'vscode-languageclient/node'; -import { RoslynLanguageServer } from './roslynLanguageServer'; -import { RunTestsParams, RunTestsPartialResult, RunTestsRequest, TestProgress } from './roslynProtocol'; -import { commonOptions } from '../shared/options'; -import { UriConverter } from './uriConverter'; -import { showErrorMessage } from '../shared/observers/utils/showMessage'; -import { getCSharpDevKit } from '../utils/getCSharpDevKit'; +import { RoslynLanguageServer } from '../server/roslynLanguageServer'; +import { RunTestsParams, RunTestsPartialResult, RunTestsRequest, TestProgress } from '../server/roslynProtocol'; +import { commonOptions } from '../../shared/options'; +import { UriConverter } from '../utils/uriConverter'; +import { showErrorMessage } from '../../shared/observers/utils/showMessage'; +import { getCSharpDevKit } from '../../utils/getCSharpDevKit'; export function registerUnitTestingCommands(context: vscode.ExtensionContext, languageServer: RoslynLanguageServer) { if (getCSharpDevKit()) { diff --git a/src/lsptoolshost/utils/combineDocumentSelectors.ts b/src/lsptoolshost/utils/combineDocumentSelectors.ts new file mode 100644 index 0000000000..14ad132b6b --- /dev/null +++ b/src/lsptoolshost/utils/combineDocumentSelectors.ts @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; + +export function combineDocumentSelectors(...selectors: vscode.DocumentSelector[]): vscode.DocumentSelector { + return selectors.reduce<(string | vscode.DocumentFilter)[]>((acc, selector) => acc.concat(selector), []); +} diff --git a/src/lsptoolshost/serverStateChange.ts b/src/lsptoolshost/utils/isString.ts similarity index 60% rename from src/lsptoolshost/serverStateChange.ts rename to src/lsptoolshost/utils/isString.ts index 0db11810ec..acf6c629a4 100644 --- a/src/lsptoolshost/serverStateChange.ts +++ b/src/lsptoolshost/utils/isString.ts @@ -3,14 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export enum ServerState { - Stopped = 0, - Started = 1, - ProjectInitializationStarted = 2, - ProjectInitializationComplete = 3, -} - -export interface ServerStateChangeEvent { - state: ServerState; - workspaceLabel: string; +export function isString(value: any): value is string { + return typeof value === 'string' || value instanceof String; } diff --git a/src/lsptoolshost/uriConverter.ts b/src/lsptoolshost/utils/uriConverter.ts similarity index 92% rename from src/lsptoolshost/uriConverter.ts rename to src/lsptoolshost/utils/uriConverter.ts index 88c6485998..24d19ff79a 100644 --- a/src/lsptoolshost/uriConverter.ts +++ b/src/lsptoolshost/utils/uriConverter.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { CSharpProjectedDocumentContentProvider } from '../razor/src/csharp/csharpProjectedDocumentContentProvider'; +import { CSharpProjectedDocumentContentProvider } from '../../razor/src/csharp/csharpProjectedDocumentContentProvider'; export class UriConverter { public static serialize(uri: vscode.Uri): string { diff --git a/src/lsptoolshost/miscellaneousFileNotifier.ts b/src/lsptoolshost/workspace/miscellaneousFileNotifier.ts similarity index 89% rename from src/lsptoolshost/miscellaneousFileNotifier.ts rename to src/lsptoolshost/workspace/miscellaneousFileNotifier.ts index 100f73c179..32d6bce5ea 100644 --- a/src/lsptoolshost/miscellaneousFileNotifier.ts +++ b/src/lsptoolshost/workspace/miscellaneousFileNotifier.ts @@ -5,10 +5,10 @@ import * as vscode from 'vscode'; import * as crypto from 'crypto'; -import { RoslynLanguageServer } from './roslynLanguageServer'; -import { ActionOption, showWarningMessage } from '../shared/observers/utils/showMessage'; -import { ServerState } from './serverStateChange'; -import { languageServerOptions } from '../shared/options'; +import { RoslynLanguageServer } from '../server/roslynLanguageServer'; +import { ActionOption, showWarningMessage } from '../../shared/observers/utils/showMessage'; +import { languageServerOptions } from '../../shared/options'; +import { ServerState } from '../server/languageServerEvents'; const SuppressMiscellaneousFilesToastsOption = 'dotnet.server.suppressMiscellaneousFilesToasts'; const NotifiedDocuments = new Set(); diff --git a/src/lsptoolshost/workspace/workspaceCommands.ts b/src/lsptoolshost/workspace/workspaceCommands.ts new file mode 100644 index 0000000000..c2aeea5a2a --- /dev/null +++ b/src/lsptoolshost/workspace/workspaceCommands.ts @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { RoslynLanguageServer } from '../server/roslynLanguageServer'; +import { createLaunchTargetForSolution } from '../../shared/launchTarget'; +import { getCSharpDevKit } from '../../utils/getCSharpDevKit'; + +/** + * Register commands that drive the workspace. + */ +export function registerWorkspaceCommands(context: vscode.ExtensionContext, languageServer: RoslynLanguageServer) { + if (!getCSharpDevKit()) { + context.subscriptions.push( + vscode.commands.registerCommand('dotnet.openSolution', async () => openSolution(languageServer)) + ); + } +} + +async function openSolution(languageServer: RoslynLanguageServer): Promise { + if (!vscode.workspace.workspaceFolders) { + return undefined; + } + + const solutionFiles = await vscode.workspace.findFiles('**/*.{sln,slnf}'); + const launchTargets = solutionFiles.map(createLaunchTargetForSolution); + const launchTarget = await vscode.window.showQuickPick(launchTargets, { + matchOnDescription: true, + placeHolder: `Select solution file`, + }); + + if (launchTarget) { + const uri = vscode.Uri.file(launchTarget.target); + await languageServer.openSolution(uri); + return uri; + } +} diff --git a/src/lsptoolshost/workspace/workspaceStatus.ts b/src/lsptoolshost/workspace/workspaceStatus.ts new file mode 100644 index 0000000000..b51ca172b5 --- /dev/null +++ b/src/lsptoolshost/workspace/workspaceStatus.ts @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { RoslynLanguageServerEvents, ServerState } from '../server/languageServerEvents'; +import { combineDocumentSelectors } from '../utils/combineDocumentSelectors'; +import { languageServerOptions } from '../../shared/options'; + +export class WorkspaceStatus { + static createStatusItem(context: vscode.ExtensionContext, languageServerEvents: RoslynLanguageServerEvents) { + const documentSelector = combineDocumentSelectors(languageServerOptions.documentSelector); + const openSolutionCommand = { + command: 'dotnet.openSolution', + title: vscode.l10n.t('Open solution'), + }; + const restartServerCommand = { + command: 'dotnet.restartServer', + title: vscode.l10n.t('Restart server'), + }; + + const item = vscode.languages.createLanguageStatusItem('csharp.workspaceStatus', documentSelector); + item.name = vscode.l10n.t('C# Workspace Status'); + item.severity = vscode.LanguageStatusSeverity.Error; + item.command = openSolutionCommand; + context.subscriptions.push(item); + + languageServerEvents.onServerStateChange((e) => { + item.text = e.workspaceLabel; + item.busy = e.state === ServerState.ProjectInitializationStarted; + item.severity = + e.state === ServerState.Stopped + ? vscode.LanguageStatusSeverity.Error + : vscode.LanguageStatusSeverity.Information; + item.command = e.state === ServerState.Stopped ? restartServerCommand : openSolutionCommand; + }); + } +} diff --git a/src/main.ts b/src/main.ts index 2f2dd84fdc..9eb06d7a67 100644 --- a/src/main.ts +++ b/src/main.ts @@ -22,27 +22,27 @@ import IInstallDependencies from './packageManager/IInstallDependencies'; import { installRuntimeDependencies } from './installRuntimeDependencies'; import { isValidDownload } from './packageManager/isValidDownload'; import { getDotnetPackApi } from './dotnetPack'; -import { RoslynLanguageServer, activateRoslynLanguageServer } from './lsptoolshost/roslynLanguageServer'; +import { RoslynLanguageServer } from './lsptoolshost/server/roslynLanguageServer'; import { MigrateOptions } from './shared/migrateOptions'; -import { getBrokeredServiceContainer } from './lsptoolshost/services/brokeredServicesHosting'; +import { getBrokeredServiceContainer } from './lsptoolshost/serviceBroker/brokeredServicesHosting'; import { CSharpDevKitExports } from './csharpDevKitExports'; -import Descriptors from './lsptoolshost/services/descriptors'; +import Descriptors from './lsptoolshost/solutionSnapshot/descriptors'; import { GlobalBrokeredServiceContainer } from '@microsoft/servicehub-framework'; import { CSharpExtensionExports, OmnisharpExtensionExports } from './csharpExtensionExports'; import { csharpDevkitExtensionId, getCSharpDevKit } from './utils/getCSharpDevKit'; import { BlazorDebugConfigurationProvider } from './razor/src/blazorDebug/blazorDebugConfigurationProvider'; -import { RoslynLanguageServerExport } from './lsptoolshost/roslynLanguageServerExportChannel'; -import { RoslynLanguageServerEvents } from './lsptoolshost/languageServerEvents'; -import { ServerState } from './lsptoolshost/serverStateChange'; -import { SolutionSnapshotProvider } from './lsptoolshost/services/solutionSnapshotProvider'; +import { SolutionSnapshotProvider } from './lsptoolshost/solutionSnapshot/solutionSnapshotProvider'; import { commonOptions, languageServerOptions, omnisharpOptions, razorOptions } from './shared/options'; -import { BuildResultDiagnostics } from './lsptoolshost/services/buildResultReporterService'; +import { BuildResultDiagnostics } from './lsptoolshost/diagnostics/buildResultReporterService'; import { debugSessionTracker } from './coreclrDebug/provisionalDebugSessionTracker'; -import { getComponentFolder } from './lsptoolshost/builtInComponents'; import { activateOmniSharpLanguageServer, ActivationResult } from './omnisharp/omnisharpLanguageServer'; import { ActionOption, showErrorMessage } from './shared/observers/utils/showMessage'; import { lt } from 'semver'; import { TelemetryEventNames } from './shared/telemetryEventNames'; +import { RoslynLanguageServerEvents, ServerState } from './lsptoolshost/server/languageServerEvents'; +import { activateRoslynLanguageServer } from './lsptoolshost/activate'; +import { RoslynLanguageServerExport } from './lsptoolshost/extensions/roslynLanguageServerExportChannel'; +import { getComponentFolder } from './lsptoolshost/extensions/builtInComponents'; export async function activate( context: vscode.ExtensionContext diff --git a/src/razor/src/codeActions/codeActionsHandler.ts b/src/razor/src/codeActions/codeActionsHandler.ts index c91446023d..8af500ba76 100644 --- a/src/razor/src/codeActions/codeActionsHandler.ts +++ b/src/razor/src/codeActions/codeActionsHandler.ts @@ -10,10 +10,10 @@ import { RazorLanguageServerClient } from '../razorLanguageServerClient'; import { RazorLogger } from '../razorLogger'; import { SerializableDelegatedCodeActionParams } from './serializableDelegatedCodeActionParams'; import { LanguageKind } from '../rpc/languageKind'; -import { UriConverter } from '../../../lsptoolshost/uriConverter'; +import { UriConverter } from '../../../lsptoolshost/utils/uriConverter'; import { SerializableRazorResolveCodeActionParams } from './serializableRazorResolveCodeActionParams'; import { RazorDocumentSynchronizer } from '../document/razorDocumentSynchronizer'; -import { provideCodeActionsCommand, resolveCodeActionCommand } from '../../../lsptoolshost/razorCommands'; +import { provideCodeActionsCommand, resolveCodeActionCommand } from '../../../lsptoolshost/razor/razorCommands'; export class CodeActionsHandler { private static readonly provideCodeActionsEndpoint = 'razor/provideCodeActions'; diff --git a/src/razor/src/completion/completionHandler.ts b/src/razor/src/completion/completionHandler.ts index 48247a16aa..44d4a15968 100644 --- a/src/razor/src/completion/completionHandler.ts +++ b/src/razor/src/completion/completionHandler.ts @@ -18,7 +18,7 @@ import { RequestType, TextEdit, } from 'vscode-languageclient'; -import { provideCompletionsCommand, resolveCompletionsCommand } from '../../../lsptoolshost/razorCommands'; +import { provideCompletionsCommand, resolveCompletionsCommand } from '../../../lsptoolshost/razor/razorCommands'; import { RazorDocumentManager } from '../document/razorDocumentManager'; import { RazorDocumentSynchronizer } from '../document/razorDocumentSynchronizer'; import { RazorLanguageServerClient } from '../razorLanguageServerClient'; @@ -26,7 +26,7 @@ import { RazorLogger } from '../razorLogger'; import { SerializableDelegatedCompletionParams } from './serializableDelegatedCompletionParams'; import { SerializableDelegatedCompletionItemResolveParams } from './serializableDelegatedCompletionItemResolveParams'; import { LanguageKind } from '../rpc/languageKind'; -import { UriConverter } from '../../../lsptoolshost/uriConverter'; +import { UriConverter } from '../../../lsptoolshost/utils/uriConverter'; import { SerializableTextEdit } from '../rpc/serializableTextEdit'; import { CSharpProjectedDocument } from '../csharp/csharpProjectedDocument'; import { IProjectedDocument } from '../projection/IProjectedDocument'; diff --git a/src/razor/src/diagnostics/razorDiagnosticHandler.ts b/src/razor/src/diagnostics/razorDiagnosticHandler.ts index 8c63228f40..d734681b0f 100644 --- a/src/razor/src/diagnostics/razorDiagnosticHandler.ts +++ b/src/razor/src/diagnostics/razorDiagnosticHandler.ts @@ -7,12 +7,12 @@ import * as vscode from 'vscode'; import { DocumentDiagnosticReport, DocumentDiagnosticParams, RequestType } from 'vscode-languageclient'; import { RazorLanguageServerClient } from '../razorLanguageServerClient'; import { RazorDocumentManager } from '../document/razorDocumentManager'; -import { UriConverter } from '../../../lsptoolshost/uriConverter'; +import { UriConverter } from '../../../lsptoolshost/utils/uriConverter'; import { RazorLanguageServiceClient } from '../razorLanguageServiceClient'; import { RazorLanguageFeatureBase } from '../razorLanguageFeatureBase'; import { RazorDocumentSynchronizer } from '../document/razorDocumentSynchronizer'; import { RazorLogger } from '../razorLogger'; -import { roslynPullDiagnosticCommand } from '../../../lsptoolshost/razorCommands'; +import { roslynPullDiagnosticCommand } from '../../../lsptoolshost/razor/razorCommands'; export class RazorDiagnosticHandler extends RazorLanguageFeatureBase { private static readonly razorPullDiagnosticsCommand = 'razor/csharpPullDiagnostics'; diff --git a/src/razor/src/document/razorDocumentManager.ts b/src/razor/src/document/razorDocumentManager.ts index b90e303fd2..3d5adf0872 100644 --- a/src/razor/src/document/razorDocumentManager.ts +++ b/src/razor/src/document/razorDocumentManager.ts @@ -17,7 +17,7 @@ import { IRazorDocumentChangeEvent } from './IRazorDocumentChangeEvent'; import { IRazorDocumentManager } from './IRazorDocumentManager'; import { RazorDocumentChangeKind } from './razorDocumentChangeKind'; import { createDocument } from './razorDocumentFactory'; -import { razorInitializeCommand } from '../../../lsptoolshost/razorCommands'; +import { razorInitializeCommand } from '../../../lsptoolshost/razor/razorCommands'; import { PlatformInformation } from '../../../shared/platform'; import { generateUuid } from 'vscode-languageclient/lib/common/utils/uuid'; diff --git a/src/razor/src/dynamicFile/dynamicFileInfoHandler.ts b/src/razor/src/dynamicFile/dynamicFileInfoHandler.ts index 62300f0f5f..7208c637b8 100644 --- a/src/razor/src/dynamicFile/dynamicFileInfoHandler.ts +++ b/src/razor/src/dynamicFile/dynamicFileInfoHandler.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { UriConverter } from '../../../lsptoolshost/uriConverter'; +import { UriConverter } from '../../../lsptoolshost/utils/uriConverter'; import { RazorDocumentManager } from '../document/razorDocumentManager'; import { RazorLogger } from '../razorLogger'; import { ProvideDynamicFileParams } from './provideDynamicFileParams'; diff --git a/src/razor/src/extension.ts b/src/razor/src/extension.ts index fde0a20394..7e2ddc43e5 100644 --- a/src/razor/src/extension.ts +++ b/src/razor/src/extension.ts @@ -42,14 +42,14 @@ import { RazorDiagnosticHandler } from './diagnostics/razorDiagnosticHandler'; import { RazorSimplifyMethodHandler } from './simplify/razorSimplifyMethodHandler'; import TelemetryReporter from '@vscode/extension-telemetry'; import { CSharpDevKitExports } from '../../csharpDevKitExports'; -import { DotnetRuntimeExtensionResolver } from '../../lsptoolshost/dotnetRuntimeExtensionResolver'; +import { DotnetRuntimeExtensionResolver } from '../../lsptoolshost/dotnetRuntime/dotnetRuntimeExtensionResolver'; import { PlatformInformation } from '../../shared/platform'; import { RazorLanguageServerOptions } from './razorLanguageServerOptions'; import { resolveRazorLanguageServerOptions } from './razorLanguageServerOptionsResolver'; import { RazorFormatNewFileHandler } from './formatNewFile/razorFormatNewFileHandler'; import { InlayHintHandler } from './inlayHint/inlayHintHandler'; import { InlayHintResolveHandler } from './inlayHint/inlayHintResolveHandler'; -import { getComponentPaths } from '../../lsptoolshost/builtInComponents'; +import { getComponentPaths } from '../../lsptoolshost/extensions/builtInComponents'; import { BlazorDebugConfigurationProvider } from './blazorDebug/blazorDebugConfigurationProvider'; // We specifically need to take a reference to a particular instance of the vscode namespace, diff --git a/src/razor/src/formatNewFile/razorFormatNewFileHandler.ts b/src/razor/src/formatNewFile/razorFormatNewFileHandler.ts index 998cd618dd..d89011c493 100644 --- a/src/razor/src/formatNewFile/razorFormatNewFileHandler.ts +++ b/src/razor/src/formatNewFile/razorFormatNewFileHandler.ts @@ -12,7 +12,7 @@ import { RazorLanguageFeatureBase } from '../razorLanguageFeatureBase'; import { RazorDocumentSynchronizer } from '../document/razorDocumentSynchronizer'; import { RazorLogger } from '../razorLogger'; import { SerializableFormatNewFileParams } from './serializableFormatNewFileParams'; -import { roslynFormatNewFileCommand } from '../../../lsptoolshost/razorCommands'; +import { roslynFormatNewFileCommand } from '../../../lsptoolshost/razor/razorCommands'; export class RazorFormatNewFileHandler extends RazorLanguageFeatureBase { private static readonly razorFormatNewFileCommand = 'razor/formatNewFile'; diff --git a/src/razor/src/inlayHint/inlayHintHandler.ts b/src/razor/src/inlayHint/inlayHintHandler.ts index 622f5fe311..5a4f6e5146 100644 --- a/src/razor/src/inlayHint/inlayHintHandler.ts +++ b/src/razor/src/inlayHint/inlayHintHandler.ts @@ -9,8 +9,8 @@ import { RazorDocumentManager } from '../document/razorDocumentManager'; import { RazorLanguageServerClient } from '../razorLanguageServerClient'; import { RazorLogger } from '../razorLogger'; import { SerializableInlayHintParams } from './serializableInlayHintParams'; -import { provideInlayHintsCommand } from '../../../lsptoolshost/razorCommands'; -import { UriConverter } from '../../../lsptoolshost/uriConverter'; +import { provideInlayHintsCommand } from '../../../lsptoolshost/razor/razorCommands'; +import { UriConverter } from '../../../lsptoolshost/utils/uriConverter'; import { RazorDocumentSynchronizer } from '../document/razorDocumentSynchronizer'; export class InlayHintHandler { diff --git a/src/razor/src/inlayHint/inlayHintResolveHandler.ts b/src/razor/src/inlayHint/inlayHintResolveHandler.ts index d0d3b0f6f1..264e437f3c 100644 --- a/src/razor/src/inlayHint/inlayHintResolveHandler.ts +++ b/src/razor/src/inlayHint/inlayHintResolveHandler.ts @@ -9,7 +9,7 @@ import { RazorDocumentManager } from '../document/razorDocumentManager'; import { RazorLanguageServerClient } from '../razorLanguageServerClient'; import { RazorLogger } from '../razorLogger'; import { SerializableInlayHintResolveParams } from './serializableInlayHintResolveParams'; -import { resolveInlayHintCommand } from '../../../lsptoolshost/razorCommands'; +import { resolveInlayHintCommand } from '../../../lsptoolshost/razor/razorCommands'; export class InlayHintResolveHandler { private static readonly resolveInlayHint = 'razor/inlayHintResolve'; diff --git a/src/razor/src/simplify/razorSimplifyMethodHandler.ts b/src/razor/src/simplify/razorSimplifyMethodHandler.ts index 2c340e85c0..59fca7967e 100644 --- a/src/razor/src/simplify/razorSimplifyMethodHandler.ts +++ b/src/razor/src/simplify/razorSimplifyMethodHandler.ts @@ -7,7 +7,7 @@ import * as vscode from 'vscode'; import { RequestType, TextDocumentIdentifier } from 'vscode-languageclient'; import { RazorLanguageServerClient } from '../razorLanguageServerClient'; import { RazorDocumentManager } from '../document/razorDocumentManager'; -import { UriConverter } from '../../../lsptoolshost/uriConverter'; +import { UriConverter } from '../../../lsptoolshost/utils/uriConverter'; import { RazorLanguageServiceClient } from '../razorLanguageServiceClient'; import { RazorLanguageFeatureBase } from '../razorLanguageFeatureBase'; import { RazorDocumentSynchronizer } from '../document/razorDocumentSynchronizer'; @@ -15,7 +15,7 @@ import { RazorLogger } from '../razorLogger'; import { SerializableDelegatedSimplifyMethodParams } from './serializableDelegatedSimplifyMethodParams'; import SerializableSimplifyMethodParams from './serializableSimplifyMethodParams'; import { TextEdit } from 'vscode-html-languageservice'; -import { roslynSimplifyMethodCommand } from '../../../lsptoolshost/razorCommands'; +import { roslynSimplifyMethodCommand } from '../../../lsptoolshost/razor/razorCommands'; export class RazorSimplifyMethodHandler extends RazorLanguageFeatureBase { private static readonly razorSimplifyMethodCommand = 'razor/simplifyMethod'; diff --git a/src/shared/dotnetConfigurationProvider.ts b/src/shared/dotnetConfigurationProvider.ts index d07e5449a2..cebce60a0b 100644 --- a/src/shared/dotnetConfigurationProvider.ts +++ b/src/shared/dotnetConfigurationProvider.ts @@ -7,13 +7,13 @@ import * as fs from 'fs-extra'; import * as vscode from 'vscode'; import { IWorkspaceDebugInformationProvider, ProjectDebugInformation } from './IWorkspaceDebugInformationProvider'; import { AssetGenerator, AssetOperations, addTasksJsonIfNecessary, getBuildOperations } from './assets'; -import { getServiceBroker } from '../lsptoolshost/services/brokeredServicesHosting'; -import Descriptors from '../lsptoolshost/services/descriptors'; +import { getServiceBroker } from '../lsptoolshost/serviceBroker/brokeredServicesHosting'; +import Descriptors from '../lsptoolshost/solutionSnapshot/descriptors'; import { DotnetDebugConfigurationServiceErrorKind, IDotnetDebugConfigurationService, IDotnetDebugConfigurationServiceResult, -} from '../lsptoolshost/services/IDotnetDebugConfigurationService'; +} from '../lsptoolshost/debugger/IDotnetDebugConfigurationService'; import { DotnetWorkspaceConfigurationProvider } from './workspaceConfigurationProvider'; // User errors that can be shown to the user. diff --git a/tasks/profilingTasks.ts b/tasks/profilingTasks.ts new file mode 100644 index 0000000000..ea6fc91334 --- /dev/null +++ b/tasks/profilingTasks.ts @@ -0,0 +1,112 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import fs from 'fs'; +import * as gulp from 'gulp'; +import * as path from 'path'; +import { basicSlnTestProject, runIntegrationTest } from './testHelpers'; +import { outPath } from './projectPaths'; +import { execSync } from 'child_process'; + +createProfilingTasks(); + +function createProfilingTasks() { + const profilingOutputFolder = path.join(outPath, 'profiling'); + gulp.task(`profiling:csharp:${basicSlnTestProject}`, async () => { + // Ensure the profiling output folder exists, otherwise the outputs will not get written. + fs.mkdirSync(path.join(outPath, 'profiling'), { recursive: true }); + + await runIntegrationTest( + basicSlnTestProject, + path.join('lsptoolshost', 'integrationTests'), + `[C#][${basicSlnTestProject}]`, + undefined, + undefined, + { + ROSLYN_DOTNET_EventPipeOutputPath: path.join(profilingOutputFolder, 'csharp-trace.{pid}.nettrace'), + } + ); + + const files = fs.readdirSync(profilingOutputFolder); + const nettraceFiles = files.filter((f) => f.endsWith('.nettrace')); + if (nettraceFiles.length === 0) { + throw new Error('No .nettrace files found in the profiling output folder.'); + } + }); + + gulp.task('mergeTraces', async () => { + await mergeTraces(profilingOutputFolder); + }); + + gulp.task('profiling', gulp.series(`profiling:csharp:${basicSlnTestProject}`, 'mergeTraces')); +} + +async function mergeTraces(profilingOutputFolder: string) { + const files = fs.readdirSync(profilingOutputFolder); + const nettraceFiles = files.filter((f) => f.endsWith('.nettrace')); + if (nettraceFiles.length === 0) { + throw new Error('No .nettrace files found in the profiling output folder.'); + } + + // Function to spawn a tool + function spawnTool(command: string, args: string[], warnOnError = false) { + try { + console.log(`##[command] ${command} ${args.join(' ')}`); + execSync(`${command} ${args.join(' ')}`, { stdio: 'inherit' }); + } catch (error) { + if (warnOnError) { + console.warn(`Failed: ${error}`); + } else { + throw error; + } + } + } + + // Ensure the dotnet-pgo tool is installed. + // Additional versions of this can be found at https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet8-transport/NuGet/dotnet-pgo/ + spawnTool('dotnet', [ + 'tool', + 'update', + '-g', + 'dotnet-pgo', + '--version', + '8.0.0-rc.2.23479.6', + '--add-source', + 'https://pkgs.dev.azure.com/azure-public/vside/_packaging/msft_consumption/nuget/v3/index.json', + '--ignore-failed-sources', + ]); + + console.log('##[group] Converting .nettrace to .mibc'); + nettraceFiles.forEach((file) => { + spawnTool( + 'dotnet-pgo', + [ + 'create-mibc', + '-t', + path.join(profilingOutputFolder, file), + '-o', + path.join(profilingOutputFolder, `${path.basename(file, '.nettrace')}.mibc`), + ], + true + ); + }); + console.log('##[endgroup]'); + + const mibcFiles = fs.readdirSync(profilingOutputFolder).filter((f) => f.endsWith('.mibc')); + if (mibcFiles.length === 0) { + throw new Error('No .mibc files were produced.'); + } + + const mergedTraceLocation = path.join(profilingOutputFolder, 'merged'); + fs.mkdirSync(mergedTraceLocation, { recursive: true }); + + const inputArgs = ['merge', '--compressed']; + mibcFiles.forEach((file) => { + inputArgs.push('-i', path.join(profilingOutputFolder, file)); + }); + inputArgs.push('-o', path.join(mergedTraceLocation, 'merged.mibc')); + + spawnTool('dotnet-pgo', inputArgs); +} diff --git a/tasks/projectPaths.ts b/tasks/projectPaths.ts index 3f74d1f76e..ce44b6805d 100644 --- a/tasks/projectPaths.ts +++ b/tasks/projectPaths.ts @@ -5,7 +5,7 @@ import * as path from 'path'; import { commandLineOptions } from './commandLineArguments'; -import { componentInfo } from '../src/lsptoolshost/builtInComponents'; +import { componentInfo } from '../src/lsptoolshost/extensions/builtInComponents'; export const rootPath = path.resolve(__dirname, '..'); diff --git a/tasks/testHelpers.ts b/tasks/testHelpers.ts new file mode 100644 index 0000000000..d8dfa5cbf3 --- /dev/null +++ b/tasks/testHelpers.ts @@ -0,0 +1,141 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import fs from 'fs'; +import * as path from 'path'; +import { rootPath, outPath } from './projectPaths'; +import { prepareVSCodeAndExecuteTests } from '../test/vscodeLauncher'; + +export const basicSlnTestProject = 'slnWithCsproj'; +export const integrationTestProjects = [basicSlnTestProject]; + +export async function runDevKitIntegrationTests( + testAssetName: string, + testFolderName: string, + suiteName: string, + env: NodeJS.ProcessEnv = {} +) { + // Tests using C# Dev Kit tests are a bit different from the rest - we are not able to restart the Dev Kit server and there + // are not easy APIs to use to know if the project is reloading due to workspace changes. + // So we have to isolate the C# Dev Kit tests into smaller test runs (in this case, per file), where each run + // launches VSCode and runs the tests in that file. + const testFolder = path.join(rootPath, 'test', testFolderName); + console.log(`Searching for test files in ${testFolder}`); + const allFiles = fs + .readdirSync(testFolder, { + recursive: true, + }) + .filter((f) => typeof f === 'string'); + const devKitTestFiles = allFiles.filter((f) => f.endsWith('.test.ts')).map((f) => path.join(testFolder, f)); + if (devKitTestFiles.length === 0) { + throw new Error(`No test files found in ${testFolder}`); + } + + let failed: boolean = false; + for (const testFile of devKitTestFiles) { + try { + await runIntegrationTest( + testAssetName, + testFolderName, + suiteName, + `devkit_${testAssetName}.code-workspace`, + testFile, + env + ); + } catch (err) { + // We have to catch the error to continue running tests from the rest of the files. + console.error(`##[error] Tests in ${path.basename(testFile)} failed`, err); + failed = true; + } + } + + if (failed) { + // Ensure the task fails if any tests failed. + throw new Error(`One or more tests failed`); + } +} + +export async function runIntegrationTest( + testAssetName: string, + testFolderName: string, + suiteName: string, + vscodeWorkspaceFileName = `${testAssetName}.code-workspace`, + testFile: string | undefined = undefined, + env: NodeJS.ProcessEnv = {} +): Promise { + const testFolder = path.join('test', testFolderName); + return await runJestIntegrationTest(testAssetName, testFolder, vscodeWorkspaceFileName, suiteName, env, testFile); +} + +/** + * Runs jest based integration tests. + * @param testAssetName the name of the test asset + * @param testFolderName the relative path (from workspace root) + * @param workspaceFileName the name of the vscode workspace file to use. + * @param suiteName a unique name for the test suite being run. + * @param env any environment variables needed. + * @param testFile the full path to a specific test file to run. + */ +export async function runJestIntegrationTest( + testAssetName: string, + testFolderName: string, + workspaceFileName: string, + suiteName: string, + env: NodeJS.ProcessEnv = {}, + testFile: string | undefined = undefined +) { + const logName = testFile ? `${suiteName}_${path.basename(testFile)}` : suiteName; + + // Set VSCode to produce logs in a unique directory for this test run. + const userDataDir = path.join(outPath, 'userData', logName); + + // Test assets are always in a testAssets folder inside the integration test folder. + const assetsPath = path.join(rootPath, testFolderName, 'testAssets'); + if (!fs.existsSync(assetsPath)) { + throw new Error(`Could not find test assets at ${assetsPath}`); + } + const workspacePath = path.join(assetsPath, testAssetName, '.vscode', workspaceFileName); + if (!fs.existsSync(workspacePath)) { + throw new Error(`Could not find vscode workspace to open at ${workspacePath}`); + } + + // The runner (that loads in the vscode process to run tests) is in the test folder in the *output* directory. + const vscodeRunnerPath = path.join(outPath, testFolderName, 'index.js'); + if (!fs.existsSync(vscodeRunnerPath)) { + throw new Error(`Could not find vscode runner in out/ at ${vscodeRunnerPath}`); + } + + // Configure the file and suite name in CI to avoid having multiple test runs stomp on each other. + env.JEST_JUNIT_OUTPUT_NAME = getJUnitFileName(logName); + env.JEST_SUITE_NAME = suiteName; + + if (testFile) { + console.log(`Setting test file filter to: ${testFile}`); + process.env.TEST_FILE_FILTER = testFile; + } + + try { + const result = await prepareVSCodeAndExecuteTests(rootPath, vscodeRunnerPath, workspacePath, userDataDir, env); + if (result > 0) { + // The VSCode API will generally throw if jest fails the test, but we can get errors before the test runs (e.g. launching VSCode). + // So here we make sure to error if we don't get a clean exit code. + throw new Error(`Exit code: ${result}`); + } + + return result; + } catch (err) { + // If we hit an error, copy the logs VSCode produced to a directory that CI can find. + const vscodeLogs = path.join(userDataDir, 'logs'); + const logOutputPath = path.join(outPath, 'logs', logName); + console.log(`Copying logs from ${vscodeLogs} to ${logOutputPath}`); + fs.cpSync(vscodeLogs, logOutputPath, { recursive: true, force: true }); + + throw err; + } +} + +export function getJUnitFileName(logName: string) { + return `${logName.replaceAll(' ', '_')}_junit.xml`; +} diff --git a/tasks/testTasks.ts b/tasks/testTasks.ts index e9d5063675..49d2d6b1e2 100644 --- a/tasks/testTasks.ts +++ b/tasks/testTasks.ts @@ -3,17 +3,24 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import fs from 'fs'; import * as gulp from 'gulp'; import * as path from 'path'; -import { codeExtensionPath, rootPath, outPath } from './projectPaths'; +import { codeExtensionPath, rootPath } from './projectPaths'; import * as jest from 'jest'; import { Config } from '@jest/types'; import { jestOmniSharpUnitTestProjectName } from '../test/omnisharp/omnisharpUnitTests/jest.config'; import { jestUnitTestProjectName } from '../test/lsptoolshost/unitTests/jest.config'; import { razorTestProjectName } from '../test/razor/razorTests/jest.config'; import { jestArtifactTestsProjectName } from '../test/lsptoolshost/artifactTests/jest.config'; -import { prepareVSCodeAndExecuteTests } from '../test/vscodeLauncher'; +import { + getJUnitFileName, + integrationTestProjects, + runDevKitIntegrationTests, + runIntegrationTest, + runJestIntegrationTest, +} from './testHelpers'; + +const razorIntegrationTestProjects = ['RazorApp']; createUnitTestSubTasks(); createIntegrationTestSubTasks(); @@ -50,7 +57,6 @@ function createUnitTestSubTasks() { } function createIntegrationTestSubTasks() { - const integrationTestProjects = ['slnWithCsproj']; for (const projectName of integrationTestProjects) { gulp.task(`test:integration:csharp:${projectName}`, async () => runIntegrationTest(projectName, path.join('lsptoolshost', 'integrationTests'), `[C#][${projectName}]`) @@ -75,7 +81,6 @@ function createIntegrationTestSubTasks() { gulp.series(integrationTestProjects.map((projectName) => `test:integration:devkit:${projectName}`)) ); - const razorIntegrationTestProjects = ['RazorApp']; for (const projectName of razorIntegrationTestProjects) { gulp.task(`test:razorintegration:${projectName}`, async () => runIntegrationTest( @@ -159,125 +164,6 @@ async function runOmnisharpJestIntegrationTest(testAssetName: string, engine: 's await runJestIntegrationTest(testAssetName, testFolder, workspaceFile, suiteName, env); } -async function runDevKitIntegrationTests(testAssetName: string, testFolderName: string, suiteName: string) { - // Tests using C# Dev Kit tests are a bit different from the rest - we are not able to restart the Dev Kit server and there - // are not easy APIs to use to know if the project is reloading due to workspace changes. - // So we have to isolate the C# Dev Kit tests into smaller test runs (in this case, per file), where each run - // launches VSCode and runs the tests in that file. - const testFolder = path.join(rootPath, 'test', testFolderName); - console.log(`Searching for test files in ${testFolder}`); - const allFiles = fs - .readdirSync(testFolder, { - recursive: true, - }) - .filter((f) => typeof f === 'string'); - const devKitTestFiles = allFiles.filter((f) => f.endsWith('.test.ts')).map((f) => path.join(testFolder, f)); - if (devKitTestFiles.length === 0) { - throw new Error(`No test files found in ${testFolder}`); - } - - let failed: boolean = false; - for (const testFile of devKitTestFiles) { - try { - await runIntegrationTest( - testAssetName, - testFolderName, - suiteName, - `devkit_${testAssetName}.code-workspace`, - testFile - ); - } catch (err) { - // We have to catch the error to continue running tests from the rest of the files. - console.error(`##[error] Tests in ${path.basename(testFile)} failed`, err); - failed = true; - } - } - - if (failed) { - // Ensure the task fails if any tests failed. - throw new Error(`One or more tests failed`); - } -} - -async function runIntegrationTest( - testAssetName: string, - testFolderName: string, - suiteName: string, - vscodeWorkspaceFileName = `${testAssetName}.code-workspace`, - testFile: string | undefined = undefined -) { - const testFolder = path.join('test', testFolderName); - const env: NodeJS.ProcessEnv = {}; - return await runJestIntegrationTest(testAssetName, testFolder, vscodeWorkspaceFileName, suiteName, env, testFile); -} - -/** - * Runs jest based integration tests. - * @param testAssetName the name of the test asset - * @param testFolderName the relative path (from workspace root) - * @param workspaceFileName the name of the vscode workspace file to use. - * @param suiteName a unique name for the test suite being run. - * @param env any environment variables needed. - * @param testFile the full path to a specific test file to run. - */ -async function runJestIntegrationTest( - testAssetName: string, - testFolderName: string, - workspaceFileName: string, - suiteName: string, - env: NodeJS.ProcessEnv = {}, - testFile: string | undefined = undefined -) { - const logName = testFile ? `${suiteName}_${path.basename(testFile)}` : suiteName; - - // Set VSCode to produce logs in a unique directory for this test run. - const userDataDir = path.join(outPath, 'userData', logName); - - // Test assets are always in a testAssets folder inside the integration test folder. - const assetsPath = path.join(rootPath, testFolderName, 'testAssets'); - if (!fs.existsSync(assetsPath)) { - throw new Error(`Could not find test assets at ${assetsPath}`); - } - const workspacePath = path.join(assetsPath, testAssetName, '.vscode', workspaceFileName); - if (!fs.existsSync(workspacePath)) { - throw new Error(`Could not find vscode workspace to open at ${workspacePath}`); - } - - // The runner (that loads in the vscode process to run tests) is in the test folder in the *output* directory. - const vscodeRunnerPath = path.join(outPath, testFolderName, 'index.js'); - if (!fs.existsSync(vscodeRunnerPath)) { - throw new Error(`Could not find vscode runner in out/ at ${vscodeRunnerPath}`); - } - - // Configure the file and suite name in CI to avoid having multiple test runs stomp on each other. - env.JEST_JUNIT_OUTPUT_NAME = getJUnitFileName(logName); - env.JEST_SUITE_NAME = suiteName; - - if (testFile) { - console.log(`Setting test file filter to: ${testFile}`); - process.env.TEST_FILE_FILTER = testFile; - } - - try { - const result = await prepareVSCodeAndExecuteTests(rootPath, vscodeRunnerPath, workspacePath, userDataDir, env); - if (result > 0) { - // The VSCode API will generally throw if jest fails the test, but we can get errors before the test runs (e.g. launching VSCode). - // So here we make sure to error if we don't get a clean exit code. - throw new Error(`Exit code: ${result}`); - } - - return result; - } catch (err) { - // If we hit an error, copy the logs VSCode produced to a directory that CI can find. - const vscodeLogs = path.join(userDataDir, 'logs'); - const logOutputPath = path.join(outPath, 'logs', logName); - console.log(`Copying logs from ${vscodeLogs} to ${logOutputPath}`); - fs.cpSync(vscodeLogs, logOutputPath, { recursive: true, force: true }); - - throw err; - } -} - async function runJestTest(project: string) { process.env.JEST_JUNIT_OUTPUT_NAME = getJUnitFileName(project); process.env.JEST_SUITE_NAME = project; @@ -295,7 +181,3 @@ async function runJestTest(project: string) { throw new Error('Tests failed.'); } } - -function getJUnitFileName(logName: string) { - return `${logName.replaceAll(' ', '_')}_junit.xml`; -} diff --git a/test/lsptoolshost/integrationTests/buildDiagnostics.integration.test.ts b/test/lsptoolshost/integrationTests/buildDiagnostics.integration.test.ts index d05844cbac..7d49a91670 100644 --- a/test/lsptoolshost/integrationTests/buildDiagnostics.integration.test.ts +++ b/test/lsptoolshost/integrationTests/buildDiagnostics.integration.test.ts @@ -6,7 +6,10 @@ import * as vscode from 'vscode'; import { describe, test, expect, beforeAll, afterAll, beforeEach, afterEach } from '@jest/globals'; import testAssetWorkspace from './testAssets/testAssetWorkspace'; -import { AnalysisSetting, BuildDiagnosticsService } from '../../../src/lsptoolshost/buildDiagnosticsService'; +import { + AnalysisSetting, + BuildDiagnosticsService, +} from '../../../src/lsptoolshost/diagnostics/buildDiagnosticsService'; import * as integrationHelpers from './integrationHelpers'; import path from 'path'; describe(`Build and live diagnostics dedupe`, () => { diff --git a/test/lsptoolshost/integrationTests/documentDiagnostics.integration.test.ts b/test/lsptoolshost/integrationTests/documentDiagnostics.integration.test.ts index dcfaeb39e2..6221bbd4ae 100644 --- a/test/lsptoolshost/integrationTests/documentDiagnostics.integration.test.ts +++ b/test/lsptoolshost/integrationTests/documentDiagnostics.integration.test.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode'; import { describe, test, beforeAll, afterAll, expect, beforeEach, afterEach } from '@jest/globals'; import testAssetWorkspace from './testAssets/testAssetWorkspace'; -import { AnalysisSetting } from '../../../src/lsptoolshost/buildDiagnosticsService'; +import { AnalysisSetting } from '../../../src/lsptoolshost/diagnostics/buildDiagnosticsService'; import path from 'path'; import { getCode, setBackgroundAnalysisScopes, waitForExpectedDiagnostics } from './diagnosticsHelpers'; import { diff --git a/test/lsptoolshost/integrationTests/integrationHelpers.ts b/test/lsptoolshost/integrationTests/integrationHelpers.ts index 2a5c686d23..a7d3b89ebf 100644 --- a/test/lsptoolshost/integrationTests/integrationHelpers.ts +++ b/test/lsptoolshost/integrationTests/integrationHelpers.ts @@ -8,7 +8,7 @@ import * as path from 'path'; import * as semver from 'semver'; import { CSharpExtensionExports } from '../../../src/csharpExtensionExports'; import { existsSync } from 'fs'; -import { ServerState } from '../../../src/lsptoolshost/serverStateChange'; +import { ServerState } from '../../../src/lsptoolshost/server/languageServerEvents'; import testAssetWorkspace from './testAssets/testAssetWorkspace'; import { EOL, platform } from 'os'; import { describe, expect, test } from '@jest/globals'; diff --git a/test/lsptoolshost/integrationTests/unitTests.integration.test.ts b/test/lsptoolshost/integrationTests/unitTests.integration.test.ts index ce7ccf03b7..8753a65607 100644 --- a/test/lsptoolshost/integrationTests/unitTests.integration.test.ts +++ b/test/lsptoolshost/integrationTests/unitTests.integration.test.ts @@ -14,7 +14,7 @@ import { getCodeLensesAsync, openFileInWorkspaceAsync, } from './integrationHelpers'; -import { TestProgress } from '../../../src/lsptoolshost/roslynProtocol'; +import { TestProgress } from '../../../src/lsptoolshost/server/roslynProtocol'; describeIfCSharp(`Unit Testing Tests`, () => { beforeAll(async () => { diff --git a/test/lsptoolshost/integrationTests/workspaceDiagnostics.integration.test.ts b/test/lsptoolshost/integrationTests/workspaceDiagnostics.integration.test.ts index 234f87a4c4..09fb878944 100644 --- a/test/lsptoolshost/integrationTests/workspaceDiagnostics.integration.test.ts +++ b/test/lsptoolshost/integrationTests/workspaceDiagnostics.integration.test.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode'; import { describe, test, expect, beforeAll, afterAll } from '@jest/globals'; import testAssetWorkspace from './testAssets/testAssetWorkspace'; -import { AnalysisSetting } from '../../../src/lsptoolshost/buildDiagnosticsService'; +import { AnalysisSetting } from '../../../src/lsptoolshost/diagnostics/buildDiagnosticsService'; import { getCode, setBackgroundAnalysisScopes, waitForExpectedDiagnostics } from './diagnosticsHelpers'; import { activateCSharpExtension, describeIfCSharp } from './integrationHelpers'; diff --git a/test/lsptoolshost/unitTests/configurationMiddleware.test.ts b/test/lsptoolshost/unitTests/configurationMiddleware.test.ts index 46b9f41d95..11b4f764ba 100644 --- a/test/lsptoolshost/unitTests/configurationMiddleware.test.ts +++ b/test/lsptoolshost/unitTests/configurationMiddleware.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { readFileSync } from 'fs'; -import { convertServerOptionNameToClientConfigurationName } from '../../../src/lsptoolshost/optionNameConverter'; +import { convertServerOptionNameToClientConfigurationName } from '../../../src/lsptoolshost/options/optionNameConverter'; import { describe, test, expect } from '@jest/globals'; const editorBehaviorSection = 1; diff --git a/test/lsptoolshost/unitTests/languageServerConfigChangeObserver.test.ts b/test/lsptoolshost/unitTests/languageServerConfigChangeObserver.test.ts index e8bf2928df..cea6af83a6 100644 --- a/test/lsptoolshost/unitTests/languageServerConfigChangeObserver.test.ts +++ b/test/lsptoolshost/unitTests/languageServerConfigChangeObserver.test.ts @@ -5,7 +5,7 @@ import { timeout } from 'rxjs/operators'; import { from as observableFrom, Subject, BehaviorSubject } from 'rxjs'; -import { registerLanguageServerOptionChanges } from '../../../src/lsptoolshost/optionChanges'; +import { registerLanguageServerOptionChanges } from '../../../src/lsptoolshost/options/optionChanges'; import { describe, beforeEach, test, expect } from '@jest/globals'; import * as vscode from 'vscode'; diff --git a/test/razor/razorIntegrationTests/completion.integration.test.ts b/test/razor/razorIntegrationTests/completion.integration.test.ts index 0760f1d609..53d056986c 100644 --- a/test/razor/razorIntegrationTests/completion.integration.test.ts +++ b/test/razor/razorIntegrationTests/completion.integration.test.ts @@ -5,11 +5,11 @@ import * as path from 'path'; import * as vscode from 'vscode'; -import { beforeAll, afterAll, test, expect, beforeEach } from '@jest/globals'; +import { beforeAll, afterAll, test, expect, beforeEach, describe } from '@jest/globals'; import testAssetWorkspace from './testAssets/testAssetWorkspace'; import * as integrationHelpers from '../../lsptoolshost/integrationTests/integrationHelpers'; -integrationHelpers.describeIfWindows(`Razor Completion ${testAssetWorkspace.description}`, function () { +describe.skip(`Razor Completion ${testAssetWorkspace.description}`, function () { beforeAll(async function () { if (!integrationHelpers.isRazorWorkspace(vscode.workspace)) { return; diff --git a/version.json b/version.json index 75cb2cde07..364125d358 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "2.64", + "version": "2.65", "publicReleaseRefSpec": [ "^refs/heads/release$", "^refs/heads/prerelease$",