From 4c9c2f5727b1b9417aebb7f7e898b4a4babbc2f7 Mon Sep 17 00:00:00 2001 From: w3lld1 <42353747+w3lld1@users.noreply.github.com> Date: Sat, 18 Jul 2026 21:18:37 +0200 Subject: [PATCH] Fix #2313: Clear terminal scrollback in-band --- .../Extension/EditorOperationsService.cs | 12 ++++- .../Workspace/LanguageServerSettings.cs | 18 +++++++ .../EditorOperationsServiceTests.cs | 53 ++++++++++++++++++- 3 files changed, 80 insertions(+), 3 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs b/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs index 9c3e0c4ff..7010d67ee 100644 --- a/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs +++ b/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System; using Microsoft.PowerShell.EditorServices.Extensions; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.TextDocument; @@ -17,15 +18,18 @@ internal class EditorOperationsService : IEditorOperations private readonly PsesInternalHost _psesHost; private readonly WorkspaceService _workspaceService; private readonly ILanguageServerFacade _languageServer; + private readonly ConfigurationService _configurationService; public EditorOperationsService( PsesInternalHost psesHost, WorkspaceService workspaceService, - ILanguageServerFacade languageServer) + ILanguageServerFacade languageServer, + ConfigurationService configurationService) { _psesHost = psesHost; _workspaceService = workspaceService; _languageServer = languageServer; + _configurationService = configurationService; } public async Task GetEditorContextAsync() @@ -260,6 +264,12 @@ public async Task SetStatusBarMessageAsync(string message, int? timeout) public void ClearTerminal() { + if (_configurationService.CurrentSettings.IntegratedConsole.ForceClearScrollbackBuffer) + { + Console.Write("\u001b[3J"); + return; + } + if (!TestHasLanguageServer(warnUser: false)) { return; diff --git a/src/PowerShellEditorServices/Services/Workspace/LanguageServerSettings.cs b/src/PowerShellEditorServices/Services/Workspace/LanguageServerSettings.cs index d310903ae..63e85c3a5 100644 --- a/src/PowerShellEditorServices/Services/Workspace/LanguageServerSettings.cs +++ b/src/PowerShellEditorServices/Services/Workspace/LanguageServerSettings.cs @@ -21,6 +21,7 @@ internal class LanguageServerSettings public CodeFormattingSettings CodeFormatting { get; set; } public CodeFoldingSettings CodeFolding { get; set; } public PesterSettings Pester { get; set; } + public IntegratedConsoleSettings IntegratedConsole { get; set; } public string Cwd { get; set; } public bool EnableReferencesCodeLens { get; set; } = true; public bool AnalyzeOpenDocumentsOnly { get; set; } @@ -31,6 +32,7 @@ public LanguageServerSettings() CodeFormatting = new CodeFormattingSettings(); CodeFolding = new CodeFoldingSettings(); Pester = new PesterSettings(); + IntegratedConsole = new IntegratedConsoleSettings(); } public void Update( @@ -47,6 +49,7 @@ public void Update( CodeFormatting = new CodeFormattingSettings(settings.CodeFormatting); CodeFolding.Update(settings.CodeFolding, logger); Pester.Update(settings.Pester, logger); + IntegratedConsole = new IntegratedConsoleSettings(settings.IntegratedConsole); Cwd = settings.Cwd; EnableReferencesCodeLens = settings.EnableReferencesCodeLens; AnalyzeOpenDocumentsOnly = settings.AnalyzeOpenDocumentsOnly; @@ -55,6 +58,21 @@ public void Update( } } + internal class IntegratedConsoleSettings + { + public IntegratedConsoleSettings() { } + + public IntegratedConsoleSettings(IntegratedConsoleSettings integratedConsoleSettings) + { + if (integratedConsoleSettings is not null) + { + ForceClearScrollbackBuffer = integratedConsoleSettings.ForceClearScrollbackBuffer; + } + } + + public bool ForceClearScrollbackBuffer { get; set; } + } + internal class ScriptAnalysisSettings { private readonly object updateLock = new(); diff --git a/test/PowerShellEditorServices.Test/Extensions/EditorOperationsServiceTests.cs b/test/PowerShellEditorServices.Test/Extensions/EditorOperationsServiceTests.cs index 45ef4e538..df5779b63 100644 --- a/test/PowerShellEditorServices.Test/Extensions/EditorOperationsServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Extensions/EditorOperationsServiceTests.cs @@ -6,13 +6,21 @@ using Microsoft.Extensions.Logging.Abstractions; using Microsoft.PowerShell.EditorServices.Extensions; using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.Configuration; using Microsoft.PowerShell.EditorServices.Services.Extension; using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using Newtonsoft.Json; using OmniSharp.Extensions.LanguageServer.Protocol; using Xunit; namespace PowerShellEditorServices.Test.Extensions { + [CollectionDefinition("Console", DisableParallelization = true)] + public class ConsoleCollectionDefinition + { + } + + [Collection("Console")] [Trait("Category", "Extensions")] public class EditorOperationsServiceTests { @@ -36,7 +44,8 @@ public void GetWorkspaceOpenDocumentsReturnsOnlyOpenDocumentsAndCurrentInMemoryS EditorOperationsService editorOperationsService = new( psesHost: null, workspaceService, - languageServer: null); + languageServer: null, + new ConfigurationService()); WorkspaceOpenDocument[] documents = editorOperationsService.GetWorkspaceOpenDocuments(); @@ -60,7 +69,8 @@ public void GetWorkspaceOpenDocumentsTracksEditedAndUntitledSaveStates() EditorOperationsService editorOperationsService = new( psesHost: null, workspaceService, - languageServer: null); + languageServer: null, + new ConfigurationService()); WorkspaceOpenDocument[] initialDocuments = editorOperationsService.GetWorkspaceOpenDocuments(); Assert.Contains(initialDocuments, static document => document.Path.EndsWith("open-saved.ps1") && document.Saved); @@ -87,6 +97,45 @@ public void GetWorkspaceOpenDocumentsTracksEditedAndUntitledSaveStates() Assert.Contains(savedDocuments, static document => document.Path.StartsWith("untitled:", StringComparison.Ordinal) && !document.Saved); } + [Fact] + public void LanguageServerSettingsUpdatesForceClearScrollbackBuffer() + { + LanguageServerSettings incomingSettings = JsonConvert.DeserializeObject( + "{\"integratedConsole\":{\"forceClearScrollbackBuffer\":true}}"); + LanguageServerSettings currentSettings = new(); + + currentSettings.Update(incomingSettings, workspaceRootPath: string.Empty, NullLogger.Instance); + + Assert.True(currentSettings.IntegratedConsole.ForceClearScrollbackBuffer); + } + + [Fact] + public void ClearTerminalWritesEraseSavedLinesWhenConfigured() + { + WorkspaceService workspaceService = new(NullLoggerFactory.Instance); + ConfigurationService configurationService = new(); + configurationService.CurrentSettings.IntegratedConsole.ForceClearScrollbackBuffer = true; + EditorOperationsService editorOperationsService = new( + psesHost: null, + workspaceService, + languageServer: null, + configurationService); + StringWriter output = new(); + TextWriter originalOutput = Console.Out; + + try + { + Console.SetOut(output); + editorOperationsService.ClearTerminal(); + } + finally + { + Console.SetOut(originalOutput); + } + + Assert.Equal("\u001b[3J", output.ToString()); + } + private static ScriptFile CreateFileBuffer(WorkspaceService workspaceService, string fileName) { string filePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"), fileName);