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