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:
@@ -4,7 +4,8 @@
|
||||
xmlns:term="using:Iciclecreek.Terminal"
|
||||
x:Class="ClaudeDo.Ui.Views.InteractiveTerminalView"
|
||||
x:DataType="vm:InteractiveTerminalViewModel"
|
||||
x:Name="Root">
|
||||
x:Name="Root"
|
||||
DragDrop.AllowDrop="True">
|
||||
<!--
|
||||
Process="" suppresses TerminalView.OnLoaded's built-in auto-launch (it otherwise spawns its
|
||||
own cmd.exe/bash the moment this control loads). PtyTerminalSession sets Process/Args/
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Platform.Storage;
|
||||
using ClaudeDo.Ui.ViewModels;
|
||||
|
||||
namespace ClaudeDo.Ui.Views;
|
||||
@@ -12,6 +16,12 @@ public partial class InteractiveTerminalView : UserControl
|
||||
InitializeComponent();
|
||||
TerminalHost.Loaded += OnTerminalHostLoaded;
|
||||
DataContextChanged += OnDataContextChanged;
|
||||
|
||||
// Tunnel: TerminalControl handles KeyDown itself and forwards Ctrl+V to the child as a raw
|
||||
// control byte, where nothing acts on it — paste has to be intercepted before it gets there.
|
||||
AddHandler(KeyDownEvent, OnPreviewKeyDown, RoutingStrategies.Tunnel);
|
||||
AddHandler(DragDrop.DragOverEvent, OnDragOver);
|
||||
AddHandler(DragDrop.DropEvent, OnDrop);
|
||||
}
|
||||
|
||||
private void OnDataContextChanged(object? sender, EventArgs e)
|
||||
@@ -25,4 +35,44 @@ public partial class InteractiveTerminalView : UserControl
|
||||
if (DataContext is InteractiveTerminalViewModel vm)
|
||||
vm.AttachControl(TerminalHost);
|
||||
}
|
||||
|
||||
private void OnPreviewKeyDown(object? sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key != Key.V || !e.KeyModifiers.HasFlag(KeyModifiers.Control) || e.KeyModifiers.HasFlag(KeyModifiers.Alt))
|
||||
return;
|
||||
if (DataContext is not InteractiveTerminalViewModel vm) return;
|
||||
|
||||
e.Handled = true;
|
||||
_ = vm.PasteAsync();
|
||||
}
|
||||
|
||||
private static bool IsFilesDrop(DragEventArgs e) => e.DataTransfer?.Contains(DataFormat.File) == true;
|
||||
|
||||
private void OnDragOver(object? sender, DragEventArgs e)
|
||||
{
|
||||
e.DragEffects = IsFilesDrop(e) ? DragDropEffects.Copy : DragDropEffects.None;
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
// Dropping files types their paths into the session's composer (no Enter) — the common case is
|
||||
// "look at this file", not "run something".
|
||||
private void OnDrop(object? sender, DragEventArgs e)
|
||||
{
|
||||
if (DataContext is not InteractiveTerminalViewModel vm || !IsFilesDrop(e)) return;
|
||||
e.Handled = true;
|
||||
|
||||
var text = FormatDroppedPaths(e.DataTransfer!.TryGetFiles()?.Select(i => i.TryGetLocalPath()));
|
||||
if (text.Length > 0) _ = vm.SendTextAsync(text);
|
||||
}
|
||||
|
||||
/// <summary>Joins dropped file paths into one insertable chunk: quoted when they contain a
|
||||
/// space, space-separated, with a trailing space so the user can keep typing.</summary>
|
||||
internal static string FormatDroppedPaths(IEnumerable<string?>? paths)
|
||||
{
|
||||
var usable = (paths ?? Enumerable.Empty<string?>())
|
||||
.Where(p => !string.IsNullOrWhiteSpace(p))
|
||||
.Select(p => p!.Contains(' ') ? $"\"{p}\"" : p)
|
||||
.ToList();
|
||||
return usable.Count == 0 ? string.Empty : string.Join(" ", usable) + " ";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user