feat(ui): Einfuegen und Datei-Drop im interaktiven Terminal

Ctrl+V wurde von TerminalControl als rohes Control-Byte an das Kind
weitergegeben und tat nichts; jetzt wird es im Tunnel abgefangen und geht
ueber TerminalView.PasteAsync, damit Bracketed Paste greift und ein
mehrzeiliger Paste nicht auf der ersten Zeile abschickt. Gedroppte Dateien
werden als Pfade in den Composer getippt, ohne Enter.
This commit is contained in:
mika kuns
2026-09-07 08:17:37 +02:00
parent 0db9fadee9
commit 23491d3b49
5 changed files with 122 additions and 1 deletions
@@ -137,6 +137,42 @@ public sealed class PtyTerminalSession : IDisposable
}
}
/// <summary>
/// Inserts <paramref name="text"/> at the cursor without submitting it — the drop-a-file-path
/// case. Same write seam as <see cref="SendLineAsync"/>, just without the Enter.
/// </summary>
public async Task<bool> SendTextAsync(string text)
{
if (_control is not { } control || !IsRunning || string.IsNullOrEmpty(text)) return false;
try
{
await SendRawAsync(control, text);
return true;
}
catch (Exception)
{
return false;
}
}
/// <summary>
/// Pastes the clipboard into the session. <see cref="TerminalControl"/> wires paste to the
/// right-click menu only, so Ctrl+V otherwise reaches the child as a raw control byte and does
/// nothing. Goes through the emulator's own <see cref="TerminalView.PasteAsync"/> rather than
/// writing the clipboard text raw, because that is what honours bracketed-paste mode — without
/// it a multi-line paste is read line by line and submits on the first newline.
/// </summary>
public async Task PasteAsync()
{
if (_control is not { } control || !IsRunning) return;
try
{
if (s_terminalViewField?.GetValue(control) is TerminalView view)
await view.PasteAsync();
}
catch (Exception) { /* best-effort, same as SendLineAsync */ }
}
// Long enough that the two writes can't be read as one chunk by the child's stdin reader.
private static readonly TimeSpan SubmitDelay = TimeSpan.FromMilliseconds(150);