feat(plugins): UI components (#2898)

* parsing rendering private osc table

* move components to DCS and add tests

* refactor: move components to their own thing

* ribbon and selected-ribbon ui components

* nested list ui component

* selected and indices for nested list

* coordinates and size for ui components

* use Text with ribbon

* add tests for components

* refactor: ui components

* refactor: ui components api

* style(fmt): rustfmt

* style(fmt): cleanups
This commit is contained in:
Aram Drevekenin 2023-11-02 08:17:23 +01:00 committed by GitHub
parent 35d93189e3
commit 7e5f22f8c9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 2117 additions and 4 deletions

View file

@ -235,6 +235,7 @@ fn read_from_channel(
}))); })));
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
let debug = false; let debug = false;
let arrow_fonts = true;
let mut terminal_output = TerminalPane::new( let mut terminal_output = TerminalPane::new(
0, 0,
pane_geom, pane_geom,
@ -249,6 +250,7 @@ fn read_from_channel(
None, None,
None, None,
debug, debug,
arrow_fonts,
); // 0 is the pane index ); // 0 is the pane index
loop { loop {
if !should_keep_running.load(Ordering::SeqCst) { if !should_keep_running.load(Ordering::SeqCst) {

View file

@ -0,0 +1 @@
Pznested_list;105,116,101,109,32,49;105,116,101,109,32,50;|105,116,101,109,32,51;105,116,101,109,32,52;|105,116,101,109,32,53,32,108,115,100,107,102,106,108,115,107,100,106,32,102,108,107,115,100,106,102,32,108,107,115,100,106,32,102,108,107,115,106,100,32,102;||105,116,101,109,32,54,32,115,108,100,107,102,106,115,108,107,100,102,106,32,108,115,100,107,106,102,32,108,115,107,100,106,102,32,108,115,107,100,106,102,32,108,107,115,100,106,102,32,108,115,107,100,106,102,32;105,116,101,109,32,55\

View file

@ -0,0 +1 @@
Pznested_list;1/1/20/3;$0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,8$$10,11,12,13,14,15,16,17,18,19,20,21,22,23,24$105,116,101,109,32,49,32,119,105,116,104,32,115,111,109,101,32,110,105,99,101,32,116,101,120,116,46,46,46;|$8$0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32$1,2,3,4,5,6,7,8,9,10,11,12,13,14$105,116,101,109,32,50,32,119,105,116,104,32,115,111,109,101,32,110,111,116,32,115,111,32,110,105,99,101,32,116,101,120,116;x$0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,8$$5,6,7,8,9,10,11,12,13,14,15,16,17,18,19$105,116,101,109,32,51,32,105,115,32,97,32,114,101,97,108,32,101,121,101,32,111,112,101,110,101,114;|$8$$0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,1,2,3,4,5,6,7,8,9,10,11,12,13,14$105,116,101,109,32,52,32,105,115,32,106,117,115,116,32,97,110,111,116,104,101,114,32,105,116,101,109,44,32,114,101,97,108,108,121\

View file

@ -0,0 +1 @@
Pzribbon;27,91,49,109,60,27,91,51,56,59,53,59,57,109,110,27,91,51,57,59,51,56,59,53,59,48,109,62,32,82,69,83,73,90,69\

View file

@ -0,0 +1 @@
Pzribbon;2/2/10/;1,2,3,4$114,105,98,98,111,110,32,49\

1
src/tests/fixtures/table-ui-component vendored Normal file
View file

@ -0,0 +1 @@
Pztable;5;7;116,105,116,108,101,49;116,105,116,108,101,50;116,105,116,108,101,51;116,105,116,108,101,52;116,105,116,108,101,53;49,108,107,115,100,106,102,108,107,115,100,106,102;108,107,106,100,115,102,108,107,115,106,100,102;108,107,106,115,100,108,107,102,106,115,102,100;108,107,106,115,100,108,107,106,102,100;108,107,106,100,115,108,107,106,115,100,102;50,108,107,115,100,106,102,108,107,115,100,106,102;108,107,106,100,115,102,108,107,115,106,100,102;108,107,106,240,159,152,179,240,159,152,179,115,100,108,107,102,106,115,102,100;108,107,106,115,100,108,107,106,102,100;108,107,106,100,115,108,107,106,115,100,102;51,108,107,115,100,106,102,108,107,115,100,106,102;108,107,106,100,115,102,108,107,115,106,100,102;108,107,106,115,100,240,159,152,179,108,107,102,106,115,102,100;108,107,106,115,100,108,107,106,102,100;108,107,106,100,115,108,107,106,115,100,102;52,108,107,115,100,106,102,108,107,115,50,50,50,100,106,102;108,107,240,159,152,179,50,50,50,106,100,115,102,108,107,115,106,100,102;108,107,50,50,50,106,115,100,108,107,102,106,115,102,100;108,107,119,119,119,106,115,100,108,107,106,102,100;108,107,106,100,115,108,107,106,115,100,102;53,108,107,115,100,106,102,108,107,115,100,106,102;108,107,106,100,115,102,108,107,115,106,100,102;108,107,106,115,100,108,107,102,106,115,102,100;108,107,115,100,106,102,108,107,115,106,102,100,108,107,106,115,100,108,107,106,102,100;108,107,106,100,114,114,114,114,115,108,107,106,115,100,102;27,91,51,54,109,54,108,107,115,100,106,102,108,107,240,159,152,179,115,100,106,102;27,91,52,54,109,108,240,159,152,179,107,106,100,115,102,108,107,115,106,100,102;108,107,106,115,100,108,107,102,106,115,102,100;108,107,106,115,100,108,107,106,102,100;108,119,119,107,119,107,106,100,115,108,107,106,115,100,102\

View file

@ -0,0 +1 @@
Pztable;4/4/20/3;3;5;116,105,116,108,101,49;116,105,116,108,101,50;116,105,116,108,101,51;1,2,3,4$99,111,110,116,101,110,116,32,49;$$0,1,2,3,4,5,6,7,8$99,111,110,116,101,110,116,32,50;99,111,110,116,101,110,116,32,51;x99,111,110,116,101,110,116,32,49,49;x99,111,110,116,101,110,116,32,50,50;x99,111,110,116,101,110,116,32,51,51;99,111,110,116,101,110,116,32,49,49,49;99,111,110,116,101,110,116,32,50,50,50;99,111,110,116,101,110,116,32,51,51;99,111,110,116,101,110,116,32,49,49;x99,111,110,116,101,110,116,32,50,50;99,111,110,116,101,110,116,32,51,51\

1
src/tests/fixtures/text-ui-component vendored Normal file
View file

@ -0,0 +1 @@
Pztext;x0,1,2$3,4,5$7,8,9$102,111,111,32,98,97,114,32,98,97,122\

View file

@ -0,0 +1 @@
Pztext;58/4//;x0,1,2$3,4,5$7,8,9$102,111,111,32,98,97,114,32,98,97,122\

View file

@ -38,6 +38,7 @@ use crate::panes::terminal_character::{
AnsiCode, CharacterStyles, CharsetIndex, Cursor, CursorShape, StandardCharset, AnsiCode, CharacterStyles, CharsetIndex, Cursor, CursorShape, StandardCharset,
TerminalCharacter, EMPTY_TERMINAL_CHARACTER, TerminalCharacter, EMPTY_TERMINAL_CHARACTER,
}; };
use crate::ui::components::UiComponentParser;
fn get_top_non_canonical_rows(rows: &mut Vec<Row>) -> Vec<Row> { fn get_top_non_canonical_rows(rows: &mut Vec<Row>) -> Vec<Row> {
let mut index_of_last_non_canonical_row = None; let mut index_of_last_non_canonical_row = None;
@ -369,7 +370,10 @@ pub struct Grid {
pub focus_event_tracking: bool, pub focus_event_tracking: bool,
pub search_results: SearchResult, pub search_results: SearchResult,
pub pending_clipboard_update: Option<String>, pub pending_clipboard_update: Option<String>,
ui_component_bytes: Option<Vec<u8>>,
style: Style,
debug: bool, debug: bool,
arrow_fonts: bool,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -457,7 +461,9 @@ impl Grid {
link_handler: Rc<RefCell<LinkHandler>>, link_handler: Rc<RefCell<LinkHandler>>,
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>, character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
sixel_image_store: Rc<RefCell<SixelImageStore>>, sixel_image_store: Rc<RefCell<SixelImageStore>>,
style: Style, // TODO: consolidate this with terminal_emulator_colors
debug: bool, debug: bool,
arrow_fonts: bool,
) -> Self { ) -> Self {
let sixel_grid = SixelGrid::new(character_cell_size.clone(), sixel_image_store); let sixel_grid = SixelGrid::new(character_cell_size.clone(), sixel_image_store);
// make sure this is initialized as it is used internally // make sure this is initialized as it is used internally
@ -507,7 +513,10 @@ impl Grid {
search_results: Default::default(), search_results: Default::default(),
sixel_grid, sixel_grid,
pending_clipboard_update: None, pending_clipboard_update: None,
ui_component_bytes: None,
style,
debug, debug,
arrow_fonts,
} }
} }
pub fn render_full_viewport(&mut self) { pub fn render_full_viewport(&mut self) {
@ -2196,6 +2205,9 @@ impl Perform for Grid {
params.iter().collect(), params.iter().collect(),
); );
} }
} else if c == 'z' {
// UI-component (Zellij internal)
self.ui_component_bytes = Some(vec![]);
} }
} }
@ -2205,12 +2217,21 @@ impl Perform for Grid {
// we explicitly set this to false here because in the context of Sixel, we only render the // we explicitly set this to false here because in the context of Sixel, we only render the
// image when it's done, i.e. in the unhook method // image when it's done, i.e. in the unhook method
self.should_render = false; self.should_render = false;
} else if let Some(ui_component_bytes) = self.ui_component_bytes.as_mut() {
ui_component_bytes.push(byte);
} }
} }
fn unhook(&mut self) { fn unhook(&mut self) {
if self.sixel_grid.is_parsing() { if self.sixel_grid.is_parsing() {
self.create_sixel_image(); self.create_sixel_image();
} else if let Some(mut ui_component_bytes) = self.ui_component_bytes.take() {
let component_bytes = ui_component_bytes.drain(..);
let style = self.style.clone();
let arrow_fonts = self.arrow_fonts;
UiComponentParser::new(self, style, arrow_fonts)
.parse(component_bytes.collect())
.non_fatal();
} }
self.mark_for_rerender(); self.mark_for_rerender();
} }

View file

@ -49,7 +49,9 @@ macro_rules! get_or_create_grid {
$self.link_handler.clone(), $self.link_handler.clone(),
$self.character_cell_size.clone(), $self.character_cell_size.clone(),
$self.sixel_image_store.clone(), $self.sixel_image_store.clone(),
$self.style.clone(),
$self.debug, $self.debug,
$self.arrow_fonts,
); );
grid.hide_cursor(); grid.hide_cursor();
grid grid
@ -85,6 +87,7 @@ pub(crate) struct PluginPane {
loading_indication: LoadingIndication, loading_indication: LoadingIndication,
requesting_permissions: Option<PluginPermission>, requesting_permissions: Option<PluginPermission>,
debug: bool, debug: bool,
arrow_fonts: bool,
} }
impl PluginPane { impl PluginPane {
@ -103,6 +106,7 @@ impl PluginPane {
style: Style, style: Style,
invoked_with: Option<Run>, invoked_with: Option<Run>,
debug: bool, debug: bool,
arrow_fonts: bool,
) -> Self { ) -> Self {
let loading_indication = LoadingIndication::new(title.clone()).with_colors(style.colors); let loading_indication = LoadingIndication::new(title.clone()).with_colors(style.colors);
let initial_loading_message = loading_indication.to_string(); let initial_loading_message = loading_indication.to_string();
@ -134,6 +138,7 @@ impl PluginPane {
loading_indication, loading_indication,
requesting_permissions: None, requesting_permissions: None,
debug, debug,
arrow_fonts,
}; };
for client_id in currently_connected_clients { for client_id in currently_connected_clients {
plugin.handle_plugin_bytes(client_id, initial_loading_message.as_bytes().to_vec()); plugin.handle_plugin_bytes(client_id, initial_loading_message.as_bytes().to_vec());

View file

@ -115,6 +115,7 @@ pub struct TerminalPane {
// held on startup and can possibly be used to display some errors // held on startup and can possibly be used to display some errors
pane_frame_color_override: Option<(PaletteColor, Option<String>)>, pane_frame_color_override: Option<(PaletteColor, Option<String>)>,
invoked_with: Option<Run>, invoked_with: Option<Run>,
arrow_fonts: bool,
} }
impl Pane for TerminalPane { impl Pane for TerminalPane {
@ -786,6 +787,7 @@ impl TerminalPane {
initial_pane_title: Option<String>, initial_pane_title: Option<String>,
invoked_with: Option<Run>, invoked_with: Option<Run>,
debug: bool, debug: bool,
arrow_fonts: bool,
) -> TerminalPane { ) -> TerminalPane {
let initial_pane_title = let initial_pane_title =
initial_pane_title.unwrap_or_else(|| format!("Pane #{}", pane_index)); initial_pane_title.unwrap_or_else(|| format!("Pane #{}", pane_index));
@ -797,7 +799,9 @@ impl TerminalPane {
link_handler, link_handler,
character_cell_size, character_cell_size,
sixel_image_store, sixel_image_store,
style.clone(),
debug, debug,
arrow_fonts,
); );
TerminalPane { TerminalPane {
frame: HashMap::new(), frame: HashMap::new(),
@ -822,6 +826,7 @@ impl TerminalPane {
banner: None, banner: None,
pane_frame_color_override: None, pane_frame_color_override: None,
invoked_with, invoked_with,
arrow_fonts,
} }
} }
pub fn get_x(&self) -> usize { pub fn get_x(&self) -> usize {

File diff suppressed because it is too large Load diff

View file

@ -29,6 +29,7 @@ fn create_pane() -> TerminalPane {
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
let debug = false; let debug = false;
let arrow_fonts = true;
let mut terminal_pane = TerminalPane::new( let mut terminal_pane = TerminalPane::new(
pid, pid,
fake_win_size, fake_win_size,
@ -43,6 +44,7 @@ fn create_pane() -> TerminalPane {
None, None,
None, None,
debug, debug,
arrow_fonts,
); // 0 is the pane index ); // 0 is the pane index
let content = read_fixture(); let content = read_fixture();
terminal_pane.handle_pty_bytes(content); terminal_pane.handle_pty_bytes(content);

View file

@ -0,0 +1,47 @@
---
source: zellij-server/src/panes/./unit/grid_tests.rs
assertion_line: 3210
expression: "format!(\"{:?}\", grid)"
---
00 (C): > item 1
01 (C): > item 2
02 (C): - item 3
03 (C): > item 4
04 (C): - item 5 lsdkfjlskdj flksdjf lksdj flksjd f
05 (C): > item 6 sldkfjslkdfj lsdkjf lskdjf lskdjf lksdjf lskdjf
06 (C): > item 7
07 (C):
08 (C):
09 (C):
10 (C):
11 (C):
12 (C):
13 (C):
14 (C):
15 (C):
16 (C):
17 (C):
18 (C):
19 (C):
20 (C):
21 (C):
22 (C):
23 (C):
24 (C):
25 (C):
26 (C):
27 (C):
28 (C):
29 (C):
30 (C):
31 (C):
32 (C):
33 (C):
34 (C):
35 (C):
36 (C):
37 (C):
38 (C):
39 (C):
40 (C):

View file

@ -0,0 +1,47 @@
---
source: zellij-server/src/panes/./unit/grid_tests.rs
assertion_line: 3237
expression: "format!(\"{:?}\", grid)"
---
00 (C):
01 (C): > item 1 with some
02 (C): - item 2 with som
03 (C): > item 3 is a real
04 (C):
05 (C):
06 (C):
07 (C):
08 (C):
09 (C):
10 (C):
11 (C):
12 (C):
13 (C):
14 (C):
15 (C):
16 (C):
17 (C):
18 (C):
19 (C):
20 (C):
21 (C):
22 (C):
23 (C):
24 (C):
25 (C):
26 (C):
27 (C):
28 (C):
29 (C):
30 (C):
31 (C):
32 (C):
33 (C):
34 (C):
35 (C):
36 (C):
37 (C):
38 (C):
39 (C):
40 (C):

View file

@ -0,0 +1,47 @@
---
source: zellij-server/src/panes/./unit/grid_tests.rs
assertion_line: 2949
expression: "format!(\"{:?}\", grid)"
---
00 (C):  <n> RESIZE 
01 (C):
02 (C):
03 (C):
04 (C):
05 (C):
06 (C):
07 (C):
08 (C):
09 (C):
10 (C):
11 (C):
12 (C):
13 (C):
14 (C):
15 (C):
16 (C):
17 (C):
18 (C):
19 (C):
20 (C):
21 (C):
22 (C):
23 (C):
24 (C):
25 (C):
26 (C):
27 (C):
28 (C):
29 (C):
30 (C):
31 (C):
32 (C):
33 (C):
34 (C):
35 (C):
36 (C):
37 (C):
38 (C):
39 (C):
40 (C):

View file

@ -0,0 +1,47 @@
---
source: zellij-server/src/panes/./unit/grid_tests.rs
assertion_line: 2921
expression: "format!(\"{:?}\", grid)"
---
00 (C):  <n> RESIZE 
01 (C):
02 (C):
03 (C):
04 (C):
05 (C):
06 (C):
07 (C):
08 (C):
09 (C):
10 (C):
11 (C):
12 (C):
13 (C):
14 (C):
15 (C):
16 (C):
17 (C):
18 (C):
19 (C):
20 (C):
21 (C):
22 (C):
23 (C):
24 (C):
25 (C):
26 (C):
27 (C):
28 (C):
29 (C):
30 (C):
31 (C):
32 (C):
33 (C):
34 (C):
35 (C):
36 (C):
37 (C):
38 (C):
39 (C):
40 (C):

View file

@ -0,0 +1,47 @@
---
source: zellij-server/src/panes/./unit/grid_tests.rs
assertion_line: 3183
expression: "format!(\"{:?}\", grid)"
---
00 (C):
01 (C):
02 (C):  ribbon 1 
03 (C):
04 (C):
05 (C):
06 (C):
07 (C):
08 (C):
09 (C):
10 (C):
11 (C):
12 (C):
13 (C):
14 (C):
15 (C):
16 (C):
17 (C):
18 (C):
19 (C):
20 (C):
21 (C):
22 (C):
23 (C):
24 (C):
25 (C):
26 (C):
27 (C):
28 (C):
29 (C):
30 (C):
31 (C):
32 (C):
33 (C):
34 (C):
35 (C):
36 (C):
37 (C):
38 (C):
39 (C):
40 (C):

View file

@ -0,0 +1,47 @@
---
source: zellij-server/src/panes/./unit/grid_tests.rs
assertion_line: 3102
expression: "format!(\"{:?}\", grid)"
---
00 (C): title1 title2 title3 title4 title5
01 (C): 1lksdjflksdjf lkjdsflksjdf lkjsdlkfjsfd lkjsdlkjfd lkjdslkjsdf
02 (C): 2lksdjflksdjf lkjdsflksjdf lkj😳😳sdlkfjsfd lkjsdlkjfd lkjdslkjsdf
03 (C): 3lksdjflksdjf lkjdsflksjdf lkjsd😳lkfjsfd lkjsdlkjfd lkjdslkjsdf
04 (C): 4lksdjflks222djf lk😳222jdsflksjdf lk222jsdlkfjsfd lkwwwjsdlkjfd lkjdslkjsdf
05 (C): 5lksdjflksdjf lkjdsflksjdf lkjsdlkfjsfd lksdjflksjfdlkjsdlkjfd lkjdrrrrslkjsdf
06 (C): 6lksdjflk😳sdjf l😳kjdsflksjdf lkjsdlkfjsfd lkjsdlkjfd lwwkwkjdslkjsdf
07 (C):
08 (C):
09 (C):
10 (C):
11 (C):
12 (C):
13 (C):
14 (C):
15 (C):
16 (C):
17 (C):
18 (C):
19 (C):
20 (C):
21 (C):
22 (C):
23 (C):
24 (C):
25 (C):
26 (C):
27 (C):
28 (C):
29 (C):
30 (C):
31 (C):
32 (C):
33 (C):
34 (C):
35 (C):
36 (C):
37 (C):
38 (C):
39 (C):
40 (C):

View file

@ -0,0 +1,47 @@
---
source: zellij-server/src/panes/./unit/grid_tests.rs
assertion_line: 3129
expression: "format!(\"{:?}\", grid)"
---
00 (C):
01 (C):
02 (C):
03 (C):
04 (C): title1
05 (C): content 1
06 (C): content 11
07 (C):
08 (C):
09 (C):
10 (C):
11 (C):
12 (C):
13 (C):
14 (C):
15 (C):
16 (C):
17 (C):
18 (C):
19 (C):
20 (C):
21 (C):
22 (C):
23 (C):
24 (C):
25 (C):
26 (C):
27 (C):
28 (C):
29 (C):
30 (C):
31 (C):
32 (C):
33 (C):
34 (C):
35 (C):
36 (C):
37 (C):
38 (C):
39 (C):
40 (C):

View file

@ -0,0 +1,47 @@
---
source: zellij-server/src/panes/./unit/grid_tests.rs
assertion_line: 3264
expression: "format!(\"{:?}\", grid)"
---
00 (C): foo bar baz
01 (C):
02 (C):
03 (C):
04 (C):
05 (C):
06 (C):
07 (C):
08 (C):
09 (C):
10 (C):
11 (C):
12 (C):
13 (C):
14 (C):
15 (C):
16 (C):
17 (C):
18 (C):
19 (C):
20 (C):
21 (C):
22 (C):
23 (C):
24 (C):
25 (C):
26 (C):
27 (C):
28 (C):
29 (C):
30 (C):
31 (C):
32 (C):
33 (C):
34 (C):
35 (C):
36 (C):
37 (C):
38 (C):
39 (C):
40 (C):

View file

@ -0,0 +1,47 @@
---
source: zellij-server/src/panes/./unit/grid_tests.rs
assertion_line: 3291
expression: "format!(\"{:?}\", grid)"
---
00 (C):
01 (C):
02 (C):
03 (C):
04 (C): foo bar baz
05 (C):
06 (C):
07 (C):
08 (C):
09 (C):
10 (C):
11 (C):
12 (C):
13 (C):
14 (C):
15 (C):
16 (C):
17 (C):
18 (C):
19 (C):
20 (C):
21 (C):
22 (C):
23 (C):
24 (C):
25 (C):
26 (C):
27 (C):
28 (C):
29 (C):
30 (C):
31 (C):
32 (C):
33 (C):
34 (C):
35 (C):
36 (C):
37 (C):
38 (C):
39 (C):
40 (C):

View file

@ -37,6 +37,7 @@ pub fn scrolling_inside_a_pane() {
let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default())); let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
let debug = false; let debug = false;
let arrow_fonts = true;
let mut terminal_pane = TerminalPane::new( let mut terminal_pane = TerminalPane::new(
pid, pid,
fake_win_size, fake_win_size,
@ -51,6 +52,7 @@ pub fn scrolling_inside_a_pane() {
None, None,
None, None,
debug, debug,
arrow_fonts,
); // 0 is the pane index ); // 0 is the pane index
let mut text_to_fill_pane = String::new(); let mut text_to_fill_pane = String::new();
for i in 0..30 { for i in 0..30 {
@ -81,6 +83,7 @@ pub fn sixel_image_inside_terminal_pane() {
height: 21, height: 21,
}))); })));
let debug = false; let debug = false;
let arrow_fonts = true;
let mut terminal_pane = TerminalPane::new( let mut terminal_pane = TerminalPane::new(
pid, pid,
fake_win_size, fake_win_size,
@ -95,6 +98,7 @@ pub fn sixel_image_inside_terminal_pane() {
None, None,
None, None,
debug, debug,
arrow_fonts,
); // 0 is the pane index ); // 0 is the pane index
let sixel_image_bytes = "\u{1b}Pq let sixel_image_bytes = "\u{1b}Pq
#0;2;0;0;0#1;2;100;100;0#2;2;0;100;0 #0;2;0;0;0#1;2;100;100;0#2;2;0;100;0
@ -125,6 +129,7 @@ pub fn partial_sixel_image_inside_terminal_pane() {
height: 21, height: 21,
}))); })));
let debug = false; let debug = false;
let arrow_fonts = true;
let mut terminal_pane = TerminalPane::new( let mut terminal_pane = TerminalPane::new(
pid, pid,
fake_win_size, fake_win_size,
@ -139,6 +144,7 @@ pub fn partial_sixel_image_inside_terminal_pane() {
None, None,
None, None,
debug, debug,
arrow_fonts,
); // 0 is the pane index ); // 0 is the pane index
let pane_content = read_fixture("sixel-image-500px.six"); let pane_content = read_fixture("sixel-image-500px.six");
terminal_pane.handle_pty_bytes(pane_content); terminal_pane.handle_pty_bytes(pane_content);
@ -163,6 +169,7 @@ pub fn overflowing_sixel_image_inside_terminal_pane() {
height: 21, height: 21,
}))); })));
let debug = false; let debug = false;
let arrow_fonts = true;
let mut terminal_pane = TerminalPane::new( let mut terminal_pane = TerminalPane::new(
pid, pid,
fake_win_size, fake_win_size,
@ -177,6 +184,7 @@ pub fn overflowing_sixel_image_inside_terminal_pane() {
None, None,
None, None,
debug, debug,
arrow_fonts,
); // 0 is the pane index ); // 0 is the pane index
let pane_content = read_fixture("sixel-image-500px.six"); let pane_content = read_fixture("sixel-image-500px.six");
terminal_pane.handle_pty_bytes(pane_content); terminal_pane.handle_pty_bytes(pane_content);
@ -200,6 +208,7 @@ pub fn scrolling_through_a_sixel_image() {
height: 21, height: 21,
}))); })));
let debug = false; let debug = false;
let arrow_fonts = true;
let mut terminal_pane = TerminalPane::new( let mut terminal_pane = TerminalPane::new(
pid, pid,
fake_win_size, fake_win_size,
@ -214,6 +223,7 @@ pub fn scrolling_through_a_sixel_image() {
None, None,
None, None,
debug, debug,
arrow_fonts,
); // 0 is the pane index ); // 0 is the pane index
let mut text_to_fill_pane = String::new(); let mut text_to_fill_pane = String::new();
for i in 0..30 { for i in 0..30 {
@ -248,6 +258,7 @@ pub fn multiple_sixel_images_in_pane() {
height: 21, height: 21,
}))); })));
let debug = false; let debug = false;
let arrow_fonts = true;
let mut terminal_pane = TerminalPane::new( let mut terminal_pane = TerminalPane::new(
pid, pid,
fake_win_size, fake_win_size,
@ -262,6 +273,7 @@ pub fn multiple_sixel_images_in_pane() {
None, None,
None, None,
debug, debug,
arrow_fonts,
); // 0 is the pane index ); // 0 is the pane index
let mut text_to_fill_pane = String::new(); let mut text_to_fill_pane = String::new();
for i in 0..5 { for i in 0..5 {
@ -294,6 +306,7 @@ pub fn resizing_pane_with_sixel_images() {
height: 21, height: 21,
}))); })));
let debug = false; let debug = false;
let arrow_fonts = true;
let mut terminal_pane = TerminalPane::new( let mut terminal_pane = TerminalPane::new(
pid, pid,
fake_win_size, fake_win_size,
@ -308,6 +321,7 @@ pub fn resizing_pane_with_sixel_images() {
None, None,
None, None,
debug, debug,
arrow_fonts,
); // 0 is the pane index ); // 0 is the pane index
let mut text_to_fill_pane = String::new(); let mut text_to_fill_pane = String::new();
for i in 0..5 { for i in 0..5 {
@ -343,6 +357,7 @@ pub fn changing_character_cell_size_with_sixel_images() {
height: 21, height: 21,
}))); })));
let debug = false; let debug = false;
let arrow_fonts = true;
let mut terminal_pane = TerminalPane::new( let mut terminal_pane = TerminalPane::new(
pid, pid,
fake_win_size, fake_win_size,
@ -357,6 +372,7 @@ pub fn changing_character_cell_size_with_sixel_images() {
None, None,
None, None,
debug, debug,
arrow_fonts,
); // 0 is the pane index ); // 0 is the pane index
let mut text_to_fill_pane = String::new(); let mut text_to_fill_pane = String::new();
for i in 0..5 { for i in 0..5 {
@ -397,6 +413,7 @@ pub fn keep_working_after_corrupted_sixel_image() {
height: 21, height: 21,
}))); })));
let debug = false; let debug = false;
let arrow_fonts = true;
let mut terminal_pane = TerminalPane::new( let mut terminal_pane = TerminalPane::new(
pid, pid,
fake_win_size, fake_win_size,
@ -411,6 +428,7 @@ pub fn keep_working_after_corrupted_sixel_image() {
None, None,
None, None,
debug, debug,
arrow_fonts,
); // 0 is the pane index ); // 0 is the pane index
let sixel_image_bytes = "\u{1b}PI AM CORRUPTED BWAHAHAq let sixel_image_bytes = "\u{1b}PI AM CORRUPTED BWAHAHAq
@ -449,6 +467,7 @@ pub fn pane_with_frame_position_is_on_frame() {
height: 21, height: 21,
}))); })));
let debug = false; let debug = false;
let arrow_fonts = true;
let mut terminal_pane = TerminalPane::new( let mut terminal_pane = TerminalPane::new(
pid, pid,
fake_win_size, fake_win_size,
@ -463,6 +482,7 @@ pub fn pane_with_frame_position_is_on_frame() {
None, None,
None, None,
debug, debug,
arrow_fonts,
); // 0 is the pane index ); // 0 is the pane index
terminal_pane.set_content_offset(Offset::frame(1)); terminal_pane.set_content_offset(Offset::frame(1));
@ -537,6 +557,7 @@ pub fn pane_with_bottom_and_right_borders_position_is_on_frame() {
height: 21, height: 21,
}))); })));
let debug = false; let debug = false;
let arrow_fonts = true;
let mut terminal_pane = TerminalPane::new( let mut terminal_pane = TerminalPane::new(
pid, pid,
fake_win_size, fake_win_size,
@ -551,6 +572,7 @@ pub fn pane_with_bottom_and_right_borders_position_is_on_frame() {
None, None,
None, None,
debug, debug,
arrow_fonts,
); // 0 is the pane index ); // 0 is the pane index
terminal_pane.set_content_offset(Offset::shift(1, 1)); terminal_pane.set_content_offset(Offset::shift(1, 1));
@ -625,6 +647,7 @@ pub fn frameless_pane_position_is_on_frame() {
height: 21, height: 21,
}))); })));
let debug = false; let debug = false;
let arrow_fonts = true;
let mut terminal_pane = TerminalPane::new( let mut terminal_pane = TerminalPane::new(
pid, pid,
fake_win_size, fake_win_size,
@ -639,6 +662,7 @@ pub fn frameless_pane_position_is_on_frame() {
None, None,
None, None,
debug, debug,
arrow_fonts,
); // 0 is the pane index ); // 0 is the pane index
terminal_pane.set_content_offset(Offset::default()); terminal_pane.set_content_offset(Offset::default());

View file

@ -560,6 +560,7 @@ pub(crate) struct Screen {
// also be this session // also be this session
default_layout: Box<Layout>, default_layout: Box<Layout>,
default_shell: Option<PathBuf>, default_shell: Option<PathBuf>,
arrow_fonts: bool,
} }
impl Screen { impl Screen {
@ -579,6 +580,7 @@ impl Screen {
session_serialization: bool, session_serialization: bool,
serialize_pane_viewport: bool, serialize_pane_viewport: bool,
scrollback_lines_to_serialize: Option<usize>, scrollback_lines_to_serialize: Option<usize>,
arrow_fonts: bool,
) -> Self { ) -> Self {
let session_name = mode_info.session_name.clone().unwrap_or_default(); let session_name = mode_info.session_name.clone().unwrap_or_default();
let session_info = SessionInfo::new(session_name.clone()); let session_info = SessionInfo::new(session_name.clone());
@ -613,6 +615,7 @@ impl Screen {
session_serialization, session_serialization,
serialize_pane_viewport, serialize_pane_viewport,
scrollback_lines_to_serialize, scrollback_lines_to_serialize,
arrow_fonts,
} }
} }
@ -1151,6 +1154,7 @@ impl Screen {
swap_layouts, swap_layouts,
self.default_shell.clone(), self.default_shell.clone(),
self.debug, self.debug,
self.arrow_fonts,
); );
self.tabs.insert(tab_index, tab); self.tabs.insert(tab_index, tab);
Ok(()) Ok(())
@ -2034,7 +2038,7 @@ pub(crate) fn screen_thread_main(
debug: bool, debug: bool,
default_layout: Box<Layout>, default_layout: Box<Layout>,
) -> Result<()> { ) -> Result<()> {
let capabilities = config_options.simplified_ui; let arrow_fonts = !config_options.simplified_ui.unwrap_or_default();
let draw_pane_frames = config_options.pane_frames.unwrap_or(true); let draw_pane_frames = config_options.pane_frames.unwrap_or(true);
let auto_layout = config_options.auto_layout.unwrap_or(true); let auto_layout = config_options.auto_layout.unwrap_or(true);
let session_serialization = config_options.session_serialization.unwrap_or(true); let session_serialization = config_options.session_serialization.unwrap_or(true);
@ -2057,7 +2061,8 @@ pub(crate) fn screen_thread_main(
config_options.default_mode.unwrap_or_default(), config_options.default_mode.unwrap_or_default(),
&client_attributes, &client_attributes,
PluginCapabilities { PluginCapabilities {
arrow_fonts: capabilities.unwrap_or_default(), // ¯\_(ツ)_/¯
arrow_fonts: !arrow_fonts,
}, },
), ),
draw_pane_frames, draw_pane_frames,
@ -2070,6 +2075,7 @@ pub(crate) fn screen_thread_main(
session_serialization, session_serialization,
serialize_pane_viewport, serialize_pane_viewport,
scrollback_lines_to_serialize, scrollback_lines_to_serialize,
arrow_fonts,
); );
let mut pending_tab_ids: HashSet<usize> = HashSet::new(); let mut pending_tab_ids: HashSet<usize> = HashSet::new();

View file

@ -41,6 +41,7 @@ pub struct LayoutApplier<'a> {
focus_pane_id: &'a mut Option<PaneId>, focus_pane_id: &'a mut Option<PaneId>,
os_api: Box<dyn ServerOsApi>, os_api: Box<dyn ServerOsApi>,
debug: bool, debug: bool,
arrow_fonts: bool,
} }
impl<'a> LayoutApplier<'a> { impl<'a> LayoutApplier<'a> {
@ -61,6 +62,7 @@ impl<'a> LayoutApplier<'a> {
focus_pane_id: &'a mut Option<PaneId>, focus_pane_id: &'a mut Option<PaneId>,
os_api: &Box<dyn ServerOsApi>, os_api: &Box<dyn ServerOsApi>,
debug: bool, debug: bool,
arrow_fonts: bool,
) -> Self { ) -> Self {
let viewport = viewport.clone(); let viewport = viewport.clone();
let senders = senders.clone(); let senders = senders.clone();
@ -90,6 +92,7 @@ impl<'a> LayoutApplier<'a> {
focus_pane_id, focus_pane_id,
os_api, os_api,
debug, debug,
arrow_fonts,
} }
} }
pub fn apply_layout( pub fn apply_layout(
@ -256,6 +259,7 @@ impl<'a> LayoutApplier<'a> {
self.style, self.style,
layout.run.clone(), layout.run.clone(),
self.debug, self.debug,
self.arrow_fonts,
); );
if let Some(pane_initial_contents) = &layout.pane_initial_contents { if let Some(pane_initial_contents) = &layout.pane_initial_contents {
new_plugin.handle_pty_bytes(pane_initial_contents.as_bytes().into()); new_plugin.handle_pty_bytes(pane_initial_contents.as_bytes().into());
@ -292,6 +296,7 @@ impl<'a> LayoutApplier<'a> {
initial_title, initial_title,
layout.run.clone(), layout.run.clone(),
self.debug, self.debug,
self.arrow_fonts,
); );
if let Some(pane_initial_contents) = &layout.pane_initial_contents { if let Some(pane_initial_contents) = &layout.pane_initial_contents {
new_pane.handle_pty_bytes(pane_initial_contents.as_bytes().into()); new_pane.handle_pty_bytes(pane_initial_contents.as_bytes().into());
@ -381,6 +386,7 @@ impl<'a> LayoutApplier<'a> {
self.style, self.style,
floating_pane_layout.run.clone(), floating_pane_layout.run.clone(),
self.debug, self.debug,
self.arrow_fonts,
); );
if let Some(pane_initial_contents) = &floating_pane_layout.pane_initial_contents { if let Some(pane_initial_contents) = &floating_pane_layout.pane_initial_contents {
new_pane.handle_pty_bytes(pane_initial_contents.as_bytes().into()); new_pane.handle_pty_bytes(pane_initial_contents.as_bytes().into());
@ -420,6 +426,7 @@ impl<'a> LayoutApplier<'a> {
initial_title, initial_title,
floating_pane_layout.run.clone(), floating_pane_layout.run.clone(),
self.debug, self.debug,
self.arrow_fonts,
); );
if let Some(pane_initial_contents) = &floating_pane_layout.pane_initial_contents { if let Some(pane_initial_contents) = &floating_pane_layout.pane_initial_contents {
new_pane.handle_pty_bytes(pane_initial_contents.as_bytes().into()); new_pane.handle_pty_bytes(pane_initial_contents.as_bytes().into());

View file

@ -185,6 +185,7 @@ pub(crate) struct Tab {
swap_layouts: SwapLayouts, swap_layouts: SwapLayouts,
default_shell: Option<PathBuf>, default_shell: Option<PathBuf>,
debug: bool, debug: bool,
arrow_fonts: bool,
} }
#[derive(Clone, Debug, Default, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Serialize, Deserialize)]
@ -533,6 +534,7 @@ impl Tab {
swap_layouts: (Vec<SwapTiledLayout>, Vec<SwapFloatingLayout>), swap_layouts: (Vec<SwapTiledLayout>, Vec<SwapFloatingLayout>),
default_shell: Option<PathBuf>, default_shell: Option<PathBuf>,
debug: bool, debug: bool,
arrow_fonts: bool,
) -> Self { ) -> Self {
let name = if name.is_empty() { let name = if name.is_empty() {
format!("Tab #{}", index + 1) format!("Tab #{}", index + 1)
@ -621,6 +623,7 @@ impl Tab {
swap_layouts, swap_layouts,
default_shell, default_shell,
debug, debug,
arrow_fonts,
} }
} }
@ -652,6 +655,7 @@ impl Tab {
&mut self.focus_pane_id, &mut self.focus_pane_id,
&self.os_api, &self.os_api,
self.debug, self.debug,
self.arrow_fonts,
) )
.apply_layout( .apply_layout(
layout, layout,
@ -713,6 +717,7 @@ impl Tab {
&mut self.focus_pane_id, &mut self.focus_pane_id,
&self.os_api, &self.os_api,
self.debug, self.debug,
self.arrow_fonts,
) )
.apply_floating_panes_layout_to_existing_panes( .apply_floating_panes_layout_to_existing_panes(
&layout_candidate, &layout_candidate,
@ -767,6 +772,7 @@ impl Tab {
&mut self.focus_pane_id, &mut self.focus_pane_id,
&self.os_api, &self.os_api,
self.debug, self.debug,
self.arrow_fonts,
) )
.apply_tiled_panes_layout_to_existing_panes( .apply_tiled_panes_layout_to_existing_panes(
&layout_candidate, &layout_candidate,
@ -1053,6 +1059,7 @@ impl Tab {
initial_pane_title, initial_pane_title,
invoked_with, invoked_with,
self.debug, self.debug,
self.arrow_fonts,
)) as Box<dyn Pane> )) as Box<dyn Pane>
}, },
PaneId::Plugin(plugin_pid) => { PaneId::Plugin(plugin_pid) => {
@ -1075,6 +1082,7 @@ impl Tab {
self.style, self.style,
invoked_with, invoked_with,
self.debug, self.debug,
self.arrow_fonts,
)) as Box<dyn Pane> )) as Box<dyn Pane>
}, },
}; };
@ -1111,6 +1119,7 @@ impl Tab {
None, None,
None, None,
self.debug, self.debug,
self.arrow_fonts,
); );
new_pane.update_name("EDITING SCROLLBACK"); // we do this here and not in the new_pane.update_name("EDITING SCROLLBACK"); // we do this here and not in the
// constructor so it won't be overrided // constructor so it won't be overrided
@ -1183,6 +1192,7 @@ impl Tab {
None, None,
run, run,
self.debug, self.debug,
self.arrow_fonts,
); );
let replaced_pane = if self.floating_panes.panes_contain(&old_pane_id) { let replaced_pane = if self.floating_panes.panes_contain(&old_pane_id) {
self.floating_panes self.floating_panes
@ -1235,6 +1245,7 @@ impl Tab {
self.style, self.style,
run, run,
self.debug, self.debug,
self.arrow_fonts,
); );
let replaced_pane = if self.floating_panes.panes_contain(&old_pane_id) { let replaced_pane = if self.floating_panes.panes_contain(&old_pane_id) {
self.floating_panes self.floating_panes
@ -1303,6 +1314,7 @@ impl Tab {
initial_pane_title, initial_pane_title,
None, None,
self.debug, self.debug,
self.arrow_fonts,
); );
self.tiled_panes self.tiled_panes
.split_pane_horizontally(pid, Box::new(new_terminal), client_id); .split_pane_horizontally(pid, Box::new(new_terminal), client_id);
@ -1360,6 +1372,7 @@ impl Tab {
initial_pane_title, initial_pane_title,
None, None,
self.debug, self.debug,
self.arrow_fonts,
); );
self.tiled_panes self.tiled_panes
.split_pane_vertically(pid, Box::new(new_terminal), client_id); .split_pane_vertically(pid, Box::new(new_terminal), client_id);

View file

@ -224,6 +224,7 @@ fn create_new_tab(size: Size, default_mode: ModeInfo) -> Tab {
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
let debug = false; let debug = false;
let arrow_fonts = true;
let mut tab = Tab::new( let mut tab = Tab::new(
index, index,
position, position,
@ -247,6 +248,7 @@ fn create_new_tab(size: Size, default_mode: ModeInfo) -> Tab {
(vec![], vec![]), (vec![], vec![]),
None, None,
debug, debug,
arrow_fonts,
); );
tab.apply_layout( tab.apply_layout(
TiledPaneLayout::default(), TiledPaneLayout::default(),
@ -297,6 +299,7 @@ fn create_new_tab_with_swap_layouts(
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
let debug = false; let debug = false;
let arrow_fonts = true;
let mut tab = Tab::new( let mut tab = Tab::new(
index, index,
position, position,
@ -320,6 +323,7 @@ fn create_new_tab_with_swap_layouts(
swap_layouts, swap_layouts,
None, None,
debug, debug,
arrow_fonts,
); );
let ( let (
base_layout, base_layout,
@ -372,6 +376,7 @@ fn create_new_tab_with_os_api(
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
let debug = false; let debug = false;
let arrow_fonts = true;
let mut tab = Tab::new( let mut tab = Tab::new(
index, index,
position, position,
@ -395,6 +400,7 @@ fn create_new_tab_with_os_api(
(vec![], vec![]), // swap layouts (vec![], vec![]), // swap layouts
None, None,
debug, debug,
arrow_fonts,
); );
tab.apply_layout( tab.apply_layout(
TiledPaneLayout::default(), TiledPaneLayout::default(),
@ -433,6 +439,7 @@ fn create_new_tab_with_layout(size: Size, default_mode: ModeInfo, layout: &str)
let layout = Layout::from_str(layout, "layout_file_name".into(), None, None).unwrap(); let layout = Layout::from_str(layout, "layout_file_name".into(), None, None).unwrap();
let (tab_layout, floating_panes_layout) = layout.new_tab(); let (tab_layout, floating_panes_layout) = layout.new_tab();
let debug = false; let debug = false;
let arrow_fonts = true;
let mut tab = Tab::new( let mut tab = Tab::new(
index, index,
position, position,
@ -456,6 +463,7 @@ fn create_new_tab_with_layout(size: Size, default_mode: ModeInfo, layout: &str)
(vec![], vec![]), // swap layouts (vec![], vec![]), // swap layouts
None, None,
debug, debug,
arrow_fonts,
); );
let pane_ids = tab_layout let pane_ids = tab_layout
.extract_run_instructions() .extract_run_instructions()
@ -508,6 +516,7 @@ fn create_new_tab_with_mock_pty_writer(
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
let debug = false; let debug = false;
let arrow_fonts = true;
let mut tab = Tab::new( let mut tab = Tab::new(
index, index,
position, position,
@ -531,6 +540,7 @@ fn create_new_tab_with_mock_pty_writer(
(vec![], vec![]), // swap layouts (vec![], vec![]), // swap layouts
None, None,
debug, debug,
arrow_fonts,
); );
tab.apply_layout( tab.apply_layout(
TiledPaneLayout::default(), TiledPaneLayout::default(),
@ -574,6 +584,7 @@ fn create_new_tab_with_sixel_support(
let copy_options = CopyOptions::default(); let copy_options = CopyOptions::default();
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
let debug = false; let debug = false;
let arrow_fonts = true;
let mut tab = Tab::new( let mut tab = Tab::new(
index, index,
position, position,
@ -597,6 +608,7 @@ fn create_new_tab_with_sixel_support(
(vec![], vec![]), // swap layouts (vec![], vec![]), // swap layouts
None, None,
debug, debug,
arrow_fonts,
); );
tab.apply_layout( tab.apply_layout(
TiledPaneLayout::default(), TiledPaneLayout::default(),
@ -633,6 +645,7 @@ fn take_snapshot(ansi_instructions: &str, rows: usize, columns: usize, palette:
height: 21, height: 21,
}))); })));
let debug = false; let debug = false;
let arrow_fonts = true;
let mut grid = Grid::new( let mut grid = Grid::new(
rows, rows,
columns, columns,
@ -641,7 +654,9 @@ fn take_snapshot(ansi_instructions: &str, rows: usize, columns: usize, palette:
Rc::new(RefCell::new(LinkHandler::new())), Rc::new(RefCell::new(LinkHandler::new())),
character_cell_size, character_cell_size,
sixel_image_store, sixel_image_store,
Style::default(),
debug, debug,
arrow_fonts,
); );
let mut vte_parser = vte::Parser::new(); let mut vte_parser = vte::Parser::new();
for &byte in ansi_instructions.as_bytes() { for &byte in ansi_instructions.as_bytes() {
@ -663,6 +678,7 @@ fn take_snapshot_with_sixel(
height: 21, height: 21,
}))); })));
let debug = false; let debug = false;
let arrow_fonts = true;
let mut grid = Grid::new( let mut grid = Grid::new(
rows, rows,
columns, columns,
@ -671,7 +687,9 @@ fn take_snapshot_with_sixel(
Rc::new(RefCell::new(LinkHandler::new())), Rc::new(RefCell::new(LinkHandler::new())),
character_cell_size, character_cell_size,
sixel_image_store, sixel_image_store,
Style::default(),
debug, debug,
arrow_fonts,
); );
let mut vte_parser = vte::Parser::new(); let mut vte_parser = vte::Parser::new();
for &byte in ansi_instructions.as_bytes() { for &byte in ansi_instructions.as_bytes() {
@ -690,6 +708,7 @@ fn take_snapshot_and_cursor_position(
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
let debug = false; let debug = false;
let arrow_fonts = true;
let mut grid = Grid::new( let mut grid = Grid::new(
rows, rows,
columns, columns,
@ -698,7 +717,9 @@ fn take_snapshot_and_cursor_position(
Rc::new(RefCell::new(LinkHandler::new())), Rc::new(RefCell::new(LinkHandler::new())),
Rc::new(RefCell::new(None)), Rc::new(RefCell::new(None)),
sixel_image_store, sixel_image_store,
Style::default(),
debug, debug,
arrow_fonts,
); );
let mut vte_parser = vte::Parser::new(); let mut vte_parser = vte::Parser::new();
for &byte in ansi_instructions.as_bytes() { for &byte in ansi_instructions.as_bytes() {

View file

@ -165,6 +165,7 @@ fn create_new_tab(size: Size) -> Tab {
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
let debug = false; let debug = false;
let arrow_fonts = true;
let mut tab = Tab::new( let mut tab = Tab::new(
index, index,
position, position,
@ -188,6 +189,7 @@ fn create_new_tab(size: Size) -> Tab {
(vec![], vec![]), // swap layouts (vec![], vec![]), // swap layouts
None, None,
debug, debug,
arrow_fonts,
); );
tab.apply_layout( tab.apply_layout(
TiledPaneLayout::default(), TiledPaneLayout::default(),
@ -223,6 +225,7 @@ fn create_new_tab_with_layout(size: Size, layout: TiledPaneLayout) -> Tab {
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
let debug = false; let debug = false;
let arrow_fonts = true;
let mut tab = Tab::new( let mut tab = Tab::new(
index, index,
position, position,
@ -246,6 +249,7 @@ fn create_new_tab_with_layout(size: Size, layout: TiledPaneLayout) -> Tab {
(vec![], vec![]), // swap layouts (vec![], vec![]), // swap layouts
None, None,
debug, debug,
arrow_fonts,
); );
let mut new_terminal_ids = vec![]; let mut new_terminal_ids = vec![];
for i in 0..layout.extract_run_instructions().len() { for i in 0..layout.extract_run_instructions().len() {
@ -287,6 +291,7 @@ fn create_new_tab_with_cell_size(
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
let debug = false; let debug = false;
let arrow_fonts = true;
let mut tab = Tab::new( let mut tab = Tab::new(
index, index,
position, position,
@ -310,6 +315,7 @@ fn create_new_tab_with_cell_size(
(vec![], vec![]), // swap layouts (vec![], vec![]), // swap layouts
None, None,
debug, debug,
arrow_fonts,
); );
tab.apply_layout( tab.apply_layout(
TiledPaneLayout::default(), TiledPaneLayout::default(),

View file

@ -0,0 +1,43 @@
use std::fmt::{self, Display, Formatter};
#[derive(Debug, Clone)]
pub struct Coordinates {
pub x: usize,
pub y: usize,
pub width: Option<usize>,
pub height: Option<usize>,
}
impl Coordinates {
pub fn stringify_with_y_offset(&self, y_offset: usize) -> String {
format!("\u{1b}[{};{}H", self.y + y_offset + 1, self.x + 1)
}
}
impl Display for Coordinates {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "\u{1b}[{};{}H", self.y + 1, self.x + 1)
}
}
pub fn is_too_wide(
character_width: usize,
current_width: usize,
component_coordinates: &Option<Coordinates>,
) -> bool {
if let Some(max_width) = component_coordinates.as_ref().and_then(|p| p.width) {
if current_width + character_width > max_width {
return true;
}
}
false
}
pub fn is_too_high(current_height: usize, component_coordinates: &Option<Coordinates>) -> bool {
if let Some(max_height) = component_coordinates.as_ref().and_then(|p| p.height) {
if current_height > max_height {
return true;
}
}
false
}

View file

@ -0,0 +1,187 @@
mod component_coordinates;
mod nested_list;
mod ribbon;
mod table;
mod text;
use crate::panes::grid::Grid;
use zellij_utils::errors::prelude::*;
use zellij_utils::{data::Style, lazy_static::lazy_static, regex::Regex, vte};
use component_coordinates::{is_too_high, is_too_wide, Coordinates};
use nested_list::{nested_list, parse_nested_list_items};
use ribbon::{emphasis_variants_for_ribbon, emphasis_variants_for_selected_ribbon, ribbon};
use table::table;
use text::{parse_text, parse_text_params, stringify_text, text, Text};
macro_rules! parse_next_param {
($next_param:expr, $type:ident, $component_name:expr, $item_name:expr) => {{
$next_param
.and_then(|stringified_param| stringified_param.parse::<$type>().ok())
.with_context(|| format!("{} must have {}", $component_name, $item_name))?
}};
}
macro_rules! parse_vte_bytes {
($self:expr, $encoded_component:expr) => {{
let mut vte_parser = vte::Parser::new();
for &byte in &$encoded_component {
vte_parser.advance($self.grid, byte);
}
}};
}
#[derive(Debug)]
pub struct UiComponentParser<'a> {
grid: &'a mut Grid,
style: Style,
arrow_fonts: bool,
}
impl<'a> UiComponentParser<'a> {
pub fn new(grid: &'a mut Grid, style: Style, arrow_fonts: bool) -> Self {
UiComponentParser {
grid,
style,
arrow_fonts,
}
}
pub fn parse(&mut self, bytes: Vec<u8>) -> Result<()> {
// The stages of parsing:
// 1. We decode the bytes to utf8 and get something like (as a String): `component_name;111;222;333`
// 2. We split this string by `;` to get at the parameters themselves
// 3. We extract the component name, and then behave according to the component
// 4. Some components interpret their parameters as bytes, and so have another layer of
// utf8 decoding, others would take them verbatim, some will act depending on their
// placement (eg. the `table` component treats the first two parameters as integers for
// the columns/rows of the table, and then treats the rest of the component as utf8
// encoded bytes, each one representing one cell in the table)
// 5. Each component parses its parameters, creating a String of ANSI instructions of its
// own representing instructions to create the component
// 6. Finally, we take this string, encode it back into bytes and pass it back through the ANSI
// parser (our `Grid`) in order to create a representation of it on screen
let mut params: Vec<String> = String::from_utf8_lossy(&bytes)
.to_string()
.split(';')
.map(|c| c.to_owned())
.collect();
let mut params_iter = params.iter_mut().peekable();
let component_name = params_iter
.next()
.with_context(|| format!("ui component must have a name"))?;
// parse coordinates
let mut component_coordinates = None;
if let Some(coordinates) = params_iter.peek() {
component_coordinates = self.parse_coordinates(coordinates)?;
if component_coordinates.is_some() {
let _ = params_iter.next(); // we just peeked, let's consume the coords now
}
}
if component_name == &"table" {
let columns = parse_next_param!(params_iter.next(), usize, "table", "columns");
let rows = parse_next_param!(params_iter.next(), usize, "table", "rows");
let stringified_params = parse_text_params(params_iter);
let encoded_table = table(
columns,
rows,
stringified_params,
Some(self.style.colors.green),
&self.style,
component_coordinates,
);
parse_vte_bytes!(self, encoded_table);
Ok(())
} else if component_name == &"ribbon" {
let stringified_params = parse_text_params(params_iter)
.into_iter()
.next()
.with_context(|| format!("a ribbon must have text"))?;
let encoded_text = ribbon(
stringified_params,
&self.style,
self.arrow_fonts,
component_coordinates,
);
parse_vte_bytes!(self, encoded_text);
Ok(())
} else if component_name == &"nested_list" {
let nested_list_items = parse_nested_list_items(params_iter);
let encoded_nested_list =
nested_list(nested_list_items, &self.style, component_coordinates);
parse_vte_bytes!(self, encoded_nested_list);
Ok(())
} else if component_name == &"text" {
let stringified_params = parse_text_params(params_iter)
.into_iter()
.next()
.with_context(|| format!("text must have, well, text..."))?;
let encoded_text = text(stringified_params, &self.style, component_coordinates);
parse_vte_bytes!(self, encoded_text);
Ok(())
} else {
Err(anyhow!("Unknown component: {}", component_name))
}
}
fn parse_coordinates(&self, coordinates: &str) -> Result<Option<Coordinates>> {
lazy_static! {
static ref RE: Regex = Regex::new(r"(\d*)/(\d*)/(\d*)/(\d*)").unwrap();
}
if let Some(captures) = RE.captures_iter(&coordinates).next() {
let x = captures[1].parse::<usize>().with_context(|| {
format!(
"Failed to parse x coordinates for string: {:?}",
coordinates
)
})?;
let y = captures[2].parse::<usize>().with_context(|| {
format!(
"Failed to parse y coordinates for string: {:?}",
coordinates
)
})?;
let width = captures[3].parse::<usize>().ok();
let height = captures[4].parse::<usize>().ok();
Ok(Some(Coordinates {
x,
y,
width,
height,
}))
} else {
Ok(None)
}
}
}
fn parse_selected(stringified: &mut String) -> bool {
let mut selected = false;
if stringified.chars().next() == Some('x') {
selected = true;
stringified.remove(0);
}
selected
}
fn parse_indices(stringified: &mut String) -> Vec<Vec<usize>> {
stringified
.chars()
.collect::<Vec<_>>()
.iter()
.rposition(|c| c == &'$')
.map(|last_position| stringified.drain(0..=last_position).collect::<String>())
.map(|indices_string| {
let mut all_indices = vec![];
let raw_indices_for_each_variant = indices_string.split('$');
for index_string in raw_indices_for_each_variant {
let indices_for_variant = index_string
.split(',')
.filter_map(|s| s.parse::<usize>().ok())
.collect();
all_indices.push(indices_for_variant)
}
all_indices
})
.unwrap_or_default()
}

View file

@ -0,0 +1,142 @@
use super::{
is_too_high, parse_indices, parse_selected, parse_text, stringify_text, Coordinates, Text,
};
use crate::panes::terminal_character::{AnsiCode, RESET_STYLES};
use zellij_utils::data::Style;
use unicode_width::UnicodeWidthChar;
#[derive(Debug, Clone)]
pub struct NestedListItem {
pub text: Text,
pub indentation_level: usize,
}
pub fn nested_list(
mut contents: Vec<NestedListItem>,
style: &Style,
coordinates: Option<Coordinates>,
) -> Vec<u8> {
let mut stringified = String::new();
let max_width = coordinates
.as_ref()
.and_then(|c| c.width)
.unwrap_or_else(|| max_nested_item_width(&contents));
for (line_index, line_item) in contents.drain(..).enumerate() {
if is_too_high(line_index + 1, &coordinates) {
break;
}
let mut reset_styles_for_item = RESET_STYLES;
if line_item.text.selected {
reset_styles_for_item.background = None;
};
let padding = line_item.indentation_level * 2 + 1;
let bulletin = if line_item.indentation_level % 2 == 0 {
"> "
} else {
"- "
};
let text_style = reset_styles_for_item.bold(Some(AnsiCode::On));
let (mut text, text_width) = stringify_text(
&line_item.text,
Some(padding + bulletin.len()),
&coordinates,
style,
text_style,
);
text = pad_line(text, max_width, padding, text_width);
let go_to_row_instruction = coordinates
.as_ref()
.map(|c| c.stringify_with_y_offset(line_index))
.unwrap_or_else(|| {
if line_index != 0 {
format!("\n\r")
} else {
"".to_owned()
}
});
if line_item.text.selected {
let selected_background = RESET_STYLES.background(Some(style.colors.bg.into()));
stringified.push_str(&format!(
"{}{}{}{:padding$}{bulletin}{}{text}{}",
go_to_row_instruction,
selected_background,
reset_styles_for_item,
" ",
text_style,
RESET_STYLES
));
} else {
stringified.push_str(&format!(
"{}{}{:padding$}{bulletin}{}{text}{}",
go_to_row_instruction, reset_styles_for_item, " ", text_style, RESET_STYLES
));
}
}
stringified.as_bytes().to_vec()
}
pub fn parse_nested_list_items<'a>(
params_iter: impl Iterator<Item = &'a mut String>,
) -> Vec<NestedListItem> {
params_iter
.flat_map(|mut stringified| {
let indentation_level = parse_indentation_level(&mut stringified);
let selected = parse_selected(&mut stringified);
let indices = parse_indices(&mut stringified);
let text = parse_text(&mut stringified).map_err(|e| e.to_string())?;
let text = Text {
text,
selected,
indices,
};
Ok::<NestedListItem, String>(NestedListItem {
text,
indentation_level,
})
})
.collect::<Vec<NestedListItem>>()
}
fn parse_indentation_level(stringified: &mut String) -> usize {
let mut indentation_level = 0;
loop {
if stringified.is_empty() {
break;
}
if stringified.chars().next() == Some('|') {
stringified.remove(0);
indentation_level += 1;
} else {
break;
}
}
indentation_level
}
fn max_nested_item_width(contents: &Vec<NestedListItem>) -> usize {
let mut width_of_longest_line = 0;
for line_item in contents.iter() {
let mut line_item_text_width = 0;
for character in line_item.text.text.chars() {
let character_width = character.width().unwrap_or(0);
line_item_text_width += character_width;
}
let bulletin_width = 2;
let padding = line_item.indentation_level * 2 + 1;
let total_width = line_item_text_width + bulletin_width + padding;
if width_of_longest_line < total_width {
width_of_longest_line = total_width;
}
}
width_of_longest_line
}
fn pad_line(text: String, max_width: usize, padding: usize, text_width: usize) -> String {
if max_width > text_width + padding + 2 {
// 2 is the bulletin
let end_padding = max_width.saturating_sub(text_width + padding + 2);
return format!("{}{:end_padding$}", text, " ");
}
text
}

View file

@ -0,0 +1,115 @@
use super::{is_too_wide, Coordinates, Text};
use crate::panes::terminal_character::{AnsiCode, CharacterStyles, RESET_STYLES};
use unicode_width::UnicodeWidthChar;
use zellij_utils::data::{PaletteColor, Style};
static ARROW_SEPARATOR: &str = "";
pub fn ribbon(
content: Text,
style: &Style,
arrow_fonts: bool,
component_coordinates: Option<Coordinates>,
) -> Vec<u8> {
let colors = style.colors;
let (first_arrow_styles, text_style, last_arrow_styles) = if content.selected {
(
character_style(colors.black, colors.green),
character_style(colors.black, colors.green),
character_style(colors.green, colors.black),
)
} else {
(
character_style(colors.black, colors.fg),
character_style(colors.black, colors.fg),
character_style(colors.fg, colors.black),
)
};
let (text, _text_width) =
stringify_ribbon_text(&content, &component_coordinates, style, text_style);
let mut stringified = component_coordinates
.map(|c| c.to_string())
.unwrap_or_else(|| String::new());
let arrow = if arrow_fonts { ARROW_SEPARATOR } else { "" };
stringified.push_str(&format!(
"{}{}{}{} {} {}{}{}",
RESET_STYLES,
first_arrow_styles,
arrow,
text_style,
text,
last_arrow_styles,
arrow,
RESET_STYLES
));
stringified.as_bytes().to_vec()
}
pub fn emphasis_variants_for_ribbon(style: &Style) -> [PaletteColor; 4] {
[
style.colors.red,
style.colors.white,
style.colors.blue,
style.colors.magenta,
]
}
pub fn emphasis_variants_for_selected_ribbon(style: &Style) -> [PaletteColor; 4] {
[
style.colors.red,
style.colors.orange,
style.colors.magenta,
style.colors.blue,
]
}
fn stringify_ribbon_text(
text: &Text,
coordinates: &Option<Coordinates>,
style: &Style,
text_style: CharacterStyles,
) -> (String, usize) {
let mut stringified = String::new();
let mut text_width = 0;
for (i, character) in text.text.chars().enumerate() {
let character_width = character.width().unwrap_or(0);
if is_too_wide(character_width, text_width, &coordinates) {
break;
}
if !text.indices.is_empty() {
let character_with_styling =
color_ribbon_index_character(character, i, &text, style, text_style);
stringified.push_str(&character_with_styling);
} else {
stringified.push(character);
}
text_width += character_width;
}
(stringified, text_width)
}
fn color_ribbon_index_character(
character: char,
index: usize,
text: &Text,
style: &Style,
base_style: CharacterStyles,
) -> String {
let character_style = if text.selected {
text.style_of_index_for_selected_ribbon(index, style)
.map(|foreground_style| base_style.foreground(Some(foreground_style.into())))
.unwrap_or(base_style)
} else {
text.style_of_index_for_ribbon(index, style)
.map(|foreground_style| base_style.foreground(Some(foreground_style.into())))
.unwrap_or(base_style)
};
format!("{}{}{}", character_style, character, base_style)
}
fn character_style(foreground: PaletteColor, background: PaletteColor) -> CharacterStyles {
RESET_STYLES
.foreground(Some(foreground.into()))
.background(Some(background.into()))
.bold(Some(AnsiCode::On))
}

View file

@ -0,0 +1,105 @@
use super::{is_too_high, is_too_wide, stringify_text, Coordinates, Text};
use crate::panes::terminal_character::{AnsiCode, RESET_STYLES};
use std::collections::BTreeMap;
use zellij_utils::{
data::{PaletteColor, Style},
shared::ansi_len,
};
pub fn table(
columns: usize,
_rows: usize,
contents: Vec<Text>,
title_color: Option<PaletteColor>,
style: &Style,
coordinates: Option<Coordinates>,
) -> Vec<u8> {
let mut stringified = String::new();
// we first arrange the data by columns so that we can pad them by the widest one
let stringified_columns = stringify_table_columns(contents, columns);
let stringified_rows = stringify_table_rows(stringified_columns, &coordinates);
let title_styles = RESET_STYLES
.foreground(title_color.map(|t| t.into()))
.bold(Some(AnsiCode::On));
let cell_styles = RESET_STYLES.bold(Some(AnsiCode::On));
for (row_index, (_, row)) in stringified_rows.into_iter().enumerate() {
let is_title_row = row_index == 0;
if is_too_high(row_index + 1, &coordinates) {
break;
}
for cell in row {
let mut reset_styles_for_item = RESET_STYLES;
let mut text_style = if is_title_row {
title_styles
} else {
cell_styles
};
if cell.selected {
reset_styles_for_item.background = None;
text_style = text_style.background(Some(style.colors.bg.into()));
}
// here we intentionally don't pass our coordinates even if we have them, because
// these cells have already been padded and truncated
let (text, _text_width) = stringify_text(&cell, None, &None, style, text_style);
stringified.push_str(&format!("{}{}{} ", text_style, text, reset_styles_for_item));
}
let next_row_instruction = coordinates
.as_ref()
.map(|c| c.stringify_with_y_offset(row_index + 1))
.unwrap_or_else(|| format!("\n\r"));
stringified.push_str(&next_row_instruction);
}
if let Some(coordinates) = coordinates {
format!("{}{}", coordinates, stringified)
.as_bytes()
.to_vec()
} else {
stringified.as_bytes().to_vec()
}
}
fn stringify_table_columns(contents: Vec<Text>, columns: usize) -> BTreeMap<usize, Vec<Text>> {
let mut stringified_columns: BTreeMap<usize, Vec<Text>> = BTreeMap::new();
for (i, cell) in contents.into_iter().enumerate() {
let column_index = i % columns;
stringified_columns
.entry(column_index)
.or_insert_with(Vec::new)
.push(cell);
}
stringified_columns
}
fn max_table_column_width(column: &Vec<Text>) -> usize {
let mut max_column_width = 0;
for cell in column {
let cell_width = ansi_len(&cell.text);
if cell_width > max_column_width {
max_column_width = cell_width;
}
}
max_column_width
}
fn stringify_table_rows(
stringified_columns: BTreeMap<usize, Vec<Text>>,
coordinates: &Option<Coordinates>,
) -> BTreeMap<usize, Vec<Text>> {
let mut stringified_rows: BTreeMap<usize, Vec<Text>> = BTreeMap::new();
let mut row_width = 0;
for (_, column) in stringified_columns.into_iter() {
let max_column_width = max_table_column_width(&column);
if is_too_wide(max_column_width + 1, row_width, &coordinates) {
break;
}
row_width += max_column_width + 1;
for (row_index, mut cell) in column.into_iter().enumerate() {
cell.pad_text(max_column_width);
stringified_rows
.entry(row_index)
.or_insert_with(Vec::new)
.push(cell);
}
}
stringified_rows
}

View file

@ -0,0 +1,166 @@
use super::{
emphasis_variants_for_ribbon, emphasis_variants_for_selected_ribbon, is_too_wide,
parse_indices, parse_selected, Coordinates,
};
use crate::panes::terminal_character::{AnsiCode, CharacterStyles, RESET_STYLES};
use zellij_utils::{
data::{PaletteColor, Style},
shared::ansi_len,
};
use unicode_width::UnicodeWidthChar;
use zellij_utils::errors::prelude::*;
pub fn text(content: Text, style: &Style, component_coordinates: Option<Coordinates>) -> Vec<u8> {
let mut text_style = RESET_STYLES.bold(Some(AnsiCode::On));
if content.selected {
text_style = text_style.background(Some(style.colors.bg.into()));
}
let (text, _text_width) =
stringify_text(&content, None, &component_coordinates, style, text_style);
match component_coordinates {
Some(component_coordinates) => format!("{}{}{}", component_coordinates, text_style, text)
.as_bytes()
.to_vec(),
None => format!("{}{}", text_style, text).as_bytes().to_vec(),
}
}
pub fn stringify_text(
text: &Text,
left_padding: Option<usize>,
coordinates: &Option<Coordinates>,
style: &Style,
text_style: CharacterStyles,
) -> (String, usize) {
let mut text_width = 0;
let mut stringified = String::new();
for (i, character) in text.text.chars().enumerate() {
let character_width = character.width().unwrap_or(0);
if is_too_wide(
character_width,
left_padding.unwrap_or(0) + text_width,
&coordinates,
) {
break;
}
text_width += character_width;
if !text.indices.is_empty() {
let character_with_styling =
color_index_character(character, i, &text, style, text_style);
stringified.push_str(&character_with_styling);
} else {
stringified.push(character);
}
}
(stringified, text_width)
}
pub fn color_index_character(
character: char,
index: usize,
text: &Text,
style: &Style,
base_text_style: CharacterStyles,
) -> String {
let character_style = text
.style_of_index(index, style)
.map(|foreground_style| base_text_style.foreground(Some(foreground_style.into())))
.unwrap_or(base_text_style);
format!("{}{}{}", character_style, character, base_text_style)
}
pub fn emphasis_variants(style: &Style) -> [PaletteColor; 4] {
[
style.colors.orange,
style.colors.cyan,
style.colors.green,
style.colors.magenta,
]
}
pub fn parse_text_params<'a>(params_iter: impl Iterator<Item = &'a mut String>) -> Vec<Text> {
params_iter
.flat_map(|mut stringified| {
let selected = parse_selected(&mut stringified);
let indices = parse_indices(&mut stringified);
let text = parse_text(&mut stringified).map_err(|e| e.to_string())?;
Ok::<Text, String>(Text {
text,
selected,
indices,
})
})
.collect::<Vec<Text>>()
}
#[derive(Debug, Clone)]
pub struct Text {
pub text: String,
pub selected: bool,
pub indices: Vec<Vec<usize>>,
}
impl Text {
pub fn pad_text(&mut self, max_column_width: usize) {
for _ in ansi_len(&self.text)..max_column_width {
self.text.push(' ');
}
}
pub fn style_of_index(&self, index: usize, style: &Style) -> Option<PaletteColor> {
let index_variant_styles = emphasis_variants(style);
for i in (0..=3).rev() {
// we do this in reverse to give precedence to the last applied
// style
if let Some(indices) = self.indices.get(i) {
if indices.contains(&index) {
return Some(index_variant_styles[i]);
}
}
}
None
}
pub fn style_of_index_for_ribbon(&self, index: usize, style: &Style) -> Option<PaletteColor> {
let index_variant_styles = emphasis_variants_for_ribbon(style);
for i in (0..=3).rev() {
// we do this in reverse to give precedence to the last applied
// style
if let Some(indices) = self.indices.get(i) {
if indices.contains(&index) {
return Some(index_variant_styles[i]);
}
}
}
None
}
pub fn style_of_index_for_selected_ribbon(
&self,
index: usize,
style: &Style,
) -> Option<PaletteColor> {
let index_variant_styles = emphasis_variants_for_selected_ribbon(style);
for i in (0..=3).rev() {
// we do this in reverse to give precedence to the last applied
// style
if let Some(indices) = self.indices.get(i) {
if indices.contains(&index) {
return Some(index_variant_styles[i]);
}
}
}
None
}
}
pub fn parse_text(stringified: &mut String) -> Result<String> {
let mut utf8 = vec![];
for stringified_character in stringified.split(',') {
utf8.push(
stringified_character
.to_string()
.parse::<u8>()
.with_context(|| format!("Failed to parse utf8"))?,
);
}
Ok(String::from_utf8_lossy(&utf8).to_string())
}

View file

@ -1,4 +1,5 @@
pub mod boundaries; pub mod boundaries;
pub mod components;
pub mod loading_indication; pub mod loading_indication;
pub mod overlay; pub mod overlay;
pub mod pane_boundaries_frame; pub mod pane_boundaries_frame;

View file

@ -10,7 +10,7 @@ use crate::{
use insta::assert_snapshot; use insta::assert_snapshot;
use std::path::PathBuf; use std::path::PathBuf;
use zellij_utils::cli::CliAction; use zellij_utils::cli::CliAction;
use zellij_utils::data::{Event, Resize}; use zellij_utils::data::{Event, Resize, Style};
use zellij_utils::errors::{prelude::*, ErrorContext}; use zellij_utils::errors::{prelude::*, ErrorContext};
use zellij_utils::input::actions::Action; use zellij_utils::input::actions::Action;
use zellij_utils::input::command::{RunCommand, TerminalAction}; use zellij_utils::input::command::{RunCommand, TerminalAction};
@ -67,6 +67,7 @@ fn take_snapshots_and_cursor_coordinates_from_render_events<'a>(
height: 21, height: 21,
}))); })));
let debug = false; let debug = false;
let arrow_fonts = true;
let mut grid = Grid::new( let mut grid = Grid::new(
screen_size.rows, screen_size.rows,
screen_size.cols, screen_size.cols,
@ -75,7 +76,9 @@ fn take_snapshots_and_cursor_coordinates_from_render_events<'a>(
Rc::new(RefCell::new(LinkHandler::new())), Rc::new(RefCell::new(LinkHandler::new())),
character_cell_size, character_cell_size,
sixel_image_store, sixel_image_store,
Style::default(),
debug, debug,
arrow_fonts,
); );
let snapshots: Vec<(Option<(usize, usize)>, String)> = all_events let snapshots: Vec<(Option<(usize, usize)>, String)> = all_events
.filter_map(|server_instruction| { .filter_map(|server_instruction| {
@ -241,6 +244,7 @@ fn create_new_screen(size: Size) -> Screen {
let scrollback_lines_to_serialize = None; let scrollback_lines_to_serialize = None;
let debug = false; let debug = false;
let arrow_fonts = true;
let screen = Screen::new( let screen = Screen::new(
bus, bus,
&client_attributes, &client_attributes,
@ -256,6 +260,7 @@ fn create_new_screen(size: Size) -> Screen {
session_serialization, session_serialization,
serialize_pane_viewport, serialize_pane_viewport,
scrollback_lines_to_serialize, scrollback_lines_to_serialize,
arrow_fonts,
); );
screen screen
} }

View file

@ -17,6 +17,7 @@
//! //!
pub mod prelude; pub mod prelude;
pub mod shim; pub mod shim;
pub mod ui_components;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::BTreeMap; use std::collections::BTreeMap;

View file

@ -10,6 +10,7 @@ pub use zellij_utils::plugin_api;
use zellij_utils::plugin_api::plugin_command::ProtobufPluginCommand; use zellij_utils::plugin_api::plugin_command::ProtobufPluginCommand;
use zellij_utils::plugin_api::plugin_ids::{ProtobufPluginIds, ProtobufZellijVersion}; use zellij_utils::plugin_api::plugin_ids::{ProtobufPluginIds, ProtobufZellijVersion};
pub use super::ui_components::*;
pub use zellij_utils::prost::{self, *}; pub use zellij_utils::prost::{self, *};
// Subscription Handling // Subscription Handling

View file

@ -0,0 +1,12 @@
mod nested_list;
mod ribbon;
mod table;
mod text;
pub use zellij_utils::plugin_api;
pub use zellij_utils::prost::{self, *};
pub use nested_list::*;
pub use ribbon::*;
pub use table::*;
pub use text::*;

View file

@ -0,0 +1,73 @@
use super::Text;
use std::ops::RangeBounds;
#[derive(Debug, Default, Clone)]
pub struct NestedListItem {
indentation_level: usize,
content: Text,
}
impl NestedListItem {
pub fn new<S: AsRef<str>>(text: S) -> Self
where
S: ToString,
{
NestedListItem {
content: Text::new(text),
..Default::default()
}
}
pub fn indent(mut self, indentation_level: usize) -> Self {
self.indentation_level = indentation_level;
self
}
pub fn selected(mut self) -> Self {
self.content = self.content.selected();
self
}
pub fn color_indices(mut self, index_level: usize, indices: Vec<usize>) -> Self {
self.content = self.content.color_indices(index_level, indices);
self
}
pub fn color_range<R: RangeBounds<usize>>(mut self, index_level: usize, indices: R) -> Self {
self.content = self.content.color_range(index_level, indices);
self
}
pub fn serialize(&self) -> String {
let mut serialized = String::new();
for _ in 0..self.indentation_level {
serialized.push('|');
}
format!("{}{}", serialized, self.content.serialize())
}
}
/// render a nested list with arbitrary data
pub fn print_nested_list(items: Vec<NestedListItem>) {
let items = items
.into_iter()
.map(|i| i.serialize())
.collect::<Vec<_>>()
.join(";");
print!("\u{1b}Pznested_list;{}\u{1b}\\", items)
}
pub fn print_nested_list_with_coordinates(
items: Vec<NestedListItem>,
x: usize,
y: usize,
width: Option<usize>,
height: Option<usize>,
) {
let width = width.map(|w| w.to_string()).unwrap_or_default();
let height = height.map(|h| h.to_string()).unwrap_or_default();
let items = items
.into_iter()
.map(|i| i.serialize())
.collect::<Vec<_>>()
.join(";");
print!(
"\u{1b}Pznested_list;{}/{}/{}/{};{}\u{1b}\\",
x, y, width, height, items
)
}

View file

@ -0,0 +1,24 @@
use super::Text;
pub fn print_ribbon(text: Text) {
print!("\u{1b}Pzribbon;{}\u{1b}\\", text.serialize());
}
pub fn print_ribbon_with_coordinates(
text: Text,
x: usize,
y: usize,
width: Option<usize>,
height: Option<usize>,
) {
let width = width.map(|w| w.to_string()).unwrap_or_default();
let height = height.map(|h| h.to_string()).unwrap_or_default();
print!(
"\u{1b}Pzribbon;{}/{}/{}/{};{}\u{1b}\\",
x,
y,
width,
height,
text.serialize()
);
}

View file

@ -0,0 +1,61 @@
use super::Text;
/// render a table with arbitrary data
#[derive(Debug, Clone)]
pub struct Table {
contents: Vec<Vec<Text>>,
}
impl Table {
pub fn new() -> Self {
Table { contents: vec![] }
}
pub fn add_row(mut self, row: Vec<impl ToString>) -> Self {
self.contents
.push(row.iter().map(|c| Text::new(c.to_string())).collect());
self
}
pub fn add_styled_row(mut self, row: Vec<Text>) -> Self {
self.contents.push(row);
self
}
pub fn serialize(&self) -> String {
let columns = self
.contents
.get(0)
.map(|first_row| first_row.len())
.unwrap_or(0);
let rows = self.contents.len();
let contents = self
.contents
.iter()
.flatten()
.map(|t| t.serialize())
.collect::<Vec<_>>()
.join(";");
format!("{};{};{}\u{1b}\\", columns, rows, contents)
}
}
pub fn print_table(table: Table) {
print!("\u{1b}Pztable;{}", table.serialize())
}
pub fn print_table_with_coordinates(
table: Table,
x: usize,
y: usize,
width: Option<usize>,
height: Option<usize>,
) {
let width = width.map(|w| w.to_string()).unwrap_or_default();
let height = height.map(|h| h.to_string()).unwrap_or_default();
print!(
"\u{1b}Pztable;{}/{}/{}/{};{}\u{1b}\\",
x,
y,
width,
height,
table.serialize()
)
}

View file

@ -0,0 +1,107 @@
use std::ops::Bound;
use std::ops::RangeBounds;
#[derive(Debug, Default, Clone)]
pub struct Text {
text: String,
selected: bool,
indices: Vec<Vec<usize>>,
}
impl Text {
pub fn new<S: AsRef<str>>(content: S) -> Self
where
S: ToString,
{
Text {
text: content.to_string(),
selected: false,
indices: vec![],
}
}
pub fn selected(mut self) -> Self {
self.selected = true;
self
}
pub fn color_indices(mut self, index_level: usize, mut indices: Vec<usize>) -> Self {
self.pad_indices(index_level);
self.indices
.get_mut(index_level)
.map(|i| i.append(&mut indices));
self
}
pub fn color_range<R: RangeBounds<usize>>(mut self, index_level: usize, indices: R) -> Self {
self.pad_indices(index_level);
let start = match indices.start_bound() {
Bound::Unbounded => 0,
Bound::Included(s) => *s,
Bound::Excluded(s) => *s,
};
let end = match indices.end_bound() {
Bound::Unbounded => self.text.chars().count(),
Bound::Included(s) => *s + 1,
Bound::Excluded(s) => *s,
};
let indices = (start..end).into_iter();
self.indices
.get_mut(index_level)
.map(|i| i.append(&mut indices.into_iter().collect()));
self
}
fn pad_indices(&mut self, index_level: usize) {
if self.indices.get(index_level).is_none() {
for _ in self.indices.len()..=index_level {
self.indices.push(vec![]);
}
}
}
pub fn serialize(&self) -> String {
let text = self
.text
.to_string()
.as_bytes()
.iter()
.map(|b| b.to_string())
.collect::<Vec<_>>()
.join(",");
let mut indices = String::new();
for index_variants in &self.indices {
indices.push_str(&format!(
"{}$",
index_variants
.iter()
.map(|i| i.to_string())
.collect::<Vec<_>>()
.join(",")
));
}
if self.selected {
format!("x{}{}", indices, text)
} else {
format!("{}{}", indices, text)
}
}
}
pub fn print_text(text: Text) {
print!("\u{1b}Pztext;{}\u{1b}\\", text.serialize())
}
pub fn print_text_with_coordinates(
text: Text,
x: usize,
y: usize,
width: Option<usize>,
height: Option<usize>,
) {
let width = width.map(|w| w.to_string()).unwrap_or_default();
let height = height.map(|h| h.to_string()).unwrap_or_default();
print!(
"\u{1b}Pztext;{}/{}/{}/{};{}\u{1b}\\",
x,
y,
width,
height,
text.serialize()
)
}