mirror of
https://github.com/wez/wezterm.git
synced 2024-12-25 14:22:37 +03:00
remove dead code
This logic has all moved to the render layer
This commit is contained in:
parent
afbd262740
commit
e13b4f7dc9
@ -19,7 +19,6 @@ pub mod screen;
|
||||
pub use crate::screen::*;
|
||||
|
||||
pub mod selection;
|
||||
use crate::selection::{SelectionCoordinate, SelectionRange};
|
||||
|
||||
use termwiz::hyperlink::Hyperlink;
|
||||
|
||||
|
@ -15,7 +15,6 @@ use termwiz::escape::csi::{
|
||||
};
|
||||
use termwiz::escape::osc::{ChangeColorPair, ColorOrQuery, ITermFileData, ITermProprietary};
|
||||
use termwiz::escape::{Action, ControlCode, Esc, EscCode, OneBased, OperatingSystemCommand, CSI};
|
||||
use termwiz::hyperlink::Rule as HyperlinkRule;
|
||||
use termwiz::image::{ImageCell, ImageData, TextureCoordinate};
|
||||
use termwiz::surface::CursorShape;
|
||||
|
||||
@ -195,38 +194,11 @@ pub struct TerminalState {
|
||||
sgr_mouse: bool,
|
||||
button_event_mouse: bool,
|
||||
current_mouse_button: MouseButton,
|
||||
mouse_position: CursorPosition,
|
||||
cursor_visible: bool,
|
||||
dec_line_drawing_mode: bool,
|
||||
|
||||
/// Which hyperlink is considered to be highlighted, because the
|
||||
/// mouse_position is over a cell with a Hyperlink attribute.
|
||||
current_highlight: Option<Arc<Hyperlink>>,
|
||||
|
||||
/// Keeps track of double and triple clicks
|
||||
last_mouse_click: Option<LastMouseClick>,
|
||||
|
||||
/// Used to compute the offset to the top of the viewport.
|
||||
/// This is used to display the scrollback of the terminal.
|
||||
/// It is distinct from the scroll_region in that the scroll region
|
||||
/// afects how the terminal output is scrolled as data is output,
|
||||
/// and the viewport_offset is used to index into the scrollback
|
||||
/// purely for display purposes.
|
||||
/// The offset is measured from the top of the physical viewable
|
||||
/// screen with larger numbers going backwards.
|
||||
pub(crate) viewport_offset: VisibleRowIndex,
|
||||
|
||||
/// Remembers the starting coordinate of the selection prior to
|
||||
/// dragging.
|
||||
selection_start: Option<SelectionCoordinate>,
|
||||
/// Holds the not-normalized selection range.
|
||||
selection_range: Option<SelectionRange>,
|
||||
|
||||
tabs: TabStop,
|
||||
|
||||
hyperlink_rules: Vec<HyperlinkRule>,
|
||||
hyperlink_rules_generation: usize,
|
||||
|
||||
/// The terminal title string
|
||||
title: String,
|
||||
palette: Option<ColorPalette>,
|
||||
@ -281,8 +253,6 @@ impl TerminalState {
|
||||
) -> TerminalState {
|
||||
let screen = ScreenOrAlt::new(physical_rows, physical_cols, &config);
|
||||
|
||||
let (hyperlink_rules_generation, hyperlink_rules) = config.hyperlink_rules();
|
||||
|
||||
TerminalState {
|
||||
config,
|
||||
screen,
|
||||
@ -299,15 +269,7 @@ impl TerminalState {
|
||||
cursor_visible: true,
|
||||
dec_line_drawing_mode: false,
|
||||
current_mouse_button: MouseButton::None,
|
||||
mouse_position: CursorPosition::default(),
|
||||
current_highlight: None,
|
||||
last_mouse_click: None,
|
||||
viewport_offset: 0,
|
||||
selection_range: None,
|
||||
selection_start: None,
|
||||
tabs: TabStop::new(physical_cols, 8),
|
||||
hyperlink_rules,
|
||||
hyperlink_rules_generation,
|
||||
title: "wezterm".to_string(),
|
||||
palette: None,
|
||||
pixel_height,
|
||||
@ -356,160 +318,6 @@ impl TerminalState {
|
||||
&mut self.screen
|
||||
}
|
||||
|
||||
pub fn get_selection_text(&self) -> String {
|
||||
let mut s = String::new();
|
||||
|
||||
if let Some(sel) = self.selection_range.as_ref().map(|r| r.normalize()) {
|
||||
let screen = self.screen();
|
||||
let mut last_was_wrapped = false;
|
||||
for y in sel.rows() {
|
||||
let idx = screen.scrollback_or_visible_row(y);
|
||||
let cols = sel.cols_for_row(y);
|
||||
let last_col_idx = cols.end.min(screen.lines[idx].cells().len()) - 1;
|
||||
if !s.is_empty() && !last_was_wrapped {
|
||||
s.push('\n');
|
||||
}
|
||||
s.push_str(screen.lines[idx].columns_as_str(cols).trim_end());
|
||||
|
||||
let last_cell = &screen.lines[idx].cells()[last_col_idx];
|
||||
// TODO: should really test for any unicode whitespace
|
||||
last_was_wrapped = last_cell.attrs().wrapped() && last_cell.str() != " ";
|
||||
}
|
||||
}
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
/// Dirty the lines in the current selection range
|
||||
fn dirty_selection_lines(&mut self) {
|
||||
if let Some(sel) = self.selection_range.as_ref().map(|r| r.normalize()) {
|
||||
let screen = self.screen_mut();
|
||||
for y in screen.scrollback_or_visible_range(&sel.rows()) {
|
||||
screen.line_mut(y).set_dirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_selection(&mut self) {
|
||||
self.dirty_selection_lines();
|
||||
self.selection_range = None;
|
||||
self.selection_start = None;
|
||||
}
|
||||
|
||||
/// If `cols` on the specified `row` intersect with the selection range,
|
||||
/// clear the selection rnage. This doesn't invalidate the selection,
|
||||
/// it just cancels rendering the selected text.
|
||||
/// Returns true if the selection is invalidated or not present, which
|
||||
/// is useful to terminate a loop when there is no more work to be done.
|
||||
fn clear_selection_if_intersects(
|
||||
&mut self,
|
||||
cols: Range<usize>,
|
||||
row: ScrollbackOrVisibleRowIndex,
|
||||
) -> bool {
|
||||
let sel = self.selection_range.take();
|
||||
match sel {
|
||||
Some(sel) => {
|
||||
let sel = sel.normalize();
|
||||
let sel_cols = sel.cols_for_row(row);
|
||||
if intersects_range(cols, sel_cols) {
|
||||
// Intersects, so clear the selection
|
||||
self.clear_selection();
|
||||
true
|
||||
} else {
|
||||
self.selection_range = Some(sel);
|
||||
false
|
||||
}
|
||||
}
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// If `rows` intersect with the selection range, clear the selection rnage.
|
||||
/// This doesn't invalidate the selection, it just cancels rendering the
|
||||
/// selected text.
|
||||
/// Returns true if the selection is invalidated or not present, which
|
||||
/// is useful to terminate a loop when there is no more work to be done.
|
||||
fn clear_selection_if_intersects_rows(
|
||||
&mut self,
|
||||
rows: Range<ScrollbackOrVisibleRowIndex>,
|
||||
) -> bool {
|
||||
let sel = self.selection_range.take();
|
||||
match sel {
|
||||
Some(sel) => {
|
||||
let sel_rows = sel.rows();
|
||||
if intersects_range(rows, sel_rows) {
|
||||
// Intersects, so clear the selection
|
||||
self.clear_selection();
|
||||
true
|
||||
} else {
|
||||
self.selection_range = Some(sel);
|
||||
false
|
||||
}
|
||||
}
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn hyperlink_for_cell(
|
||||
&mut self,
|
||||
x: usize,
|
||||
y: ScrollbackOrVisibleRowIndex,
|
||||
) -> Option<Arc<Hyperlink>> {
|
||||
let rules = &self.hyperlink_rules;
|
||||
|
||||
let idx = self.screen.scrollback_or_visible_row(y);
|
||||
match self.screen.lines.get_mut(idx) {
|
||||
Some(ref mut line) => {
|
||||
line.scan_and_create_hyperlinks(rules);
|
||||
match line.cells().get(x) {
|
||||
Some(cell) => cell.attrs().hyperlink.as_ref().cloned(),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Invalidate rows that have hyperlinks
|
||||
fn invalidate_hyperlinks(&mut self) {
|
||||
let screen = self.screen_mut();
|
||||
for line in &mut screen.lines {
|
||||
if line.has_hyperlink() {
|
||||
line.set_dirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// If the configuration changed, obtain the new hyperlink rules
|
||||
/// from it and invalidate any implicit hyperlinks so that we
|
||||
/// re-apply the rules to the display
|
||||
fn refresh_hyperlink_rules(&mut self) {
|
||||
if self.config.generation() != self.hyperlink_rules_generation {
|
||||
let (generation, rules) = self.config.hyperlink_rules();
|
||||
self.hyperlink_rules = rules;
|
||||
self.hyperlink_rules_generation = generation;
|
||||
|
||||
// dirty all lines as any of them may have changed
|
||||
// their hyperlinkyness
|
||||
let screen = self.screen_mut();
|
||||
for line in &mut screen.lines {
|
||||
line.invalidate_implicit_hyperlinks();
|
||||
line.set_dirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Called after a mouse move or viewport scroll to recompute the
|
||||
/// current highlight
|
||||
fn recompute_highlight(&mut self) {
|
||||
self.refresh_hyperlink_rules();
|
||||
let line_idx = self.mouse_position.y as ScrollbackOrVisibleRowIndex
|
||||
- self.viewport_offset as ScrollbackOrVisibleRowIndex;
|
||||
let x = self.mouse_position.x;
|
||||
self.current_highlight = self.hyperlink_for_cell(x, line_idx);
|
||||
self.invalidate_hyperlinks();
|
||||
}
|
||||
|
||||
fn set_clipboard_contents(&self, text: Option<String>) -> anyhow::Result<()> {
|
||||
if let Some(clip) = self.clipboard.as_ref() {
|
||||
clip.set_contents(text)?;
|
||||
@ -517,196 +325,14 @@ impl TerminalState {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Single click prepares the start of a new selection
|
||||
fn mouse_single_click_left(&mut self, event: MouseEvent) -> Result<(), Error> {
|
||||
// Prepare to start a new selection.
|
||||
// We don't form the selection until the mouse drags.
|
||||
self.selection_range = None;
|
||||
self.selection_start = Some(SelectionCoordinate {
|
||||
x: event.x,
|
||||
y: event.y as ScrollbackOrVisibleRowIndex
|
||||
- self.viewport_offset as ScrollbackOrVisibleRowIndex,
|
||||
});
|
||||
self.set_clipboard_contents(None)
|
||||
}
|
||||
|
||||
/// Double click to select a word on the current line
|
||||
fn mouse_double_click_left(&mut self, event: MouseEvent) -> Result<(), Error> {
|
||||
let y = event.y as ScrollbackOrVisibleRowIndex
|
||||
- self.viewport_offset as ScrollbackOrVisibleRowIndex;
|
||||
let idx = self.screen().scrollback_or_visible_row(y);
|
||||
let selection_range = match self.screen().lines[idx]
|
||||
.compute_double_click_range(event.x, |s| self.config.is_double_click_word(s))
|
||||
{
|
||||
DoubleClickRange::Range(click_range) => SelectionRange {
|
||||
start: SelectionCoordinate {
|
||||
x: click_range.start,
|
||||
y,
|
||||
},
|
||||
end: SelectionCoordinate {
|
||||
x: click_range.end - 1,
|
||||
y,
|
||||
},
|
||||
},
|
||||
DoubleClickRange::RangeWithWrap(range_start) => {
|
||||
let start_coord = SelectionCoordinate {
|
||||
x: range_start.start,
|
||||
y,
|
||||
};
|
||||
|
||||
let mut end_coord = SelectionCoordinate {
|
||||
x: range_start.end - 1,
|
||||
y,
|
||||
};
|
||||
|
||||
for y_cont in idx + 1..self.screen().lines.len() {
|
||||
match self.screen().lines[y_cont]
|
||||
.compute_double_click_range(0, |s| self.config.is_double_click_word(s))
|
||||
{
|
||||
DoubleClickRange::Range(range_end) => {
|
||||
if range_end.end > range_end.start {
|
||||
end_coord = SelectionCoordinate {
|
||||
x: range_end.end - 1,
|
||||
y: y + (y_cont - idx) as i32,
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
DoubleClickRange::RangeWithWrap(range_end) => {
|
||||
end_coord = SelectionCoordinate {
|
||||
x: range_end.end - 1,
|
||||
y: y + (y_cont - idx) as i32,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SelectionRange {
|
||||
start: start_coord,
|
||||
end: end_coord,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: if selection_range.start.x == 0, search backwards for wrapping
|
||||
// lines too.
|
||||
|
||||
self.selection_start = Some(selection_range.start);
|
||||
self.selection_range = Some(selection_range);
|
||||
|
||||
self.dirty_selection_lines();
|
||||
let text = self.get_selection_text();
|
||||
debug!(
|
||||
"finish 2click selection {:?} '{}'",
|
||||
self.selection_range, text
|
||||
);
|
||||
self.set_clipboard_contents(Some(text))
|
||||
}
|
||||
|
||||
/// triple click to select the current line
|
||||
fn mouse_triple_click_left(&mut self, event: MouseEvent) -> Result<(), Error> {
|
||||
let y = event.y as ScrollbackOrVisibleRowIndex
|
||||
- self.viewport_offset as ScrollbackOrVisibleRowIndex;
|
||||
self.selection_start = Some(SelectionCoordinate { x: event.x, y });
|
||||
self.selection_range = Some(SelectionRange {
|
||||
start: SelectionCoordinate { x: 0, y },
|
||||
end: SelectionCoordinate {
|
||||
x: usize::max_value(),
|
||||
y,
|
||||
},
|
||||
});
|
||||
self.dirty_selection_lines();
|
||||
let text = self.get_selection_text();
|
||||
debug!(
|
||||
"finish 3click selection {:?} '{}'",
|
||||
self.selection_range, text
|
||||
);
|
||||
self.set_clipboard_contents(Some(text))
|
||||
}
|
||||
|
||||
fn mouse_press_left(&mut self, event: MouseEvent) -> Result<(), Error> {
|
||||
self.current_mouse_button = MouseButton::Left;
|
||||
self.dirty_selection_lines();
|
||||
match self.last_mouse_click.as_ref() {
|
||||
Some(&LastMouseClick { streak: 1, .. }) => {
|
||||
self.mouse_single_click_left(event)?;
|
||||
}
|
||||
Some(&LastMouseClick { streak: 2, .. }) => {
|
||||
self.mouse_double_click_left(event)?;
|
||||
}
|
||||
Some(&LastMouseClick { streak: 3, .. }) => {
|
||||
self.mouse_triple_click_left(event)?;
|
||||
}
|
||||
// otherwise, clear out the selection
|
||||
_ => {
|
||||
self.selection_range = None;
|
||||
self.selection_start = None;
|
||||
self.set_clipboard_contents(None)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mouse_release_left(
|
||||
&mut self,
|
||||
event: MouseEvent,
|
||||
host: &mut dyn TerminalHost,
|
||||
) -> Result<(), Error> {
|
||||
// Finish selecting a region, update clipboard
|
||||
self.current_mouse_button = MouseButton::None;
|
||||
if let Some(&LastMouseClick { streak: 1, .. }) = self.last_mouse_click.as_ref() {
|
||||
// Only consider a drag selection if we have a streak==1.
|
||||
// The double/triple click cases are handled above.
|
||||
let text = self.get_selection_text();
|
||||
if !text.is_empty() {
|
||||
debug!(
|
||||
"finish drag selection {:?} '{}'",
|
||||
self.selection_range, text
|
||||
);
|
||||
self.set_clipboard_contents(Some(text))?;
|
||||
} else if let Some(link) = self.current_highlight() {
|
||||
// If the button release wasn't a drag, consider
|
||||
// whether it was a click on a hyperlink
|
||||
host.click_link(&link);
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
self.mouse_button_release(event, host.writer())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selection_range(&self) -> Option<SelectionRange> {
|
||||
self.selection_range.map(|r| r.normalize())
|
||||
}
|
||||
|
||||
fn mouse_drag_left(&mut self, event: MouseEvent) -> Result<(), Error> {
|
||||
// dragging out the selection region
|
||||
// TODO: may drag and change the viewport
|
||||
self.dirty_selection_lines();
|
||||
let end = SelectionCoordinate {
|
||||
x: event.x,
|
||||
y: event.y as ScrollbackOrVisibleRowIndex
|
||||
- self.viewport_offset as ScrollbackOrVisibleRowIndex,
|
||||
};
|
||||
let sel = match self.selection_range.take() {
|
||||
None => SelectionRange::start(self.selection_start.unwrap_or(end)).extend(end),
|
||||
Some(sel) => sel.extend(end),
|
||||
};
|
||||
self.selection_range = Some(sel);
|
||||
// Dirty lines again to reflect new range
|
||||
self.dirty_selection_lines();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mouse_wheel(
|
||||
&mut self,
|
||||
event: MouseEvent,
|
||||
writer: &mut dyn std::io::Write,
|
||||
) -> Result<(), Error> {
|
||||
let (report_button, scroll_delta, key) = match event.button {
|
||||
MouseButton::WheelUp(amount) => (64, -(amount as i64), KeyCode::UpArrow),
|
||||
MouseButton::WheelDown(amount) => (65, amount as i64, KeyCode::DownArrow),
|
||||
let (report_button, key) = match event.button {
|
||||
MouseButton::WheelUp(_) => (64, KeyCode::UpArrow),
|
||||
MouseButton::WheelDown(_) => (65, KeyCode::DownArrow),
|
||||
_ => bail!("unexpected mouse event {:?}", event),
|
||||
};
|
||||
|
||||
@ -717,8 +343,6 @@ impl TerminalState {
|
||||
} else if self.screen.is_alt_screen_active() {
|
||||
// Send cursor keys instead (equivalent to xterm's alternateScroll mode)
|
||||
self.key_down(key, KeyModifiers::default(), writer)?;
|
||||
} else {
|
||||
self.scroll_viewport(scroll_delta)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -792,66 +416,6 @@ impl TerminalState {
|
||||
event.y = event.y.min(self.screen().physical_rows as i64 - 1);
|
||||
event.x = event.x.min(self.screen().physical_cols - 1);
|
||||
|
||||
// Remember the last reported mouse position so that we can use it
|
||||
// for highlighting clickable things elsewhere.
|
||||
let new_position = CursorPosition {
|
||||
x: event.x,
|
||||
y: event.y as VisibleRowIndex,
|
||||
..self.mouse_position
|
||||
};
|
||||
|
||||
if new_position != self.mouse_position {
|
||||
self.mouse_position = new_position;
|
||||
self.recompute_highlight();
|
||||
}
|
||||
|
||||
// First pass to figure out if we're messing with the selection
|
||||
let send_event = self.sgr_mouse && !event.modifiers.contains(KeyModifiers::SHIFT);
|
||||
|
||||
// Perform click counting
|
||||
if event.kind == MouseEventKind::Press {
|
||||
let click = match self.last_mouse_click.take() {
|
||||
None => LastMouseClick::new(event.button),
|
||||
Some(click) => click.add(event.button),
|
||||
};
|
||||
self.last_mouse_click = Some(click);
|
||||
}
|
||||
|
||||
if !send_event {
|
||||
match (event, self.current_mouse_button) {
|
||||
(
|
||||
MouseEvent {
|
||||
kind: MouseEventKind::Press,
|
||||
button: MouseButton::Left,
|
||||
..
|
||||
},
|
||||
_,
|
||||
) => {
|
||||
return self.mouse_press_left(event);
|
||||
}
|
||||
(
|
||||
MouseEvent {
|
||||
kind: MouseEventKind::Release,
|
||||
button: MouseButton::Left,
|
||||
..
|
||||
},
|
||||
_,
|
||||
) => {
|
||||
return self.mouse_release_left(event, host);
|
||||
}
|
||||
(
|
||||
MouseEvent {
|
||||
kind: MouseEventKind::Move,
|
||||
..
|
||||
},
|
||||
MouseButton::Left,
|
||||
) => {
|
||||
return self.mouse_drag_left(event);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
match event {
|
||||
MouseEvent {
|
||||
kind: MouseEventKind::Press,
|
||||
@ -1061,17 +625,6 @@ impl TerminalState {
|
||||
buf.as_str()
|
||||
}
|
||||
|
||||
PageUp if mods == KeyModifiers::SHIFT => {
|
||||
let rows = self.screen().physical_rows as i64;
|
||||
self.scroll_viewport(-rows);
|
||||
""
|
||||
}
|
||||
PageDown if mods == KeyModifiers::SHIFT => {
|
||||
let rows = self.screen().physical_rows as i64;
|
||||
self.scroll_viewport(rows);
|
||||
""
|
||||
}
|
||||
|
||||
PageUp | PageDown | Insert | Delete => {
|
||||
let c = match key {
|
||||
Insert => 2,
|
||||
@ -1144,14 +697,6 @@ impl TerminalState {
|
||||
// debug!("sending {:?}, {:?}", to_send, key);
|
||||
writer.write_all(to_send.as_bytes())?;
|
||||
|
||||
// Reset the viewport if we sent data to the parser
|
||||
if !to_send.is_empty()
|
||||
&& self.viewport_offset != 0
|
||||
&& self.config.scroll_to_bottom_on_key_input()
|
||||
{
|
||||
self.set_scroll_viewport(0);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -1167,68 +712,10 @@ impl TerminalState {
|
||||
self.pixel_height = pixel_height;
|
||||
self.pixel_width = pixel_width;
|
||||
self.tabs.resize(physical_cols);
|
||||
self.set_scroll_viewport(0);
|
||||
// Ensure that the cursor is within the new bounds of the screen
|
||||
self.set_cursor_pos(&Position::Relative(0), &Position::Relative(0));
|
||||
}
|
||||
|
||||
/// Returns true if any of the visible lines are marked dirty
|
||||
pub fn has_dirty_lines(&self) -> bool {
|
||||
let screen = self.screen();
|
||||
let height = screen.physical_rows;
|
||||
let len = screen.lines.len() - self.viewport_offset as usize;
|
||||
|
||||
for line in screen.lines.iter().skip(len - height) {
|
||||
if line.is_dirty() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns the set of visible lines that are dirty.
|
||||
/// The return value is a Vec<(line_idx, line, selrange)>, where
|
||||
/// line_idx is relative to the top of the viewport.
|
||||
/// The selrange value is the column range representing the selected
|
||||
/// columns on this line.
|
||||
pub fn get_dirty_lines(&self) -> Vec<(usize, &Line, Range<usize>)> {
|
||||
let mut res = Vec::new();
|
||||
|
||||
let screen = self.screen();
|
||||
let height = screen.physical_rows;
|
||||
let len = screen.lines.len() - self.viewport_offset as usize;
|
||||
|
||||
let selection = self.selection_range.map(|r| r.normalize());
|
||||
|
||||
for (i, line) in screen.lines.iter().skip(len - height).enumerate() {
|
||||
if i >= height {
|
||||
// When scrolling back, make sure we don't emit lines that
|
||||
// are below the bottom of the viewport
|
||||
break;
|
||||
}
|
||||
if line.is_dirty() {
|
||||
let selrange = match selection {
|
||||
None => 0..0,
|
||||
Some(sel) => {
|
||||
// i is relative to the viewport, convert it back to
|
||||
// something we can relate to the selection
|
||||
let row = (i as ScrollbackOrVisibleRowIndex)
|
||||
- self.viewport_offset as ScrollbackOrVisibleRowIndex;
|
||||
sel.cols_for_row(row)
|
||||
}
|
||||
};
|
||||
res.push((i, &*line, selrange));
|
||||
}
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
pub fn get_viewport_offset(&self) -> VisibleRowIndex {
|
||||
self.viewport_offset
|
||||
}
|
||||
|
||||
/// Clear the dirty flag for all dirty lines
|
||||
pub fn clean_dirty_lines(&mut self) {
|
||||
let screen = self.screen_mut();
|
||||
@ -1250,7 +737,7 @@ impl TerminalState {
|
||||
pub fn cursor_pos(&self) -> CursorPosition {
|
||||
CursorPosition {
|
||||
x: self.cursor.x,
|
||||
y: self.cursor.y + self.viewport_offset,
|
||||
y: self.cursor.y,
|
||||
shape: if self.cursor_visible {
|
||||
self.cursor.shape
|
||||
} else {
|
||||
@ -1259,11 +746,6 @@ impl TerminalState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the currently highlighted hyperlink
|
||||
pub fn current_highlight(&self) -> Option<Arc<Hyperlink>> {
|
||||
self.current_highlight.as_ref().cloned()
|
||||
}
|
||||
|
||||
/// Sets the cursor position. x and y are 0-based and relative to the
|
||||
/// top left of the visible screen.
|
||||
/// TODO: DEC origin mode impacts the interpreation of these
|
||||
@ -1291,45 +773,12 @@ impl TerminalState {
|
||||
screen.dirty_line(new_y);
|
||||
}
|
||||
|
||||
pub fn set_scroll_viewport(&mut self, position: VisibleRowIndex) {
|
||||
self.clear_selection();
|
||||
let position = position.max(0);
|
||||
|
||||
let rows = self.screen().physical_rows;
|
||||
let avail_scrollback = self.screen().lines.len().saturating_sub(rows);
|
||||
|
||||
let position = position.min(avail_scrollback as i64);
|
||||
|
||||
self.viewport_offset = position;
|
||||
let top = self
|
||||
.screen()
|
||||
.lines
|
||||
.len()
|
||||
.saturating_sub(rows + position as usize);
|
||||
{
|
||||
let screen = self.screen_mut();
|
||||
for y in top..top + rows {
|
||||
screen.line_mut(y).set_dirty();
|
||||
}
|
||||
}
|
||||
self.recompute_highlight();
|
||||
}
|
||||
|
||||
/// Adjust the scroll position of the viewport by delta.
|
||||
/// Dirties the lines that are now in view.
|
||||
pub fn scroll_viewport(&mut self, delta: VisibleRowIndex) {
|
||||
let position = self.viewport_offset - delta;
|
||||
self.set_scroll_viewport(position);
|
||||
}
|
||||
|
||||
fn scroll_up(&mut self, num_rows: usize) {
|
||||
self.clear_selection();
|
||||
let scroll_region = self.scroll_region.clone();
|
||||
self.screen_mut().scroll_up(&scroll_region, num_rows)
|
||||
}
|
||||
|
||||
fn scroll_down(&mut self, num_rows: usize) {
|
||||
self.clear_selection();
|
||||
let scroll_region = self.scroll_region.clone();
|
||||
self.screen_mut().scroll_down(&scroll_region, num_rows)
|
||||
}
|
||||
@ -1581,7 +1030,6 @@ impl TerminalState {
|
||||
)) => {
|
||||
if !self.screen.is_alt_screen_active() {
|
||||
self.screen.activate_alt_screen();
|
||||
self.set_scroll_viewport(0);
|
||||
}
|
||||
}
|
||||
Mode::ResetDecPrivateMode(DecPrivateMode::Code(
|
||||
@ -1589,7 +1037,6 @@ impl TerminalState {
|
||||
)) => {
|
||||
if self.screen.is_alt_screen_active() {
|
||||
self.screen.activate_primary_screen();
|
||||
self.set_scroll_viewport(0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1650,7 +1097,6 @@ impl TerminalState {
|
||||
self.screen.activate_alt_screen();
|
||||
self.set_cursor_pos(&Position::Absolute(0), &Position::Absolute(0));
|
||||
self.erase_in_display(EraseInDisplay::EraseDisplay);
|
||||
self.set_scroll_viewport(0);
|
||||
}
|
||||
}
|
||||
Mode::ResetDecPrivateMode(DecPrivateMode::Code(
|
||||
@ -1659,7 +1105,6 @@ impl TerminalState {
|
||||
if self.screen.is_alt_screen_active() {
|
||||
self.screen.activate_primary_screen();
|
||||
self.restore_cursor();
|
||||
self.set_scroll_viewport(0);
|
||||
}
|
||||
}
|
||||
Mode::SaveDecPrivateMode(DecPrivateMode::Code(_))
|
||||
@ -1773,14 +1218,6 @@ impl TerminalState {
|
||||
screen.clear_line(y, col_range.clone(), &pen);
|
||||
}
|
||||
}
|
||||
|
||||
for y in row_range {
|
||||
if self
|
||||
.clear_selection_if_intersects(col_range.clone(), y as ScrollbackOrVisibleRowIndex)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn perform_csi_edit(&mut self, edit: Edit) {
|
||||
@ -1795,16 +1232,11 @@ impl TerminalState {
|
||||
screen.erase_cell(x, y);
|
||||
}
|
||||
}
|
||||
self.clear_selection_if_intersects(x..limit, y as ScrollbackOrVisibleRowIndex);
|
||||
}
|
||||
Edit::DeleteLine(n) => {
|
||||
if self.scroll_region.contains(&self.cursor.y) {
|
||||
let scroll_region = self.cursor.y..self.scroll_region.end;
|
||||
self.screen_mut().scroll_up(&scroll_region, n as usize);
|
||||
|
||||
let scrollback_region = self.cursor.y as ScrollbackOrVisibleRowIndex
|
||||
..self.scroll_region.end as ScrollbackOrVisibleRowIndex;
|
||||
self.clear_selection_if_intersects_rows(scrollback_region);
|
||||
}
|
||||
}
|
||||
Edit::EraseCharacter(n) => {
|
||||
@ -1818,7 +1250,6 @@ impl TerminalState {
|
||||
screen.set_cell(x, y, &blank);
|
||||
}
|
||||
}
|
||||
self.clear_selection_if_intersects(x..limit, y as ScrollbackOrVisibleRowIndex);
|
||||
}
|
||||
|
||||
Edit::EraseInLine(erase) => {
|
||||
@ -1833,7 +1264,6 @@ impl TerminalState {
|
||||
};
|
||||
|
||||
self.screen_mut().clear_line(cy, range.clone(), &pen);
|
||||
self.clear_selection_if_intersects(range, cy as ScrollbackOrVisibleRowIndex);
|
||||
}
|
||||
Edit::InsertCharacter(n) => {
|
||||
let y = self.cursor.y;
|
||||
@ -1847,16 +1277,11 @@ impl TerminalState {
|
||||
screen.insert_cell(x, y);
|
||||
}
|
||||
}
|
||||
self.clear_selection_if_intersects(x..limit, y as ScrollbackOrVisibleRowIndex);
|
||||
}
|
||||
Edit::InsertLine(n) => {
|
||||
if self.scroll_region.contains(&self.cursor.y) {
|
||||
let scroll_region = self.cursor.y..self.scroll_region.end;
|
||||
self.screen_mut().scroll_down(&scroll_region, n as usize);
|
||||
|
||||
let scrollback_region = self.cursor.y as ScrollbackOrVisibleRowIndex
|
||||
..self.scroll_region.end as ScrollbackOrVisibleRowIndex;
|
||||
self.clear_selection_if_intersects_rows(scrollback_region);
|
||||
}
|
||||
}
|
||||
Edit::ScrollDown(n) => self.scroll_down(n as usize),
|
||||
@ -2144,11 +1569,6 @@ impl<'a> Performer<'a> {
|
||||
// Assign the cell
|
||||
self.screen_mut().set_cell(x + x_offset, y, &cell);
|
||||
|
||||
self.clear_selection_if_intersects(
|
||||
x..x + print_width,
|
||||
y as ScrollbackOrVisibleRowIndex,
|
||||
);
|
||||
|
||||
if self.insert {
|
||||
x_offset += print_width;
|
||||
} else if x + print_width < width {
|
||||
|
@ -6,7 +6,7 @@ mod c0;
|
||||
use bitflags::bitflags;
|
||||
mod c1;
|
||||
mod csi;
|
||||
mod selection;
|
||||
// mod selection; FIXME: port to render layer
|
||||
use crate::color::ColorPalette;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::cell::RefCell;
|
||||
@ -79,7 +79,6 @@ impl TerminalHost for TestHost {
|
||||
struct TestTerm {
|
||||
term: Terminal,
|
||||
host: TestHost,
|
||||
clip: Arc<dyn Clipboard>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -111,7 +110,6 @@ impl TestTerm {
|
||||
Self {
|
||||
term,
|
||||
host: TestHost::new(),
|
||||
clip,
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,74 +169,6 @@ impl TestTerm {
|
||||
self.print("!p");
|
||||
}
|
||||
|
||||
fn mouse(&mut self, event: MouseEvent) -> Result<(), Error> {
|
||||
self.term.mouse_event(event, &mut self.host)
|
||||
}
|
||||
|
||||
fn get_clipboard(&self) -> Option<String> {
|
||||
self.clip.get_contents().ok()
|
||||
}
|
||||
|
||||
/// Inject n_times clicks of the button at the specified coordinates
|
||||
fn click_n(&mut self, x: usize, y: i64, button: MouseButton, n_times: usize) {
|
||||
for _ in 0..n_times {
|
||||
self.mouse(MouseEvent {
|
||||
kind: MouseEventKind::Press,
|
||||
x,
|
||||
y,
|
||||
button,
|
||||
modifiers: KeyModifiers::default(),
|
||||
})
|
||||
.unwrap();
|
||||
self.mouse(MouseEvent {
|
||||
kind: MouseEventKind::Release,
|
||||
x,
|
||||
y,
|
||||
button,
|
||||
modifiers: KeyModifiers::default(),
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// Left mouse button drag from the start to the end coordinates
|
||||
fn drag_select(&mut self, start_x: usize, start_y: i64, end_x: usize, end_y: i64) {
|
||||
// Break any outstanding click streak that might falsely trigger due to
|
||||
// this unit test happening much faster than the CLICK_INTERVAL allows.
|
||||
self.click_n(0, 0, MouseButton::Right, 1);
|
||||
|
||||
// Now inject the appropriate left click events
|
||||
|
||||
self.mouse(MouseEvent {
|
||||
kind: MouseEventKind::Press,
|
||||
x: start_x,
|
||||
y: start_y,
|
||||
button: MouseButton::Left,
|
||||
modifiers: KeyModifiers::default(),
|
||||
})
|
||||
.unwrap();
|
||||
assert!(self.get_clipboard().is_none());
|
||||
|
||||
self.mouse(MouseEvent {
|
||||
kind: MouseEventKind::Move,
|
||||
x: end_x,
|
||||
y: end_y,
|
||||
button: MouseButton::None,
|
||||
modifiers: KeyModifiers::default(),
|
||||
})
|
||||
.unwrap();
|
||||
assert!(self.get_clipboard().is_none());
|
||||
|
||||
self.mouse(MouseEvent {
|
||||
kind: MouseEventKind::Release,
|
||||
x: end_x,
|
||||
y: end_y,
|
||||
button: MouseButton::Left,
|
||||
modifiers: KeyModifiers::default(),
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn assert_cursor_pos(&self, x: usize, y: i64, reason: Option<&str>) {
|
||||
let cursor = self.cursor_pos();
|
||||
let expect = CursorPosition {
|
||||
@ -254,41 +184,19 @@ impl TestTerm {
|
||||
}
|
||||
|
||||
fn assert_dirty_lines(&self, expected: &[usize], reason: Option<&str>) {
|
||||
let dirty_indices: Vec<usize> = self.get_dirty_lines().iter().map(|&(i, ..)| i).collect();
|
||||
let dirty_indices: Vec<usize> = self
|
||||
.screen()
|
||||
.lines
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, line)| if line.is_dirty() { Some(i) } else { None })
|
||||
.collect();
|
||||
assert_eq!(
|
||||
&dirty_indices, &expected,
|
||||
"actual dirty lines (left) didn't match expected dirty lines (right) reason={:?}",
|
||||
reason
|
||||
);
|
||||
}
|
||||
|
||||
fn viewport_lines(&self) -> Vec<Line> {
|
||||
let screen = self.screen();
|
||||
let line_count = screen.lines.len();
|
||||
let viewport = self.viewport_offset;
|
||||
let phs_rows = screen.physical_rows;
|
||||
screen
|
||||
.all_lines()
|
||||
.iter()
|
||||
.skip((line_count as i64 - phs_rows as i64 - viewport) as usize)
|
||||
.take(phs_rows)
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
fn print_viewport_lines(&self) {
|
||||
println!("viewport contents are:");
|
||||
for line in self.viewport_lines() {
|
||||
println!("[{}]", line.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_viewport_contents(&self, expect_lines: &[&str]) {
|
||||
self.print_viewport_lines();
|
||||
|
||||
let expect: Vec<Line> = expect_lines.iter().map(|s| (*s).into()).collect();
|
||||
|
||||
assert_lines_equal(&self.viewport_lines(), &expect, Compare::TEXT);
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for TestTerm {
|
||||
|
Loading…
Reference in New Issue
Block a user