Repository: karimould/zellij-forgot Branch: main Commit: 0ae3e40f116a Files: 8 Total size: 22.4 KB Directory structure: gitextract_2x9kc03_/ ├── .cargo/ │ └── config.toml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── dev.kdl └── src/ ├── helper.rs └── main.rs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .cargo/config.toml ================================================ [build] target = "wasm32-wasi" ================================================ FILE: .gitignore ================================================ /target ================================================ FILE: Cargo.toml ================================================ [package] name = "zellij_forgot" version = "0.4.2" edition = "2021" authors = ["Karim Ould Mahieddine"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] owo-colors = "4.1.0" zellij-tile = "0.41.2" chrono = "0.4" ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2026 Karim Ould Mahieddine Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # ❔Zellij Forgot Zellij is your solution for quickly accessing and remembering your keybindings (or anything else) in Zellij. Forget the hassle of remembering all your keybindings (or other stuff), let Zellij Forgot do it for you! ![usage](https://github.com/karimould/zellij-forgot/raw/main/assets/forgot.gif) ## 🛠️ Installation To install the Zellij Forgot plugin, follow the standard plugin installation process for Zellij. Place the compiled zellij_forgot.wasm file in the appropriate directory. You can find the current release [here](https://github.com/karimould/zellij-forgot/releases) 1. Download the `zellij_forgot.wasm` file from the release matching your installed Zellij version 2. Place it in `~/zellij-plugins` 3. From inside Zellij, run `zellij action new-pane --plugin file:~/zellij-plugins/zellij_forgot.wasm --floating` ## 🎹 Set Up Your Keybinds and Activate 'Zellij Forgot' with Ease The 'Zellij Forgot' plugin fetches the displayed keybinds—or anything else you want to remember—directly from the plugin's configuration key-value map, or automatically loads them from your Zellij keybinds. However, if you prefer not to automatically load the Zellij keybindings, you can easily opt out of this feature. To disable automatic keybinding loading, simply add the following line to your plugin configuration: ```kdl "LOAD_ZELLIJ_BINDINGS" "false" ``` Here is an example with some stuff to remember/forget. ```kdl shared_except "locked" { bind "Ctrl y" { LaunchOrFocusPlugin "file:~/zellij-plugins/zellij_forgot.wasm" { "LOAD_ZELLIJ_BINDINGS" "false" "buy eggs" "5x" "learn rust" "5h" "clean up code" "tomorrow" floating true } } } ``` Below is an example using Zellij's default keybinds. To integrate this into your setup, append the following snippet to your zellij config, specifically within the keybinds segment: Add the following to your [zellij config](https://zellij.dev/documentation/configuration.html) somewhere inside the [`keybinds`](https://zellij.dev/documentation/keybindings.html) section: ```kdl shared_except "locked" { bind "Ctrl y" { LaunchOrFocusPlugin "file:~/zellij-plugins/zellij_forgot.wasm" { "lock" "ctrl + g" "unlock" "ctrl + g" "new pane" "ctrl + p + n" "change focus of pane" "ctrl + p + arrow key" "close pane" "ctrl + p + x" "rename pane" "ctrl + p + c" "toggle fullscreen" "ctrl + p + f" "toggle floating pane" "ctrl + p + w" "toggle embed pane" "ctrl + p + e" "choose right pane" "ctrl + p + l" "choose left pane" "ctrl + p + r" "choose upper pane" "ctrl + p + k" "choose lower pane" "ctrl + p + j" "new tab" "ctrl + t + n" "close tab" "ctrl + t + x" "change focus of tab" "ctrl + t + arrow key" "rename tab" "ctrl + t + r" "sync tab" "ctrl + t + s" "brake pane to new tab" "ctrl + t + b" "brake pane left" "ctrl + t + [" "brake pane right" "ctrl + t + ]" "toggle tab" "ctrl + t + tab" "increase pane size" "ctrl + n + +" "decrease pane size" "ctrl + n + -" "increase pane top" "ctrl + n + k" "increase pane right" "ctrl + n + l" "increase pane bottom" "ctrl + n + j" "increase pane left" "ctrl + n + h" "decrease pane top" "ctrl + n + K" "decrease pane right" "ctrl + n + L" "decrease pane bottom" "ctrl + n + J" "decrease pane left" "ctrl + n + H" "move pane to top" "ctrl + h + k" "move pane to right" "ctrl + h + l" "move pane to bottom" "ctrl + h + j" "move pane to left" "ctrl + h + h" "search" "ctrl + s + s" "go into edit mode" "ctrl + s + e" "detach session" "ctrl + o + w" "open session manager" "ctrl + o + w" "quit zellij" "ctrl + q" floating true } } } ``` Remember, this is just a template. Feel free to customize the keybindings to better suit your workflow! ## Development Load the `dev.kdl` layout from inside zellij: `zellij action new-tab -l dev.kdl` or from outside Zellij with `zellij -l dev.kdl` ================================================ FILE: dev.kdl ================================================ layout { pane size=1 borderless=true { plugin location="zellij:tab-bar" } pane split_direction="vertical" { pane command="nvim" pane { pane edit="Cargo.toml" pane stacked=true { pane size="10%" command="bash" name="COMPILE AND RELOAD PLUGIN" { args "-c" "cargo build && zellij action start-or-reload-plugin file:target/wasm32-wasi/debug/zellij_forgot.wasm" // if you have "watchexec" installed, you can comment the above line and uncomment the below one to build + reload the plugin on fs changes // args "-c" "watchexec 'cargo build && zellij action start-or-reload-plugin file:target/wasm32-wasi/debug/rust-plugin-example.wasm'" } pane name="zellij_forgot" { plugin location="file:target/wasm32-wasi/debug/zellij_forgot.wasm" { "lock" "ctrl + g" "unlock" "ctrl + g" "new pane" "ctrl + p + n" "change focus of pane" "ctrl + p + arrow key" "close pane" "ctrl + p + x" "rename pane" "ctrl + p + c" "toggle fullscreen" "ctrl + p + f" "toggle floating pane" "ctrl + p + w" "toggle embed pane" "ctrl + p + e" "choose right pane" "ctrl + p + l" "choose left pane" "ctrl + p + r" "choose upper pane" "ctrl + p + k" "choose lower pane" "ctrl + p + j" "new tab" "ctrl + t + n" "close tab" "ctrl + t + x" "change focus of tab" "ctrl + t + arrow key" "rename tab" "ctrl + t + r" "sync tab" "ctrl + t + s" "brake pane to new tab" "ctrl + t + b" "brake pane left" "ctrl + t + [" "brake pane right" "ctrl + t + ]" "toggle tab" "ctrl + t + tab" "increase pane size" "ctrl + n + +" "decrease pane size" "ctrl + n + -" "increase pane top" "ctrl + n + k" "increase pane right" "ctrl + n + l" "increase pane bottom" "ctrl + n + j" "increase pane left" "ctrl + n + h" "decrease pane top" "ctrl + n + K" "decrease pane right" "ctrl + n + L" "decrease pane bottom" "ctrl + n + J" "decrease pane left" "ctrl + n + H" "move pane to top" "ctrl + h + k" "move pane to right" "ctrl + h + l" "move pane to bottom" "ctrl + h + j" "move pane to left" "ctrl + h + h" "search" "ctrl + s + s" "go into edit mode" "ctrl + s + e" "detach session" "ctrl + o + w" "open session manager" "ctrl + o + w" "quit zellij" "ctrl + q" } } } } } pane size=2 borderless=true { plugin location="zellij:status-bar" } } ================================================ FILE: src/helper.rs ================================================ use zellij_tile::prelude::{actions::Action, InputMode}; pub fn modes_to_string(mode: InputMode) -> String { match mode { InputMode::Normal => "Normal".to_string(), InputMode::Locked => "Locked".to_string(), InputMode::Resize => "Resize".to_string(), InputMode::Pane => "Pane".to_string(), InputMode::Tab => "Tab".to_string(), InputMode::Scroll => "Scroll".to_string(), InputMode::EnterSearch => "Enter Search".to_string(), InputMode::Search => "Search".to_string(), InputMode::RenameTab => "RenameTab".to_string(), InputMode::RenamePane => "RenamePane".to_string(), InputMode::Session => "Session".to_string(), InputMode::Move => "Move".to_string(), InputMode::Prompt => "Prompt".to_string(), InputMode::Tmux => "Tmux".to_string(), } } // Zellij Version 0.41.2 pub fn actions_to_string(actions: Vec) -> String { actions .iter() .map(|action| match action { Action::Quit => "Quit".to_string(), Action::Write(key, data, bool) => format!( "Write({:?}, {}, {})", key, String::from_utf8_lossy(data), bool ), Action::WriteChars(chars) => format!("WriteChars({})", chars), Action::SwitchToMode(mode) => format!("SwitchToMode({:?})", mode), Action::SwitchModeForAllClients(mode) => format!("SwitchModeForAllClients({:?})", mode), Action::Resize(resize, direction) => format!("Resize({:?}, {:?})", resize, direction), Action::FocusNextPane => "FocusNextPane".to_string(), Action::FocusPreviousPane => "FocusPreviousPane".to_string(), Action::MoveFocus(direction) => format!("MoveFocus({:?})", direction), Action::MoveFocusOrTab(direction) => format!("MoveFocusOrTab({:?})", direction), Action::MovePane(direction) => format!("MovePane({:?})", direction), Action::MovePaneBackwards => "MovePaneBackward".to_string(), Action::ClearScreen => "ClearScreen".to_string(), Action::DumpScreen(_, _) => "DumpScreen".to_string(), Action::DumpLayout => "DumpLayout".to_string(), Action::EditScrollback => "EditScrollback".to_string(), Action::ScrollUp => "ScrollUp".to_string(), Action::ScrollUpAt(position) => format!("ScrollUpAt({:?})", position), Action::ScrollDown => "ScrollDown".to_string(), Action::ScrollDownAt(position) => format!("ScrollDownAt({:?})", position), Action::ScrollToBottom => "ScrollToBottom".to_string(), Action::ScrollToTop => "ScrollToTop".to_string(), Action::PageScrollUp => "PageScrollUp".to_string(), Action::PageScrollDown => "PageScrollDown".to_string(), Action::HalfPageScrollUp => "HalfPageScrollUp".to_string(), Action::HalfPageScrollDown => "HalfPageScrollDown".to_string(), Action::ToggleFocusFullscreen => "ToggleFocusFullscreen".to_string(), Action::TogglePaneFrames => "TogglePaneFrames".to_string(), Action::ToggleActiveSyncTab => "ToggleActiveSyncTab".to_string(), Action::NewPane(direction, command, bool) => { format!("NewPane({:?}, {:?}, {})", direction, command, bool) } Action::EditFile(path, direction, bool1, bool2, bool3, coordinates) => format!( "EditFile({:?}, {:?}, {}, {}, {}, {:?})", path, direction, bool1, bool2, bool3, coordinates ), Action::NewFloatingPane(run_command_action, command, option) => { format!( "NewFloatingPane({:?}, {:?}, {:?})", run_command_action, command, option ) } Action::NewTiledPane(direction, run_command_action, command) => format!( "NewTiledPane({:?}, {:?}, {:?})", direction, run_command_action, command ), Action::NewInPlacePane(run_command_action, command) => { format!("NewInPlacePane({:?}, {:?})", run_command_action, command) } Action::TogglePaneEmbedOrFloating => "TogglePaneEmbedOrFloating".to_string(), Action::ToggleFloatingPanes => "ToggleFloatingPanes".to_string(), Action::CloseFocus => "CloseFocus".to_string(), Action::PaneNameInput(data) => { format!("PaneNameInput({})", String::from_utf8_lossy(data)) } Action::UndoRenamePane => "UndoRenamePane".to_string(), Action::NewTab(..) => "NewTab".to_string(), Action::NoOp => "NoOp".to_string(), Action::GoToNextTab => "GoToNextTab".to_string(), Action::GoToPreviousTab => "GoToPreviousTab".to_string(), Action::CloseTab => "CloseTab".to_string(), Action::GoToTab(index) => format!("GoToTab({})", index), Action::GoToTabName(name, exact) => format!("GoToTabName({}, {})", name, exact), Action::ToggleTab => "ToggleTab".to_string(), Action::TabNameInput(data) => { format!("TabNameInput({})", String::from_utf8_lossy(data)) } Action::UndoRenameTab => "UndoRenameTab".to_string(), Action::Run(run_command_action) => format!("Run({:?})", run_command_action), Action::Detach => "Detach".to_string(), Action::LeftClick(position) => format!("LeftClick({:?})", position), Action::RightClick(position) => format!("RightClick({:?})", position), Action::MiddleClick(position) => format!("MiddleClick({:?})", position), Action::LaunchOrFocusPlugin(plugin, _, _, _, _) => { format!("LaunchOrFocusPlugin({})", plugin.location_string()) } Action::LaunchPlugin(_, _, _, _, _) => "LaunchPlugin".to_string(), Action::LeftMouseRelease(position) => format!("LeftMouseRelease({:?})", position), Action::RightMouseRelease(position) => format!("RightMouseRelease({:?})", position), Action::MiddleMouseRelease(position) => format!("MiddleMouseRelease({:?})", position), Action::MouseHoldLeft(position) => format!("MouseHoldLeft({:?})", position), Action::MouseHoldRight(position) => format!("MouseHoldRight({:?})", position), Action::MouseHoldMiddle(position) => format!("MouseHoldMiddle({:?})", position), Action::Copy => "Copy".to_string(), Action::Confirm => "Confirm".to_string(), Action::Deny => "Deny".to_string(), Action::SkipConfirm(_) => "SkipConfirm".to_string(), Action::SearchInput(data) => format!("SearchInput({})", String::from_utf8_lossy(data)), Action::Search(direction) => format!("Search({:?})", direction), Action::SearchToggleOption(option) => format!("SearchToggleOption({:?})", option), Action::ToggleMouseMode => "ToggleMouseMode".to_string(), Action::PreviousSwapLayout => "PreviousSwapLayout".to_string(), Action::NextSwapLayout => "NextSwapLayout".to_string(), Action::QueryTabNames => "QueryTabNames".to_string(), Action::NewTiledPluginPane(plugin, _, _, _) => { format!("NewTiledPluginPane({})", plugin.location_string()) } Action::NewFloatingPluginPane(plugin, _, _, _, _) => { format!("NewFloatingPluginPane({})", plugin.location_string()) } Action::NewInPlacePluginPane(plugin, _, _) => { format!("NewInPlacePluginPane({})", plugin.location_string()) } Action::StartOrReloadPlugin(plugin) => { format!("StartOrReloadPlugin({})", plugin.location_string()) } Action::CloseTerminalPane(id) => format!("CloseTerminalPane({})", id), Action::ClosePluginPane(id) => format!("ClosePluginPane({})", id), Action::FocusTerminalPaneWithId(id, force) => { format!("FocusTerminalPaneWithId({}, {})", id, force) } Action::FocusPluginPaneWithId(id, force) => { format!("FocusPluginPaneWithId({}, {})", id, force) } Action::RenameTerminalPane(id, data) => format!( "RenameTerminalPane({}, {})", id, String::from_utf8_lossy(data) ), Action::RenamePluginPane(id, data) => format!( "RenamePluginPane({}, {})", id, String::from_utf8_lossy(data) ), Action::RenameTab(id, data) => { format!("RenameTab({}, {})", id, String::from_utf8_lossy(data)) } Action::BreakPane => "BreakPane".to_string(), Action::BreakPaneRight => "BreakPaneRight".to_string(), Action::BreakPaneLeft => "BreakPaneLeft".to_string(), Action::RenameSession(name) => format!("RenameSession({})", name), _ => "Unknown Action".to_string(), }) .collect::>() .join(", ") } ================================================ FILE: src/main.rs ================================================ use owo_colors::OwoColorize; use std::collections::BTreeMap; use zellij_tile::prelude::*; mod helper; static LOAD_ZELLIJ_BINDINGS: &str = "LOAD_ZELLIJ_BINDINGS"; #[derive(Default)] struct State { load_zellij_bindings: Option, keybinds: BTreeMap, filter: String, } impl State { fn filtered_keybinds(&self) -> Vec<(&String, &String)> { let filter = self.filter.to_lowercase(); self.keybinds .iter() .filter(|(k, v)| { k.to_lowercase().contains(&filter) || v.to_lowercase().contains(&filter) }) .collect() } fn max_key_length(&self) -> usize { self.filtered_keybinds() .iter() .map(|(key, _)| key.len()) .max() .unwrap_or(0) } } register_plugin!(State); impl ZellijPlugin for State { fn load(&mut self, configuration: BTreeMap) { request_permission(&[ PermissionType::ReadApplicationState, PermissionType::ChangeApplicationState, ]); subscribe(&[EventType::Key, EventType::ModeUpdate]); self.load_zellij_bindings = configuration.get(LOAD_ZELLIJ_BINDINGS).cloned(); self.keybinds = configuration; self.keybinds.remove(LOAD_ZELLIJ_BINDINGS); } fn update(&mut self, event: Event) -> bool { let mut should_render = false; match event { Event::Key(key) => match key.bare_key { BareKey::Esc => { close_focus(); } BareKey::Char('c') if key.has_modifiers(&[KeyModifier::Ctrl]) => { close_focus(); } BareKey::Backspace => { self.filter.pop(); should_render = true; } BareKey::Char(c) if c.is_ascii_alphanumeric() || c == '-' || c == ':' || c.is_whitespace() => { self.filter.push(c); should_render = true; } _ => {} }, Event::ModeUpdate(mode_info) => { match self.load_zellij_bindings == Some("false".to_string()) { true => {} false => { for (mode, key_actions) in mode_info.keybinds { let mode_str = helper::modes_to_string(mode).cyan().bold().to_string(); for (key, actions) in key_actions { let key_str = key.to_string(); let action_str = helper::actions_to_string(actions).cyan().bold().to_string(); self.keybinds.insert( format!("Mode: {}, Action: {}", mode_str, action_str), key_str, ); } } } } should_render = true; } _ => (), }; should_render } fn render(&mut self, _rows: usize, _cols: usize) { for (key, value) in &self.filtered_keybinds() { println!( "{} {:width$} {} {}", "-".green(), key, "->".green(), value.cyan().bold(), width = &self.max_key_length() ); } println!(); println!( "{} {}", ">".cyan().bold(), if self.filter.is_empty() { "Search: _".cyan().bold().to_string() } else { format!("{}{}", "Search: ", self.filter) .cyan() .bold() .to_string() } ); } }