Remove Emacs-like shortcuts from `TextArea::input`
rhysd opened this issue · 11 comments
The problem
Currently TextArea::input
supports Emacs-like keyboard shortcuts. This was a good idea when tui-textarea had less features. However, now tui-textarea supports more features and adds more default key shortcuts and running out key combinations. And now it causes some conflicts.
C-a
is used for moving to head of line in Emacs, but usually it is used for selecting all text in normal textareaC-v
is used for moving to the next page in Emacs, but usually it is used for pasting yanked text
Current default key shortcuts can be found here: https://github.com/rhysd/tui-textarea#minimal-usage
Emacs solves the problem by key sequence (like C-x b
) but tui-textarea doesn't have the functionality (and should not have for avoiding complexity).
tui-textarea aims to be something like <textarea>
element in HTML as described in the first sentence of README.md. So the default shortcuts should be intuitive and kept simple.
The solution
Remove Emacs-like shortcuts from TextArea::input
and add TextArea::input_with_emacs_shortcuts
instead.
Supported key shortcuts are as follows.
TextArea::input
Mappings | Description |
---|---|
Ctrl+H , Backspace |
Delete one character before cursor |
Delete |
Delete one character next to cursor |
Enter |
Insert newline |
Alt+Backspace |
Delete one word before cursor |
Alt+Delete |
Delete one word next to cursor |
Ctrl+Z |
Undo |
Alt+Z |
Redo |
Ctrl+C |
Copy selected text |
Ctrl+X |
Cut selected text |
Ctrl+V |
Paste yanked text |
Ctrl+A |
Select all text |
Ctrl+F , → |
Move cursor forward by one character |
Ctrl+B , ← |
Move cursor backward by one character |
Ctrl+P , ↑ |
Move cursor up by one line |
Ctrl+N , ↓ |
Move cursor down by one line |
Ctrl+→ |
Move cursor forward by word |
Ctrl+← |
Move cursor backward by word |
Ctrl+↑ |
Move cursor up by paragraph |
Ctrl+↓ |
Move cursor down by paragraph |
End , Ctrl+Alt+→ |
Move cursor to the end of line |
Home , Ctrl+Alt+← |
Move cursor to the head of line |
Ctrl+Alt+↑ |
Move cursor to top of lines |
Ctrl+Alt+↓ |
Move cursor to bottom of lines |
PageDown |
Scroll down by page |
PageUp |
Scroll up by page |
Note: Alt+Z
is unusual, but Ctrl+Shift+Z
cannot be detected due to limitation of the terminal escape sequence.
Note: Ctrl+N
, Ctrl+P
, Ctrl+F
, Ctrl+B
, Ctrl+H
remain because they are supported on macOS by default key shortcuts.
TextArea::input_with_emacs_shortcuts
Mappings | Description |
---|---|
Ctrl+H , Backspace |
Delete one character before cursor |
Ctrl+D , Delete |
Delete one character next to cursor |
Ctrl+M , Enter |
Insert newline |
Ctrl+K |
Delete from cursor until the end of line |
Ctrl+J |
Delete from cursor until the head of line |
Ctrl+W , Alt+H |
Delete one word before cursor |
Alt+D |
Delete one word next to cursor |
Ctrl+U |
Undo |
Ctrl+R |
Redo |
Ctrl+Y |
Paste yanked text |
Ctrl+Space |
Start selection |
Ctrl+W |
Cut selected text |
Alt+W |
Copy selected text |
Ctrl+G |
Cancel selection |
Ctrl+F , → |
Move cursor forward by one character |
Ctrl+B , ← |
Move cursor backward by one character |
Ctrl+P , ↑ |
Move cursor up by one line |
Ctrl+N , ↓ |
Move cursor down by one line |
Alt+F |
Move cursor forward by word |
Atl+B |
Move cursor backward by word |
Alt+] , Alt+P |
Move cursor up by paragraph |
Alt+[ , Alt+N |
Move cursor down by paragraph |
Ctrl+E , End , Ctrl+Alt+F |
Move cursor to the end of line |
Ctrl+A , Home , Ctrl+Alt+B |
Move cursor to the head of line |
Alt+< , Ctrl+Alt+P , |
Move cursor to top of lines |
Alt+> , Ctrl+Alt+N , |
Move cursor to bottom of lines |
Ctrl+V , PageDown |
Scroll down by page |
Alt+V , PageUp |
Scroll up by page |
I certainly think that ctrlz,ctrlx,ctrlc,ctrlv should be standard. And ctrla. I added ctrlc and ctrlx in the select PR
BTW - I am working on a table lookup key dispatch mechanism making it much easier for the caller to change the key mappings. At present I have basically had to copy and paste the entire input function into my gitui code just to tweak a few keys
I think most CLI users would expect Emacs key bindings to work as a default (I actually use Ctrl+A/E/U/etc. everywhere in macOS as there's an OS level switch for turning on the shortcuts), and Ctrl+C to always mean SIGINT rather than copy. Strong preference for keeping that the default so that apps built with tui-textarea generally "just work", and instead making the non-emacs mode the opt-in / configurable approach.
Ctrlc does not always equal sigint . Raw mode passes it through as a plain ol keystroke. Interestingly this is bound to the other new issue about clipboard etc
Sure - but IMO your app should probably do something reasonable with that rather than treating it as copy at least in the default situation.
It is up to the app author to decide what ctrlc should do. If they pass that keystroke to tta rather than acting on it themselves then doing a copy is reasonable. The app I am working on exits cleanly on ctrlc but if tta has focus then it will pass it to tta
I see your point that its the app creator that decides, but I'd caution that there's a problem with that approach. It leads to building apps that all act slightly inconsistently with respect to how they handle keystrokes. For the most part I find it jarring when an application changes widely held conventions. Ctrl+C is basically never copy on macOS (that's Cmd+C). So my point isn't that the shortcuts shouldn't be customizable - it should be. My point is that out of the box the defaults should respect and be consistent with the operating system defaults.
P.S. if you're interested, there's some discussion about how to design keybinding more generally at ratatui-org/ratatui#627. It would be worth feeding some ideas about how libs can take advantage of something like that.
Sorry for the delay in response. I was working on refactorying and bug fix.
I am working on a table lookup key dispatch mechanism making it much easier for the caller to change the key mappings.
It would be useful as a separate crate. For me, match
statement is powerful enough to define basic key bindings and don't want more flexibility. I saw some discussion about key bindings implementation in the forum of ratatui repository as @joshka pointed. You may be interested in it.
everywhere in macOS as there's an OS level switch for turning on the shortcuts), and Ctrl+C to always mean SIGINT rather than copy. Strong preference for keeping that the default so that apps built with tui-textarea generally "just work", and instead making the non-emacs mode the opt-in / configurable approach.
Ah, nice point. I've not thought about C-c sends signal. Is it common sense for general ratatui applications? demo
example in ratatui does not stop on C-c. I would expect C-c to stop the process when the application is non-interactive. But I'm not sure the expectation can be applied to interactive applications.
General TUI app users' expectations would be:
- They would expect basic Emacs shortcuts like C-f, C-b, ... since many language REPLs provide them
- They would not expect Emacs's copy/paste shortcuts (C-Space for starting selection, C-w for cut, and C-y for copy)
I'd like to enable both Emacs shortcuts and copy/paste/undo/redo shortcuts in general textarea in browser. The problem is C-a.
It might be a good idea to follow key bindings of micro text editor:
Using Alt
for Emacs-like shortcuts may be another option:
Mappings | Description |
---|---|
Alt+H , Backspace |
Delete one character before cursor |
Alt+D , Delete |
Delete one character next to cursor |
Enter |
Insert newline |
Alt+Backspace |
Delete one word before cursor |
Alt+Delete |
Delete one word next to cursor |
Ctrl+Z |
Undo |
Alt+Z |
Redo |
Ctrl+C |
Copy selected text |
Ctrl+X |
Cut selected text |
Ctrl+V |
Paste yanked text |
Ctrl+A |
Select all text |
Alt+F , → |
Move cursor forward by one character |
Alt+B , ← |
Move cursor backward by one character |
Alt+P , ↑ |
Move cursor up by one line |
Alt+N , ↓ |
Move cursor down by one line |
Ctrl+→ |
Move cursor forward by word |
Ctrl+← |
Move cursor backward by word |
Ctrl+↑ |
Move cursor up by paragraph |
Ctrl+↓ |
Move cursor down by paragraph |
Alt+E , End , Ctrl+Alt+→ |
Move cursor to the end of line |
Alt+A , Home , Ctrl+Alt+← |
Move cursor to the head of line |
Ctrl+Alt+↑ |
Move cursor to top of lines |
Ctrl+Alt+↓ |
Move cursor to bottom of lines |
Alt+V , PageDown |
Scroll down by page |
PageUp |
Scroll up by page |
Alt+K |
Delete from cursor until the end of line |
Ah, nice point. I've not thought about C-c sends signal. Is it common sense for general ratatui applications? demo example in ratatui does not stop on C-c. I would expect C-c to stop the process when the application is non-interactive. But I'm not sure the expectation can be applied to interactive applications.
I'm not 100% sure what the general rule for this should be, but probably a TUI that's just a display type UI should stop running when Ctrl+C is pressed, editor type TUIs are probably a bit different. Ctrl+Z should put the app into the background rather than being undo (try it in vim).
Using Alt for Emacs-like shortcuts may be another option:
This brings me back to the comment about respecting the conventions of the operating system / environment. For that reason, I'd suggest avoiding this option.
My 10cents
The key dispatch logic in input
should not be in tta. tta should be an API driven component, Many real uses require a complete replacement of that input
code, my use of it involved basically copying and pasting that huge chunk of logic and tweaking it.
I would
- make it more table driven
- have multiple starter tables (emacs like, ctrlzxcv, ....)
You need to make sure that if I have my own input interpreter it has the same features that the in box one has - ie no secret hooks into tta. You could argue that the input interpreter should be separate sample / crate.
probably a TUI that's just a display type UI should stop running when Ctrl+C is pressed, editor type TUIs are probably a bit different. Ctrl+Z should put the app into the background rather than being undo (try it in vim)
I tried tig
and I confirmed both C-c and C-z are working as you said. I need more consideration. Thank you for the point.
Many real uses require a complete replacement of that input code
Yes, that's what I assume. Key shortcuts are very application specific. TextArea::input
is just a convenient method for casual use. APIs are more fundamental.
You could argue that the input interpreter should be separate sample / crate.
I would suggest to create separate crate for this.
use your_crate::InputState;
// Input state such as current pending key sequence and table of key shortcuts
let mut input_state = InputState::default();
// event loop
loop {
let input: Input = crossterm::event::read()?.into();
// Receive new single input and modify textarea state if necessary
input_state.apply(input, &mut textarea);
// ...
}