-
Notifications
You must be signed in to change notification settings - Fork 3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for setting prompt marks via OSC 133 #5860
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -14,7 +14,7 @@ use crate::ansi::{ | |||||
}; | ||||||
use crate::config::Config; | ||||||
use crate::event::{Event, EventListener}; | ||||||
use crate::grid::{Dimensions, Grid, GridIterator, Scroll}; | ||||||
use crate::grid::{Dimensions, Grid, GridCell, GridIterator, Scroll}; | ||||||
use crate::index::{self, Boundary, Column, Direction, Line, Point, Side}; | ||||||
use crate::selection::{Selection, SelectionRange, SelectionType}; | ||||||
use crate::term::cell::{Cell, Flags, LineLength}; | ||||||
|
@@ -501,6 +501,57 @@ impl<T> Term<T> { | |||||
} | ||||||
} | ||||||
|
||||||
/// Go to next prompt farthest down in history. | ||||||
pub fn goto_next_prompt(&mut self) | ||||||
where | ||||||
T: EventListener, | ||||||
{ | ||||||
let point = if self.mode.contains(TermMode::VI) { | ||||||
self.vi_mode_cursor.point | ||||||
} else { | ||||||
let display_offset = self.grid.display_offset() as i32; | ||||||
// If we're outside of Vi mode use bottom most visible line. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Topmost/bottommost are one word, as weird as it looks. |
||||||
Point::new(Line(self.screen_lines() as i32 - 1 - display_offset), Column(0)) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are you using the bottommost line? If I have a prompt visible at the top and another prompt in the middle of my screen, I want to jump to the prompt in the middle of my screen. I don't want to jump to the next offscreen prompt. This is the same as search, the top of the screen is always to be considered the origin of the search. This also shouldn't be inverted for |
||||||
}; | ||||||
|
||||||
let prompt_point = match self.next_prompt(point) { | ||||||
Some(point) => point, | ||||||
None => return, | ||||||
}; | ||||||
|
||||||
if self.mode().contains(TermMode::VI) { | ||||||
self.vi_goto_point(prompt_point); | ||||||
} else { | ||||||
let scroll = Scroll::Delta(point.line.0 - prompt_point.line.0); | ||||||
self.scroll_display(scroll); | ||||||
} | ||||||
} | ||||||
|
||||||
/// Go to previous prompt farthest up in history. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't go to the previous prompt farthest up in history, that would mean we skip over as many prompts as possible. We go to the previous prompt in history, so the opposite of farthest. Could say closest/nearest up in history but I feel like that's unnecessary. |
||||||
pub fn goto_previous_prompt(&mut self) | ||||||
where | ||||||
T: EventListener, | ||||||
{ | ||||||
let point = if self.mode.contains(TermMode::VI) { | ||||||
self.vi_mode_cursor.point | ||||||
} else { | ||||||
// If we're outside of Vi mode use topmost visible line. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
Point::new(Line(-(self.grid.display_offset() as i32)), Column(0)) | ||||||
}; | ||||||
|
||||||
let prompt_point = match self.previous_prompt(point) { | ||||||
Some(point) => point, | ||||||
None => return, | ||||||
}; | ||||||
|
||||||
if self.mode.contains(TermMode::VI) { | ||||||
self.vi_goto_point(prompt_point); | ||||||
} else { | ||||||
let scroll = Scroll::Delta(point.line.0 - prompt_point.line.0); | ||||||
self.scroll_display(scroll); | ||||||
} | ||||||
} | ||||||
|
||||||
#[must_use] | ||||||
pub fn damage(&mut self, selection: Option<SelectionRange>) -> TermDamage<'_> { | ||||||
// Ensure the entire terminal is damaged after entering insert mode. | ||||||
|
@@ -1046,10 +1097,13 @@ impl<T> Term<T> { | |||||
let c = self.grid.cursor.charsets[self.active_charset].map(c); | ||||||
let fg = self.grid.cursor.template.fg; | ||||||
let bg = self.grid.cursor.template.bg; | ||||||
let flags = self.grid.cursor.template.flags; | ||||||
let mut flags = self.grid.cursor.template.flags; | ||||||
|
||||||
let mut cursor_cell = self.grid.cursor_cell(); | ||||||
|
||||||
// Preserve PROMPT_MARK if we had it. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Also is this necessary? The prompt shouldn't usually be overwritten? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It does get overwritten due to the way everyone is sending prompt marks. They send prompt mark and then start writing the prompt, leading to overwriting the cell. The documentation and recommendations state that you should send it before writing the prompt. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Really not a big fan of this aspect of the escape. Either way though this comment should be fixed. |
||||||
flags |= cursor_cell.flags & Flags::PROMPT_MARK; | ||||||
|
||||||
// Clear all related cells when overwriting a fullwidth cell. | ||||||
if cursor_cell.flags.intersects(Flags::WIDE_CHAR | Flags::WIDE_CHAR_SPACER) { | ||||||
// Remove wide char and spacer. | ||||||
|
@@ -1600,6 +1654,9 @@ impl<T: EventListener> Handler for Term<T> { | |||||
let bg = cursor.template.bg; | ||||||
let point = cursor.point; | ||||||
|
||||||
// We don't want to clear prompt markers. | ||||||
let prompt_marked = *self.grid[point.line][Column(0)].flags() & Flags::PROMPT_MARK; | ||||||
|
||||||
Comment on lines
+1657
to
+1659
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this necessary? Who rewrites a prompt without writing the prompt there? |
||||||
let (left, right) = match mode { | ||||||
ansi::LineClearMode::Right => (point.column, Column(self.columns())), | ||||||
ansi::LineClearMode::Left => (Column(0), point.column + 1), | ||||||
|
@@ -1613,6 +1670,9 @@ impl<T: EventListener> Handler for Term<T> { | |||||
*cell = bg.into(); | ||||||
} | ||||||
|
||||||
// Restore prompt mark if it got reset. | ||||||
self.grid[point.line][Column(0)].flags_mut().insert(prompt_marked); | ||||||
|
||||||
let range = self.grid.cursor.point.line..=self.grid.cursor.point.line; | ||||||
self.selection = self.selection.take().filter(|s| !s.intersects_range(range)); | ||||||
} | ||||||
|
@@ -1724,13 +1784,15 @@ impl<T: EventListener> Handler for Term<T> { | |||||
}, | ||||||
ansi::ClearMode::Below => { | ||||||
let cursor = self.grid.cursor.point; | ||||||
let prompt_mark = self.grid[cursor.line][Column(0)].flags & Flags::PROMPT_MARK; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here, question if we should keep the prompt mark. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's what zsh doing right after |
||||||
for cell in &mut self.grid[cursor.line][cursor.column..] { | ||||||
*cell = bg.into(); | ||||||
} | ||||||
|
||||||
if (cursor.line.0 as usize) < screen_lines - 1 { | ||||||
self.grid.reset_region((cursor.line + 1)..); | ||||||
} | ||||||
self.grid[cursor.line][Column(0)].flags.insert(prompt_mark); | ||||||
|
||||||
let range = cursor.line..Line(screen_lines as i32); | ||||||
self.selection = self.selection.take().filter(|s| !s.intersects_range(range)); | ||||||
|
@@ -2072,6 +2134,15 @@ impl<T: EventListener> Handler for Term<T> { | |||||
} | ||||||
} | ||||||
|
||||||
#[inline] | ||||||
fn set_prompt_mark(&mut self) { | ||||||
let mark_point = self.line_search_left(self.grid.cursor.point); | ||||||
trace!("Setting shell prompt mark at: line={}", mark_point.line); | ||||||
|
||||||
// Set prompt mark flag. | ||||||
self.grid[mark_point].flags_mut().insert(Flags::PROMPT_MARK); | ||||||
} | ||||||
|
||||||
#[inline] | ||||||
fn text_area_size_pixels(&mut self) { | ||||||
let width = self.cell_width * self.columns(); | ||||||
|
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -434,6 +434,46 @@ impl<T> Term<T> { | |||||||||
|
||||||||||
point | ||||||||||
} | ||||||||||
|
||||||||||
/// Find the next shell prompt. | ||||||||||
pub fn next_prompt(&self, mut point: Point) -> Option<Point> { | ||||||||||
// If we're at the start of the prompt go to the next. | ||||||||||
if point.column == Column(0) { | ||||||||||
point.line += 1 | ||||||||||
} else { | ||||||||||
point.column = Column(0); | ||||||||||
} | ||||||||||
chrisduerr marked this conversation as resolved.
Show resolved
Hide resolved
Comment on lines
+443
to
+445
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
A bit shorter like this and probably compiles down to the same thing anyway? |
||||||||||
|
||||||||||
while point.line <= self.bottommost_line() { | ||||||||||
if self.grid[point].flags.contains(Flags::PROMPT_MARK) { | ||||||||||
return Some(point); | ||||||||||
} | ||||||||||
|
||||||||||
point.line += 1; | ||||||||||
} | ||||||||||
|
||||||||||
None | ||||||||||
} | ||||||||||
|
||||||||||
/// Find the previous shell prompt. | ||||||||||
pub fn previous_prompt(&self, mut point: Point) -> Option<Point> { | ||||||||||
// If we're at the start of the prompt go to the previous. | ||||||||||
if point.column == Column(0) { | ||||||||||
point.line.0 -= 1 | ||||||||||
} else { | ||||||||||
point.column = Column(0); | ||||||||||
} | ||||||||||
|
||||||||||
while point.line >= self.grid.topmost_line() { | ||||||||||
if self.grid[point].flags.contains(Flags::PROMPT_MARK) { | ||||||||||
return Some(point); | ||||||||||
} | ||||||||||
|
||||||||||
point.line -= 1; | ||||||||||
} | ||||||||||
|
||||||||||
None | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
/// Iterator over regex matches. | ||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -67,6 +67,7 @@ ref_tests! { | |
saved_cursor_alt | ||
sgr | ||
underline | ||
prompt_marks | ||
} | ||
|
||
fn read_u8<P>(path: P) -> Vec<u8> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be above
ALL_UNDERLINES
. The "special" constants are usually best kept at the bottom because that way one can easily see at a glance that all bits are correctly set for the other constants.