Repository: sunjon/Shade.nvim Branch: master Commit: 4286b5abc47d Files: 3 Total size: 14.0 KB Directory structure: gitextract_wrvkyd1r/ ├── LICENSE ├── README.md └── lua/ └── shade.lua ================================================ FILE CONTENTS ================================================ ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2021 Senghan Bright 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 ================================================ # shade.nvim Shade is a Neovim plugin that dims your inactive windows, making it easier to see the active window at a glance. screenshot ## Installation ### [Packer](https://github.com/wbthomason/packer.nvim) ``` use 'sunjon/shade.nvim' ``` ### [Vim-Plug](https://github.com/junegunn/vim-plug) ``` Plug 'sunjon/shade.nvim' ``` ## Configuration ``` require'shade'.setup({ overlay_opacity = 50, opacity_step = 1, keys = { brightness_up = '', brightness_down = '', toggle = 's', } }) ``` * The `keys` table above shows available actions. No mappings are defined by default. * The color of the numbers in the brightness control popup can be customized by creating a highlight group named: `ShadeBrightnessPopup` and setting the attributes to your liking. ## License Copyright (c) Senghan Bright. Distributed under the MIT license ================================================ FILE: lua/shade.lua ================================================ -- TODO: remove all active_overlays on tab change local api = vim.api local E = {} E.DEFAULT_OVERLAY_OPACITY = 50 E.DEFAULT_OPACITY_STEP = 1 E.DEBUG_OVERLAY_OPACITY = 90 E.NOTIFICATION_TIMEOUT = 1000 -- ms local state = {} state.active = false state.active_overlays = {} state.shade_nsid = nil state.notification_timer = nil state.notification_window = nil -- TODO: log to file and/or nvim_echo local function log(event, msg) if state.debug == false then return end msg = tostring(msg) local info = debug.getinfo(2, "Sl") local line_info = "[shade:" .. info.currentline .. "]" local timestamp = ("%s %-15s"):format(os.date("%H:%M:%S"), line_info) local event_msg = ("%-10s %s"):format(event, msg) print(timestamp .. " : " .. event_msg) end -- local font = { 0x7C, 0xC6, 0xCE, 0xDE, 0xF6, 0xE6, 0x7C, 0x00, -- (0) 0x30, 0x70, 0x30, 0x30, 0x30, 0x30, 0xFC, 0x00, -- (1) 0x78, 0xCC, 0x0C, 0x38, 0x60, 0xCC, 0xFC, 0x00, -- (2) 0x78, 0xCC, 0x0C, 0x38, 0x0C, 0xCC, 0x70, 0x00, -- (3) 0x1C, 0x3C, 0x6C, 0xCC, 0xFE, 0x0C, 0x1E, 0x00, -- (4) 0xFC, 0xC0, 0xF8, 0x0C, 0x0C, 0xCC, 0x78, 0x00, -- (5) 0x38, 0x60, 0xC0, 0xF8, 0xCC, 0xCC, 0x78, 0x00, -- (6) 0xFC, 0xCC, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x00, -- (7) 0x78, 0xCC, 0xCC, 0x78, 0xCC, 0xCC, 0x78, 0x00, -- (8) 0x78, 0xCC, 0xCC, 0x7C, 0x0C, 0x18, 0x70, 0x00, -- (9) } local function get_digit(number, pos) local n = 10 ^ pos local n1 = 10 ^ (pos - 1) return math.floor((number % n) / n1) end local function digitize(number) assert(type(number) == "number") local len = math.floor(math.log10(number) + 1) local block_chars = {[0] = " ", [1] = "▀", [2] = "▄", [3] = "█"} -- generate bit table local offset, char, row_bits, hex_val, b local characters = {} for n = 1, len do offset = get_digit(number, len - n + 1) * 8 char = {} for row = 1, 8 do row_bits = {} hex_val = font[offset + row] for i = 1, 8 do b = bit.band(bit.rshift(hex_val, 8 - i), 1) row_bits[i] = b end table.insert(char, row_bits) end table.insert(characters, char) end -- generate strings local output = {} local upper, lower, combined, row_str for row = 1, 8, 2 do row_str = " " for _, c in ipairs(characters) do for col = 1, 8 do upper = c[row][col] lower = c[row + 1][col] * 2 combined = block_chars[upper + lower] row_str = row_str .. combined end end row_str = row_str .. " " table.insert(output, row_str) end return output end -- local function filter_wininfo(wininfo) return { relative = "editor", style = "minimal", focusable = false, row = wininfo.winrow - 1, col = wininfo.wincol - 1, width = wininfo.width, height = wininfo.height, } end -- local function create_hl_groups() local overlay_color if state.debug == true then overlay_color = "#77a992" else overlay_color = "None" end api.nvim_command("highlight ShadeOverlay gui='nocombine' guibg=" .. overlay_color) -- Link to default hl_group if not user defined local exists, _ = pcall(function() return vim.api.nvim_get_hl_by_name("ShadeBrightnessPopup", false) end) if not exists then api.nvim_command("highlight link ShadeBrightnessPopup Number") end end -- local function create_floatwin(config) local window = {} window.wincfg = config window.bufid = api.nvim_create_buf(false, true) window.winid = api.nvim_open_win(window.bufid, false, config) return window end -- local function map_key(mode, key, action) local req_module = ("lua require'shade'.%s"):format(action) vim.api.nvim_set_keymap(mode, key, req_module, {noremap = true, silent = true}) end local function shade_window(winid) local overlay = state.active_overlays[winid] if overlay then if api.nvim_win_is_valid(overlay.winid) then api.nvim_win_set_option(overlay.winid, "winblend", state.overlay_opacity) log("shade_window", ("[%d] : overlay %d ON (winblend: %d)"):format(winid, overlay.winid, state.overlay_opacity)) end else log("shade_window", "overlay not found for " .. winid) end end local function unshade_window(winid) local overlay = state.active_overlays[winid] if overlay then if api.nvim_win_is_valid(overlay.winid) then api.nvim_win_set_option(overlay.winid, "winblend", 100) log("unshade_window", ("[%d] : overlay %d OFF (winblend: 100 [disabled])"):format(winid, overlay.winid)) end else log("unshade_window", "overlay not found for " .. winid) end end -- shade everything on a tabpage except current window local function shade_tabpage(winid) winid = winid or api.nvim_get_current_win() for overlay_winid, _ in pairs(state.active_overlays) do local diff_enabled = api.nvim_win_get_option(overlay_winid, 'diff') if overlay_winid ~= winid and diff_enabled == false then log("deactivating window", overlay_winid) shade_window(overlay_winid) end end end -- local function remove_all_overlays() for _, overlay in pairs(state.active_overlays) do api.nvim_win_close(overlay.winid, true) end state.active_overlays = {} end local function create_overlay_window(winid, config) local new_window = create_floatwin(config) state.active_overlays[winid] = new_window api.nvim_win_set_option(new_window.winid, "winhighlight", "Normal:ShadeOverlay") api.nvim_win_set_option(new_window.winid, "winblend", state.overlay_opacity) log("create overlay", ("[%d] : overlay %d created"):format(winid, state.active_overlays[winid].winid)) end -- local function create_tabpage_overlays(tabid) local wincfg for _, winid in pairs(api.nvim_tabpage_list_wins(tabid)) do wincfg = api.nvim_call_function("getwininfo", {winid})[1] create_overlay_window(winid, filter_wininfo(wincfg)) end unshade_window(api.nvim_get_current_win()) end local shade = {} -- init shade.init = function(opts) state.active_overlays = {} opts = opts or {} state.debug = opts.debug or false state.overlay_opacity = opts.overlay_opacity or (state.debug == true and E.DEBUG_OVERLAY_OPACITY or E.DEFAULT_OVERLAY_OPACITY) state.opacity_step = opts.opacity_step or E.DEFAULT_OPACITY_STEP state.shade_under_float = opts.shade_under_float or true state.shade_nsid = api.nvim_create_namespace("shade") local shade_action = { ["brightness_up"] = "brightness_up()", ["brightness_down"] = "brightness_down()", ["toggle"] = "toggle()", } if opts.keys ~= nil then for action, key in pairs(opts.keys) do if not shade_action[action] then log("init:keymap", "unknown action " .. action) else map_key("n", key, shade_action[action]) end end end create_hl_groups() api.nvim_set_decoration_provider(state.shade_nsid, {on_win = shade.event_listener}) -- setup autocommands -- TODO: set a precalculated winid api.nvim_exec([[ augroup shade au! au WinEnter,VimEnter * call v:lua.require'shade'.autocmd('WinEnter', win_getid()) au WinClosed * call v:lua.require'shade'.autocmd('WinClosed', expand('')) au TabEnter * call v:lua.require'shade'.autocmd('TabEnter', win_getid()) au OptionSet diff call v:lua.require'shade'.autocmd('OptionSet', win_getid()) augroup END ]], false) log("Init", "-- Shade.nvim started --") return true end -- shade.on_win_enter = function(event, winid) log(event, winid) if not state.active_overlays[winid] then local float_cfg = api.nvim_win_get_config(winid) if float_cfg["relative"] == "" then local wincfg = api.nvim_call_function("getwininfo", {winid})[1] create_overlay_window(winid, filter_wininfo(wincfg)) else log(event, "floating window ignored: " .. winid) if not state.shade_under_float then return end end end -- hide the overlay on entered window unshade_window(winid) -- place overlays on all other windows shade_tabpage(winid) end shade.event_listener = function(_, winid, _, _, _) local cached = state.active_overlays[winid] if not cached then return end -- check if window dims match cache local current_wincfg = vim.api.nvim_call_function("getwininfo", {winid})[1] local resize_metrics = {"width", "height", "wincol", "winrow"} for _, m in pairs(resize_metrics) do if current_wincfg[m] ~= cached.wincfg[m] then log("event_listener: resized", winid) state.active_overlays[winid].wincfg = current_wincfg api.nvim_win_set_config(cached.winid, filter_wininfo(current_wincfg)) goto continue end end ::continue:: end -- -- destroy overlay window on WinClosed shade.on_win_closed = function(event, winid) winid = tonumber(winid) -- TODO: when did winid become a string? local overlay = state.active_overlays[winid] if overlay == nil then log(event, "no overlay to close") else api.nvim_win_close(overlay.winid, false) log(event, ("[%d] : overlay %d destroyed"):format(winid, overlay.winid)) state.active_overlays[winid] = nil end end shade.change_brightness = function(level) local curr_winid = api.nvim_get_current_win() state.overlay_opacity = level for id, w in pairs(state.active_overlays) do if id ~= curr_winid then log("winblend: winid" .. w.winid, level) api.nvim_win_set_option(w.winid, "winblend", level) end end local status_opts = { relative = "editor", style = "minimal", focusable = false, row = 1, col = vim.o.columns - 18, width = 18, height = 4, } if state.notification_window == nil then state.notification_window = create_floatwin(status_opts) api.nvim_win_set_option(state.notification_window.winid, "winhighlight", "Normal:ShadeBrightnessPopup") api.nvim_win_set_option(state.notification_window.winid, "winblend", 10) log("notification", "popup created") end local output_lines = digitize(level) api.nvim_buf_set_lines(state.notification_window.bufid, 0, 7, false, output_lines) if state.notification_timer ~= nil then state.notification_timer:stop() state.notification_timer = nil log("notification", "timer aborted") end state.notification_timer = vim.loop.new_timer() state.notification_timer:start(E.NOTIFICATION_TIMEOUT, 0, vim.schedule_wrap( function() if api.nvim_win_is_valid(state.notification_window.winid) then api.nvim_win_close(state.notification_window.winid, true) state.notification_window = nil log("notification", "timer ended") log("notification", "popup closed") end end)) end -- Main local M = {} M.setup = function(opts) if state.active == true then return end shade.init(opts) state.active = true end M.brightness_up = function() if not state.active then return end local adjusted = state.overlay_opacity + state.opacity_step if adjusted > 99 then adjusted = 99 end shade.change_brightness(adjusted) end M.brightness_down = function() if not state.active then return end local adjusted = state.overlay_opacity - state.opacity_step if adjusted < 0 then adjusted = 0 end shade.change_brightness(adjusted) end M.toggle = function() if state.active then remove_all_overlays() print("off") state.active = false else create_tabpage_overlays(0) print("on") state.active = true end end M.autocmd = function(event, winid) if not state.active then return end log("AutoCmd: " .. event .. " : " .. winid) local event_fn = { ["WinEnter"] = function() shade.on_win_enter(event, winid) end, ["WinClosed"] = function() shade.on_win_closed(event, winid) end, ["TabEnter"] = function() remove_all_overlays() create_tabpage_overlays(0) end, ["OptionSet"] = function() local diff_enabled = api.nvim_get_vvar('option_new') if diff_enabled then unshade_window(winid) shade_tabpage(winid) end end } local fn = event_fn[event] if fn then fn() end end return M