bindings[$keyPattern] = empty($fallbackActions) ? $action : new FallbackAction([$action, ...$fallbackActions]); } /** * Get the action bound to a key. */ public function get(Key $key): ?ActionInterface { return $this->bindings[(string) $key] ?? null; } /** * Get all bindings. * * @return ActionInterface[] Keyed by key pattern */ public function getAll(): array { return $this->bindings; } /** * Create default Emacs-style keybindings. * * @param bool $smartBrackets Enable smart bracket pairing */ public static function createDefault(History $history, HistorySearch $search, bool $smartBrackets = false): self { $bindings = new self(); // Enter/Return $acceptLine = new FallbackAction([ new RejectSyntaxErrorAction(), new InsertLineBreakOnUnclosedBracketsAction($smartBrackets), new InsertLineBreakOnIncompleteStatementAction(), new SubmitLineAction(), ], false); $bindings->bind('char:'."\n", $acceptLine); $bindings->bind('char:'."\r", $acceptLine); // Shift+Enter variants (CSI-u / modifyOtherKeys) $bindings->bind('escape:[13;2u', new InsertLineBreakAction()); $bindings->bind('escape:[13;2~', new InsertLineBreakAction()); $bindings->bind('escape:[27;2;13~', new InsertLineBreakAction()); // Shift+Enter remapped by some terminal setups (Esc+Enter) $bindings->bind('escape:'."\r", new InsertLineBreakAction()); $bindings->bind('escape:'."\n", new InsertLineBreakAction()); // Backspace $backspace = $smartBrackets ? new FallbackAction([new DedentLeadingIndentationAction(), new DeleteBracketPairAction(), new DeleteBackwardCharAction()]) : new FallbackAction([new DedentLeadingIndentationAction(), new DeleteBackwardCharAction()]); $bindings->bind('control:h', $backspace); $bindings->bind('control:?', $backspace); // Delete $bindings->bind('escape:[3~', new DeleteForwardAction()); // Arrow keys $bindings->bind('escape:[D', new MoveLeftAction()); $bindings->bind('escape:[C', new AcceptSuggestionAction(), new MoveRightAction()); $bindings->bind('escape:[A', new PreviousHistoryAction($history)); $bindings->bind('escape:[B', new NextHistoryAction($history)); // Emacs cursor movement $bindings->bind('control:b', new MoveLeftAction()); $bindings->bind('control:f', new AcceptSuggestionAction(), new MoveRightAction()); $bindings->bind('control:a', new MoveToStartAction()); $bindings->bind('control:e', new MoveToEndAction()); // Word movement - Alt+Arrow $bindings->bind('escape:[1;3D', new MoveWordLeftAction()); $bindings->bind('escape:[1;3C', new AcceptSuggestionWordAction(), new MoveWordRightAction()); // Word movement - Ctrl+Arrow $bindings->bind('escape:[1;5D', new MoveWordLeftAction()); $bindings->bind('escape:[1;5C', new MoveWordRightAction()); // Word movement - Emacs style $bindings->bind('escape:b', new MoveWordLeftAction()); $bindings->bind('escape:f', new AcceptSuggestionWordAction(), new MoveWordRightAction()); // Home/End keys $bindings->bind('escape:[H', new MoveToStartAction()); $bindings->bind('escape:[F', new MoveToEndAction()); $bindings->bind('escape:[1~', new MoveToStartAction()); $bindings->bind('escape:[4~', new MoveToEndAction()); // Kill operations $bindings->bind('control:k', new KillLineAction()); $bindings->bind('control:u', new KillWholeLineAction()); $bindings->bind('control:w', new KillWordAction()); // Buffer control $bindings->bind('control:c', new ClearBufferAction()); $bindings->bind('control:d', new ExitIfEmptyAction(), new DeleteForwardAction()); // History search $bindings->bind('control:r', new ReverseSearchAction($search)); // Clear screen $bindings->bind('control:l', new ClearScreenAction()); // Smart bracket pairing if ($smartBrackets) { // Opening brackets $bindings->bind('char:(', new InsertOpenBracketAction('(')); $bindings->bind('char:[', new InsertOpenBracketAction('[')); $bindings->bind('char:{', new InsertOpenBracketAction('{')); // Closing brackets $bindings->bind('char:)', new InsertCloseBracketAction(')')); $bindings->bind('char:]', new InsertCloseBracketAction(']')); $bindings->bind('char:}', new InsertCloseBracketAction('}')); // Quotes $bindings->bind('char:"', new InsertQuoteAction('"')); $bindings->bind("char:'", new InsertQuoteAction("'")); } return $bindings; } }