mirror of
https://github.com/zellij-org/zellij.git
synced 2024-12-03 13:33:00 +03:00
add basic tab bar #166
This commit is contained in:
parent
7b69fcb8e0
commit
ce54127d7d
8
Cargo.lock
generated
8
Cargo.lock
generated
@ -1579,6 +1579,14 @@ dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tab-bar"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"colored",
|
||||
"zellij-tile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.0"
|
||||
|
@ -53,6 +53,7 @@ members = [
|
||||
"zellij-tile",
|
||||
"default-tiles/status-bar",
|
||||
"default-tiles/strider",
|
||||
"default-tiles/tab-bar",
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
|
@ -1,6 +1,10 @@
|
||||
---
|
||||
direction: Horizontal
|
||||
parts:
|
||||
- direction: Vertical
|
||||
split_size:
|
||||
Fixed: 1
|
||||
plugin: tab-bar
|
||||
- direction: Vertical
|
||||
expansion_boundary: true
|
||||
- direction: Vertical
|
||||
|
18
build-all.sh
18
build-all.sh
@ -1,19 +1,25 @@
|
||||
#!/bin/sh
|
||||
|
||||
total=6
|
||||
|
||||
# This is temporary while https://github.com/rust-lang/cargo/issues/7004 is open
|
||||
|
||||
echo "Building zellij-tile (1/5)..."
|
||||
echo "Building zellij-tile (1/$total)..."
|
||||
cd zellij-tile
|
||||
cargo build --release
|
||||
echo "Building status-bar (2/5)..."
|
||||
echo "Building status-bar (2/$total)..."
|
||||
cd ../default-tiles/status-bar
|
||||
cargo build --release
|
||||
echo "Building strider (3/5)..."
|
||||
echo "Building strider (3/$total)..."
|
||||
cd ../strider
|
||||
cargo build --release
|
||||
echo "Optimising WASM executables (4/5)..."
|
||||
echo "Building tab-bar (4/$total)..."
|
||||
cd ../tab-bar
|
||||
cargo build --release
|
||||
echo "Optimising WASM executables (5/$total)..."
|
||||
cd ../..
|
||||
wasm-opt -O target/wasm32-wasi/release/status-bar.wasm -o target/status-bar.wasm || cp target/wasm32-wasi/release/status-bar.wasm target/status-bar.wasm
|
||||
wasm-opt -O target/wasm32-wasi/release/strider.wasm -o target/strider.wasm || cp target/wasm32-wasi/release/strider.wasm target/strider.wasm
|
||||
echo "Building zellij (5/5)..."
|
||||
cargo build $@
|
||||
wasm-opt -O target/wasm32-wasi/release/tab-bar.wasm -o assets/plugins/tab-bar.wasm || cp target/wasm32-wasi/release/tab-bar.wasm target/tab-bar.wasm
|
||||
echo "Building zellij (6/$total)..."
|
||||
cargo build $@
|
||||
|
1
build.rs
1
build.rs
@ -42,6 +42,7 @@ fn main() {
|
||||
let project_dirs = ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap();
|
||||
let data_dir = project_dirs.data_dir();
|
||||
drop(fs::remove_file(data_dir.join("plugins/status-bar.wasm")));
|
||||
drop(fs::remove_file(data_dir.join("plugins/tab-bar.wasm")));
|
||||
drop(fs::remove_file(data_dir.join("plugins/strider.wasm")));
|
||||
drop(fs::remove_file(data_dir.join("layouts/default.yaml")));
|
||||
drop(fs::remove_file(data_dir.join("layouts/strider.yaml")));
|
||||
|
2
default-tiles/tab-bar/.cargo/config.toml
Normal file
2
default-tiles/tab-bar/.cargo/config.toml
Normal file
@ -0,0 +1,2 @@
|
||||
[build]
|
||||
target = "wasm32-wasi"
|
10
default-tiles/tab-bar/Cargo.toml
Normal file
10
default-tiles/tab-bar/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "tab-bar"
|
||||
version = "0.1.0"
|
||||
authors = ["Jonah Caplan <jonahcaplan@gmail.com>"]
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
colored = "2"
|
||||
zellij-tile = { path = "../../zellij-tile" }
|
1
default-tiles/tab-bar/LICENSE.md
Symbolic link
1
default-tiles/tab-bar/LICENSE.md
Symbolic link
@ -0,0 +1 @@
|
||||
../../LICENSE.md
|
40
default-tiles/tab-bar/src/main.rs
Normal file
40
default-tiles/tab-bar/src/main.rs
Normal file
@ -0,0 +1,40 @@
|
||||
use colored::*;
|
||||
use zellij_tile::*;
|
||||
|
||||
#[derive(Default)]
|
||||
struct State {
|
||||
active_tab_index: usize,
|
||||
num_tabs: usize,
|
||||
}
|
||||
|
||||
register_tile!(State);
|
||||
|
||||
impl ZellijTile for State {
|
||||
fn init(&mut self) {
|
||||
set_selectable(false);
|
||||
set_invisible_borders(true);
|
||||
set_max_height(1);
|
||||
self.active_tab_index = 0;
|
||||
self.num_tabs = 0;
|
||||
}
|
||||
|
||||
fn draw(&mut self, _rows: usize, _cols: usize) {
|
||||
let mut s = String::new();
|
||||
let active_tab = self.active_tab_index + 1;
|
||||
for i in 1..=self.num_tabs {
|
||||
let tab;
|
||||
if i == active_tab {
|
||||
tab = format!("*{} ", i).black().bold().on_magenta();
|
||||
} else {
|
||||
tab = format!("-{} ", i).white();
|
||||
}
|
||||
s = format!("{}{}", s, tab);
|
||||
}
|
||||
println!("Tabs: {}\u{1b}[40m\u{1b}[0K", s);
|
||||
}
|
||||
|
||||
fn update_tabs(&mut self, active_tab_index: usize, num_tabs: usize) {
|
||||
self.active_tab_index = active_tab_index;
|
||||
self.num_tabs = num_tabs;
|
||||
}
|
||||
}
|
@ -50,6 +50,7 @@ fn split_horizontally_with_gap(rect: &PositionAndSize) -> (PositionAndSize, Posi
|
||||
|
||||
pub struct Tab {
|
||||
pub index: usize,
|
||||
pub position: usize,
|
||||
panes: BTreeMap<PaneId, Box<dyn Pane>>,
|
||||
panes_to_hide: HashSet<PaneId>,
|
||||
active_terminal: Option<PaneId>,
|
||||
@ -168,6 +169,7 @@ impl Tab {
|
||||
// FIXME: Too many arguments here! Maybe bundle all of the senders for the whole program in a struct?
|
||||
pub fn new(
|
||||
index: usize,
|
||||
position: usize,
|
||||
full_screen_ws: &PositionAndSize,
|
||||
mut os_api: Box<dyn OsApi>,
|
||||
send_pty_instructions: SenderWithContext<PtyInstruction>,
|
||||
@ -191,6 +193,7 @@ impl Tab {
|
||||
};
|
||||
Tab {
|
||||
index,
|
||||
position,
|
||||
panes,
|
||||
max_panes,
|
||||
panes_to_hide: HashSet::new(),
|
||||
|
@ -197,6 +197,7 @@ pub enum ScreenContext {
|
||||
SwitchTabNext,
|
||||
SwitchTabPrev,
|
||||
CloseTab,
|
||||
GoToTab,
|
||||
}
|
||||
|
||||
impl From<&ScreenInstruction> for ScreenContext {
|
||||
@ -234,6 +235,7 @@ impl From<&ScreenInstruction> for ScreenContext {
|
||||
ScreenInstruction::SwitchTabNext => ScreenContext::SwitchTabNext,
|
||||
ScreenInstruction::SwitchTabPrev => ScreenContext::SwitchTabPrev,
|
||||
ScreenInstruction::CloseTab => ScreenContext::CloseTab,
|
||||
ScreenInstruction::GoToTab(_) => ScreenContext::GoToTab,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -277,6 +279,7 @@ pub enum PluginContext {
|
||||
GlobalInput,
|
||||
Unload,
|
||||
Quit,
|
||||
Tabs,
|
||||
}
|
||||
|
||||
impl From<&PluginInstruction> for PluginContext {
|
||||
@ -288,6 +291,7 @@ impl From<&PluginInstruction> for PluginContext {
|
||||
PluginInstruction::GlobalInput(_) => PluginContext::GlobalInput,
|
||||
PluginInstruction::Unload(_) => PluginContext::Unload,
|
||||
PluginInstruction::Quit => PluginContext::Quit,
|
||||
PluginInstruction::UpdateTabs(..) => PluginContext::Tabs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -47,4 +47,5 @@ pub enum Action {
|
||||
GoToPreviousTab,
|
||||
/// Close the current tab.
|
||||
CloseTab,
|
||||
GoToTab(u32),
|
||||
}
|
||||
|
@ -227,6 +227,11 @@ impl InputHandler {
|
||||
.unwrap();
|
||||
self.command_is_executing.wait_until_pane_is_closed();
|
||||
}
|
||||
Action::GoToTab(i) => {
|
||||
self.send_screen_instructions
|
||||
.send(ScreenInstruction::GoToTab(i))
|
||||
.unwrap();
|
||||
}
|
||||
Action::NoOp => {}
|
||||
}
|
||||
|
||||
|
@ -133,6 +133,9 @@ fn get_defaults_for_mode(mode: &InputMode) -> Result<ModeKeybinds, String> {
|
||||
Key::Ctrl('g'),
|
||||
vec![Action::SwitchToMode(InputMode::Normal)],
|
||||
);
|
||||
for i in '1'..='9' {
|
||||
defaults.insert(Key::Char(i), vec![Action::GoToTab(i.to_digit(10).unwrap())]);
|
||||
}
|
||||
defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Command)]);
|
||||
}
|
||||
InputMode::Scroll => {
|
||||
|
@ -416,6 +416,9 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
|
||||
screen.apply_layout(layout, new_pane_pids);
|
||||
command_is_executing.done_opening_new_pane();
|
||||
}
|
||||
ScreenInstruction::GoToTab(tab_index) => {
|
||||
screen.go_to_tab(tab_index as usize)
|
||||
}
|
||||
ScreenInstruction::Quit => {
|
||||
break;
|
||||
}
|
||||
@ -511,6 +514,17 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
|
||||
|
||||
buf_tx.send(wasi_stdout(&plugin_env.wasi_env)).unwrap();
|
||||
}
|
||||
PluginInstruction::UpdateTabs(active_tab_index, num_tabs) => {
|
||||
for (instance, _) in plugin_map.values() {
|
||||
let handler = instance.exports.get_function("update_tabs").unwrap();
|
||||
handler
|
||||
.call(&[
|
||||
Value::I32(active_tab_index as i32),
|
||||
Value::I32(num_tabs as i32),
|
||||
])
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
// FIXME: Deduplicate this with the callback below!
|
||||
PluginInstruction::Input(pid, input_bytes) => {
|
||||
let (instance, plugin_env) = plugin_map.get(&pid).unwrap();
|
||||
|
@ -45,6 +45,7 @@ pub enum ScreenInstruction {
|
||||
SwitchTabNext,
|
||||
SwitchTabPrev,
|
||||
CloseTab,
|
||||
GoToTab(u32),
|
||||
}
|
||||
|
||||
/// A [`Screen`] holds multiple [`Tab`]s, each one holding multiple [`panes`](crate::client::panes).
|
||||
@ -98,8 +99,10 @@ impl Screen {
|
||||
/// [pane](crate::client::panes) with PTY file descriptor `pane_id`.
|
||||
pub fn new_tab(&mut self, pane_id: RawFd) {
|
||||
let tab_index = self.get_new_tab_index();
|
||||
let position = self.tabs.len();
|
||||
let tab = Tab::new(
|
||||
tab_index,
|
||||
position,
|
||||
&self.full_screen_ws,
|
||||
self.os_api.clone(),
|
||||
self.send_pty_instructions.clone(),
|
||||
@ -110,6 +113,7 @@ impl Screen {
|
||||
);
|
||||
self.active_tab_index = Some(tab_index);
|
||||
self.tabs.insert(tab_index, tab);
|
||||
self.update_tabs();
|
||||
self.render();
|
||||
}
|
||||
|
||||
@ -126,34 +130,52 @@ impl Screen {
|
||||
|
||||
/// Sets this [`Screen`]'s active [`Tab`] to the next tab.
|
||||
pub fn switch_tab_next(&mut self) {
|
||||
let active_tab_id = self.get_active_tab().unwrap().index;
|
||||
let tab_ids: Vec<usize> = self.tabs.keys().copied().collect();
|
||||
let first_tab = tab_ids.get(0).unwrap();
|
||||
let active_tab_id_position = tab_ids.iter().position(|id| id == &active_tab_id).unwrap();
|
||||
if let Some(next_tab) = tab_ids.get(active_tab_id_position + 1) {
|
||||
self.active_tab_index = Some(*next_tab);
|
||||
} else {
|
||||
self.active_tab_index = Some(*first_tab);
|
||||
let active_tab_pos = self.get_active_tab().unwrap().position;
|
||||
let new_tab_pos = (active_tab_pos + 1) % self.tabs.len();
|
||||
|
||||
for tab in self.tabs.values() {
|
||||
if tab.position == new_tab_pos {
|
||||
self.active_tab_index = Some(tab.index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.update_tabs();
|
||||
self.render();
|
||||
}
|
||||
|
||||
/// Sets this [`Screen`]'s active [`Tab`] to the previous tab.
|
||||
pub fn switch_tab_prev(&mut self) {
|
||||
let active_tab_id = self.get_active_tab().unwrap().index;
|
||||
let tab_ids: Vec<usize> = self.tabs.keys().copied().collect();
|
||||
let first_tab = tab_ids.get(0).unwrap();
|
||||
let last_tab = tab_ids.last().unwrap();
|
||||
|
||||
let active_tab_id_position = tab_ids.iter().position(|id| id == &active_tab_id).unwrap();
|
||||
if active_tab_id == *first_tab {
|
||||
self.active_tab_index = Some(*last_tab)
|
||||
} else if let Some(prev_tab) = tab_ids.get(active_tab_id_position - 1) {
|
||||
self.active_tab_index = Some(*prev_tab)
|
||||
let active_tab_pos = self.get_active_tab().unwrap().position;
|
||||
let new_tab_pos = if active_tab_pos == 0 {
|
||||
self.tabs.len() - 1
|
||||
} else {
|
||||
active_tab_pos - 1
|
||||
};
|
||||
for tab in self.tabs.values() {
|
||||
if tab.position == new_tab_pos {
|
||||
self.active_tab_index = Some(tab.index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.update_tabs();
|
||||
self.render();
|
||||
}
|
||||
|
||||
pub fn go_to_tab(&mut self, mut tab_index: usize) {
|
||||
tab_index -= 1;
|
||||
let active_tab = self.get_active_tab().unwrap();
|
||||
match self.tabs.values().find(|t| t.position == tab_index) {
|
||||
Some(t) => {
|
||||
if t.index != active_tab.index {
|
||||
self.active_tab_index = Some(t.index);
|
||||
self.update_tabs();
|
||||
self.render();
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Closes this [`Screen`]'s active [`Tab`], exiting the application if it happens
|
||||
/// to be the last tab.
|
||||
pub fn close_tab(&mut self) {
|
||||
@ -174,6 +196,13 @@ impl Screen {
|
||||
self.send_app_instructions
|
||||
.send(AppInstruction::Exit)
|
||||
.unwrap();
|
||||
} else {
|
||||
for t in self.tabs.values_mut() {
|
||||
if t.position > active_tab.position {
|
||||
t.position -= 1;
|
||||
}
|
||||
}
|
||||
self.update_tabs();
|
||||
}
|
||||
}
|
||||
|
||||
@ -213,8 +242,10 @@ impl Screen {
|
||||
/// and switching to it.
|
||||
pub fn apply_layout(&mut self, layout: Layout, new_pids: Vec<RawFd>) {
|
||||
let tab_index = self.get_new_tab_index();
|
||||
let position = self.tabs.len();
|
||||
let mut tab = Tab::new(
|
||||
tab_index,
|
||||
position,
|
||||
&self.full_screen_ws,
|
||||
self.os_api.clone(),
|
||||
self.send_pty_instructions.clone(),
|
||||
@ -226,5 +257,17 @@ impl Screen {
|
||||
tab.apply_layout(layout, new_pids);
|
||||
self.active_tab_index = Some(tab_index);
|
||||
self.tabs.insert(tab_index, tab);
|
||||
self.update_tabs();
|
||||
}
|
||||
|
||||
fn update_tabs(&self) {
|
||||
if let Some(active_tab) = self.get_active_tab() {
|
||||
self.send_plugin_instructions
|
||||
.send(PluginInstruction::UpdateTabs(
|
||||
active_tab.position,
|
||||
self.tabs.len(),
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ pub enum PluginInstruction {
|
||||
Input(u32, Vec<u8>), // plugin id, input bytes
|
||||
GlobalInput(Vec<u8>), // input bytes
|
||||
Unload(u32),
|
||||
UpdateTabs(usize, usize), // num tabs, active tab
|
||||
Quit,
|
||||
}
|
||||
|
||||
|
@ -33,6 +33,7 @@ pub fn main() {
|
||||
let data_dir = project_dirs.data_dir();
|
||||
let assets = asset_map! {
|
||||
"target/status-bar.wasm" => "plugins/status-bar.wasm",
|
||||
"target/tab-bar.wasm" => "plugins/tab-bar.wasm",
|
||||
"target/strider.wasm" => "plugins/strider.wasm",
|
||||
"assets/layouts/default.yaml" => "layouts/default.yaml",
|
||||
"assets/layouts/strider.yaml" => "layouts/strider.yaml",
|
||||
|
@ -7,6 +7,7 @@ pub trait ZellijTile {
|
||||
fn draw(&mut self, rows: usize, cols: usize) {}
|
||||
fn handle_key(&mut self, key: Key) {}
|
||||
fn handle_global_key(&mut self, key: Key) {}
|
||||
fn update_tabs(&mut self, active_tab_index: usize, num_active_tabs: usize) {}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
@ -42,5 +43,14 @@ macro_rules! register_tile {
|
||||
state.borrow_mut().handle_global_key($crate::get_key());
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn update_tabs(active_tab_index: i32, num_active_tabs: i32) {
|
||||
STATE.with(|state| {
|
||||
state
|
||||
.borrow_mut()
|
||||
.update_tabs(active_tab_index as usize, num_active_tabs as usize);
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user