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:
parent
35d93189e3
commit
7e5f22f8c9
44 changed files with 2117 additions and 4 deletions
|
|
@ -235,6 +235,7 @@ fn read_from_channel(
|
|||
})));
|
||||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||||
let debug = false;
|
||||
let arrow_fonts = true;
|
||||
let mut terminal_output = TerminalPane::new(
|
||||
0,
|
||||
pane_geom,
|
||||
|
|
@ -249,6 +250,7 @@ fn read_from_channel(
|
|||
None,
|
||||
None,
|
||||
debug,
|
||||
arrow_fonts,
|
||||
); // 0 is the pane index
|
||||
loop {
|
||||
if !should_keep_running.load(Ordering::SeqCst) {
|
||||
|
|
|
|||
1
src/tests/fixtures/nested-list-ui-component
vendored
Normal file
1
src/tests/fixtures/nested-list-ui-component
vendored
Normal 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\
|
||||
1
src/tests/fixtures/nested-list-ui-component-with-coordinates
vendored
Normal file
1
src/tests/fixtures/nested-list-ui-component-with-coordinates
vendored
Normal 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\
|
||||
1
src/tests/fixtures/ribbon-ui-component
vendored
Normal file
1
src/tests/fixtures/ribbon-ui-component
vendored
Normal 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\
|
||||
1
src/tests/fixtures/ribbon-ui-component-with-coordinates
vendored
Normal file
1
src/tests/fixtures/ribbon-ui-component-with-coordinates
vendored
Normal 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
1
src/tests/fixtures/table-ui-component
vendored
Normal 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\
|
||||
1
src/tests/fixtures/table-ui-component-with-coordinates
vendored
Normal file
1
src/tests/fixtures/table-ui-component-with-coordinates
vendored
Normal 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
1
src/tests/fixtures/text-ui-component
vendored
Normal 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\
|
||||
1
src/tests/fixtures/text-ui-component-with-coordinates
vendored
Normal file
1
src/tests/fixtures/text-ui-component-with-coordinates
vendored
Normal 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\
|
||||
|
|
@ -38,6 +38,7 @@ use crate::panes::terminal_character::{
|
|||
AnsiCode, CharacterStyles, CharsetIndex, Cursor, CursorShape, StandardCharset,
|
||||
TerminalCharacter, EMPTY_TERMINAL_CHARACTER,
|
||||
};
|
||||
use crate::ui::components::UiComponentParser;
|
||||
|
||||
fn get_top_non_canonical_rows(rows: &mut Vec<Row>) -> Vec<Row> {
|
||||
let mut index_of_last_non_canonical_row = None;
|
||||
|
|
@ -369,7 +370,10 @@ pub struct Grid {
|
|||
pub focus_event_tracking: bool,
|
||||
pub search_results: SearchResult,
|
||||
pub pending_clipboard_update: Option<String>,
|
||||
ui_component_bytes: Option<Vec<u8>>,
|
||||
style: Style,
|
||||
debug: bool,
|
||||
arrow_fonts: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
@ -457,7 +461,9 @@ impl Grid {
|
|||
link_handler: Rc<RefCell<LinkHandler>>,
|
||||
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
||||
sixel_image_store: Rc<RefCell<SixelImageStore>>,
|
||||
style: Style, // TODO: consolidate this with terminal_emulator_colors
|
||||
debug: bool,
|
||||
arrow_fonts: bool,
|
||||
) -> Self {
|
||||
let sixel_grid = SixelGrid::new(character_cell_size.clone(), sixel_image_store);
|
||||
// make sure this is initialized as it is used internally
|
||||
|
|
@ -507,7 +513,10 @@ impl Grid {
|
|||
search_results: Default::default(),
|
||||
sixel_grid,
|
||||
pending_clipboard_update: None,
|
||||
ui_component_bytes: None,
|
||||
style,
|
||||
debug,
|
||||
arrow_fonts,
|
||||
}
|
||||
}
|
||||
pub fn render_full_viewport(&mut self) {
|
||||
|
|
@ -2196,6 +2205,9 @@ impl Perform for Grid {
|
|||
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
|
||||
// image when it's done, i.e. in the unhook method
|
||||
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) {
|
||||
if self.sixel_grid.is_parsing() {
|
||||
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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,9 @@ macro_rules! get_or_create_grid {
|
|||
$self.link_handler.clone(),
|
||||
$self.character_cell_size.clone(),
|
||||
$self.sixel_image_store.clone(),
|
||||
$self.style.clone(),
|
||||
$self.debug,
|
||||
$self.arrow_fonts,
|
||||
);
|
||||
grid.hide_cursor();
|
||||
grid
|
||||
|
|
@ -85,6 +87,7 @@ pub(crate) struct PluginPane {
|
|||
loading_indication: LoadingIndication,
|
||||
requesting_permissions: Option<PluginPermission>,
|
||||
debug: bool,
|
||||
arrow_fonts: bool,
|
||||
}
|
||||
|
||||
impl PluginPane {
|
||||
|
|
@ -103,6 +106,7 @@ impl PluginPane {
|
|||
style: Style,
|
||||
invoked_with: Option<Run>,
|
||||
debug: bool,
|
||||
arrow_fonts: bool,
|
||||
) -> Self {
|
||||
let loading_indication = LoadingIndication::new(title.clone()).with_colors(style.colors);
|
||||
let initial_loading_message = loading_indication.to_string();
|
||||
|
|
@ -134,6 +138,7 @@ impl PluginPane {
|
|||
loading_indication,
|
||||
requesting_permissions: None,
|
||||
debug,
|
||||
arrow_fonts,
|
||||
};
|
||||
for client_id in currently_connected_clients {
|
||||
plugin.handle_plugin_bytes(client_id, initial_loading_message.as_bytes().to_vec());
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ pub struct TerminalPane {
|
|||
// held on startup and can possibly be used to display some errors
|
||||
pane_frame_color_override: Option<(PaletteColor, Option<String>)>,
|
||||
invoked_with: Option<Run>,
|
||||
arrow_fonts: bool,
|
||||
}
|
||||
|
||||
impl Pane for TerminalPane {
|
||||
|
|
@ -786,6 +787,7 @@ impl TerminalPane {
|
|||
initial_pane_title: Option<String>,
|
||||
invoked_with: Option<Run>,
|
||||
debug: bool,
|
||||
arrow_fonts: bool,
|
||||
) -> TerminalPane {
|
||||
let initial_pane_title =
|
||||
initial_pane_title.unwrap_or_else(|| format!("Pane #{}", pane_index));
|
||||
|
|
@ -797,7 +799,9 @@ impl TerminalPane {
|
|||
link_handler,
|
||||
character_cell_size,
|
||||
sixel_image_store,
|
||||
style.clone(),
|
||||
debug,
|
||||
arrow_fonts,
|
||||
);
|
||||
TerminalPane {
|
||||
frame: HashMap::new(),
|
||||
|
|
@ -822,6 +826,7 @@ impl TerminalPane {
|
|||
banner: None,
|
||||
pane_frame_color_override: None,
|
||||
invoked_with,
|
||||
arrow_fonts,
|
||||
}
|
||||
}
|
||||
pub fn get_x(&self) -> usize {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -29,6 +29,7 @@ fn create_pane() -> TerminalPane {
|
|||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||||
let debug = false;
|
||||
let arrow_fonts = true;
|
||||
let mut terminal_pane = TerminalPane::new(
|
||||
pid,
|
||||
fake_win_size,
|
||||
|
|
@ -43,6 +44,7 @@ fn create_pane() -> TerminalPane {
|
|||
None,
|
||||
None,
|
||||
debug,
|
||||
arrow_fonts,
|
||||
); // 0 is the pane index
|
||||
let content = read_fixture();
|
||||
terminal_pane.handle_pty_bytes(content);
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
||||
|
|
@ -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):
|
||||
|
||||
|
|
@ -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):
|
||||
|
||||
|
|
@ -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):
|
||||
|
||||
|
|
@ -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):
|
||||
|
||||
|
|
@ -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):
|
||||
|
||||
|
|
@ -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):
|
||||
|
||||
|
|
@ -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):
|
||||
|
||||
|
|
@ -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):
|
||||
|
||||
|
|
@ -37,6 +37,7 @@ pub fn scrolling_inside_a_pane() {
|
|||
let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
|
||||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||||
let debug = false;
|
||||
let arrow_fonts = true;
|
||||
let mut terminal_pane = TerminalPane::new(
|
||||
pid,
|
||||
fake_win_size,
|
||||
|
|
@ -51,6 +52,7 @@ pub fn scrolling_inside_a_pane() {
|
|||
None,
|
||||
None,
|
||||
debug,
|
||||
arrow_fonts,
|
||||
); // 0 is the pane index
|
||||
let mut text_to_fill_pane = String::new();
|
||||
for i in 0..30 {
|
||||
|
|
@ -81,6 +83,7 @@ pub fn sixel_image_inside_terminal_pane() {
|
|||
height: 21,
|
||||
})));
|
||||
let debug = false;
|
||||
let arrow_fonts = true;
|
||||
let mut terminal_pane = TerminalPane::new(
|
||||
pid,
|
||||
fake_win_size,
|
||||
|
|
@ -95,6 +98,7 @@ pub fn sixel_image_inside_terminal_pane() {
|
|||
None,
|
||||
None,
|
||||
debug,
|
||||
arrow_fonts,
|
||||
); // 0 is the pane index
|
||||
let sixel_image_bytes = "\u{1b}Pq
|
||||
#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,
|
||||
})));
|
||||
let debug = false;
|
||||
let arrow_fonts = true;
|
||||
let mut terminal_pane = TerminalPane::new(
|
||||
pid,
|
||||
fake_win_size,
|
||||
|
|
@ -139,6 +144,7 @@ pub fn partial_sixel_image_inside_terminal_pane() {
|
|||
None,
|
||||
None,
|
||||
debug,
|
||||
arrow_fonts,
|
||||
); // 0 is the pane index
|
||||
let pane_content = read_fixture("sixel-image-500px.six");
|
||||
terminal_pane.handle_pty_bytes(pane_content);
|
||||
|
|
@ -163,6 +169,7 @@ pub fn overflowing_sixel_image_inside_terminal_pane() {
|
|||
height: 21,
|
||||
})));
|
||||
let debug = false;
|
||||
let arrow_fonts = true;
|
||||
let mut terminal_pane = TerminalPane::new(
|
||||
pid,
|
||||
fake_win_size,
|
||||
|
|
@ -177,6 +184,7 @@ pub fn overflowing_sixel_image_inside_terminal_pane() {
|
|||
None,
|
||||
None,
|
||||
debug,
|
||||
arrow_fonts,
|
||||
); // 0 is the pane index
|
||||
let pane_content = read_fixture("sixel-image-500px.six");
|
||||
terminal_pane.handle_pty_bytes(pane_content);
|
||||
|
|
@ -200,6 +208,7 @@ pub fn scrolling_through_a_sixel_image() {
|
|||
height: 21,
|
||||
})));
|
||||
let debug = false;
|
||||
let arrow_fonts = true;
|
||||
let mut terminal_pane = TerminalPane::new(
|
||||
pid,
|
||||
fake_win_size,
|
||||
|
|
@ -214,6 +223,7 @@ pub fn scrolling_through_a_sixel_image() {
|
|||
None,
|
||||
None,
|
||||
debug,
|
||||
arrow_fonts,
|
||||
); // 0 is the pane index
|
||||
let mut text_to_fill_pane = String::new();
|
||||
for i in 0..30 {
|
||||
|
|
@ -248,6 +258,7 @@ pub fn multiple_sixel_images_in_pane() {
|
|||
height: 21,
|
||||
})));
|
||||
let debug = false;
|
||||
let arrow_fonts = true;
|
||||
let mut terminal_pane = TerminalPane::new(
|
||||
pid,
|
||||
fake_win_size,
|
||||
|
|
@ -262,6 +273,7 @@ pub fn multiple_sixel_images_in_pane() {
|
|||
None,
|
||||
None,
|
||||
debug,
|
||||
arrow_fonts,
|
||||
); // 0 is the pane index
|
||||
let mut text_to_fill_pane = String::new();
|
||||
for i in 0..5 {
|
||||
|
|
@ -294,6 +306,7 @@ pub fn resizing_pane_with_sixel_images() {
|
|||
height: 21,
|
||||
})));
|
||||
let debug = false;
|
||||
let arrow_fonts = true;
|
||||
let mut terminal_pane = TerminalPane::new(
|
||||
pid,
|
||||
fake_win_size,
|
||||
|
|
@ -308,6 +321,7 @@ pub fn resizing_pane_with_sixel_images() {
|
|||
None,
|
||||
None,
|
||||
debug,
|
||||
arrow_fonts,
|
||||
); // 0 is the pane index
|
||||
let mut text_to_fill_pane = String::new();
|
||||
for i in 0..5 {
|
||||
|
|
@ -343,6 +357,7 @@ pub fn changing_character_cell_size_with_sixel_images() {
|
|||
height: 21,
|
||||
})));
|
||||
let debug = false;
|
||||
let arrow_fonts = true;
|
||||
let mut terminal_pane = TerminalPane::new(
|
||||
pid,
|
||||
fake_win_size,
|
||||
|
|
@ -357,6 +372,7 @@ pub fn changing_character_cell_size_with_sixel_images() {
|
|||
None,
|
||||
None,
|
||||
debug,
|
||||
arrow_fonts,
|
||||
); // 0 is the pane index
|
||||
let mut text_to_fill_pane = String::new();
|
||||
for i in 0..5 {
|
||||
|
|
@ -397,6 +413,7 @@ pub fn keep_working_after_corrupted_sixel_image() {
|
|||
height: 21,
|
||||
})));
|
||||
let debug = false;
|
||||
let arrow_fonts = true;
|
||||
let mut terminal_pane = TerminalPane::new(
|
||||
pid,
|
||||
fake_win_size,
|
||||
|
|
@ -411,6 +428,7 @@ pub fn keep_working_after_corrupted_sixel_image() {
|
|||
None,
|
||||
None,
|
||||
debug,
|
||||
arrow_fonts,
|
||||
); // 0 is the pane index
|
||||
|
||||
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,
|
||||
})));
|
||||
let debug = false;
|
||||
let arrow_fonts = true;
|
||||
let mut terminal_pane = TerminalPane::new(
|
||||
pid,
|
||||
fake_win_size,
|
||||
|
|
@ -463,6 +482,7 @@ pub fn pane_with_frame_position_is_on_frame() {
|
|||
None,
|
||||
None,
|
||||
debug,
|
||||
arrow_fonts,
|
||||
); // 0 is the pane index
|
||||
|
||||
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,
|
||||
})));
|
||||
let debug = false;
|
||||
let arrow_fonts = true;
|
||||
let mut terminal_pane = TerminalPane::new(
|
||||
pid,
|
||||
fake_win_size,
|
||||
|
|
@ -551,6 +572,7 @@ pub fn pane_with_bottom_and_right_borders_position_is_on_frame() {
|
|||
None,
|
||||
None,
|
||||
debug,
|
||||
arrow_fonts,
|
||||
); // 0 is the pane index
|
||||
|
||||
terminal_pane.set_content_offset(Offset::shift(1, 1));
|
||||
|
|
@ -625,6 +647,7 @@ pub fn frameless_pane_position_is_on_frame() {
|
|||
height: 21,
|
||||
})));
|
||||
let debug = false;
|
||||
let arrow_fonts = true;
|
||||
let mut terminal_pane = TerminalPane::new(
|
||||
pid,
|
||||
fake_win_size,
|
||||
|
|
@ -639,6 +662,7 @@ pub fn frameless_pane_position_is_on_frame() {
|
|||
None,
|
||||
None,
|
||||
debug,
|
||||
arrow_fonts,
|
||||
); // 0 is the pane index
|
||||
|
||||
terminal_pane.set_content_offset(Offset::default());
|
||||
|
|
|
|||
|
|
@ -560,6 +560,7 @@ pub(crate) struct Screen {
|
|||
// also be this session
|
||||
default_layout: Box<Layout>,
|
||||
default_shell: Option<PathBuf>,
|
||||
arrow_fonts: bool,
|
||||
}
|
||||
|
||||
impl Screen {
|
||||
|
|
@ -579,6 +580,7 @@ impl Screen {
|
|||
session_serialization: bool,
|
||||
serialize_pane_viewport: bool,
|
||||
scrollback_lines_to_serialize: Option<usize>,
|
||||
arrow_fonts: bool,
|
||||
) -> Self {
|
||||
let session_name = mode_info.session_name.clone().unwrap_or_default();
|
||||
let session_info = SessionInfo::new(session_name.clone());
|
||||
|
|
@ -613,6 +615,7 @@ impl Screen {
|
|||
session_serialization,
|
||||
serialize_pane_viewport,
|
||||
scrollback_lines_to_serialize,
|
||||
arrow_fonts,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1151,6 +1154,7 @@ impl Screen {
|
|||
swap_layouts,
|
||||
self.default_shell.clone(),
|
||||
self.debug,
|
||||
self.arrow_fonts,
|
||||
);
|
||||
self.tabs.insert(tab_index, tab);
|
||||
Ok(())
|
||||
|
|
@ -2034,7 +2038,7 @@ pub(crate) fn screen_thread_main(
|
|||
debug: bool,
|
||||
default_layout: Box<Layout>,
|
||||
) -> 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 auto_layout = config_options.auto_layout.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(),
|
||||
&client_attributes,
|
||||
PluginCapabilities {
|
||||
arrow_fonts: capabilities.unwrap_or_default(),
|
||||
// ¯\_(ツ)_/¯
|
||||
arrow_fonts: !arrow_fonts,
|
||||
},
|
||||
),
|
||||
draw_pane_frames,
|
||||
|
|
@ -2070,6 +2075,7 @@ pub(crate) fn screen_thread_main(
|
|||
session_serialization,
|
||||
serialize_pane_viewport,
|
||||
scrollback_lines_to_serialize,
|
||||
arrow_fonts,
|
||||
);
|
||||
|
||||
let mut pending_tab_ids: HashSet<usize> = HashSet::new();
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ pub struct LayoutApplier<'a> {
|
|||
focus_pane_id: &'a mut Option<PaneId>,
|
||||
os_api: Box<dyn ServerOsApi>,
|
||||
debug: bool,
|
||||
arrow_fonts: bool,
|
||||
}
|
||||
|
||||
impl<'a> LayoutApplier<'a> {
|
||||
|
|
@ -61,6 +62,7 @@ impl<'a> LayoutApplier<'a> {
|
|||
focus_pane_id: &'a mut Option<PaneId>,
|
||||
os_api: &Box<dyn ServerOsApi>,
|
||||
debug: bool,
|
||||
arrow_fonts: bool,
|
||||
) -> Self {
|
||||
let viewport = viewport.clone();
|
||||
let senders = senders.clone();
|
||||
|
|
@ -90,6 +92,7 @@ impl<'a> LayoutApplier<'a> {
|
|||
focus_pane_id,
|
||||
os_api,
|
||||
debug,
|
||||
arrow_fonts,
|
||||
}
|
||||
}
|
||||
pub fn apply_layout(
|
||||
|
|
@ -256,6 +259,7 @@ impl<'a> LayoutApplier<'a> {
|
|||
self.style,
|
||||
layout.run.clone(),
|
||||
self.debug,
|
||||
self.arrow_fonts,
|
||||
);
|
||||
if let Some(pane_initial_contents) = &layout.pane_initial_contents {
|
||||
new_plugin.handle_pty_bytes(pane_initial_contents.as_bytes().into());
|
||||
|
|
@ -292,6 +296,7 @@ impl<'a> LayoutApplier<'a> {
|
|||
initial_title,
|
||||
layout.run.clone(),
|
||||
self.debug,
|
||||
self.arrow_fonts,
|
||||
);
|
||||
if let Some(pane_initial_contents) = &layout.pane_initial_contents {
|
||||
new_pane.handle_pty_bytes(pane_initial_contents.as_bytes().into());
|
||||
|
|
@ -381,6 +386,7 @@ impl<'a> LayoutApplier<'a> {
|
|||
self.style,
|
||||
floating_pane_layout.run.clone(),
|
||||
self.debug,
|
||||
self.arrow_fonts,
|
||||
);
|
||||
if let Some(pane_initial_contents) = &floating_pane_layout.pane_initial_contents {
|
||||
new_pane.handle_pty_bytes(pane_initial_contents.as_bytes().into());
|
||||
|
|
@ -420,6 +426,7 @@ impl<'a> LayoutApplier<'a> {
|
|||
initial_title,
|
||||
floating_pane_layout.run.clone(),
|
||||
self.debug,
|
||||
self.arrow_fonts,
|
||||
);
|
||||
if let Some(pane_initial_contents) = &floating_pane_layout.pane_initial_contents {
|
||||
new_pane.handle_pty_bytes(pane_initial_contents.as_bytes().into());
|
||||
|
|
|
|||
|
|
@ -185,6 +185,7 @@ pub(crate) struct Tab {
|
|||
swap_layouts: SwapLayouts,
|
||||
default_shell: Option<PathBuf>,
|
||||
debug: bool,
|
||||
arrow_fonts: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
|
|
@ -533,6 +534,7 @@ impl Tab {
|
|||
swap_layouts: (Vec<SwapTiledLayout>, Vec<SwapFloatingLayout>),
|
||||
default_shell: Option<PathBuf>,
|
||||
debug: bool,
|
||||
arrow_fonts: bool,
|
||||
) -> Self {
|
||||
let name = if name.is_empty() {
|
||||
format!("Tab #{}", index + 1)
|
||||
|
|
@ -621,6 +623,7 @@ impl Tab {
|
|||
swap_layouts,
|
||||
default_shell,
|
||||
debug,
|
||||
arrow_fonts,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -652,6 +655,7 @@ impl Tab {
|
|||
&mut self.focus_pane_id,
|
||||
&self.os_api,
|
||||
self.debug,
|
||||
self.arrow_fonts,
|
||||
)
|
||||
.apply_layout(
|
||||
layout,
|
||||
|
|
@ -713,6 +717,7 @@ impl Tab {
|
|||
&mut self.focus_pane_id,
|
||||
&self.os_api,
|
||||
self.debug,
|
||||
self.arrow_fonts,
|
||||
)
|
||||
.apply_floating_panes_layout_to_existing_panes(
|
||||
&layout_candidate,
|
||||
|
|
@ -767,6 +772,7 @@ impl Tab {
|
|||
&mut self.focus_pane_id,
|
||||
&self.os_api,
|
||||
self.debug,
|
||||
self.arrow_fonts,
|
||||
)
|
||||
.apply_tiled_panes_layout_to_existing_panes(
|
||||
&layout_candidate,
|
||||
|
|
@ -1053,6 +1059,7 @@ impl Tab {
|
|||
initial_pane_title,
|
||||
invoked_with,
|
||||
self.debug,
|
||||
self.arrow_fonts,
|
||||
)) as Box<dyn Pane>
|
||||
},
|
||||
PaneId::Plugin(plugin_pid) => {
|
||||
|
|
@ -1075,6 +1082,7 @@ impl Tab {
|
|||
self.style,
|
||||
invoked_with,
|
||||
self.debug,
|
||||
self.arrow_fonts,
|
||||
)) as Box<dyn Pane>
|
||||
},
|
||||
};
|
||||
|
|
@ -1111,6 +1119,7 @@ impl Tab {
|
|||
None,
|
||||
None,
|
||||
self.debug,
|
||||
self.arrow_fonts,
|
||||
);
|
||||
new_pane.update_name("EDITING SCROLLBACK"); // we do this here and not in the
|
||||
// constructor so it won't be overrided
|
||||
|
|
@ -1183,6 +1192,7 @@ impl Tab {
|
|||
None,
|
||||
run,
|
||||
self.debug,
|
||||
self.arrow_fonts,
|
||||
);
|
||||
let replaced_pane = if self.floating_panes.panes_contain(&old_pane_id) {
|
||||
self.floating_panes
|
||||
|
|
@ -1235,6 +1245,7 @@ impl Tab {
|
|||
self.style,
|
||||
run,
|
||||
self.debug,
|
||||
self.arrow_fonts,
|
||||
);
|
||||
let replaced_pane = if self.floating_panes.panes_contain(&old_pane_id) {
|
||||
self.floating_panes
|
||||
|
|
@ -1303,6 +1314,7 @@ impl Tab {
|
|||
initial_pane_title,
|
||||
None,
|
||||
self.debug,
|
||||
self.arrow_fonts,
|
||||
);
|
||||
self.tiled_panes
|
||||
.split_pane_horizontally(pid, Box::new(new_terminal), client_id);
|
||||
|
|
@ -1360,6 +1372,7 @@ impl Tab {
|
|||
initial_pane_title,
|
||||
None,
|
||||
self.debug,
|
||||
self.arrow_fonts,
|
||||
);
|
||||
self.tiled_panes
|
||||
.split_pane_vertically(pid, Box::new(new_terminal), client_id);
|
||||
|
|
|
|||
|
|
@ -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 sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||||
let debug = false;
|
||||
let arrow_fonts = true;
|
||||
let mut tab = Tab::new(
|
||||
index,
|
||||
position,
|
||||
|
|
@ -247,6 +248,7 @@ fn create_new_tab(size: Size, default_mode: ModeInfo) -> Tab {
|
|||
(vec![], vec![]),
|
||||
None,
|
||||
debug,
|
||||
arrow_fonts,
|
||||
);
|
||||
tab.apply_layout(
|
||||
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 sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||||
let debug = false;
|
||||
let arrow_fonts = true;
|
||||
let mut tab = Tab::new(
|
||||
index,
|
||||
position,
|
||||
|
|
@ -320,6 +323,7 @@ fn create_new_tab_with_swap_layouts(
|
|||
swap_layouts,
|
||||
None,
|
||||
debug,
|
||||
arrow_fonts,
|
||||
);
|
||||
let (
|
||||
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 sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||||
let debug = false;
|
||||
let arrow_fonts = true;
|
||||
let mut tab = Tab::new(
|
||||
index,
|
||||
position,
|
||||
|
|
@ -395,6 +400,7 @@ fn create_new_tab_with_os_api(
|
|||
(vec![], vec![]), // swap layouts
|
||||
None,
|
||||
debug,
|
||||
arrow_fonts,
|
||||
);
|
||||
tab.apply_layout(
|
||||
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 (tab_layout, floating_panes_layout) = layout.new_tab();
|
||||
let debug = false;
|
||||
let arrow_fonts = true;
|
||||
let mut tab = Tab::new(
|
||||
index,
|
||||
position,
|
||||
|
|
@ -456,6 +463,7 @@ fn create_new_tab_with_layout(size: Size, default_mode: ModeInfo, layout: &str)
|
|||
(vec![], vec![]), // swap layouts
|
||||
None,
|
||||
debug,
|
||||
arrow_fonts,
|
||||
);
|
||||
let pane_ids = tab_layout
|
||||
.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 sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||||
let debug = false;
|
||||
let arrow_fonts = true;
|
||||
let mut tab = Tab::new(
|
||||
index,
|
||||
position,
|
||||
|
|
@ -531,6 +540,7 @@ fn create_new_tab_with_mock_pty_writer(
|
|||
(vec![], vec![]), // swap layouts
|
||||
None,
|
||||
debug,
|
||||
arrow_fonts,
|
||||
);
|
||||
tab.apply_layout(
|
||||
TiledPaneLayout::default(),
|
||||
|
|
@ -574,6 +584,7 @@ fn create_new_tab_with_sixel_support(
|
|||
let copy_options = CopyOptions::default();
|
||||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||||
let debug = false;
|
||||
let arrow_fonts = true;
|
||||
let mut tab = Tab::new(
|
||||
index,
|
||||
position,
|
||||
|
|
@ -597,6 +608,7 @@ fn create_new_tab_with_sixel_support(
|
|||
(vec![], vec![]), // swap layouts
|
||||
None,
|
||||
debug,
|
||||
arrow_fonts,
|
||||
);
|
||||
tab.apply_layout(
|
||||
TiledPaneLayout::default(),
|
||||
|
|
@ -633,6 +645,7 @@ fn take_snapshot(ansi_instructions: &str, rows: usize, columns: usize, palette:
|
|||
height: 21,
|
||||
})));
|
||||
let debug = false;
|
||||
let arrow_fonts = true;
|
||||
let mut grid = Grid::new(
|
||||
rows,
|
||||
columns,
|
||||
|
|
@ -641,7 +654,9 @@ fn take_snapshot(ansi_instructions: &str, rows: usize, columns: usize, palette:
|
|||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
character_cell_size,
|
||||
sixel_image_store,
|
||||
Style::default(),
|
||||
debug,
|
||||
arrow_fonts,
|
||||
);
|
||||
let mut vte_parser = vte::Parser::new();
|
||||
for &byte in ansi_instructions.as_bytes() {
|
||||
|
|
@ -663,6 +678,7 @@ fn take_snapshot_with_sixel(
|
|||
height: 21,
|
||||
})));
|
||||
let debug = false;
|
||||
let arrow_fonts = true;
|
||||
let mut grid = Grid::new(
|
||||
rows,
|
||||
columns,
|
||||
|
|
@ -671,7 +687,9 @@ fn take_snapshot_with_sixel(
|
|||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
character_cell_size,
|
||||
sixel_image_store,
|
||||
Style::default(),
|
||||
debug,
|
||||
arrow_fonts,
|
||||
);
|
||||
let mut vte_parser = vte::Parser::new();
|
||||
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 terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||||
let debug = false;
|
||||
let arrow_fonts = true;
|
||||
let mut grid = Grid::new(
|
||||
rows,
|
||||
columns,
|
||||
|
|
@ -698,7 +717,9 @@ fn take_snapshot_and_cursor_position(
|
|||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
Rc::new(RefCell::new(None)),
|
||||
sixel_image_store,
|
||||
Style::default(),
|
||||
debug,
|
||||
arrow_fonts,
|
||||
);
|
||||
let mut vte_parser = vte::Parser::new();
|
||||
for &byte in ansi_instructions.as_bytes() {
|
||||
|
|
|
|||
|
|
@ -165,6 +165,7 @@ fn create_new_tab(size: Size) -> Tab {
|
|||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||||
let debug = false;
|
||||
let arrow_fonts = true;
|
||||
let mut tab = Tab::new(
|
||||
index,
|
||||
position,
|
||||
|
|
@ -188,6 +189,7 @@ fn create_new_tab(size: Size) -> Tab {
|
|||
(vec![], vec![]), // swap layouts
|
||||
None,
|
||||
debug,
|
||||
arrow_fonts,
|
||||
);
|
||||
tab.apply_layout(
|
||||
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 terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||||
let debug = false;
|
||||
let arrow_fonts = true;
|
||||
let mut tab = Tab::new(
|
||||
index,
|
||||
position,
|
||||
|
|
@ -246,6 +249,7 @@ fn create_new_tab_with_layout(size: Size, layout: TiledPaneLayout) -> Tab {
|
|||
(vec![], vec![]), // swap layouts
|
||||
None,
|
||||
debug,
|
||||
arrow_fonts,
|
||||
);
|
||||
let mut new_terminal_ids = vec![];
|
||||
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 terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||||
let debug = false;
|
||||
let arrow_fonts = true;
|
||||
let mut tab = Tab::new(
|
||||
index,
|
||||
position,
|
||||
|
|
@ -310,6 +315,7 @@ fn create_new_tab_with_cell_size(
|
|||
(vec![], vec![]), // swap layouts
|
||||
None,
|
||||
debug,
|
||||
arrow_fonts,
|
||||
);
|
||||
tab.apply_layout(
|
||||
TiledPaneLayout::default(),
|
||||
|
|
|
|||
43
zellij-server/src/ui/components/component_coordinates.rs
Normal file
43
zellij-server/src/ui/components/component_coordinates.rs
Normal 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
|
||||
}
|
||||
187
zellij-server/src/ui/components/mod.rs
Normal file
187
zellij-server/src/ui/components/mod.rs
Normal 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()
|
||||
}
|
||||
142
zellij-server/src/ui/components/nested_list.rs
Normal file
142
zellij-server/src/ui/components/nested_list.rs
Normal 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
|
||||
}
|
||||
115
zellij-server/src/ui/components/ribbon.rs
Normal file
115
zellij-server/src/ui/components/ribbon.rs
Normal 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))
|
||||
}
|
||||
105
zellij-server/src/ui/components/table.rs
Normal file
105
zellij-server/src/ui/components/table.rs
Normal 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
|
||||
}
|
||||
166
zellij-server/src/ui/components/text.rs
Normal file
166
zellij-server/src/ui/components/text.rs
Normal 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())
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
pub mod boundaries;
|
||||
pub mod components;
|
||||
pub mod loading_indication;
|
||||
pub mod overlay;
|
||||
pub mod pane_boundaries_frame;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use crate::{
|
|||
use insta::assert_snapshot;
|
||||
use std::path::PathBuf;
|
||||
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::input::actions::Action;
|
||||
use zellij_utils::input::command::{RunCommand, TerminalAction};
|
||||
|
|
@ -67,6 +67,7 @@ fn take_snapshots_and_cursor_coordinates_from_render_events<'a>(
|
|||
height: 21,
|
||||
})));
|
||||
let debug = false;
|
||||
let arrow_fonts = true;
|
||||
let mut grid = Grid::new(
|
||||
screen_size.rows,
|
||||
screen_size.cols,
|
||||
|
|
@ -75,7 +76,9 @@ fn take_snapshots_and_cursor_coordinates_from_render_events<'a>(
|
|||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
character_cell_size,
|
||||
sixel_image_store,
|
||||
Style::default(),
|
||||
debug,
|
||||
arrow_fonts,
|
||||
);
|
||||
let snapshots: Vec<(Option<(usize, usize)>, String)> = all_events
|
||||
.filter_map(|server_instruction| {
|
||||
|
|
@ -241,6 +244,7 @@ fn create_new_screen(size: Size) -> Screen {
|
|||
let scrollback_lines_to_serialize = None;
|
||||
|
||||
let debug = false;
|
||||
let arrow_fonts = true;
|
||||
let screen = Screen::new(
|
||||
bus,
|
||||
&client_attributes,
|
||||
|
|
@ -256,6 +260,7 @@ fn create_new_screen(size: Size) -> Screen {
|
|||
session_serialization,
|
||||
serialize_pane_viewport,
|
||||
scrollback_lines_to_serialize,
|
||||
arrow_fonts,
|
||||
);
|
||||
screen
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
//!
|
||||
pub mod prelude;
|
||||
pub mod shim;
|
||||
pub mod ui_components;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ pub use zellij_utils::plugin_api;
|
|||
use zellij_utils::plugin_api::plugin_command::ProtobufPluginCommand;
|
||||
use zellij_utils::plugin_api::plugin_ids::{ProtobufPluginIds, ProtobufZellijVersion};
|
||||
|
||||
pub use super::ui_components::*;
|
||||
pub use zellij_utils::prost::{self, *};
|
||||
|
||||
// Subscription Handling
|
||||
|
|
|
|||
12
zellij-tile/src/ui_components/mod.rs
Normal file
12
zellij-tile/src/ui_components/mod.rs
Normal 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::*;
|
||||
73
zellij-tile/src/ui_components/nested_list.rs
Normal file
73
zellij-tile/src/ui_components/nested_list.rs
Normal 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
|
||||
)
|
||||
}
|
||||
24
zellij-tile/src/ui_components/ribbon.rs
Normal file
24
zellij-tile/src/ui_components/ribbon.rs
Normal 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()
|
||||
);
|
||||
}
|
||||
61
zellij-tile/src/ui_components/table.rs
Normal file
61
zellij-tile/src/ui_components/table.rs
Normal 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()
|
||||
)
|
||||
}
|
||||
107
zellij-tile/src/ui_components/text.rs
Normal file
107
zellij-tile/src/ui_components/text.rs
Normal 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()
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue