diff options
| author | Jonah Caplan <jonahcaplan@gmail.com> | 2021-03-08 04:06:23 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-03-08 10:06:23 +0100 |
| commit | 44b0246e91ae65b09f3e2d9b111772a6b093135b (patch) | |
| tree | c1a422e0f9ffb02cb978ae4d5bb68018deb2a998 /src | |
| parent | New tab styling (#216) (diff) | |
| download | sl0ppij-44b0246e91ae65b09f3e2d9b111772a6b093135b.tar.gz sl0ppij-44b0246e91ae65b09f3e2d9b111772a6b093135b.tar.xz sl0ppij-44b0246e91ae65b09f3e2d9b111772a6b093135b.zip | |
feat(tabs): add ability to set tab name (#212)
* send all tabs in vec
* works but no input filtering
* add event types
* add event handler for tab events
* fmt fixups
* update tab name in place, and escape rename works
* rename handle_tab_event handle_tab_rename_keypress
* handle empty new_name when renaming
* fix(tabs): pad active tab name too
* fix(tabs): report proper length
* fix(tabs): always render active tab
* style(fmt): rustfmt
Co-authored-by: Aram Drevekenin <aram@poor.dev>
Diffstat (limited to 'src')
| -rw-r--r-- | src/client/layout.rs | 3 | ||||
| -rw-r--r-- | src/client/tab.rs | 26 | ||||
| -rw-r--r-- | src/common/errors.rs | 2 | ||||
| -rw-r--r-- | src/common/input/actions.rs | 2 | ||||
| -rw-r--r-- | src/common/input/handler.rs | 29 | ||||
| -rw-r--r-- | src/common/input/keybinds.rs | 25 | ||||
| -rw-r--r-- | src/common/mod.rs | 81 | ||||
| -rw-r--r-- | src/common/screen.rs | 45 | ||||
| -rw-r--r-- | src/common/wasm_vm.rs | 21 |
9 files changed, 196 insertions, 38 deletions
diff --git a/src/client/layout.rs b/src/client/layout.rs index c032be38..1b383540 100644 --- a/src/client/layout.rs +++ b/src/client/layout.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; use std::{fs::File, io::prelude::*}; +use crate::common::wasm_vm::EventType; use crate::panes::PositionAndSize; fn split_space_to_parts_vertically( @@ -180,6 +181,8 @@ pub struct Layout { pub plugin: Option<PathBuf>, #[serde(default)] pub expansion_boundary: bool, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub events: Vec<EventType>, } impl Layout { diff --git a/src/client/tab.rs b/src/client/tab.rs index bf096367..1ef0b720 100644 --- a/src/client/tab.rs +++ b/src/client/tab.rs @@ -2,11 +2,13 @@ //! as well as how they should be resized use crate::common::{AppInstruction, SenderWithContext}; +use crate::layout::Layout; use crate::panes::{PaneId, PositionAndSize, TerminalPane}; use crate::pty_bus::{PtyInstruction, VteEvent}; +use crate::wasm_vm::{PluginInputType, PluginInstruction}; use crate::{boundaries::Boundaries, panes::PluginPane}; -use crate::{layout::Layout, wasm_vm::PluginInstruction}; use crate::{os_input_output::OsApi, utils::shared::pad_to_size}; +use serde::{Deserialize, Serialize}; use std::os::unix::io::RawFd; use std::{ cmp::Reverse, @@ -51,6 +53,7 @@ fn split_horizontally_with_gap(rect: &PositionAndSize) -> (PositionAndSize, Posi pub struct Tab { pub index: usize, pub position: usize, + pub name: String, panes: BTreeMap<PaneId, Box<dyn Pane>>, panes_to_hide: HashSet<PaneId>, active_terminal: Option<PaneId>, @@ -64,6 +67,14 @@ pub struct Tab { expansion_boundary: Option<PositionAndSize>, } +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct TabData { + /* subset of fields to publish to plugins */ + pub position: usize, + pub name: String, + pub active: bool, +} + // FIXME: Use a struct that has a pane_type enum, to reduce all of the duplication pub trait Pane { fn x(&self) -> usize; @@ -170,6 +181,7 @@ impl Tab { pub fn new( index: usize, position: usize, + name: String, full_screen_ws: &PositionAndSize, mut os_api: Box<dyn OsApi>, send_pty_instructions: SenderWithContext<PtyInstruction>, @@ -195,6 +207,7 @@ impl Tab { index, position, panes, + name, max_panes, panes_to_hide: HashSet::new(), active_terminal: pane_id, @@ -249,7 +262,11 @@ impl Tab { if let Some(plugin) = &layout.plugin { let (pid_tx, pid_rx) = channel(); self.send_plugin_instructions - .send(PluginInstruction::Load(pid_tx, plugin.clone())) + .send(PluginInstruction::Load( + pid_tx, + plugin.clone(), + layout.events.clone(), + )) .unwrap(); let pid = pid_rx.recv().unwrap(); let new_plugin = PluginPane::new( @@ -546,7 +563,10 @@ impl Tab { } Some(PaneId::Plugin(pid)) => { self.send_plugin_instructions - .send(PluginInstruction::Input(pid, input_bytes)) + .send(PluginInstruction::Input( + PluginInputType::Normal(pid), + input_bytes, + )) .unwrap(); } _ => {} diff --git a/src/common/errors.rs b/src/common/errors.rs index 03aec714..df741629 100644 --- a/src/common/errors.rs +++ b/src/common/errors.rs @@ -198,6 +198,7 @@ pub enum ScreenContext { SwitchTabPrev, CloseTab, GoToTab, + UpdateTabName, } impl From<&ScreenInstruction> for ScreenContext { @@ -236,6 +237,7 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::SwitchTabPrev => ScreenContext::SwitchTabPrev, ScreenInstruction::CloseTab => ScreenContext::CloseTab, ScreenInstruction::GoToTab(_) => ScreenContext::GoToTab, + ScreenInstruction::UpdateTabName(_) => ScreenContext::UpdateTabName, } } } diff --git a/src/common/input/actions.rs b/src/common/input/actions.rs index fe5e2dad..88a7036f 100644 --- a/src/common/input/actions.rs +++ b/src/common/input/actions.rs @@ -48,4 +48,6 @@ pub enum Action { /// Close the current tab. CloseTab, GoToTab(u32), + TabNameInput(Vec<u8>), + SaveTabName, } diff --git a/src/common/input/handler.rs b/src/common/input/handler.rs index 5c4f1971..52db9b4f 100644 --- a/src/common/input/handler.rs +++ b/src/common/input/handler.rs @@ -7,7 +7,7 @@ use crate::errors::ContextType; use crate::os_input_output::OsApi; use crate::pty_bus::PtyInstruction; use crate::screen::ScreenInstruction; -use crate::wasm_vm::PluginInstruction; +use crate::wasm_vm::{EventType, PluginInputType, PluginInstruction}; use crate::CommandIsExecuting; use serde::{Deserialize, Serialize}; @@ -232,6 +232,28 @@ impl InputHandler { .send(ScreenInstruction::GoToTab(i)) .unwrap(); } + Action::TabNameInput(c) => { + self.send_plugin_instructions + .send(PluginInstruction::Input( + PluginInputType::Event(EventType::Tab), + c.clone(), + )) + .unwrap(); + self.send_screen_instructions + .send(ScreenInstruction::UpdateTabName(c)) + .unwrap(); + } + Action::SaveTabName => { + self.send_plugin_instructions + .send(PluginInstruction::Input( + PluginInputType::Event(EventType::Tab), + vec![b'\n'], + )) + .unwrap(); + self.send_screen_instructions + .send(ScreenInstruction::UpdateTabName(vec![b'\n'])) + .unwrap(); + } Action::NoOp => {} } @@ -265,6 +287,7 @@ pub enum InputMode { Tab, /// `Scroll` mode allows scrolling up and down within a pane. Scroll, + RenameTab, } /// Represents the contents of the help message that is printed in the status bar, @@ -310,10 +333,14 @@ pub fn get_help(mode: InputMode) -> Help { keybinds.push(("←↓↑→".to_string(), "Move focus".to_string())); keybinds.push(("n".to_string(), "New".to_string())); keybinds.push(("x".to_string(), "Close".to_string())); + keybinds.push(("r".to_string(), "Rename".to_string())); } InputMode::Scroll => { keybinds.push(("↓↑".to_string(), "Scroll".to_string())); } + InputMode::RenameTab => { + keybinds.push(("Enter".to_string(), "when done".to_string())); + } } keybinds.push(("ESC".to_string(), "BACK".to_string())); keybinds.push(("q".to_string(), "QUIT".to_string())); diff --git a/src/common/input/keybinds.rs b/src/common/input/keybinds.rs index e56488b4..46abf4ab 100644 --- a/src/common/input/keybinds.rs +++ b/src/common/input/keybinds.rs @@ -128,6 +128,13 @@ fn get_defaults_for_mode(mode: &InputMode) -> Result<ModeKeybinds, String> { defaults.insert(Key::Char('n'), vec![Action::NewTab]); defaults.insert(Key::Char('x'), vec![Action::CloseTab]); + defaults.insert( + Key::Char('r'), + vec![ + Action::SwitchToMode(InputMode::RenameTab), + Action::TabNameInput(vec![0]), + ], + ); defaults.insert(Key::Char('q'), vec![Action::Quit]); defaults.insert( Key::Ctrl('g'), @@ -155,6 +162,23 @@ fn get_defaults_for_mode(mode: &InputMode) -> Result<ModeKeybinds, String> { ); defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Command)]); } + InputMode::RenameTab => { + defaults.insert( + Key::Char('\n'), + vec![Action::SaveTabName, Action::SwitchToMode(InputMode::Tab)], + ); + defaults.insert( + Key::Ctrl('g'), + vec![Action::SwitchToMode(InputMode::Normal)], + ); + defaults.insert( + Key::Esc, + vec![ + Action::TabNameInput(vec![0x1b]), + Action::SwitchToMode(InputMode::Tab), + ], + ); + } } Ok(defaults) @@ -178,6 +202,7 @@ pub fn key_to_actions( }; match *mode { InputMode::Normal => mode_keybind_or_action(Action::Write(input)), + InputMode::RenameTab => mode_keybind_or_action(Action::TabNameInput(input)), _ => mode_keybind_or_action(Action::NoOp), } } diff --git a/src/common/mod.rs b/src/common/mod.rs index f78303ea..7b19bcef 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -34,7 +34,9 @@ use os_input_output::OsApi; use pty_bus::{PtyBus, PtyInstruction}; use screen::{Screen, ScreenInstruction}; use utils::consts::{ZELLIJ_IPC_PIPE, ZELLIJ_ROOT_PLUGIN_DIR}; -use wasm_vm::{wasi_stdout, wasi_write_string, zellij_imports, PluginInstruction}; +use wasm_vm::{ + wasi_stdout, wasi_write_string, zellij_imports, EventType, PluginInputType, PluginInstruction, +}; #[derive(Serialize, Deserialize, Debug)] pub enum ApiCommand { @@ -419,6 +421,9 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) { ScreenInstruction::GoToTab(tab_index) => { screen.go_to_tab(tab_index as usize) } + ScreenInstruction::UpdateTabName(c) => { + screen.update_active_tab_name(c); + } ScreenInstruction::Quit => { break; } @@ -438,6 +443,11 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) { let store = Store::default(); let mut plugin_id = 0; let mut plugin_map = HashMap::new(); + let handler_map: HashMap<EventType, String> = + [(EventType::Tab, "handle_tab_rename_keypress".to_string())] + .iter() + .cloned() + .collect(); move || loop { let (event, mut err_ctx) = receive_plugin_instructions @@ -448,7 +458,7 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) { send_pty_instructions.update(err_ctx); send_app_instructions.update(err_ctx); match event { - PluginInstruction::Load(pid_tx, path) => { + PluginInstruction::Load(pid_tx, path, events) => { let project_dirs = ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap(); let plugin_dir = project_dirs.data_dir().join("plugins/"); @@ -490,6 +500,7 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) { send_screen_instructions: send_screen_instructions.clone(), send_app_instructions: send_app_instructions.clone(), wasi_env, + events, }; let zellij = zellij_imports(&store, &plugin_env); @@ -514,32 +525,58 @@ 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() { + PluginInstruction::UpdateTabs(mut tabs) => { + for (instance, plugin_env) in plugin_map.values() { + if !plugin_env.events.contains(&EventType::Tab) { + continue; + } 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(); + tabs.sort_by(|a, b| a.position.cmp(&b.position)); + wasi_write_string( + &plugin_env.wasi_env, + &serde_json::to_string(&tabs).unwrap(), + ); + handler.call(&[]).unwrap(); } } // FIXME: Deduplicate this with the callback below! - PluginInstruction::Input(pid, input_bytes) => { - let (instance, plugin_env) = plugin_map.get(&pid).unwrap(); - - let handle_key = instance.exports.get_function("handle_key").unwrap(); - for key in input_bytes.keys() { - if let Ok(key) = key { - wasi_write_string( - &plugin_env.wasi_env, - &serde_json::to_string(&key).unwrap(), - ); - handle_key.call(&[]).unwrap(); + PluginInstruction::Input(input_type, input_bytes) => { + match input_type { + PluginInputType::Normal(pid) => { + let (instance, plugin_env) = plugin_map.get(&pid).unwrap(); + let handle_key = + instance.exports.get_function("handle_key").unwrap(); + for key in input_bytes.keys() { + if let Ok(key) = key { + wasi_write_string( + &plugin_env.wasi_env, + &serde_json::to_string(&key).unwrap(), + ); + handle_key.call(&[]).unwrap(); + } + } + } + PluginInputType::Event(event) => { + for (instance, plugin_env) in plugin_map.values() { + if !plugin_env.events.contains(&event) { + continue; + } + let handle_key = instance + .exports + .get_function(handler_map.get(&event).unwrap()) + .unwrap(); + for key in input_bytes.keys() { + if let Ok(key) = key { + wasi_write_string( + &plugin_env.wasi_env, + &serde_json::to_string(&key).unwrap(), + ); + handle_key.call(&[]).unwrap(); + } + } + } } } - drop(send_screen_instructions.send(ScreenInstruction::Render)); } PluginInstruction::GlobalInput(input_bytes) => { diff --git a/src/common/screen.rs b/src/common/screen.rs index 7747b43b..199a1223 100644 --- a/src/common/screen.rs +++ b/src/common/screen.rs @@ -2,13 +2,14 @@ use std::collections::BTreeMap; use std::os::unix::io::RawFd; +use std::str; use std::sync::mpsc::Receiver; use super::{AppInstruction, SenderWithContext}; use crate::os_input_output::OsApi; use crate::panes::PositionAndSize; use crate::pty_bus::{PtyInstruction, VteEvent}; -use crate::tab::Tab; +use crate::tab::{Tab, TabData}; use crate::{errors::ErrorContext, wasm_vm::PluginInstruction}; use crate::{layout::Layout, panes::PaneId}; @@ -46,6 +47,7 @@ pub enum ScreenInstruction { SwitchTabPrev, CloseTab, GoToTab(u32), + UpdateTabName(Vec<u8>), } /// A [`Screen`] holds multiple [`Tab`]s, each one holding multiple [`panes`](crate::client::panes). @@ -69,6 +71,7 @@ pub struct Screen { active_tab_index: Option<usize>, /// The [`OsApi`] this [`Screen`] uses. os_api: Box<dyn OsApi>, + tabname_buf: String, } impl Screen { @@ -92,6 +95,7 @@ impl Screen { active_tab_index: None, tabs: BTreeMap::new(), os_api, + tabname_buf: String::new(), } } @@ -103,6 +107,7 @@ impl Screen { let tab = Tab::new( tab_index, position, + String::new(), &self.full_screen_ws, self.os_api.clone(), self.send_pty_instructions.clone(), @@ -246,6 +251,7 @@ impl Screen { let mut tab = Tab::new( tab_index, position, + String::new(), &self.full_screen_ws, self.os_api.clone(), self.send_pty_instructions.clone(), @@ -261,13 +267,36 @@ impl Screen { } 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(); + let mut tab_data = vec![]; + let active_tab_index = self.active_tab_index.unwrap(); + for tab in self.tabs.values() { + tab_data.push(TabData { + position: tab.position, + name: tab.name.clone(), + active: active_tab_index == tab.index, + }); + } + self.send_plugin_instructions + .send(PluginInstruction::UpdateTabs(tab_data)) + .unwrap(); + } + + pub fn update_active_tab_name(&mut self, buf: Vec<u8>) { + let s = str::from_utf8(&buf).unwrap(); + match s { + "\0" => { + self.tabname_buf = String::new(); + } + "\n" => { + let new_name = self.tabname_buf.clone(); + let active_tab = self.get_active_tab_mut().unwrap(); + active_tab.name = new_name; + self.update_tabs(); + self.render(); + } + c => { + self.tabname_buf.push_str(c); + } } } } diff --git a/src/common/wasm_vm.rs b/src/common/wasm_vm.rs index 5d4b1e30..15b920ad 100644 --- a/src/common/wasm_vm.rs +++ b/src/common/wasm_vm.rs @@ -1,24 +1,36 @@ +use crate::tab::TabData; +use serde::{Deserialize, Serialize}; use std::{ path::PathBuf, sync::mpsc::{channel, Sender}, }; use wasmer::{imports, Function, ImportObject, Store, WasmerEnv}; use wasmer_wasi::WasiEnv; -// use crate::utils::logging::debug_log_to_file; use super::{ input::handler::get_help, pty_bus::PtyInstruction, screen::ScreenInstruction, AppInstruction, PaneId, SenderWithContext, }; +#[derive(Clone, Debug, PartialEq, Hash, Eq, Serialize, Deserialize)] +pub enum EventType { + Tab, +} + +#[derive(Clone, Debug)] +pub enum PluginInputType { + Normal(u32), + Event(EventType), +} + #[derive(Clone, Debug)] pub enum PluginInstruction { - Load(Sender<u32>, PathBuf), + Load(Sender<u32>, PathBuf, Vec<EventType>), Draw(Sender<String>, u32, usize, usize), // String buffer, plugin id, rows, cols - Input(u32, Vec<u8>), // plugin id, input bytes + Input(PluginInputType, Vec<u8>), // plugin id, input bytes GlobalInput(Vec<u8>), // input bytes Unload(u32), - UpdateTabs(usize, usize), // num tabs, active tab + UpdateTabs(Vec<TabData>), // num tabs, active tab Quit, } @@ -29,6 +41,7 @@ pub struct PluginEnv { pub send_app_instructions: SenderWithContext<AppInstruction>, pub send_pty_instructions: SenderWithContext<PtyInstruction>, // FIXME: This should be a big bundle of all of the channels pub wasi_env: WasiEnv, + pub events: Vec<EventType>, } // Plugin API --------------------------------------------------------------------------------------------------------- |
