1use super::TerminalManager;
2use par_term_config::{Cell, Theme};
3
4impl TerminalManager {
5 pub fn get_cells_with_scrollback(
7 &self,
8 scroll_offset: usize,
9 selection: Option<((usize, usize), (usize, usize))>,
10 rectangular: bool,
11 _cursor: Option<((usize, usize), f32)>,
12 ) -> Vec<Cell> {
13 let pty = self.pty_session.lock();
14 let terminal = pty.terminal();
15 let mut term = terminal.lock();
16 let grid = term.active_grid();
17
18 let cursor_with_style = None;
19
20 let rows = grid.rows();
21 let cols = grid.cols();
22 let scrollback_len = grid.scrollback_len();
23 let clamped_offset = scroll_offset.min(scrollback_len);
24 let total_lines = scrollback_len + rows;
25 let end_line = total_lines.saturating_sub(clamped_offset);
26 let start_line = end_line.saturating_sub(rows);
27
28 let mut cells = Vec::with_capacity(rows * cols);
29
30 for line_idx in start_line..end_line {
31 let screen_row = line_idx - start_line;
32
33 if line_idx < scrollback_len {
34 if let Some(line) = grid.scrollback_line(line_idx) {
35 Self::push_line_from_slice(
36 line,
37 cols,
38 &mut cells,
39 screen_row,
40 selection,
41 rectangular,
42 cursor_with_style,
43 &self.theme,
44 );
45 } else {
46 Self::push_empty_cells(cols, &mut cells);
47 }
48 } else {
49 let grid_row = line_idx - scrollback_len;
50 Self::push_grid_row(
51 grid,
52 grid_row,
53 cols,
54 &mut cells,
55 screen_row,
56 selection,
57 rectangular,
58 cursor_with_style,
59 &self.theme,
60 );
61 }
62 }
63
64 let highlights = term.get_trigger_highlights();
66 for highlight in &highlights {
67 let abs_row = scrollback_len + highlight.row;
68 if abs_row < start_line || abs_row >= end_line {
69 continue;
70 }
71 let screen_row = abs_row - start_line;
72
73 for col in highlight.col_start..highlight.col_end.min(cols) {
74 let cell_idx = screen_row * cols + col;
75 if cell_idx < cells.len() {
76 if let Some((r, g, b)) = highlight.fg {
77 cells[cell_idx].fg_color = [r, g, b, 255];
78 }
79 if let Some((r, g, b)) = highlight.bg {
80 cells[cell_idx].bg_color = [r, g, b, 255];
81 }
82 }
83 }
84 }
85 term.clear_expired_highlights();
86
87 cells
88 }
89
90 #[allow(clippy::too_many_arguments)]
91 pub(crate) fn push_line_from_slice(
92 line: &[par_term_emu_core_rust::cell::Cell],
93 cols: usize,
94 dest: &mut Vec<Cell>,
95 screen_row: usize,
96 selection: Option<((usize, usize), (usize, usize))>,
97 rectangular: bool,
98 cursor: Option<(
99 (usize, usize),
100 f32,
101 par_term_emu_core_rust::cursor::CursorStyle,
102 )>,
103 theme: &Theme,
104 ) {
105 let copy_len = cols.min(line.len());
106 for (col, cell) in line[..copy_len].iter().enumerate() {
107 let is_selected = Self::is_cell_selected(col, screen_row, selection, rectangular);
108 let cursor_info = cursor.and_then(|((cx, cy), opacity, style)| {
109 if cx == col && cy == screen_row {
110 Some((opacity, style))
111 } else {
112 None
113 }
114 });
115 dest.push(Self::convert_term_cell_with_theme(
116 cell,
117 is_selected,
118 cursor_info,
119 theme,
120 ));
121 }
122
123 if copy_len < cols {
124 Self::push_empty_cells(cols - copy_len, dest);
125 }
126 }
127
128 #[allow(clippy::too_many_arguments)]
129 pub(crate) fn push_grid_row(
130 grid: &par_term_emu_core_rust::grid::Grid,
131 row: usize,
132 cols: usize,
133 dest: &mut Vec<Cell>,
134 screen_row: usize,
135 selection: Option<((usize, usize), (usize, usize))>,
136 rectangular: bool,
137 cursor: Option<(
138 (usize, usize),
139 f32,
140 par_term_emu_core_rust::cursor::CursorStyle,
141 )>,
142 theme: &Theme,
143 ) {
144 for col in 0..cols {
145 let is_selected = Self::is_cell_selected(col, screen_row, selection, rectangular);
146 let cursor_info = cursor.and_then(|((cx, cy), opacity, style)| {
147 if cx == col && cy == screen_row {
148 Some((opacity, style))
149 } else {
150 None
151 }
152 });
153 if let Some(cell) = grid.get(col, row) {
154 dest.push(Self::convert_term_cell_with_theme(
155 cell,
156 is_selected,
157 cursor_info,
158 theme,
159 ));
160 } else {
161 dest.push(Cell::default());
162 }
163 }
164 }
165
166 pub(crate) fn push_empty_cells(count: usize, dest: &mut Vec<Cell>) {
167 for _ in 0..count {
168 dest.push(Cell::default());
169 }
170 }
171
172 pub(crate) fn is_cell_selected(
174 col: usize,
175 row: usize,
176 selection: Option<((usize, usize), (usize, usize))>,
177 rectangular: bool,
178 ) -> bool {
179 if let Some(((start_col, start_row), (end_col, end_row))) = selection {
180 if rectangular {
181 let min_col = start_col.min(end_col);
182 let max_col = start_col.max(end_col);
183 let min_row = start_row.min(end_row);
184 let max_row = start_row.max(end_row);
185
186 return col >= min_col && col <= max_col && row >= min_row && row <= max_row;
187 }
188
189 if start_row == end_row {
190 return row == start_row && col >= start_col && col <= end_col;
191 }
192
193 if row == start_row {
194 return col >= start_col;
195 } else if row == end_row {
196 return col <= end_col;
197 } else if row > start_row && row < end_row {
198 return true;
199 }
200 }
201 false
202 }
203
204 pub(crate) fn convert_term_cell_with_theme(
205 term_cell: &par_term_emu_core_rust::cell::Cell,
206 is_selected: bool,
207 cursor_info: Option<(f32, par_term_emu_core_rust::cursor::CursorStyle)>,
208 theme: &Theme,
209 ) -> Cell {
210 use par_term_emu_core_rust::color::{Color as TermColor, NamedColor};
211 use par_term_emu_core_rust::cursor::CursorStyle as TermCursorStyle;
212
213 let bg_rgb = term_cell.bg.to_rgb();
214 let fg_rgb = term_cell.fg.to_rgb();
215 let has_colored_bg = bg_rgb != (0, 0, 0);
216 let has_reverse = term_cell.flags.reverse();
217
218 if has_colored_bg || has_reverse {
219 log::debug!(
220 "Cell with colored BG or REVERSE: '{}' (U+{:04X}): fg={:?} (RGB:{},{},{}), bg={:?} (RGB:{},{},{}), reverse={}, flags={:?}",
221 if term_cell.c.is_control() {
222 '?'
223 } else {
224 term_cell.c
225 },
226 term_cell.c as u32,
227 term_cell.fg,
228 fg_rgb.0,
229 fg_rgb.1,
230 fg_rgb.2,
231 term_cell.bg,
232 bg_rgb.0,
233 bg_rgb.1,
234 bg_rgb.2,
235 has_reverse,
236 term_cell.flags
237 );
238 }
239
240 let fg = match &term_cell.fg {
242 TermColor::Named(named) => {
243 #[allow(unreachable_patterns)]
244 let theme_color = match named {
245 NamedColor::Black => theme.black,
246 NamedColor::Red => theme.red,
247 NamedColor::Green => theme.green,
248 NamedColor::Yellow => theme.yellow,
249 NamedColor::Blue => theme.blue,
250 NamedColor::Magenta => theme.magenta,
251 NamedColor::Cyan => theme.cyan,
252 NamedColor::White => theme.white,
253 NamedColor::BrightBlack => theme.bright_black,
254 NamedColor::BrightRed => theme.bright_red,
255 NamedColor::BrightGreen => theme.bright_green,
256 NamedColor::BrightYellow => theme.bright_yellow,
257 NamedColor::BrightBlue => theme.bright_blue,
258 NamedColor::BrightMagenta => theme.bright_magenta,
259 NamedColor::BrightCyan => theme.bright_cyan,
260 NamedColor::BrightWhite => theme.bright_white,
261 _ => theme.foreground,
262 };
263 (theme_color.r, theme_color.g, theme_color.b)
264 }
265 _ => term_cell.fg.to_rgb(),
266 };
267
268 let bg = match &term_cell.bg {
269 TermColor::Named(named) => {
270 #[allow(unreachable_patterns)]
271 let theme_color = match named {
272 NamedColor::Black => theme.black,
273 NamedColor::Red => theme.red,
274 NamedColor::Green => theme.green,
275 NamedColor::Yellow => theme.yellow,
276 NamedColor::Blue => theme.blue,
277 NamedColor::Magenta => theme.magenta,
278 NamedColor::Cyan => theme.cyan,
279 NamedColor::White => theme.white,
280 NamedColor::BrightBlack => theme.bright_black,
281 NamedColor::BrightRed => theme.bright_red,
282 NamedColor::BrightGreen => theme.bright_green,
283 NamedColor::BrightYellow => theme.bright_yellow,
284 NamedColor::BrightBlue => theme.bright_blue,
285 NamedColor::BrightMagenta => theme.bright_magenta,
286 NamedColor::BrightCyan => theme.bright_cyan,
287 NamedColor::BrightWhite => theme.bright_white,
288 _ => theme.background,
289 };
290 (theme_color.r, theme_color.g, theme_color.b)
291 }
292 _ => term_cell.bg.to_rgb(),
293 };
294
295 let is_reverse = term_cell.flags.reverse();
296
297 let (fg_color, bg_color) = if let Some((opacity, style)) = cursor_info {
298 let blend = |normal: u8, inverted: u8, opacity: f32| -> u8 {
299 (normal as f32 * (1.0 - opacity) + inverted as f32 * opacity) as u8
300 };
301
302 match style {
303 TermCursorStyle::SteadyBlock | TermCursorStyle::BlinkingBlock => (
304 [
305 blend(fg.0, bg.0, opacity),
306 blend(fg.1, bg.1, opacity),
307 blend(fg.2, bg.2, opacity),
308 255,
309 ],
310 [
311 blend(bg.0, fg.0, opacity),
312 blend(bg.1, fg.1, opacity),
313 blend(bg.2, fg.2, opacity),
314 255,
315 ],
316 ),
317 TermCursorStyle::SteadyBar
318 | TermCursorStyle::BlinkingBar
319 | TermCursorStyle::SteadyUnderline
320 | TermCursorStyle::BlinkingUnderline => (
321 [
322 blend(fg.0, bg.0, opacity),
323 blend(fg.1, bg.1, opacity),
324 blend(fg.2, bg.2, opacity),
325 255,
326 ],
327 [
328 blend(bg.0, fg.0, opacity),
329 blend(bg.1, fg.1, opacity),
330 blend(bg.2, fg.2, opacity),
331 255,
332 ],
333 ),
334 }
335 } else if is_selected || is_reverse {
336 ([bg.0, bg.1, bg.2, 255], [fg.0, fg.1, fg.2, 255])
337 } else {
338 ([fg.0, fg.1, fg.2, 255], [bg.0, bg.1, bg.2, 255])
339 };
340
341 let grapheme = if term_cell.has_combining_chars() {
342 term_cell.get_grapheme()
343 } else {
344 term_cell.base_char().to_string()
345 };
346
347 Cell {
348 grapheme,
349 fg_color,
350 bg_color,
351 bold: term_cell.flags.bold(),
352 italic: term_cell.flags.italic(),
353 underline: term_cell.flags.underline(),
354 strikethrough: term_cell.flags.strikethrough(),
355 hyperlink_id: term_cell.flags.hyperlink_id,
356 wide_char: term_cell.flags.wide_char(),
357 wide_char_spacer: term_cell.flags.wide_char_spacer(),
358 }
359 }
360}