fix(panes): show visual error when unable to split panes vertically/horizontally (#2025)
* fix(panes): show visual error when failing to split pane vertically/horizontally * fix: lockfile
This commit is contained in:
parent
177cd20bea
commit
c3115a428e
15 changed files with 449 additions and 19 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
|
@ -3889,7 +3889,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zellij"
|
name = "zellij"
|
||||||
version = "0.34.4"
|
version = "0.34.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"dialoguer",
|
"dialoguer",
|
||||||
|
|
@ -3908,7 +3908,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zellij-client"
|
name = "zellij-client"
|
||||||
version = "0.34.4"
|
version = "0.34.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"insta",
|
"insta",
|
||||||
"log",
|
"log",
|
||||||
|
|
@ -3921,7 +3921,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zellij-server"
|
name = "zellij-server"
|
||||||
version = "0.34.4"
|
version = "0.34.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term",
|
"ansi_term",
|
||||||
"arrayvec 0.7.2",
|
"arrayvec 0.7.2",
|
||||||
|
|
@ -3951,7 +3951,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zellij-tile"
|
name = "zellij-tile"
|
||||||
version = "0.34.4"
|
version = "0.34.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
@ -3963,14 +3963,14 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zellij-tile-utils"
|
name = "zellij-tile-utils"
|
||||||
version = "0.34.4"
|
version = "0.34.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term",
|
"ansi_term",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zellij-utils"
|
name = "zellij-utils"
|
||||||
version = "0.34.4"
|
version = "0.34.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-std",
|
"async-std",
|
||||||
|
|
|
||||||
79
zellij-server/src/background_jobs.rs
Normal file
79
zellij-server/src/background_jobs.rs
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
use zellij_utils::async_std::task;
|
||||||
|
use zellij_utils::errors::{prelude::*, BackgroundJobContext, ContextType};
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use crate::panes::PaneId;
|
||||||
|
use crate::screen::ScreenInstruction;
|
||||||
|
use crate::thread_bus::Bus;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub enum BackgroundJob {
|
||||||
|
DisplayPaneError(PaneId, String),
|
||||||
|
Exit,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&BackgroundJob> for BackgroundJobContext {
|
||||||
|
fn from(background_job: &BackgroundJob) -> Self {
|
||||||
|
match *background_job {
|
||||||
|
BackgroundJob::DisplayPaneError(..) => BackgroundJobContext::DisplayPaneError,
|
||||||
|
BackgroundJob::Exit => BackgroundJobContext::Exit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static FLASH_DURATION_MS: u64 = 1000;
|
||||||
|
|
||||||
|
pub(crate) fn background_jobs_main(bus: Bus<BackgroundJob>) -> Result<()> {
|
||||||
|
let err_context = || "failed to write to pty".to_string();
|
||||||
|
let mut running_jobs: HashMap<BackgroundJob, Instant> = HashMap::new();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let (event, mut err_ctx) = bus.recv().with_context(err_context)?;
|
||||||
|
err_ctx.add_call(ContextType::BackgroundJob((&event).into()));
|
||||||
|
let job = event.clone();
|
||||||
|
match event {
|
||||||
|
BackgroundJob::DisplayPaneError(pane_id, text) => {
|
||||||
|
if job_already_running(job, &mut running_jobs) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
task::spawn({
|
||||||
|
let senders = bus.senders.clone();
|
||||||
|
async move {
|
||||||
|
let _ = senders.send_to_screen(
|
||||||
|
ScreenInstruction::AddRedPaneFrameColorOverride(pane_id, Some(text)),
|
||||||
|
);
|
||||||
|
task::sleep(std::time::Duration::from_millis(FLASH_DURATION_MS)).await;
|
||||||
|
let _ = senders.send_to_screen(
|
||||||
|
ScreenInstruction::ClearPaneFrameColorOverride(pane_id),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
BackgroundJob::Exit => {
|
||||||
|
return Ok(());
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn job_already_running(
|
||||||
|
job: BackgroundJob,
|
||||||
|
running_jobs: &mut HashMap<BackgroundJob, Instant>,
|
||||||
|
) -> bool {
|
||||||
|
match running_jobs.get_mut(&job) {
|
||||||
|
Some(current_running_job_start_time) => {
|
||||||
|
if current_running_job_start_time.elapsed() > Duration::from_millis(FLASH_DURATION_MS) {
|
||||||
|
*current_running_job_start_time = Instant::now();
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
running_jobs.insert(job.clone(), Instant::now());
|
||||||
|
false
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ pub mod output;
|
||||||
pub mod panes;
|
pub mod panes;
|
||||||
pub mod tab;
|
pub mod tab;
|
||||||
|
|
||||||
|
mod background_jobs;
|
||||||
mod logging_pipe;
|
mod logging_pipe;
|
||||||
mod plugins;
|
mod plugins;
|
||||||
mod pty;
|
mod pty;
|
||||||
|
|
@ -13,6 +14,7 @@ mod terminal_bytes;
|
||||||
mod thread_bus;
|
mod thread_bus;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
|
use background_jobs::{background_jobs_main, BackgroundJob};
|
||||||
use log::info;
|
use log::info;
|
||||||
use pty_writer::{pty_writer_main, PtyWriteInstruction};
|
use pty_writer::{pty_writer_main, PtyWriteInstruction};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
@ -110,6 +112,7 @@ pub(crate) struct SessionMetaData {
|
||||||
pty_thread: Option<thread::JoinHandle<()>>,
|
pty_thread: Option<thread::JoinHandle<()>>,
|
||||||
plugin_thread: Option<thread::JoinHandle<()>>,
|
plugin_thread: Option<thread::JoinHandle<()>>,
|
||||||
pty_writer_thread: Option<thread::JoinHandle<()>>,
|
pty_writer_thread: Option<thread::JoinHandle<()>>,
|
||||||
|
background_jobs_thread: Option<thread::JoinHandle<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for SessionMetaData {
|
impl Drop for SessionMetaData {
|
||||||
|
|
@ -118,6 +121,7 @@ impl Drop for SessionMetaData {
|
||||||
let _ = self.senders.send_to_screen(ScreenInstruction::Exit);
|
let _ = self.senders.send_to_screen(ScreenInstruction::Exit);
|
||||||
let _ = self.senders.send_to_plugin(PluginInstruction::Exit);
|
let _ = self.senders.send_to_plugin(PluginInstruction::Exit);
|
||||||
let _ = self.senders.send_to_pty_writer(PtyWriteInstruction::Exit);
|
let _ = self.senders.send_to_pty_writer(PtyWriteInstruction::Exit);
|
||||||
|
let _ = self.senders.send_to_background_jobs(BackgroundJob::Exit);
|
||||||
if let Some(screen_thread) = self.screen_thread.take() {
|
if let Some(screen_thread) = self.screen_thread.take() {
|
||||||
let _ = screen_thread.join();
|
let _ = screen_thread.join();
|
||||||
}
|
}
|
||||||
|
|
@ -130,6 +134,9 @@ impl Drop for SessionMetaData {
|
||||||
if let Some(pty_writer_thread) = self.pty_writer_thread.take() {
|
if let Some(pty_writer_thread) = self.pty_writer_thread.take() {
|
||||||
let _ = pty_writer_thread.join();
|
let _ = pty_writer_thread.join();
|
||||||
}
|
}
|
||||||
|
if let Some(background_jobs_thread) = self.background_jobs_thread.take() {
|
||||||
|
let _ = background_jobs_thread.join();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -638,6 +645,10 @@ fn init_session(
|
||||||
channels::unbounded();
|
channels::unbounded();
|
||||||
let to_pty_writer = SenderWithContext::new(to_pty_writer);
|
let to_pty_writer = SenderWithContext::new(to_pty_writer);
|
||||||
|
|
||||||
|
let (to_background_jobs, background_jobs_receiver): ChannelWithContext<BackgroundJob> =
|
||||||
|
channels::unbounded();
|
||||||
|
let to_background_jobs = SenderWithContext::new(to_background_jobs);
|
||||||
|
|
||||||
// Determine and initialize the data directory
|
// Determine and initialize the data directory
|
||||||
let data_dir = opts.data_dir.unwrap_or_else(get_default_data_dir);
|
let data_dir = opts.data_dir.unwrap_or_else(get_default_data_dir);
|
||||||
|
|
||||||
|
|
@ -664,6 +675,7 @@ fn init_session(
|
||||||
Some(&to_plugin),
|
Some(&to_plugin),
|
||||||
Some(&to_server),
|
Some(&to_server),
|
||||||
Some(&to_pty_writer),
|
Some(&to_pty_writer),
|
||||||
|
Some(&to_background_jobs),
|
||||||
Some(os_input.clone()),
|
Some(os_input.clone()),
|
||||||
),
|
),
|
||||||
opts.debug,
|
opts.debug,
|
||||||
|
|
@ -684,6 +696,7 @@ fn init_session(
|
||||||
Some(&to_plugin),
|
Some(&to_plugin),
|
||||||
Some(&to_server),
|
Some(&to_server),
|
||||||
Some(&to_pty_writer),
|
Some(&to_pty_writer),
|
||||||
|
Some(&to_background_jobs),
|
||||||
Some(os_input.clone()),
|
Some(os_input.clone()),
|
||||||
);
|
);
|
||||||
let max_panes = opts.max_panes;
|
let max_panes = opts.max_panes;
|
||||||
|
|
@ -711,6 +724,7 @@ fn init_session(
|
||||||
Some(&to_plugin),
|
Some(&to_plugin),
|
||||||
None,
|
None,
|
||||||
Some(&to_pty_writer),
|
Some(&to_pty_writer),
|
||||||
|
Some(&to_background_jobs),
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
let store = Store::default();
|
let store = Store::default();
|
||||||
|
|
@ -738,18 +752,37 @@ fn init_session(
|
||||||
Some(&to_plugin),
|
Some(&to_plugin),
|
||||||
Some(&to_server),
|
Some(&to_server),
|
||||||
None,
|
None,
|
||||||
|
Some(&to_background_jobs),
|
||||||
Some(os_input.clone()),
|
Some(os_input.clone()),
|
||||||
);
|
);
|
||||||
|| pty_writer_main(pty_writer_bus).fatal()
|
|| pty_writer_main(pty_writer_bus).fatal()
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let background_jobs_thread = thread::Builder::new()
|
||||||
|
.name("background_jobs".to_string())
|
||||||
|
.spawn({
|
||||||
|
let background_jobs_bus = Bus::new(
|
||||||
|
vec![background_jobs_receiver],
|
||||||
|
Some(&to_screen),
|
||||||
|
Some(&to_pty),
|
||||||
|
Some(&to_plugin),
|
||||||
|
Some(&to_server),
|
||||||
|
Some(&to_pty_writer),
|
||||||
|
None,
|
||||||
|
Some(os_input.clone()),
|
||||||
|
);
|
||||||
|
|| background_jobs_main(background_jobs_bus).fatal()
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
SessionMetaData {
|
SessionMetaData {
|
||||||
senders: ThreadSenders {
|
senders: ThreadSenders {
|
||||||
to_screen: Some(to_screen),
|
to_screen: Some(to_screen),
|
||||||
to_pty: Some(to_pty),
|
to_pty: Some(to_pty),
|
||||||
to_plugin: Some(to_plugin),
|
to_plugin: Some(to_plugin),
|
||||||
to_pty_writer: Some(to_pty_writer),
|
to_pty_writer: Some(to_pty_writer),
|
||||||
|
to_background_jobs: Some(to_background_jobs),
|
||||||
to_server: None,
|
to_server: None,
|
||||||
should_silently_fail: false,
|
should_silently_fail: false,
|
||||||
},
|
},
|
||||||
|
|
@ -760,5 +793,6 @@ fn init_session(
|
||||||
pty_thread: Some(pty_thread),
|
pty_thread: Some(pty_thread),
|
||||||
plugin_thread: Some(plugin_thread),
|
plugin_thread: Some(plugin_thread),
|
||||||
pty_writer_thread: Some(pty_writer_thread),
|
pty_writer_thread: Some(pty_writer_thread),
|
||||||
|
background_jobs_thread: Some(background_jobs_thread),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@ pub(crate) struct PluginPane {
|
||||||
prev_pane_name: String,
|
prev_pane_name: String,
|
||||||
frame: HashMap<ClientId, PaneFrame>,
|
frame: HashMap<ClientId, PaneFrame>,
|
||||||
borderless: bool,
|
borderless: bool,
|
||||||
|
pane_frame_color_override: Option<(PaletteColor, Option<String>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PluginPane {
|
impl PluginPane {
|
||||||
|
|
@ -102,6 +103,7 @@ impl PluginPane {
|
||||||
vte_parsers: HashMap::new(),
|
vte_parsers: HashMap::new(),
|
||||||
grids: HashMap::new(),
|
grids: HashMap::new(),
|
||||||
style,
|
style,
|
||||||
|
pane_frame_color_override: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -244,7 +246,13 @@ impl Pane for PluginPane {
|
||||||
}
|
}
|
||||||
if let Some(grid) = self.grids.get(&client_id) {
|
if let Some(grid) = self.grids.get(&client_id) {
|
||||||
let err_context = || format!("failed to render frame for client {client_id}");
|
let err_context = || format!("failed to render frame for client {client_id}");
|
||||||
let pane_title = if self.pane_name.is_empty()
|
let pane_title = if let Some(text_color_override) = self
|
||||||
|
.pane_frame_color_override
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|(_color, text)| text.as_ref())
|
||||||
|
{
|
||||||
|
text_color_override.into()
|
||||||
|
} else if self.pane_name.is_empty()
|
||||||
&& input_mode == InputMode::RenamePane
|
&& input_mode == InputMode::RenamePane
|
||||||
&& frame_params.is_main_client
|
&& frame_params.is_main_client
|
||||||
{
|
{
|
||||||
|
|
@ -257,12 +265,15 @@ impl Pane for PluginPane {
|
||||||
self.pane_name.clone()
|
self.pane_name.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
let frame = PaneFrame::new(
|
let mut frame = PaneFrame::new(
|
||||||
self.current_geom().into(),
|
self.current_geom().into(),
|
||||||
grid.scrollback_position_and_length(),
|
grid.scrollback_position_and_length(),
|
||||||
pane_title,
|
pane_title,
|
||||||
frame_params,
|
frame_params,
|
||||||
);
|
);
|
||||||
|
if let Some((frame_color_override, _text)) = self.pane_frame_color_override.as_ref() {
|
||||||
|
frame.override_color(*frame_color_override);
|
||||||
|
}
|
||||||
|
|
||||||
let res = match self.frame.get(&client_id) {
|
let res = match self.frame.get(&client_id) {
|
||||||
// TODO: use and_then or something?
|
// TODO: use and_then or something?
|
||||||
|
|
@ -469,6 +480,17 @@ impl Pane for PluginPane {
|
||||||
)]))
|
)]))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
fn add_red_pane_frame_color_override(&mut self, error_text: Option<String>) {
|
||||||
|
self.pane_frame_color_override = Some((self.style.colors.red, error_text));
|
||||||
|
}
|
||||||
|
fn clear_pane_frame_color_override(&mut self) {
|
||||||
|
self.pane_frame_color_override = None;
|
||||||
|
}
|
||||||
|
fn frame_color_override(&self) -> Option<PaletteColor> {
|
||||||
|
self.pane_frame_color_override
|
||||||
|
.as_ref()
|
||||||
|
.map(|(color, _text)| *color)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PluginPane {
|
impl PluginPane {
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,8 @@ pub struct TerminalPane {
|
||||||
is_held: Option<(Option<i32>, IsFirstRun, RunCommand)>, // a "held" pane means that its command has either exited and the pane is waiting for a
|
is_held: Option<(Option<i32>, IsFirstRun, RunCommand)>, // a "held" pane means that its command has either exited and the pane is waiting for a
|
||||||
// possible user instruction to be re-run, or that the command has not yet been run
|
// possible user instruction to be re-run, or that the command has not yet been run
|
||||||
banner: Option<String>, // a banner to be rendered inside this TerminalPane, used for panes
|
banner: Option<String>, // a banner to be rendered inside this TerminalPane, used for panes
|
||||||
// held on startup and can possibly be used to display some errors
|
// held on startup and can possibly be used to display some errors
|
||||||
|
pane_frame_color_override: Option<(PaletteColor, Option<String>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pane for TerminalPane {
|
impl Pane for TerminalPane {
|
||||||
|
|
@ -301,7 +302,13 @@ impl Pane for TerminalPane {
|
||||||
) -> Result<Option<(Vec<CharacterChunk>, Option<String>)>> {
|
) -> Result<Option<(Vec<CharacterChunk>, Option<String>)>> {
|
||||||
let err_context = || format!("failed to render frame for client {client_id}");
|
let err_context = || format!("failed to render frame for client {client_id}");
|
||||||
// TODO: remove the cursor stuff from here
|
// TODO: remove the cursor stuff from here
|
||||||
let pane_title = if self.pane_name.is_empty()
|
let pane_title = if let Some(text_color_override) = self
|
||||||
|
.pane_frame_color_override
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|(_color, text)| text.as_ref())
|
||||||
|
{
|
||||||
|
text_color_override.into()
|
||||||
|
} else if self.pane_name.is_empty()
|
||||||
&& input_mode == InputMode::RenamePane
|
&& input_mode == InputMode::RenamePane
|
||||||
&& frame_params.is_main_client
|
&& frame_params.is_main_client
|
||||||
{
|
{
|
||||||
|
|
@ -353,6 +360,9 @@ impl Pane for TerminalPane {
|
||||||
frame.add_exit_status(exit_status.as_ref().copied());
|
frame.add_exit_status(exit_status.as_ref().copied());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some((frame_color_override, _text)) = self.pane_frame_color_override.as_ref() {
|
||||||
|
frame.override_color(*frame_color_override);
|
||||||
|
}
|
||||||
|
|
||||||
let res = match self.frame.get(&client_id) {
|
let res = match self.frame.get(&client_id) {
|
||||||
// TODO: use and_then or something?
|
// TODO: use and_then or something?
|
||||||
|
|
@ -669,6 +679,17 @@ impl Pane for TerminalPane {
|
||||||
}
|
}
|
||||||
self.set_should_render(true);
|
self.set_should_render(true);
|
||||||
}
|
}
|
||||||
|
fn add_red_pane_frame_color_override(&mut self, error_text: Option<String>) {
|
||||||
|
self.pane_frame_color_override = Some((self.style.colors.red, error_text));
|
||||||
|
}
|
||||||
|
fn clear_pane_frame_color_override(&mut self) {
|
||||||
|
self.pane_frame_color_override = None;
|
||||||
|
}
|
||||||
|
fn frame_color_override(&self) -> Option<PaletteColor> {
|
||||||
|
self.pane_frame_color_override
|
||||||
|
.as_ref()
|
||||||
|
.map(|(color, _text)| *color)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TerminalPane {
|
impl TerminalPane {
|
||||||
|
|
@ -717,6 +738,7 @@ impl TerminalPane {
|
||||||
search_term: String::new(),
|
search_term: String::new(),
|
||||||
is_held: None,
|
is_held: None,
|
||||||
banner: None,
|
banner: None,
|
||||||
|
pane_frame_color_override: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn get_x(&self) -> usize {
|
pub fn get_x(&self) -> usize {
|
||||||
|
|
|
||||||
|
|
@ -205,6 +205,19 @@ impl TiledPanes {
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
pub fn borderless_pane_geoms(&self) -> Vec<Viewport> {
|
||||||
|
self.panes
|
||||||
|
.values()
|
||||||
|
.filter_map(|p| {
|
||||||
|
let geom = p.position_and_size();
|
||||||
|
if p.borderless() {
|
||||||
|
Some(geom.into())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
pub fn first_selectable_pane_id(&self) -> Option<PaneId> {
|
pub fn first_selectable_pane_id(&self) -> Option<PaneId> {
|
||||||
self.panes
|
self.panes
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -295,6 +308,19 @@ impl TiledPanes {
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
pub fn can_split_active_pane_horizontally(&self, client_id: ClientId) -> bool {
|
||||||
|
let active_pane_id = &self.active_panes.get(&client_id).unwrap();
|
||||||
|
let active_pane = self.panes.get(active_pane_id).unwrap();
|
||||||
|
let full_pane_size = active_pane.position_and_size();
|
||||||
|
if full_pane_size.rows.is_fixed() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if split(SplitDirection::Horizontal, &full_pane_size).is_some() {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn split_pane_horizontally(
|
pub fn split_pane_horizontally(
|
||||||
&mut self,
|
&mut self,
|
||||||
pid: PaneId,
|
pid: PaneId,
|
||||||
|
|
@ -313,6 +339,19 @@ impl TiledPanes {
|
||||||
self.relayout(SplitDirection::Vertical);
|
self.relayout(SplitDirection::Vertical);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn can_split_active_pane_vertically(&self, client_id: ClientId) -> bool {
|
||||||
|
let active_pane_id = &self.active_panes.get(&client_id).unwrap();
|
||||||
|
let active_pane = self.panes.get(active_pane_id).unwrap();
|
||||||
|
let full_pane_size = active_pane.position_and_size();
|
||||||
|
if full_pane_size.cols.is_fixed() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if split(SplitDirection::Vertical, &full_pane_size).is_some() {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn split_pane_vertically(
|
pub fn split_pane_vertically(
|
||||||
&mut self,
|
&mut self,
|
||||||
pid: PaneId,
|
pid: PaneId,
|
||||||
|
|
|
||||||
|
|
@ -224,6 +224,8 @@ pub enum ScreenInstruction {
|
||||||
SearchToggleCaseSensitivity(ClientId),
|
SearchToggleCaseSensitivity(ClientId),
|
||||||
SearchToggleWholeWord(ClientId),
|
SearchToggleWholeWord(ClientId),
|
||||||
SearchToggleWrap(ClientId),
|
SearchToggleWrap(ClientId),
|
||||||
|
AddRedPaneFrameColorOverride(PaneId, Option<String>), // Option<String> => optional error text
|
||||||
|
ClearPaneFrameColorOverride(PaneId),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&ScreenInstruction> for ScreenContext {
|
impl From<&ScreenInstruction> for ScreenContext {
|
||||||
|
|
@ -355,6 +357,12 @@ impl From<&ScreenInstruction> for ScreenContext {
|
||||||
},
|
},
|
||||||
ScreenInstruction::SearchToggleWholeWord(..) => ScreenContext::SearchToggleWholeWord,
|
ScreenInstruction::SearchToggleWholeWord(..) => ScreenContext::SearchToggleWholeWord,
|
||||||
ScreenInstruction::SearchToggleWrap(..) => ScreenContext::SearchToggleWrap,
|
ScreenInstruction::SearchToggleWrap(..) => ScreenContext::SearchToggleWrap,
|
||||||
|
ScreenInstruction::AddRedPaneFrameColorOverride(..) => {
|
||||||
|
ScreenContext::AddRedPaneFrameColorOverride
|
||||||
|
},
|
||||||
|
ScreenInstruction::ClearPaneFrameColorOverride(..) => {
|
||||||
|
ScreenContext::ClearPaneFrameColorOverride
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2112,6 +2120,26 @@ pub(crate) fn screen_thread_main(
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
},
|
},
|
||||||
|
ScreenInstruction::AddRedPaneFrameColorOverride(pane_id, error_text) => {
|
||||||
|
let all_tabs = screen.get_tabs_mut();
|
||||||
|
for tab in all_tabs.values_mut() {
|
||||||
|
if tab.has_pane_with_pid(&pane_id) {
|
||||||
|
tab.add_red_pane_frame_color_override(pane_id, error_text);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
screen.render()?;
|
||||||
|
},
|
||||||
|
ScreenInstruction::ClearPaneFrameColorOverride(pane_id) => {
|
||||||
|
let all_tabs = screen.get_tabs_mut();
|
||||||
|
for tab in all_tabs.values_mut() {
|
||||||
|
if tab.has_pane_with_pid(&pane_id) {
|
||||||
|
tab.clear_pane_frame_color_override(pane_id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
screen.render()?;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ use zellij_utils::input::command::RunCommand;
|
||||||
use zellij_utils::position::{Column, Line};
|
use zellij_utils::position::{Column, Line};
|
||||||
use zellij_utils::{position::Position, serde};
|
use zellij_utils::{position::Position, serde};
|
||||||
|
|
||||||
|
use crate::background_jobs::BackgroundJob;
|
||||||
use crate::pty_writer::PtyWriteInstruction;
|
use crate::pty_writer::PtyWriteInstruction;
|
||||||
use crate::screen::CopyOptions;
|
use crate::screen::CopyOptions;
|
||||||
use crate::ui::pane_boundaries_frame::FrameParams;
|
use crate::ui::pane_boundaries_frame::FrameParams;
|
||||||
|
|
@ -386,6 +387,9 @@ pub trait Pane {
|
||||||
fn hold(&mut self, _exit_status: Option<i32>, _is_first_run: bool, _run_command: RunCommand) {
|
fn hold(&mut self, _exit_status: Option<i32>, _is_first_run: bool, _run_command: RunCommand) {
|
||||||
// No-op by default, only terminal panes support holding
|
// No-op by default, only terminal panes support holding
|
||||||
}
|
}
|
||||||
|
fn add_red_pane_frame_color_override(&mut self, _error_text: Option<String>);
|
||||||
|
fn clear_pane_frame_color_override(&mut self);
|
||||||
|
fn frame_color_override(&self) -> Option<PaletteColor>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -618,14 +622,18 @@ impl Tab {
|
||||||
.send_to_pty(PtyInstruction::ClosePane(PaneId::Terminal(*unused_pid)))
|
.send_to_pty(PtyInstruction::ClosePane(PaneId::Terminal(*unused_pid)))
|
||||||
.with_context(err_context)?;
|
.with_context(err_context)?;
|
||||||
}
|
}
|
||||||
// FIXME: This is another hack to crop the viewport to fixed-size panes. Once you can have
|
|
||||||
// non-fixed panes that are part of the viewport, get rid of this!
|
// here we offset the viewport from borderless panes that are on the edges of the
|
||||||
|
// screen, this is so that when we don't have pane boundaries (eg. when they were
|
||||||
|
// disabled by the user) boundaries won't be drawn around these panes
|
||||||
|
// geometrically, we can only do this with panes that are on the edges of the
|
||||||
|
// screen - so it's mostly a best-effort thing
|
||||||
let display_area = {
|
let display_area = {
|
||||||
let display_area = self.display_area.borrow();
|
let display_area = self.display_area.borrow();
|
||||||
*display_area
|
*display_area
|
||||||
};
|
};
|
||||||
self.resize_whole_tab(display_area);
|
self.resize_whole_tab(display_area);
|
||||||
let boundary_geoms = self.tiled_panes.fixed_pane_geoms();
|
let boundary_geoms = self.tiled_panes.borderless_pane_geoms();
|
||||||
for geom in boundary_geoms {
|
for geom in boundary_geoms {
|
||||||
self.offset_viewport(&geom)
|
self.offset_viewport(&geom)
|
||||||
}
|
}
|
||||||
|
|
@ -1035,6 +1043,20 @@ impl Tab {
|
||||||
self.should_clear_display_before_rendering = true;
|
self.should_clear_display_before_rendering = true;
|
||||||
self.tiled_panes.focus_pane(pid, client_id);
|
self.tiled_panes.focus_pane(pid, client_id);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
log::error!("No room to split pane horizontally");
|
||||||
|
if let Some(active_pane_id) = self.tiled_panes.get_active_pane_id(client_id) {
|
||||||
|
self.senders
|
||||||
|
.send_to_background_jobs(BackgroundJob::DisplayPaneError(
|
||||||
|
active_pane_id,
|
||||||
|
"TOO SMALL!".into(),
|
||||||
|
))
|
||||||
|
.with_context(err_context)?;
|
||||||
|
}
|
||||||
|
self.senders
|
||||||
|
.send_to_pty(PtyInstruction::ClosePane(pid))
|
||||||
|
.with_context(err_context)?;
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -1075,6 +1097,20 @@ impl Tab {
|
||||||
self.should_clear_display_before_rendering = true;
|
self.should_clear_display_before_rendering = true;
|
||||||
self.tiled_panes.focus_pane(pid, client_id);
|
self.tiled_panes.focus_pane(pid, client_id);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
log::error!("No room to split pane vertically");
|
||||||
|
if let Some(active_pane_id) = self.tiled_panes.get_active_pane_id(client_id) {
|
||||||
|
self.senders
|
||||||
|
.send_to_background_jobs(BackgroundJob::DisplayPaneError(
|
||||||
|
active_pane_id,
|
||||||
|
"TOO SMALL!".into(),
|
||||||
|
))
|
||||||
|
.with_context(err_context)?;
|
||||||
|
}
|
||||||
|
self.senders
|
||||||
|
.send_to_pty(PtyInstruction::ClosePane(pid))
|
||||||
|
.with_context(err_context)?;
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -1139,6 +1175,11 @@ impl Tab {
|
||||||
.values()
|
.values()
|
||||||
.any(|s_p| s_p.pid() == PaneId::Plugin(plugin_id))
|
.any(|s_p| s_p.pid() == PaneId::Plugin(plugin_id))
|
||||||
}
|
}
|
||||||
|
pub fn has_pane_with_pid(&self, pid: &PaneId) -> bool {
|
||||||
|
self.tiled_panes.panes_contain(pid)
|
||||||
|
|| self.floating_panes.panes_contain(pid)
|
||||||
|
|| self.suppressed_panes.values().any(|s_p| s_p.pid() == *pid)
|
||||||
|
}
|
||||||
pub fn handle_pty_bytes(&mut self, pid: u32, bytes: VteBytes) -> Result<()> {
|
pub fn handle_pty_bytes(&mut self, pid: u32, bytes: VteBytes) -> Result<()> {
|
||||||
if self.is_pending {
|
if self.is_pending {
|
||||||
self.pending_instructions
|
self.pending_instructions
|
||||||
|
|
@ -2809,6 +2850,39 @@ impl Tab {
|
||||||
self.is_pending
|
self.is_pending
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_red_pane_frame_color_override(
|
||||||
|
&mut self,
|
||||||
|
pane_id: PaneId,
|
||||||
|
error_text: Option<String>,
|
||||||
|
) {
|
||||||
|
if let Some(pane) = self
|
||||||
|
.tiled_panes
|
||||||
|
.get_pane_mut(pane_id)
|
||||||
|
.or_else(|| self.floating_panes.get_pane_mut(pane_id))
|
||||||
|
.or_else(|| {
|
||||||
|
self.suppressed_panes
|
||||||
|
.values_mut()
|
||||||
|
.find(|s_p| s_p.pid() == pane_id)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
pane.add_red_pane_frame_color_override(error_text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn clear_pane_frame_color_override(&mut self, pane_id: PaneId) {
|
||||||
|
if let Some(pane) = self
|
||||||
|
.tiled_panes
|
||||||
|
.get_pane_mut(pane_id)
|
||||||
|
.or_else(|| self.floating_panes.get_pane_mut(pane_id))
|
||||||
|
.or_else(|| {
|
||||||
|
self.suppressed_panes
|
||||||
|
.values_mut()
|
||||||
|
.find(|s_p| s_p.pid() == pane_id)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
pane.clear_pane_frame_color_override();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn show_floating_panes(&mut self) {
|
fn show_floating_panes(&mut self) {
|
||||||
// this function is to be preferred to directly invoking floating_panes.toggle_show_panes(true)
|
// this function is to be preferred to directly invoking floating_panes.toggle_show_panes(true)
|
||||||
self.floating_panes.toggle_show_panes(true);
|
self.floating_panes.toggle_show_panes(true);
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use crate::{
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use zellij_utils::data::{Direction, Resize, ResizeStrategy};
|
use zellij_utils::data::{Direction, Resize, ResizeStrategy};
|
||||||
use zellij_utils::errors::prelude::*;
|
use zellij_utils::errors::prelude::*;
|
||||||
use zellij_utils::input::layout::PaneLayout;
|
use zellij_utils::input::layout::{PaneLayout, SplitDirection, SplitSize};
|
||||||
use zellij_utils::ipc::IpcReceiverWithContext;
|
use zellij_utils::ipc::IpcReceiverWithContext;
|
||||||
use zellij_utils::pane_size::{Size, SizeInPixels};
|
use zellij_utils::pane_size::{Size, SizeInPixels};
|
||||||
|
|
||||||
|
|
@ -187,6 +187,62 @@ fn create_new_tab(size: Size) -> Tab {
|
||||||
tab
|
tab
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_new_tab_with_layout(size: Size, layout: PaneLayout) -> Tab {
|
||||||
|
let index = 0;
|
||||||
|
let position = 0;
|
||||||
|
let name = String::new();
|
||||||
|
let os_api = Box::new(FakeInputOutput {});
|
||||||
|
let senders = ThreadSenders::default().silently_fail_on_send();
|
||||||
|
let max_panes = None;
|
||||||
|
let mode_info = ModeInfo::default();
|
||||||
|
let style = Style::default();
|
||||||
|
let draw_pane_frames = true;
|
||||||
|
let client_id = 1;
|
||||||
|
let session_is_mirrored = true;
|
||||||
|
let mut connected_clients = HashSet::new();
|
||||||
|
let character_cell_info = Rc::new(RefCell::new(None));
|
||||||
|
connected_clients.insert(client_id);
|
||||||
|
let connected_clients = Rc::new(RefCell::new(connected_clients));
|
||||||
|
let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
|
||||||
|
let copy_options = CopyOptions::default();
|
||||||
|
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||||||
|
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||||||
|
let mut tab = Tab::new(
|
||||||
|
index,
|
||||||
|
position,
|
||||||
|
name,
|
||||||
|
size,
|
||||||
|
character_cell_info,
|
||||||
|
sixel_image_store,
|
||||||
|
os_api,
|
||||||
|
senders,
|
||||||
|
max_panes,
|
||||||
|
style,
|
||||||
|
mode_info,
|
||||||
|
draw_pane_frames,
|
||||||
|
connected_clients,
|
||||||
|
session_is_mirrored,
|
||||||
|
client_id,
|
||||||
|
copy_options,
|
||||||
|
terminal_emulator_colors,
|
||||||
|
terminal_emulator_color_codes,
|
||||||
|
);
|
||||||
|
let mut new_terminal_ids = vec![];
|
||||||
|
for i in 0..layout.extract_run_instructions().len() {
|
||||||
|
new_terminal_ids.push((i as u32, None));
|
||||||
|
}
|
||||||
|
tab.apply_layout(
|
||||||
|
layout,
|
||||||
|
// vec![(1, None), (2, None)],
|
||||||
|
new_terminal_ids,
|
||||||
|
HashMap::new(),
|
||||||
|
index,
|
||||||
|
client_id,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
tab
|
||||||
|
}
|
||||||
|
|
||||||
fn create_new_tab_with_cell_size(
|
fn create_new_tab_with_cell_size(
|
||||||
size: Size,
|
size: Size,
|
||||||
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
||||||
|
|
@ -677,6 +733,32 @@ pub fn cannot_split_largest_pane_when_there_is_no_room() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn cannot_split_panes_vertically_when_active_pane_has_fixed_columns() {
|
||||||
|
let size = Size { cols: 50, rows: 20 };
|
||||||
|
let mut initial_layout = PaneLayout::default();
|
||||||
|
initial_layout.children_split_direction = SplitDirection::Vertical;
|
||||||
|
let mut fixed_child = PaneLayout::default();
|
||||||
|
fixed_child.split_size = Some(SplitSize::Fixed(30));
|
||||||
|
initial_layout.children = vec![fixed_child, PaneLayout::default()];
|
||||||
|
let mut tab = create_new_tab_with_layout(size, initial_layout);
|
||||||
|
tab.vertical_split(PaneId::Terminal(3), None, 1).unwrap();
|
||||||
|
assert_eq!(tab.tiled_panes.panes.len(), 2, "Tab still has two panes");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn cannot_split_panes_horizontally_when_active_pane_has_fixed_rows() {
|
||||||
|
let size = Size { cols: 50, rows: 20 };
|
||||||
|
let mut initial_layout = PaneLayout::default();
|
||||||
|
initial_layout.children_split_direction = SplitDirection::Horizontal;
|
||||||
|
let mut fixed_child = PaneLayout::default();
|
||||||
|
fixed_child.split_size = Some(SplitSize::Fixed(12));
|
||||||
|
initial_layout.children = vec![fixed_child, PaneLayout::default()];
|
||||||
|
let mut tab = create_new_tab_with_layout(size, initial_layout);
|
||||||
|
tab.horizontal_split(PaneId::Terminal(3), None, 1).unwrap();
|
||||||
|
assert_eq!(tab.tiled_panes.panes.len(), 2, "Tab still has two panes");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn toggle_focused_pane_fullscreen() {
|
pub fn toggle_focused_pane_fullscreen() {
|
||||||
let size = Size {
|
let size = Size {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
//! Definitions and helpers for sending and receiving messages between threads.
|
//! Definitions and helpers for sending and receiving messages between threads.
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
os_input_output::ServerOsApi, plugins::PluginInstruction, pty::PtyInstruction,
|
background_jobs::BackgroundJob, os_input_output::ServerOsApi, plugins::PluginInstruction,
|
||||||
pty_writer::PtyWriteInstruction, screen::ScreenInstruction, ServerInstruction,
|
pty::PtyInstruction, pty_writer::PtyWriteInstruction, screen::ScreenInstruction,
|
||||||
|
ServerInstruction,
|
||||||
};
|
};
|
||||||
use zellij_utils::errors::prelude::*;
|
use zellij_utils::errors::prelude::*;
|
||||||
use zellij_utils::{channels, channels::SenderWithContext, errors::ErrorContext};
|
use zellij_utils::{channels, channels::SenderWithContext, errors::ErrorContext};
|
||||||
|
|
@ -15,6 +16,7 @@ pub struct ThreadSenders {
|
||||||
pub to_plugin: Option<SenderWithContext<PluginInstruction>>,
|
pub to_plugin: Option<SenderWithContext<PluginInstruction>>,
|
||||||
pub to_server: Option<SenderWithContext<ServerInstruction>>,
|
pub to_server: Option<SenderWithContext<ServerInstruction>>,
|
||||||
pub to_pty_writer: Option<SenderWithContext<PtyWriteInstruction>>,
|
pub to_pty_writer: Option<SenderWithContext<PtyWriteInstruction>>,
|
||||||
|
pub to_background_jobs: Option<SenderWithContext<BackgroundJob>>,
|
||||||
// this is a convenience for the unit tests
|
// this is a convenience for the unit tests
|
||||||
// it's not advisable to set it to true in production code
|
// it's not advisable to set it to true in production code
|
||||||
pub should_silently_fail: bool,
|
pub should_silently_fail: bool,
|
||||||
|
|
@ -109,6 +111,23 @@ impl ThreadSenders {
|
||||||
.context("failed to send message to pty writer")
|
.context("failed to send message to pty writer")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn send_to_background_jobs(&self, background_job: BackgroundJob) -> Result<()> {
|
||||||
|
if self.should_silently_fail {
|
||||||
|
let _ = self
|
||||||
|
.to_background_jobs
|
||||||
|
.as_ref()
|
||||||
|
.map(|sender| sender.send(background_job))
|
||||||
|
.unwrap_or_else(|| Ok(()));
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
self.to_background_jobs
|
||||||
|
.as_ref()
|
||||||
|
.context("failed to get background jobs sender")?
|
||||||
|
.send(background_job)
|
||||||
|
.to_anyhow()
|
||||||
|
.context("failed to send message to background jobs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub fn silently_fail_on_send(mut self) -> Self {
|
pub fn silently_fail_on_send(mut self) -> Self {
|
||||||
|
|
@ -142,6 +161,7 @@ impl<T> Bus<T> {
|
||||||
to_plugin: Option<&SenderWithContext<PluginInstruction>>,
|
to_plugin: Option<&SenderWithContext<PluginInstruction>>,
|
||||||
to_server: Option<&SenderWithContext<ServerInstruction>>,
|
to_server: Option<&SenderWithContext<ServerInstruction>>,
|
||||||
to_pty_writer: Option<&SenderWithContext<PtyWriteInstruction>>,
|
to_pty_writer: Option<&SenderWithContext<PtyWriteInstruction>>,
|
||||||
|
to_background_jobs: Option<&SenderWithContext<BackgroundJob>>,
|
||||||
os_input: Option<Box<dyn ServerOsApi>>,
|
os_input: Option<Box<dyn ServerOsApi>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Bus {
|
Bus {
|
||||||
|
|
@ -152,6 +172,7 @@ impl<T> Bus<T> {
|
||||||
to_plugin: to_plugin.cloned(),
|
to_plugin: to_plugin.cloned(),
|
||||||
to_server: to_server.cloned(),
|
to_server: to_server.cloned(),
|
||||||
to_pty_writer: to_pty_writer.cloned(),
|
to_pty_writer: to_pty_writer.cloned(),
|
||||||
|
to_background_jobs: to_background_jobs.cloned(),
|
||||||
should_silently_fail: false,
|
should_silently_fail: false,
|
||||||
},
|
},
|
||||||
os_input: os_input.clone(),
|
os_input: os_input.clone(),
|
||||||
|
|
@ -174,6 +195,7 @@ impl<T> Bus<T> {
|
||||||
to_plugin: None,
|
to_plugin: None,
|
||||||
to_server: None,
|
to_server: None,
|
||||||
to_pty_writer: None,
|
to_pty_writer: None,
|
||||||
|
to_background_jobs: None,
|
||||||
should_silently_fail: true,
|
should_silently_fail: true,
|
||||||
},
|
},
|
||||||
os_input: None,
|
os_input: None,
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,9 @@ impl PaneFrame {
|
||||||
pub fn indicate_first_run(&mut self) {
|
pub fn indicate_first_run(&mut self) {
|
||||||
self.is_first_run = true;
|
self.is_first_run = true;
|
||||||
}
|
}
|
||||||
|
pub fn override_color(&mut self, color: PaletteColor) {
|
||||||
|
self.color = Some(color);
|
||||||
|
}
|
||||||
fn client_cursor(&self, client_id: ClientId) -> Vec<TerminalCharacter> {
|
fn client_cursor(&self, client_id: ClientId) -> Vec<TerminalCharacter> {
|
||||||
let color = client_id_to_colors(client_id, self.style.colors);
|
let color = client_id_to_colors(client_id, self.style.colors);
|
||||||
background_color(" ", color.map(|c| c.0))
|
background_color(" ", color.map(|c| c.0))
|
||||||
|
|
|
||||||
|
|
@ -238,7 +238,9 @@ impl<'a> PaneContentsAndUi<'a> {
|
||||||
session_is_mirrored: bool,
|
session_is_mirrored: bool,
|
||||||
) -> Option<PaletteColor> {
|
) -> Option<PaletteColor> {
|
||||||
let pane_focused_for_client_id = self.focused_clients.contains(&client_id);
|
let pane_focused_for_client_id = self.focused_clients.contains(&client_id);
|
||||||
if pane_focused_for_client_id {
|
if let Some(override_color) = self.pane.frame_color_override() {
|
||||||
|
Some(override_color)
|
||||||
|
} else if pane_focused_for_client_id {
|
||||||
match mode {
|
match mode {
|
||||||
InputMode::Normal | InputMode::Locked => {
|
InputMode::Normal | InputMode::Locked => {
|
||||||
if session_is_mirrored || !self.multiple_users_exist_in_session {
|
if session_is_mirrored || !self.multiple_users_exist_in_session {
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,12 @@ use zellij_utils::data::Resize;
|
||||||
use zellij_utils::errors::{prelude::*, ErrorContext};
|
use zellij_utils::errors::{prelude::*, ErrorContext};
|
||||||
use zellij_utils::input::actions::Action;
|
use zellij_utils::input::actions::Action;
|
||||||
use zellij_utils::input::command::{RunCommand, TerminalAction};
|
use zellij_utils::input::command::{RunCommand, TerminalAction};
|
||||||
use zellij_utils::input::layout::{PaneLayout, SplitDirection};
|
use zellij_utils::input::layout::{PaneLayout, SplitDirection, SplitSize};
|
||||||
use zellij_utils::input::options::Options;
|
use zellij_utils::input::options::Options;
|
||||||
use zellij_utils::ipc::IpcReceiverWithContext;
|
use zellij_utils::ipc::IpcReceiverWithContext;
|
||||||
use zellij_utils::pane_size::{Size, SizeInPixels};
|
use zellij_utils::pane_size::{Size, SizeInPixels};
|
||||||
|
|
||||||
|
use crate::background_jobs::BackgroundJob;
|
||||||
use crate::pty_writer::PtyWriteInstruction;
|
use crate::pty_writer::PtyWriteInstruction;
|
||||||
use std::env::set_var;
|
use std::env::set_var;
|
||||||
use std::os::unix::io::RawFd;
|
use std::os::unix::io::RawFd;
|
||||||
|
|
@ -238,6 +239,7 @@ struct MockScreen {
|
||||||
pub main_client_id: u16,
|
pub main_client_id: u16,
|
||||||
pub pty_receiver: Option<Receiver<(PtyInstruction, ErrorContext)>>,
|
pub pty_receiver: Option<Receiver<(PtyInstruction, ErrorContext)>>,
|
||||||
pub pty_writer_receiver: Option<Receiver<(PtyWriteInstruction, ErrorContext)>>,
|
pub pty_writer_receiver: Option<Receiver<(PtyWriteInstruction, ErrorContext)>>,
|
||||||
|
pub background_jobs_receiver: Option<Receiver<(BackgroundJob, ErrorContext)>>,
|
||||||
pub screen_receiver: Option<Receiver<(ScreenInstruction, ErrorContext)>>,
|
pub screen_receiver: Option<Receiver<(ScreenInstruction, ErrorContext)>>,
|
||||||
pub server_receiver: Option<Receiver<(ServerInstruction, ErrorContext)>>,
|
pub server_receiver: Option<Receiver<(ServerInstruction, ErrorContext)>>,
|
||||||
pub plugin_receiver: Option<Receiver<(PluginInstruction, ErrorContext)>>,
|
pub plugin_receiver: Option<Receiver<(PluginInstruction, ErrorContext)>>,
|
||||||
|
|
@ -246,6 +248,7 @@ struct MockScreen {
|
||||||
pub to_plugin: SenderWithContext<PluginInstruction>,
|
pub to_plugin: SenderWithContext<PluginInstruction>,
|
||||||
pub to_server: SenderWithContext<ServerInstruction>,
|
pub to_server: SenderWithContext<ServerInstruction>,
|
||||||
pub to_pty_writer: SenderWithContext<PtyWriteInstruction>,
|
pub to_pty_writer: SenderWithContext<PtyWriteInstruction>,
|
||||||
|
pub to_background_jobs: SenderWithContext<BackgroundJob>,
|
||||||
pub os_input: FakeInputOutput,
|
pub os_input: FakeInputOutput,
|
||||||
pub client_attributes: ClientAttributes,
|
pub client_attributes: ClientAttributes,
|
||||||
pub config_options: Options,
|
pub config_options: Options,
|
||||||
|
|
@ -264,6 +267,7 @@ impl MockScreen {
|
||||||
Some(&self.to_plugin.clone()),
|
Some(&self.to_plugin.clone()),
|
||||||
Some(&self.to_server.clone()),
|
Some(&self.to_server.clone()),
|
||||||
Some(&self.to_pty_writer.clone()),
|
Some(&self.to_pty_writer.clone()),
|
||||||
|
Some(&self.to_background_jobs.clone()),
|
||||||
Some(Box::new(self.os_input.clone())),
|
Some(Box::new(self.os_input.clone())),
|
||||||
)
|
)
|
||||||
.should_silently_fail();
|
.should_silently_fail();
|
||||||
|
|
@ -352,6 +356,7 @@ impl MockScreen {
|
||||||
pty_thread: None,
|
pty_thread: None,
|
||||||
plugin_thread: None,
|
plugin_thread: None,
|
||||||
pty_writer_thread: None,
|
pty_writer_thread: None,
|
||||||
|
background_jobs_thread: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -376,6 +381,10 @@ impl MockScreen {
|
||||||
channels::unbounded();
|
channels::unbounded();
|
||||||
let to_pty_writer = SenderWithContext::new(to_pty_writer);
|
let to_pty_writer = SenderWithContext::new(to_pty_writer);
|
||||||
|
|
||||||
|
let (to_background_jobs, background_jobs_receiver): ChannelWithContext<BackgroundJob> =
|
||||||
|
channels::unbounded();
|
||||||
|
let to_background_jobs = SenderWithContext::new(to_background_jobs);
|
||||||
|
|
||||||
let client_attributes = ClientAttributes {
|
let client_attributes = ClientAttributes {
|
||||||
size,
|
size,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
@ -390,6 +399,7 @@ impl MockScreen {
|
||||||
to_pty: Some(to_pty.clone()),
|
to_pty: Some(to_pty.clone()),
|
||||||
to_plugin: Some(to_plugin.clone()),
|
to_plugin: Some(to_plugin.clone()),
|
||||||
to_pty_writer: Some(to_pty_writer.clone()),
|
to_pty_writer: Some(to_pty_writer.clone()),
|
||||||
|
to_background_jobs: Some(to_background_jobs.clone()),
|
||||||
to_server: Some(to_server.clone()),
|
to_server: Some(to_server.clone()),
|
||||||
should_silently_fail: true,
|
should_silently_fail: true,
|
||||||
},
|
},
|
||||||
|
|
@ -400,6 +410,7 @@ impl MockScreen {
|
||||||
pty_thread: None,
|
pty_thread: None,
|
||||||
plugin_thread: None,
|
plugin_thread: None,
|
||||||
pty_writer_thread: None,
|
pty_writer_thread: None,
|
||||||
|
background_jobs_thread: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let os_input = FakeInputOutput::default();
|
let os_input = FakeInputOutput::default();
|
||||||
|
|
@ -409,6 +420,7 @@ impl MockScreen {
|
||||||
main_client_id,
|
main_client_id,
|
||||||
pty_receiver: Some(pty_receiver),
|
pty_receiver: Some(pty_receiver),
|
||||||
pty_writer_receiver: Some(pty_writer_receiver),
|
pty_writer_receiver: Some(pty_writer_receiver),
|
||||||
|
background_jobs_receiver: Some(background_jobs_receiver),
|
||||||
screen_receiver: Some(screen_receiver),
|
screen_receiver: Some(screen_receiver),
|
||||||
server_receiver: Some(server_receiver),
|
server_receiver: Some(server_receiver),
|
||||||
plugin_receiver: Some(plugin_receiver),
|
plugin_receiver: Some(plugin_receiver),
|
||||||
|
|
@ -417,6 +429,7 @@ impl MockScreen {
|
||||||
to_plugin,
|
to_plugin,
|
||||||
to_server,
|
to_server,
|
||||||
to_pty_writer,
|
to_pty_writer,
|
||||||
|
to_background_jobs,
|
||||||
os_input,
|
os_input,
|
||||||
client_attributes,
|
client_attributes,
|
||||||
config_options,
|
config_options,
|
||||||
|
|
|
||||||
|
|
@ -215,6 +215,7 @@ pub enum ContextType {
|
||||||
StdinHandler,
|
StdinHandler,
|
||||||
AsyncTask,
|
AsyncTask,
|
||||||
PtyWrite(PtyWriteContext),
|
PtyWrite(PtyWriteContext),
|
||||||
|
BackgroundJob(BackgroundJobContext),
|
||||||
/// An empty, placeholder call. This should be thought of as representing no call at all.
|
/// An empty, placeholder call. This should be thought of as representing no call at all.
|
||||||
/// A call stack representation filled with these is the representation of an empty call stack.
|
/// A call stack representation filled with these is the representation of an empty call stack.
|
||||||
Empty,
|
Empty,
|
||||||
|
|
@ -231,6 +232,7 @@ impl Display for ContextType {
|
||||||
ContextType::StdinHandler => Some(("stdin_handler_thread:", "AcceptInput".to_string())),
|
ContextType::StdinHandler => Some(("stdin_handler_thread:", "AcceptInput".to_string())),
|
||||||
ContextType::AsyncTask => Some(("stream_terminal_bytes:", "AsyncTask".to_string())),
|
ContextType::AsyncTask => Some(("stream_terminal_bytes:", "AsyncTask".to_string())),
|
||||||
ContextType::PtyWrite(c) => Some(("pty_writer_thread:", format!("{:?}", c))),
|
ContextType::PtyWrite(c) => Some(("pty_writer_thread:", format!("{:?}", c))),
|
||||||
|
ContextType::BackgroundJob(c) => Some(("background_jobs_thread:", format!("{:?}", c))),
|
||||||
ContextType::Empty => None,
|
ContextType::Empty => None,
|
||||||
} {
|
} {
|
||||||
write!(f, "{} {}", left.purple(), right.green())
|
write!(f, "{} {}", left.purple(), right.green())
|
||||||
|
|
@ -350,6 +352,8 @@ pub enum ScreenContext {
|
||||||
SearchToggleCaseSensitivity,
|
SearchToggleCaseSensitivity,
|
||||||
SearchToggleWholeWord,
|
SearchToggleWholeWord,
|
||||||
SearchToggleWrap,
|
SearchToggleWrap,
|
||||||
|
AddRedPaneFrameColorOverride,
|
||||||
|
ClearPaneFrameColorOverride,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stack call representations corresponding to the different types of [`PtyInstruction`]s.
|
/// Stack call representations corresponding to the different types of [`PtyInstruction`]s.
|
||||||
|
|
@ -420,6 +424,12 @@ pub enum PtyWriteContext {
|
||||||
Exit,
|
Exit,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum BackgroundJobContext {
|
||||||
|
DisplayPaneError,
|
||||||
|
Exit,
|
||||||
|
}
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum ZellijError {
|
pub enum ZellijError {
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ impl Dimension {
|
||||||
pub fn fixed(size: usize) -> Dimension {
|
pub fn fixed(size: usize) -> Dimension {
|
||||||
Self {
|
Self {
|
||||||
constraint: Constraint::Fixed(size),
|
constraint: Constraint::Fixed(size),
|
||||||
inner: 1,
|
inner: size,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue