1use crate::output::{CharacterChunk, SixelImageChunk};
2use crate::panes::sixel::SixelImageStore;
3use crate::panes::LinkHandler;
4use crate::panes::{
5 grid::Grid,
6 terminal_character::{render_first_run_banner, TerminalCharacter, EMPTY_TERMINAL_CHARACTER},
7};
8use crate::pty::VteBytes;
9use crate::tab::{AdjustedInput, Pane};
10use crate::ClientId;
11use std::cell::RefCell;
12use std::collections::{HashMap, HashSet};
13use std::fmt::Debug;
14use std::rc::Rc;
15use std::time::{self, Instant};
16use vte;
17use zellij_utils::input::command::RunCommand;
18use zellij_utils::input::mouse::{MouseEvent, MouseEventType};
19use zellij_utils::pane_size::Offset;
20use zellij_utils::{
21 data::{
22 BareKey, InputMode, KeyWithModifier, Palette, PaletteColor, PaneId as ZellijUtilsPaneId,
23 Style, Styling,
24 },
25 errors::prelude::*,
26 input::layout::Run,
27 pane_size::PaneGeom,
28 pane_size::SizeInPixels,
29 position::Position,
30 shared::make_terminal_title,
31};
32
33use crate::ui::pane_boundaries_frame::{FrameParams, PaneFrame};
34
35pub const SELECTION_SCROLL_INTERVAL_MS: u64 = 10;
36
37const LEFT_ARROW: &[u8] = &[27, 91, 68];
39const RIGHT_ARROW: &[u8] = &[27, 91, 67];
40const UP_ARROW: &[u8] = &[27, 91, 65];
41const DOWN_ARROW: &[u8] = &[27, 91, 66];
42const HOME_KEY: &[u8] = &[27, 91, 72];
43const END_KEY: &[u8] = &[27, 91, 70];
44pub const BRACKETED_PASTE_BEGIN: &[u8] = &[27, 91, 50, 48, 48, 126];
45pub const BRACKETED_PASTE_END: &[u8] = &[27, 91, 50, 48, 49, 126];
46const ENTER_NEWLINE: &[u8] = &[10];
47const ESC: &[u8] = &[27];
48const ENTER_CARRIAGE_RETURN: &[u8] = &[13];
49const SPACE: &[u8] = &[32];
50const CTRL_C: &[u8] = &[3]; const TERMINATING_STRING: &str = "\0";
52const DELETE_KEY: &str = "\u{007F}";
53const BACKSPACE_KEY: &str = "\u{0008}";
54
55#[derive(Debug, Clone, PartialEq, Eq, Hash)]
57enum AnsiEncoding {
58 Left,
59 Right,
60 Up,
61 Down,
62 Home,
63 End,
64}
65
66impl AnsiEncoding {
67 pub fn as_bytes(&self) -> &[u8] {
71 match self {
72 Self::Left => "OD".as_bytes(),
73 Self::Right => "OC".as_bytes(),
74 Self::Up => "OA".as_bytes(),
75 Self::Down => "OB".as_bytes(),
76 Self::Home => &[27, 79, 72], Self::End => &[27, 79, 70], }
79 }
80
81 pub fn as_vec_bytes(&self) -> Vec<u8> {
82 self.as_bytes().to_vec()
83 }
84}
85
86#[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug)]
87pub enum PaneId {
88 Terminal(u32),
89 Plugin(u32), }
91
92impl From<ZellijUtilsPaneId> for PaneId {
94 fn from(zellij_utils_pane_id: ZellijUtilsPaneId) -> Self {
95 match zellij_utils_pane_id {
96 ZellijUtilsPaneId::Terminal(id) => PaneId::Terminal(id),
97 ZellijUtilsPaneId::Plugin(id) => PaneId::Plugin(id),
98 }
99 }
100}
101
102impl Into<ZellijUtilsPaneId> for PaneId {
103 fn into(self) -> ZellijUtilsPaneId {
104 match self {
105 PaneId::Terminal(id) => ZellijUtilsPaneId::Terminal(id),
106 PaneId::Plugin(id) => ZellijUtilsPaneId::Plugin(id),
107 }
108 }
109}
110
111type IsFirstRun = bool;
112
113#[allow(clippy::too_many_arguments)]
116pub struct TerminalPane {
117 pub grid: Grid,
118 pub pid: u32,
119 pub selectable: bool,
120 pub geom: PaneGeom,
121 pub geom_override: Option<PaneGeom>,
122 pub active_at: Instant,
123 pub style: Style,
124 vte_parser: vte::Parser,
125 selection_scrolled_at: time::Instant,
126 content_offset: Offset,
127 pane_title: String,
128 pane_name: String,
129 prev_pane_name: String,
130 frame: HashMap<ClientId, PaneFrame>,
131 borderless: bool,
132 exclude_from_sync: bool,
133 fake_cursor_locations: HashSet<(usize, usize)>, search_term: String,
135 is_held: Option<(Option<i32>, IsFirstRun, RunCommand)>, banner: Option<String>, pane_frame_color_override: Option<(PaletteColor, Option<String>)>,
140 invoked_with: Option<Run>,
141 #[allow(dead_code)]
142 arrow_fonts: bool,
143}
144
145impl Pane for TerminalPane {
146 fn x(&self) -> usize {
147 self.get_x()
148 }
149 fn y(&self) -> usize {
150 self.get_y()
151 }
152 fn rows(&self) -> usize {
153 self.get_rows()
154 }
155 fn cols(&self) -> usize {
156 self.get_columns()
157 }
158 fn get_content_x(&self) -> usize {
159 self.get_x() + self.content_offset.left
160 }
161 fn get_content_y(&self) -> usize {
162 self.get_y() + self.content_offset.top
163 }
164 fn get_content_columns(&self) -> usize {
165 self.get_columns()
168 .saturating_sub(self.content_offset.left + self.content_offset.right)
169 }
170 fn get_content_rows(&self) -> usize {
171 self.get_rows()
174 .saturating_sub(self.content_offset.top + self.content_offset.bottom)
175 }
176 fn reset_size_and_position_override(&mut self) {
177 self.geom_override = None;
178 self.reflow_lines();
179 }
180 fn set_geom(&mut self, position_and_size: PaneGeom) {
181 let is_pinned = self.geom.is_pinned;
182 self.geom = position_and_size;
183 self.geom.is_pinned = is_pinned;
184 self.reflow_lines();
185 self.render_full_viewport();
186 }
187 fn set_geom_override(&mut self, pane_geom: PaneGeom) {
188 self.geom_override = Some(pane_geom);
189 self.reflow_lines();
190 }
191 fn handle_pty_bytes(&mut self, bytes: VteBytes) {
192 self.set_should_render(true);
193 for &byte in &bytes {
194 self.vte_parser.advance(&mut self.grid, byte);
195 }
196 }
197 fn cursor_coordinates(&self) -> Option<(usize, usize)> {
198 if self.get_content_rows() < 1 || self.get_content_columns() < 1 {
200 return None;
202 }
203 let Offset { top, left, .. } = self.content_offset;
204 self.grid
205 .cursor_coordinates()
206 .map(|(x, y)| (x + left, y + top))
207 }
208 fn is_mid_frame(&self) -> bool {
209 self.grid.is_mid_frame()
210 }
211 fn adjust_input_to_terminal(
212 &mut self,
213 key_with_modifier: &Option<KeyWithModifier>,
214 raw_input_bytes: Vec<u8>,
215 raw_input_bytes_are_kitty: bool,
216 client_id: Option<ClientId>,
217 ) -> Option<AdjustedInput> {
218 self.reset_selection(client_id);
224 if !self.grid.bracketed_paste_mode {
225 match raw_input_bytes.as_slice() {
230 BRACKETED_PASTE_BEGIN | BRACKETED_PASTE_END => {
231 return Some(AdjustedInput::WriteBytesToTerminal(vec![]))
232 },
233 _ => {},
234 }
235 }
236
237 if self.is_held.is_some() {
238 if key_with_modifier
239 .as_ref()
240 .map(|k| k.is_key_without_modifier(BareKey::Enter))
241 .unwrap_or(false)
242 {
243 self.handle_held_run()
244 } else if key_with_modifier
245 .as_ref()
246 .map(|k| k.is_key_without_modifier(BareKey::Esc))
247 .unwrap_or(false)
248 {
249 self.handle_held_drop_to_shell()
250 } else if key_with_modifier
251 .as_ref()
252 .map(|k| k.is_key_with_ctrl_modifier(BareKey::Char('c')))
253 .unwrap_or(false)
254 {
255 Some(AdjustedInput::CloseThisPane)
256 } else {
257 match raw_input_bytes.as_slice() {
258 ENTER_CARRIAGE_RETURN | ENTER_NEWLINE | SPACE => self.handle_held_run(),
259 ESC => self.handle_held_drop_to_shell(),
260 CTRL_C => Some(AdjustedInput::CloseThisPane),
261 _ => None,
262 }
263 }
264 } else {
265 if self.grid.supports_kitty_keyboard_protocol {
266 self.adjust_input_to_terminal_with_kitty_keyboard_protocol(
267 key_with_modifier,
268 raw_input_bytes,
269 raw_input_bytes_are_kitty,
270 )
271 } else {
272 self.adjust_input_to_terminal_without_kitty_keyboard_protocol(
273 key_with_modifier,
274 raw_input_bytes,
275 raw_input_bytes_are_kitty,
276 )
277 }
278 }
279 }
280 fn position_and_size(&self) -> PaneGeom {
281 self.geom
282 }
283 fn current_geom(&self) -> PaneGeom {
284 self.geom_override.unwrap_or(self.geom)
285 }
286 fn geom_override(&self) -> Option<PaneGeom> {
287 self.geom_override
288 }
289 fn should_render(&self) -> bool {
290 self.grid.should_render
291 }
292 fn set_should_render(&mut self, should_render: bool) {
293 self.grid.should_render = should_render;
294 }
295 fn render_full_viewport(&mut self) {
296 self.frame.clear();
299 self.grid.render_full_viewport();
300 }
301 fn selectable(&self) -> bool {
302 self.selectable
303 }
304 fn set_selectable(&mut self, selectable: bool) {
305 self.selectable = selectable;
306 }
307 fn render(
308 &mut self,
309 _client_id: Option<ClientId>,
310 ) -> Result<Option<(Vec<CharacterChunk>, Option<String>, Vec<SixelImageChunk>)>> {
311 if self.should_render() {
312 let content_x = self.get_content_x();
313 let content_y = self.get_content_y();
314 let rows = self.get_content_rows();
315 let columns = self.get_content_columns();
316 if rows < 1 || columns < 1 {
317 return Ok(None);
318 }
319 match self.grid.render(content_x, content_y, &self.style) {
320 Ok(rendered_assets) => {
321 self.set_should_render(false);
322 return Ok(rendered_assets);
323 },
324 e => return e,
325 }
326 } else {
327 Ok(None)
328 }
329 }
330 fn render_frame(
331 &mut self,
332 client_id: ClientId,
333 frame_params: FrameParams,
334 input_mode: InputMode,
335 ) -> Result<Option<(Vec<CharacterChunk>, Option<String>)>> {
336 let err_context = || format!("failed to render frame for client {client_id}");
337 let pane_title = if let Some(text_color_override) = self
339 .pane_frame_color_override
340 .as_ref()
341 .and_then(|(_color, text)| text.as_ref())
342 {
343 text_color_override.into()
344 } else if self.pane_name.is_empty()
345 && input_mode == InputMode::RenamePane
346 && frame_params.is_main_client
347 {
348 String::from("Enter name...")
349 } else if input_mode == InputMode::EnterSearch
350 && frame_params.is_main_client
351 && self.search_term.is_empty()
352 {
353 String::from("Enter search...")
354 } else if (input_mode == InputMode::EnterSearch || input_mode == InputMode::Search)
355 && !self.search_term.is_empty()
356 {
357 let mut modifier_text = String::new();
358 if self.grid.search_results.has_modifiers_set() {
359 let mut modifiers = Vec::new();
360 modifier_text.push_str(" [");
361 if self.grid.search_results.case_insensitive {
362 modifiers.push("c")
363 }
364 if self.grid.search_results.whole_word_only {
365 modifiers.push("o")
366 }
367 if self.grid.search_results.wrap_search {
368 modifiers.push("w")
369 }
370 modifier_text.push_str(&modifiers.join(", "));
371 modifier_text.push(']');
372 }
373 format!("SEARCHING: {}{}", self.search_term, modifier_text)
374 } else if self.pane_name.is_empty() {
375 self.grid
376 .title
377 .clone()
378 .unwrap_or_else(|| self.pane_title.clone())
379 } else {
380 self.pane_name.clone()
381 };
382
383 let frame_geom = self.current_geom();
384 let is_pinned = frame_geom.is_pinned;
385 let mut frame = PaneFrame::new(
386 frame_geom.into(),
387 self.grid.scrollback_position_and_length(),
388 pane_title,
389 frame_params,
390 )
391 .is_pinned(is_pinned);
392 if let Some((exit_status, is_first_run, _run_command)) = &self.is_held {
393 if *is_first_run {
394 frame.indicate_first_run();
395 } else {
396 frame.add_exit_status(exit_status.as_ref().copied());
397 }
398 }
399 if let Some((frame_color_override, _text)) = self.pane_frame_color_override.as_ref() {
400 frame.override_color(*frame_color_override);
401 }
402
403 let res = match self.frame.get(&client_id) {
404 Some(last_frame) => {
406 if &frame != last_frame {
407 if !self.borderless {
408 let frame_output = frame.render().with_context(err_context)?;
409 self.frame.insert(client_id, frame);
410 Some(frame_output)
411 } else {
412 None
413 }
414 } else {
415 None
416 }
417 },
418 None => {
419 if !self.borderless {
420 let frame_output = frame.render().with_context(err_context)?;
421 self.frame.insert(client_id, frame);
422 Some(frame_output)
423 } else {
424 None
425 }
426 },
427 };
428 Ok(res)
429 }
430 fn render_fake_cursor(
431 &mut self,
432 cursor_color: PaletteColor,
433 text_color: PaletteColor,
434 ) -> Option<String> {
435 let mut vte_output = None;
436 if let Some((cursor_x, cursor_y)) = self.cursor_coordinates() {
437 let mut character_under_cursor = self
438 .grid
439 .get_character_under_cursor()
440 .unwrap_or(EMPTY_TERMINAL_CHARACTER);
441 character_under_cursor.styles.update(|styles| {
442 styles.background = Some(cursor_color.into());
443 styles.foreground = Some(text_color.into());
444 });
445 self.fake_cursor_locations.insert((cursor_y, cursor_x));
447 let mut fake_cursor = format!(
448 "\u{1b}[{};{}H\u{1b}[m{}", self.get_content_y() + cursor_y + 1, self.get_content_x() + cursor_x + 1,
451 &character_under_cursor.styles,
452 );
453 fake_cursor.push(character_under_cursor.character);
454 vte_output = Some(fake_cursor);
455 }
456 vte_output
457 }
458 fn render_terminal_title(&mut self, input_mode: InputMode) -> String {
459 let pane_title = if self.pane_name.is_empty() && input_mode == InputMode::RenamePane {
460 "Enter name..."
461 } else if self.pane_name.is_empty() {
462 self.grid.title.as_deref().unwrap_or("")
463 } else {
464 &self.pane_name
465 };
466 make_terminal_title(pane_title)
467 }
468 fn update_name(&mut self, name: &str) {
469 match name {
470 TERMINATING_STRING => {
471 self.pane_name = String::new();
472 },
473 DELETE_KEY | BACKSPACE_KEY => {
474 self.pane_name.pop();
475 },
476 c => {
477 self.pane_name.push_str(c);
478 },
479 }
480 self.set_should_render(true);
481 }
482 fn pid(&self) -> PaneId {
483 PaneId::Terminal(self.pid)
484 }
485 fn reduce_height(&mut self, percent: f64) {
486 if let Some(p) = self.geom.rows.as_percent() {
487 self.geom.rows.set_percent(p - percent);
488 self.set_should_render(true);
489 }
490 }
491 fn increase_height(&mut self, percent: f64) {
492 if let Some(p) = self.geom.rows.as_percent() {
493 self.geom.rows.set_percent(p + percent);
494 self.set_should_render(true);
495 }
496 }
497 fn reduce_width(&mut self, percent: f64) {
498 if let Some(p) = self.geom.cols.as_percent() {
499 self.geom.cols.set_percent(p - percent);
500 self.set_should_render(true);
501 }
502 }
503 fn increase_width(&mut self, percent: f64) {
504 if let Some(p) = self.geom.cols.as_percent() {
505 self.geom.cols.set_percent(p + percent);
506 self.set_should_render(true);
507 }
508 }
509 fn push_down(&mut self, count: usize) {
510 self.geom.y += count;
511 self.reflow_lines();
512 }
513 fn push_right(&mut self, count: usize) {
514 self.geom.x += count;
515 self.reflow_lines();
516 }
517 fn pull_left(&mut self, count: usize) {
518 self.geom.x -= count;
519 self.reflow_lines();
520 }
521 fn pull_up(&mut self, count: usize) {
522 self.geom.y -= count;
523 self.reflow_lines();
524 }
525 fn dump_screen(&self, full: bool, _client_id: Option<ClientId>) -> String {
526 self.grid.dump_screen(full)
527 }
528 fn clear_screen(&mut self) {
529 self.grid.clear_screen()
530 }
531 fn scroll_up(&mut self, count: usize, _client_id: ClientId) {
532 self.grid.move_viewport_up(count);
533 self.set_should_render(true);
534 }
535 fn scroll_down(&mut self, count: usize, _client_id: ClientId) {
536 self.grid.move_viewport_down(count);
537 self.set_should_render(true);
538 }
539 fn clear_scroll(&mut self) {
540 self.grid.reset_viewport();
541 self.set_should_render(true);
542 }
543 fn is_scrolled(&self) -> bool {
544 self.grid.is_scrolled
545 }
546
547 fn active_at(&self) -> Instant {
548 self.active_at
549 }
550
551 fn set_active_at(&mut self, time: Instant) {
552 self.active_at = time;
553 }
554 fn cursor_shape_csi(&self) -> String {
555 self.grid.cursor_shape().get_csi_str().to_string()
556 }
557 fn drain_messages_to_pty(&mut self) -> Vec<Vec<u8>> {
558 self.grid.pending_messages_to_pty.drain(..).collect()
559 }
560
561 fn drain_clipboard_update(&mut self) -> Option<String> {
562 self.grid.pending_clipboard_update.take()
563 }
564
565 fn start_selection(&mut self, start: &Position, _client_id: ClientId) {
566 self.grid.start_selection(start);
567 self.set_should_render(true);
568 }
569
570 fn update_selection(&mut self, to: &Position, _client_id: ClientId) {
571 let should_scroll = self.selection_scrolled_at.elapsed()
572 >= time::Duration::from_millis(SELECTION_SCROLL_INTERVAL_MS);
573 let cursor_at_the_bottom = to.line.0 < 0 && should_scroll;
574 let cursor_at_the_top = to.line.0 as usize >= self.grid.height && should_scroll;
575 let cursor_in_the_middle = to.line.0 >= 0 && (to.line.0 as usize) < self.grid.height;
576
577 if cursor_at_the_bottom {
579 self.grid.scroll_up_one_line();
580 self.selection_scrolled_at = time::Instant::now();
581 self.set_should_render(true);
582 } else if cursor_at_the_top {
583 self.grid.scroll_down_one_line();
584 self.selection_scrolled_at = time::Instant::now();
585 self.set_should_render(true);
586 } else if cursor_in_the_middle {
587 self.grid.update_selection(to);
590 }
591 }
592
593 fn end_selection(&mut self, end: &Position, _client_id: ClientId) {
594 self.grid.end_selection(end);
595 self.set_should_render(true);
596 }
597
598 fn reset_selection(&mut self, _client_id: Option<ClientId>) {
599 self.grid.reset_selection();
600 }
601
602 fn get_selected_text(&self, _client_id: ClientId) -> Option<String> {
603 self.grid.get_selected_text()
604 }
605
606 fn set_frame(&mut self, _frame: bool) {
607 self.frame.clear();
608 }
609
610 fn set_content_offset(&mut self, offset: Offset) {
611 self.content_offset = offset;
612 self.reflow_lines();
613 }
614
615 fn get_content_offset(&self) -> Offset {
616 self.content_offset
617 }
618
619 fn store_pane_name(&mut self) {
620 if self.pane_name != self.prev_pane_name {
621 self.prev_pane_name = self.pane_name.clone()
622 }
623 }
624 fn load_pane_name(&mut self) {
625 if self.pane_name != self.prev_pane_name {
626 self.pane_name = self.prev_pane_name.clone()
627 }
628 }
629
630 fn set_borderless(&mut self, borderless: bool) {
631 self.borderless = borderless;
632 }
633 fn borderless(&self) -> bool {
634 self.borderless
635 }
636
637 fn set_exclude_from_sync(&mut self, exclude_from_sync: bool) {
638 self.exclude_from_sync = exclude_from_sync;
639 }
640
641 fn exclude_from_sync(&self) -> bool {
642 self.exclude_from_sync
643 }
644
645 fn mouse_event(&self, event: &MouseEvent, _client_id: ClientId) -> Option<String> {
646 self.grid.mouse_event_signal(event)
647 }
648
649 fn mouse_left_click(&self, position: &Position, is_held: bool) -> Option<String> {
650 self.grid.mouse_left_click_signal(position, is_held)
651 }
652 fn mouse_left_click_release(&self, position: &Position) -> Option<String> {
653 self.grid.mouse_left_click_release_signal(position)
654 }
655 fn mouse_right_click(&self, position: &Position, is_held: bool) -> Option<String> {
656 self.grid.mouse_right_click_signal(position, is_held)
657 }
658 fn mouse_right_click_release(&self, position: &Position) -> Option<String> {
659 self.grid.mouse_right_click_release_signal(position)
660 }
661 fn mouse_middle_click(&self, position: &Position, is_held: bool) -> Option<String> {
662 self.grid.mouse_middle_click_signal(position, is_held)
663 }
664 fn mouse_middle_click_release(&self, position: &Position) -> Option<String> {
665 self.grid.mouse_middle_click_release_signal(position)
666 }
667 fn mouse_scroll_up(&self, position: &Position) -> Option<String> {
668 self.grid.mouse_scroll_up_signal(position)
669 }
670 fn mouse_scroll_down(&self, position: &Position) -> Option<String> {
671 self.grid.mouse_scroll_down_signal(position)
672 }
673 fn focus_event(&self) -> Option<String> {
674 self.grid.focus_event()
675 }
676 fn unfocus_event(&self) -> Option<String> {
677 self.grid.unfocus_event()
678 }
679 fn get_line_number(&self) -> Option<usize> {
680 Some(self.grid.absolute_position_in_scrollback() + 1)
682 }
683
684 fn update_search_term(&mut self, needle: &str) {
685 match needle {
686 TERMINATING_STRING => {
687 self.search_term = String::new();
688 },
689 DELETE_KEY | BACKSPACE_KEY => {
690 self.search_term.pop();
691 },
692 c => {
693 self.search_term.push_str(c);
694 },
695 }
696 self.grid.clear_search();
697 if !self.search_term.is_empty() {
698 self.grid.set_search_string(&self.search_term);
699 }
700 self.set_should_render(true);
701 }
702 fn search_down(&mut self) {
703 if self.search_term.is_empty() {
704 return; }
706 self.grid.search_down();
707 self.set_should_render(true);
708 }
709 fn search_up(&mut self) {
710 if self.search_term.is_empty() {
711 return; }
713 self.grid.search_up();
714 self.set_should_render(true);
715 }
716 fn toggle_search_case_sensitivity(&mut self) {
717 self.grid.toggle_search_case_sensitivity();
718 self.set_should_render(true);
719 }
720 fn toggle_search_whole_words(&mut self) {
721 self.grid.toggle_search_whole_words();
722 self.set_should_render(true);
723 }
724 fn toggle_search_wrap(&mut self) {
725 self.grid.toggle_search_wrap();
726 }
727 fn clear_search(&mut self) {
728 self.grid.clear_search();
729 self.search_term.clear();
730 }
731 fn is_alternate_mode_active(&self) -> bool {
732 self.grid.is_alternate_mode_active()
733 }
734 fn hold(&mut self, exit_status: Option<i32>, is_first_run: bool, run_command: RunCommand) {
735 self.invoked_with = Some(Run::Command(run_command.clone()));
736 self.is_held = Some((exit_status, is_first_run, run_command));
737 if is_first_run {
738 self.render_first_run_banner();
739 }
740 self.set_should_render(true);
741 }
742 fn add_red_pane_frame_color_override(&mut self, error_text: Option<String>) {
743 self.pane_frame_color_override = Some((self.style.colors.exit_code_error.base, error_text));
744 }
745 fn add_highlight_pane_frame_color_override(
746 &mut self,
747 text: Option<String>,
748 _client_id: Option<ClientId>,
749 ) {
750 self.pane_frame_color_override = Some((self.style.colors.frame_highlight.base, text));
752 }
753 fn clear_pane_frame_color_override(&mut self, _client_id: Option<ClientId>) {
754 self.pane_frame_color_override = None;
756 }
757 fn frame_color_override(&self) -> Option<PaletteColor> {
758 self.pane_frame_color_override
759 .as_ref()
760 .map(|(color, _text)| *color)
761 }
762 fn invoked_with(&self) -> &Option<Run> {
763 &self.invoked_with
764 }
765 fn set_title(&mut self, title: String) {
766 self.pane_title = title;
767 }
768 fn current_title(&self) -> String {
769 if self.pane_name.is_empty() {
770 self.grid
771 .title
772 .as_deref()
773 .unwrap_or(&self.pane_title)
774 .into()
775 } else {
776 self.pane_name.to_owned()
777 }
778 }
779 fn custom_title(&self) -> Option<String> {
780 if self.pane_name.is_empty() {
781 None
782 } else {
783 Some(self.pane_name.clone())
784 }
785 }
786 fn exit_status(&self) -> Option<i32> {
787 self.is_held
788 .as_ref()
789 .and_then(|(exit_status, _, _)| *exit_status)
790 }
791 fn is_held(&self) -> bool {
792 self.is_held.is_some()
793 }
794 fn exited(&self) -> bool {
795 match self.is_held {
796 Some((_, is_first_run, _)) => !is_first_run,
797 None => false,
798 }
799 }
800 fn rename(&mut self, buf: Vec<u8>) {
801 self.pane_name = String::from_utf8_lossy(&buf).to_string();
802 self.set_should_render(true);
803 }
804 fn serialize(&self, scrollback_lines_to_serialize: Option<usize>) -> Option<String> {
805 self.grid.serialize(scrollback_lines_to_serialize)
806 }
807 fn rerun(&mut self) -> Option<RunCommand> {
808 self.is_held.take().map(|(_, _, run_command)| {
812 self.is_held = None;
813 self.grid.reset_terminal_state();
814 self.set_should_render(true);
815 self.remove_banner();
816 run_command.clone()
817 })
818 }
819 fn update_theme(&mut self, theme: Styling) {
820 self.style.colors = theme.clone();
821 self.grid.update_theme(theme);
822 if self.banner.is_some() {
823 self.render_first_run_banner();
825 }
826 }
827 fn update_arrow_fonts(&mut self, should_support_arrow_fonts: bool) {
828 self.arrow_fonts = should_support_arrow_fonts;
829 self.grid.update_arrow_fonts(should_support_arrow_fonts);
830 }
831 fn update_rounded_corners(&mut self, rounded_corners: bool) {
832 self.style.rounded_corners = rounded_corners;
833 self.frame.clear();
834 }
835 fn drain_fake_cursors(&mut self) -> Option<HashSet<(usize, usize)>> {
836 if !self.fake_cursor_locations.is_empty() {
837 for (y, _x) in &self.fake_cursor_locations {
838 self.grid.update_line_for_rendering(*y);
843 }
844 Some(self.fake_cursor_locations.drain().collect())
845 } else {
846 None
847 }
848 }
849 fn toggle_pinned(&mut self) {
850 self.geom.is_pinned = !self.geom.is_pinned;
851 }
852 fn set_pinned(&mut self, should_be_pinned: bool) {
853 self.geom.is_pinned = should_be_pinned;
854 }
855 fn intercept_left_mouse_click(&mut self, position: &Position, client_id: ClientId) -> bool {
856 if self.position_is_on_frame(position) {
857 let relative_position = self.relative_position(position);
858 if let Some(client_frame) = self.frame.get_mut(&client_id) {
859 if client_frame.clicked_on_pinned(relative_position) {
860 self.toggle_pinned();
861 return true;
862 }
863 }
864 }
865 false
866 }
867 fn intercept_mouse_event_on_frame(&mut self, event: &MouseEvent, client_id: ClientId) -> bool {
868 if self.position_is_on_frame(&event.position) {
869 let relative_position = self.relative_position(&event.position);
870 if let MouseEventType::Press = event.event_type {
871 if let Some(client_frame) = self.frame.get_mut(&client_id) {
872 if client_frame.clicked_on_pinned(relative_position) {
873 self.toggle_pinned();
874 return true;
875 }
876 }
877 }
878 }
879 false
880 }
881 fn reset_logical_position(&mut self) {
882 self.geom.logical_position = None;
883 }
884}
885
886impl TerminalPane {
887 #[allow(clippy::too_many_arguments)]
888 pub fn new(
889 pid: u32,
890 position_and_size: PaneGeom,
891 style: Style,
892 pane_index: usize,
893 pane_name: String,
894 link_handler: Rc<RefCell<LinkHandler>>,
895 character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
896 sixel_image_store: Rc<RefCell<SixelImageStore>>,
897 terminal_emulator_colors: Rc<RefCell<Palette>>,
898 terminal_emulator_color_codes: Rc<RefCell<HashMap<usize, String>>>,
899 initial_pane_title: Option<String>,
900 invoked_with: Option<Run>,
901 debug: bool,
902 arrow_fonts: bool,
903 styled_underlines: bool,
904 explicitly_disable_keyboard_protocol: bool,
905 ) -> TerminalPane {
906 let initial_pane_title =
907 initial_pane_title.unwrap_or_else(|| format!("Pane #{}", pane_index));
908 let grid = Grid::new(
909 position_and_size.rows.as_usize(),
910 position_and_size.cols.as_usize(),
911 terminal_emulator_colors,
912 terminal_emulator_color_codes,
913 link_handler,
914 character_cell_size,
915 sixel_image_store,
916 style.clone(),
917 debug,
918 arrow_fonts,
919 styled_underlines,
920 explicitly_disable_keyboard_protocol,
921 );
922 TerminalPane {
923 frame: HashMap::new(),
924 content_offset: Offset::default(),
925 pid,
926 grid,
927 selectable: true,
928 geom: position_and_size,
929 geom_override: None,
930 vte_parser: vte::Parser::new(),
931 active_at: Instant::now(),
932 style,
933 selection_scrolled_at: time::Instant::now(),
934 pane_title: initial_pane_title,
935 pane_name: pane_name.clone(),
936 prev_pane_name: pane_name,
937 borderless: false,
938 exclude_from_sync: false,
939 fake_cursor_locations: HashSet::new(),
940 search_term: String::new(),
941 is_held: None,
942 banner: None,
943 pane_frame_color_override: None,
944 invoked_with,
945 arrow_fonts,
946 }
947 }
948 pub fn get_x(&self) -> usize {
949 match self.geom_override {
950 Some(position_and_size_override) => position_and_size_override.x,
951 None => self.geom.x,
952 }
953 }
954 pub fn get_y(&self) -> usize {
955 match self.geom_override {
956 Some(position_and_size_override) => position_and_size_override.y,
957 None => self.geom.y,
958 }
959 }
960 pub fn get_columns(&self) -> usize {
961 match self.geom_override {
962 Some(position_and_size_override) => position_and_size_override.cols.as_usize(),
963 None => self.geom.cols.as_usize(),
964 }
965 }
966 pub fn get_rows(&self) -> usize {
967 match self.geom_override {
968 Some(position_and_size_override) => position_and_size_override.rows.as_usize(),
969 None => self.geom.rows.as_usize(),
970 }
971 }
972 fn reflow_lines(&mut self) {
973 let rows = self.get_content_rows();
974 let cols = self.get_content_columns();
975 self.grid.force_change_size(rows, cols);
976 if self.banner.is_some() {
977 self.grid.reset_terminal_state();
978 self.render_first_run_banner();
979 }
980 self.set_should_render(true);
981 }
982 pub fn read_buffer_as_lines(&self) -> Vec<Vec<TerminalCharacter>> {
983 self.grid.as_character_lines()
984 }
985 pub fn cursor_coordinates(&self) -> Option<(usize, usize)> {
986 if self.get_content_rows() < 1 || self.get_content_columns() < 1 {
988 return None;
990 }
991 self.grid.cursor_coordinates()
992 }
993 fn render_first_run_banner(&mut self) {
994 let columns = self.get_content_columns();
995 let rows = self.get_content_rows();
996 let banner = match &self.is_held {
997 Some((_exit_status, _is_first_run, run_command)) => {
998 render_first_run_banner(columns, rows, &self.style, Some(run_command))
999 },
1000 None => render_first_run_banner(columns, rows, &self.style, None),
1001 };
1002 self.banner = Some(banner.clone());
1003 self.handle_pty_bytes(banner.as_bytes().to_vec());
1004 }
1005 fn remove_banner(&mut self) {
1006 if self.banner.is_some() {
1007 self.grid.reset_terminal_state();
1008 self.set_should_render(true);
1009 self.banner = None;
1010 }
1011 }
1012 fn adjust_input_to_terminal_with_kitty_keyboard_protocol(
1013 &self,
1014 key: &Option<KeyWithModifier>,
1015 raw_input_bytes: Vec<u8>,
1016 raw_input_bytes_are_kitty: bool,
1017 ) -> Option<AdjustedInput> {
1018 if raw_input_bytes_are_kitty || key.is_none() {
1019 Some(AdjustedInput::WriteBytesToTerminal(raw_input_bytes))
1020 } else {
1021 key.as_ref()
1025 .and_then(|k| k.serialize_kitty())
1026 .map(|s| AdjustedInput::WriteBytesToTerminal(s.as_bytes().to_vec()))
1027 }
1028 }
1029 fn adjust_input_to_terminal_without_kitty_keyboard_protocol(
1030 &self,
1031 key: &Option<KeyWithModifier>,
1032 raw_input_bytes: Vec<u8>,
1033 raw_input_bytes_are_kitty: bool,
1034 ) -> Option<AdjustedInput> {
1035 if self.grid.new_line_mode {
1036 let key_is_enter = raw_input_bytes.as_slice() == &[13]
1037 || key
1038 .as_ref()
1039 .map(|k| k.is_key_without_modifier(BareKey::Enter))
1040 .unwrap_or(false);
1041 if key_is_enter {
1042 return Some(AdjustedInput::WriteBytesToTerminal(
1044 "\u{0d}\u{0a}".as_bytes().to_vec(),
1045 ));
1046 };
1047 }
1048 if self.grid.cursor_key_mode {
1049 let key_is_left_arrow = raw_input_bytes.as_slice() == LEFT_ARROW
1050 || key
1051 .as_ref()
1052 .map(|k| k.is_key_without_modifier(BareKey::Left))
1053 .unwrap_or(false);
1054 let key_is_right_arrow = raw_input_bytes.as_slice() == RIGHT_ARROW
1055 || key
1056 .as_ref()
1057 .map(|k| k.is_key_without_modifier(BareKey::Right))
1058 .unwrap_or(false);
1059 let key_is_up_arrow = raw_input_bytes.as_slice() == UP_ARROW
1060 || key
1061 .as_ref()
1062 .map(|k| k.is_key_without_modifier(BareKey::Up))
1063 .unwrap_or(false);
1064 let key_is_down_arrow = raw_input_bytes.as_slice() == DOWN_ARROW
1065 || key
1066 .as_ref()
1067 .map(|k| k.is_key_without_modifier(BareKey::Down))
1068 .unwrap_or(false);
1069 let key_is_home_key = raw_input_bytes.as_slice() == HOME_KEY
1070 || key
1071 .as_ref()
1072 .map(|k| k.is_key_without_modifier(BareKey::Home))
1073 .unwrap_or(false);
1074 let key_is_end_key = raw_input_bytes.as_slice() == END_KEY
1075 || key
1076 .as_ref()
1077 .map(|k| k.is_key_without_modifier(BareKey::End))
1078 .unwrap_or(false);
1079 if key_is_left_arrow {
1080 return Some(AdjustedInput::WriteBytesToTerminal(
1081 AnsiEncoding::Left.as_vec_bytes(),
1082 ));
1083 } else if key_is_right_arrow {
1084 return Some(AdjustedInput::WriteBytesToTerminal(
1085 AnsiEncoding::Right.as_vec_bytes(),
1086 ));
1087 } else if key_is_up_arrow {
1088 return Some(AdjustedInput::WriteBytesToTerminal(
1089 AnsiEncoding::Up.as_vec_bytes(),
1090 ));
1091 } else if key_is_down_arrow {
1092 return Some(AdjustedInput::WriteBytesToTerminal(
1093 AnsiEncoding::Down.as_vec_bytes(),
1094 ));
1095 } else if key_is_home_key {
1096 return Some(AdjustedInput::WriteBytesToTerminal(
1097 AnsiEncoding::Home.as_vec_bytes(),
1098 ));
1099 } else if key_is_end_key {
1100 return Some(AdjustedInput::WriteBytesToTerminal(
1101 AnsiEncoding::End.as_vec_bytes(),
1102 ));
1103 }
1104 }
1105 if raw_input_bytes_are_kitty {
1106 key.as_ref()
1111 .and_then(|k| k.serialize_non_kitty())
1112 .map(|s| AdjustedInput::WriteBytesToTerminal(s.as_bytes().to_vec()))
1113 } else {
1114 Some(AdjustedInput::WriteBytesToTerminal(raw_input_bytes))
1115 }
1116 }
1117 fn handle_held_run(&mut self) -> Option<AdjustedInput> {
1118 self.is_held.take().map(|(_, _, run_command)| {
1119 self.is_held = None;
1120 self.grid.reset_terminal_state();
1121 self.set_should_render(true);
1122 self.remove_banner();
1123 AdjustedInput::ReRunCommandInThisPane(run_command.clone())
1124 })
1125 }
1126 fn handle_held_drop_to_shell(&mut self) -> Option<AdjustedInput> {
1127 self.is_held.take().map(|(_, _, run_command)| {
1128 let working_dir = run_command.cwd.clone();
1130 self.is_held = None;
1131 self.grid.reset_terminal_state();
1132 self.set_should_render(true);
1133 self.remove_banner();
1134 AdjustedInput::DropToShellInThisPane { working_dir }
1135 })
1136 }
1137}
1138
1139#[cfg(test)]
1140#[path = "./unit/terminal_pane_tests.rs"]
1141mod grid_tests;
1142
1143#[cfg(test)]
1144#[path = "./unit/search_in_pane_tests.rs"]
1145mod search_tests;