terminal: Don’t include line breaks for soft wrap in Assistant terminal context (#25415)

> Detects and combines wrapped lines into single logical lines, more
accurately representing the actual terminal content.


```shell
perl -i -pe \
    's/"vscode-languageserver(\/node)?"/"\@zed-industries\/vscode-languageserver$1"/g' packages/css/lib/node/cssServerMain.js
```

<img width="518" alt="image"
src="https://github.com/user-attachments/assets/52d9327c-c381-4e5f-a676-0cf84c824388"
/>

<img width="1314" alt="image"
src="https://github.com/user-attachments/assets/0a32e1f9-7e95-482e-9beb-2e8a6c40584c"
/>




Closes https://github.com/zed-industries/zed/issues/25341

Release Notes:

- Fixed a bug where context for the terminal assistant would add line
breaks in the presence of soft wrapped lines.

---------

Co-authored-by: Peter Tripp <peter@zed.dev>
This commit is contained in:
Richard Hao 2025-03-15 22:28:26 +08:00 committed by GitHub
parent 021d6584cc
commit c282acbe65
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -8,12 +8,12 @@ pub mod terminal_settings;
use alacritty_terminal::{
event::{Event as AlacTermEvent, EventListener, Notify, WindowSize},
event_loop::{EventLoop, Msg, Notifier},
grid::{Dimensions, Scroll as AlacScroll},
grid::{Dimensions, Grid, Row, Scroll as AlacScroll},
index::{Boundary, Column, Direction as AlacDirection, Line, Point as AlacPoint},
selection::{Selection, SelectionRange, SelectionType},
sync::FairMutex,
term::{
cell::Cell,
cell::{Cell, Flags},
search::{Match, RegexIter, RegexSearch},
Config, RenderableCursor, TermMode,
},
@ -1386,28 +1386,59 @@ impl Terminal {
pub fn last_n_non_empty_lines(&self, n: usize) -> Vec<String> {
let term = self.term.clone();
let terminal = term.lock_unfair();
let grid = terminal.grid();
let mut lines = Vec::new();
let mut current_line = terminal.bottommost_line();
while lines.len() < n {
let mut line_buffer = String::new();
for cell in &terminal.grid()[current_line] {
line_buffer.push(cell.c);
}
let line = line_buffer.trim_end();
if !line.is_empty() {
lines.push(line.to_string());
let mut current_line = grid.bottommost_line().0;
let topmost_line = grid.topmost_line().0;
while current_line >= topmost_line && lines.len() < n {
let logical_line_start = self.find_logical_line_start(grid, current_line, topmost_line);
let logical_line = self.construct_logical_line(grid, logical_line_start, current_line);
if let Some(line) = self.process_line(logical_line) {
lines.push(line);
}
if current_line == terminal.topmost_line() {
break;
}
current_line = Line(current_line.0 - 1);
// Move to the line above the start of the current logical line
current_line = logical_line_start - 1;
}
lines.reverse();
lines
}
fn find_logical_line_start(&self, grid: &Grid<Cell>, current: i32, topmost: i32) -> i32 {
let mut line_start = current;
while line_start > topmost {
let prev_line = Line(line_start - 1);
let last_cell = &grid[prev_line][Column(grid.columns() - 1)];
if !last_cell.flags.contains(Flags::WRAPLINE) {
break;
}
line_start -= 1;
}
line_start
}
fn construct_logical_line(&self, grid: &Grid<Cell>, start: i32, end: i32) -> String {
let mut logical_line = String::new();
for row in start..=end {
let grid_row = &grid[Line(row)];
logical_line.push_str(&row_to_string(grid_row));
}
logical_line
}
fn process_line(&self, line: String) -> Option<String> {
let trimmed = line.trim_end().to_string();
if !trimmed.is_empty() {
Some(trimmed)
} else {
None
}
}
pub fn focus_in(&self) {
if self.last_content.mode.contains(TermMode::FOCUS_IN_OUT) {
self.write_to_pty("\x1b[I".to_string());
@ -1838,6 +1869,14 @@ impl Terminal {
}
}
// Helper function to convert a grid row to a string
pub fn row_to_string(row: &Row<Cell>) -> String {
row[..Column(row.len())]
.iter()
.map(|cell| cell.c)
.collect::<String>()
}
fn is_path_surrounded_by_common_symbols(path: &str) -> bool {
// Avoid detecting `[]` or `()` strings as paths, surrounded by common symbols
path.len() > 2