use std::collections::VecDeque;
use crate::cell::Cell;
use crate::event::TermEvent;
use crate::grid::Grid;
use crate::serialize::{MarkerId, MarkerKind, MarkerPosition};
use super::{CommandLine, MAX_MARKERS, Marker, MarkerEntry, MarkerIndex, Term};
impl Term {
fn primary_grid(&self) -> &Grid {
if self.on_alt {
&self.alt_grid
} else {
&self.grid
}
}
pub(super) fn markers(&self) -> &VecDeque<Marker> {
if self.on_alt {
&self.alt_markers
} else {
&self.normal_markers
}
}
fn markers_mut(&mut self) -> &mut VecDeque<Marker> {
if self.on_alt {
&mut self.alt_markers
} else {
&mut self.normal_markers
}
}
pub fn add_marker(&mut self, row: usize) -> MarkerId {
let line = self.viewport_to_abs(row, 0).line;
self.push_marker(line, 0, MarkerKind::Plain)
}
fn push_marker(&mut self, line: usize, col: usize, kind: MarkerKind) -> MarkerId {
let id = MarkerId(self.next_marker_id);
self.next_marker_id += 1;
let mut disposed = Vec::new();
let markers = self.markers_mut();
while markers.len() >= MAX_MARKERS {
let Some(m) = markers.pop_front() else {
break;
};
disposed.push(m.id);
}
markers.push_back(Marker {
id,
line,
col,
kind,
});
for id in disposed {
self.events.push(TermEvent::MarkerDisposed(id));
}
self.events.push(TermEvent::MarkerCreated {
id,
line: line as u32,
kind,
});
id
}
pub(super) fn add_command_mark(&mut self, kind: MarkerKind) {
if self.on_alt {
return;
}
let line = self.scrollback.len() + self.cursor.row;
let col = self.cursor.col + usize::from(self.cursor.pending_wrap);
self.push_marker(line, col, kind);
}
pub fn command_marks(&self) -> Vec<(MarkerId, usize, MarkerKind)> {
self.normal_markers
.iter()
.filter(|m| m.kind != MarkerKind::Plain)
.map(|m| (m.id, m.line, m.kind))
.collect()
}
pub fn command_lines(&self) -> Vec<CommandLine> {
let mut out: Vec<CommandLine> = Vec::new();
let mut pending: Option<(usize, usize)> = None;
for m in &self.normal_markers {
match m.kind {
MarkerKind::CommandStart => pending = Some((m.line, m.col)),
MarkerKind::OutputStart => {
if let Some((b_line, b_col)) = pending.take() {
let (b_line, b_col) =
self.command_start(self.primary_grid(), b_line, b_col, m.line);
let command =
self.extract_lines(self.primary_grid(), b_line, b_col, m.line, m.col);
out.push(CommandLine {
line: self.doc_line_of(self.primary_grid(), b_line),
command,
exit: None,
});
}
}
MarkerKind::CommandFinished(exit) => {
if let Some(last) = out.last_mut()
&& last.exit.is_none()
{
last.exit = exit;
}
}
MarkerKind::Plain | MarkerKind::PromptStart => {}
}
}
out
}
fn command_start(&self, grid: &Grid, line: usize, col: usize, end: usize) -> (usize, usize) {
let (mut line, mut col) = (line, col);
while line < end && !self.row_in(grid, line).is_wrapped() {
let cells = self.line_in(grid, line);
if col < cells.len() && !cells[col..].iter().all(Cell::is_blank) {
break;
}
line += 1;
col = 0;
}
(line, col)
}
fn doc_line_of(&self, grid: &Grid, abs: usize) -> usize {
(0..abs)
.filter(|&l| !self.row_in(grid, l).is_wrapped())
.count()
}
pub fn remove_marker(&mut self, id: MarkerId) {
let before = self.normal_markers.len() + self.alt_markers.len();
self.normal_markers.retain(|m| m.id != id);
self.alt_markers.retain(|m| m.id != id);
if self.normal_markers.len() + self.alt_markers.len() != before {
self.events.push(TermEvent::MarkerDisposed(id));
}
}
pub fn marker_index(&self) -> MarkerIndex {
MarkerIndex {
markers: self
.markers()
.iter()
.map(|m| MarkerEntry {
id: m.id,
line: m.line as u32,
kind: m.kind,
})
.collect(),
evicted_total: self.evicted_total,
epoch: self.marker_epoch,
}
}
pub(super) fn bump_marker_epoch(&mut self) {
self.marker_epoch = self.marker_epoch.wrapping_add(1);
}
pub(super) fn markers_shift_below_margin(&mut self, from: usize) {
let mut moved = false;
for m in &mut self.normal_markers {
if m.line >= from {
m.line += 1;
moved = true;
}
}
if moved {
self.bump_marker_epoch();
}
}
pub(super) fn markers_evict_oldest(&mut self) {
let mut disposed = Vec::new();
self.normal_markers.retain_mut(|m| {
if m.line == 0 {
disposed.push(m.id);
false
} else {
m.line -= 1;
true
}
});
for id in disposed {
self.events.push(TermEvent::MarkerDisposed(id));
}
}
pub(super) fn markers_rotate_region(&mut self, top: usize, bottom: usize, up: bool) {
let mut disposed = Vec::new();
let mut moved = false;
self.markers_mut().retain_mut(|m| {
if m.line < top || m.line > bottom {
return true; }
let dropped_edge = if up { top } else { bottom };
if m.line == dropped_edge {
disposed.push(m.id);
false
} else {
m.line = if up { m.line - 1 } else { m.line + 1 };
moved = true;
true
}
});
for id in disposed {
self.events.push(TermEvent::MarkerDisposed(id));
}
if moved {
self.bump_marker_epoch();
}
}
pub(super) fn marker_positions(&self) -> Vec<MarkerPosition> {
let top = self.scrollback.len() - self.display_offset;
let rows = self.grid.rows();
self.markers()
.iter()
.filter_map(|m| {
let row = m.line.checked_sub(top)?;
(row < rows).then_some(MarkerPosition {
id: m.id,
row,
kind: m.kind,
})
})
.collect()
}
}