namespace DodoSSH.Client.Shell.Keyboard; /// /// What a latched Ctrl or Alt does to the key pressed after it. /// /// /// /// The encodings xterm uses, and nothing cleverer. Ctrl with a character is that character's /// control form — c & 0x1F, the rule caret notation names. Alt is an Escape in front, which is /// what readline's M-b and M-f and every Emacs binding expect from a terminal. Ctrl or Alt with /// a cursor key is the modified CSI form, CSI 1 ; m X, which is how Ctrl+← jumps a word in bash and /// zsh. /// /// /// Anything else goes through untouched. Ctrl with a pasted line or with a function key has no /// encoding a shell agrees on, and mangling one into something else would be worse than ignoring the /// latch. The latch still releases, because the person pressed the key it was latched for. /// /// internal static class TerminalModifiers { /// Applies whichever modifiers are latched to one key's worth of bytes. internal static byte[] Apply(byte[] payload, bool control, bool alt) { ArgumentNullException.ThrowIfNull(payload); if (!control && !alt) { return payload; } // ESC [ X for the six cursor keys that have a modified form: the arrows, Home and End. if (payload is [0x1B, (byte)'[', var final] && final is (byte)'A' or (byte)'B' or (byte)'C' or (byte)'D' or (byte)'H' or (byte)'F') { var modifier = 1 + (alt ? 2 : 0) + (control ? 4 : 0); return [0x1B, (byte)'[', (byte)'1', (byte)';', (byte)('0' + modifier), final]; } var result = payload; if (control && payload is [var single]) { result = [ControlForm(single)]; } return alt ? [0x1B, .. result] : result; } /// The control character for one byte, or the byte itself where there is none. /// Space as NUL is the one addition to caret notation, and every terminal makes it. private static byte ControlForm(byte c) => c == (byte)' ' ? (byte)0x00 : c < 0x80 && KeySequence.TryControl((char)c, out var control) ? control : c; }