![]() Closes #33945. Here's my attempt to describe what's going on in that issue and what this fix is doing: We always render the terminal inline assistant starting on the line after the cursor, with a height of 4 lines. When deploying it, we scroll the viewport to the bottom of the terminal so that the assistant will be in view. When scrolling while the assistant is deployed (including in that case), we need to make an adjustment that "pushes up" the terminal content by the height of the assistant, so that we can scroll to see all the normal content plus the assistant itself. That quantity is `scroll_top`, which represents _how much height in the current viewport is occupied by the assistant that would otherwise be occupied by terminal content_. So when you scroll up and a line of the assistant's height goes out of view, `scroll_top` decreases by 1, etc. When we scroll to the bottom after deploying the assistant, we set `scroll_top` to the result of calling `max_scroll_top`, which computes it this way: ``` block.height.saturating_sub(viewport_lines.saturating_sub(terminal_lines)) ``` Which, being interpreted, is "the height of the assistant, minus any viewport lines that are not occupied by terminal content", i.e. the assistant is allowed to eat up vertical space below the last line of terminal content without increasing `scroll_top`. The problem comes when we clear the screen---this adds a full screen to `terminal_lines`, but the cursor is positioned at the top of the viewport with blank lines below, just like at the beginning of a session when `terminal_lines == 1`. Those blank lines should be available to the assistant, but the `scroll_top` calculation doesn't reflect that. I've tried to fix this by basing the `max_scroll_top` calculation on the position of the cursor instead of the raw `terminal_lines` value. There was also a special case for `viewport_lines == terminal_lines` that I think can now be removed. Release Notes: - Fixed the positioning of the terminal inline assistant when it's deployed after clearing the terminal. |
||
---|---|---|
.. | ||
scripts | ||
src | ||
Cargo.toml | ||
LICENSE-GPL | ||
README.md |
Design notes:
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.
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 many distinct paths for getting keystrokes to the terminal:
-
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 -
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 -
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, bypassingtry_keystroke()
. -
Pasted text has a separate pathway.
Generally, there's a distinction between 'keystrokes that need to be mapped' and 'strings which need to be written'. I've attempted to unify these under the '.try_keystroke()' API and the .input()
API (which try_keystroke uses) so we have consistent input handling across the terminal