├── README.md ├── autoload └── keycastr.vim ├── LICENSE ├── doc └── keycastr.txt └── lua └── keycastr └── init.lua /README.md: -------------------------------------------------------------------------------- 1 | # nvim-keycastr 2 | 3 | Please read [help](doc/keycastr.txt) for details. 4 | -------------------------------------------------------------------------------- /autoload/keycastr.vim: -------------------------------------------------------------------------------- 1 | function! keycastr#enable() abort 2 | let s:winid = popup_create('', #{ 3 | \ line: &lines - &cmdheight - 1, 4 | \ col: &columns, 5 | \ pos: 'botright', 6 | \ maxwidth: 50, 7 | \ minwidth: 50, 8 | \ wrap: v:false, 9 | \ }) 10 | augroup keycastr 11 | autocmd! 12 | autocmd KeyInputPre * call s:on_key(v:event.typedchar) 13 | augroup END 14 | endfunction 15 | 16 | function! s:on_key(typedchar) abort 17 | if empty(a:typedchar) || a:typedchar ==# "\" 18 | return 19 | endif 20 | let key = keytrans(a:typedchar) 21 | let text = getbufline(winbufnr(s:winid), 1, '$')[0] .. key 22 | call popup_settext(s:winid, text->strcharpart(text->strcharlen() - 50)) 23 | if mode() ==# 'c' 24 | redraw 25 | endif 26 | endfunction 27 | 28 | function! keycastr#disable() abort 29 | autocmd! keycastr 30 | call popup_close(s:winid) 31 | unlet s:winid 32 | endfunction 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Hibiki 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 | -------------------------------------------------------------------------------- /doc/keycastr.txt: -------------------------------------------------------------------------------- 1 | *keycastr.txt* Visualize typed key strokes 2 | 3 | Author: Hibiki 4 | License: MIT License 5 | URL: https://github.com/4513ECHO/nvim-keycastr 6 | Last change: 2024 Jul 13 7 | 8 | ============================================================================== 9 | CONTENTS *keycastr-contents* 10 | 11 | Introduction |keycastr-introduction| 12 | Interface |keycastr-interface| 13 | Lua Interface |keycastr-lua-interface| 14 | Options |keycastr-options| 15 | About |keycastr-about| 16 | 17 | ============================================================================== 18 | INTRODUCTION *keycastr-introduction* 19 | 20 | *keycastr* ( *nvim-keycastr* ) is a plugin to visualize typed key strokes in 21 | the floating window, inspired by KeyCastr in macOS. 22 | 23 | https://github.com/keycastr/keycastr 24 | 25 | Neovim v0.10.0+ or Vim v9.1.0568+ is required. 26 | (But Vim support is experimental and not documented well.) 27 | 28 | ============================================================================== 29 | INTERFACE *keycastr-interface* 30 | 31 | ------------------------------------------------------------------------------ 32 | LUA INTERFACE *keycastr-lua-interface* 33 | 34 | "keycastr" is a module from `require('keycastr')`. 35 | 36 | keycastr.config.get() *keycastr.config.get()* 37 | Return the current configuration of the plugin. 38 | 39 | keycastr.config.set({config}) *keycastr.config.set()* 40 | Set the configuration of the plugin. 41 | See also |keycastr-options| for the configuration. 42 | 43 | keycastr.enable() *keycastr.enable()* 44 | Enable the visualizer and open the window. 45 | 46 | keycastr.disable() *keycastr.disable()* 47 | Diable the visualizer and close the window. 48 | 49 | keycastr.show() *keycastr.show()* 50 | (Re-)open the visualizer window. 51 | |keycastr.enable()| must be called before calling this function. 52 | 53 | keycastr.hide() *keycastr.hide()* 54 | Hide the visualizer window but the visualizer is still enabled in 55 | background. Use |keycastr.show()| to show the window again. 56 | 57 | ------------------------------------------------------------------------------ 58 | OPTIONS *keycastr-options* 59 | 60 | ignore_mouse *keycastr-option-ignore_mouse* 61 | |Boolean| (default: |v:true|) 62 | Whether ignores keys from mouse events, for example ||, 63 | ||, ||, etc. 64 | 65 | win_config *keycastr-option-win_config* 66 | |Dictionary| (default: `{ width = 50, height = 1 }`) 67 | Configurations for the visualizer window. 68 | See also |nvim_open_win()| for descriptions of the options. 69 | To set "anchor", "row" and "col" values, use 70 | |keycastr-option-position| instead. 71 | 72 | position *keycastr-option-position* 73 | |String| (enum of `"NE"|"NW"|"SE"|"SW"`, default: "NE") 74 | Higher layer for "anchor", "row" and "col" values for 75 | |keycastr-option-win_config|. 76 | 77 | ============================================================================== 78 | ABOUT *keycastr-about* 79 | 80 | |keycastr| is developed by Hibiki(4513ECHO) and licensed under the MIT 81 | License. Visit the project page for the latest version: 82 | 83 | https://github.com/4513ECHO/nvim-keycastr 84 | 85 | ============================================================================== 86 | vim:tw=78:ts=8:ft=help:norl:noet:fen: 87 | -------------------------------------------------------------------------------- /lua/keycastr/init.lua: -------------------------------------------------------------------------------- 1 | ---@diagnostic disable: duplicate-doc-alias, duplicate-doc-field 2 | ---@alias keycastr.state { bufnr: integer, buf_length: integer, winid: integer|nil } 3 | ---@class keycastr.config 4 | ---@field ignore_mouse boolean 5 | ---@field win_config vim.api.keyset.win_config 6 | ---@field position "NE"|"NW"|"SE"|"SW 7 | 8 | local M = {} 9 | local ns = vim.api.nvim_create_namespace "keycastr" 10 | ---@type keycastr.config 11 | local default_config = { 12 | ignore_mouse = true, 13 | win_config = { 14 | width = 50, 15 | height = 1, 16 | }, 17 | position = "SE", 18 | } 19 | ---@type keycastr.config 20 | local current_config = vim.deepcopy(default_config) 21 | ---@type keycastr.state|nil 22 | local state 23 | 24 | ---@param position "NE"|"NW"|"SE"|"SW" 25 | ---@return vim.api.keyset.win_config 26 | local function get_position(position) 27 | if position == "NE" then 28 | return { anchor = "NE", row = 0, col = vim.o.columns } 29 | elseif position == "NW" then 30 | return { anchor = "NW", row = 0, col = 0 } 31 | elseif position == "SE" then 32 | return { 33 | anchor = "SE", 34 | row = vim.o.lines - vim.o.cmdheight - 1, 35 | col = vim.o.columns, 36 | } 37 | elseif position == "SW" then 38 | return { anchor = "SW", row = vim.o.lines - vim.o.cmdheight - 1, col = 0 } 39 | end 40 | error("[keycastr] Invalid anchor value: " .. position) 41 | end 42 | 43 | ---@param config keycastr.config 44 | ---@return vim.api.keyset.win_config 45 | local function config_to_open_win(config) 46 | return vim.tbl_deep_extend("force", { 47 | relative = "editor", 48 | focusable = false, 49 | style = "minimal", 50 | }, get_position(config.position), config.win_config) 51 | end 52 | 53 | ---@param key string 54 | local function draw(key) 55 | if not state then 56 | return 57 | end 58 | local config = M.config.get() 59 | state.buf_length = state.buf_length + #key 60 | -- NOTE: Error may occur due to textlock 61 | pcall(vim.api.nvim_buf_set_text, state.bufnr, -1, -1, -1, -1, { key }) 62 | if state.winid and state.buf_length > config.win_config.width then 63 | local leftcol = state.buf_length - config.win_config.width 64 | vim.api.nvim_win_call( 65 | state.winid, 66 | function() vim.fn.winrestview { leftcol = leftcol } end 67 | ) 68 | end 69 | -- NOTE: Screen update is needed in command-line mode 70 | if vim.api.nvim_get_mode().mode == "c" then 71 | vim.cmd.redraw() 72 | end 73 | end 74 | 75 | local mouse_keys = { 76 | "Mouse>", 77 | "Drag>", 78 | "Release>", 79 | "WheelUp>", 80 | "WheelDown>", 81 | "WheelLeft>", 82 | "WheelRight>", 83 | } 84 | 85 | M.config = { 86 | ---@return keycastr.config 87 | get = function() return current_config end, 88 | ---@param user_config keycastr.config 89 | set = function(user_config) 90 | current_config = 91 | vim.tbl_deep_extend("force", default_config, current_config, user_config) 92 | if state and state.winid then 93 | vim.api.nvim_win_set_config( 94 | state.winid, 95 | config_to_open_win(current_config) 96 | ) 97 | end 98 | end, 99 | } 100 | 101 | function M.enable() 102 | if not M.show() then 103 | return 104 | end 105 | local ignore_mouse = M.config.get().ignore_mouse 106 | 107 | vim.on_key(function(_, typed) 108 | local key = vim.fn.keytrans(typed) 109 | if 110 | ignore_mouse 111 | and vim.iter(mouse_keys):any(function(v) return vim.endswith(key, v) end) 112 | then 113 | return 114 | end 115 | draw(key) 116 | end, ns) 117 | end 118 | 119 | ---@return boolean #whether the window is newly opened 120 | function M.show() 121 | if state and state.winid then 122 | return false 123 | end 124 | local config = M.config.get() 125 | local bufnr = state and state.bufnr or vim.api.nvim_create_buf(false, true) 126 | local winid = vim.api.nvim_open_win(bufnr, false, config_to_open_win(config)) 127 | vim.api.nvim_set_option_value("cursorline", false, { win = winid }) 128 | vim.api.nvim_set_option_value("wrap", false, { win = winid }) 129 | if state then 130 | state.winid = winid 131 | else 132 | state = { bufnr = bufnr, buf_length = 0, winid = winid } 133 | end 134 | return true 135 | end 136 | 137 | function M.disable() 138 | if state then 139 | M.hide() 140 | vim.api.nvim_buf_delete(state.bufnr, {}) 141 | state = nil 142 | vim.on_key(nil, ns) 143 | end 144 | end 145 | 146 | function M.hide() 147 | if state and state.winid then 148 | vim.api.nvim_win_close(state.winid, true) 149 | state.winid = nil 150 | end 151 | end 152 | 153 | return M 154 | --------------------------------------------------------------------------------