Merge branch 'main' of https://github.com/zellij-org/zellij into tab-layout

This commit is contained in:
a-kenji 2021-08-01 20:25:33 +02:00
commit fc7bc3cc8b
13 changed files with 196 additions and 89 deletions

View file

@ -12,6 +12,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
* Add displaying of the `session-name` to the `tab-bar` (https://github.com/zellij-org/zellij/pull/608)
* Add command to dump `layouts` to stdout (https://github.com/zellij-org/zellij/pull/623)
* `zellij setup --dump-layout [LAYOUT]` [default, strider, disable-status]
* Add `action`: `ScrollToBottom` (https://github.com/zellij-org/zellij/pull/626)
* Bound by default to `^c` in `scroll` mode, scrolls to bottom and exists the scroll mode
* Simplify deserialization slightly (https://github.com/zellij-org/zellij/pull/633)
* Fix update plugin attributes on inactive tab (https://github.com/zellij-org/zellij/pull/634)
## [0.15.0] - 2021-07-19
* Kill children properly (https://github.com/zellij-org/zellij/pull/601)

View file

@ -29,3 +29,4 @@ Once the organization reaches 10 members, a reasonable and achievable process mu
* Roee Shapira <ro33.sha@gmail.com>
* Alex Kenji Berthold <aks.kenji@protonmail.com>
* Kyle Sutherland-Cash <kyle.sutherlandcash@gmail.com>
* Dante Pippi <dante.dpf@gmail.com>

View file

@ -5,16 +5,14 @@ mod tests;
use crate::install::populate_data_dir;
use sessions::{assert_session, assert_session_ne, get_active_session, list_sessions};
use std::convert::TryFrom;
use std::process;
use zellij_client::{os_input_output::get_client_os_input, start_client, ClientInfo};
use zellij_server::{os_input_output::get_server_os_input, start_server};
use zellij_utils::{
cli::{CliArgs, Command, Sessions},
consts::{ZELLIJ_TMP_DIR, ZELLIJ_TMP_LOG_DIR},
input::{config::Config, layout::Layout, options::Options},
logging::*,
setup::{find_default_config_dir, get_default_data_dir, get_layout_dir, Setup},
setup::{get_default_data_dir, Setup},
structopt::StructOpt,
};
@ -26,25 +24,6 @@ pub fn main() {
list_sessions();
}
let config = match Config::try_from(&opts) {
Ok(config) => config,
Err(e) => {
eprintln!("There was an error in the config file:\n{}", e);
process::exit(1);
}
};
let config_options = Options::from_cli(&config.options, opts.command.clone());
if let Some(Command::Setup(ref setup)) = opts.command {
Setup::from_cli(setup, &opts, &config_options).map_or_else(
|e| {
eprintln!("{:?}", e);
process::exit(1);
},
|_| {},
);
};
atomic_create_dir(&*ZELLIJ_TMP_DIR).unwrap();
atomic_create_dir(&*ZELLIJ_TMP_LOG_DIR).unwrap();
if let Some(path) = opts.server {
@ -75,6 +54,14 @@ pub fn main() {
session_name = Some(get_active_session());
}
let (config, _, config_options) = match Setup::from_options(&opts) {
Ok(results) => results,
Err(e) => {
eprintln!("{}", e);
process::exit(1);
}
};
start_client(
Box::new(os_input),
opts,
@ -83,6 +70,14 @@ pub fn main() {
None,
);
} else {
let (config, layout, _) = match Setup::from_options(&opts) {
Ok(results) => results,
Err(e) => {
eprintln!("{}", e);
process::exit(1);
}
};
let session_name = opts
.session
.clone()
@ -94,16 +89,6 @@ pub fn main() {
#[cfg(not(disable_automatic_asset_installation))]
populate_data_dir(&data_dir);
let layout_dir = config_options.layout_dir.or_else(|| {
get_layout_dir(opts.config_dir.clone().or_else(find_default_config_dir))
});
let layout = Layout::from_path_or_default(
opts.layout.as_ref(),
opts.layout_path.as_ref(),
layout_dir,
)
.map(|layout| layout.construct_main_layout());
start_client(
Box::new(os_input),
opts,

View file

@ -127,6 +127,12 @@ fn route_action(
.send_to_screen(ScreenInstruction::ScrollDownAt(point))
.unwrap();
}
Action::ScrollToBottom => {
session
.senders
.send_to_screen(ScreenInstruction::ScrollToBottom)
.unwrap();
}
Action::PageScrollUp => {
session
.senders

View file

@ -50,15 +50,16 @@ pub(crate) enum ScreenInstruction {
ScrollUpAt(Position),
ScrollDown,
ScrollDownAt(Position),
ScrollToBottom,
PageScrollUp,
PageScrollDown,
ClearScroll,
CloseFocusedPane,
ToggleActiveTerminalFullscreen,
SetSelectable(PaneId, bool),
SetFixedHeight(PaneId, usize),
SetFixedWidth(PaneId, usize),
SetInvisibleBorders(PaneId, bool),
SetSelectable(PaneId, bool, usize),
SetFixedHeight(PaneId, usize, usize),
SetFixedWidth(PaneId, usize, usize),
SetInvisibleBorders(PaneId, bool, usize),
ClosePane(PaneId),
ApplyLayout(Layout, Vec<RawFd>),
NewTab(RawFd),
@ -103,6 +104,7 @@ impl From<&ScreenInstruction> for ScreenContext {
ScreenInstruction::Exit => ScreenContext::Exit,
ScreenInstruction::ScrollUp => ScreenContext::ScrollUp,
ScreenInstruction::ScrollDown => ScreenContext::ScrollDown,
ScreenInstruction::ScrollToBottom => ScreenContext::ScrollToBottom,
ScreenInstruction::PageScrollUp => ScreenContext::PageScrollUp,
ScreenInstruction::PageScrollDown => ScreenContext::PageScrollDown,
ScreenInstruction::ClearScroll => ScreenContext::ClearScroll,
@ -335,6 +337,11 @@ impl Screen {
}
}
/// Returns a mutable reference to this [`Screen`]'s indexed [`Tab`].
pub fn get_indexed_tab_mut(&mut self, tab_index: usize) -> Option<&mut Tab> {
self.get_tabs_mut().get_mut(&tab_index)
}
/// Creates a new [`Tab`] in this [`Screen`], applying the specified [`Layout`]
/// and switching to it.
pub fn apply_layout(&mut self, layout: Layout, new_pids: Vec<RawFd>) {
@ -353,7 +360,7 @@ impl Screen {
self.colors,
self.session_state.clone(),
);
tab.apply_layout(layout, new_pids);
tab.apply_layout(layout, new_pids, tab_index);
self.active_tab_index = Some(tab_index);
self.tabs.insert(tab_index, tab);
self.update_tabs();
@ -569,6 +576,12 @@ pub(crate) fn screen_thread_main(
.unwrap()
.scroll_terminal_down(&point, 3);
}
ScreenInstruction::ScrollToBottom => {
screen
.get_active_tab_mut()
.unwrap()
.scroll_active_terminal_to_bottom();
}
ScreenInstruction::PageScrollUp => {
screen
.get_active_tab_mut()
@ -591,29 +604,53 @@ pub(crate) fn screen_thread_main(
screen.get_active_tab_mut().unwrap().close_focused_pane();
screen.render();
}
ScreenInstruction::SetSelectable(id, selectable) => {
screen
.get_active_tab_mut()
.unwrap()
.set_pane_selectable(id, selectable);
ScreenInstruction::SetSelectable(id, selectable, tab_index) => {
screen.get_indexed_tab_mut(tab_index).map_or_else(
|| {
log::warn!(
"Tab index #{} not found, could not set selectable for plugin #{:?}.",
tab_index,
id
)
},
|tab| tab.set_pane_selectable(id, selectable),
);
}
ScreenInstruction::SetFixedHeight(id, fixed_height) => {
screen
.get_active_tab_mut()
.unwrap()
.set_pane_fixed_height(id, fixed_height);
ScreenInstruction::SetFixedHeight(id, fixed_height, tab_index) => {
screen.get_indexed_tab_mut(tab_index).map_or_else(
|| {
log::warn!(
"Tab index #{} not found, could not set fixed height for plugin #{:?}.",
tab_index,
id
)
},
|tab| tab.set_pane_fixed_height(id, fixed_height),
);
}
ScreenInstruction::SetFixedWidth(id, fixed_width) => {
screen
.get_active_tab_mut()
.unwrap()
.set_pane_fixed_width(id, fixed_width);
ScreenInstruction::SetFixedWidth(id, fixed_width, tab_index) => {
screen.get_indexed_tab_mut(tab_index).map_or_else(
|| {
log::warn!(
"Tab index #{} not found, could not set fixed width for plugin #{:?}.",
tab_index,
id
)
},
|tab| tab.set_pane_fixed_width(id, fixed_width),
);
}
ScreenInstruction::SetInvisibleBorders(id, invisible_borders) => {
screen
.get_active_tab_mut()
.unwrap()
.set_pane_invisible_borders(id, invisible_borders);
ScreenInstruction::SetInvisibleBorders(id, invisible_borders, tab_index) => {
screen.get_indexed_tab_mut(tab_index).map_or_else(
|| {
log::warn!(
r#"Tab index #{} not found, could not set invisible borders for plugin #{:?}."#,
tab_index,
id
)
},
|tab| tab.set_pane_invisible_borders(id, invisible_borders),
);
screen.render();
}
ScreenInstruction::ClosePane(id) => {

View file

@ -301,7 +301,7 @@ impl Tab {
}
}
pub fn apply_layout(&mut self, layout: Layout, new_pids: Vec<RawFd>) {
pub fn apply_layout(&mut self, layout: Layout, new_pids: Vec<RawFd>, tab_index: usize) {
// TODO: this should be an attribute on Screen instead of full_screen_ws
let free_space = PositionAndSize {
x: 0,
@ -340,7 +340,7 @@ impl Tab {
if let Some(Run::Plugin(Some(plugin))) = &layout.run {
let (pid_tx, pid_rx) = channel();
self.senders
.send_to_plugin(PluginInstruction::Load(pid_tx, plugin.clone()))
.send_to_plugin(PluginInstruction::Load(pid_tx, plugin.clone(), tab_index))
.unwrap();
let pid = pid_rx.recv().unwrap();
let new_plugin = PluginPane::new(
@ -2280,6 +2280,16 @@ impl Tab {
self.render();
}
}
pub fn scroll_active_terminal_to_bottom(&mut self) {
if let Some(active_terminal_id) = self.get_active_terminal_id() {
let active_terminal = self
.panes
.get_mut(&PaneId::Terminal(active_terminal_id))
.unwrap();
active_terminal.clear_scroll();
self.render();
}
}
pub fn clear_active_terminal_scroll(&mut self) {
if let Some(active_terminal_id) = self.get_active_terminal_id() {
let active_terminal = self

View file

@ -28,7 +28,7 @@ use zellij_utils::{input::command::TerminalAction, serde, zellij_tile};
#[derive(Clone, Debug)]
pub(crate) enum PluginInstruction {
Load(Sender<u32>, PathBuf),
Load(Sender<u32>, PathBuf, usize), // tx_pid, path_of_plugin , tab_index
Update(Option<u32>, Event), // Focused plugin / broadcast, event data
Render(Sender<String>, u32, usize, usize), // String buffer, plugin id, rows, cols
Unload(u32),
@ -50,6 +50,7 @@ impl From<&PluginInstruction> for PluginContext {
#[derive(WasmerEnv, Clone)]
pub(crate) struct PluginEnv {
pub plugin_id: u32,
pub tab_index: usize,
pub senders: ThreadSenders,
pub wasi_env: WasiEnv,
pub subscriptions: Arc<Mutex<HashSet<EventType>>>,
@ -64,7 +65,7 @@ pub(crate) fn wasm_thread_main(bus: Bus<PluginInstruction>, store: Store, data_d
let (event, mut err_ctx) = bus.recv().expect("failed to receive event on channel");
err_ctx.add_call(ContextType::Plugin((&event).into()));
match event {
PluginInstruction::Load(pid_tx, path) => {
PluginInstruction::Load(pid_tx, path, tab_index) => {
let plugin_dir = data_dir.join("plugins/");
let wasm_bytes = fs::read(&path)
.or_else(|_| fs::read(&path.with_extension("wasm")))
@ -100,6 +101,7 @@ pub(crate) fn wasm_thread_main(bus: Bus<PluginInstruction>, store: Store, data_d
let plugin_env = PluginEnv {
plugin_id,
tab_index,
senders: bus.senders.clone(),
wasi_env,
subscriptions: Arc::new(Mutex::new(HashSet::new())),
@ -193,6 +195,7 @@ fn host_set_selectable(plugin_env: &PluginEnv, selectable: i32) {
.send_to_screen(ScreenInstruction::SetSelectable(
PaneId::Plugin(plugin_env.plugin_id),
selectable,
plugin_env.tab_index,
))
.unwrap()
}
@ -204,6 +207,7 @@ fn host_set_fixed_height(plugin_env: &PluginEnv, fixed_height: i32) {
.send_to_screen(ScreenInstruction::SetFixedHeight(
PaneId::Plugin(plugin_env.plugin_id),
fixed_height,
plugin_env.tab_index,
))
.unwrap()
}
@ -215,6 +219,7 @@ fn host_set_fixed_width(plugin_env: &PluginEnv, fixed_width: i32) {
.send_to_screen(ScreenInstruction::SetFixedWidth(
PaneId::Plugin(plugin_env.plugin_id),
fixed_width,
plugin_env.tab_index,
))
.unwrap()
}
@ -226,6 +231,7 @@ fn host_set_invisible_borders(plugin_env: &PluginEnv, invisible_borders: i32) {
.send_to_screen(ScreenInstruction::SetInvisibleBorders(
PaneId::Plugin(plugin_env.plugin_id),
invisible_borders,
plugin_env.tab_index,
))
.unwrap()
}

View file

@ -178,6 +178,8 @@ keybinds:
key: [Ctrl: 'p',]
- action: [SwitchToMode: Session,]
key: [Ctrl: 'o',]
- action: [ScrollToBottom, SwitchToMode: Normal,]
key: [Ctrl: 'c',]
- action: [Quit,]
key: [Ctrl: 'q',]
- action: [ScrollDown,]

View file

@ -202,6 +202,7 @@ pub enum ScreenContext {
ScrollUpAt,
ScrollDown,
ScrollDownAt,
ScrollToBottom,
PageScrollUp,
PageScrollDown,
ClearScroll,

View file

@ -49,6 +49,8 @@ pub enum Action {
ScrollDown,
/// Scroll down at point
ScrollDownAt(Position),
/// Scroll down to bottom in focus pane.
ScrollToBottom,
/// Scroll up one page in focus pane.
PageScrollUp,
/// Scroll down one page in focus pane.

View file

@ -123,8 +123,8 @@ impl Layout {
layout: Option<&PathBuf>,
layout_path: Option<&PathBuf>,
layout_dir: Option<PathBuf>,
) -> Option<Layout> {
let layout_result = layout
) -> Option<Result<Layout, ConfigError>> {
layout
.map(|p| Layout::from_dir(p, layout_dir.as_ref()))
.or_else(|| layout_path.map(|p| Layout::new(p)))
.or_else(|| {
@ -132,16 +132,7 @@ impl Layout {
&std::path::PathBuf::from("default"),
layout_dir.as_ref(),
))
});
match layout_result {
None => None,
Some(Ok(layout)) => Some(layout),
Some(Err(e)) => {
eprintln!("There was an error in the layout file:\n{}", e);
std::process::exit(1);
}
}
})
}
// Currently still needed but on nightly
@ -297,12 +288,9 @@ impl Layout {
pub fn construct_main_layout(&self) -> MainLayout {
let (pre_tab, post_tab, tabs) = self.split_main_and_tab_layout();
// Todo: A proper LayoutError
if tabs.is_empty() {
panic!("The layout file should have a `tabs` section specified");
}
if tabs.len() > 1 {
panic!("The layout file should have one single tab in the `tabs` section specified");
panic!("The layout file should have a [`tabs`] section specified");
}
MainLayout {

View file

@ -528,10 +528,7 @@ fn no_tabs_specified_should_panic() {
}
#[test]
#[should_panic]
// TODO Make error out of this
// Only untill #631 is fixed
fn multiple_tabs_specified_should_panic() {
fn multiple_tabs_specified_should_not_panic() {
let path = layout_test_dir("multiple-tabs-should-panic.yaml".into());
let layout = Layout::new(&path);
let _main_layout = layout.unwrap().construct_main_layout();

View file

@ -1,11 +1,18 @@
use crate::cli::CliArgs;
use crate::consts::{
FEATURES, SYSTEM_DEFAULT_CONFIG_DIR, SYSTEM_DEFAULT_DATA_DIR_PREFIX, VERSION, ZELLIJ_PROJ_DIR,
use crate::{
cli::{CliArgs, Command},
consts::{
FEATURES, SYSTEM_DEFAULT_CONFIG_DIR, SYSTEM_DEFAULT_DATA_DIR_PREFIX, VERSION,
ZELLIJ_PROJ_DIR,
},
input::{
config::{Config, ConfigError},
layout::{Layout, MainLayout},
options::Options,
},
};
use crate::input::options::Options;
use directories_next::BaseDirs;
use serde::{Deserialize, Serialize};
use std::{io::Write, path::Path, path::PathBuf};
use std::{convert::TryFrom, io::Write, path::Path, path::PathBuf, process};
use structopt::StructOpt;
const CONFIG_LOCATION: &str = ".config/zellij";
@ -139,6 +146,68 @@ pub struct Setup {
impl Setup {
/// Entrypoint from main
/// Merges options from the config file and the command line options
/// into `[Options]`, the command line options superceding the config
/// file options:
/// 1. command line options (`zellij options`)
/// 2. config options (`config.yaml`)
pub fn from_options(
opts: &CliArgs,
) -> Result<(Config, Option<MainLayout>, Options), ConfigError> {
let clean = match &opts.command {
Some(Command::Setup(ref setup)) => setup.clean,
_ => false,
};
log::info!("{:?}", clean);
let config = if !clean {
match Config::try_from(opts) {
Ok(config) => config,
Err(e) => {
eprintln!("There was an error in the config file:");
return Err(e);
}
}
} else {
Config::default()
};
let config_options = Options::from_cli(&config.options, opts.command.clone());
let layout_dir = config_options
.layout_dir
.clone()
.or_else(|| get_layout_dir(opts.config_dir.clone().or_else(find_default_config_dir)));
let layout_result = Layout::from_path_or_default(
opts.layout.as_ref(),
opts.layout_path.as_ref(),
layout_dir,
);
let layout = match layout_result {
None => None,
Some(Ok(layout)) => Some(layout),
Some(Err(e)) => {
eprintln!("There was an error in the layout file:");
return Err(e);
}
}
.map(|layout| layout.construct_main_layout());
if let Some(Command::Setup(ref setup)) = &opts.command {
setup.from_cli(opts, &config_options).map_or_else(
|e| {
eprintln!("{:?}", e);
process::exit(1);
},
|_| {},
);
};
Ok((config, layout, config_options))
}
/// General setup helpers
pub fn from_cli(&self, opts: &CliArgs, config_options: &Options) -> std::io::Result<()> {
if self.clean {
return Ok(());
@ -201,7 +270,6 @@ impl Setup {
}
}
if let Some(config_file) = config_file {
use crate::input::config::Config;
message.push_str(&format!("[CONFIG FILE]: {:?}\n", config_file));
match Config::new(&config_file) {
Ok(_) => message.push_str("[CONFIG FILE]: Well defined.\n"),