Fixed scrolling on text input

This commit is contained in:
Mikayla Maki 2022-08-18 13:22:48 -07:00
parent d545e59b49
commit aa98e5c3f7
3 changed files with 50 additions and 40 deletions

View file

@ -4,6 +4,17 @@ This crate is split into two conceptual halves:
- The terminal.rs file and the src/mappings/ folder, these contain the code for interacting with Alacritty and maintaining the pty event loop. Some behavior in this file is constrained by terminal protocols and standards. The Zed init function is also placed here.
- Everything else. These other files integrate the `Terminal` struct created in terminal.rs into the rest of GPUI. The main entry point for GPUI is the terminal_view.rs file and the modal.rs file.
Terminals are created externally, and so can fail in unexpected ways However, GPUI currently does not have an API for models than can fail to instantiate. `TerminalBuilder` solves this by using Rust's type system to split `Terminal` instantiation into a 2 step process: first attempt to create the file handles with `TerminalBuilder::new()`, check the result, then call `TerminalBuilder::subscribe(cx)` from within a model context.
The TerminalView struct abstracts over failed and successful terminals, and provides a standardized way of instantiating an always-successful view of a terminal.
ttys are created externally, and so can fail in unexpected ways. However, GPUI currently does not have an API for models than can fail to instantiate. `TerminalBuilder` solves this by using Rust's type system to split tty instantiation into a 2 step process: first attempt to create the file handles with `TerminalBuilder::new()`, check the result, then call `TerminalBuilder::subscribe(cx)` from within a model context.
The TerminalView struct abstracts over failed and successful terminals, passing focus through to the associated view and allowing clients to build a terminal without worrying about errors.
#Input
There are currently 3 distinct paths for getting keystrokes to the terminal:
1. Terminal specific characters and bindings. Things like ctrl-a mapping to ASCII control character 1, ANSI escape codes associated with the function keys, etc. These are caught with a raw key-down handler in the element and are processed immediately. This is done with the `try_keystroke()` method on Terminal
2. GPU Action handlers. GPUI clobbers a few vital keys by adding bindings to them in the global context. These keys are synthesized and then dispatched through the same `try_keystroke()` API as the above mappings
3. IME text. When the special character mappings fail, we pass the keystroke back to GPUI to hand it to the IME system. This comes back to us in the `View::replace_text_in_range()` method, and we then send that to the terminal directly, bypassing `try_keystroke()`.

View file

@ -149,9 +149,9 @@ impl ConnectedView {
{
cx.show_character_palette();
} else {
self.terminal
.read(cx)
.try_keystroke(&Keystroke::parse("ctrl-cmd-space").unwrap());
self.terminal.update(cx, |term, _| {
term.try_keystroke(&Keystroke::parse("ctrl-cmd-space").unwrap())
});
}
}
@ -258,41 +258,41 @@ impl ConnectedView {
///Synthesize the keyboard event corresponding to 'up'
fn up(&mut self, _: &Up, cx: &mut ViewContext<Self>) {
self.clear_bel(cx);
self.terminal
.read(cx)
.try_keystroke(&Keystroke::parse("up").unwrap());
self.terminal.update(cx, |term, _| {
term.try_keystroke(&Keystroke::parse("up").unwrap())
});
}
///Synthesize the keyboard event corresponding to 'down'
fn down(&mut self, _: &Down, cx: &mut ViewContext<Self>) {
self.clear_bel(cx);
self.terminal
.read(cx)
.try_keystroke(&Keystroke::parse("down").unwrap());
self.terminal.update(cx, |term, _| {
term.try_keystroke(&Keystroke::parse("down").unwrap())
});
}
///Synthesize the keyboard event corresponding to 'ctrl-c'
fn ctrl_c(&mut self, _: &CtrlC, cx: &mut ViewContext<Self>) {
self.clear_bel(cx);
self.terminal
.read(cx)
.try_keystroke(&Keystroke::parse("ctrl-c").unwrap());
self.terminal.update(cx, |term, _| {
term.try_keystroke(&Keystroke::parse("ctrl-c").unwrap())
});
}
///Synthesize the keyboard event corresponding to 'escape'
fn escape(&mut self, _: &Escape, cx: &mut ViewContext<Self>) {
self.clear_bel(cx);
self.terminal
.read(cx)
.try_keystroke(&Keystroke::parse("escape").unwrap());
self.terminal.update(cx, |term, _| {
term.try_keystroke(&Keystroke::parse("escape").unwrap())
});
}
///Synthesize the keyboard event corresponding to 'enter'
fn enter(&mut self, _: &Enter, cx: &mut ViewContext<Self>) {
self.clear_bel(cx);
self.terminal
.read(cx)
.try_keystroke(&Keystroke::parse("enter").unwrap());
self.terminal.update(cx, |term, _| {
term.try_keystroke(&Keystroke::parse("enter").unwrap())
});
}
}
@ -358,8 +358,10 @@ impl View for ConnectedView {
text: &str,
cx: &mut ViewContext<Self>,
) {
self.terminal
.update(cx, |terminal, _| terminal.write_to_pty(text.into()));
self.terminal.update(cx, |terminal, _| {
terminal.write_to_pty(text.into());
terminal.scroll(alacritty_terminal::grid::Scroll::Bottom);
});
}
fn keymap_context(&self, _: &gpui::AppContext) -> gpui::keymap::Context {

View file

@ -467,14 +467,14 @@ impl Terminal {
AlacTermEvent::ClipboardStore(_, data) => {
cx.write_to_clipboard(ClipboardItem::new(data.to_string()))
}
AlacTermEvent::ClipboardLoad(_, format) => self.notify_pty(format(
AlacTermEvent::ClipboardLoad(_, format) => self.write_to_pty(format(
&cx.read_from_clipboard()
.map(|ci| ci.text().to_string())
.unwrap_or_else(|| "".to_string()),
)),
AlacTermEvent::PtyWrite(out) => self.notify_pty(out.clone()),
AlacTermEvent::PtyWrite(out) => self.write_to_pty(out.clone()),
AlacTermEvent::TextAreaSizeRequest(format) => {
self.notify_pty(format(self.cur_size.into()))
self.write_to_pty(format(self.cur_size.into()))
}
AlacTermEvent::CursorBlinkingChange => {
cx.emit(Event::BlinkChanged);
@ -517,7 +517,7 @@ impl Terminal {
let term_style = &cx.global::<Settings>().theme.terminal;
to_alac_rgb(get_color_at_index(index, &term_style.colors))
});
self.notify_pty(format(color))
self.write_to_pty(format(color))
}
}
InternalEvent::Resize(new_size) => {
@ -528,7 +528,7 @@ impl Terminal {
term.resize(*new_size);
}
InternalEvent::Clear => {
self.notify_pty("\x0c".to_string());
self.write_to_pty("\x0c".to_string());
term.clear_screen(ClearMode::Saved);
}
InternalEvent::Scroll(scroll) => term.scroll_display(*scroll),
@ -548,12 +548,8 @@ impl Terminal {
}
}
pub fn notify_pty(&self, txt: String) {
self.pty_tx.notify(txt.into_bytes());
}
///Write the Input payload to the tty.
pub fn write_to_pty(&mut self, input: String) {
pub fn write_to_pty(&self, input: String) {
self.pty_tx.notify(input.into_bytes());
}
@ -566,10 +562,11 @@ impl Terminal {
self.events.push(InternalEvent::Clear)
}
pub fn try_keystroke(&self, keystroke: &Keystroke) -> bool {
pub fn try_keystroke(&mut self, keystroke: &Keystroke) -> bool {
let esc = to_esc_str(keystroke, &self.last_mode);
if let Some(esc) = esc {
self.notify_pty(esc);
self.write_to_pty(esc);
self.scroll(Scroll::Bottom);
true
} else {
false
@ -579,11 +576,11 @@ impl Terminal {
///Paste text into the terminal
pub fn paste(&self, text: &str) {
if self.last_mode.contains(TermMode::BRACKETED_PASTE) {
self.notify_pty("\x1b[200~".to_string());
self.notify_pty(text.replace('\x1b', ""));
self.notify_pty("\x1b[201~".to_string());
self.write_to_pty("\x1b[200~".to_string());
self.write_to_pty(text.replace('\x1b', ""));
self.write_to_pty("\x1b[201~".to_string());
} else {
self.notify_pty(text.replace("\r\n", "\r").replace('\n', "\r"));
self.write_to_pty(text.replace("\r\n", "\r").replace('\n', "\r"));
}
}
@ -619,13 +616,13 @@ impl Terminal {
pub fn focus_in(&self) {
if self.last_mode.contains(TermMode::FOCUS_IN_OUT) {
self.notify_pty("\x1b[I".to_string());
self.write_to_pty("\x1b[I".to_string());
}
}
pub fn focus_out(&self) {
if self.last_mode.contains(TermMode::FOCUS_IN_OUT) {
self.notify_pty("\x1b[O".to_string());
self.write_to_pty("\x1b[O".to_string());
}
}