Class: PWN::Plugins::REPL::PWNMultiLineInput
- Inherits:
-
Object
- Object
- PWN::Plugins::REPL::PWNMultiLineInput
- Defined in:
- lib/pwn/plugins/repl.rb
Overview
Custom input handler for pwn-ai and pwn-asm to support multi-line submissions:
-
Use only SHIFT+ENTER to insert a newline (continue editing).
-
Plain ENTER submits the full (possibly multi-line) buffer.
-
Multi-line pastes are supported (Reline handles n in buffer; submit with ENTER).
Strict SHIFT+ENTER only — no Ctrl+J, Alt-Enter, or other fallbacks (per requirements).
Constant Summary collapse
- SHIFT_ENTER_SEQS =
SHIFT+ENTER escape sequences (byte arrays). These are terminal-dependent. Listed common ones for xterm, VTE (terminator), kitty, wezterm, etc. (with modifyOtherKeys / extended-keys enabled).
For tmux + terminator (or similar):
In ~/.tmux.conf (then `tmux kill-server` + new session): set -g extended-keys on set -g xterm-keys on Use TERM=xterm-256color (or equivalent that supports the CSI) in your terminal profile.The bindings make matching sequences produce :key_newline (insert n without submit).
If after typing text + SHIFT+ENTER it still submits instead of newline:
1. Apply the tmux.conf + TERM changes above and fully restart tmux. 2. In your *real* terminal (the one running `pwn`), run a capture script from /tmp ONLY: ruby /tmp/capture_keys.rb (Debugging scripts must live in /tmp per user rule; never commit them to /opt/pwn.) 3. Paste the exact bytes array for the SHIFT+ENTER press here so it can be added to the list. [ [27, 91, 49, 51, 59, 50, 126], # \e[13;2~ [27, 91, 50, 55, 59, 50, 59, 49, 51, 126], # \e[27;2;13~ [27, 91, 49, 51, 59, 50, 117], # \e[13;2u (CSI u) [27, 91, 50, 55, 59, 50, 59, 49, 51, 117], # \e[27;2;13u [27, 91, 49, 59, 50, 126], # \e[1;2~ [27, 13], # \e\r (ESC+CR variant) [27, 10], # \e\n (ESC+LF variant) [27, 91, 13, 59, 50, 126], # \e[13;2~ alt numeric [27, 91, 49, 59, 50, 117], # \e[1;2u [27, 91, 50, 55, 59, 50, 13, 126], # \e[27;2;13~ variant [27, 79, 77] # \eOM (application-keypad Enter; some emulators emit this for S-Enter) ].freeze
- ENABLE_EXTENDED_KEYS =
CSI sequences that ask the terminal to start/stop encoding Shift+Enter (and other modified keys) distinctly from plain Enter. Without one of these active, most emulators send the SAME byte (0x0D) for both, so SHIFT_ENTER_SEQS can never match.
\e[>4;1m / \e[>4;0m xterm modifyOtherKeys on/off (level 1 — disambiguates Shift+Enter without altering Ctrl-C). xterm, VTE/Terminator, iTerm2, Konsole. tmux ≥3.2 with `extended-keys on` honours this request and re-encodes as CSI-u to the inner app. \e[>1u / \e[<u kitty keyboard protocol push/pop, flags=1 "disambiguate escape codes". kitty, wezterm, foot, ghostty, alacritty, recent tmux.Emitting both is harmless on terminals that support neither —they’re DEC-private CSIs and get silently ignored.
"\e[>4;1m\e[>1u"- DISABLE_EXTENDED_KEYS =
"\e[<u\e[>4;0m"
Instance Attribute Summary collapse
-
#line_buffer ⇒ Object
readonly
Returns the value of attribute line_buffer.
Instance Method Summary collapse
-
#initialize(pry_instance) ⇒ PWNMultiLineInput
constructor
A new instance of PWNMultiLineInput.
-
#install_shift_enter_bindings ⇒ Object
Register SHIFT+ENTER → :key_newline on Reline’s default keymaps.
- #readline(prompt) ⇒ Object
-
#reline_config ⇒ Object
Reline ≤ 0.5.x exposed a top-level ‘Reline.config` delegator.
-
#tty? ⇒ Boolean
Compatibility with Pry input expectations.
- #winsize ⇒ Object
Constructor Details
#initialize(pry_instance) ⇒ PWNMultiLineInput
Returns a new instance of PWNMultiLineInput.
77 78 79 80 81 |
# File 'lib/pwn/plugins/repl.rb', line 77 def initialize(pry_instance) @line_buffer = '' pry_instance.config.pwn_ai_original_input = Pry.input install_shift_enter_bindings end |
Instance Attribute Details
#line_buffer ⇒ Object (readonly)
Returns the value of attribute line_buffer.
23 24 25 |
# File 'lib/pwn/plugins/repl.rb', line 23 def line_buffer @line_buffer end |
Instance Method Details
#install_shift_enter_bindings ⇒ Object
Register SHIFT+ENTER → :key_newline on Reline’s default keymaps.
IMPORTANT: do NOT use add_oneshot_key_binding for this. Reline’s LineEditor#input_key calls reset_oneshot_key_bindings on EVERY keystroke (it’s designed for dialog trap-keys = “next keypress only”), so oneshot bindings are wiped the moment the user types their first character — Shift+Enter then falls through as an unrecognised CSI and is silently swallowed. Default-keymap bindings persist for the life of the Config object.
Scoping is handled by the input-handler swap, not the binding lifetime: outside pwn-ai/pwn-asm, Pry uses its own input, PWNMultiLineInput#readline never runs, ENABLE_EXTENDED_KEYS is never emitted, the terminal sends plain 0x0D for Shift+Enter, and these bindings never match. So registering once at construction is safe.
109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/pwn/plugins/repl.rb', line 109 def install_shift_enter_bindings return if self.class.instance_variable_get(:@shift_enter_installed) cfg = reline_config %i[emacs vi_insert].each do |keymap| SHIFT_ENTER_SEQS.each do |seq| cfg.add_default_key_binding_by_keymap(keymap, seq, :key_newline) end end self.class.instance_variable_set(:@shift_enter_installed, true) end |
#readline(prompt) ⇒ Object
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
# File 'lib/pwn/plugins/repl.rb', line 121 def readline(prompt) # Ask the terminal to encode Shift+Enter distinctly from Enter for # the duration of this read. Without this, most emulators send 0x0D # for both and SHIFT_ENTER_SEQS can never match. Reset in `ensure`. tty = $stdout.respond_to?(:tty?) && $stdout.tty? if tty $stdout.write(ENABLE_EXTENDED_KEYS) $stdout.flush end begin # readmultiline with confirm block that *always* returns true: # => default (plain) ENTER triggers finish/submit of the (multi-line) buffer # SHIFT+ENTER (matched seq) triggers :key_newline (insert \n, stay in edit mode) # Reline handles multi-line pastes by splitting on \n in the buffer. @line_buffer = Reline.readmultiline(prompt, true) { |_buffer| true } || '' ensure if tty $stdout.write(DISABLE_EXTENDED_KEYS) $stdout.flush end end @line_buffer end |
#reline_config ⇒ Object
Reline ≤ 0.5.x exposed a top-level ‘Reline.config` delegator. Reline ≥ 0.6.x removed it; the Config object now lives only on the (private) singleton `Reline.core`. Probe in order of preference so the same code works across both.
87 88 89 90 91 92 |
# File 'lib/pwn/plugins/repl.rb', line 87 def reline_config return Reline.config if Reline.respond_to?(:config) return Reline.core.config if Reline.respond_to?(:core) Reline.send(:core).config end |
#tty? ⇒ Boolean
Compatibility with Pry input expectations
147 148 149 |
# File 'lib/pwn/plugins/repl.rb', line 147 def tty? true end |
#winsize ⇒ Object
151 152 153 |
# File 'lib/pwn/plugins/repl.rb', line 151 def winsize [TTY::Screen.rows || 24, TTY::Screen.columns || 80] end |