diff --git a/src/DevSpaces/DevSpaceRegistry.cs b/src/DevSpaces/DevSpaceRegistry.cs index 9ae2f76be..75768f681 100644 --- a/src/DevSpaces/DevSpaceRegistry.cs +++ b/src/DevSpaces/DevSpaceRegistry.cs @@ -12,15 +12,6 @@ public static ViewModels.DevSpaces GetOrCreate(ViewModels.Repository repository) return GetOrCreateEntry(repository)?.Model; } - public static void PrepareForRepositorySwitch(ViewModels.Repository repository) - { - if (repository == null || string.IsNullOrEmpty(repository.FullPath)) - return; - - if (_spaces.TryGetValue(repository.FullPath, out var entry)) - entry.Model.ActivateDashboard(); - } - public static ViewModels.DevSpaces Attach(ViewModels.Repository repository, Border host) { if (repository == null || host == null) diff --git a/src/DevSpaces/TerminalViewportState.cs b/src/DevSpaces/TerminalViewportState.cs new file mode 100644 index 000000000..f21509760 --- /dev/null +++ b/src/DevSpaces/TerminalViewportState.cs @@ -0,0 +1,51 @@ +using System; + +namespace DevBoard.DevSpaces +{ + internal sealed class TerminalViewportState + { + public void Update(int columns, int rows) + { + if (columns <= 0 || rows <= 0) + return; + + lock (_gate) + { + _columns = columns; + _rows = rows; + _apply?.Invoke(columns, rows); + } + } + + public void Attach(Action apply) + { + ArgumentNullException.ThrowIfNull(apply); + + lock (_gate) + { + _apply = apply; + apply(_columns, _rows); + } + } + + public void Detach() + { + lock (_gate) + _apply = null; + } + + public (int Columns, int Rows) Current + { + get + { + lock (_gate) + return (_columns, _rows); + } + } + + private readonly object _gate = new(); + private Action _apply; + private int _columns = 80; + private int _rows = 25; + } +} diff --git a/src/DevSpaces/WindowsTerminalDevSpaceSurface.cs b/src/DevSpaces/WindowsTerminalDevSpaceSurface.cs index a842fb804..785f83399 100644 --- a/src/DevSpaces/WindowsTerminalDevSpaceSurface.cs +++ b/src/DevSpaces/WindowsTerminalDevSpaceSurface.cs @@ -55,18 +55,20 @@ public async Task StartAsync(DevSpaceLaunchSpec spec) await _host.WaitForNativeCreatedAsync(_cts.Token).ConfigureAwait(false); + var viewport = _viewport.Current; var options = new PtyOptions { Name = spec.Process, App = spec.Process, CommandLine = spec.Arguments, Cwd = spec.WorkingDirectory, - Cols = 80, - Rows = 25, + Cols = viewport.Columns, + Rows = viewport.Rows, }; _pty = await PtyProvider.SpawnAsync(options, _cts.Token).ConfigureAwait(false); _pty.ProcessExited += OnProcessExited; + _viewport.Attach(ResizePty); _acceptInput = true; _readerTask = ReadOutputAsync(_cts.Token); @@ -97,6 +99,7 @@ public void Stop() _host.TerminalResized -= OnTerminalResized; _host.ScrollChanged -= OnScrollChanged; _scrollBar.PropertyChanged -= OnScrollBarPropertyChanged; + _viewport.Detach(); var pty = _pty; _pty = null; @@ -208,9 +211,11 @@ private void OnInputGenerated(string text) private void OnTerminalResized(int cols, int rows) { - if (cols <= 0 || rows <= 0) - return; + _viewport.Update(cols, rows); + } + private void ResizePty(int cols, int rows) + { try { _pty?.Resize(cols, rows); @@ -276,6 +281,7 @@ private void RaiseExited(int exitCode) private readonly Views.WindowsTerminalNativeHost _host = new(); private readonly ScrollBar _scrollBar = new(); private readonly TerminalTranscriptSink _transcriptSink; + private readonly TerminalViewportState _viewport = new(); private readonly CancellationTokenSource _cts = new(); private readonly Channel _input = Channel.CreateUnbounded(new UnboundedChannelOptions { diff --git a/tests/DevBoard.Tests/TerminalViewportStateDetachTests.cs b/tests/DevBoard.Tests/TerminalViewportStateDetachTests.cs new file mode 100644 index 000000000..5f0e6a1ba --- /dev/null +++ b/tests/DevBoard.Tests/TerminalViewportStateDetachTests.cs @@ -0,0 +1,20 @@ +using DevBoard.DevSpaces; +using Xunit; + +namespace DevBoard.Tests; + +public class TerminalViewportStateDetachTests +{ + [Fact] + public void Detach_StopsApplyingFutureResizeEvents() + { + var state = new TerminalViewportState(); + var calls = 0; + state.Attach((_, _) => calls++); + state.Detach(); + + state.Update(140, 48); + + Assert.Equal(1, calls); + } +} diff --git a/tests/DevBoard.Tests/TerminalViewportStateTests.cs b/tests/DevBoard.Tests/TerminalViewportStateTests.cs new file mode 100644 index 000000000..5008a2c04 --- /dev/null +++ b/tests/DevBoard.Tests/TerminalViewportStateTests.cs @@ -0,0 +1,44 @@ +using DevBoard.DevSpaces; +using Xunit; + +namespace DevBoard.Tests; + +public class TerminalViewportStateTests +{ + [Fact] + public void Attach_ReplaysSizeObservedBeforePtyExists() + { + var state = new TerminalViewportState(); + state.Update(132, 44); + + (int Columns, int Rows)? applied = null; + state.Attach((columns, rows) => applied = (columns, rows)); + + Assert.Equal((132, 44), applied); + } + + [Fact] + public void Update_AppliesNewSizeAfterPtyIsAttached() + { + var state = new TerminalViewportState(); + (int Columns, int Rows)? applied = null; + state.Attach((columns, rows) => applied = (columns, rows)); + + state.Update(101, 37); + + Assert.Equal((101, 37), applied); + } + + [Fact] + public void InvalidSize_DoesNotReplaceLastValidViewport() + { + var state = new TerminalViewportState(); + state.Update(120, 40); + state.Update(0, 0); + + (int Columns, int Rows)? applied = null; + state.Attach((columns, rows) => applied = (columns, rows)); + + Assert.Equal((120, 40), applied); + } +}