├── .gitignore ├── LICENSE ├── defaults ├── visual_mode.lua ├── search_mode.lua ├── scroll_mode.lua ├── ui_mode.lua ├── keybinds.md └── copy_mode.lua ├── README.md └── plugin └── init.lua /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Lua sources 2 | luac.out 3 | 4 | # luarocks build files 5 | *.src.rock 6 | *.zip 7 | *.tar.gz 8 | 9 | # Object files 10 | *.o 11 | *.os 12 | *.ko 13 | *.obj 14 | *.elf 15 | 16 | # Precompiled Headers 17 | *.gch 18 | *.pch 19 | 20 | # Libraries 21 | *.lib 22 | *.a 23 | *.la 24 | *.lo 25 | *.def 26 | *.exp 27 | 28 | # Shared objects (inc. Windows DLLs) 29 | *.dll 30 | *.so 31 | *.so.* 32 | *.dylib 33 | 34 | # Executables 35 | *.exe 36 | *.out 37 | *.app 38 | *.i*86 39 | *.x86_64 40 | *.hex 41 | 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Malthe Larsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /defaults/visual_mode.lua: -------------------------------------------------------------------------------- 1 | -- NOTE: This file is used to make status text for copy mode visual selections 2 | local wezterm = require("wezterm") 3 | 4 | ---Create status text with hints 5 | ---@param hint_icons {left_seperator: string, key_hint_seperator: string, mod_seperator: string} 6 | ---@param hint_colors {key_hint_seperator: string, key: string, hint: string, bg: string, left_bg: string} 7 | ---@param mode_colors {bg: string, fg: string} 8 | ---@return string 9 | local function get_hint_status_text(hint_icons, hint_colors, mode_colors) 10 | return wezterm.format({ 11 | { Foreground = { Color = hint_colors.bg } }, 12 | { Background = { Color = hint_colors.left_bg } }, 13 | { Text = hint_icons.left_seperator }, 14 | { Background = { Color = hint_colors.bg } }, 15 | -- ... 16 | { Foreground = { Color = hint_colors.key } }, 17 | { Text = "Shift,CTRL,ALT?" }, 18 | { Text = hint_icons.mod_seperator }, 19 | { Text = "v: " }, 20 | { Foreground = { Color = hint_colors.hint } }, 21 | { Text = "Change visual mode" }, 22 | { Foreground = { Color = hint_colors.key_hint_seperator } }, 23 | { Text = hint_icons.key_hint_seperator }, 24 | -- ... 25 | { Foreground = { Color = hint_colors.key } }, 26 | { Text = "o/O: " }, 27 | { Foreground = { Color = hint_colors.hint } }, 28 | { Text = "Other end of selection" }, 29 | { Foreground = { Color = hint_colors.key_hint_seperator } }, 30 | { Text = hint_icons.key_hint_seperator }, 31 | -- ... 32 | { Foreground = { Color = hint_colors.key } }, 33 | { Text = "s/S: " }, 34 | { Foreground = { Color = hint_colors.hint } }, 35 | { Text = "Semantic jump" }, 36 | { Foreground = { Color = hint_colors.key_hint_seperator } }, 37 | { Text = hint_icons.key_hint_seperator }, 38 | -- ... 39 | { Foreground = { Color = hint_colors.key } }, 40 | { Text = "y: " }, 41 | { Foreground = { Color = hint_colors.hint } }, 42 | { Text = "Copy and exit" }, 43 | { Foreground = { Color = hint_colors.key_hint_seperator } }, 44 | { Text = hint_icons.key_hint_seperator }, 45 | -- ... 46 | { Foreground = { Color = hint_colors.key } }, 47 | { Text = "hjkl: " }, 48 | { Foreground = { Color = hint_colors.hint } }, 49 | { Text = "Move " }, 50 | -- ... 51 | { Attribute = { Intensity = "Bold" } }, 52 | { Foreground = { Color = mode_colors.bg } }, 53 | { Text = hint_icons.left_seperator }, 54 | { Foreground = { Color = mode_colors.fg } }, 55 | { Background = { Color = mode_colors.bg } }, 56 | { Text = "Visual " }, 57 | }) 58 | end 59 | 60 | ---Create mode status text 61 | ---@param bg string 62 | ---@param fg string 63 | ---@param left_seperator string 64 | ---@return string 65 | local function get_mode_status_text(left_seperator, bg, fg) 66 | return wezterm.format({ 67 | { Attribute = { Intensity = "Bold" } }, 68 | { Foreground = { Color = bg } }, 69 | { Text = left_seperator }, 70 | { Foreground = { Color = fg } }, 71 | { Background = { Color = bg } }, 72 | { Text = "Visual " }, 73 | }) 74 | end 75 | 76 | return { 77 | get_mode_status_text = get_mode_status_text, 78 | get_hint_status_text = get_hint_status_text, 79 | } 80 | -------------------------------------------------------------------------------- /defaults/search_mode.lua: -------------------------------------------------------------------------------- 1 | local wezterm = require("wezterm") 2 | local modal = wezterm.plugin.require("https://github.com/MLFlexer/modal.wezterm") 3 | 4 | ---Create status text with hints 5 | ---@param hint_icons {left_seperator: string, key_hint_seperator: string, mod_seperator: string} 6 | ---@param hint_colors {key_hint_seperator: string, key: string, hint: string, bg: string, left_bg: string} 7 | ---@param mode_colors {bg: string, fg: string} 8 | ---@return string 9 | local function get_hint_status_text(hint_icons, hint_colors, mode_colors) 10 | return wezterm.format({ 11 | { Foreground = { Color = hint_colors.bg } }, 12 | { Background = { Color = hint_colors.left_bg } }, 13 | { Text = hint_icons.left_seperator }, 14 | { Background = { Color = hint_colors.bg } }, 15 | -- ... 16 | { Foreground = { Color = hint_colors.key } }, 17 | { Text = "CTRL" }, 18 | { Text = hint_icons.mod_seperator }, 19 | { Text = "p/n, 󰓢 : " }, 20 | { Foreground = { Color = hint_colors.hint } }, 21 | { Text = "Prev/Next result" }, 22 | { Foreground = { Color = hint_colors.key_hint_seperator } }, 23 | { Text = hint_icons.key_hint_seperator }, 24 | -- ... 25 | { Foreground = { Color = hint_colors.key } }, 26 | { Text = "CTRL" }, 27 | { Text = hint_icons.mod_seperator }, 28 | { Text = "r: " }, 29 | { Foreground = { Color = hint_colors.hint } }, 30 | { Text = "Cycle match type" }, 31 | { Foreground = { Color = hint_colors.key_hint_seperator } }, 32 | { Text = hint_icons.key_hint_seperator }, 33 | -- ... 34 | { Foreground = { Color = hint_colors.key } }, 35 | { Text = "CTRL" }, 36 | { Text = hint_icons.mod_seperator }, 37 | { Text = "u: " }, 38 | { Foreground = { Color = hint_colors.hint } }, 39 | { Text = "Clear search" }, 40 | { Foreground = { Color = hint_colors.key_hint_seperator } }, 41 | { Text = hint_icons.key_hint_seperator }, 42 | -- ... 43 | { Foreground = { Color = hint_colors.key } }, 44 | { Text = "Enter: " }, 45 | { Foreground = { Color = hint_colors.hint } }, 46 | { Text = "Accep pattern" }, 47 | { Foreground = { Color = hint_colors.key_hint_seperator } }, 48 | { Text = hint_icons.key_hint_seperator }, 49 | -- ... 50 | { Foreground = { Color = hint_colors.key } }, 51 | { Text = "Esc: " }, 52 | { Foreground = { Color = hint_colors.hint } }, 53 | { Text = "End search" }, 54 | -- ... 55 | { Attribute = { Intensity = "Bold" } }, 56 | { Foreground = { Color = mode_colors.bg } }, 57 | { Text = hint_icons.left_seperator }, 58 | { Foreground = { Color = mode_colors.fg } }, 59 | { Background = { Color = mode_colors.bg } }, 60 | { Text = "Search " }, 61 | }) 62 | end 63 | 64 | ---Create mode status text 65 | ---@param bg string 66 | ---@param fg string 67 | ---@param left_seperator string 68 | ---@return string 69 | local function get_mode_status_text(left_seperator, bg, fg) 70 | return wezterm.format({ 71 | { Attribute = { Intensity = "Bold" } }, 72 | { Foreground = { Color = bg } }, 73 | { Text = left_seperator }, 74 | { Foreground = { Color = fg } }, 75 | { Background = { Color = bg } }, 76 | { Text = "Search " }, 77 | }) 78 | end 79 | 80 | return { 81 | get_mode_status_text = get_mode_status_text, 82 | get_hint_status_text = get_hint_status_text, 83 | key_table = { 84 | { 85 | key = "Enter", 86 | mods = "NONE", 87 | action = wezterm.action.Multiple({ 88 | wezterm.action_callback(function(window, pane) 89 | wezterm.emit("modal.enter", "copy_mode", window, pane) 90 | end), 91 | wezterm.action.CopyMode("AcceptPattern"), 92 | }), 93 | }, 94 | { 95 | key = "Escape", 96 | action = wezterm.action_callback(function(window, pane) 97 | wezterm.GLOBAL.search_mode = false 98 | window:perform_action(modal.exit_mode("search_mode"), pane) 99 | window:perform_action(modal.activate_mode("copy_mode"), pane) 100 | end), 101 | }, 102 | { 103 | key = "c", 104 | mods = "CTRL", 105 | action = wezterm.action_callback(function(window, pane) 106 | wezterm.GLOBAL.search_mode = false 107 | window:perform_action(modal.exit_mode("search_mode"), pane) 108 | window:perform_action(modal.activate_mode("copy_mode"), pane) 109 | end), 110 | }, 111 | { key = "n", mods = "CTRL", action = wezterm.action.CopyMode("NextMatch") }, 112 | { key = "p", mods = "CTRL", action = wezterm.action.CopyMode("PriorMatch") }, 113 | { key = "r", mods = "CTRL", action = wezterm.action.CopyMode("CycleMatchType") }, 114 | { key = "u", mods = "CTRL", action = wezterm.action.CopyMode("ClearPattern") }, 115 | { 116 | key = "PageUp", 117 | mods = "NONE", 118 | action = wezterm.action.CopyMode("PriorMatchPage"), 119 | }, 120 | { 121 | key = "PageDown", 122 | mods = "NONE", 123 | action = wezterm.action.CopyMode("NextMatchPage"), 124 | }, 125 | { key = "UpArrow", mods = "NONE", action = wezterm.action.CopyMode("PriorMatch") }, 126 | { key = "DownArrow", mods = "NONE", action = wezterm.action.CopyMode("NextMatch") }, 127 | }, 128 | } 129 | -------------------------------------------------------------------------------- /defaults/scroll_mode.lua: -------------------------------------------------------------------------------- 1 | local wezterm = require("wezterm") 2 | local act = wezterm.action 3 | local modal = wezterm.plugin.require("https://github.com/MLFlexer/modal.wezterm") 4 | 5 | ---Create status text with hints 6 | ---@param hint_icons {left_seperator: string, key_hint_seperator: string, mod_seperator: string} 7 | ---@param hint_colors {key_hint_seperator: string, key: string, hint: string, bg: string, left_bg: string} 8 | ---@param mode_colors {bg: string, fg: string} 9 | ---@return string 10 | local function get_hint_status_text(hint_icons, hint_colors, mode_colors) 11 | return wezterm.format({ 12 | { Foreground = { Color = hint_colors.bg } }, 13 | { Background = { Color = hint_colors.left_bg } }, 14 | { Text = hint_icons.left_seperator }, 15 | { Background = { Color = hint_colors.bg } }, 16 | -- ... 17 | { Foreground = { Color = hint_colors.key } }, 18 | { Text = "Shift?" }, 19 | { Text = hint_icons.mod_seperator }, 20 | { Text = "jk: " }, 21 | { Foreground = { Color = hint_colors.hint } }, 22 | { Text = "Line scroll" }, 23 | { Foreground = { Color = hint_colors.key_hint_seperator } }, 24 | { Text = hint_icons.key_hint_seperator }, 25 | -- ... 26 | { Foreground = { Color = hint_colors.key } }, 27 | { Text = "Shift?" }, 28 | { Text = hint_icons.mod_seperator }, 29 | { Text = "d/u: " }, 30 | { Foreground = { Color = hint_colors.hint } }, 31 | { Text = "Page scroll" }, 32 | { Foreground = { Color = hint_colors.key_hint_seperator } }, 33 | { Text = hint_icons.key_hint_seperator }, 34 | -- ... 35 | { Foreground = { Color = hint_colors.key } }, 36 | { Text = "{/}, p/n: " }, 37 | { Foreground = { Color = hint_colors.hint } }, 38 | { Text = "Prev/Next prompt" }, 39 | { Foreground = { Color = hint_colors.key_hint_seperator } }, 40 | { Text = hint_icons.key_hint_seperator }, 41 | -- ... 42 | { Foreground = { Color = hint_colors.key } }, 43 | { Text = "g/G: " }, 44 | { Foreground = { Color = hint_colors.hint } }, 45 | { Text = "top/bottom" }, 46 | { Foreground = { Color = hint_colors.key_hint_seperator } }, 47 | { Text = hint_icons.key_hint_seperator }, 48 | -- ... 49 | { Foreground = { Color = hint_colors.key } }, 50 | { Text = "z: " }, 51 | { Foreground = { Color = hint_colors.hint } }, 52 | { Text = "Zoom" }, 53 | { Foreground = { Color = hint_colors.key_hint_seperator } }, 54 | { Text = hint_icons.key_hint_seperator }, 55 | -- ... 56 | { Foreground = { Color = hint_colors.key } }, 57 | { Text = "v: " }, 58 | { Foreground = { Color = hint_colors.hint } }, 59 | { Text = "Copy mode " }, 60 | -- ... 61 | { Attribute = { Intensity = "Bold" } }, 62 | { Foreground = { Color = mode_colors.bg } }, 63 | { Text = hint_icons.left_seperator }, 64 | { Foreground = { Color = mode_colors.fg } }, 65 | { Background = { Color = mode_colors.bg } }, 66 | { Text = "Scroll " }, 67 | }) 68 | end 69 | 70 | ---Create mode status text 71 | ---@param bg string 72 | ---@param fg string 73 | ---@param left_seperator string 74 | ---@return string 75 | local function get_mode_status_text(left_seperator, bg, fg) 76 | return wezterm.format({ 77 | { Attribute = { Intensity = "Bold" } }, 78 | { Foreground = { Color = bg } }, 79 | { Text = left_seperator }, 80 | { Foreground = { Color = fg } }, 81 | { Background = { Color = bg } }, 82 | { Text = "Scroll " }, 83 | }) 84 | end 85 | 86 | return { 87 | get_mode_status_text = get_mode_status_text, 88 | get_hint_status_text = get_hint_status_text, 89 | key_table = { 90 | -- Cancel the mode by pressing escape 91 | { key = "Escape", action = modal.exit_mode("Scroll") }, 92 | { key = "c", mods = "CTRL", action = modal.exit_mode("Scroll") }, 93 | 94 | { key = "UpArrow", action = act.ScrollByLine(-1) }, 95 | { key = "DownArrow", action = act.ScrollByLine(1) }, 96 | { key = "k", action = act.ScrollByLine(-1) }, 97 | { key = "j", action = act.ScrollByLine(1) }, 98 | 99 | { key = "UpArrow", mods = "SHIFT", action = act.ScrollByLine(-5) }, 100 | { key = "DownArrow", mods = "SHIFT", action = act.ScrollByLine(5) }, 101 | { key = "K", mods = "SHIFT", action = act.ScrollByLine(-5) }, 102 | { key = "J", mods = "SHIFT", action = act.ScrollByLine(5) }, 103 | 104 | { key = "u", action = act.ScrollByPage(-0.5) }, 105 | { key = "d", action = act.ScrollByPage(0.5) }, 106 | { key = "U", mods = "SHIFT", action = act.ScrollByPage(-1) }, 107 | { key = "D", mods = "SHIFT", action = act.ScrollByPage(1) }, 108 | 109 | { key = "p", action = act.ScrollToPrompt(-1) }, 110 | { key = "n", action = act.ScrollToPrompt(1) }, 111 | { key = "{", action = act.ScrollToPrompt(-1) }, 112 | { key = "}", action = act.ScrollToPrompt(1) }, 113 | 114 | { key = "g", action = act.ScrollToTop }, 115 | { key = "G", mods = "SHIFT", action = act.ScrollToBottom }, 116 | 117 | { key = "z", action = wezterm.action.TogglePaneZoomState }, 118 | 119 | { key = "v", action = modal.activate_mode("copy_mode") }, 120 | { key = "/", action = modal.activate_mode("search_mode") }, 121 | }, 122 | } 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # modal.wezterm 2 | Vim-like modal keybindings for your terminal! ✌️ 3 | 4 | ## Overview 5 | Add keybindings which operate with vim-like modal bindings to accelerate your workflow. 6 | This plugin adds great opt-in default modes along with optional visual indicators and hints for the keybindings. 7 | 8 | #### Default Copy/Visual/Search mode 9 | Improvements to [Wezterms CopyMode](https://wezfurlong.org/wezterm/copymode.html) with vim keybindings 10 | ![Demo of Copy/Search/Visual mode](https://github.com/MLFlexer/modal.wezterm/assets/75012728/08c2af5c-c75a-4764-bd63-729a91080f79) 11 | 12 | 13 | 14 | #### Default UI mode 15 | 16 | ![Demo of UI mode](https://github.com/MLFlexer/modal.wezterm/assets/75012728/84e5860a-5659-43d9-af51-bb2942b005a6) 17 | 18 | ## Default modes 19 | I have included some default modes which are opt-in as to improve performance for all users. 20 | 21 | ### Copy mode, with Search and Visual submodes 22 | ![Copy mode](https://github.com/MLFlexer/modal.wezterm/assets/75012728/d11d088c-f99a-464f-8de6-2a447da1e57a) 23 | ![Visual mode](https://github.com/MLFlexer/modal.wezterm/assets/75012728/b8c63098-f8e3-481e-8881-b617d6b87a80) 24 | ![Search mode](https://github.com/MLFlexer/modal.wezterm/assets/75012728/ee8a9881-2c7f-499f-ba66-f55f81360869) 25 | 26 | ### UI mode 27 | UI mode has vim-like bindings to navigate and modify panes, tabs and other UI elements. 28 | ![UI mode](https://github.com/MLFlexer/modal.wezterm/assets/75012728/07545d3d-2f94-44df-8f02-cb9ff0ee6d0a) 29 | 30 | ### Scroll mode 31 | In scroll mode you can scroll with familiar vim bindings. 32 | ![Scroll mode](https://github.com/MLFlexer/modal.wezterm/assets/75012728/52e792dd-580d-4bea-a31f-e5f589212217) 33 | 34 | ## Setup 35 | It is recommended to do the setup with some [Customization](#Customization). However if you just want to try it out you can follow the [Preset](#Preset) 36 | ### Preset 37 | Add the following to the bottom of your config: 38 | ```lua 39 | local wezterm = require("wezterm") 40 | local modal = wezterm.plugin.require("https://github.com/MLFlexer/modal.wezterm") 41 | modal.apply_to_config(config) 42 | modal.set_default_keys(config) 43 | ``` 44 | This will add the keybindings to enter and exit modes: 45 | * `ALT-u` to enter UI mode from normal mode 46 | * `ALT-c` to enter Copy mode from normal mode 47 | * `v` to enter visual mode from Copy mode 48 | * `/` to enter search mode from Copy mode 49 | * `ALT-n` to enter Scroll mode from normal mode 50 | * `esc` or `CTRL-c` to leave current non-normal mode 51 | 52 | Checkout the [keybinding descriptions](/defaults/keybinds.md) 53 | 54 | ### Customization 55 | 1. Require the plugin: 56 | ```lua 57 | local wezterm = require("wezterm") 58 | local modal = wezterm.plugin.require("https://github.com/MLFlexer/modal.wezterm") 59 | ``` 60 | 2. Add your own mode 61 | 62 | *There are more examples of key tables and status texts with and without hints in the `/defaults` directory.* 63 | ```lua 64 | -- example key table 65 | local key_table = { 66 | { key = "Escape", action = modal.exit_mode("mode_name") }, 67 | { key = "c", mods = "CTRL", action = modal.exit_mode("mode_name") }, 68 | { key = "z", action = wezterm.action.TogglePaneZoomState }, 69 | } 70 | -- example right status text 71 | local status_text = wezterm.format({ 72 | { Attribute = { Intensity = "Bold" } }, 73 | { Foreground = { Color = "Red" } }, 74 | { Text = wezterm.nerdfonts.ple_left_half_circle_thick }, 75 | { Foreground = { Color = "Black" } }, 76 | { Background = { Color = "Red" } }, 77 | { Text = "MODE NAME " }, 78 | }) 79 | modal.add_mode("mode_name", key_table, status_text) 80 | ``` 81 | 3. Add you keybind to enter the mode 82 | ```lua 83 | config.keys = { 84 | -- ... 85 | -- your other keybindings 86 | { 87 | key = "m", 88 | mods = "ALT", 89 | action = activate_mode("mode_name"), 90 | } 91 | } 92 | ``` 93 | 4. Add the modes to your config 94 | ```lua 95 | config.key_tables = modal.key_tables 96 | ``` 97 | 5. Change right status text when entering/leaving mode 98 | ```lua 99 | wezterm.on("update-right-status", function(window, _) 100 | modal.set_right_status(window) 101 | end) 102 | ``` 103 | 104 | ## Configuration 105 | ### Enabling default modes 106 | If you want to enable a default mode, then you can add the following: 107 | ```lua 108 | modal.enable_defaults("https://github.com/MLFlexer/modal.wezterm") 109 | -- "ui_mode" can be replaced by any filename from the /defaults directory 110 | local key_table = require("ui_mode").key_table 111 | 112 | local icons = { 113 | left_seperator = wezterm.nerdfonts.ple_left_half_circle_thick, 114 | key_hint_seperator = " | ", 115 | mod_seperator = "-", 116 | } 117 | local hint_colors = { 118 | key_hint_seperator = "Yellow", 119 | key = "Green", 120 | hint = "Red", 121 | bg = "Black", 122 | left_bg = "Gray", 123 | } 124 | local mode_colors = { bg = "Red", fg = "Black" } 125 | local status_text = require("ui_mode").get_hint_status_text(icons, hint_colors, mode_colors) 126 | 127 | modal.add_mode("UI", key_table, status_text) 128 | 129 | config.keys = { 130 | -- ... 131 | -- your other keybindings 132 | { 133 | key = "u", 134 | mods = "ALT", 135 | action = activate_mode("UI"), 136 | } 137 | } 138 | config.key_tables = modal.key_tables 139 | ``` 140 | Checkout the specific lua files to see the keybindings and what functionality each mode exports 141 | 142 | ### Adding custom right status text 143 | To add a custom right status you can use the [wezterm.format()](https://wezfurlong.org/wezterm/config/lua/wezterm/format.html) function to create a formatted string. You can then add it as an argument when you add your mode: 144 | ```lua 145 | local custom_status = wezterm.format({ 146 | { Attribute = { Intensity = "Bold" } }, 147 | { Foreground = { Color = bg } }, 148 | { Text = wezterm.nerdfonts.ple_left_half_circle_thick }, 149 | { Foreground = { Color = fg } }, 150 | { Background = { Color = bg } }, 151 | { Text = "Some custom text " }, 152 | }) 153 | modal.add_mode("mode_name", key_table, custom_status) 154 | ``` 155 | You should then add the text to your right status by following the steps in the next paragraph 156 | ### Update right status on mode change 157 | #### *Recommended:* Using enter and exit events 158 | You can use the `modal.enter` and `modal.exit` events to set the right status: 159 | ```lua 160 | wezterm.on("modal.enter", function(name, window, pane) 161 | modal.set_right_status(window, name) 162 | modal.set_window_title(pane, name) 163 | end) 164 | 165 | wezterm.on("modal.exit", function(name, window, pane) 166 | window:set_right_status("NOT IN A MODE") 167 | modal.reset_window_title(pane) 168 | end) 169 | ``` 170 | #### Using the wezterm.on("update-right-status", ...) event 171 | Alternatively you can show some other text in the right status by making a simple if statement in your `wezterm.on` function: 172 | ```lua 173 | wezterm.on("update-right-status", function(window, _) 174 | if modal.get_mode(window) then -- is nil if you are not in a mode 175 | modal.set_right_status(window) 176 | else 177 | -- your other status 178 | end 179 | end) 180 | ``` 181 | 182 | ## Credits 183 | Thanks to [github.com/twilsoft/wezmode](https://github.com/twilsoft/wezmode) for the inspiration to make this plugin. I have created this plugin as a lua alternative to wezmode, as I wanted to extend wezmode, but with lua instead of typescript. 184 | -------------------------------------------------------------------------------- /plugin/init.lua: -------------------------------------------------------------------------------- 1 | local wezterm = require("wezterm") 2 | 3 | -- see https://wezfurlong.org/wezterm/config/lua/keyassignment/index.html 4 | ---@alias KeyAssignment any 5 | 6 | ---@alias key_bind {key: string, mods: string|nil, action: any} 7 | ---@alias key_table key_bind[] 8 | ---@alias mode { name: string, key_table_name: string, status_text: string | nil} 9 | 10 | -- map from key_table_name to mode 11 | ---@type table 12 | local modes = { 13 | copy_mode = { name = "copy_mode", key_table_name = "copy_mode", status_text = "Copy" }, 14 | search_mode = { name = "search_mode", key_table_name = "search_mode", status_text = "Search" }, 15 | } 16 | 17 | -- map from key_table_name to key_table 18 | ---@type table 19 | local key_tables = {} 20 | 21 | ---@param window any 22 | ---@return mode | nil 23 | local function get_mode(window) 24 | local active_key_table = window:active_key_table() 25 | 26 | if active_key_table then 27 | return modes[active_key_table] 28 | end 29 | end 30 | 31 | ---@param name string 32 | ---@param key_table {key: string, mods: string|nil, action: any}[] 33 | ---@param key_table_name? string 34 | ---@param status_text? string 35 | local function add_mode(name, key_table, status_text, key_table_name) 36 | if key_table_name then 37 | modes[key_table_name] = { name = name, key_table_name = key_table_name, status_text = status_text } 38 | key_tables[key_table_name] = key_table 39 | else 40 | modes[name] = { name = name, key_table_name = name, status_text = status_text } 41 | key_tables[name] = key_table 42 | end 43 | end 44 | 45 | ---Wrapper for creating a simple status text 46 | ---@param left_seperator {text: string, bg: string, fg: string} 47 | ---@param key_hints {text: string, bg: string, fg: string} 48 | ---@param mode {text: string, bg: string, fg: string} 49 | ---@return string 50 | local function create_status_text(left_seperator, key_hints, mode) 51 | return wezterm.format({ 52 | { Foreground = { Color = left_seperator.fg } }, 53 | { Background = { Color = left_seperator.bg } }, 54 | { Text = left_seperator.text }, 55 | { Foreground = { Color = key_hints.fg } }, 56 | { Background = { Color = key_hints.bg } }, 57 | { Text = key_hints.text }, 58 | { Foreground = { Color = mode.bg } }, 59 | { Background = { Color = key_hints.bg } }, 60 | { Text = left_seperator.text }, 61 | { Foreground = { Color = mode.fg } }, 62 | { Background = { Color = mode.bg } }, 63 | { Text = mode.text }, 64 | }) 65 | end 66 | 67 | local function enable_defaults(url) 68 | local plugin 69 | for _, p in ipairs(wezterm.plugin.list()) do 70 | if p.url == url then 71 | plugin = p 72 | break 73 | end 74 | end 75 | package.path = package.path .. ";" .. plugin.plugin_dir .. "/defaults/?.lua" 76 | end 77 | 78 | ---sets the current modal status to the right status 79 | ---@param window any 80 | ---@param name? string 81 | local function set_right_status(window, name) 82 | if name then 83 | local mode = modes[name] 84 | window:set_right_status(mode.status_text) 85 | else 86 | local mode = get_mode(window) 87 | if mode then 88 | window:set_right_status(mode.status_text) 89 | end 90 | end 91 | end 92 | 93 | ---sets the window title by emitting a OSC 2 escape sequence 94 | ---@param pane any 95 | ---@param name string 96 | local function set_window_title(pane, name) 97 | if name then 98 | pane:inject_output("\x1b]2;" .. name .. "\x1b\\") 99 | end 100 | end 101 | 102 | ---resets the window title to the foreground process by emitting a OSC 2 escape sequence 103 | ---@param pane any 104 | local function reset_window_title(pane) 105 | pane:inject_output("\x1b]2;" .. string.gsub(pane:get_foreground_process_name(), "(.*[/\\])(.*)", "%2") .. "\x1b\\") 106 | end 107 | 108 | ---Activates mode 109 | ---@param name string 110 | ---@param activate_key_table_params? table -- parameters as defined here: https://wezfurlong.org/wezterm/config/lua/keyassignment/ActivateKeyTable.html 111 | ---@return KeyAssignment 112 | local function activate_mode(name, activate_key_table_params) 113 | if name == "copy_mode" then 114 | return wezterm.action.Multiple({ 115 | wezterm.action.ActivateCopyMode, 116 | wezterm.action_callback(function(window, pane) 117 | wezterm.emit("modal.enter", name, window, pane) 118 | end), 119 | }) 120 | elseif name == "search_mode" then 121 | return wezterm.action.Multiple({ 122 | wezterm.action.Search({ CaseSensitiveString = "" }), 123 | wezterm.action_callback(function(window, pane) 124 | wezterm.emit("modal.enter", name, window, pane) 125 | end), 126 | }) 127 | else 128 | local parameters = activate_key_table_params or { one_shot = false } 129 | parameters.name = name 130 | 131 | return wezterm.action.Multiple({ 132 | wezterm.action.ActivateKeyTable(parameters), 133 | wezterm.action_callback(function(window, pane) 134 | wezterm.emit("modal.enter", name, window, pane) 135 | end), 136 | }) 137 | end 138 | end 139 | 140 | ---Exits active mode 141 | ---@param name string 142 | ---@return KeyAssignment 143 | local function exit_mode(name) 144 | if name == "copy_mode" then 145 | return wezterm.action.Multiple({ 146 | wezterm.action.CopyMode("Close"), 147 | wezterm.action_callback(function(window, pane) 148 | wezterm.emit("modal.exit", name, window, pane) 149 | end), 150 | }) 151 | elseif name == "search_mode" then 152 | return wezterm.action.Multiple({ 153 | wezterm.action.CopyMode("ClearPattern"), 154 | wezterm.action.CopyMode("Close"), 155 | wezterm.action_callback(function(window, pane) 156 | wezterm.emit("modal.exit", name, window, pane) 157 | end), 158 | }) 159 | else 160 | return wezterm.action.Multiple({ 161 | "PopKeyTable", 162 | wezterm.action_callback(function(window, pane) 163 | wezterm.emit("modal.exit", name, window, pane) 164 | end), 165 | }) 166 | end 167 | end 168 | 169 | ---Exits all active modes 170 | ---@param name string 171 | ---@return KeyAssignment 172 | local function exit_all_modes(name) 173 | return wezterm.action.Multiple({ 174 | wezterm.action.ClearKeyTableStack(), 175 | wezterm.action_callback(function(window, pane) 176 | wezterm.emit("modal.exit_all", name, window, pane) 177 | end), 178 | }) 179 | end 180 | 181 | local function apply_to_config(config) 182 | enable_defaults("https://github.com/MLFlexer/modal.wezterm") 183 | local icons = { 184 | left_seperator = wezterm.nerdfonts.ple_left_half_circle_thick, 185 | key_hint_seperator = "  ", 186 | mod_seperator = "-", 187 | } 188 | 189 | if not config.colors then 190 | if config.color_scheme then 191 | config.colors = wezterm.color.get_builtin_schemes()[config.color_scheme] 192 | else 193 | config.colors = wezterm.color.get_default_colors() 194 | end 195 | end 196 | 197 | local colors = { 198 | key_hint_seperator = config.colors.foreground, 199 | key = config.colors.foreground, 200 | hint = config.colors.foreground, 201 | bg = config.colors.background, 202 | left_bg = config.colors.background, 203 | } 204 | 205 | local fg_status_color = config.colors.background 206 | local status_text = 207 | require("ui_mode").get_hint_status_text(icons, colors, { bg = config.colors.ansi[2], fg = fg_status_color }) 208 | 209 | add_mode("UI", require("ui_mode").key_table, status_text) 210 | 211 | status_text = 212 | require("scroll_mode").get_hint_status_text(icons, colors, { bg = config.colors.ansi[7], fg = fg_status_color }) 213 | add_mode("Scroll", require("scroll_mode").key_table, status_text) 214 | 215 | status_text = 216 | require("copy_mode").get_hint_status_text(icons, colors, { bg = config.colors.ansi[4], fg = fg_status_color }) 217 | add_mode("copy_mode", require("copy_mode").key_table, status_text) 218 | 219 | status_text = 220 | require("search_mode").get_hint_status_text(icons, colors, { bg = config.colors.ansi[6], fg = fg_status_color }) 221 | add_mode("search_mode", require("search_mode").key_table, status_text) 222 | 223 | status_text = 224 | require("visual_mode").get_hint_status_text(icons, colors, { bg = config.colors.ansi[3], fg = fg_status_color }) 225 | add_mode("Visual", {}, status_text) 226 | 227 | config.key_tables = key_tables 228 | end 229 | 230 | local function set_default_keys(config) 231 | if config.keys == nil then 232 | config.keys = {} 233 | end 234 | table.insert(config.keys, { 235 | key = "n", 236 | mods = "ALT", 237 | action = activate_mode("Scroll"), 238 | }) 239 | table.insert(config.keys, { 240 | key = "u", 241 | mods = "ALT", 242 | action = activate_mode("UI"), 243 | }) 244 | table.insert(config.keys, { 245 | key = "c", 246 | mods = "ALT", 247 | action = activate_mode("copy_mode"), 248 | }) 249 | end 250 | 251 | return { 252 | set_right_status = set_right_status, 253 | set_window_title = set_window_title, 254 | reset_window_title = reset_window_title, 255 | add_mode = add_mode, 256 | get_mode = get_mode, 257 | create_status_text = create_status_text, 258 | modes = modes, 259 | key_tables = key_tables, 260 | enable_defaults = enable_defaults, 261 | apply_to_config = apply_to_config, 262 | set_default_keys = set_default_keys, 263 | activate_mode = activate_mode, 264 | exit_mode = exit_mode, 265 | exit_all_modes = exit_all_modes, 266 | } 267 | -------------------------------------------------------------------------------- /defaults/ui_mode.lua: -------------------------------------------------------------------------------- 1 | local wezterm = require("wezterm") 2 | local act = wezterm.action 3 | local modal = wezterm.plugin.require("https://github.com/MLFlexer/modal.wezterm") 4 | 5 | -- From smartsplits.nvim 6 | local function is_vim(pane) 7 | -- this is set by the smart-splits.nvim plugin, and unset on ExitPre in Neovim 8 | return pane:get_user_vars().IS_NVIM == "true" 9 | end 10 | 11 | local function activate_pane_direction(key, direction, mods, vim_mods) 12 | return { 13 | key = key, 14 | mods = mods, 15 | action = wezterm.action_callback(function(win, pane) 16 | if is_vim(pane) then 17 | -- pass the keys through to vim/nvim 18 | win:perform_action({ 19 | SendKey = { key = key, mods = vim_mods }, 20 | }, pane) 21 | else 22 | win:perform_action({ ActivatePaneDirection = direction }, pane) 23 | end 24 | end), 25 | } 26 | end 27 | 28 | local function adjust_pane_size(key, direction, mods, vim_key, vim_mods, cells) 29 | return { 30 | key = key, 31 | mods = mods, 32 | action = wezterm.action_callback(function(win, pane) 33 | if is_vim(pane) then 34 | -- pass the keys through to vim/nvim 35 | win:perform_action({ 36 | SendKey = { key = vim_key, mods = vim_mods }, 37 | }, pane) 38 | else 39 | win:perform_action({ AdjustPaneSize = { direction, cells } }, pane) 40 | end 41 | end), 42 | } 43 | end 44 | 45 | ---Create status text with hints 46 | ---@param hint_icons {left_seperator: string, key_hint_seperator: string, mod_seperator: string} 47 | ---@param hint_colors {key_hint_seperator: string, key: string, hint: string, bg: string, left_bg: string} 48 | ---@param mode_colors {bg: string, fg: string} 49 | ---@return string 50 | local function get_hint_status_text(hint_icons, hint_colors, mode_colors) 51 | return wezterm.format({ 52 | { Foreground = { Color = hint_colors.bg } }, 53 | { Background = { Color = hint_colors.left_bg } }, 54 | { Text = hint_icons.left_seperator }, 55 | { Background = { Color = hint_colors.bg } }, 56 | -- ... 57 | { Foreground = { Color = hint_colors.key } }, 58 | { Text = "C?" }, 59 | { Text = hint_icons.mod_seperator }, 60 | { Text = "hjkl: " }, 61 | { Foreground = { Color = hint_colors.hint } }, 62 | { Text = "Go/resize" }, 63 | { Foreground = { Color = hint_colors.key_hint_seperator } }, 64 | { Text = hint_icons.key_hint_seperator }, 65 | -- ... 66 | { Foreground = { Color = hint_colors.key } }, 67 | { Text = "-: " }, 68 | { Foreground = { Color = hint_colors.hint } }, 69 | { Text = "Split" }, 70 | { Foreground = { Color = hint_colors.key_hint_seperator } }, 71 | { Text = hint_icons.key_hint_seperator }, 72 | -- ... 73 | { Foreground = { Color = hint_colors.key } }, 74 | { Text = "z: " }, 75 | { Foreground = { Color = hint_colors.hint } }, 76 | { Text = "Zoom" }, 77 | { Foreground = { Color = hint_colors.key_hint_seperator } }, 78 | { Text = hint_icons.key_hint_seperator }, 79 | -- ... 80 | { Foreground = { Color = hint_colors.key } }, 81 | { Text = "r: " }, 82 | { Foreground = { Color = hint_colors.hint } }, 83 | { Text = "Rotate" }, 84 | { Foreground = { Color = hint_colors.key_hint_seperator } }, 85 | { Text = hint_icons.key_hint_seperator }, 86 | -- ... 87 | { Foreground = { Color = hint_colors.key } }, 88 | { Text = "q: " }, 89 | { Foreground = { Color = hint_colors.hint } }, 90 | { Text = "Close pane" }, 91 | { Foreground = { Color = hint_colors.key_hint_seperator } }, 92 | { Text = hint_icons.key_hint_seperator }, 93 | -- ... 94 | { Foreground = { Color = hint_colors.key } }, 95 | { Text = "t/T: " }, 96 | { Foreground = { Color = hint_colors.hint } }, 97 | { Text = "Open/Close tab" }, 98 | { Foreground = { Color = hint_colors.key_hint_seperator } }, 99 | { Text = hint_icons.key_hint_seperator }, 100 | -- ... 101 | { Foreground = { Color = hint_colors.key } }, 102 | { Text = "H/Tab, L/S-Tab: " }, 103 | { Foreground = { Color = hint_colors.hint } }, 104 | { Text = "Next/Prev tab" }, 105 | { Foreground = { Color = hint_colors.key_hint_seperator } }, 106 | { Text = hint_icons.key_hint_seperator }, 107 | -- ... 108 | { Foreground = { Color = hint_colors.key } }, 109 | { Text = "JK: " }, 110 | { Foreground = { Color = hint_colors.hint } }, 111 | { Text = "Move tab" }, 112 | { Foreground = { Color = hint_colors.key_hint_seperator } }, 113 | { Text = hint_icons.key_hint_seperator }, 114 | -- ... 115 | { Foreground = { Color = hint_colors.key } }, 116 | { Text = "n: " }, 117 | { Foreground = { Color = hint_colors.hint } }, 118 | { Text = "Rename tab" }, 119 | { Foreground = { Color = hint_colors.key_hint_seperator } }, 120 | { Text = hint_icons.key_hint_seperator }, 121 | -- ... 122 | { Foreground = { Color = hint_colors.key } }, 123 | { Text = "ALT" }, 124 | { Text = hint_icons.mod_seperator }, 125 | { Text = "t: " }, 126 | { Foreground = { Color = hint_colors.hint } }, 127 | { Text = "Tab nav" }, 128 | { Foreground = { Color = hint_colors.key_hint_seperator } }, 129 | { Text = hint_icons.key_hint_seperator }, 130 | -- ... 131 | { Foreground = { Color = hint_colors.key } }, 132 | { Text = "w/W: " }, 133 | { Foreground = { Color = hint_colors.hint } }, 134 | { Text = "Next/Prev Workspace " }, 135 | -- ... 136 | { Attribute = { Intensity = "Bold" } }, 137 | { Foreground = { Color = mode_colors.bg } }, 138 | { Text = hint_icons.left_seperator }, 139 | { Foreground = { Color = mode_colors.fg } }, 140 | { Background = { Color = mode_colors.bg } }, 141 | { Text = "UI " }, 142 | }) 143 | end 144 | 145 | ---Create mode status text 146 | ---@param bg string 147 | ---@param fg string 148 | ---@param left_seperator string 149 | ---@return string 150 | local function get_mode_status_text(left_seperator, bg, fg) 151 | return wezterm.format({ 152 | { Attribute = { Intensity = "Bold" } }, 153 | { Foreground = { Color = bg } }, 154 | { Text = left_seperator }, 155 | { Foreground = { Color = fg } }, 156 | { Background = { Color = bg } }, 157 | { Text = "UI " }, 158 | }) 159 | end 160 | 161 | return { 162 | get_mode_status_text = get_mode_status_text, 163 | get_hint_status_text = get_hint_status_text, 164 | key_table = { 165 | -- Cancel the mode by pressing escape 166 | { 167 | key = "Escape", 168 | action = modal.exit_mode("UI"), 169 | }, 170 | { key = "c", mods = "CTRL", action = modal.exit_mode("UI") }, 171 | 172 | -- Activate panes 173 | activate_pane_direction("h", "Left", "", "CTRL"), 174 | activate_pane_direction("j", "Down", "", "CTRL"), 175 | activate_pane_direction("k", "Up", "", "CTRL"), 176 | activate_pane_direction("l", "Right", "", "CTRL"), 177 | 178 | -- Resize 179 | adjust_pane_size("h", "Left", "CTRL", "h", "CTRL|ALT", 1), 180 | adjust_pane_size("l", "Right", "CTRL", "l", "CTRL|ALT", 1), 181 | adjust_pane_size("k", "Up", "CTRL", "k", "CTRL|ALT", 1), 182 | adjust_pane_size("j", "Down", "CTRL", "j", "CTRL|ALT", 1), 183 | adjust_pane_size("LeftArrow", "Left", "", "h", "CTRL|ALT", 1), 184 | adjust_pane_size("RightArrow", "Right", "", "l", "CTRL|ALT", 1), 185 | adjust_pane_size("UpArrow", "Up", "", "k", "CTRL|ALT", 1), 186 | adjust_pane_size("DownArrow", "Down", "", "j", "CTRL|ALT", 1), 187 | 188 | -- Zoom toggle 189 | { key = "z", action = wezterm.action.TogglePaneZoomState }, 190 | 191 | -- Split pane 192 | { key = "-", action = wezterm.action.SplitVertical }, 193 | { key = "_", mods = "SHIFT", action = wezterm.action.SplitHorizontal }, 194 | 195 | -- Close pane 196 | { key = "q", action = wezterm.action.CloseCurrentPane({ confirm = true }) }, 197 | 198 | -- Rotate panes 199 | { key = "r", action = act.RotatePanes("Clockwise") }, 200 | { key = "R", action = act.RotatePanes("CounterClockwise") }, 201 | 202 | -- Selecting 203 | { key = "S", mods = "SHIFT", action = act.PaneSelect({}) }, 204 | -- Swap 205 | { key = "s", action = act.PaneSelect({ mode = "SwapWithActive" }) }, 206 | 207 | -- Tabs 208 | { key = "t", action = act.SpawnTab("CurrentPaneDomain") }, 209 | { key = "T", mods = "SHIFT", action = wezterm.action.CloseCurrentTab({ confirm = true }) }, 210 | 211 | { key = "H", mods = "SHIFT", action = act.ActivateTabRelative(-1) }, 212 | { key = "L", mods = "SHIFT", action = act.ActivateTabRelative(1) }, 213 | { key = "Tab", mods = "", action = act.ActivateTabRelative(1) }, 214 | { key = "Tab", mods = "SHIFT", action = act.ActivateTabRelative(-1) }, 215 | 216 | { key = "J", mods = "SHIFT", action = act.MoveTabRelative(-1) }, 217 | { key = "K", mods = "SHIFT", action = act.MoveTabRelative(1) }, 218 | 219 | { key = "t", mods = "ALT", action = wezterm.action.ShowTabNavigator }, 220 | 221 | { 222 | key = "n", 223 | action = act.Multiple({ 224 | act.PopKeyTable, 225 | act.PromptInputLine({ 226 | description = "Enter new name for tab", 227 | action = wezterm.action_callback(function(window, pane, name) 228 | if name then 229 | window:active_tab():set_title(name) 230 | end 231 | window:perform_action( 232 | act.ActivateKeyTable({ 233 | name = "UI", 234 | one_shot = false, 235 | }), 236 | pane 237 | ) 238 | end), 239 | }), 240 | }), 241 | }, 242 | 243 | -- Workspace 244 | { key = "w", action = act.SwitchWorkspaceRelative(1) }, 245 | { key = "W", mods = "SHIFT", action = act.SwitchWorkspaceRelative(-1) }, 246 | 247 | -- New window 248 | { key = "N", mods = "SHIFT", action = wezterm.action.SpawnWindow }, 249 | 250 | -- Move pane to new window 251 | { 252 | key = "P", 253 | mods = "SHIFT", 254 | action = wezterm.action.Multiple({ 255 | wezterm.action_callback(function(_, pane) 256 | pane:move_to_new_window() 257 | end), 258 | modal.exit_mode("UI"), 259 | }), 260 | }, 261 | { 262 | key = "p", 263 | action = wezterm.action.Multiple({ 264 | wezterm.action_callback(function(_, pane) 265 | pane:move_to_new_tab() 266 | end), 267 | modal.exit_mode("UI"), 268 | }), 269 | }, 270 | -- font size 271 | { key = "+", mods = "CTRL", action = wezterm.action.IncreaseFontSize }, 272 | { key = "-", mods = "CTRL", action = wezterm.action.DecreaseFontSize }, 273 | 274 | -- toggle fullscreen 275 | { 276 | key = "f", 277 | action = wezterm.action.ToggleFullScreen, 278 | }, 279 | }, 280 | } 281 | -------------------------------------------------------------------------------- /defaults/keybinds.md: -------------------------------------------------------------------------------- 1 | # Keybindings 2 | 3 | ## Copy mode 4 | 5 | | Modifiers | Key | Description | 6 | |-----------|----------|---------------------------------------| 7 | | | Escape | Cancel the mode | 8 | | CTRL | c | Cancel the mode | 9 | | | Enter | Move to start of next line | 10 | | | $ | Move to end of line content | 11 | | | 0 | Move to start of line | 12 | | | ^ | Move to start of line content | 13 | | | End | Move to end of line content | 14 | | | Home | Move to start of line | 15 | | SHIFT | g | Move to scrollback top | 16 | | SHIFT | G | Move to scrollback bottom | 17 | | SHIFT | H | Move to viewport top | 18 | | SHIFT | L | Move to viewport bottom | 19 | | SHIFT | M | Move to viewport middle | 20 | | | o | Move to other end of visual selection | 21 | | SHIFT | O | Move to other end of visual selection horizontally | 22 | | | v | Visual line mode | 23 | | SHIFT | V | Visual line mode | 24 | | CTRL | v | Visual block mode | 25 | | ALT | v | Visual semantic zone mode | 26 | | | s | Move backward semantic zone | 27 | | SHIFT | S | Move forward semantic zone | 28 | | | h | Move left | 29 | | | j | Move down | 30 | | | k | Move up | 31 | | | l | Move right | 32 | | | LeftArrow| Move left | 33 | | | RightArrow| Move right | 34 | | | UpArrow | Move up | 35 | | | DownArrow| Move down | 36 | | | w | Move forward word | 37 | | | e | Move to end of forward word | 38 | | | b | Move backward word | 39 | | CTRL | LeftArrow| Move backward word | 40 | | CTRL | RightArrow| Move forward word | 41 | | CTRL | d | Move down by page (half) | 42 | | CTRL | u | Move up by page (half) | 43 | | | PageUp | Move up by page | 44 | | | PageDown | Move down by page | 45 | | CTRL | b | Move up by page | 46 | | CTRL | f | Move down by page | 47 | | | y | Copy to clipboard and exit mode | 48 | | SHIFT | / | Enter search mode and edit pattern | 49 | | | / | Enter search mode and edit pattern | 50 | | | n | Next match | 51 | | SHIFT | P | Next match | 52 | | | p | Previous match | 53 | | SHIFT | N | Previous match | 54 | | | , | Jump reverse | 55 | | | ; | Jump again | 56 | | | f | Jump forward | 57 | | SHIFT | F | Jump backward | 58 | | | t | Jump backward (prev char) | 59 | | SHIFT | T | Jump backward (prev char) | 60 | 61 | ### Visual mode 62 | 63 | | Modifiers | Key | Description | 64 | |-----------------------|------|---------------------------------| 65 | | Shift, CTRL, ALT | v | Change visual mode | 66 | | | o | Move to the other end of selection | 67 | | SHIFT | O | Move to the other end of selection | 68 | | | s | Semantic jump | 69 | | SHIFT | S | Semantic jump | 70 | | | y | Copy and exit | 71 | | | h | Move left | 72 | | | j | Move down | 73 | | | k | Move up | 74 | | | l | Move right | 75 | 76 | ### Search mode 77 | | Modifiers | Key | Action | Description | 78 | |-----------------|------------|--------------------------------|----------------------------------------| 79 | | | Enter | Accept Pattern | Enter copy mode with accepted pattern | 80 | | | Escape | Exit Search Mode | Exit search mode and enter copy mode | 81 | | CTRL | c | Exit Search Mode | Exit search mode and enter copy mode | 82 | | CTRL | n | Next Match | Go to the next search match | 83 | | CTRL | p | Previous Match | Go to the previous search match | 84 | | CTRL | r | Cycle Match Type | Cycle through match types | 85 | | CTRL | u | Clear Pattern | Clear the current search pattern | 86 | | | PageUp | Prior Match Page | Go to the previous match on the page | 87 | | | PageDown | Next Match Page | Go to the next match on the page | 88 | | | UpArrow | Prior Match | Go to the previous match | 89 | | | DownArrow | Next Match | Go to the next match | 90 | 91 | ## UI mode 92 | 93 | | Modifiers | Key | Action | Description | 94 | |-----------------|------------|---------------------------------|-----------------------------------------| 95 | | | Escape | Exit Mode | Exit UI mode | 96 | | CTRL | c | Exit Mode | Exit UI mode | 97 | | | h | Activate Pane Left | Move to left pane | 98 | | | j | Activate Pane Down | Move to down pane | 99 | | | k | Activate Pane Up | Move to up pane | 100 | | | l | Activate Pane Right | Move to right pane | 101 | | CTRL | h | Adjust Pane Size Left | Resize pane left | 102 | | CTRL | j | Adjust Pane Size Down | Resize pane down | 103 | | CTRL | k | Adjust Pane Size Up | Resize pane up | 104 | | CTRL | l | Adjust Pane Size Right | Resize pane right | 105 | | | LeftArrow | Adjust Pane Size Left | Resize pane left | 106 | | | RightArrow | Adjust Pane Size Right | Resize pane right | 107 | | | UpArrow | Adjust Pane Size Up | Resize pane up | 108 | | | DownArrow | Adjust Pane Size Down | Resize pane down | 109 | | | z | Toggle Zoom | Zoom in/out | 110 | | | - | Split Pane Vertical | Split pane vertically | 111 | | SHIFT | _ | Split Pane Horizontal | Split pane horizontally | 112 | | | q | Close Pane | Close current pane | 113 | | | r | Rotate Panes Clockwise | Rotate panes clockwise | 114 | | SHIFT | R | Rotate Panes CounterClockwise | Rotate panes counterclockwise | 115 | | SHIFT | S | Pane Select | Select pane | 116 | | | s | Swap Panes | Swap active pane | 117 | | | t | Spawn New Tab | Open new tab | 118 | | SHIFT | T | Close Tab | Close current tab | 119 | | SHIFT | H | Activate Previous Tab | Switch to previous tab | 120 | | SHIFT | L | Activate Next Tab | Switch to next tab | 121 | | | Tab | Activate Next Tab | Switch to next tab | 122 | | SHIFT | Tab | Activate Previous Tab | Switch to previous tab | 123 | | SHIFT | J | Move Tab Left | Move tab left | 124 | | SHIFT | K | Move Tab Right | Move tab right | 125 | | ALT | t | Show Tab Navigator | Show tab navigator | 126 | | | n | Rename Tab | Rename current tab | 127 | | | w | Switch to Next Workspace | Switch to next workspace | 128 | | SHIFT | W | Switch to Previous Workspace | Switch to previous workspace | 129 | | SHIFT | N | Spawn New Window | Open new window | 130 | | SHIFT | P | Move Pane to New Window | Move current pane to a new window | 131 | | | p | Move Pane to New Tab | Move current pane to a new tab | 132 | | CTRL | + | Increase Font Size | Increase font size | 133 | | CTRL | - | Decrease Font Size | Decrease font size | 134 | | | f | Toggle Fullscreen | Toggle fullscreen mode | 135 | 136 | ## Scroll mode 137 | 138 | | Modifiers | Key | Description | 139 | |-----------|----------|-----------------------------------| 140 | | | Escape | Cancel the "Scroll" mode | 141 | | CTRL | c | Cancel the "Scroll" mode | 142 | | | UpArrow | Scroll up by 1 line | 143 | | | DownArrow| Scroll down by 1 line | 144 | | | k | Scroll up by 1 line | 145 | | | j | Scroll down by 1 line | 146 | | SHIFT | UpArrow | Scroll up by 5 lines | 147 | | SHIFT | DownArrow| Scroll down by 5 lines | 148 | | SHIFT | K | Scroll up by 5 lines | 149 | | SHIFT | J | Scroll down by 5 lines | 150 | | | u | Scroll up by half a page | 151 | | | d | Scroll down by half a page | 152 | | SHIFT | U | Scroll up by a page | 153 | | SHIFT | D | Scroll down by a page | 154 | | | p | Scroll to previous prompt | 155 | | | n | Scroll to next prompt | 156 | | | { | Scroll to previous prompt | 157 | | | } | Scroll to next prompt | 158 | | | g | Scroll to top | 159 | | SHIFT | G | Scroll to bottom | 160 | | | z | Toggle pane zoom state | 161 | | | v | Activate "copy_mode" | 162 | | | / | Activate "search_mode" | 163 | 164 | -------------------------------------------------------------------------------- /defaults/copy_mode.lua: -------------------------------------------------------------------------------- 1 | local wezterm = require("wezterm") 2 | local modal = wezterm.plugin.require("https://github.com/MLFlexer/modal.wezterm") 3 | 4 | ---Create status text with hints 5 | ---@param hint_icons {left_seperator: string, key_hint_seperator: string, mod_seperator: string} 6 | ---@param hint_colors {key_hint_seperator: string, key: string, hint: string, bg: string, left_bg: string} 7 | ---@param mode_colors {bg: string, fg: string} 8 | ---@return string 9 | local function get_hint_status_text(hint_icons, hint_colors, mode_colors) 10 | return wezterm.format({ 11 | { Foreground = { Color = hint_colors.bg } }, 12 | { Background = { Color = hint_colors.left_bg } }, 13 | { Text = hint_icons.left_seperator }, 14 | { Background = { Color = hint_colors.bg } }, 15 | -- ... 16 | { Foreground = { Color = hint_colors.key } }, 17 | { Text = "Shift,CTRL,ALT?" }, 18 | { Text = hint_icons.mod_seperator }, 19 | { Text = "v: " }, 20 | { Foreground = { Color = hint_colors.hint } }, 21 | { Text = "Visual mode" }, 22 | { Foreground = { Color = hint_colors.key_hint_seperator } }, 23 | { Text = hint_icons.key_hint_seperator }, 24 | -- ... 25 | { Foreground = { Color = hint_colors.key } }, 26 | { Text = "/: " }, 27 | { Foreground = { Color = hint_colors.hint } }, 28 | { Text = "Search mode" }, 29 | { Foreground = { Color = hint_colors.key_hint_seperator } }, 30 | { Text = hint_icons.key_hint_seperator }, 31 | -- ... 32 | { Foreground = { Color = hint_colors.key } }, 33 | { Text = "p/n: " }, 34 | { Foreground = { Color = hint_colors.hint } }, 35 | { Text = "Prev/Next result" }, 36 | { Foreground = { Color = hint_colors.key_hint_seperator } }, 37 | { Text = hint_icons.key_hint_seperator }, 38 | -- ... 39 | { Foreground = { Color = hint_colors.key } }, 40 | { Text = "s/S: " }, 41 | { Foreground = { Color = hint_colors.hint } }, 42 | { Text = "Semantic jump" }, 43 | { Foreground = { Color = hint_colors.key_hint_seperator } }, 44 | { Text = hint_icons.key_hint_seperator }, 45 | -- ... 46 | { Foreground = { Color = hint_colors.key } }, 47 | { Text = "y: " }, 48 | { Foreground = { Color = hint_colors.hint } }, 49 | { Text = "Copy and exit" }, 50 | { Foreground = { Color = hint_colors.key_hint_seperator } }, 51 | { Text = hint_icons.key_hint_seperator }, 52 | -- ... 53 | { Foreground = { Color = hint_colors.key } }, 54 | { Text = "hjkl: " }, 55 | { Foreground = { Color = hint_colors.hint } }, 56 | { Text = "Move " }, 57 | -- ... 58 | { Attribute = { Intensity = "Bold" } }, 59 | { Foreground = { Color = mode_colors.bg } }, 60 | { Text = hint_icons.left_seperator }, 61 | { Foreground = { Color = mode_colors.fg } }, 62 | { Background = { Color = mode_colors.bg } }, 63 | { Text = "Copy " }, 64 | }) 65 | end 66 | 67 | ---Create mode status text 68 | ---@param bg string 69 | ---@param fg string 70 | ---@param left_seperator string 71 | ---@return string 72 | local function get_mode_status_text(left_seperator, bg, fg) 73 | return wezterm.format({ 74 | { Attribute = { Intensity = "Bold" } }, 75 | { Foreground = { Color = bg } }, 76 | { Text = left_seperator }, 77 | { Foreground = { Color = fg } }, 78 | { Background = { Color = bg } }, 79 | { Text = "Copy " }, 80 | }) 81 | end 82 | 83 | return { 84 | get_mode_status_text = get_mode_status_text, 85 | get_hint_status_text = get_hint_status_text, 86 | key_table = { 87 | -- Cancel the mode by pressing escape 88 | { 89 | key = "Escape", 90 | action = wezterm.action_callback(function(window, pane) 91 | if wezterm.GLOBAL.visual_mode then 92 | wezterm.emit("modal.enter", "copy_mode", window, pane) 93 | window:perform_action( 94 | wezterm.action.CopyMode({ SetSelectionMode = wezterm.GLOBAL.visual_mode }), 95 | pane 96 | ) 97 | wezterm.GLOBAL.visual_mode = nil 98 | elseif wezterm.GLOBAL.search_mode then 99 | wezterm.GLOBAL.search_mode = false 100 | window:perform_action(modal.exit_mode("search_mode"), pane) 101 | window:perform_action(modal.activate_mode("copy_mode"), pane) 102 | else 103 | wezterm.GLOBAL.search_mode = false 104 | window:perform_action(modal.exit_mode("copy_mode"), pane) 105 | end 106 | end), 107 | }, 108 | { 109 | key = "c", 110 | mods = "CTRL", 111 | action = wezterm.action_callback(function(window, pane) 112 | if wezterm.GLOBAL.visual_mode then 113 | wezterm.emit("modal.enter", "copy_mode", window, pane) 114 | window:perform_action( 115 | wezterm.action.CopyMode({ SetSelectionMode = wezterm.GLOBAL.visual_mode }), 116 | pane 117 | ) 118 | wezterm.GLOBAL.visual_mode = nil 119 | elseif wezterm.GLOBAL.search_mode then 120 | wezterm.GLOBAL.search_mode = false 121 | window:perform_action(modal.exit_mode("search_mode"), pane) 122 | window:perform_action(modal.activate_mode("copy_mode"), pane) 123 | else 124 | wezterm.GLOBAL.search_mode = false 125 | window:perform_action(modal.exit_mode("copy_mode"), pane) 126 | end 127 | end), 128 | }, 129 | 130 | -- Move to start and end of line 131 | { key = "Enter", action = wezterm.action.CopyMode("MoveToStartOfNextLine") }, 132 | { key = "$", action = wezterm.action.CopyMode("MoveToEndOfLineContent") }, 133 | { key = "0", action = wezterm.action.CopyMode("MoveToStartOfLine") }, 134 | { key = "^", action = wezterm.action.CopyMode("MoveToStartOfLineContent") }, 135 | 136 | { key = "End", action = wezterm.action.CopyMode("MoveToEndOfLineContent") }, 137 | { key = "Home", action = wezterm.action.CopyMode("MoveToStartOfLine") }, 138 | 139 | -- Move to 140 | { key = "g", action = wezterm.action.CopyMode("MoveToScrollbackTop") }, 141 | { key = "G", mods = "SHIFT", action = wezterm.action.CopyMode("MoveToScrollbackBottom") }, 142 | { key = "H", mods = "SHIFT", action = wezterm.action.CopyMode("MoveToViewportTop") }, 143 | { key = "L", mods = "SHIFT", action = wezterm.action.CopyMode("MoveToViewportBottom") }, 144 | { key = "M", mods = "SHIFT", action = wezterm.action.CopyMode("MoveToViewportMiddle") }, 145 | 146 | -- Jump to other end of visual selection 147 | { key = "o", action = wezterm.action.CopyMode("MoveToSelectionOtherEnd") }, 148 | { key = "O", mods = "SHIFT", action = wezterm.action.CopyMode("MoveToSelectionOtherEndHoriz") }, 149 | 150 | -- Visual line mode 151 | { 152 | key = "v", 153 | action = wezterm.action_callback(function(window, pane) 154 | if wezterm.GLOBAL.visual_mode == "Cell" then 155 | wezterm.emit("modal.enter", "copy_mode", window, pane) 156 | window:perform_action( 157 | wezterm.action.CopyMode({ SetSelectionMode = wezterm.GLOBAL.visual_mode }), 158 | pane 159 | ) 160 | wezterm.GLOBAL.visual_mode = nil 161 | else 162 | window:perform_action( 163 | wezterm.action.Multiple({ 164 | wezterm.action.CopyMode({ SetSelectionMode = "Cell" }), 165 | wezterm.action_callback(function(_, _) 166 | wezterm.GLOBAL.visual_mode = "Cell" 167 | wezterm.emit("modal.enter", "Visual", window, pane) 168 | end), 169 | }), 170 | pane 171 | ) 172 | end 173 | end), 174 | }, 175 | { 176 | key = "V", 177 | mods = "SHIFT", 178 | action = wezterm.action_callback(function(window, pane) 179 | if wezterm.GLOBAL.visual_mode == "Line" then 180 | wezterm.emit("modal.enter", "copy_mode", window, pane) 181 | window:perform_action( 182 | wezterm.action.CopyMode({ SetSelectionMode = wezterm.GLOBAL.visual_mode }), 183 | pane 184 | ) 185 | wezterm.GLOBAL.visual_mode = nil 186 | else 187 | window:perform_action( 188 | wezterm.action.Multiple({ 189 | wezterm.action.CopyMode({ SetSelectionMode = "Line" }), 190 | wezterm.action_callback(function(_, _) 191 | wezterm.GLOBAL.visual_mode = "Line" 192 | wezterm.emit("modal.enter", "Visual", window, pane) 193 | end), 194 | }), 195 | pane 196 | ) 197 | end 198 | end), 199 | }, 200 | { 201 | key = "v", 202 | mods = "CTRL", 203 | action = wezterm.action_callback(function(window, pane) 204 | if wezterm.GLOBAL.visual_mode == "Block" then 205 | wezterm.emit("modal.enter", "copy_mode", window, pane) 206 | window:perform_action( 207 | wezterm.action.CopyMode({ SetSelectionMode = wezterm.GLOBAL.visual_mode }), 208 | pane 209 | ) 210 | wezterm.GLOBAL.visual_mode = nil 211 | else 212 | window:perform_action( 213 | wezterm.action.Multiple({ 214 | wezterm.action.CopyMode({ SetSelectionMode = "Block" }), 215 | wezterm.action_callback(function(_, _) 216 | wezterm.GLOBAL.visual_mode = "Block" 217 | wezterm.emit("modal.enter", "Visual", window, pane) 218 | end), 219 | }), 220 | pane 221 | ) 222 | end 223 | end), 224 | }, 225 | { 226 | key = "v", 227 | mods = "ALT", 228 | action = wezterm.action_callback(function(window, pane) 229 | if wezterm.GLOBAL.visual_mode == "SemanticZone" then 230 | wezterm.emit("modal.enter", "copy_mode", window, pane) 231 | window:perform_action( 232 | wezterm.action.CopyMode({ SetSelectionMode = wezterm.GLOBAL.visual_mode }), 233 | pane 234 | ) 235 | wezterm.GLOBAL.visual_mode = nil 236 | else 237 | window:perform_action( 238 | wezterm.action.Multiple({ 239 | wezterm.action.CopyMode({ SetSelectionMode = "SemanticZone" }), 240 | wezterm.action_callback(function(_, _) 241 | wezterm.GLOBAL.visual_mode = "SemanticZone" 242 | wezterm.emit("modal.enter", "Visual", window, pane) 243 | end), 244 | }), 245 | pane 246 | ) 247 | end 248 | end), 249 | }, 250 | 251 | { 252 | key = "s", 253 | mods = "NONE", 254 | action = wezterm.action.CopyMode("MoveBackwardSemanticZone"), 255 | }, 256 | { 257 | key = "S", 258 | mods = "SHIFT", 259 | action = wezterm.action.CopyMode("MoveForwardSemanticZone"), 260 | }, 261 | 262 | -- Move by cell 263 | { key = "h", action = wezterm.action.CopyMode("MoveLeft") }, 264 | { key = "j", action = wezterm.action.CopyMode("MoveDown") }, 265 | { key = "k", action = wezterm.action.CopyMode("MoveUp") }, 266 | { key = "l", action = wezterm.action.CopyMode("MoveRight") }, 267 | { key = "LeftArrow", action = wezterm.action.CopyMode("MoveLeft") }, 268 | { key = "RightArrow", action = wezterm.action.CopyMode("MoveRight") }, 269 | { key = "UpArrow", action = wezterm.action.CopyMode("MoveUp") }, 270 | { key = "DownArrow", action = wezterm.action.CopyMode("MoveDown") }, 271 | 272 | -- Move by word 273 | { key = "w", action = wezterm.action.CopyMode("MoveForwardWord") }, 274 | { key = "e", action = wezterm.action.CopyMode("MoveForwardWordEnd") }, 275 | { key = "b", action = wezterm.action.CopyMode("MoveBackwardWord") }, 276 | { key = "LeftArrow", mods = "CTRL", action = wezterm.action.CopyMode("MoveBackwardWord") }, 277 | { key = "RightArrow", mods = "CTRL", action = wezterm.action.CopyMode("MoveForwardWord") }, 278 | 279 | -- Move by page 280 | { key = "d", mods = "CTRL", action = wezterm.action.CopyMode({ MoveByPage = 0.5 }) }, 281 | { 282 | key = "u", 283 | mods = "CTRL", 284 | action = wezterm.action.CopyMode({ MoveByPage = -0.5 }), 285 | }, 286 | { key = "PageUp", action = wezterm.action.CopyMode("PageUp") }, 287 | { key = "PageDown", action = wezterm.action.CopyMode("PageDown") }, 288 | { key = "b", mods = "CTRL", action = wezterm.action.CopyMode("PageUp") }, 289 | { key = "f", mods = "CTRL", action = wezterm.action.CopyMode("PageDown") }, 290 | 291 | -- yank 292 | { 293 | key = "y", 294 | action = wezterm.action.Multiple({ 295 | { CopyTo = "ClipboardAndPrimarySelection" }, 296 | modal.exit_mode("copy_mode"), 297 | }), 298 | }, 299 | 300 | --Search binds 301 | { 302 | key = "/", 303 | mods = "SHIFT", 304 | action = wezterm.action_callback(function(window, pane) 305 | if wezterm.GLOBAL.search_mode then 306 | wezterm.emit("modal.enter", "search_mode", window, pane) 307 | window:perform_action(wezterm.action.CopyMode("EditPattern"), pane) 308 | else 309 | wezterm.GLOBAL.search_mode = true 310 | window:perform_action(modal.activate_mode("search_mode"), pane) 311 | end 312 | end), 313 | }, 314 | { 315 | key = "/", 316 | mods = "NONE", 317 | action = wezterm.action_callback(function(window, pane) 318 | if wezterm.GLOBAL.search_mode then 319 | wezterm.emit("modal.enter", "search_mode", window, pane) 320 | window:perform_action(wezterm.action.CopyMode("EditPattern"), pane) 321 | else 322 | wezterm.GLOBAL.search_mode = true 323 | window:perform_action(modal.activate_mode("search_mode"), pane) 324 | end 325 | end), 326 | }, 327 | { key = "n", mods = "NONE", action = wezterm.action.CopyMode("NextMatch") }, 328 | { key = "P", mods = "SHIFT", action = wezterm.action.CopyMode("NextMatch") }, 329 | { key = "p", mods = "NONE", action = wezterm.action.CopyMode("PriorMatch") }, 330 | { key = "N", mods = "SHIFT", action = wezterm.action.CopyMode("PriorMatch") }, 331 | 332 | { key = ",", mods = "NONE", action = wezterm.action.CopyMode("JumpReverse") }, 333 | { key = ";", mods = "NONE", action = wezterm.action.CopyMode("JumpAgain") }, 334 | 335 | { 336 | key = "f", 337 | action = wezterm.action.CopyMode({ JumpForward = { prev_char = false } }), 338 | }, 339 | { 340 | key = "F", 341 | mods = "SHIFT", 342 | action = wezterm.action.CopyMode({ JumpBackward = { prev_char = false } }), 343 | }, 344 | 345 | { 346 | key = "t", 347 | action = wezterm.action.CopyMode({ JumpBackward = { prev_char = true } }), 348 | }, 349 | { 350 | key = "T", 351 | mods = "SHIFT", 352 | action = wezterm.action.CopyMode({ JumpBackward = { prev_char = true } }), 353 | }, 354 | }, 355 | } 356 | --------------------------------------------------------------------------------