feat(ui): add floating panes (#1066)
* basic functionality * close and reopen scratch terminal working * embed/float and resize whole tab for floating and static floating panes * move focus working * fix focus change in floating panes * move pane with mouse * floating z indices * tests and better resize algorithm * starting to work on performance * some performance experimentations * new render engine * reverse painters algorithm for floating panes * fix frame buffering * improve ux situation * handle multiple new panes on screen without overlap * adjust keybindings * adjust key hints * fix multiuser frame ui * fix various floating/multiuser bugs * remove stuff * wide characters under floating panes * fix wide character frame override * fix non-frame boundaries interactions with floating panes * fix selection character width * fix title frame wide char overflow * fix existing tests * add tests * refactor output out of tab * refactor floating panes out of tab * refactor tab * moar refactoring * refactorings and bring back terminal window title setting * add frame vte output * remove more unused stuff * remove even more unused stuff * you know the drill * refactor floating panes and remove more stuffs * refactor pane grids * remove unused output caching * refactor output * remove unused stuff * rustfmt * some formatting * rustfmt * reduce clippy to normal * remove comment * remove unused * fix closign pane * fix tests
This commit is contained in:
parent
10a22c479f
commit
821e7cbc5a
61 changed files with 6808 additions and 1250 deletions
2
.github/workflows/rust.yml
vendored
2
.github/workflows/rust.yml
vendored
|
|
@ -75,4 +75,4 @@ jobs:
|
|||
- name: Install cargo-make
|
||||
run: test -x "${HOME}/.cargo/bin/cargo-make" || cargo install --debug cargo-make
|
||||
- name: Check Lints
|
||||
run: cargo make clippy -D clippy::all
|
||||
run: cargo make clippy
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ pub const MOVE_FOCUS_IN_PANE_MODE: [u8; 1] = [112]; // p
|
|||
pub const SPLIT_DOWN_IN_PANE_MODE: [u8; 1] = [100]; // d
|
||||
pub const SPLIT_RIGHT_IN_PANE_MODE: [u8; 1] = [114]; // r
|
||||
pub const TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE: [u8; 1] = [102]; // f
|
||||
pub const TOGGLE_FLOATING_PANES: [u8; 1] = [119]; // w
|
||||
pub const CLOSE_PANE_IN_PANE_MODE: [u8; 1] = [120]; // x
|
||||
pub const MOVE_FOCUS_DOWN_IN_PANE_MODE: [u8; 1] = [106]; // j
|
||||
pub const MOVE_FOCUS_UP_IN_PANE_MODE: [u8; 1] = [107]; // k
|
||||
|
|
@ -1664,6 +1665,53 @@ pub fn bracketed_paste() {
|
|||
assert_snapshot!(last_snapshot);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
pub fn toggle_floating_panes() {
|
||||
let fake_win_size = Size {
|
||||
cols: 120,
|
||||
rows: 24,
|
||||
};
|
||||
|
||||
let mut test_attempts = 10;
|
||||
let last_snapshot = loop {
|
||||
RemoteRunner::kill_running_sessions(fake_win_size);
|
||||
let mut runner = RemoteRunner::new(fake_win_size).add_step(Step {
|
||||
name: "Toggle floating panes",
|
||||
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(3, 2)
|
||||
{
|
||||
remote_terminal.send_key(&PANE_MODE);
|
||||
remote_terminal.send_key(&TOGGLE_FLOATING_PANES);
|
||||
// back to normal mode after split
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
});
|
||||
runner.run_all_steps();
|
||||
let last_snapshot = runner.take_snapshot_after(Step {
|
||||
name: "Wait for new pane to appear",
|
||||
instruction: |remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.cursor_position_is(33, 7) && remote_terminal.tip_appears() {
|
||||
// cursor is in the newly opened second pane
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
});
|
||||
if runner.test_timed_out && test_attempts > 0 {
|
||||
test_attempts -= 1;
|
||||
continue;
|
||||
} else {
|
||||
break last_snapshot;
|
||||
}
|
||||
};
|
||||
assert_snapshot!(last_snapshot);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
pub fn focus_tab_with_layout() {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
|||
use std::sync::{Arc, Mutex};
|
||||
use zellij_tile::data::Palette;
|
||||
|
||||
use zellij_server::panes::TerminalPane;
|
||||
use zellij_server::panes::{LinkHandler, TerminalPane};
|
||||
use zellij_utils::pane_size::{Dimension, PaneGeom, Size};
|
||||
use zellij_utils::{vte, zellij_tile};
|
||||
|
||||
|
|
@ -12,6 +12,9 @@ use std::net::TcpStream;
|
|||
|
||||
use std::path::Path;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
const ZELLIJ_EXECUTABLE_LOCATION: &str = "/usr/src/zellij/x86_64-unknown-linux-musl/release/zellij";
|
||||
const ZELLIJ_LAYOUT_PATH: &str = "/usr/src/zellij/fixtures/layouts";
|
||||
const CONNECTION_STRING: &str = "127.0.0.1:2222";
|
||||
|
|
@ -141,16 +144,23 @@ fn read_from_channel(
|
|||
let thread = std::thread::Builder::new()
|
||||
.name("read_thread".into())
|
||||
.spawn({
|
||||
let pane_geom = *pane_geom;
|
||||
let should_keep_running = should_keep_running.clone();
|
||||
let channel = channel.clone();
|
||||
let last_snapshot = last_snapshot.clone();
|
||||
let cursor_coordinates = cursor_coordinates.clone();
|
||||
let mut vte_parser = vte::Parser::new();
|
||||
let mut terminal_output =
|
||||
TerminalPane::new(0, *pane_geom, Palette::default(), 0, String::new()); // 0 is the pane index
|
||||
let mut retries_left = 3;
|
||||
move || {
|
||||
let mut retries_left = 3;
|
||||
let mut should_sleep = false;
|
||||
let mut vte_parser = vte::Parser::new();
|
||||
let mut terminal_output = TerminalPane::new(
|
||||
0,
|
||||
pane_geom,
|
||||
Palette::default(),
|
||||
0,
|
||||
String::new(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
); // 0 is the pane index
|
||||
loop {
|
||||
if !should_keep_running.load(Ordering::SeqCst) {
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
source: src/tests/e2e/cases.rs
|
||||
expression: last_snapshot
|
||||
|
||||
---
|
||||
Zellij (e2e-test) Tab #1
|
||||
┌ Pane #1 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│$ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ ┌ Pane #2 ─────────────────────────────────────────────────┐ │
|
||||
│ │$ █ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SCROLL <o> SESSION <q> QUIT
|
||||
Tip: Alt + <n> => new pane. Alt + <[] or hjkl> => navigate. Alt + <+-> => resize pane.
|
||||
|
|
@ -199,6 +199,8 @@ impl InputHandler {
|
|||
}
|
||||
Action::CloseFocus
|
||||
| Action::NewPane(_)
|
||||
| Action::ToggleFloatingPanes
|
||||
| Action::TogglePaneEmbedOrFloating
|
||||
| Action::NewTab(_)
|
||||
| Action::GoToNextTab
|
||||
| Action::GoToPreviousTab
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
pub mod os_input_output;
|
||||
pub mod output;
|
||||
pub mod panes;
|
||||
pub mod tab;
|
||||
|
||||
|
|
@ -29,7 +30,6 @@ use crate::{
|
|||
os_input_output::ServerOsApi,
|
||||
pty::{pty_thread_main, Pty, PtyInstruction},
|
||||
screen::{screen_thread_main, ScreenInstruction},
|
||||
tab::Output,
|
||||
thread_bus::{Bus, ThreadSenders},
|
||||
wasm_vm::{wasm_thread_main, PluginInstruction},
|
||||
};
|
||||
|
|
@ -63,7 +63,7 @@ pub enum ServerInstruction {
|
|||
ClientId,
|
||||
Option<PluginsConfig>,
|
||||
),
|
||||
Render(Option<Output>),
|
||||
Render(Option<HashMap<ClientId, String>>),
|
||||
UnblockInputThread,
|
||||
ClientExit(ClientId),
|
||||
RemoveClient(ClientId),
|
||||
|
|
@ -492,14 +492,12 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
|
|||
.send_to_plugin(PluginInstruction::RemoveClient(client_id))
|
||||
.unwrap();
|
||||
}
|
||||
ServerInstruction::Render(mut output) => {
|
||||
ServerInstruction::Render(serialized_output) => {
|
||||
let client_ids = session_state.read().unwrap().client_ids();
|
||||
// Here the output is of the type Option<String> sent by screen thread.
|
||||
// If `Some(_)`- unwrap it and forward it to the clients to render.
|
||||
// If `None`- Send an exit instruction. This is the case when a user closes the last Tab/Pane.
|
||||
if let Some(op) = &mut output {
|
||||
for (client_id, client_render_instruction) in &mut op.client_render_instructions
|
||||
{
|
||||
if let Some(output) = &serialized_output {
|
||||
for (client_id, client_render_instruction) in output.iter() {
|
||||
os_input.send_to_client(
|
||||
*client_id,
|
||||
ServerToClientMsg::Render(client_render_instruction.clone()),
|
||||
|
|
|
|||
571
zellij-server/src/output/mod.rs
Normal file
571
zellij-server/src/output/mod.rs
Normal file
|
|
@ -0,0 +1,571 @@
|
|||
use std::collections::VecDeque;
|
||||
|
||||
use crate::panes::selection::Selection;
|
||||
use crate::panes::Row;
|
||||
|
||||
use crate::{
|
||||
panes::terminal_character::{AnsiCode, CharacterStyles},
|
||||
panes::{LinkHandler, TerminalCharacter, EMPTY_TERMINAL_CHARACTER},
|
||||
ClientId,
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
use std::fmt::Write;
|
||||
use std::rc::Rc;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
str,
|
||||
};
|
||||
use zellij_utils::pane_size::PaneGeom;
|
||||
|
||||
fn vte_goto_instruction(x_coords: usize, y_coords: usize, vte_output: &mut String) {
|
||||
write!(
|
||||
vte_output,
|
||||
"\u{1b}[{};{}H\u{1b}[m",
|
||||
y_coords + 1, // + 1 because VTE is 1 indexed
|
||||
x_coords + 1,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn adjust_styles_for_possible_selection(
|
||||
chunk_selection_and_background_color: Option<(Selection, AnsiCode)>,
|
||||
character_styles: CharacterStyles,
|
||||
chunk_y: usize,
|
||||
chunk_width: usize,
|
||||
) -> CharacterStyles {
|
||||
chunk_selection_and_background_color
|
||||
.and_then(|(selection, background_color)| {
|
||||
if selection.contains(chunk_y, chunk_width) {
|
||||
Some(character_styles.background(Some(background_color)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or(character_styles)
|
||||
}
|
||||
|
||||
fn write_changed_styles(
|
||||
character_styles: &mut CharacterStyles,
|
||||
current_character_styles: CharacterStyles,
|
||||
chunk_changed_colors: Option<[Option<AnsiCode>; 256]>,
|
||||
link_handler: Option<&std::cell::Ref<LinkHandler>>,
|
||||
vte_output: &mut String,
|
||||
) {
|
||||
if let Some(new_styles) =
|
||||
character_styles.update_and_return_diff(¤t_character_styles, chunk_changed_colors)
|
||||
{
|
||||
// if let Some(osc8_link) = link_handler.as_ref().and_then(|l_h| l_h.borrow().output_osc8(new_styles.link_anchor)) {
|
||||
if let Some(osc8_link) =
|
||||
link_handler.and_then(|l_h| l_h.output_osc8(new_styles.link_anchor))
|
||||
{
|
||||
write!(vte_output, "{}{}", new_styles, osc8_link).unwrap();
|
||||
} else {
|
||||
write!(vte_output, "{}", new_styles).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_character_chunks(
|
||||
character_chunks: Vec<CharacterChunk>,
|
||||
link_handler: Option<&mut Rc<RefCell<LinkHandler>>>,
|
||||
) -> String {
|
||||
let mut vte_output = String::new(); // TODO: preallocate character_chunks.len()?
|
||||
let link_handler = link_handler.map(|l_h| l_h.borrow());
|
||||
for character_chunk in character_chunks {
|
||||
let chunk_selection_and_background_color = character_chunk.selection_and_background_color();
|
||||
let chunk_changed_colors = character_chunk.changed_colors();
|
||||
let mut character_styles = CharacterStyles::new();
|
||||
vte_goto_instruction(character_chunk.x, character_chunk.y, &mut vte_output);
|
||||
let mut chunk_width = character_chunk.x;
|
||||
for t_character in character_chunk.terminal_characters.iter() {
|
||||
let current_character_styles = adjust_styles_for_possible_selection(
|
||||
chunk_selection_and_background_color,
|
||||
t_character.styles,
|
||||
character_chunk.y,
|
||||
chunk_width,
|
||||
);
|
||||
write_changed_styles(
|
||||
&mut character_styles,
|
||||
current_character_styles,
|
||||
chunk_changed_colors,
|
||||
link_handler.as_ref(),
|
||||
&mut vte_output,
|
||||
);
|
||||
chunk_width += t_character.width;
|
||||
vte_output.push(t_character.character);
|
||||
}
|
||||
character_styles.clear();
|
||||
}
|
||||
vte_output
|
||||
}
|
||||
|
||||
type AbsoluteMiddleStart = usize;
|
||||
type AbsoluteMiddleEnd = usize;
|
||||
type PadLeftEndBy = usize;
|
||||
type PadRightStartBy = usize;
|
||||
fn adjust_middle_segment_for_wide_chars(
|
||||
middle_start: usize,
|
||||
middle_end: usize,
|
||||
terminal_characters: &[TerminalCharacter],
|
||||
) -> (
|
||||
AbsoluteMiddleStart,
|
||||
AbsoluteMiddleEnd,
|
||||
PadLeftEndBy,
|
||||
PadRightStartBy,
|
||||
) {
|
||||
let mut absolute_middle_start_index = None;
|
||||
let mut absolute_middle_end_index = None;
|
||||
let mut current_x = 0;
|
||||
let mut pad_left_end_by = 0;
|
||||
let mut pad_right_start_by = 0;
|
||||
for (absolute_index, t_character) in terminal_characters.iter().enumerate() {
|
||||
current_x += t_character.width;
|
||||
if current_x >= middle_start && absolute_middle_start_index.is_none() {
|
||||
if current_x > middle_start {
|
||||
pad_left_end_by = current_x - middle_start;
|
||||
absolute_middle_start_index = Some(absolute_index);
|
||||
} else {
|
||||
absolute_middle_start_index = Some(absolute_index + 1);
|
||||
}
|
||||
}
|
||||
if current_x >= middle_end && absolute_middle_end_index.is_none() {
|
||||
absolute_middle_end_index = Some(absolute_index + 1);
|
||||
if current_x > middle_end {
|
||||
pad_right_start_by = current_x - middle_end;
|
||||
}
|
||||
}
|
||||
}
|
||||
(
|
||||
absolute_middle_start_index.unwrap(),
|
||||
absolute_middle_end_index.unwrap(),
|
||||
pad_left_end_by,
|
||||
pad_right_start_by,
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Output {
|
||||
pre_vte_instructions: HashMap<ClientId, Vec<String>>,
|
||||
post_vte_instructions: HashMap<ClientId, Vec<String>>,
|
||||
client_character_chunks: HashMap<ClientId, Vec<CharacterChunk>>,
|
||||
link_handler: Option<Rc<RefCell<LinkHandler>>>,
|
||||
floating_panes_stack: Option<FloatingPanesStack>,
|
||||
}
|
||||
|
||||
impl Output {
|
||||
pub fn add_clients(
|
||||
&mut self,
|
||||
client_ids: &HashSet<ClientId>,
|
||||
link_handler: Rc<RefCell<LinkHandler>>,
|
||||
floating_panes_stack: Option<FloatingPanesStack>,
|
||||
) {
|
||||
self.link_handler = Some(link_handler);
|
||||
self.floating_panes_stack = floating_panes_stack;
|
||||
for client_id in client_ids {
|
||||
self.client_character_chunks.insert(*client_id, vec![]);
|
||||
}
|
||||
}
|
||||
pub fn add_character_chunks_to_client(
|
||||
&mut self,
|
||||
client_id: ClientId,
|
||||
mut character_chunks: Vec<CharacterChunk>,
|
||||
z_index: Option<usize>,
|
||||
) {
|
||||
if let Some(client_character_chunks) = self.client_character_chunks.get_mut(&client_id) {
|
||||
if let Some(floating_panes_stack) = &self.floating_panes_stack {
|
||||
let mut visible_character_chunks =
|
||||
floating_panes_stack.visible_character_chunks(character_chunks, z_index);
|
||||
client_character_chunks.append(&mut visible_character_chunks);
|
||||
} else {
|
||||
client_character_chunks.append(&mut character_chunks);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn add_character_chunks_to_multiple_clients(
|
||||
&mut self,
|
||||
character_chunks: Vec<CharacterChunk>,
|
||||
client_ids: impl Iterator<Item = ClientId>,
|
||||
z_index: Option<usize>,
|
||||
) {
|
||||
for client_id in client_ids {
|
||||
self.add_character_chunks_to_client(client_id, character_chunks.clone(), z_index);
|
||||
// TODO: forgo clone by adding an all_clients thing?
|
||||
}
|
||||
}
|
||||
pub fn add_post_vte_instruction_to_multiple_clients(
|
||||
&mut self,
|
||||
client_ids: impl Iterator<Item = ClientId>,
|
||||
vte_instruction: &str,
|
||||
) {
|
||||
for client_id in client_ids {
|
||||
let entry = self
|
||||
.post_vte_instructions
|
||||
.entry(client_id)
|
||||
.or_insert_with(Vec::new);
|
||||
entry.push(String::from(vte_instruction));
|
||||
}
|
||||
}
|
||||
pub fn add_pre_vte_instruction_to_multiple_clients(
|
||||
&mut self,
|
||||
client_ids: impl Iterator<Item = ClientId>,
|
||||
vte_instruction: &str,
|
||||
) {
|
||||
for client_id in client_ids {
|
||||
let entry = self
|
||||
.pre_vte_instructions
|
||||
.entry(client_id)
|
||||
.or_insert_with(Vec::new);
|
||||
entry.push(String::from(vte_instruction));
|
||||
}
|
||||
}
|
||||
pub fn add_post_vte_instruction_to_client(
|
||||
&mut self,
|
||||
client_id: ClientId,
|
||||
vte_instruction: &str,
|
||||
) {
|
||||
let entry = self
|
||||
.post_vte_instructions
|
||||
.entry(client_id)
|
||||
.or_insert_with(Vec::new);
|
||||
entry.push(String::from(vte_instruction));
|
||||
}
|
||||
pub fn add_pre_vte_instruction_to_client(
|
||||
&mut self,
|
||||
client_id: ClientId,
|
||||
vte_instruction: &str,
|
||||
) {
|
||||
let entry = self
|
||||
.pre_vte_instructions
|
||||
.entry(client_id)
|
||||
.or_insert_with(Vec::new);
|
||||
entry.push(String::from(vte_instruction));
|
||||
}
|
||||
pub fn serialize(&mut self) -> HashMap<ClientId, String> {
|
||||
let mut serialized_render_instructions = HashMap::new();
|
||||
|
||||
for (client_id, client_character_chunks) in self.client_character_chunks.drain() {
|
||||
let mut client_serialized_render_instructions = String::new();
|
||||
|
||||
// append pre-vte instructions for this client
|
||||
if let Some(pre_vte_instructions_for_client) =
|
||||
self.pre_vte_instructions.remove(&client_id)
|
||||
{
|
||||
for vte_instruction in pre_vte_instructions_for_client {
|
||||
client_serialized_render_instructions.push_str(&vte_instruction);
|
||||
}
|
||||
}
|
||||
|
||||
// append the actual vte
|
||||
client_serialized_render_instructions.push_str(&serialize_character_chunks(
|
||||
client_character_chunks,
|
||||
self.link_handler.as_mut(),
|
||||
)); // TODO: less allocations?
|
||||
|
||||
// append post-vte instructions for this client
|
||||
if let Some(post_vte_instructions_for_client) =
|
||||
self.post_vte_instructions.remove(&client_id)
|
||||
{
|
||||
for vte_instruction in post_vte_instructions_for_client {
|
||||
client_serialized_render_instructions.push_str(&vte_instruction);
|
||||
}
|
||||
}
|
||||
|
||||
serialized_render_instructions.insert(client_id, client_serialized_render_instructions);
|
||||
}
|
||||
serialized_render_instructions
|
||||
}
|
||||
}
|
||||
|
||||
// this struct represents the geometry of a group of floating panes
|
||||
// we use it to filter out CharacterChunks who are behind these geometries
|
||||
// and so would not be visible. If a chunk is partially covered, it is adjusted
|
||||
// to include only the non-covered parts
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct FloatingPanesStack {
|
||||
pub layers: Vec<PaneGeom>,
|
||||
}
|
||||
|
||||
impl FloatingPanesStack {
|
||||
pub fn visible_character_chunks(
|
||||
&self,
|
||||
mut character_chunks: Vec<CharacterChunk>,
|
||||
z_index: Option<usize>,
|
||||
) -> Vec<CharacterChunk> {
|
||||
let z_index = z_index.unwrap_or(0);
|
||||
let mut chunks_to_check: Vec<CharacterChunk> = character_chunks.drain(..).collect();
|
||||
let mut visible_chunks = vec![];
|
||||
'chunk_loop: loop {
|
||||
match chunks_to_check.pop() {
|
||||
Some(mut c_chunk) => {
|
||||
let panes_to_check = self.layers.iter().skip(z_index);
|
||||
for pane_geom in panes_to_check {
|
||||
let new_chunk_to_check = self.remove_covered_parts(pane_geom, &mut c_chunk);
|
||||
if let Some(new_chunk_to_check) = new_chunk_to_check {
|
||||
// this happens when the pane covers the middle of the chunk, and so we
|
||||
// end up with an extra chunk we need to check (eg. against panes above
|
||||
// this one)
|
||||
chunks_to_check.push(new_chunk_to_check);
|
||||
}
|
||||
if c_chunk.terminal_characters.is_empty() {
|
||||
continue 'chunk_loop;
|
||||
}
|
||||
}
|
||||
visible_chunks.push(c_chunk);
|
||||
}
|
||||
None => {
|
||||
break 'chunk_loop;
|
||||
}
|
||||
}
|
||||
}
|
||||
visible_chunks
|
||||
}
|
||||
fn remove_covered_parts(
|
||||
&self,
|
||||
pane_geom: &PaneGeom,
|
||||
c_chunk: &mut CharacterChunk,
|
||||
) -> Option<CharacterChunk> {
|
||||
let pane_top_edge = pane_geom.y;
|
||||
let pane_left_edge = pane_geom.x;
|
||||
let pane_bottom_edge = pane_geom.y + pane_geom.rows.as_usize().saturating_sub(1);
|
||||
let pane_right_edge = pane_geom.x + pane_geom.cols.as_usize().saturating_sub(1);
|
||||
let c_chunk_left_side = c_chunk.x;
|
||||
let c_chunk_right_side = c_chunk.x + (c_chunk.width()).saturating_sub(1);
|
||||
if pane_top_edge <= c_chunk.y && pane_bottom_edge >= c_chunk.y {
|
||||
if pane_left_edge <= c_chunk_left_side && pane_right_edge >= c_chunk_right_side {
|
||||
// pane covers chunk completely
|
||||
drop(c_chunk.terminal_characters.drain(..));
|
||||
return None;
|
||||
} else if pane_right_edge > c_chunk_left_side
|
||||
&& pane_right_edge < c_chunk_right_side
|
||||
&& pane_left_edge <= c_chunk_left_side
|
||||
{
|
||||
// pane covers chunk partially to the left
|
||||
let covered_part = c_chunk.drain_by_width(pane_right_edge + 1 - c_chunk_left_side);
|
||||
drop(covered_part);
|
||||
c_chunk.x = pane_right_edge + 1;
|
||||
return None;
|
||||
} else if pane_left_edge > c_chunk_left_side
|
||||
&& pane_left_edge < c_chunk_right_side
|
||||
&& pane_right_edge >= c_chunk_right_side
|
||||
{
|
||||
// pane covers chunk partially to the right
|
||||
c_chunk.retain_by_width(pane_left_edge - c_chunk_left_side);
|
||||
return None;
|
||||
} else if pane_left_edge >= c_chunk_left_side && pane_right_edge <= c_chunk_right_side {
|
||||
// pane covers chunk middle
|
||||
let (left_chunk_characters, right_chunk_characters) = c_chunk.cut_middle_out(
|
||||
pane_left_edge - c_chunk_left_side,
|
||||
(pane_right_edge + 1) - c_chunk_left_side,
|
||||
);
|
||||
let left_chunk_x = c_chunk_left_side;
|
||||
let right_chunk_x = pane_right_edge + 1;
|
||||
let left_chunk =
|
||||
CharacterChunk::new(left_chunk_characters, left_chunk_x, c_chunk.y);
|
||||
c_chunk.x = right_chunk_x;
|
||||
c_chunk.terminal_characters = right_chunk_characters;
|
||||
return Some(left_chunk);
|
||||
}
|
||||
};
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct CharacterChunk {
|
||||
pub terminal_characters: Vec<TerminalCharacter>,
|
||||
pub x: usize,
|
||||
pub y: usize,
|
||||
pub changed_colors: Option<[Option<AnsiCode>; 256]>,
|
||||
selection_and_background_color: Option<(Selection, AnsiCode)>,
|
||||
}
|
||||
|
||||
impl CharacterChunk {
|
||||
pub fn new(terminal_characters: Vec<TerminalCharacter>, x: usize, y: usize) -> Self {
|
||||
CharacterChunk {
|
||||
terminal_characters,
|
||||
x,
|
||||
y,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
pub fn add_selection_and_background(
|
||||
&mut self,
|
||||
selection: Selection,
|
||||
background_color: AnsiCode,
|
||||
offset_x: usize,
|
||||
offset_y: usize,
|
||||
) {
|
||||
self.selection_and_background_color =
|
||||
Some((selection.offset(offset_x, offset_y), background_color));
|
||||
}
|
||||
pub fn selection_and_background_color(&self) -> Option<(Selection, AnsiCode)> {
|
||||
self.selection_and_background_color
|
||||
}
|
||||
pub fn add_changed_colors(&mut self, changed_colors: Option<[Option<AnsiCode>; 256]>) {
|
||||
self.changed_colors = changed_colors;
|
||||
}
|
||||
pub fn changed_colors(&self) -> Option<[Option<AnsiCode>; 256]> {
|
||||
self.changed_colors
|
||||
}
|
||||
pub fn width(&self) -> usize {
|
||||
let mut width = 0;
|
||||
for t_character in &self.terminal_characters {
|
||||
width += t_character.width
|
||||
}
|
||||
width
|
||||
}
|
||||
pub fn drain_by_width(&mut self, x: usize) -> impl Iterator<Item = TerminalCharacter> {
|
||||
let mut drained_part: VecDeque<TerminalCharacter> = VecDeque::new();
|
||||
let mut drained_part_len = 0;
|
||||
loop {
|
||||
if self.terminal_characters.is_empty() {
|
||||
break;
|
||||
}
|
||||
let next_character = self.terminal_characters.remove(0); // TODO: consider copying self.terminal_characters into a VecDeque to make this process faster?
|
||||
if drained_part_len + next_character.width <= x {
|
||||
drained_part.push_back(next_character);
|
||||
drained_part_len += next_character.width;
|
||||
} else {
|
||||
if drained_part_len == x {
|
||||
self.terminal_characters.insert(0, next_character); // put it back
|
||||
} else if next_character.width > 1 {
|
||||
for _ in 1..next_character.width {
|
||||
self.terminal_characters.insert(0, EMPTY_TERMINAL_CHARACTER);
|
||||
drained_part.push_back(EMPTY_TERMINAL_CHARACTER);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
drained_part.into_iter()
|
||||
}
|
||||
pub fn retain_by_width(&mut self, x: usize) {
|
||||
let part_to_retain = self.drain_by_width(x);
|
||||
self.terminal_characters = part_to_retain.collect();
|
||||
}
|
||||
pub fn cut_middle_out(
|
||||
&mut self,
|
||||
middle_start: usize,
|
||||
middle_end: usize,
|
||||
) -> (Vec<TerminalCharacter>, Vec<TerminalCharacter>) {
|
||||
let (
|
||||
absolute_middle_start_index,
|
||||
absolute_middle_end_index,
|
||||
pad_left_end_by,
|
||||
pad_right_start_by,
|
||||
) = adjust_middle_segment_for_wide_chars(
|
||||
middle_start,
|
||||
middle_end,
|
||||
&self.terminal_characters,
|
||||
);
|
||||
let mut terminal_characters: Vec<TerminalCharacter> =
|
||||
self.terminal_characters.drain(..).collect();
|
||||
let mut characters_on_the_right: Vec<TerminalCharacter> = terminal_characters
|
||||
.drain(absolute_middle_end_index..)
|
||||
.collect();
|
||||
let mut characters_on_the_left: Vec<TerminalCharacter> = terminal_characters
|
||||
.drain(..absolute_middle_start_index)
|
||||
.collect();
|
||||
if pad_left_end_by > 0 {
|
||||
characters_on_the_left.resize(pad_left_end_by, EMPTY_TERMINAL_CHARACTER);
|
||||
}
|
||||
if pad_right_start_by > 0 {
|
||||
for _ in 0..pad_right_start_by {
|
||||
characters_on_the_right.insert(0, EMPTY_TERMINAL_CHARACTER);
|
||||
}
|
||||
}
|
||||
(characters_on_the_left, characters_on_the_right)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct OutputBuffer {
|
||||
changed_lines: Vec<usize>, // line index
|
||||
should_update_all_lines: bool,
|
||||
}
|
||||
|
||||
impl Default for OutputBuffer {
|
||||
fn default() -> Self {
|
||||
OutputBuffer {
|
||||
changed_lines: vec![],
|
||||
should_update_all_lines: true, // first time we should do a full render
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OutputBuffer {
|
||||
pub fn update_line(&mut self, line_index: usize) {
|
||||
if !self.should_update_all_lines {
|
||||
self.changed_lines.push(line_index);
|
||||
}
|
||||
}
|
||||
pub fn update_all_lines(&mut self) {
|
||||
self.clear();
|
||||
self.should_update_all_lines = true;
|
||||
}
|
||||
pub fn clear(&mut self) {
|
||||
self.changed_lines.clear();
|
||||
self.should_update_all_lines = false;
|
||||
}
|
||||
pub fn changed_chunks_in_viewport(
|
||||
&self,
|
||||
viewport: &[Row],
|
||||
viewport_width: usize,
|
||||
viewport_height: usize,
|
||||
x_offset: usize,
|
||||
y_offset: usize,
|
||||
) -> Vec<CharacterChunk> {
|
||||
if self.should_update_all_lines {
|
||||
let mut changed_chunks = Vec::with_capacity(viewport.len());
|
||||
for line_index in 0..viewport_height {
|
||||
let terminal_characters =
|
||||
self.extract_line_from_viewport(line_index, viewport, viewport_width);
|
||||
let x = x_offset; // right now we only buffer full lines as this doesn't seem to have a huge impact on performance, but the infra is here if we want to change this
|
||||
let y = line_index + y_offset;
|
||||
changed_chunks.push(CharacterChunk::new(terminal_characters, x, y));
|
||||
}
|
||||
changed_chunks
|
||||
} else {
|
||||
let mut line_changes = self.changed_lines.to_vec();
|
||||
line_changes.sort_unstable();
|
||||
line_changes.dedup();
|
||||
let mut changed_chunks = Vec::with_capacity(line_changes.len());
|
||||
for line_index in line_changes {
|
||||
let terminal_characters =
|
||||
self.extract_line_from_viewport(line_index, viewport, viewport_width);
|
||||
let x = x_offset;
|
||||
let y = line_index + y_offset;
|
||||
changed_chunks.push(CharacterChunk::new(terminal_characters, x, y));
|
||||
}
|
||||
changed_chunks
|
||||
}
|
||||
}
|
||||
fn extract_characters_from_row(
|
||||
&self,
|
||||
row: &Row,
|
||||
viewport_width: usize,
|
||||
) -> Vec<TerminalCharacter> {
|
||||
let mut terminal_characters: Vec<TerminalCharacter> = row.columns.iter().copied().collect();
|
||||
// pad row
|
||||
let row_width = row.width();
|
||||
if row_width < viewport_width {
|
||||
let mut padding = vec![EMPTY_TERMINAL_CHARACTER; viewport_width - row_width];
|
||||
terminal_characters.append(&mut padding);
|
||||
}
|
||||
terminal_characters
|
||||
}
|
||||
fn extract_line_from_viewport(
|
||||
&self,
|
||||
line_index: usize,
|
||||
viewport: &[Row],
|
||||
viewport_width: usize,
|
||||
) -> Vec<TerminalCharacter> {
|
||||
match viewport.get(line_index) {
|
||||
// TODO: iterator?
|
||||
Some(row) => self.extract_characters_from_row(row, viewport_width),
|
||||
None => {
|
||||
vec![EMPTY_TERMINAL_CHARACTER; viewport_width]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
813
zellij-server/src/panes/floating_panes.rs
Normal file
813
zellij-server/src/panes/floating_panes.rs
Normal file
|
|
@ -0,0 +1,813 @@
|
|||
use zellij_utils::{position::Position, zellij_tile};
|
||||
|
||||
use crate::tab::floating_pane_grid::FloatingPaneGrid;
|
||||
use crate::tab::Pane;
|
||||
|
||||
use crate::{
|
||||
os_input_output::ServerOsApi,
|
||||
output::{FloatingPanesStack, Output},
|
||||
panes::PaneId,
|
||||
ui::pane_contents_and_ui::PaneContentsAndUi,
|
||||
ClientId,
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
use std::rc::Rc;
|
||||
use zellij_tile::data::{ModeInfo, Palette};
|
||||
use zellij_utils::pane_size::{Offset, PaneGeom, Size, Viewport};
|
||||
|
||||
macro_rules! resize_pty {
|
||||
($pane:expr, $os_input:expr) => {
|
||||
if let PaneId::Terminal(ref pid) = $pane.pid() {
|
||||
// FIXME: This `set_terminal_size_using_fd` call would be best in
|
||||
// `TerminalPane::reflow_lines`
|
||||
$os_input.set_terminal_size_using_fd(
|
||||
*pid,
|
||||
$pane.get_content_columns() as u16,
|
||||
$pane.get_content_rows() as u16,
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub struct FloatingPanes {
|
||||
panes: BTreeMap<PaneId, Box<dyn Pane>>,
|
||||
display_area: Rc<RefCell<Size>>,
|
||||
viewport: Rc<RefCell<Viewport>>,
|
||||
desired_pane_positions: HashMap<PaneId, PaneGeom>, // this represents the positions of panes the user moved with intention, rather than by resizing the terminal window
|
||||
z_indices: Vec<PaneId>,
|
||||
active_panes: HashMap<ClientId, PaneId>,
|
||||
show_panes: bool,
|
||||
pane_being_moved_with_mouse: Option<(PaneId, Position)>,
|
||||
}
|
||||
|
||||
#[allow(clippy::borrowed_box)]
|
||||
impl FloatingPanes {
|
||||
pub fn new(display_area: Rc<RefCell<Size>>, viewport: Rc<RefCell<Viewport>>) -> Self {
|
||||
FloatingPanes {
|
||||
panes: BTreeMap::new(),
|
||||
display_area,
|
||||
viewport,
|
||||
desired_pane_positions: HashMap::new(),
|
||||
z_indices: vec![],
|
||||
show_panes: false,
|
||||
active_panes: HashMap::new(),
|
||||
pane_being_moved_with_mouse: None,
|
||||
}
|
||||
}
|
||||
pub fn stack(&self) -> FloatingPanesStack {
|
||||
let layers = self
|
||||
.z_indices
|
||||
.iter()
|
||||
.map(|pane_id| self.panes.get(pane_id).unwrap().position_and_size())
|
||||
.collect();
|
||||
FloatingPanesStack { layers }
|
||||
}
|
||||
pub fn pane_ids(&self) -> impl Iterator<Item = &PaneId> {
|
||||
self.panes.keys()
|
||||
}
|
||||
pub fn add_pane(&mut self, pane_id: PaneId, pane: Box<dyn Pane>) {
|
||||
self.desired_pane_positions
|
||||
.insert(pane_id, pane.position_and_size());
|
||||
self.panes.insert(pane_id, pane);
|
||||
self.z_indices.push(pane_id);
|
||||
}
|
||||
pub fn remove_pane(&mut self, pane_id: PaneId) -> Option<Box<dyn Pane>> {
|
||||
self.z_indices.retain(|p_id| *p_id != pane_id);
|
||||
self.desired_pane_positions.remove(&pane_id);
|
||||
self.panes.remove(&pane_id)
|
||||
}
|
||||
pub fn get(&self, pane_id: &PaneId) -> Option<&Box<dyn Pane>> {
|
||||
self.panes.get(pane_id)
|
||||
}
|
||||
pub fn get_mut(&mut self, pane_id: &PaneId) -> Option<&mut Box<dyn Pane>> {
|
||||
self.panes.get_mut(pane_id)
|
||||
}
|
||||
pub fn get_active_pane(&self, client_id: ClientId) -> Option<&Box<dyn Pane>> {
|
||||
self.active_panes
|
||||
.get(&client_id)
|
||||
.and_then(|active_pane_id| self.panes.get(active_pane_id))
|
||||
}
|
||||
pub fn get_active_pane_mut(&mut self, client_id: ClientId) -> Option<&mut Box<dyn Pane>> {
|
||||
self.active_panes
|
||||
.get(&client_id)
|
||||
.and_then(|active_pane_id| self.panes.get_mut(active_pane_id))
|
||||
}
|
||||
pub fn panes_are_visible(&self) -> bool {
|
||||
self.show_panes
|
||||
}
|
||||
pub fn has_active_panes(&self) -> bool {
|
||||
!self.active_panes.is_empty()
|
||||
}
|
||||
pub fn has_panes(&self) -> bool {
|
||||
!self.panes.is_empty()
|
||||
}
|
||||
pub fn active_pane_id(&self, client_id: ClientId) -> Option<PaneId> {
|
||||
self.active_panes.get(&client_id).copied()
|
||||
}
|
||||
pub fn toggle_show_panes(&mut self, should_show_floating_panes: bool) {
|
||||
self.show_panes = should_show_floating_panes;
|
||||
}
|
||||
pub fn active_panes_contain(&self, client_id: &ClientId) -> bool {
|
||||
self.active_panes.contains_key(client_id)
|
||||
}
|
||||
pub fn panes_contain(&self, pane_id: &PaneId) -> bool {
|
||||
self.panes.contains_key(pane_id)
|
||||
}
|
||||
pub fn find_room_for_new_pane(&mut self) -> Option<PaneGeom> {
|
||||
let display_area = *self.display_area.borrow();
|
||||
let viewport = *self.viewport.borrow();
|
||||
let floating_pane_grid = FloatingPaneGrid::new(
|
||||
&mut self.panes,
|
||||
&mut self.desired_pane_positions,
|
||||
display_area,
|
||||
viewport,
|
||||
);
|
||||
floating_pane_grid.find_room_for_new_pane()
|
||||
}
|
||||
pub fn first_floating_pane_id(&self) -> Option<PaneId> {
|
||||
self.panes.keys().next().copied()
|
||||
}
|
||||
pub fn first_active_floating_pane_id(&self) -> Option<PaneId> {
|
||||
self.active_panes.values().next().copied()
|
||||
}
|
||||
pub fn set_force_render(&mut self) {
|
||||
for pane in self.panes.values_mut() {
|
||||
pane.set_should_render(true);
|
||||
pane.set_should_render_boundaries(true);
|
||||
pane.render_full_viewport();
|
||||
}
|
||||
}
|
||||
pub fn set_pane_frames(&mut self, os_api: &mut Box<dyn ServerOsApi>) {
|
||||
for pane in self.panes.values_mut() {
|
||||
// floating panes should always have a frame unless explicitly set otherwise
|
||||
if !pane.borderless() {
|
||||
pane.set_frame(true);
|
||||
pane.set_content_offset(Offset::frame(1));
|
||||
} else {
|
||||
pane.set_content_offset(Offset::default());
|
||||
}
|
||||
resize_pty!(pane, os_api);
|
||||
}
|
||||
}
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn render(
|
||||
&mut self,
|
||||
connected_clients_in_app: &Rc<RefCell<HashSet<ClientId>>>,
|
||||
connected_clients: &HashSet<ClientId>,
|
||||
mode_info: &HashMap<ClientId, ModeInfo>,
|
||||
default_mode_info: &ModeInfo,
|
||||
session_is_mirrored: bool,
|
||||
output: &mut Output,
|
||||
colors: Palette,
|
||||
) {
|
||||
let mut floating_panes: Vec<_> = self.panes.iter_mut().collect();
|
||||
floating_panes.sort_by(|(a_id, _a_pane), (b_id, _b_pane)| {
|
||||
self.z_indices
|
||||
.iter()
|
||||
.position(|id| id == *a_id)
|
||||
.unwrap()
|
||||
.cmp(&self.z_indices.iter().position(|id| id == *b_id).unwrap())
|
||||
});
|
||||
|
||||
for (z_index, (kind, pane)) in floating_panes.iter_mut().enumerate() {
|
||||
let mut active_panes = self.active_panes.clone();
|
||||
let multiple_users_exist_in_session = { connected_clients_in_app.borrow().len() > 1 };
|
||||
active_panes.retain(|c_id, _| connected_clients.contains(c_id));
|
||||
let mut pane_contents_and_ui = PaneContentsAndUi::new(
|
||||
pane,
|
||||
output,
|
||||
colors,
|
||||
&active_panes,
|
||||
multiple_users_exist_in_session,
|
||||
Some(z_index + 1), // +1 because 0 is reserved for non-floating panes
|
||||
);
|
||||
for &client_id in connected_clients {
|
||||
let client_mode = mode_info.get(&client_id).unwrap_or(default_mode_info).mode;
|
||||
pane_contents_and_ui.render_pane_frame(client_id, client_mode, session_is_mirrored);
|
||||
if let PaneId::Plugin(..) = kind {
|
||||
pane_contents_and_ui.render_pane_contents_for_client(client_id);
|
||||
}
|
||||
// this is done for panes that don't have their own cursor (eg. panes of
|
||||
// another user)
|
||||
pane_contents_and_ui.render_fake_cursor_if_needed(client_id);
|
||||
}
|
||||
if let PaneId::Terminal(..) = kind {
|
||||
pane_contents_and_ui
|
||||
.render_pane_contents_to_multiple_clients(connected_clients.iter().copied());
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn resize(&mut self, new_screen_size: Size) {
|
||||
let display_area = *self.display_area.borrow();
|
||||
let viewport = *self.viewport.borrow();
|
||||
let mut floating_pane_grid = FloatingPaneGrid::new(
|
||||
&mut self.panes,
|
||||
&mut self.desired_pane_positions,
|
||||
display_area,
|
||||
viewport,
|
||||
);
|
||||
floating_pane_grid.resize(new_screen_size);
|
||||
self.set_force_render();
|
||||
}
|
||||
pub fn resize_active_pane_left(
|
||||
&mut self,
|
||||
client_id: ClientId,
|
||||
os_api: &mut Box<dyn ServerOsApi>,
|
||||
) -> bool {
|
||||
// true => successfully resized
|
||||
let display_area = *self.display_area.borrow();
|
||||
let viewport = *self.viewport.borrow();
|
||||
if let Some(active_floating_pane_id) = self.active_panes.get(&client_id) {
|
||||
let mut floating_pane_grid = FloatingPaneGrid::new(
|
||||
&mut self.panes,
|
||||
&mut self.desired_pane_positions,
|
||||
display_area,
|
||||
viewport,
|
||||
);
|
||||
floating_pane_grid.resize_pane_left(active_floating_pane_id);
|
||||
for pane in self.panes.values_mut() {
|
||||
resize_pty!(pane, os_api);
|
||||
}
|
||||
self.set_force_render();
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
pub fn resize_active_pane_right(
|
||||
&mut self,
|
||||
client_id: ClientId,
|
||||
os_api: &mut Box<dyn ServerOsApi>,
|
||||
) -> bool {
|
||||
// true => successfully resized
|
||||
let display_area = *self.display_area.borrow();
|
||||
let viewport = *self.viewport.borrow();
|
||||
if let Some(active_floating_pane_id) = self.active_panes.get(&client_id) {
|
||||
let mut floating_pane_grid = FloatingPaneGrid::new(
|
||||
&mut self.panes,
|
||||
&mut self.desired_pane_positions,
|
||||
display_area,
|
||||
viewport,
|
||||
);
|
||||
floating_pane_grid.resize_pane_right(&active_floating_pane_id);
|
||||
for pane in self.panes.values_mut() {
|
||||
resize_pty!(pane, os_api);
|
||||
}
|
||||
self.set_force_render();
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
pub fn resize_active_pane_down(
|
||||
&mut self,
|
||||
client_id: ClientId,
|
||||
os_api: &mut Box<dyn ServerOsApi>,
|
||||
) -> bool {
|
||||
// true => successfully resized
|
||||
let display_area = *self.display_area.borrow();
|
||||
let viewport = *self.viewport.borrow();
|
||||
if let Some(active_floating_pane_id) = self.active_panes.get(&client_id) {
|
||||
let mut floating_pane_grid = FloatingPaneGrid::new(
|
||||
&mut self.panes,
|
||||
&mut self.desired_pane_positions,
|
||||
display_area,
|
||||
viewport,
|
||||
);
|
||||
floating_pane_grid.resize_pane_down(&active_floating_pane_id);
|
||||
for pane in self.panes.values_mut() {
|
||||
resize_pty!(pane, os_api);
|
||||
}
|
||||
self.set_force_render();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
pub fn resize_active_pane_up(
|
||||
&mut self,
|
||||
client_id: ClientId,
|
||||
os_api: &mut Box<dyn ServerOsApi>,
|
||||
) -> bool {
|
||||
// true => successfully resized
|
||||
let display_area = *self.display_area.borrow();
|
||||
let viewport = *self.viewport.borrow();
|
||||
if let Some(active_floating_pane_id) = self.active_panes.get(&client_id) {
|
||||
let mut floating_pane_grid = FloatingPaneGrid::new(
|
||||
&mut self.panes,
|
||||
&mut self.desired_pane_positions,
|
||||
display_area,
|
||||
viewport,
|
||||
);
|
||||
floating_pane_grid.resize_pane_up(&active_floating_pane_id);
|
||||
for pane in self.panes.values_mut() {
|
||||
resize_pty!(pane, os_api);
|
||||
}
|
||||
self.set_force_render();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
pub fn resize_active_pane_increase(
|
||||
&mut self,
|
||||
client_id: ClientId,
|
||||
os_api: &mut Box<dyn ServerOsApi>,
|
||||
) -> bool {
|
||||
// true => successfully resized
|
||||
let display_area = *self.display_area.borrow();
|
||||
let viewport = *self.viewport.borrow();
|
||||
if let Some(active_floating_pane_id) = self.active_panes.get(&client_id) {
|
||||
let mut floating_pane_grid = FloatingPaneGrid::new(
|
||||
&mut self.panes,
|
||||
&mut self.desired_pane_positions,
|
||||
display_area,
|
||||
viewport,
|
||||
);
|
||||
floating_pane_grid.resize_increase(&active_floating_pane_id);
|
||||
for pane in self.panes.values_mut() {
|
||||
resize_pty!(pane, os_api);
|
||||
}
|
||||
self.set_force_render();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
pub fn resize_active_pane_decrease(
|
||||
&mut self,
|
||||
client_id: ClientId,
|
||||
os_api: &mut Box<dyn ServerOsApi>,
|
||||
) -> bool {
|
||||
// true => successfully resized
|
||||
let display_area = *self.display_area.borrow();
|
||||
let viewport = *self.viewport.borrow();
|
||||
if let Some(active_floating_pane_id) = self.active_panes.get(&client_id) {
|
||||
let mut floating_pane_grid = FloatingPaneGrid::new(
|
||||
&mut self.panes,
|
||||
&mut self.desired_pane_positions,
|
||||
display_area,
|
||||
viewport,
|
||||
);
|
||||
floating_pane_grid.resize_decrease(&active_floating_pane_id);
|
||||
for pane in self.panes.values_mut() {
|
||||
resize_pty!(pane, os_api);
|
||||
}
|
||||
self.set_force_render();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
pub fn move_focus_left(
|
||||
&mut self,
|
||||
client_id: ClientId,
|
||||
connected_clients: &HashSet<ClientId>,
|
||||
) -> bool {
|
||||
// true => successfully moved
|
||||
let display_area = *self.display_area.borrow();
|
||||
let viewport = *self.viewport.borrow();
|
||||
let active_pane_id = self.active_panes.get(&client_id).copied();
|
||||
let updated_active_pane = if let Some(active_pane_id) = active_pane_id {
|
||||
let floating_pane_grid = FloatingPaneGrid::new(
|
||||
&mut self.panes,
|
||||
&mut self.desired_pane_positions,
|
||||
display_area,
|
||||
viewport,
|
||||
);
|
||||
let next_index =
|
||||
floating_pane_grid.next_selectable_pane_id_to_the_left(&active_pane_id);
|
||||
match next_index {
|
||||
Some(p) => {
|
||||
// render previously active pane so that its frame does not remain actively
|
||||
// colored
|
||||
let previously_active_pane = self
|
||||
.panes
|
||||
.get_mut(self.active_panes.get(&client_id).unwrap())
|
||||
.unwrap();
|
||||
|
||||
previously_active_pane.set_should_render(true);
|
||||
// we render the full viewport to remove any ui elements that might have been
|
||||
// there before (eg. another user's cursor)
|
||||
previously_active_pane.render_full_viewport();
|
||||
|
||||
let next_active_pane = self.panes.get_mut(&p).unwrap();
|
||||
next_active_pane.set_should_render(true);
|
||||
// we render the full viewport to remove any ui elements that might have been
|
||||
// there before (eg. another user's cursor)
|
||||
next_active_pane.render_full_viewport();
|
||||
|
||||
// move all clients
|
||||
let connected_clients: Vec<ClientId> =
|
||||
connected_clients.iter().copied().collect();
|
||||
for client_id in connected_clients {
|
||||
self.focus_pane(p, client_id);
|
||||
}
|
||||
|
||||
self.set_force_render();
|
||||
return true;
|
||||
}
|
||||
None => Some(active_pane_id),
|
||||
}
|
||||
} else {
|
||||
active_pane_id
|
||||
};
|
||||
match updated_active_pane {
|
||||
Some(updated_active_pane) => {
|
||||
let connected_clients: Vec<ClientId> = connected_clients.iter().copied().collect();
|
||||
for client_id in connected_clients {
|
||||
self.focus_pane(updated_active_pane, client_id);
|
||||
}
|
||||
self.set_force_render();
|
||||
}
|
||||
None => {
|
||||
// TODO: can this happen?
|
||||
self.active_panes.clear();
|
||||
self.z_indices.clear();
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
pub fn move_focus_right(
|
||||
&mut self,
|
||||
client_id: ClientId,
|
||||
connected_clients: &HashSet<ClientId>,
|
||||
) -> bool {
|
||||
// true => successfully moved
|
||||
let display_area = *self.display_area.borrow();
|
||||
let viewport = *self.viewport.borrow();
|
||||
let active_pane_id = self.active_panes.get(&client_id).copied();
|
||||
let updated_active_pane = if let Some(active_pane_id) = active_pane_id {
|
||||
let floating_pane_grid = FloatingPaneGrid::new(
|
||||
&mut self.panes,
|
||||
&mut self.desired_pane_positions,
|
||||
display_area,
|
||||
viewport,
|
||||
);
|
||||
let next_index =
|
||||
floating_pane_grid.next_selectable_pane_id_to_the_right(&active_pane_id);
|
||||
match next_index {
|
||||
Some(p) => {
|
||||
// render previously active pane so that its frame does not remain actively
|
||||
// colored
|
||||
let previously_active_pane = self
|
||||
.panes
|
||||
.get_mut(self.active_panes.get(&client_id).unwrap())
|
||||
.unwrap();
|
||||
|
||||
previously_active_pane.set_should_render(true);
|
||||
// we render the full viewport to remove any ui elements that might have been
|
||||
// there before (eg. another user's cursor)
|
||||
previously_active_pane.render_full_viewport();
|
||||
|
||||
let next_active_pane = self.panes.get_mut(&p).unwrap();
|
||||
next_active_pane.set_should_render(true);
|
||||
// we render the full viewport to remove any ui elements that might have been
|
||||
// there before (eg. another user's cursor)
|
||||
next_active_pane.render_full_viewport();
|
||||
|
||||
// move all clients
|
||||
let connected_clients: Vec<ClientId> =
|
||||
connected_clients.iter().copied().collect();
|
||||
for client_id in connected_clients {
|
||||
self.focus_pane(p, client_id);
|
||||
}
|
||||
|
||||
self.set_force_render();
|
||||
return true;
|
||||
}
|
||||
None => Some(active_pane_id),
|
||||
}
|
||||
} else {
|
||||
active_pane_id
|
||||
};
|
||||
match updated_active_pane {
|
||||
Some(updated_active_pane) => {
|
||||
let connected_clients: Vec<ClientId> = connected_clients.iter().copied().collect();
|
||||
for client_id in connected_clients {
|
||||
self.focus_pane(updated_active_pane, client_id);
|
||||
}
|
||||
self.set_force_render();
|
||||
}
|
||||
None => {
|
||||
// TODO: can this happen?
|
||||
self.active_panes.clear();
|
||||
self.z_indices.clear();
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
pub fn move_focus_up(
|
||||
&mut self,
|
||||
client_id: ClientId,
|
||||
connected_clients: &HashSet<ClientId>,
|
||||
) -> bool {
|
||||
// true => successfully moved
|
||||
let display_area = *self.display_area.borrow();
|
||||
let viewport = *self.viewport.borrow();
|
||||
let active_pane_id = self.active_panes.get(&client_id).copied();
|
||||
let updated_active_pane = if let Some(active_pane_id) = active_pane_id {
|
||||
let floating_pane_grid = FloatingPaneGrid::new(
|
||||
&mut self.panes,
|
||||
&mut self.desired_pane_positions,
|
||||
display_area,
|
||||
viewport,
|
||||
);
|
||||
let next_index = floating_pane_grid.next_selectable_pane_id_above(&active_pane_id);
|
||||
match next_index {
|
||||
Some(p) => {
|
||||
// render previously active pane so that its frame does not remain actively
|
||||
// colored
|
||||
let previously_active_pane = self
|
||||
.panes
|
||||
.get_mut(self.active_panes.get(&client_id).unwrap())
|
||||
.unwrap();
|
||||
|
||||
previously_active_pane.set_should_render(true);
|
||||
// we render the full viewport to remove any ui elements that might have been
|
||||
// there before (eg. another user's cursor)
|
||||
previously_active_pane.render_full_viewport();
|
||||
|
||||
let next_active_pane = self.panes.get_mut(&p).unwrap();
|
||||
next_active_pane.set_should_render(true);
|
||||
// we render the full viewport to remove any ui elements that might have been
|
||||
// there before (eg. another user's cursor)
|
||||
next_active_pane.render_full_viewport();
|
||||
|
||||
// move all clients
|
||||
let connected_clients: Vec<ClientId> =
|
||||
connected_clients.iter().copied().collect();
|
||||
for client_id in connected_clients {
|
||||
self.focus_pane(p, client_id);
|
||||
}
|
||||
|
||||
self.set_force_render();
|
||||
return true;
|
||||
}
|
||||
None => Some(active_pane_id),
|
||||
}
|
||||
} else {
|
||||
active_pane_id
|
||||
};
|
||||
match updated_active_pane {
|
||||
Some(updated_active_pane) => {
|
||||
let connected_clients: Vec<ClientId> = connected_clients.iter().copied().collect();
|
||||
for client_id in connected_clients {
|
||||
self.focus_pane(updated_active_pane, client_id);
|
||||
}
|
||||
self.set_force_render();
|
||||
}
|
||||
None => {
|
||||
// TODO: can this happen?
|
||||
self.active_panes.clear();
|
||||
self.z_indices.clear();
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
pub fn move_focus_down(
|
||||
&mut self,
|
||||
client_id: ClientId,
|
||||
connected_clients: &HashSet<ClientId>,
|
||||
) -> bool {
|
||||
// true => successfully moved
|
||||
let display_area = *self.display_area.borrow();
|
||||
let viewport = *self.viewport.borrow();
|
||||
let active_pane_id = self.active_panes.get(&client_id).copied();
|
||||
let updated_active_pane = if let Some(active_pane_id) = active_pane_id {
|
||||
let floating_pane_grid = FloatingPaneGrid::new(
|
||||
&mut self.panes,
|
||||
&mut self.desired_pane_positions,
|
||||
display_area,
|
||||
viewport,
|
||||
);
|
||||
let next_index = floating_pane_grid.next_selectable_pane_id_below(&active_pane_id);
|
||||
match next_index {
|
||||
Some(p) => {
|
||||
// render previously active pane so that its frame does not remain actively
|
||||
// colored
|
||||
let previously_active_pane = self
|
||||
.panes
|
||||
.get_mut(self.active_panes.get(&client_id).unwrap())
|
||||
.unwrap();
|
||||
|
||||
previously_active_pane.set_should_render(true);
|
||||
// we render the full viewport to remove any ui elements that might have been
|
||||
// there before (eg. another user's cursor)
|
||||
previously_active_pane.render_full_viewport();
|
||||
|
||||
let next_active_pane = self.panes.get_mut(&p).unwrap();
|
||||
next_active_pane.set_should_render(true);
|
||||
// we render the full viewport to remove any ui elements that might have been
|
||||
// there before (eg. another user's cursor)
|
||||
next_active_pane.render_full_viewport();
|
||||
|
||||
// move all clients
|
||||
let connected_clients: Vec<ClientId> =
|
||||
connected_clients.iter().copied().collect();
|
||||
for client_id in connected_clients {
|
||||
self.focus_pane(p, client_id);
|
||||
}
|
||||
|
||||
self.set_force_render();
|
||||
return true;
|
||||
}
|
||||
None => Some(active_pane_id),
|
||||
}
|
||||
} else {
|
||||
active_pane_id
|
||||
};
|
||||
match updated_active_pane {
|
||||
Some(updated_active_pane) => {
|
||||
let connected_clients: Vec<ClientId> = connected_clients.iter().copied().collect();
|
||||
for client_id in connected_clients {
|
||||
self.focus_pane(updated_active_pane, client_id);
|
||||
}
|
||||
self.set_force_render();
|
||||
}
|
||||
None => {
|
||||
// TODO: can this happen?
|
||||
self.active_panes.clear();
|
||||
self.z_indices.clear();
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
pub fn move_active_pane_down(&mut self, client_id: ClientId) {
|
||||
let display_area = *self.display_area.borrow();
|
||||
let viewport = *self.viewport.borrow();
|
||||
if let Some(active_pane_id) = self.active_panes.get(&client_id) {
|
||||
let mut floating_pane_grid = FloatingPaneGrid::new(
|
||||
&mut self.panes,
|
||||
&mut self.desired_pane_positions,
|
||||
display_area,
|
||||
viewport,
|
||||
);
|
||||
floating_pane_grid.move_pane_down(&active_pane_id);
|
||||
self.set_force_render();
|
||||
}
|
||||
}
|
||||
pub fn move_active_pane_up(&mut self, client_id: ClientId) {
|
||||
let display_area = *self.display_area.borrow();
|
||||
let viewport = *self.viewport.borrow();
|
||||
if let Some(active_pane_id) = self.active_panes.get(&client_id) {
|
||||
let mut floating_pane_grid = FloatingPaneGrid::new(
|
||||
&mut self.panes,
|
||||
&mut self.desired_pane_positions,
|
||||
display_area,
|
||||
viewport,
|
||||
);
|
||||
floating_pane_grid.move_pane_up(&active_pane_id);
|
||||
self.set_force_render();
|
||||
}
|
||||
}
|
||||
pub fn move_active_pane_left(&mut self, client_id: ClientId) {
|
||||
let display_area = *self.display_area.borrow();
|
||||
let viewport = *self.viewport.borrow();
|
||||
if let Some(active_pane_id) = self.active_panes.get(&client_id) {
|
||||
let mut floating_pane_grid = FloatingPaneGrid::new(
|
||||
&mut self.panes,
|
||||
&mut self.desired_pane_positions,
|
||||
display_area,
|
||||
viewport,
|
||||
);
|
||||
floating_pane_grid.move_pane_left(&active_pane_id);
|
||||
self.set_force_render();
|
||||
}
|
||||
}
|
||||
pub fn move_active_pane_right(&mut self, client_id: ClientId) {
|
||||
let display_area = *self.display_area.borrow();
|
||||
let viewport = *self.viewport.borrow();
|
||||
if let Some(active_pane_id) = self.active_panes.get(&client_id) {
|
||||
let mut floating_pane_grid = FloatingPaneGrid::new(
|
||||
&mut self.panes,
|
||||
&mut self.desired_pane_positions,
|
||||
display_area,
|
||||
viewport,
|
||||
);
|
||||
floating_pane_grid.move_pane_right(&active_pane_id);
|
||||
self.set_force_render();
|
||||
}
|
||||
}
|
||||
pub fn move_clients_out_of_pane(&mut self, pane_id: PaneId) {
|
||||
let active_panes: Vec<(ClientId, PaneId)> = self
|
||||
.active_panes
|
||||
.iter()
|
||||
.map(|(cid, pid)| (*cid, *pid))
|
||||
.collect();
|
||||
let next_active_pane = self.panes.keys().next().copied();
|
||||
for (client_id, active_pane_id) in active_panes {
|
||||
if active_pane_id == pane_id {
|
||||
match next_active_pane {
|
||||
Some(next_active_pane) => {
|
||||
self.active_panes.insert(client_id, next_active_pane);
|
||||
self.focus_pane(next_active_pane, client_id);
|
||||
}
|
||||
None => {
|
||||
self.defocus_pane(pane_id, client_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn focus_pane(&mut self, pane_id: PaneId, client_id: ClientId) {
|
||||
self.active_panes.insert(client_id, pane_id);
|
||||
self.z_indices.retain(|p_id| *p_id != pane_id);
|
||||
self.z_indices.push(pane_id);
|
||||
self.set_force_render();
|
||||
}
|
||||
pub fn defocus_pane(&mut self, pane_id: PaneId, client_id: ClientId) {
|
||||
self.z_indices.retain(|p_id| *p_id != pane_id);
|
||||
self.active_panes.remove(&client_id);
|
||||
self.set_force_render();
|
||||
}
|
||||
pub fn get_pane_id_at(&self, point: &Position, search_selectable: bool) -> Option<PaneId> {
|
||||
if search_selectable {
|
||||
// TODO: better - loop through z-indices and check each one if it contains the point
|
||||
let mut selectable_panes: Vec<_> =
|
||||
self.panes.iter().filter(|(_, p)| p.selectable()).collect();
|
||||
selectable_panes.sort_by(|(a_id, _a_pane), (b_id, _b_pane)| {
|
||||
self.z_indices
|
||||
.iter()
|
||||
.position(|id| id == *b_id)
|
||||
.unwrap()
|
||||
.cmp(&self.z_indices.iter().position(|id| id == *a_id).unwrap())
|
||||
});
|
||||
selectable_panes
|
||||
.iter()
|
||||
.find(|(_, p)| p.contains(point))
|
||||
.map(|(&id, _)| id)
|
||||
} else {
|
||||
let mut panes: Vec<_> = self.panes.iter().collect();
|
||||
panes.sort_by(|(a_id, _a_pane), (b_id, _b_pane)| {
|
||||
self.z_indices
|
||||
.iter()
|
||||
.position(|id| id == *b_id)
|
||||
.unwrap()
|
||||
.cmp(&self.z_indices.iter().position(|id| id == *a_id).unwrap())
|
||||
});
|
||||
panes
|
||||
.iter()
|
||||
.find(|(_, p)| p.contains(point))
|
||||
.map(|(&id, _)| id)
|
||||
}
|
||||
}
|
||||
pub fn get_pane_at_mut(
|
||||
&mut self,
|
||||
position: &Position,
|
||||
search_selectable: bool,
|
||||
) -> Option<&mut Box<dyn Pane>> {
|
||||
self.get_pane_id_at(position, search_selectable)
|
||||
.and_then(|pane_id| self.panes.get_mut(&pane_id))
|
||||
}
|
||||
pub fn set_pane_being_moved_with_mouse(&mut self, pane_id: PaneId, position: Position) {
|
||||
self.pane_being_moved_with_mouse = Some((pane_id, position));
|
||||
}
|
||||
pub fn pane_is_being_moved_with_mouse(&self) -> bool {
|
||||
self.pane_being_moved_with_mouse.is_some()
|
||||
}
|
||||
pub fn move_pane_to_position(&mut self, click_position: &Position) -> bool {
|
||||
// true => changed position
|
||||
let display_area = *self.display_area.borrow();
|
||||
let viewport = *self.viewport.borrow();
|
||||
let (pane_id, previous_position) = self.pane_being_moved_with_mouse.unwrap();
|
||||
if click_position == &previous_position {
|
||||
return false;
|
||||
}
|
||||
let move_x_by = click_position.column() as isize - previous_position.column() as isize;
|
||||
let move_y_by = click_position.line() as isize - previous_position.line() as isize;
|
||||
let mut floating_pane_grid = FloatingPaneGrid::new(
|
||||
&mut self.panes,
|
||||
&mut self.desired_pane_positions,
|
||||
display_area,
|
||||
viewport,
|
||||
);
|
||||
floating_pane_grid.move_pane_by(pane_id, move_x_by, move_y_by);
|
||||
self.set_pane_being_moved_with_mouse(pane_id, click_position.clone());
|
||||
self.set_force_render();
|
||||
true
|
||||
}
|
||||
pub fn move_pane_with_mouse(&mut self, position: Position, search_selectable: bool) -> bool {
|
||||
// true => handled, false => not handled (eg. no pane at this position)
|
||||
let show_panes = self.show_panes;
|
||||
if self.pane_being_moved_with_mouse.is_some() {
|
||||
self.move_pane_to_position(&position);
|
||||
self.set_force_render();
|
||||
return true;
|
||||
} else if let Some(pane) = self.get_pane_at_mut(&position, search_selectable) {
|
||||
let clicked_on_frame = pane.position_is_on_frame(&position);
|
||||
if show_panes && clicked_on_frame {
|
||||
let pid = pane.pid();
|
||||
if self.pane_being_moved_with_mouse.is_none() {
|
||||
self.set_pane_being_moved_with_mouse(pid, position.clone());
|
||||
}
|
||||
self.move_pane_to_position(&position);
|
||||
self.set_force_render();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
return false;
|
||||
}
|
||||
pub fn stop_moving_pane_with_mouse(&mut self, position: Position) {
|
||||
if self.pane_being_moved_with_mouse.is_some() {
|
||||
self.move_pane_to_position(&position);
|
||||
self.set_force_render();
|
||||
};
|
||||
self.pane_being_moved_with_mouse = None;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
|
||||
use std::{
|
||||
|
|
@ -20,6 +22,7 @@ use vte::{Params, Perform};
|
|||
use zellij_tile::data::{Palette, PaletteColor};
|
||||
use zellij_utils::{consts::VERSION, shared::version_number};
|
||||
|
||||
use crate::output::{CharacterChunk, OutputBuffer};
|
||||
use crate::panes::alacritty_functions::{parse_number, xparse_color};
|
||||
use crate::panes::link_handler::LinkHandler;
|
||||
use crate::panes::selection::Selection;
|
||||
|
|
@ -267,107 +270,6 @@ fn subtract_isize_from_usize(u: usize, i: isize) -> usize {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CharacterChunk {
|
||||
pub terminal_characters: Vec<TerminalCharacter>,
|
||||
pub x: usize,
|
||||
pub y: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct OutputBuffer {
|
||||
changed_lines: Vec<usize>, // line index
|
||||
should_update_all_lines: bool,
|
||||
}
|
||||
|
||||
impl Default for OutputBuffer {
|
||||
fn default() -> Self {
|
||||
OutputBuffer {
|
||||
changed_lines: vec![],
|
||||
should_update_all_lines: true, // first time we should do a full render
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OutputBuffer {
|
||||
pub fn update_line(&mut self, line_index: usize) {
|
||||
if !self.should_update_all_lines {
|
||||
self.changed_lines.push(line_index);
|
||||
}
|
||||
}
|
||||
pub fn update_all_lines(&mut self) {
|
||||
self.clear();
|
||||
self.should_update_all_lines = true;
|
||||
}
|
||||
pub fn clear(&mut self) {
|
||||
self.changed_lines.clear();
|
||||
self.should_update_all_lines = false;
|
||||
}
|
||||
pub fn changed_chunks_in_viewport(
|
||||
&self,
|
||||
viewport: &[Row],
|
||||
viewport_width: usize,
|
||||
viewport_height: usize,
|
||||
) -> Vec<CharacterChunk> {
|
||||
if self.should_update_all_lines {
|
||||
let mut changed_chunks = Vec::with_capacity(viewport.len());
|
||||
for line_index in 0..viewport_height {
|
||||
let terminal_characters =
|
||||
self.extract_line_from_viewport(line_index, viewport, viewport_width);
|
||||
changed_chunks.push(CharacterChunk {
|
||||
x: 0,
|
||||
y: line_index,
|
||||
terminal_characters,
|
||||
});
|
||||
}
|
||||
changed_chunks
|
||||
} else {
|
||||
let mut line_changes = self.changed_lines.to_vec();
|
||||
line_changes.sort_unstable();
|
||||
line_changes.dedup();
|
||||
let mut changed_chunks = Vec::with_capacity(line_changes.len());
|
||||
for line_index in line_changes {
|
||||
let terminal_characters =
|
||||
self.extract_line_from_viewport(line_index, viewport, viewport_width);
|
||||
changed_chunks.push(CharacterChunk {
|
||||
x: 0,
|
||||
y: line_index,
|
||||
terminal_characters,
|
||||
});
|
||||
}
|
||||
changed_chunks
|
||||
}
|
||||
}
|
||||
fn extract_characters_from_row(
|
||||
&self,
|
||||
row: &Row,
|
||||
viewport_width: usize,
|
||||
) -> Vec<TerminalCharacter> {
|
||||
let mut terminal_characters: Vec<TerminalCharacter> = row.columns.iter().copied().collect();
|
||||
// pad row
|
||||
let row_width = row.width();
|
||||
if row_width < viewport_width {
|
||||
let mut padding = vec![EMPTY_TERMINAL_CHARACTER; viewport_width - row_width];
|
||||
terminal_characters.append(&mut padding);
|
||||
}
|
||||
terminal_characters
|
||||
}
|
||||
fn extract_line_from_viewport(
|
||||
&self,
|
||||
line_index: usize,
|
||||
viewport: &[Row],
|
||||
viewport_width: usize,
|
||||
) -> Vec<TerminalCharacter> {
|
||||
match viewport.get(line_index) {
|
||||
// TODO: iterator?
|
||||
Some(row) => self.extract_characters_from_row(row, viewport_width),
|
||||
None => {
|
||||
vec![EMPTY_TERMINAL_CHARACTER; viewport_width]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Grid {
|
||||
lines_above: VecDeque<Row>,
|
||||
|
|
@ -401,7 +303,7 @@ pub struct Grid {
|
|||
pub selection: Selection,
|
||||
pub title: Option<String>,
|
||||
pub is_scrolled: bool,
|
||||
pub link_handler: LinkHandler,
|
||||
pub link_handler: Rc<RefCell<LinkHandler>>,
|
||||
pub ring_bell: bool,
|
||||
scrollback_buffer_lines: usize,
|
||||
}
|
||||
|
|
@ -420,7 +322,12 @@ impl Debug for Grid {
|
|||
}
|
||||
|
||||
impl Grid {
|
||||
pub fn new(rows: usize, columns: usize, colors: Palette) -> Self {
|
||||
pub fn new(
|
||||
rows: usize,
|
||||
columns: usize,
|
||||
colors: Palette,
|
||||
link_handler: Rc<RefCell<LinkHandler>>,
|
||||
) -> Self {
|
||||
Grid {
|
||||
lines_above: VecDeque::with_capacity(
|
||||
// .get_or_init() is used instead of .get().unwrap() to prevent
|
||||
|
|
@ -453,7 +360,7 @@ impl Grid {
|
|||
title: None,
|
||||
changed_colors: None,
|
||||
is_scrolled: false,
|
||||
link_handler: Default::default(),
|
||||
link_handler,
|
||||
ring_bell: false,
|
||||
scrollback_buffer_lines: 0,
|
||||
}
|
||||
|
|
@ -717,7 +624,7 @@ impl Grid {
|
|||
}
|
||||
}
|
||||
if let Some(trim_at) = trim_at {
|
||||
line.columns.truncate(trim_at);
|
||||
line.truncate(trim_at);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -865,10 +772,14 @@ impl Grid {
|
|||
}
|
||||
lines
|
||||
}
|
||||
pub fn read_changes(&mut self) -> Vec<CharacterChunk> {
|
||||
let changes =
|
||||
self.output_buffer
|
||||
.changed_chunks_in_viewport(&self.viewport, self.width, self.height);
|
||||
pub fn read_changes(&mut self, x_offset: usize, y_offset: usize) -> Vec<CharacterChunk> {
|
||||
let changes = self.output_buffer.changed_chunks_in_viewport(
|
||||
&self.viewport,
|
||||
self.width,
|
||||
self.height,
|
||||
x_offset,
|
||||
y_offset,
|
||||
);
|
||||
self.output_buffer.clear();
|
||||
changes
|
||||
}
|
||||
|
|
@ -1056,7 +967,7 @@ impl Grid {
|
|||
pub fn add_character(&mut self, terminal_character: TerminalCharacter) {
|
||||
// TODO: try to separate adding characters from moving the cursors in this function
|
||||
let character_width = terminal_character.width;
|
||||
if self.cursor.x >= self.width {
|
||||
if self.cursor.x + character_width > self.width {
|
||||
if self.disable_linewrap {
|
||||
return;
|
||||
}
|
||||
|
|
@ -1095,7 +1006,7 @@ impl Grid {
|
|||
self.viewport.get(y).unwrap().absolute_character_index(x)
|
||||
}
|
||||
pub fn move_cursor_forward_until_edge(&mut self, count: usize) {
|
||||
let count_to_move = std::cmp::min(count, self.width - (self.cursor.x));
|
||||
let count_to_move = std::cmp::min(count, self.width - self.cursor.x);
|
||||
self.cursor.x += count_to_move;
|
||||
}
|
||||
pub fn replace_characters_in_line_after_cursor(&mut self, replace_with: TerminalCharacter) {
|
||||
|
|
@ -1613,7 +1524,8 @@ impl Perform for Grid {
|
|||
if params.len() < 3 {
|
||||
return;
|
||||
}
|
||||
self.cursor.pending_styles.link_anchor = self.link_handler.dispatch_osc8(params);
|
||||
self.cursor.pending_styles.link_anchor =
|
||||
self.link_handler.borrow_mut().dispatch_osc8(params);
|
||||
}
|
||||
|
||||
// Get/set Foreground, Background, Cursor colors.
|
||||
|
|
@ -2157,6 +2069,7 @@ impl Perform for Grid {
|
|||
pub struct Row {
|
||||
pub columns: VecDeque<TerminalCharacter>,
|
||||
pub is_canonical: bool,
|
||||
width: Option<usize>,
|
||||
}
|
||||
|
||||
impl Debug for Row {
|
||||
|
|
@ -2173,12 +2086,14 @@ impl Row {
|
|||
Row {
|
||||
columns: VecDeque::with_capacity(width),
|
||||
is_canonical: false,
|
||||
width: None,
|
||||
}
|
||||
}
|
||||
pub fn from_columns(columns: VecDeque<TerminalCharacter>) -> Self {
|
||||
Row {
|
||||
columns,
|
||||
is_canonical: false,
|
||||
width: None,
|
||||
}
|
||||
}
|
||||
pub fn from_rows(mut rows: Vec<Row>, width: usize) -> Self {
|
||||
|
|
@ -2194,12 +2109,25 @@ impl Row {
|
|||
}
|
||||
pub fn with_character(mut self, terminal_character: TerminalCharacter) -> Self {
|
||||
self.columns.push_back(terminal_character);
|
||||
self.width = None;
|
||||
self
|
||||
}
|
||||
pub fn canonical(mut self) -> Self {
|
||||
self.is_canonical = true;
|
||||
self
|
||||
}
|
||||
pub fn width_cached(&mut self) -> usize {
|
||||
if self.width.is_some() {
|
||||
self.width.unwrap()
|
||||
} else {
|
||||
let mut width = 0;
|
||||
for terminal_character in &self.columns {
|
||||
width += terminal_character.width;
|
||||
}
|
||||
self.width = Some(width);
|
||||
width
|
||||
}
|
||||
}
|
||||
pub fn width(&self) -> usize {
|
||||
let mut width = 0;
|
||||
for terminal_character in &self.columns {
|
||||
|
|
@ -2239,15 +2167,18 @@ impl Row {
|
|||
absolute_index
|
||||
}
|
||||
pub fn add_character_at(&mut self, terminal_character: TerminalCharacter, x: usize) {
|
||||
match self.width().cmp(&x) {
|
||||
match self.width_cached().cmp(&x) {
|
||||
Ordering::Equal => {
|
||||
self.columns.push_back(terminal_character);
|
||||
// this is unwrapped because this always happens after self.width_cached()
|
||||
*self.width.as_mut().unwrap() += terminal_character.width;
|
||||
}
|
||||
Ordering::Less => {
|
||||
let width_offset = self.excess_width_until(x);
|
||||
self.columns
|
||||
.resize(x.saturating_sub(width_offset), EMPTY_TERMINAL_CHARACTER);
|
||||
self.columns.push_back(terminal_character);
|
||||
self.width = None;
|
||||
}
|
||||
Ordering::Greater => {
|
||||
// wide-character-aware index, where each character is counted once
|
||||
|
|
@ -2275,6 +2206,7 @@ impl Row {
|
|||
}
|
||||
_ => {}
|
||||
}
|
||||
self.width = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2291,6 +2223,7 @@ impl Row {
|
|||
self.columns.insert(insert_position, terminal_character);
|
||||
}
|
||||
}
|
||||
self.width = None;
|
||||
}
|
||||
pub fn replace_character_at(&mut self, terminal_character: TerminalCharacter, x: usize) {
|
||||
// this is much more performant than remove/insert
|
||||
|
|
@ -2303,12 +2236,15 @@ impl Row {
|
|||
self.columns.insert(x, terminal_character);
|
||||
}
|
||||
}
|
||||
self.width = None;
|
||||
}
|
||||
pub fn replace_columns(&mut self, columns: VecDeque<TerminalCharacter>) {
|
||||
self.columns = columns;
|
||||
self.width = None;
|
||||
}
|
||||
pub fn push(&mut self, terminal_character: TerminalCharacter) {
|
||||
self.columns.push_back(terminal_character);
|
||||
self.width = None;
|
||||
}
|
||||
pub fn truncate(&mut self, x: usize) {
|
||||
let width_offset = self.excess_width_until(x);
|
||||
|
|
@ -2316,6 +2252,7 @@ impl Row {
|
|||
if truncate_position < self.columns.len() {
|
||||
self.columns.truncate(truncate_position);
|
||||
}
|
||||
self.width = None;
|
||||
}
|
||||
pub fn position_accounting_for_widechars(&self, x: usize) -> usize {
|
||||
let mut position = x;
|
||||
|
|
@ -2343,9 +2280,11 @@ impl Row {
|
|||
self.columns
|
||||
.truncate(from_position_accounting_for_widechars);
|
||||
self.columns.append(&mut replace_with);
|
||||
self.width = None;
|
||||
}
|
||||
pub fn append(&mut self, to_append: &mut VecDeque<TerminalCharacter>) {
|
||||
self.columns.append(to_append);
|
||||
self.width = None;
|
||||
}
|
||||
pub fn drain_until(&mut self, x: usize) -> VecDeque<TerminalCharacter> {
|
||||
let mut drained_part: VecDeque<TerminalCharacter> = VecDeque::new();
|
||||
|
|
@ -2359,6 +2298,7 @@ impl Row {
|
|||
break;
|
||||
}
|
||||
}
|
||||
self.width = None;
|
||||
drained_part
|
||||
}
|
||||
pub fn replace_and_pad_beginning(&mut self, to: usize, terminal_character: TerminalCharacter) {
|
||||
|
|
@ -2378,6 +2318,7 @@ impl Row {
|
|||
drop(self.columns.drain(0..=to_position_accounting_for_widechars));
|
||||
}
|
||||
replace_with.append(&mut self.columns);
|
||||
self.width = None;
|
||||
self.columns = replace_with;
|
||||
}
|
||||
pub fn replace_beginning_with(&mut self, mut line_part: VecDeque<TerminalCharacter>) {
|
||||
|
|
@ -2388,6 +2329,7 @@ impl Row {
|
|||
drop(self.columns.drain(0..line_part.len()));
|
||||
}
|
||||
line_part.append(&mut self.columns);
|
||||
self.width = None;
|
||||
self.columns = line_part;
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
|
|
@ -2399,6 +2341,7 @@ impl Row {
|
|||
pub fn delete_and_return_character(&mut self, x: usize) -> Option<TerminalCharacter> {
|
||||
let erase_position = self.absolute_character_index(x);
|
||||
if erase_position < self.columns.len() {
|
||||
self.width = None;
|
||||
Some(self.columns.remove(erase_position).unwrap()) // TODO: just return the remove part?
|
||||
} else {
|
||||
None
|
||||
|
|
@ -2426,6 +2369,7 @@ impl Row {
|
|||
if parts.is_empty() {
|
||||
parts.push(self.clone());
|
||||
}
|
||||
self.width = None;
|
||||
parts
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ pub struct LinkHandler {
|
|||
link_index: u16,
|
||||
}
|
||||
#[derive(Debug, Clone)]
|
||||
struct Link {
|
||||
pub struct Link {
|
||||
id: Option<String>,
|
||||
uri: String,
|
||||
}
|
||||
|
|
@ -49,8 +49,8 @@ impl LinkHandler {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn output_osc8(&self, link_anchor: Option<LinkAnchor>) -> String {
|
||||
link_anchor.map_or("".to_string(), |link| match link {
|
||||
pub fn output_osc8(&self, link_anchor: Option<LinkAnchor>) -> Option<String> {
|
||||
link_anchor.map(|link| match link {
|
||||
LinkAnchor::Start(index) => {
|
||||
let link = self.links.get(&index).unwrap();
|
||||
let id = link
|
||||
|
|
@ -93,7 +93,7 @@ mod tests {
|
|||
}
|
||||
|
||||
let expected = format!("\u{1b}]8;id=test;http://test.com{}", TERMINATOR);
|
||||
assert_eq!(link_handler.output_osc8(anchor), expected);
|
||||
assert_eq!(link_handler.output_osc8(anchor).unwrap(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -106,6 +106,6 @@ mod tests {
|
|||
assert_eq!(anchor, Some(LinkAnchor::End));
|
||||
|
||||
let expected = format!("\u{1b}]8;;{}", TERMINATOR);
|
||||
assert_eq!(link_handler.output_osc8(anchor), expected);
|
||||
assert_eq!(link_handler.output_osc8(anchor).unwrap(), expected);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
mod alacritty_functions;
|
||||
mod grid;
|
||||
mod link_handler;
|
||||
mod floating_panes;
|
||||
pub mod grid;
|
||||
pub mod link_handler;
|
||||
mod plugin_pane;
|
||||
mod selection;
|
||||
mod terminal_character;
|
||||
pub mod selection;
|
||||
pub mod terminal_character;
|
||||
mod terminal_pane;
|
||||
|
||||
pub use alacritty_functions::*;
|
||||
pub use floating_panes::*;
|
||||
pub use grid::*;
|
||||
pub use link_handler::*;
|
||||
pub(crate) use plugin_pane::*;
|
||||
pub use terminal_character::*;
|
||||
pub(crate) use terminal_character::*;
|
||||
pub use terminal_pane::*;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use std::sync::mpsc::channel;
|
|||
use std::time::Instant;
|
||||
use std::unimplemented;
|
||||
|
||||
use crate::output::CharacterChunk;
|
||||
use crate::panes::PaneId;
|
||||
use crate::pty::VteBytes;
|
||||
use crate::tab::Pane;
|
||||
|
|
@ -134,7 +135,10 @@ impl Pane for PluginPane {
|
|||
fn set_selectable(&mut self, selectable: bool) {
|
||||
self.selectable = selectable;
|
||||
}
|
||||
fn render(&mut self, client_id: Option<ClientId>) -> Option<String> {
|
||||
fn render(
|
||||
&mut self,
|
||||
client_id: Option<ClientId>,
|
||||
) -> Option<(Vec<CharacterChunk>, Option<String>)> {
|
||||
// this is a bit of a hack but works in a pinch
|
||||
client_id?;
|
||||
let client_id = client_id.unwrap();
|
||||
|
|
@ -207,7 +211,7 @@ impl Pane for PluginPane {
|
|||
}
|
||||
}
|
||||
}
|
||||
Some(vte_output)
|
||||
Some((vec![], Some(vte_output))) // TODO: PluginPanes should have their own grid so that we can return the non-serialized TerminalCharacters and have them participate in the render buffer
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
@ -217,7 +221,7 @@ impl Pane for PluginPane {
|
|||
_client_id: ClientId,
|
||||
frame_params: FrameParams,
|
||||
input_mode: InputMode,
|
||||
) -> Option<String> {
|
||||
) -> Option<(Vec<CharacterChunk>, Option<String>)> {
|
||||
// FIXME: This is a hack that assumes all fixed-size panes are borderless. This
|
||||
// will eventually need fixing!
|
||||
if self.frame && !(self.geom.rows.is_fixed() || self.geom.cols.is_fixed()) {
|
||||
|
|
@ -328,7 +332,6 @@ impl Pane for PluginPane {
|
|||
unimplemented!();
|
||||
}
|
||||
fn start_selection(&mut self, start: &Position, client_id: ClientId) {
|
||||
log::info!("plugin pane send left click plugin instruction");
|
||||
self.send_plugin_instructions
|
||||
.send(PluginInstruction::Update(
|
||||
Some(self.pid),
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use zellij_utils::position::Position;
|
|||
|
||||
// The selection is empty when start == end
|
||||
// it includes the character at start, and everything before end.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Selection {
|
||||
pub start: Position,
|
||||
pub end: Position,
|
||||
|
|
@ -59,6 +59,16 @@ impl Selection {
|
|||
end.line.0 == row && col < end.column.0
|
||||
}
|
||||
|
||||
pub fn contains_row(&self, row: usize) -> bool {
|
||||
let row = row as isize;
|
||||
let (start, end) = if self.start <= self.end {
|
||||
(self.start, self.end)
|
||||
} else {
|
||||
(self.end, self.start)
|
||||
};
|
||||
start.line.0 <= row && end.line.0 >= row
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.start == self.end
|
||||
}
|
||||
|
|
@ -99,6 +109,13 @@ impl Selection {
|
|||
self.end.line.0 += lines as isize;
|
||||
}
|
||||
}
|
||||
pub fn offset(mut self, offset_x: usize, offset_y: usize) -> Self {
|
||||
self.start.line.0 += offset_y as isize;
|
||||
self.end.line.0 += offset_y as isize;
|
||||
self.start.column.0 += offset_x;
|
||||
self.end.column.0 += offset_x;
|
||||
self
|
||||
}
|
||||
|
||||
/// Return an iterator over the line indices, up to max, that are not present in both self and other,
|
||||
/// except for the indices of the first and last line of both self and s2, that are always included.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use std::convert::From;
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
use std::ops::{Index, IndexMut};
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
|
||||
use zellij_utils::vte::ParamsIter;
|
||||
|
||||
|
|
@ -704,6 +705,16 @@ pub struct TerminalCharacter {
|
|||
pub width: usize,
|
||||
}
|
||||
|
||||
impl TerminalCharacter {
|
||||
pub fn new(character: char) -> Self {
|
||||
TerminalCharacter {
|
||||
character,
|
||||
styles: CharacterStyles::default(),
|
||||
width: character.width().unwrap_or(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::fmt::Debug for TerminalCharacter {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.character)
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
use crate::panes::AnsiCode;
|
||||
use crate::output::CharacterChunk;
|
||||
use crate::panes::{
|
||||
grid::Grid,
|
||||
terminal_character::{
|
||||
CharacterStyles, CursorShape, TerminalCharacter, EMPTY_TERMINAL_CHARACTER,
|
||||
},
|
||||
terminal_character::{CursorShape, TerminalCharacter, EMPTY_TERMINAL_CHARACTER},
|
||||
};
|
||||
use crate::panes::{AnsiCode, LinkHandler};
|
||||
use crate::pty::VteBytes;
|
||||
use crate::tab::Pane;
|
||||
use crate::ClientId;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::{Debug, Write};
|
||||
use std::fmt::Debug;
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::rc::Rc;
|
||||
use std::time::{self, Instant};
|
||||
use zellij_utils::pane_size::Offset;
|
||||
use zellij_utils::{
|
||||
|
|
@ -184,102 +185,42 @@ impl Pane for TerminalPane {
|
|||
fn set_selectable(&mut self, selectable: bool) {
|
||||
self.selectable = selectable;
|
||||
}
|
||||
fn render(&mut self, _client_id: Option<ClientId>) -> Option<String> {
|
||||
// we don't use client_id because terminal panes render the same for all users
|
||||
fn render(
|
||||
&mut self,
|
||||
_client_id: Option<ClientId>,
|
||||
) -> Option<(Vec<CharacterChunk>, Option<String>)> {
|
||||
if self.should_render() {
|
||||
let mut vte_output = String::new();
|
||||
let mut character_styles = CharacterStyles::new();
|
||||
let mut raw_vte_output = String::new();
|
||||
let content_x = self.get_content_x();
|
||||
let content_y = self.get_content_y();
|
||||
if self.grid.clear_viewport_before_rendering {
|
||||
for line_index in 0..self.grid.height {
|
||||
write!(
|
||||
&mut vte_output,
|
||||
"\u{1b}[{};{}H\u{1b}[m",
|
||||
content_y + line_index + 1,
|
||||
content_x + 1
|
||||
)
|
||||
.unwrap(); // goto row/col and reset styles
|
||||
for _col_index in 0..self.grid.width {
|
||||
vte_output.push(EMPTY_TERMINAL_CHARACTER.character);
|
||||
}
|
||||
}
|
||||
self.grid.clear_viewport_before_rendering = false;
|
||||
}
|
||||
// here we clear the previous cursor locations by adding an empty style-less character
|
||||
// in their location, this is done before the main rendering logic so that if there
|
||||
// actually is another character there, it will be overwritten
|
||||
for (y, x) in self.fake_cursor_locations.drain() {
|
||||
// we need to make sure to update the line in the line buffer so that if there's
|
||||
// another character there it'll override it and we won't create holes with our
|
||||
// empty character
|
||||
self.grid.update_line_for_rendering(y);
|
||||
let x = content_x + x;
|
||||
let y = content_y + y;
|
||||
write!(
|
||||
&mut vte_output,
|
||||
"\u{1b}[{};{}H\u{1b}[m{}",
|
||||
y + 1,
|
||||
x + 1,
|
||||
EMPTY_TERMINAL_CHARACTER.character
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
let max_width = self.get_content_columns();
|
||||
for character_chunk in self.grid.read_changes() {
|
||||
let pane_x = self.get_content_x();
|
||||
let pane_y = self.get_content_y();
|
||||
let chunk_absolute_x = pane_x + character_chunk.x;
|
||||
let chunk_absolute_y = pane_y + character_chunk.y;
|
||||
let terminal_characters = character_chunk.terminal_characters;
|
||||
write!(
|
||||
&mut vte_output,
|
||||
"\u{1b}[{};{}H\u{1b}[m",
|
||||
chunk_absolute_y + 1,
|
||||
chunk_absolute_x + 1
|
||||
)
|
||||
.unwrap(); // goto row/col and reset styles
|
||||
|
||||
let mut chunk_width = character_chunk.x;
|
||||
for mut t_character in terminal_characters {
|
||||
// adjust the background of currently selected characters
|
||||
// doing it here is much easier than in grid
|
||||
if self.grid.selection.contains(character_chunk.y, chunk_width) {
|
||||
let color = match self.colors.bg {
|
||||
let mut character_chunks = self.grid.read_changes(content_x, content_y);
|
||||
for character_chunk in character_chunks.iter_mut() {
|
||||
character_chunk.add_changed_colors(self.grid.changed_colors);
|
||||
if self
|
||||
.grid
|
||||
.selection
|
||||
.contains_row(character_chunk.y.saturating_sub(content_y))
|
||||
{
|
||||
let background_color = match self.colors.bg {
|
||||
PaletteColor::Rgb(rgb) => AnsiCode::RgbCode(rgb),
|
||||
PaletteColor::EightBit(col) => AnsiCode::ColorIndex(col),
|
||||
};
|
||||
|
||||
t_character.styles = t_character.styles.background(Some(color));
|
||||
character_chunk.add_selection_and_background(
|
||||
self.grid.selection,
|
||||
background_color,
|
||||
content_x,
|
||||
content_y,
|
||||
);
|
||||
}
|
||||
chunk_width += t_character.width;
|
||||
if chunk_width > max_width {
|
||||
break;
|
||||
}
|
||||
|
||||
if let Some(new_styles) = character_styles
|
||||
.update_and_return_diff(&t_character.styles, self.grid.changed_colors)
|
||||
{
|
||||
write!(
|
||||
&mut vte_output,
|
||||
"{}{}",
|
||||
new_styles,
|
||||
self.grid.link_handler.output_osc8(new_styles.link_anchor)
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
vte_output.push(t_character.character);
|
||||
}
|
||||
character_styles.clear();
|
||||
}
|
||||
if self.grid.ring_bell {
|
||||
let ring_bell = '\u{7}';
|
||||
vte_output.push(ring_bell);
|
||||
raw_vte_output.push(ring_bell);
|
||||
self.grid.ring_bell = false;
|
||||
}
|
||||
self.set_should_render(false);
|
||||
Some(vte_output)
|
||||
Some((character_chunks, Some(raw_vte_output)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
@ -289,9 +230,8 @@ impl Pane for TerminalPane {
|
|||
client_id: ClientId,
|
||||
frame_params: FrameParams,
|
||||
input_mode: InputMode,
|
||||
) -> Option<String> {
|
||||
) -> Option<(Vec<CharacterChunk>, Option<String>)> {
|
||||
// TODO: remove the cursor stuff from here
|
||||
let mut vte_output = None;
|
||||
let pane_title = if self.pane_name.is_empty()
|
||||
&& input_mode == InputMode::RenamePane
|
||||
&& frame_params.is_main_client
|
||||
|
|
@ -316,18 +256,24 @@ impl Pane for TerminalPane {
|
|||
Some(last_frame) => {
|
||||
if &frame != last_frame {
|
||||
if !self.borderless {
|
||||
vte_output = Some(frame.render());
|
||||
}
|
||||
let frame_output = frame.render();
|
||||
self.frame.insert(client_id, frame);
|
||||
Some(frame_output)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
vte_output
|
||||
}
|
||||
None => {
|
||||
if !self.borderless {
|
||||
vte_output = Some(frame.render());
|
||||
}
|
||||
let frame_output = frame.render();
|
||||
self.frame.insert(client_id, frame);
|
||||
vte_output
|
||||
Some(frame_output)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -452,7 +398,7 @@ impl Pane for TerminalPane {
|
|||
self.grid.pending_messages_to_pty.drain(..).collect()
|
||||
}
|
||||
|
||||
fn start_selection(&mut self, start: &Position, client_id: ClientId) {
|
||||
fn start_selection(&mut self, start: &Position, _client_id: ClientId) {
|
||||
self.grid.start_selection(start);
|
||||
self.set_should_render(true);
|
||||
}
|
||||
|
|
@ -511,12 +457,14 @@ impl TerminalPane {
|
|||
palette: Palette,
|
||||
pane_index: usize,
|
||||
pane_name: String,
|
||||
link_handler: Rc<RefCell<LinkHandler>>,
|
||||
) -> TerminalPane {
|
||||
let initial_pane_title = format!("Pane #{}", pane_index);
|
||||
let grid = Grid::new(
|
||||
position_and_size.rows.as_usize(),
|
||||
position_and_size.cols.as_usize(),
|
||||
palette,
|
||||
link_handler.clone(),
|
||||
);
|
||||
TerminalPane {
|
||||
frame: HashMap::new(),
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,9 @@
|
|||
use super::super::TerminalPane;
|
||||
use crate::panes::LinkHandler;
|
||||
use crate::tab::Pane;
|
||||
use ::insta::assert_snapshot;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use zellij_utils::pane_size::PaneGeom;
|
||||
use zellij_utils::zellij_tile::data::Palette;
|
||||
|
||||
|
|
@ -15,7 +18,14 @@ pub fn scrolling_inside_a_pane() {
|
|||
|
||||
let pid = 1;
|
||||
let palette = Palette::default();
|
||||
let mut terminal_pane = TerminalPane::new(pid, fake_win_size, palette, 0, String::new()); // 0 is the pane index
|
||||
let mut terminal_pane = TerminalPane::new(
|
||||
pid,
|
||||
fake_win_size,
|
||||
palette,
|
||||
0,
|
||||
String::new(),
|
||||
Rc::new(RefCell::new(LinkHandler::new())),
|
||||
); // 0 is the pane index
|
||||
let mut text_to_fill_pane = String::new();
|
||||
for i in 0..30 {
|
||||
writeln!(&mut text_to_fill_pane, "\rline {}", i + 1).unwrap();
|
||||
|
|
|
|||
|
|
@ -226,6 +226,21 @@ fn route_action(
|
|||
};
|
||||
session.senders.send_to_pty(pty_instr).unwrap();
|
||||
}
|
||||
Action::TogglePaneEmbedOrFloating => {
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::TogglePaneEmbedOrFloating(client_id))
|
||||
.unwrap();
|
||||
}
|
||||
Action::ToggleFloatingPanes => {
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::ToggleFloatingPanes(
|
||||
client_id,
|
||||
session.default_shell.clone(),
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
Action::PaneNameInput(c) => {
|
||||
session
|
||||
.senders
|
||||
|
|
|
|||
|
|
@ -8,12 +8,15 @@ use std::str;
|
|||
|
||||
use zellij_utils::input::options::Clipboard;
|
||||
use zellij_utils::pane_size::Size;
|
||||
use zellij_utils::{input::layout::Layout, position::Position, zellij_tile};
|
||||
use zellij_utils::{
|
||||
input::command::TerminalAction, input::layout::Layout, position::Position, zellij_tile,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
output::Output,
|
||||
panes::PaneId,
|
||||
pty::{ClientOrTabIndex, PtyInstruction, VteBytes},
|
||||
tab::{Output, Tab},
|
||||
tab::Tab,
|
||||
thread_bus::Bus,
|
||||
ui::overlay::{Overlay, OverlayWindow, Overlayable},
|
||||
wasm_vm::PluginInstruction,
|
||||
|
|
@ -32,6 +35,8 @@ pub enum ScreenInstruction {
|
|||
PtyBytes(RawFd, VteBytes),
|
||||
Render,
|
||||
NewPane(PaneId, ClientOrTabIndex),
|
||||
TogglePaneEmbedOrFloating(ClientId),
|
||||
ToggleFloatingPanes(ClientId, Option<TerminalAction>),
|
||||
HorizontalSplit(PaneId, ClientId),
|
||||
VerticalSplit(PaneId, ClientId),
|
||||
WriteCharacter(Vec<u8>, ClientId),
|
||||
|
|
@ -101,6 +106,10 @@ impl From<&ScreenInstruction> for ScreenContext {
|
|||
ScreenInstruction::PtyBytes(..) => ScreenContext::HandlePtyBytes,
|
||||
ScreenInstruction::Render => ScreenContext::Render,
|
||||
ScreenInstruction::NewPane(..) => ScreenContext::NewPane,
|
||||
ScreenInstruction::TogglePaneEmbedOrFloating(..) => {
|
||||
ScreenContext::TogglePaneEmbedOrFloating
|
||||
}
|
||||
ScreenInstruction::ToggleFloatingPanes(..) => ScreenContext::ToggleFloatingPanes,
|
||||
ScreenInstruction::HorizontalSplit(..) => ScreenContext::HorizontalSplit,
|
||||
ScreenInstruction::VerticalSplit(..) => ScreenContext::VerticalSplit,
|
||||
ScreenInstruction::WriteCharacter(..) => ScreenContext::WriteCharacter,
|
||||
|
|
@ -364,7 +373,7 @@ impl Screen {
|
|||
|
||||
fn close_tab_at_index(&mut self, tab_index: usize) {
|
||||
let mut tab_to_close = self.tabs.remove(&tab_index).unwrap();
|
||||
let pane_ids = tab_to_close.get_pane_ids();
|
||||
let pane_ids = tab_to_close.get_all_pane_ids();
|
||||
// below we don't check the result of sending the CloseTab instruction to the pty thread
|
||||
// because this might be happening when the app is closing, at which point the pty thread
|
||||
// has already closed and this would result in an error
|
||||
|
|
@ -429,9 +438,10 @@ impl Screen {
|
|||
for tab_index in tabs_to_close {
|
||||
self.close_tab_at_index(tab_index);
|
||||
}
|
||||
let serialized_output = output.serialize();
|
||||
self.bus
|
||||
.senders
|
||||
.send_to_server(ServerInstruction::Render(Some(output)))
|
||||
.send_to_server(ServerInstruction::Render(Some(serialized_output)))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
|
|
@ -748,6 +758,30 @@ pub(crate) fn screen_thread_main(
|
|||
|
||||
screen.render();
|
||||
}
|
||||
ScreenInstruction::TogglePaneEmbedOrFloating(client_id) => {
|
||||
screen
|
||||
.get_active_tab_mut(client_id)
|
||||
.unwrap()
|
||||
.toggle_pane_embed_or_floating(client_id);
|
||||
screen
|
||||
.bus
|
||||
.senders
|
||||
.send_to_server(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
screen.render();
|
||||
}
|
||||
ScreenInstruction::ToggleFloatingPanes(client_id, default_shell) => {
|
||||
screen
|
||||
.get_active_tab_mut(client_id)
|
||||
.unwrap()
|
||||
.toggle_floating_panes(client_id, default_shell);
|
||||
screen
|
||||
.bus
|
||||
.senders
|
||||
.send_to_server(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
screen.render();
|
||||
}
|
||||
ScreenInstruction::HorizontalSplit(pid, client_id) => {
|
||||
screen
|
||||
.get_active_tab_mut(client_id)
|
||||
|
|
@ -1055,7 +1089,7 @@ pub(crate) fn screen_thread_main(
|
|||
}
|
||||
None => {
|
||||
for tab in screen.tabs.values_mut() {
|
||||
if tab.get_pane_ids().contains(&id) {
|
||||
if tab.get_all_pane_ids().contains(&id) {
|
||||
tab.close_pane(id);
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,9 +29,9 @@ impl ClipboardProvider {
|
|||
Clipboard::Primary => 'c',
|
||||
Clipboard::System => 'c',
|
||||
};
|
||||
output.push_str_to_multiple_clients(
|
||||
&format!("\u{1b}]52;{};{}\u{1b}\\", dest, base64::encode(content)),
|
||||
output.add_pre_vte_instruction_to_multiple_clients(
|
||||
client_ids,
|
||||
&format!("\u{1b}]52;{};{}\u{1b}\\", dest, base64::encode(content)),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
1243
zellij-server/src/tab/floating_pane_grid.rs
Normal file
1243
zellij-server/src/tab/floating_pane_grid.rs
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,5 @@
|
|||
use super::pane_resizer::PaneResizer;
|
||||
use crate::tab::is_inside_viewport;
|
||||
use crate::tab::{is_inside_viewport, MIN_TERMINAL_HEIGHT, MIN_TERMINAL_WIDTH};
|
||||
use crate::{panes::PaneId, tab::Pane};
|
||||
use std::cmp::Reverse;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
|
@ -14,26 +14,22 @@ use std::rc::Rc;
|
|||
const RESIZE_PERCENT: f64 = 5.0;
|
||||
const CURSOR_HEIGHT_WIDTH_RATIO: usize = 4; // this is not accurate and kind of a magic number, TODO: look into this
|
||||
|
||||
// FIXME: This should be replaced by `RESIZE_PERCENT` at some point
|
||||
const MIN_TERMINAL_HEIGHT: usize = 5;
|
||||
const MIN_TERMINAL_WIDTH: usize = 5;
|
||||
type BorderAndPaneIds = (usize, Vec<PaneId>);
|
||||
|
||||
pub struct PaneGrid<'a> {
|
||||
// panes: HashMap<&'a PaneId, &'a mut Box<dyn Pane>>,
|
||||
pub struct TiledPaneGrid<'a> {
|
||||
panes: Rc<RefCell<HashMap<PaneId, &'a mut Box<dyn Pane>>>>,
|
||||
display_area: Size, // includes all panes (including eg. the status bar and tab bar in the default layout)
|
||||
viewport: Viewport, // includes all non-UI panes
|
||||
}
|
||||
|
||||
impl<'a> PaneGrid<'a> {
|
||||
impl<'a> TiledPaneGrid<'a> {
|
||||
pub fn new(
|
||||
panes: impl IntoIterator<Item = (&'a PaneId, &'a mut Box<dyn Pane>)>,
|
||||
display_area: Size,
|
||||
viewport: Viewport,
|
||||
) -> Self {
|
||||
let panes: HashMap<_, _> = panes.into_iter().map(|(p_id, p)| (*p_id, p)).collect();
|
||||
PaneGrid {
|
||||
TiledPaneGrid {
|
||||
panes: Rc::new(RefCell::new(panes)),
|
||||
display_area,
|
||||
viewport,
|
||||
|
|
@ -1643,7 +1639,6 @@ impl<'a> PaneGrid<'a> {
|
|||
pub fn fill_space_over_pane(&mut self, id: PaneId) -> bool {
|
||||
// true => successfully filled space over pane
|
||||
// false => didn't succeed, so didn't do anything
|
||||
log::info!("fill_space_over_pane");
|
||||
let (freed_width, freed_height) = {
|
||||
let panes = self.panes.borrow_mut();
|
||||
let pane_to_close = panes.get(&id).unwrap();
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
|
||||
expression: snapshot
|
||||
|
||||
---
|
||||
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
01 (C): │ │
|
||||
02 (C): │ │
|
||||
03 (C): │ │
|
||||
04 (C): │ I am an embedded pane │
|
||||
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): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
|
||||
expression: snapshot
|
||||
|
||||
---
|
||||
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
01 (C): │ │
|
||||
02 (C): │ │
|
||||
03 (C): │ │
|
||||
04 (C): │ │
|
||||
05 (C): │ │
|
||||
06 (C): │ ┌ Pane #2 ─────────────────────────────────────────────┐ │
|
||||
07 (C): │ │ │ │
|
||||
08 (C): │ │ │ │
|
||||
09 (C): │ │ │ │
|
||||
10 (C): │ │ I am scratch terminal │ │
|
||||
11 (C): │ │ │ │
|
||||
12 (C): │ │ │ │
|
||||
13 (C): │ └──────────────────────────────────────────────────────┘ │
|
||||
14 (C): │ │
|
||||
15 (C): │ │
|
||||
16 (C): │ │
|
||||
17 (C): │ │
|
||||
18 (C): │ │
|
||||
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
|
||||
expression: snapshot
|
||||
|
||||
---
|
||||
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
01 (C): │ │
|
||||
02 (C): │ ┌ Pane #2 ─────────────── SCROLL: 0/1 ┐ ┌ Pane #2 ─────────────── SCROLL: 0/1 ┐ │
|
||||
03 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
04 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
05 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
06 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
07 (C): │ └───────────────────────────────┌ Pane #2 ─────────────────────────────────────────────────┐────────────────────────┘ │
|
||||
08 (C): │ │ │ │
|
||||
09 (C): │ │ │ │
|
||||
10 (C): │ │ │ │
|
||||
11 (C): │ │ I am scratch terminal │ │
|
||||
12 (C): │ ┌ Pane #2 ─────────────── SCROLL│ │────────── SCROLL: 0/1 ┐ │
|
||||
13 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
14 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
15 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
16 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE└──────────────────────────────────────────────────────────┘EEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
17 (C): │ └──────────────────────────────────────┘ └──────────────────────────────────────┘ │
|
||||
18 (C): │ │
|
||||
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
|
||||
expression: snapshot
|
||||
|
||||
---
|
||||
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
|
||||
01 (C): │ ││ │
|
||||
02 (C): │ ││ │
|
||||
03 (C): │ ││ │
|
||||
04 (C): │ ││ I am scratch terminal │
|
||||
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): └───────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
|
||||
expression: snapshot
|
||||
|
||||
---
|
||||
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
01 (C): │ │
|
||||
02 (C): │ ┌ Pane #2 ─────────────── SCROLL: 0/1 ┐ ┌ Pane #2 ─────────────── SCROLL: 0/1 ┐ │
|
||||
03 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
04 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
05 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│─────────────────────────────────────│EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
06 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
07 (C): │ └──────────────────────────────────────┘ └──────────────────────────────────────┘ │
|
||||
08 (C): │ │ │ │
|
||||
09 (C): │ │ I am scratch terminal │ │
|
||||
10 (C): │ │ │ │
|
||||
11 (C): │ │ │ │
|
||||
12 (C): │ ┌ Pane #2 ─────────────── SCROLL: 0/1 ┐ ┌ Pane #2 ─────────────── SCROLL: 0/1 ┐ │
|
||||
13 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
14 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│─────────────────────────────────────│EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
15 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
16 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
17 (C): │ └──────────────────────────────────────┘ └──────────────────────────────────────┘ │
|
||||
18 (C): │ │
|
||||
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
|
||||
expression: snapshot
|
||||
|
||||
---
|
||||
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
01 (C): │ │
|
||||
02 (C): │ │
|
||||
03 (C): │ │
|
||||
04 (C): │ │
|
||||
05 (C): │ ┌ Pane #2 ─────────────────────────────────────────────────┐ │
|
||||
06 (C): │ │ │ │
|
||||
07 (C): │ │ │ │
|
||||
08 (C): │ │ │ │
|
||||
09 (C): │ │ I am an embedded pane │ │
|
||||
10 (C): │ │ │ │
|
||||
11 (C): │ │ │ │
|
||||
12 (C): │ │ │ │
|
||||
13 (C): │ │ │ │
|
||||
14 (C): │ └──────────────────────────────────────────────────────────┘ │
|
||||
15 (C): │ │
|
||||
16 (C): │ │
|
||||
17 (C): │ │
|
||||
18 (C): │ │
|
||||
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
|
||||
expression: snapshot
|
||||
|
||||
---
|
||||
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
01 (C): │ │
|
||||
02 (C): │ │
|
||||
03 (C): │ │
|
||||
04 (C): │ │
|
||||
05 (C): │ ┌ Pane #2 ─────────────────────────────────────────────────┐ │
|
||||
06 (C): │ │ │ │
|
||||
07 (C): │ │ │ │
|
||||
08 (C): │ │ │ │
|
||||
09 (C): │ │ I am scratch terminal │ │
|
||||
10 (C): │ │ │ │
|
||||
11 (C): │ │ │ │
|
||||
12 (C): │ │ │ │
|
||||
13 (C): │ │ │ │
|
||||
14 (C): │ └──────────────────────────────────────────────────────────┘ │
|
||||
15 (C): │ │
|
||||
16 (C): │ │
|
||||
17 (C): │ │
|
||||
18 (C): │ │
|
||||
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
|
||||
expression: snapshot
|
||||
|
||||
---
|
||||
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
01 (C): │ │
|
||||
02 (C): │ │
|
||||
03 (C): │ │
|
||||
04 (C): │ ┌ Pane #2 ─────────────────────────────────────────────────────┐ │
|
||||
05 (C): │ │ │ │
|
||||
06 (C): │ │ │ │
|
||||
07 (C): │ │ │ │
|
||||
08 (C): │ │ I am scratch terminal │ │
|
||||
09 (C): │ │ │ │
|
||||
10 (C): │ │ │ │
|
||||
11 (C): │ │ │ │
|
||||
12 (C): │ │ │ │
|
||||
13 (C): │ │ │ │
|
||||
14 (C): │ │ │ │
|
||||
15 (C): │ └──────────────────────────────────────────────────────────────┘ │
|
||||
16 (C): │ │
|
||||
17 (C): │ │
|
||||
18 (C): │ │
|
||||
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
|
||||
expression: snapshot
|
||||
|
||||
---
|
||||
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
01 (C): │ │
|
||||
02 (C): │ ┌ Pane #2 ─────────────── SCROLL: 0/1 ┐ ┌ Pane #2 ─────────────── SCROLL: 0/1 ┐ │
|
||||
03 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
04 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
05 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEE┌ Pane #2 ─────────────────────────────────────────────────┐EEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
06 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
07 (C): │ └───────────────────────────│ │────────────────────────────┘ │
|
||||
08 (C): │ │ │ │
|
||||
09 (C): │ │ I am scratch terminal │ │
|
||||
10 (C): │ │ │ │
|
||||
11 (C): │ │ │ │
|
||||
12 (C): │ ┌ Pane #2 ─────────────── SC│ │────────────── SCROLL: 0/1 ┐ │
|
||||
13 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
14 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEE└──────────────────────────────────────────────────────────┘EEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
15 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
16 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
17 (C): │ └──────────────────────────────────────┘ └──────────────────────────────────────┘ │
|
||||
18 (C): │ │
|
||||
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
|
||||
expression: snapshot
|
||||
|
||||
---
|
||||
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
01 (C): │ │
|
||||
02 (C): │ ┌ Pane #2 ─────────────── SCROLL: 0/1 ┐ ┌ Pane #2 ─────────────── SCROLL: 0/1 ┐ │
|
||||
03 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
04 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
05 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEE┌ Pane #2 ─────────────────────────────────────────────────┐EEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
06 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
07 (C): │ └───────────────────────────│ │────────────────────────────┘ │
|
||||
08 (C): │ │ │ │
|
||||
09 (C): │ │ I am scratch terminal │ │
|
||||
10 (C): │ │ │ │
|
||||
11 (C): │ │ │ │
|
||||
12 (C): │ ┌ Pane #2 ─────────────── SC│ ┌ Pane #2 ─────────────── SCROLL: 0/1 ┐ │
|
||||
13 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
14 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEE└────────────────────────────────────────────────│EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
15 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
16 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
17 (C): │ └──────────────────────────────────────┘ └──────────────────────────────────────┘ │
|
||||
18 (C): │ │
|
||||
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
|
||||
expression: snapshot
|
||||
|
||||
---
|
||||
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
01 (C): │ │
|
||||
02 (C): │ ┌ Pane #2 ─────────────── SCROLL: 0/1 ┐ ┌ Pane #2 ─────────────── SCROLL: 0/1 ┐ │
|
||||
03 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
04 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
05 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEE┌ Pane #2 ─────────────────────────────────────────────────┐EEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
06 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
07 (C): │ └───────────────────────────│ │────────────────────────────┘ │
|
||||
08 (C): │ │ │ │
|
||||
09 (C): │ │ I am scratch terminal │ │
|
||||
10 (C): │ │ │ │
|
||||
11 (C): │ │ │ │
|
||||
12 (C): │ ┌ Pane #2 ─────────────── SC│ │────────────── SCROLL: 0/1 ┐ │
|
||||
13 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
14 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEE└──────────────────────────────────────────────────────────┘EEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
15 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
16 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
17 (C): │ └──────────────────────────────────────┘ └──────────────────────────────────────┘ │
|
||||
18 (C): │ │
|
||||
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
|
||||
expression: snapshot
|
||||
|
||||
---
|
||||
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
01 (C): │ │
|
||||
02 (C): │ ┌ Pane #2 ─────────────── SCROLL: 0/1 ┐ ┌ Pane #2 ─────────────── SCROLL: 0/1 ┐ │
|
||||
03 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
04 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
05 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEE┌ Pane #2 ───────────────────────────────────────│EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
06 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
07 (C): │ └───────────────────────────│ └──────────────────────────────────────┘ │
|
||||
08 (C): │ │ │ │
|
||||
09 (C): │ │ I am scratch terminal │ │
|
||||
10 (C): │ │ │ │
|
||||
11 (C): │ │ │ │
|
||||
12 (C): │ ┌ Pane #2 ─────────────── SC│ │────────────── SCROLL: 0/1 ┐ │
|
||||
13 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
14 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEE└──────────────────────────────────────────────────────────┘EEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
15 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
16 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
17 (C): │ └──────────────────────────────────────┘ └──────────────────────────────────────┘ │
|
||||
18 (C): │ │
|
||||
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
|
||||
expression: snapshot
|
||||
|
||||
---
|
||||
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
01 (C): │ │
|
||||
02 (C): │ ┌ Pane #2 ─────────────── SCROLL: 0/1 ┐ ┌ Pane #2 ─────────────── SCROLL: 0/1 ┐ │
|
||||
03 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
04 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
05 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEE┌ Pane #2 ─────────────────────────────────────────────────┐EEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
06 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
07 (C): │ └───────────────────────────│ │────────────────────────────┘ │
|
||||
08 (C): │ │ │ │
|
||||
09 (C): │ │ I am scratch terminal │ │
|
||||
10 (C): │ │ │ │
|
||||
11 (C): │ │ │ │
|
||||
12 (C): │ ┌ Pane #2 ─────────────── SC│ │────────────── SCROLL: 0/1 ┐ │
|
||||
13 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
14 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEE└──────────────────────────────────────────────────────────┘EEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
15 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
16 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
17 (C): │ └──────────────────────────────────────┘ └──────────────────────────────────────┘ │
|
||||
18 (C): │ │
|
||||
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
|
||||
expression: snapshot
|
||||
|
||||
---
|
||||
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
01 (C): │ │
|
||||
02 (C): │ ┌ Pane #2 ─────────────── SCROLL: 0/1 ┐ ┌ Pane #2 ─────────────── SCROLL: 0/1 ┐ │
|
||||
03 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
04 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
05 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEE┌ Pane #2 ─────────────────────────────────────────────────┐EEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
06 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
07 (C): │ └───────────────────────────│ │────────────────────────────┘ │
|
||||
08 (C): │ │ │ │
|
||||
09 (C): │ │ I am scratch terminal │ │
|
||||
10 (C): │ │ │ │
|
||||
11 (C): │ │ │ │
|
||||
12 (C): │ ┌ Pane #2 ─────────────── SC│ │────────────── SCROLL: 0/1 ┐ │
|
||||
13 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
14 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEE└──────────────────────────────────────────────────────────┘EEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
15 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
16 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
17 (C): │ └──────────────────────────────────────┘ └──────────────────────────────────────┘ │
|
||||
18 (C): │ │
|
||||
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
|
||||
expression: snapshot
|
||||
|
||||
---
|
||||
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
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): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
|
||||
expression: snapshot
|
||||
|
||||
---
|
||||
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
01 (C): │ │
|
||||
02 (C): │ │
|
||||
03 (C): │ │
|
||||
04 (C): │ │
|
||||
05 (C): │ ┌ Pane #2 ─────────────────────────────────────────────────┐ │
|
||||
06 (C): │ │ │ │
|
||||
07 (C): │ │ │ │
|
||||
08 (C): │ │ │ │
|
||||
09 (C): │ │ I am scratch terminal │ │
|
||||
10 (C): │ │ │ │
|
||||
11 (C): │ │ │ │
|
||||
12 (C): │ │ │ │
|
||||
13 (C): │ │ │ │
|
||||
14 (C): │ └──────────────────────────────────────────────────────────┘ │
|
||||
15 (C): │ │
|
||||
16 (C): │ │
|
||||
17 (C): │ │
|
||||
18 (C): │ │
|
||||
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
|
||||
expression: snapshot
|
||||
|
||||
---
|
||||
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
01 (C): │ │
|
||||
02 (C): │ │
|
||||
03 (C): │ │
|
||||
04 (C): │ │
|
||||
05 (C): │ ┌ Pane #2 ─────────────────────────────────────────────────┐ │
|
||||
06 (C): │ │ │ │
|
||||
07 (C): │ │ │ │
|
||||
08 (C): │ │ │ │
|
||||
09 (C): │ │ I am scratch terminal │ │
|
||||
10 (C): │ │ │ │
|
||||
11 (C): │ │ │ │
|
||||
12 (C): │ │ │ │
|
||||
13 (C): │ │ │ │
|
||||
14 (C): │ │ │ │
|
||||
15 (C): │ │ │ │
|
||||
16 (C): │ └──────────────────────────────────────────────────────────┘ │
|
||||
17 (C): │ │
|
||||
18 (C): │ │
|
||||
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
|
||||
expression: snapshot
|
||||
|
||||
---
|
||||
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
01 (C): │ │
|
||||
02 (C): │ │
|
||||
03 (C): │ │
|
||||
04 (C): │ │
|
||||
05 (C): │ ┌ Pane #2 ──────────────────────────────────────────────────────┐ │
|
||||
06 (C): │ │ │ │
|
||||
07 (C): │ │ │ │
|
||||
08 (C): │ │ │ │
|
||||
09 (C): │ │ I am scratch terminal │ │
|
||||
10 (C): │ │ │ │
|
||||
11 (C): │ │ │ │
|
||||
12 (C): │ │ │ │
|
||||
13 (C): │ │ │ │
|
||||
14 (C): │ └───────────────────────────────────────────────────────────────┘ │
|
||||
15 (C): │ │
|
||||
16 (C): │ │
|
||||
17 (C): │ │
|
||||
18 (C): │ │
|
||||
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
|
||||
expression: snapshot
|
||||
|
||||
---
|
||||
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
01 (C): │ │
|
||||
02 (C): │ │
|
||||
03 (C): │ │
|
||||
04 (C): │ │
|
||||
05 (C): │ ┌ Pane #2 ──────────────────────────────────────────────────────┐ │
|
||||
06 (C): │ │ │ │
|
||||
07 (C): │ │ │ │
|
||||
08 (C): │ │ │ │
|
||||
09 (C): │ │ I am scratch terminal │ │
|
||||
10 (C): │ │ │ │
|
||||
11 (C): │ │ │ │
|
||||
12 (C): │ │ │ │
|
||||
13 (C): │ │ │ │
|
||||
14 (C): │ └───────────────────────────────────────────────────────────────┘ │
|
||||
15 (C): │ │
|
||||
16 (C): │ │
|
||||
17 (C): │ │
|
||||
18 (C): │ │
|
||||
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
|
||||
expression: snapshot
|
||||
|
||||
---
|
||||
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
01 (C): │ │
|
||||
02 (C): │ │
|
||||
03 (C): │ ┌ Pane #2 ─────────────────────────────────────────────────┐ │
|
||||
04 (C): │ │ │ │
|
||||
05 (C): │ │ │ │
|
||||
06 (C): │ │ │ │
|
||||
07 (C): │ │ I am scratch terminal │ │
|
||||
08 (C): │ │ │ │
|
||||
09 (C): │ │ │ │
|
||||
10 (C): │ │ │ │
|
||||
11 (C): │ │ │ │
|
||||
12 (C): │ │ │ │
|
||||
13 (C): │ │ │ │
|
||||
14 (C): │ └──────────────────────────────────────────────────────────┘ │
|
||||
15 (C): │ │
|
||||
16 (C): │ │
|
||||
17 (C): │ │
|
||||
18 (C): │ │
|
||||
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
|
||||
expression: snapshot
|
||||
|
||||
---
|
||||
00 (C): ┌ Pane #1 ────────────────────┌ Pane #2 ─────────────────────────────────────────────────┐─────────┐
|
||||
01 (C): │ │ │ │
|
||||
02 (C): │ ┌ Pane #2 ─────────────── SCROLL: 0/1 ┐ ┌ Pane #2 ─────────────── SCROLL: 0/1 ┐
|
||||
03 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│
|
||||
04 (C): │ ┌ Pane #2 ─────────────── SCROLL: 0/1 ┐ I am scrat┌ Pane #2 ─────────────── SCROLL: 0/1 ┐
|
||||
05 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│
|
||||
06 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│
|
||||
07 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│
|
||||
08 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│
|
||||
09 (C): └─└──────────────────────────────────────┘──────────────────└──────────────────────────────────────┘
|
||||
10 (C):
|
||||
11 (C):
|
||||
12 (C):
|
||||
13 (C):
|
||||
14 (C):
|
||||
15 (C):
|
||||
16 (C):
|
||||
17 (C):
|
||||
18 (C):
|
||||
19 (C):
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
|
||||
expression: snapshot
|
||||
|
||||
---
|
||||
00 (C): ┌ Pane #1 ────────────────────┌ Pane #2 ─────────┐
|
||||
01 (C): │ │ │
|
||||
02 (C): │ ┌ Pane #┌ Pane #2 ─────────────── SCROLL: 0/1 ┐
|
||||
03 (C): │ │EEEEEEE│EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│
|
||||
04 (C): │ ┌ Pane #┌ Pane #2 ─────────────── SCROLL: 0/1 ┐
|
||||
05 (C): │ │EEEEEEE│EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│
|
||||
06 (C): │ │EEEEEEE│EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│
|
||||
07 (C): │ │EEEEEEE│EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│
|
||||
08 (C): │ │EEEEEEE│EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│
|
||||
09 (C): └─└───────└──────────────────────────────────────┘
|
||||
10 (C):
|
||||
11 (C):
|
||||
12 (C):
|
||||
13 (C):
|
||||
14 (C):
|
||||
15 (C):
|
||||
16 (C):
|
||||
17 (C):
|
||||
18 (C):
|
||||
19 (C):
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
|
||||
expression: snapshot
|
||||
|
||||
---
|
||||
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
01 (C): │ │
|
||||
02 (C): │ ┌ Pane #2 ─────────────── SCROLL: 0/1 ┐ ┌ Pane #2 ─────────────── SCROLL: 0/1 ┐ │
|
||||
03 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
04 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
05 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│─────────────────────────────────────│EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
06 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
07 (C): │ └──────────────────────────────────────┘ └──────────────────────────────────────┘ │
|
||||
08 (C): │ │ │ │
|
||||
09 (C): │ │ I am scratch terminal │ │
|
||||
10 (C): │ │ │ │
|
||||
11 (C): │ │ │ │
|
||||
12 (C): │ ┌ Pane #2 ─────────────── SCROLL: 0/1 ┐ ┌ Pane #2 ─────────────── SCROLL: 0/1 ┐ │
|
||||
13 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
14 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│─────────────────────────────────────│EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
15 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
16 (C): │ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE│ │
|
||||
17 (C): │ └──────────────────────────────────────┘ └──────────────────────────────────────┘ │
|
||||
18 (C): │ │
|
||||
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
|
||||
expression: snapshot
|
||||
|
||||
---
|
||||
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
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): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
|
||||
expression: snapshot
|
||||
|
||||
---
|
||||
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
01 (C): │ │
|
||||
02 (C): │ │
|
||||
03 (C): │ │
|
||||
04 (C): │ │
|
||||
05 (C): │ ┌ Pane #2 ─────────────────────────────────────────────────┐ │
|
||||
06 (C): │ │ │ │
|
||||
07 (C): │ │ │ │
|
||||
08 (C): │ │ │ │
|
||||
09 (C): │ │ I am scratch terminal │ │
|
||||
10 (C): │ │ │ │
|
||||
11 (C): │ │ │ │
|
||||
12 (C): │ │ │ │
|
||||
13 (C): │ │ │ │
|
||||
14 (C): │ └──────────────────────────────────────────────────────────┘ │
|
||||
15 (C): │ │
|
||||
16 (C): │ │
|
||||
17 (C): │ │
|
||||
18 (C): │ │
|
||||
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
1058
zellij-server/src/tab/unit/tab_integration_tests.rs
Normal file
1058
zellij-server/src/tab/unit/tab_integration_tests.rs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,9 +1,10 @@
|
|||
use zellij_utils::{pane_size::Viewport, zellij_tile};
|
||||
|
||||
use crate::output::CharacterChunk;
|
||||
use crate::panes::terminal_character::{TerminalCharacter, EMPTY_TERMINAL_CHARACTER, RESET_STYLES};
|
||||
use crate::tab::Pane;
|
||||
use ansi_term::Colour::{Fixed, RGB};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Write;
|
||||
use zellij_tile::data::PaletteColor;
|
||||
use zellij_utils::shared::colors;
|
||||
|
||||
|
|
@ -43,6 +44,19 @@ impl BoundarySymbol {
|
|||
self.color = color;
|
||||
*self
|
||||
}
|
||||
pub fn as_terminal_character(&self) -> TerminalCharacter {
|
||||
if self.invisible {
|
||||
EMPTY_TERMINAL_CHARACTER
|
||||
} else {
|
||||
let character = self.boundary_type.chars().next().unwrap();
|
||||
TerminalCharacter {
|
||||
character,
|
||||
width: 1,
|
||||
styles: RESET_STYLES
|
||||
.foreground(self.color.map(|palette_color| palette_color.into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for BoundarySymbol {
|
||||
|
|
@ -511,19 +525,16 @@ impl Boundaries {
|
|||
}
|
||||
}
|
||||
}
|
||||
pub fn vte_output(&self) -> String {
|
||||
let mut vte_output = String::new();
|
||||
pub fn render(&self) -> Vec<CharacterChunk> {
|
||||
let mut character_chunks = vec![];
|
||||
for (coordinates, boundary_character) in &self.boundary_characters {
|
||||
write!(
|
||||
&mut vte_output,
|
||||
"\u{1b}[{};{}H\u{1b}[m{}",
|
||||
coordinates.y + 1,
|
||||
coordinates.x + 1,
|
||||
boundary_character
|
||||
)
|
||||
.unwrap(); // goto row/col + boundary character
|
||||
character_chunks.push(CharacterChunk::new(
|
||||
vec![boundary_character.as_terminal_character()],
|
||||
coordinates.x,
|
||||
coordinates.y,
|
||||
));
|
||||
}
|
||||
vte_output
|
||||
character_chunks
|
||||
}
|
||||
fn rect_right_boundary_is_before_screen_edge(&self, rect: &dyn Pane) -> bool {
|
||||
rect.x() + rect.cols() < self.viewport.cols
|
||||
|
|
|
|||
|
|
@ -1,32 +1,64 @@
|
|||
use crate::output::CharacterChunk;
|
||||
use crate::panes::{AnsiCode, CharacterStyles, TerminalCharacter, EMPTY_TERMINAL_CHARACTER};
|
||||
use crate::ui::boundaries::boundary_type;
|
||||
use crate::ClientId;
|
||||
use ansi_term::Colour::{Fixed, RGB};
|
||||
use ansi_term::Style;
|
||||
use zellij_utils::zellij_tile::prelude::{client_id_to_colors, Palette, PaletteColor};
|
||||
use zellij_utils::{envs::get_session_name, pane_size::Viewport};
|
||||
|
||||
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
||||
|
||||
use std::fmt::Write;
|
||||
|
||||
fn color_string(character: &str, color: Option<PaletteColor>) -> String {
|
||||
match color {
|
||||
Some(PaletteColor::Rgb((r, g, b))) => RGB(r, g, b).bold().paint(character).to_string(),
|
||||
Some(PaletteColor::EightBit(color)) => Fixed(color).bold().paint(character).to_string(),
|
||||
None => Style::new().bold().paint(character).to_string(),
|
||||
fn foreground_color(characters: &str, color: Option<PaletteColor>) -> Vec<TerminalCharacter> {
|
||||
let mut colored_string = Vec::with_capacity(characters.chars().count());
|
||||
for character in characters.chars() {
|
||||
let styles = match color {
|
||||
Some(palette_color) => {
|
||||
let mut styles = CharacterStyles::new();
|
||||
styles.reset_all();
|
||||
styles
|
||||
.foreground(Some(AnsiCode::from(palette_color)))
|
||||
.bold(Some(AnsiCode::On))
|
||||
}
|
||||
None => {
|
||||
let mut styles = CharacterStyles::new();
|
||||
styles.reset_all();
|
||||
styles.bold(Some(AnsiCode::On))
|
||||
}
|
||||
};
|
||||
let terminal_character = TerminalCharacter {
|
||||
character,
|
||||
styles,
|
||||
width: character.width().unwrap_or(0),
|
||||
};
|
||||
colored_string.push(terminal_character);
|
||||
}
|
||||
colored_string
|
||||
}
|
||||
|
||||
fn background_color(character: &str, color: Option<PaletteColor>) -> String {
|
||||
match color {
|
||||
Some(PaletteColor::Rgb((r, g, b))) => {
|
||||
Style::new().on(RGB(r, g, b)).paint(character).to_string()
|
||||
fn background_color(characters: &str, color: Option<PaletteColor>) -> Vec<TerminalCharacter> {
|
||||
let mut colored_string = Vec::with_capacity(characters.chars().count());
|
||||
for character in characters.chars() {
|
||||
let styles = match color {
|
||||
Some(palette_color) => {
|
||||
let mut styles = CharacterStyles::new();
|
||||
styles.reset_all();
|
||||
styles
|
||||
.background(Some(AnsiCode::from(palette_color)))
|
||||
.bold(Some(AnsiCode::On))
|
||||
}
|
||||
Some(PaletteColor::EightBit(color)) => {
|
||||
Style::new().on(Fixed(color)).paint(character).to_string()
|
||||
None => {
|
||||
let mut styles = CharacterStyles::new();
|
||||
styles.reset_all();
|
||||
styles
|
||||
}
|
||||
None => character.to_string(),
|
||||
};
|
||||
let terminal_character = TerminalCharacter {
|
||||
character,
|
||||
styles,
|
||||
width: character.width().unwrap_or(0),
|
||||
};
|
||||
colored_string.push(terminal_character);
|
||||
}
|
||||
colored_string
|
||||
}
|
||||
|
||||
pub struct FrameParams {
|
||||
|
|
@ -70,11 +102,14 @@ impl PaneFrame {
|
|||
other_cursors_exist_in_session: frame_params.other_cursors_exist_in_session,
|
||||
}
|
||||
}
|
||||
fn client_cursor(&self, client_id: ClientId) -> String {
|
||||
fn client_cursor(&self, client_id: ClientId) -> Vec<TerminalCharacter> {
|
||||
let color = client_id_to_colors(client_id, self.colors);
|
||||
background_color(" ", color.map(|c| c.0))
|
||||
}
|
||||
fn render_title_right_side(&self, max_length: usize) -> Option<(String, usize)> {
|
||||
fn render_title_right_side(
|
||||
&self,
|
||||
max_length: usize,
|
||||
) -> Option<(Vec<TerminalCharacter>, usize)> {
|
||||
// string and length because of color
|
||||
if self.scroll_position.0 > 0 || self.scroll_position.1 > 0 {
|
||||
let prefix = " SCROLL: ";
|
||||
|
|
@ -86,17 +121,17 @@ impl PaneFrame {
|
|||
let prefix_len = prefix.chars().count();
|
||||
if prefix_len + full_indication_len <= max_length {
|
||||
Some((
|
||||
color_string(&format!("{}{}", prefix, full_indication), self.color),
|
||||
foreground_color(&format!("{}{}", prefix, full_indication), self.color),
|
||||
prefix_len + full_indication_len,
|
||||
))
|
||||
} else if full_indication_len <= max_length {
|
||||
Some((
|
||||
color_string(&full_indication, self.color),
|
||||
foreground_color(&full_indication, self.color),
|
||||
full_indication_len,
|
||||
))
|
||||
} else if short_indication_len <= max_length {
|
||||
Some((
|
||||
color_string(&short_indication, self.color),
|
||||
foreground_color(&short_indication, self.color),
|
||||
short_indication_len,
|
||||
))
|
||||
} else {
|
||||
|
|
@ -106,24 +141,24 @@ impl PaneFrame {
|
|||
None
|
||||
}
|
||||
}
|
||||
fn render_my_focus(&self, max_length: usize) -> Option<(String, usize)> {
|
||||
let left_separator = color_string(boundary_type::VERTICAL_LEFT, self.color);
|
||||
let right_separator = color_string(boundary_type::VERTICAL_RIGHT, self.color);
|
||||
fn render_my_focus(&self, max_length: usize) -> Option<(Vec<TerminalCharacter>, usize)> {
|
||||
let left_separator = foreground_color(boundary_type::VERTICAL_LEFT, self.color);
|
||||
let right_separator = foreground_color(boundary_type::VERTICAL_RIGHT, self.color);
|
||||
let full_indication_text = "MY FOCUS";
|
||||
let full_indication = format!(
|
||||
"{} {} {}",
|
||||
left_separator,
|
||||
color_string(full_indication_text, self.color),
|
||||
right_separator
|
||||
);
|
||||
let mut full_indication = vec![];
|
||||
full_indication.append(&mut left_separator.clone());
|
||||
full_indication.push(EMPTY_TERMINAL_CHARACTER);
|
||||
full_indication.append(&mut foreground_color(full_indication_text, self.color));
|
||||
full_indication.push(EMPTY_TERMINAL_CHARACTER);
|
||||
full_indication.append(&mut right_separator.clone());
|
||||
let full_indication_len = full_indication_text.width() + 4; // 2 for separators 2 for padding
|
||||
let short_indication_text = "ME";
|
||||
let short_indication = format!(
|
||||
"{} {} {}",
|
||||
left_separator,
|
||||
color_string(short_indication_text, self.color),
|
||||
right_separator
|
||||
);
|
||||
let mut short_indication = vec![];
|
||||
short_indication.append(&mut left_separator.clone());
|
||||
short_indication.push(EMPTY_TERMINAL_CHARACTER);
|
||||
short_indication.append(&mut foreground_color(short_indication_text, self.color));
|
||||
short_indication.push(EMPTY_TERMINAL_CHARACTER);
|
||||
short_indication.append(&mut right_separator.clone());
|
||||
let short_indication_len = short_indication_text.width() + 4; // 2 for separators 2 for padding
|
||||
if full_indication_len <= max_length {
|
||||
Some((full_indication, full_indication_len))
|
||||
|
|
@ -133,91 +168,110 @@ impl PaneFrame {
|
|||
None
|
||||
}
|
||||
}
|
||||
fn render_my_and_others_focus(&self, max_length: usize) -> Option<(String, usize)> {
|
||||
let left_separator = color_string(boundary_type::VERTICAL_LEFT, self.color);
|
||||
let right_separator = color_string(boundary_type::VERTICAL_RIGHT, self.color);
|
||||
fn render_my_and_others_focus(
|
||||
&self,
|
||||
max_length: usize,
|
||||
) -> Option<(Vec<TerminalCharacter>, usize)> {
|
||||
let mut left_separator = foreground_color(boundary_type::VERTICAL_LEFT, self.color);
|
||||
let mut right_separator = foreground_color(boundary_type::VERTICAL_RIGHT, self.color);
|
||||
let full_indication_text = "MY FOCUS AND:";
|
||||
let short_indication_text = "+";
|
||||
let mut full_indication = color_string(full_indication_text, self.color);
|
||||
let mut full_indication = foreground_color(full_indication_text, self.color);
|
||||
let mut full_indication_len = full_indication_text.width();
|
||||
let mut short_indication = color_string(short_indication_text, self.color);
|
||||
let mut short_indication = foreground_color(short_indication_text, self.color);
|
||||
let mut short_indication_len = short_indication_text.width();
|
||||
for client_id in &self.other_focused_clients {
|
||||
let text = format!(" {}", self.client_cursor(*client_id));
|
||||
let mut text = self.client_cursor(*client_id);
|
||||
full_indication_len += 2;
|
||||
full_indication.push_str(&text);
|
||||
full_indication.push(EMPTY_TERMINAL_CHARACTER);
|
||||
full_indication.append(&mut text.clone());
|
||||
short_indication_len += 2;
|
||||
short_indication.push_str(&text);
|
||||
short_indication.append(&mut text);
|
||||
}
|
||||
if full_indication_len + 4 <= max_length {
|
||||
// 2 for separators, 2 for padding
|
||||
Some((
|
||||
format!("{} {} {}", left_separator, full_indication, right_separator),
|
||||
full_indication_len + 4,
|
||||
))
|
||||
let mut ret = vec![];
|
||||
ret.append(&mut left_separator);
|
||||
ret.push(EMPTY_TERMINAL_CHARACTER);
|
||||
ret.append(&mut full_indication);
|
||||
ret.push(EMPTY_TERMINAL_CHARACTER);
|
||||
ret.append(&mut right_separator);
|
||||
Some((ret, full_indication_len + 4))
|
||||
} else if short_indication_len + 4 <= max_length {
|
||||
// 2 for separators, 2 for padding
|
||||
Some((
|
||||
format!(
|
||||
"{} {} {}",
|
||||
left_separator, short_indication, right_separator
|
||||
),
|
||||
short_indication_len + 4,
|
||||
))
|
||||
let mut ret = vec![];
|
||||
ret.append(&mut left_separator);
|
||||
ret.push(EMPTY_TERMINAL_CHARACTER);
|
||||
ret.append(&mut short_indication);
|
||||
ret.push(EMPTY_TERMINAL_CHARACTER);
|
||||
ret.append(&mut right_separator);
|
||||
Some((ret, short_indication_len + 4))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn render_other_focused_users(&self, max_length: usize) -> Option<(String, usize)> {
|
||||
let left_separator = color_string(boundary_type::VERTICAL_LEFT, self.color);
|
||||
let right_separator = color_string(boundary_type::VERTICAL_RIGHT, self.color);
|
||||
fn render_other_focused_users(
|
||||
&self,
|
||||
max_length: usize,
|
||||
) -> Option<(Vec<TerminalCharacter>, usize)> {
|
||||
let mut left_separator = foreground_color(boundary_type::VERTICAL_LEFT, self.color);
|
||||
let mut right_separator = foreground_color(boundary_type::VERTICAL_RIGHT, self.color);
|
||||
let full_indication_text = if self.other_focused_clients.len() == 1 {
|
||||
"FOCUSED USER:"
|
||||
} else {
|
||||
"FOCUSED USERS:"
|
||||
};
|
||||
let middle_indication_text = "U:";
|
||||
let mut full_indication = color_string(full_indication_text, self.color);
|
||||
let mut full_indication = foreground_color(full_indication_text, self.color);
|
||||
let mut full_indication_len = full_indication_text.width();
|
||||
let mut middle_indication = color_string(middle_indication_text, self.color);
|
||||
let mut middle_indication = foreground_color(middle_indication_text, self.color);
|
||||
let mut middle_indication_len = middle_indication_text.width();
|
||||
let mut short_indication = String::from("");
|
||||
let mut short_indication = vec![];
|
||||
let mut short_indication_len = 0;
|
||||
for client_id in &self.other_focused_clients {
|
||||
let text = format!(" {}", self.client_cursor(*client_id));
|
||||
let mut text = self.client_cursor(*client_id);
|
||||
full_indication_len += 2;
|
||||
full_indication.push_str(&text);
|
||||
full_indication.push(EMPTY_TERMINAL_CHARACTER);
|
||||
full_indication.append(&mut text.clone());
|
||||
middle_indication_len += 2;
|
||||
middle_indication.push_str(&text);
|
||||
middle_indication.push(EMPTY_TERMINAL_CHARACTER);
|
||||
middle_indication.append(&mut text.clone());
|
||||
short_indication_len += 2;
|
||||
short_indication.push_str(&text);
|
||||
short_indication.push(EMPTY_TERMINAL_CHARACTER);
|
||||
short_indication.append(&mut text);
|
||||
}
|
||||
if full_indication_len + 4 <= max_length {
|
||||
// 2 for separators, 2 for padding
|
||||
Some((
|
||||
format!("{} {} {}", left_separator, full_indication, right_separator),
|
||||
full_indication_len + 4,
|
||||
))
|
||||
let mut ret = vec![];
|
||||
ret.append(&mut left_separator);
|
||||
ret.push(EMPTY_TERMINAL_CHARACTER);
|
||||
ret.append(&mut full_indication);
|
||||
ret.push(EMPTY_TERMINAL_CHARACTER);
|
||||
ret.append(&mut right_separator);
|
||||
Some((ret, full_indication_len + 4))
|
||||
} else if middle_indication_len + 4 <= max_length {
|
||||
// 2 for separators, 2 for padding
|
||||
Some((
|
||||
format!(
|
||||
"{} {} {}",
|
||||
left_separator, middle_indication, right_separator
|
||||
),
|
||||
middle_indication_len + 4,
|
||||
))
|
||||
let mut ret = vec![];
|
||||
ret.append(&mut left_separator);
|
||||
ret.push(EMPTY_TERMINAL_CHARACTER);
|
||||
ret.append(&mut middle_indication);
|
||||
ret.push(EMPTY_TERMINAL_CHARACTER);
|
||||
ret.append(&mut right_separator);
|
||||
Some((ret, middle_indication_len + 4))
|
||||
} else if short_indication_len + 3 <= max_length {
|
||||
// 2 for separators, 1 for padding
|
||||
Some((
|
||||
format!("{}{} {}", left_separator, short_indication, right_separator),
|
||||
short_indication_len + 3,
|
||||
))
|
||||
let mut ret = vec![];
|
||||
ret.append(&mut left_separator);
|
||||
ret.push(EMPTY_TERMINAL_CHARACTER);
|
||||
ret.append(&mut short_indication);
|
||||
ret.push(EMPTY_TERMINAL_CHARACTER);
|
||||
ret.append(&mut right_separator);
|
||||
Some((ret, short_indication_len + 3))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn render_title_middle(&self, max_length: usize) -> Option<(String, usize)> {
|
||||
fn render_title_middle(&self, max_length: usize) -> Option<(Vec<TerminalCharacter>, usize)> {
|
||||
// string and length because of color
|
||||
if self.is_main_client
|
||||
&& self.other_focused_clients.is_empty()
|
||||
|
|
@ -237,7 +291,7 @@ impl PaneFrame {
|
|||
None
|
||||
}
|
||||
}
|
||||
fn render_title_left_side(&self, max_length: usize) -> Option<(String, usize)> {
|
||||
fn render_title_left_side(&self, max_length: usize) -> Option<(Vec<TerminalCharacter>, usize)> {
|
||||
let middle_truncated_sign = "[..]";
|
||||
let middle_truncated_sign_long = "[...]";
|
||||
let full_text = format!(" {} ", &self.title);
|
||||
|
|
@ -245,7 +299,7 @@ impl PaneFrame {
|
|||
None
|
||||
} else if full_text.width() <= max_length {
|
||||
Some((
|
||||
color_string(&full_text, self.color),
|
||||
foreground_color(&full_text, self.color),
|
||||
full_text.chars().count(),
|
||||
))
|
||||
} else {
|
||||
|
|
@ -269,35 +323,39 @@ impl PaneFrame {
|
|||
}
|
||||
}
|
||||
|
||||
let title_left_side =
|
||||
if first_part.width() + middle_truncated_sign.width() + second_part.width()
|
||||
let (title_left_side, title_length) = if first_part.width()
|
||||
+ middle_truncated_sign.width()
|
||||
+ second_part.width()
|
||||
< max_length
|
||||
{
|
||||
// this means we lost 1 character when dividing the total length into halves
|
||||
(
|
||||
format!(
|
||||
"{}{}{}",
|
||||
first_part, middle_truncated_sign_long, second_part
|
||||
),
|
||||
first_part.width() + middle_truncated_sign_long.width() + second_part.width(),
|
||||
)
|
||||
} else {
|
||||
format!("{}{}{}", first_part, middle_truncated_sign, second_part)
|
||||
(
|
||||
format!("{}{}{}", first_part, middle_truncated_sign, second_part),
|
||||
first_part.width() + middle_truncated_sign.width() + second_part.width(),
|
||||
)
|
||||
};
|
||||
Some((
|
||||
color_string(&title_left_side, self.color),
|
||||
title_left_side.chars().count(),
|
||||
))
|
||||
Some((foreground_color(&title_left_side, self.color), title_length))
|
||||
}
|
||||
}
|
||||
fn three_part_title_line(
|
||||
&self,
|
||||
left_side: &str,
|
||||
mut left_side: Vec<TerminalCharacter>,
|
||||
left_side_len: &usize,
|
||||
middle: &str,
|
||||
mut middle: Vec<TerminalCharacter>,
|
||||
middle_len: &usize,
|
||||
right_side: &str,
|
||||
mut right_side: Vec<TerminalCharacter>,
|
||||
right_side_len: &usize,
|
||||
) -> String {
|
||||
) -> Vec<TerminalCharacter> {
|
||||
let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
|
||||
let mut title_line = String::new();
|
||||
let mut title_line = vec![];
|
||||
let left_side_start_position = self.geom.x + 1;
|
||||
let middle_start_position = self.geom.x + (total_title_length / 2) - (middle_len / 2) + 1;
|
||||
let right_side_start_position =
|
||||
|
|
@ -306,24 +364,23 @@ impl PaneFrame {
|
|||
let mut col = self.geom.x;
|
||||
loop {
|
||||
if col == self.geom.x {
|
||||
title_line.push_str(&color_string(boundary_type::TOP_LEFT, self.color));
|
||||
title_line.append(&mut foreground_color(boundary_type::TOP_LEFT, self.color));
|
||||
} else if col == self.geom.x + self.geom.cols - 1 {
|
||||
title_line.push_str(&color_string(boundary_type::TOP_RIGHT, self.color));
|
||||
title_line.append(&mut foreground_color(boundary_type::TOP_RIGHT, self.color));
|
||||
} else if col == left_side_start_position {
|
||||
title_line.push_str(left_side);
|
||||
title_line.append(&mut left_side);
|
||||
col += left_side_len;
|
||||
continue;
|
||||
} else if col == middle_start_position {
|
||||
title_line.push_str(middle);
|
||||
title_line.append(&mut middle);
|
||||
col += middle_len;
|
||||
continue;
|
||||
} else if col == right_side_start_position {
|
||||
title_line.push_str(right_side);
|
||||
title_line.append(&mut right_side);
|
||||
col += right_side_len;
|
||||
continue;
|
||||
} else {
|
||||
title_line.push_str(&color_string(boundary_type::HORIZONTAL, self.color));
|
||||
// TODO: BETTER
|
||||
title_line.append(&mut foreground_color(boundary_type::HORIZONTAL, self.color));
|
||||
}
|
||||
if col == self.geom.x + self.geom.cols - 1 {
|
||||
break;
|
||||
|
|
@ -334,33 +391,32 @@ impl PaneFrame {
|
|||
}
|
||||
fn left_and_middle_title_line(
|
||||
&self,
|
||||
left_side: &str,
|
||||
mut left_side: Vec<TerminalCharacter>,
|
||||
left_side_len: &usize,
|
||||
middle: &str,
|
||||
mut middle: Vec<TerminalCharacter>,
|
||||
middle_len: &usize,
|
||||
) -> String {
|
||||
) -> Vec<TerminalCharacter> {
|
||||
let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
|
||||
let mut title_line = String::new();
|
||||
let mut title_line = vec![];
|
||||
let left_side_start_position = self.geom.x + 1;
|
||||
let middle_start_position = self.geom.x + (total_title_length / 2) - (*middle_len / 2) + 1;
|
||||
|
||||
let mut col = self.geom.x;
|
||||
loop {
|
||||
if col == self.geom.x {
|
||||
title_line.push_str(&color_string(boundary_type::TOP_LEFT, self.color));
|
||||
title_line.append(&mut foreground_color(boundary_type::TOP_LEFT, self.color));
|
||||
} else if col == self.geom.x + self.geom.cols - 1 {
|
||||
title_line.push_str(&color_string(boundary_type::TOP_RIGHT, self.color));
|
||||
title_line.append(&mut foreground_color(boundary_type::TOP_RIGHT, self.color));
|
||||
} else if col == left_side_start_position {
|
||||
title_line.push_str(left_side);
|
||||
title_line.append(&mut left_side);
|
||||
col += *left_side_len;
|
||||
continue;
|
||||
} else if col == middle_start_position {
|
||||
title_line.push_str(middle);
|
||||
title_line.append(&mut middle);
|
||||
col += *middle_len;
|
||||
continue;
|
||||
} else {
|
||||
title_line.push_str(&color_string(boundary_type::HORIZONTAL, self.color));
|
||||
// TODO: BETTER
|
||||
title_line.append(&mut foreground_color(boundary_type::HORIZONTAL, self.color));
|
||||
}
|
||||
if col == self.geom.x + self.geom.cols - 1 {
|
||||
break;
|
||||
|
|
@ -369,24 +425,27 @@ impl PaneFrame {
|
|||
}
|
||||
title_line
|
||||
}
|
||||
fn middle_only_title_line(&self, middle: &str, middle_len: &usize) -> String {
|
||||
fn middle_only_title_line(
|
||||
&self,
|
||||
mut middle: Vec<TerminalCharacter>,
|
||||
middle_len: &usize,
|
||||
) -> Vec<TerminalCharacter> {
|
||||
let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
|
||||
let mut title_line = String::new();
|
||||
let mut title_line = vec![];
|
||||
let middle_start_position = self.geom.x + (total_title_length / 2) - (*middle_len / 2) + 1;
|
||||
|
||||
let mut col = self.geom.x;
|
||||
loop {
|
||||
if col == self.geom.x {
|
||||
title_line.push_str(&color_string(boundary_type::TOP_LEFT, self.color));
|
||||
title_line.append(&mut foreground_color(boundary_type::TOP_LEFT, self.color));
|
||||
} else if col == self.geom.x + self.geom.cols - 1 {
|
||||
title_line.push_str(&color_string(boundary_type::TOP_RIGHT, self.color));
|
||||
title_line.append(&mut foreground_color(boundary_type::TOP_RIGHT, self.color));
|
||||
} else if col == middle_start_position {
|
||||
title_line.push_str(middle);
|
||||
title_line.append(&mut middle);
|
||||
col += *middle_len;
|
||||
continue;
|
||||
} else {
|
||||
title_line.push_str(&color_string(boundary_type::HORIZONTAL, self.color));
|
||||
// TODO: BETTER
|
||||
title_line.append(&mut foreground_color(boundary_type::HORIZONTAL, self.color));
|
||||
}
|
||||
if col == self.geom.x + self.geom.cols - 1 {
|
||||
break;
|
||||
|
|
@ -397,81 +456,86 @@ impl PaneFrame {
|
|||
}
|
||||
fn two_part_title_line(
|
||||
&self,
|
||||
left_side: &str,
|
||||
mut left_side: Vec<TerminalCharacter>,
|
||||
left_side_len: &usize,
|
||||
right_side: &str,
|
||||
mut right_side: Vec<TerminalCharacter>,
|
||||
right_side_len: &usize,
|
||||
) -> String {
|
||||
let left_boundary = color_string(boundary_type::TOP_LEFT, self.color);
|
||||
let right_boundary = color_string(boundary_type::TOP_RIGHT, self.color);
|
||||
) -> Vec<TerminalCharacter> {
|
||||
let mut left_boundary = foreground_color(boundary_type::TOP_LEFT, self.color);
|
||||
let mut right_boundary = foreground_color(boundary_type::TOP_RIGHT, self.color);
|
||||
let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
|
||||
let mut middle = String::new();
|
||||
for _ in (left_side_len + right_side_len)..total_title_length {
|
||||
middle.push_str(boundary_type::HORIZONTAL);
|
||||
}
|
||||
format!(
|
||||
"{}{}{}{}{}",
|
||||
left_boundary,
|
||||
left_side,
|
||||
color_string(&middle, self.color),
|
||||
color_string(right_side, self.color),
|
||||
&right_boundary
|
||||
)
|
||||
let mut ret = vec![];
|
||||
ret.append(&mut left_boundary);
|
||||
ret.append(&mut left_side);
|
||||
ret.append(&mut foreground_color(&middle, self.color));
|
||||
ret.append(&mut right_side);
|
||||
ret.append(&mut right_boundary);
|
||||
ret
|
||||
}
|
||||
fn left_only_title_line(&self, left_side: &str, left_side_len: &usize) -> String {
|
||||
let left_boundary = color_string(boundary_type::TOP_LEFT, self.color);
|
||||
let right_boundary = color_string(boundary_type::TOP_RIGHT, self.color);
|
||||
fn left_only_title_line(
|
||||
&self,
|
||||
mut left_side: Vec<TerminalCharacter>,
|
||||
left_side_len: &usize,
|
||||
) -> Vec<TerminalCharacter> {
|
||||
let mut left_boundary = foreground_color(boundary_type::TOP_LEFT, self.color);
|
||||
let mut right_boundary = foreground_color(boundary_type::TOP_RIGHT, self.color);
|
||||
let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
|
||||
let mut middle_padding = String::new();
|
||||
for _ in *left_side_len..total_title_length {
|
||||
middle_padding.push_str(boundary_type::HORIZONTAL);
|
||||
}
|
||||
format!(
|
||||
"{}{}{}{}",
|
||||
left_boundary,
|
||||
left_side,
|
||||
color_string(&middle_padding, self.color),
|
||||
&right_boundary
|
||||
)
|
||||
let mut ret = vec![];
|
||||
ret.append(&mut left_boundary);
|
||||
ret.append(&mut left_side);
|
||||
ret.append(&mut foreground_color(&middle_padding, self.color));
|
||||
ret.append(&mut right_boundary);
|
||||
ret
|
||||
}
|
||||
fn empty_title_line(&self) -> String {
|
||||
let left_boundary = color_string(boundary_type::TOP_LEFT, self.color);
|
||||
let right_boundary = color_string(boundary_type::TOP_RIGHT, self.color);
|
||||
fn empty_title_line(&self) -> Vec<TerminalCharacter> {
|
||||
let mut left_boundary = foreground_color(boundary_type::TOP_LEFT, self.color);
|
||||
let mut right_boundary = foreground_color(boundary_type::TOP_RIGHT, self.color);
|
||||
let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
|
||||
let mut middle_padding = String::new();
|
||||
for _ in 0..total_title_length {
|
||||
middle_padding.push_str(boundary_type::HORIZONTAL);
|
||||
}
|
||||
format!(
|
||||
"{}{}{}",
|
||||
left_boundary,
|
||||
color_string(&middle_padding, self.color),
|
||||
right_boundary
|
||||
)
|
||||
let mut ret = vec![];
|
||||
ret.append(&mut left_boundary);
|
||||
ret.append(&mut foreground_color(&middle_padding, self.color));
|
||||
ret.append(&mut right_boundary);
|
||||
ret
|
||||
}
|
||||
fn title_line_with_middle(&self, middle: &str, middle_len: &usize) -> String {
|
||||
fn title_line_with_middle(
|
||||
&self,
|
||||
middle: Vec<TerminalCharacter>,
|
||||
middle_len: &usize,
|
||||
) -> Vec<TerminalCharacter> {
|
||||
let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
|
||||
let length_of_each_side = total_title_length.saturating_sub(*middle_len + 2) / 2;
|
||||
let mut left_side = self.render_title_left_side(length_of_each_side);
|
||||
let mut right_side = self.render_title_right_side(length_of_each_side);
|
||||
let left_side = self.render_title_left_side(length_of_each_side);
|
||||
let right_side = self.render_title_right_side(length_of_each_side);
|
||||
|
||||
match (&mut left_side, &mut right_side) {
|
||||
match (left_side, right_side) {
|
||||
(Some((left_side, left_side_len)), Some((right_side, right_side_len))) => self
|
||||
.three_part_title_line(
|
||||
left_side,
|
||||
left_side_len,
|
||||
&left_side_len,
|
||||
middle,
|
||||
middle_len,
|
||||
right_side,
|
||||
right_side_len,
|
||||
&right_side_len,
|
||||
),
|
||||
(Some((left_side, left_side_len)), None) => {
|
||||
self.left_and_middle_title_line(left_side, left_side_len, middle, middle_len)
|
||||
self.left_and_middle_title_line(left_side, &left_side_len, middle, middle_len)
|
||||
}
|
||||
_ => self.middle_only_title_line(middle, middle_len),
|
||||
}
|
||||
}
|
||||
fn title_line_without_middle(&self) -> String {
|
||||
fn title_line_without_middle(&self) -> Vec<TerminalCharacter> {
|
||||
let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
|
||||
let left_side = self.render_title_left_side(total_title_length);
|
||||
let right_side = left_side.as_ref().and_then(|(_left_side, left_side_len)| {
|
||||
|
|
@ -480,96 +544,74 @@ impl PaneFrame {
|
|||
});
|
||||
match (left_side, right_side) {
|
||||
(Some((left_side, left_side_len)), Some((right_side, right_side_len))) => {
|
||||
self.two_part_title_line(&left_side, &left_side_len, &right_side, &right_side_len)
|
||||
self.two_part_title_line(left_side, &left_side_len, right_side, &right_side_len)
|
||||
}
|
||||
(Some((left_side, left_side_len)), None) => {
|
||||
self.left_only_title_line(&left_side, &left_side_len)
|
||||
self.left_only_title_line(left_side, &left_side_len)
|
||||
}
|
||||
_ => self.empty_title_line(),
|
||||
}
|
||||
}
|
||||
fn render_title(&self, vte_output: &mut String) {
|
||||
fn render_title(&self) -> Vec<TerminalCharacter> {
|
||||
let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
|
||||
|
||||
if let Some((middle, middle_length)) = &self.render_title_middle(total_title_length) {
|
||||
let title_text = self.title_line_with_middle(middle, middle_length);
|
||||
write!(
|
||||
vte_output,
|
||||
"\u{1b}[{};{}H\u{1b}[m{}",
|
||||
self.geom.y + 1, // +1 because goto is 1 indexed
|
||||
self.geom.x + 1, // +1 because goto is 1 indexed
|
||||
color_string(&title_text, self.color),
|
||||
)
|
||||
.unwrap(); // goto row/col + boundary character
|
||||
} else {
|
||||
let title_text = self.title_line_without_middle();
|
||||
write!(
|
||||
vte_output,
|
||||
"\u{1b}[{};{}H\u{1b}[m{}",
|
||||
self.geom.y + 1, // +1 because goto is 1 indexed
|
||||
self.geom.x + 1, // +1 because goto is 1 indexed
|
||||
color_string(&title_text, self.color),
|
||||
)
|
||||
.unwrap(); // goto row/col + boundary character
|
||||
self.render_title_middle(total_title_length)
|
||||
.map(|(middle, middle_length)| self.title_line_with_middle(middle, &middle_length))
|
||||
.or_else(|| Some(self.title_line_without_middle()))
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
pub fn render(&self) -> String {
|
||||
let mut vte_output = String::new();
|
||||
for row in self.geom.y..(self.geom.y + self.geom.rows) {
|
||||
if row == self.geom.y {
|
||||
pub fn render(&self) -> (Vec<CharacterChunk>, Option<String>) {
|
||||
let mut character_chunks = vec![];
|
||||
for row in 0..self.geom.rows {
|
||||
if row == 0 {
|
||||
// top row
|
||||
self.render_title(&mut vte_output);
|
||||
} else if row == self.geom.y + self.geom.rows - 1 {
|
||||
let title = self.render_title();
|
||||
let x = self.geom.x;
|
||||
let y = self.geom.y + row;
|
||||
character_chunks.push(CharacterChunk::new(title, x, y));
|
||||
} else if row == self.geom.rows - 1 {
|
||||
// bottom row
|
||||
for col in self.geom.x..(self.geom.x + self.geom.cols) {
|
||||
let boundary = if col == self.geom.x {
|
||||
let mut bottom_row = vec![];
|
||||
for col in 0..self.geom.cols {
|
||||
let boundary = if col == 0 {
|
||||
// bottom left corner
|
||||
boundary_type::BOTTOM_LEFT
|
||||
} else if col == self.geom.x + self.geom.cols - 1 {
|
||||
} else if col == self.geom.cols - 1 {
|
||||
// bottom right corner
|
||||
boundary_type::BOTTOM_RIGHT
|
||||
} else {
|
||||
boundary_type::HORIZONTAL
|
||||
};
|
||||
|
||||
let boundary_rendered = color_string(boundary, self.color);
|
||||
write!(
|
||||
&mut vte_output,
|
||||
"\u{1b}[{};{}H\u{1b}[m{}",
|
||||
row + 1,
|
||||
col + 1,
|
||||
boundary_rendered
|
||||
)
|
||||
.unwrap();
|
||||
let mut boundary_character = foreground_color(boundary, self.color);
|
||||
bottom_row.append(&mut boundary_character);
|
||||
}
|
||||
let x = self.geom.x;
|
||||
let y = self.geom.y + row;
|
||||
character_chunks.push(CharacterChunk::new(bottom_row, x, y));
|
||||
} else {
|
||||
write!(
|
||||
&mut vte_output,
|
||||
"\u{1b}[{};{}H\u{1b}[m{}",
|
||||
row + 1, // +1 because goto is 1 indexed
|
||||
self.geom.x + 1,
|
||||
color_string(boundary_type::VERTICAL, self.color),
|
||||
)
|
||||
.unwrap(); // goto row/col + boundary character
|
||||
write!(
|
||||
&mut vte_output,
|
||||
"\u{1b}[{};{}H\u{1b}[m{}",
|
||||
row + 1, // +1 because goto is 1 indexed
|
||||
self.geom.x + self.geom.cols,
|
||||
color_string(boundary_type::VERTICAL, self.color),
|
||||
)
|
||||
.unwrap(); // goto row/col + boundary character
|
||||
let boundary_character_left = foreground_color(boundary_type::VERTICAL, self.color);
|
||||
let boundary_character_right =
|
||||
foreground_color(boundary_type::VERTICAL, self.color);
|
||||
|
||||
let x = self.geom.x;
|
||||
let y = self.geom.y + row;
|
||||
character_chunks.push(CharacterChunk::new(boundary_character_left, x, y));
|
||||
|
||||
let x = (self.geom.x + self.geom.cols).saturating_sub(1);
|
||||
let y = self.geom.y + row;
|
||||
character_chunks.push(CharacterChunk::new(boundary_character_right, x, y));
|
||||
}
|
||||
}
|
||||
if self.is_main_client {
|
||||
write!(
|
||||
&mut vte_output,
|
||||
let vte_output = if self.is_main_client {
|
||||
Some(format!(
|
||||
"\u{1b}]0;Zellij ({}) - {}",
|
||||
get_session_name().unwrap(),
|
||||
self.title
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
vte_output
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
(character_chunks, vte_output)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use crate::output::Output;
|
||||
use crate::panes::PaneId;
|
||||
use crate::tab::{Output, Pane};
|
||||
use crate::tab::Pane;
|
||||
use crate::ui::boundaries::Boundaries;
|
||||
use crate::ui::pane_boundaries_frame::FrameParams;
|
||||
use crate::ClientId;
|
||||
|
|
@ -14,6 +15,7 @@ pub struct PaneContentsAndUi<'a> {
|
|||
colors: Palette,
|
||||
focused_clients: Vec<ClientId>,
|
||||
multiple_users_exist_in_session: bool,
|
||||
z_index: Option<usize>,
|
||||
}
|
||||
|
||||
impl<'a> PaneContentsAndUi<'a> {
|
||||
|
|
@ -23,6 +25,7 @@ impl<'a> PaneContentsAndUi<'a> {
|
|||
colors: Palette,
|
||||
active_panes: &HashMap<ClientId, PaneId>,
|
||||
multiple_users_exist_in_session: bool,
|
||||
z_index: Option<usize>,
|
||||
) -> Self {
|
||||
let focused_clients: Vec<ClientId> = active_panes
|
||||
.iter()
|
||||
|
|
@ -35,39 +38,50 @@ impl<'a> PaneContentsAndUi<'a> {
|
|||
colors,
|
||||
focused_clients,
|
||||
multiple_users_exist_in_session,
|
||||
z_index,
|
||||
}
|
||||
}
|
||||
pub fn render_pane_contents_to_multiple_clients(
|
||||
&mut self,
|
||||
clients: impl Iterator<Item = ClientId>,
|
||||
) {
|
||||
if let Some(vte_output) = self.pane.render(None) {
|
||||
// FIXME: Use Termion for cursor and style clearing?
|
||||
self.output.push_str_to_multiple_clients(
|
||||
if let Some((character_chunks, raw_vte_output)) = self.pane.render(None) {
|
||||
let clients: Vec<ClientId> = clients.collect();
|
||||
self.output.add_character_chunks_to_multiple_clients(
|
||||
character_chunks,
|
||||
clients.iter().copied(),
|
||||
self.z_index,
|
||||
);
|
||||
if let Some(raw_vte_output) = raw_vte_output {
|
||||
self.output.add_post_vte_instruction_to_multiple_clients(
|
||||
clients.iter().copied(),
|
||||
&format!(
|
||||
"\u{1b}[{};{}H\u{1b}[m{}",
|
||||
self.pane.y() + 1,
|
||||
self.pane.x() + 1,
|
||||
vte_output
|
||||
raw_vte_output
|
||||
),
|
||||
clients,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn render_pane_contents_for_client(&mut self, client_id: ClientId) {
|
||||
if let Some(vte_output) = self.pane.render(Some(client_id)) {
|
||||
// FIXME: Use Termion for cursor and style clearing?
|
||||
self.output.push_to_client(
|
||||
if let Some((character_chunks, raw_vte_output)) = self.pane.render(Some(client_id)) {
|
||||
self.output
|
||||
.add_character_chunks_to_client(client_id, character_chunks, self.z_index);
|
||||
if let Some(raw_vte_output) = raw_vte_output {
|
||||
self.output.add_post_vte_instruction_to_client(
|
||||
client_id,
|
||||
&format!(
|
||||
"\u{1b}[{};{}H\u{1b}[m{}",
|
||||
self.pane.y() + 1,
|
||||
self.pane.x() + 1,
|
||||
vte_output
|
||||
raw_vte_output
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn render_fake_cursor_if_needed(&mut self, client_id: ClientId) {
|
||||
let pane_focused_for_client_id = self.focused_clients.contains(&client_id);
|
||||
let pane_focused_for_different_client = self
|
||||
|
|
@ -84,7 +98,7 @@ impl<'a> PaneContentsAndUi<'a> {
|
|||
.unwrap();
|
||||
if let Some(colors) = client_id_to_colors(*fake_cursor_client_id, self.colors) {
|
||||
if let Some(vte_output) = self.pane.render_fake_cursor(colors.0, colors.1) {
|
||||
self.output.push_to_client(
|
||||
self.output.add_post_vte_instruction_to_client(
|
||||
client_id,
|
||||
&format!(
|
||||
"\u{1b}[{};{}H\u{1b}[m{}",
|
||||
|
|
@ -139,17 +153,18 @@ impl<'a> PaneContentsAndUi<'a> {
|
|||
other_cursors_exist_in_session: self.multiple_users_exist_in_session,
|
||||
}
|
||||
};
|
||||
if let Some(vte_output) = self.pane.render_frame(client_id, frame_params, client_mode) {
|
||||
// FIXME: Use Termion for cursor and style clearing?
|
||||
self.output.push_to_client(
|
||||
if let Some((frame_terminal_characters, vte_output)) =
|
||||
self.pane.render_frame(client_id, frame_params, client_mode)
|
||||
{
|
||||
self.output.add_character_chunks_to_client(
|
||||
client_id,
|
||||
&format!(
|
||||
"\u{1b}[{};{}H\u{1b}[m{}",
|
||||
self.pane.y() + 1,
|
||||
self.pane.x() + 1,
|
||||
vte_output
|
||||
),
|
||||
frame_terminal_characters,
|
||||
self.z_index,
|
||||
);
|
||||
if let Some(vte_output) = vte_output {
|
||||
self.output
|
||||
.add_post_vte_instruction_to_client(client_id, &vte_output);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn render_pane_boundaries(
|
||||
|
|
|
|||
|
|
@ -204,23 +204,8 @@ pub fn close_the_middle_tab() {
|
|||
new_tab(&mut screen, 1);
|
||||
new_tab(&mut screen, 2);
|
||||
new_tab(&mut screen, 3);
|
||||
dbg!(screen
|
||||
.tabs
|
||||
.values()
|
||||
.map(|t| (t.index, t.position, t.name.clone(), t.get_pane_ids()))
|
||||
.collect::<Vec<_>>());
|
||||
screen.switch_tab_prev(1);
|
||||
dbg!(screen
|
||||
.tabs
|
||||
.values()
|
||||
.map(|t| (t.index, t.position, t.name.clone(), t.get_pane_ids()))
|
||||
.collect::<Vec<_>>());
|
||||
screen.close_tab(1);
|
||||
dbg!(screen
|
||||
.tabs
|
||||
.values()
|
||||
.map(|t| (t.index, t.position, t.name.clone(), t.get_pane_ids()))
|
||||
.collect::<Vec<_>>());
|
||||
|
||||
assert_eq!(screen.tabs.len(), 2, "Two tabs left");
|
||||
assert_eq!(
|
||||
|
|
|
|||
|
|
@ -137,6 +137,10 @@ keybinds:
|
|||
key: [Char: 'f',]
|
||||
- action: [TogglePaneFrames, SwitchToMode: Normal,]
|
||||
key: [Char: 'z',]
|
||||
- action: [ToggleFloatingPanes, SwitchToMode: Normal,]
|
||||
key: [Char: 'w']
|
||||
- action: [TogglePaneEmbedOrFloating, SwitchToMode: Normal,]
|
||||
key: [Char: 'e']
|
||||
- action: [NewPane: ,]
|
||||
key: [ Alt: 'n',]
|
||||
- action: [MoveFocus: Left,]
|
||||
|
|
|
|||
8
zellij-utils/assets/layouts/no-plugins.yaml
Normal file
8
zellij-utils/assets/layouts/no-plugins.yaml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
template:
|
||||
direction: Horizontal
|
||||
parts:
|
||||
- direction: Vertical
|
||||
body: true
|
||||
tabs:
|
||||
- direction: Vertical
|
||||
|
|
@ -11,9 +11,11 @@ pub fn set_zellij(v: String) {
|
|||
}
|
||||
|
||||
pub const SESSION_NAME_ENV_KEY: &str = "ZELLIJ_SESSION_NAME";
|
||||
|
||||
pub fn get_session_name() -> Result<String> {
|
||||
Ok(var(SESSION_NAME_ENV_KEY)?)
|
||||
}
|
||||
|
||||
pub fn set_session_name(v: String) {
|
||||
set_var(SESSION_NAME_ENV_KEY, v);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -207,6 +207,8 @@ pub enum ScreenContext {
|
|||
HandlePtyBytes,
|
||||
Render,
|
||||
NewPane,
|
||||
ToggleFloatingPanes,
|
||||
TogglePaneEmbedOrFloating,
|
||||
HorizontalSplit,
|
||||
VerticalSplit,
|
||||
WriteCharacter,
|
||||
|
|
|
|||
|
|
@ -81,6 +81,10 @@ pub enum Action {
|
|||
/// Open a new pane in the specified direction (relative to focus).
|
||||
/// If no direction is specified, will try to use the biggest available space.
|
||||
NewPane(Option<Direction>),
|
||||
/// Embed focused pane in tab if floating or float focused pane if embedded
|
||||
TogglePaneEmbedOrFloating,
|
||||
/// Toggle the visibility of all floating panes (if any) in the current Tab
|
||||
ToggleFloatingPanes,
|
||||
/// Close the focus pane.
|
||||
CloseFocus,
|
||||
PaneNameInput(Vec<u8>),
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ pub fn get_mode_info(
|
|||
],
|
||||
InputMode::Pane => vec![
|
||||
("←↓↑→".to_string(), "Move focus".to_string()),
|
||||
("p".to_string(), "Next".to_string()),
|
||||
("n".to_string(), "New".to_string()),
|
||||
("d".to_string(), "Down split".to_string()),
|
||||
("r".to_string(), "Right split".to_string()),
|
||||
|
|
@ -41,6 +40,9 @@ pub fn get_mode_info(
|
|||
("f".to_string(), "Fullscreen".to_string()),
|
||||
("z".to_string(), "Frames".to_string()),
|
||||
("c".to_string(), "Rename".to_string()),
|
||||
("w".to_string(), "Floating Toggle".to_string()),
|
||||
("e".to_string(), "Embed Pane".to_string()),
|
||||
("p".to_string(), "Next".to_string()),
|
||||
],
|
||||
InputMode::Tab => vec![
|
||||
("←↓↑→".to_string(), "Move focus".to_string()),
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use crate::position::Position;
|
|||
|
||||
/// Contains the position and size of a [`Pane`], or more generally of any terminal, measured
|
||||
/// in character rows and columns.
|
||||
#[derive(Clone, Copy, Default, PartialEq, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Copy, Default, PartialEq, Debug, Serialize, Deserialize, Eq)]
|
||||
pub struct PaneGeom {
|
||||
pub x: usize,
|
||||
pub y: usize,
|
||||
|
|
@ -34,7 +34,7 @@ pub struct Size {
|
|||
pub cols: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
|
||||
#[derive(Eq, Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct Dimension {
|
||||
pub constraint: Constraint,
|
||||
inner: usize,
|
||||
|
|
@ -90,6 +90,8 @@ pub enum Constraint {
|
|||
Percent(f64),
|
||||
}
|
||||
|
||||
impl Eq for Constraint {}
|
||||
|
||||
impl PaneGeom {
|
||||
pub fn contains(&self, point: &Position) -> bool {
|
||||
let col = point.column.0 as usize;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,14 @@ impl Position {
|
|||
column: Column(self.column.0.saturating_sub(column)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn line(&self) -> isize {
|
||||
self.line.0
|
||||
}
|
||||
|
||||
pub fn column(&self) -> usize {
|
||||
self.column.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, PartialOrd)]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue