fix(ui): deliver AltGr characters to ConPTY tiles as text
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
3dc735b2af
commit
cf3db9197c
@@ -327,6 +327,8 @@ bails out immediately instead of racing past the collection-based dedupe.
|
|||||||
(scoped to `MainWindow` via `AddClassHandler<MainWindow>`) never runs there — **Escape always
|
(scoped to `MainWindow` via `AddClassHandler<MainWindow>`) never runs there — **Escape always
|
||||||
reaches the PTY**. See the note in `src/ClaudeDo.Ui/CLAUDE.md`.
|
reaches the PTY**. See the note in `src/ClaudeDo.Ui/CLAUDE.md`.
|
||||||
|
|
||||||
|
**AltGr bypass:** Windows reports AltGr as Ctrl+Alt, and Iciclecreek's `TerminalView.OnKeyDown` turns any Ctrl/Alt combo into a control sequence (NUL/0x1C/ESC) and marks it handled, so `@ \ { } € ~ |` never arrive as text. `InteractiveTerminalView.OnPreviewKeyDown` (tunnel) detects it via `IsAltGrText` and sends `KeySymbol` through `SendTextAsync` first.
|
||||||
|
|
||||||
`TaskRowViewModel.HasInteractiveSession` shows an accent "Interactive" chip instead of "Parked";
|
`TaskRowViewModel.HasInteractiveSession` shows an accent "Interactive" chip instead of "Parked";
|
||||||
tapping it jumps to that Mission Control pane. `TasksIslandViewModel.SyncInteractiveSessions`
|
tapping it jumps to that Mission Control pane. `TasksIslandViewModel.SyncInteractiveSessions`
|
||||||
mirrors Mission Control's open panes onto the rows.
|
mirrors Mission Control's open panes onto the rows.
|
||||||
|
|||||||
@@ -40,6 +40,15 @@ public partial class InteractiveTerminalView : UserControl
|
|||||||
|
|
||||||
private void OnPreviewKeyDown(object? sender, KeyEventArgs e)
|
private void OnPreviewKeyDown(object? sender, KeyEventArgs e)
|
||||||
{
|
{
|
||||||
|
// AltGr arrives as Ctrl+Alt; the library would turn it into a control sequence (and mark it handled).
|
||||||
|
if (IsAltGrText(e.KeyModifiers, e.KeySymbol))
|
||||||
|
{
|
||||||
|
if (DataContext is not InteractiveTerminalViewModel altGrVm) return;
|
||||||
|
e.Handled = true;
|
||||||
|
_ = SendTextAndReportAsync(altGrVm, e.KeySymbol!);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (e.Key != Key.V || !e.KeyModifiers.HasFlag(KeyModifiers.Control) || e.KeyModifiers.HasFlag(KeyModifiers.Alt))
|
if (e.Key != Key.V || !e.KeyModifiers.HasFlag(KeyModifiers.Control) || e.KeyModifiers.HasFlag(KeyModifiers.Alt))
|
||||||
return;
|
return;
|
||||||
if (DataContext is not InteractiveTerminalViewModel vm) return;
|
if (DataContext is not InteractiveTerminalViewModel vm) return;
|
||||||
@@ -120,6 +129,12 @@ public partial class InteractiveTerminalView : UserControl
|
|||||||
vm.ReportSendFailed();
|
vm.ReportSendFailed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>True for an AltGr keystroke (Windows reports it as Ctrl+Alt) whose
|
||||||
|
/// <paramref name="keySymbol"/> is already a single printable character, e.g. "@" or "\".</summary>
|
||||||
|
internal static bool IsAltGrText(KeyModifiers modifiers, string? keySymbol) =>
|
||||||
|
modifiers.HasFlag(KeyModifiers.Control) && modifiers.HasFlag(KeyModifiers.Alt)
|
||||||
|
&& keySymbol is { Length: 1 } && !char.IsControl(keySymbol[0]);
|
||||||
|
|
||||||
/// <summary>Joins dropped file paths into one insertable chunk: quoted when they contain a
|
/// <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>
|
/// space, space-separated, with a trailing space so the user can keep typing.</summary>
|
||||||
internal static string FormatDroppedPaths(IEnumerable<string?>? paths)
|
internal static string FormatDroppedPaths(IEnumerable<string?>? paths)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Avalonia.Input;
|
||||||
using ClaudeDo.Ui.Views;
|
using ClaudeDo.Ui.Views;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
@@ -23,4 +24,18 @@ public class InteractiveTerminalDropPathsTests
|
|||||||
Assert.Equal(string.Empty, InteractiveTerminalView.FormatDroppedPaths(new string?[] { null }));
|
Assert.Equal(string.Empty, InteractiveTerminalView.FormatDroppedPaths(new string?[] { null }));
|
||||||
Assert.Equal(string.Empty, InteractiveTerminalView.FormatDroppedPaths(null));
|
Assert.Equal(string.Empty, InteractiveTerminalView.FormatDroppedPaths(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(KeyModifiers.Control | KeyModifiers.Alt, "@", true)]
|
||||||
|
[InlineData(KeyModifiers.Control | KeyModifiers.Alt, "\\", true)]
|
||||||
|
[InlineData(KeyModifiers.Control, "c", false)]
|
||||||
|
[InlineData(KeyModifiers.Alt, "@", false)]
|
||||||
|
[InlineData(KeyModifiers.None, "@", false)]
|
||||||
|
[InlineData(KeyModifiers.Control | KeyModifiers.Alt, "\u0003", false)]
|
||||||
|
[InlineData(KeyModifiers.Control | KeyModifiers.Alt, "", false)]
|
||||||
|
[InlineData(KeyModifiers.Control | KeyModifiers.Alt, null, false)]
|
||||||
|
public void IsAltGrText_only_for_ctrl_alt_with_one_printable_char(KeyModifiers modifiers, string? symbol, bool expected)
|
||||||
|
{
|
||||||
|
Assert.Equal(expected, InteractiveTerminalView.IsAltGrText(modifiers, symbol));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user