[
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021 Senghan Bright\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# shade.nvim\n\nShade is a Neovim plugin that dims your inactive windows, making it easier to see the active window at a glance.\n\n<img src=\"https://raw.githubusercontent.com/sunjon/images/master/shade_demo.gif\" alt=\"screenshot\" width=\"800\"/>\n\n## Installation\n\n### [Packer](https://github.com/wbthomason/packer.nvim) \n\n```\nuse 'sunjon/shade.nvim'\n```\n### [Vim-Plug](https://github.com/junegunn/vim-plug)\n\n```\nPlug 'sunjon/shade.nvim'\n```\n\n## Configuration\n\n```\nrequire'shade'.setup({\n  overlay_opacity = 50,\n  opacity_step = 1,\n  keys = {\n    brightness_up    = '<C-Up>',\n    brightness_down  = '<C-Down>',\n    toggle           = '<Leader>s',\n  }\n})\n```\n\n* The `keys` table above shows available actions. No mappings are defined by default.\n\n* 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.\n\n## License\n\nCopyright (c) Senghan Bright. Distributed under the MIT license\n\n"
  },
  {
    "path": "lua/shade.lua",
    "content": "-- TODO: remove all active_overlays on tab change\nlocal api = vim.api\n\nlocal E = {}\nE.DEFAULT_OVERLAY_OPACITY = 50\nE.DEFAULT_OPACITY_STEP    = 1\nE.DEBUG_OVERLAY_OPACITY   = 90\nE.NOTIFICATION_TIMEOUT    = 1000 -- ms\n\nlocal state = {}\nstate.active              = false\nstate.active_overlays     = {}\nstate.shade_nsid          = nil\nstate.notification_timer  = nil\nstate.notification_window = nil\n\n-- TODO: log to file and/or nvim_echo\nlocal function log(event, msg)\n  if state.debug == false then\n    return\n  end\n\n  msg = tostring(msg)\n  local info = debug.getinfo(2, \"Sl\")\n  local line_info = \"[shade:\" .. info.currentline .. \"]\"\n\n  local timestamp = (\"%s %-15s\"):format(os.date(\"%H:%M:%S\"), line_info)\n  local event_msg = (\"%-10s %s\"):format(event, msg)\n  print(timestamp .. \"  : \" .. event_msg)\nend\n\n--\n\nlocal font = {\n  0x7C, 0xC6, 0xCE, 0xDE, 0xF6, 0xE6, 0x7C, 0x00,   --  (0)\n  0x30, 0x70, 0x30, 0x30, 0x30, 0x30, 0xFC, 0x00,   --  (1)\n  0x78, 0xCC, 0x0C, 0x38, 0x60, 0xCC, 0xFC, 0x00,   --  (2)\n  0x78, 0xCC, 0x0C, 0x38, 0x0C, 0xCC, 0x70, 0x00,   --  (3)\n  0x1C, 0x3C, 0x6C, 0xCC, 0xFE, 0x0C, 0x1E, 0x00,   --  (4)\n  0xFC, 0xC0, 0xF8, 0x0C, 0x0C, 0xCC, 0x78, 0x00,   --  (5)\n  0x38, 0x60, 0xC0, 0xF8, 0xCC, 0xCC, 0x78, 0x00,   --  (6)\n  0xFC, 0xCC, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x00,   --  (7)\n  0x78, 0xCC, 0xCC, 0x78, 0xCC, 0xCC, 0x78, 0x00,   --  (8)\n  0x78, 0xCC, 0xCC, 0x7C, 0x0C, 0x18, 0x70, 0x00,   --  (9)\n}\n\n\nlocal function get_digit(number, pos)\n  local n  = 10 ^ pos\n  local n1 = 10 ^ (pos - 1)\n  return math.floor((number % n) / n1)\nend\n\nlocal function digitize(number)\n  assert(type(number) == \"number\")\n  local len = math.floor(math.log10(number) + 1)\n\n  local block_chars = {[0] = \" \", [1] = \"▀\", [2] = \"▄\", [3] = \"█\"}\n\n  -- generate bit table\n  local offset, char, row_bits, hex_val, b\n  local characters = {}\n  for n = 1, len do\n    offset = get_digit(number, len - n + 1) * 8\n    char = {}\n    for row = 1, 8 do\n      row_bits = {}\n      hex_val = font[offset + row]\n      for i = 1, 8 do\n        b = bit.band(bit.rshift(hex_val, 8 - i), 1)\n        row_bits[i] = b\n      end\n      table.insert(char, row_bits)\n    end\n    table.insert(characters, char)\n  end\n\n  -- generate strings\n  local output = {}\n  local upper, lower, combined, row_str\n  for row = 1, 8, 2 do\n    row_str = \" \"\n    for _, c in ipairs(characters) do\n      for col = 1, 8 do\n        upper = c[row][col]\n        lower = c[row + 1][col] * 2\n        combined = block_chars[upper + lower]\n        row_str = row_str .. combined\n      end\n    end\n    row_str = row_str .. \" \"\n    table.insert(output, row_str)\n  end\n\n  return output\nend\n\n--\nlocal function filter_wininfo(wininfo)\n  return {\n    relative  = \"editor\",\n    style     = \"minimal\",\n    focusable = false,\n    row    = wininfo.winrow - 1,\n    col    = wininfo.wincol - 1,\n    width  = wininfo.width,\n    height = wininfo.height,\n  }\nend\n\n--\nlocal function create_hl_groups()\n  local overlay_color\n  if state.debug == true then\n    overlay_color = \"#77a992\"\n  else\n    overlay_color = \"None\"\n  end\n\n  api.nvim_command(\"highlight ShadeOverlay gui='nocombine' guibg=\" .. overlay_color)\n\n  -- Link to default hl_group if not user defined\n  local exists, _ = pcall(function()\n    return vim.api.nvim_get_hl_by_name(\"ShadeBrightnessPopup\", false)\n  end)\n  if not exists then\n    api.nvim_command(\"highlight link ShadeBrightnessPopup Number\")\n  end\nend\n\n--\n\nlocal function create_floatwin(config)\n  local window = {}\n\n  window.wincfg = config\n  window.bufid = api.nvim_create_buf(false, true)\n  window.winid = api.nvim_open_win(window.bufid, false, config)\n\n  return window\nend\n\n--\n\nlocal function map_key(mode, key, action)\n  local req_module = (\"<cmd>lua require'shade'.%s<CR>\"):format(action)\n  vim.api.nvim_set_keymap(mode, key, req_module, {noremap = true, silent = true})\nend\n\n\n\nlocal function shade_window(winid)\n  local overlay = state.active_overlays[winid]\n  if overlay then\n    if api.nvim_win_is_valid(overlay.winid) then\n      api.nvim_win_set_option(overlay.winid, \"winblend\", state.overlay_opacity)\n      log(\"shade_window\",\n        (\"[%d] : overlay %d ON (winblend: %d)\"):format(winid, overlay.winid, state.overlay_opacity))\n    end\n  else\n    log(\"shade_window\", \"overlay not found for \" .. winid)\n  end\nend\n\nlocal function unshade_window(winid)\n  local overlay = state.active_overlays[winid]\n  if overlay then\n    if api.nvim_win_is_valid(overlay.winid) then\n      api.nvim_win_set_option(overlay.winid, \"winblend\", 100)\n      log(\"unshade_window\",\n        (\"[%d] : overlay %d OFF (winblend: 100 [disabled])\"):format(winid, overlay.winid))\n    end\n  else\n    log(\"unshade_window\", \"overlay not found for \" .. winid)\n  end\nend\n\n-- shade everything on a tabpage except current window\nlocal function shade_tabpage(winid)\n  winid = winid or api.nvim_get_current_win()\n  for overlay_winid, _ in pairs(state.active_overlays) do\n    local diff_enabled = api.nvim_win_get_option(overlay_winid, 'diff')\n    if overlay_winid ~= winid and diff_enabled == false then\n      log(\"deactivating window\", overlay_winid)\n      shade_window(overlay_winid)\n    end\n  end\nend\n\n--\n\nlocal function remove_all_overlays()\n  for _, overlay in pairs(state.active_overlays) do\n    api.nvim_win_close(overlay.winid, true)\n  end\n  state.active_overlays = {}\nend\n\n\nlocal function create_overlay_window(winid, config)\n  local new_window = create_floatwin(config)\n  state.active_overlays[winid] = new_window\n\n  api.nvim_win_set_option(new_window.winid, \"winhighlight\", \"Normal:ShadeOverlay\")\n  api.nvim_win_set_option(new_window.winid, \"winblend\", state.overlay_opacity)\n\n  log(\"create overlay\",\n    (\"[%d] : overlay %d created\"):format(winid, state.active_overlays[winid].winid))\nend\n\n--\nlocal function create_tabpage_overlays(tabid)\n  local wincfg\n  for _, winid in pairs(api.nvim_tabpage_list_wins(tabid)) do\n    wincfg = api.nvim_call_function(\"getwininfo\", {winid})[1]\n    create_overlay_window(winid, filter_wininfo(wincfg))\n  end\n  unshade_window(api.nvim_get_current_win())\nend\n\nlocal shade = {}\n\n-- init\nshade.init = function(opts)\n  state.active_overlays = {}\n\n  opts = opts or {}\n  state.debug = opts.debug or false\n\n  state.overlay_opacity = opts.overlay_opacity or\n                            (state.debug == true and E.DEBUG_OVERLAY_OPACITY or\n                              E.DEFAULT_OVERLAY_OPACITY)\n  state.opacity_step = opts.opacity_step or E.DEFAULT_OPACITY_STEP\n  state.shade_under_float = opts.shade_under_float or true\n\n  state.shade_nsid = api.nvim_create_namespace(\"shade\")\n\n  local shade_action = {\n    [\"brightness_up\"] = \"brightness_up()\",\n    [\"brightness_down\"] = \"brightness_down()\",\n    [\"toggle\"] = \"toggle()\",\n  }\n\n  if opts.keys ~= nil then\n    for action, key in pairs(opts.keys) do\n      if not shade_action[action] then\n        log(\"init:keymap\", \"unknown action \" .. action)\n      else\n        map_key(\"n\", key, shade_action[action])\n      end\n    end\n  end\n\n  create_hl_groups()\n\n  api.nvim_set_decoration_provider(state.shade_nsid, {on_win = shade.event_listener})\n\n  -- setup autocommands -- TODO: set a precalculated winid\n  api.nvim_exec([[\n    augroup shade\n    au!\n    au WinEnter,VimEnter * call v:lua.require'shade'.autocmd('WinEnter',  win_getid())\n    au WinClosed         * call v:lua.require'shade'.autocmd('WinClosed', expand('<afile>'))\n    au TabEnter          * call v:lua.require'shade'.autocmd('TabEnter',  win_getid())\n    au OptionSet         diff call v:lua.require'shade'.autocmd('OptionSet', win_getid())\n    augroup END\n  ]], false)\n\n  log(\"Init\", \"-- Shade.nvim started --\")\n\n  return true\nend\n\n--\n\nshade.on_win_enter = function(event, winid)\n  log(event, winid)\n  if not state.active_overlays[winid] then\n    local float_cfg = api.nvim_win_get_config(winid)\n    if float_cfg[\"relative\"] == \"\" then\n      local wincfg = api.nvim_call_function(\"getwininfo\", {winid})[1]\n      create_overlay_window(winid, filter_wininfo(wincfg))\n    else\n      log(event, \"floating window ignored: \" .. winid)\n      if not state.shade_under_float then\n        return\n      end\n    end\n  end\n\n  -- hide the overlay on entered window\n  unshade_window(winid)\n\n  -- place overlays on all other windows\n  shade_tabpage(winid)\nend\n\nshade.event_listener = function(_, winid, _, _, _)\n  local cached = state.active_overlays[winid]\n  if not cached then\n    return\n  end\n\n  -- check if window dims match cache\n  local current_wincfg = vim.api.nvim_call_function(\"getwininfo\", {winid})[1]\n  local resize_metrics = {\"width\", \"height\", \"wincol\", \"winrow\"}\n  for _, m in pairs(resize_metrics) do\n    if current_wincfg[m] ~= cached.wincfg[m] then\n      log(\"event_listener: resized\", winid)\n      state.active_overlays[winid].wincfg = current_wincfg\n      api.nvim_win_set_config(cached.winid, filter_wininfo(current_wincfg))\n      goto continue\n    end\n  end\n  ::continue::\nend\n\n--\n-- destroy overlay window on WinClosed\nshade.on_win_closed = function(event, winid)\n  winid = tonumber(winid) -- TODO: when did winid become a string?\n  local overlay = state.active_overlays[winid]\n  if overlay == nil then\n    log(event, \"no overlay to close\")\n  else\n    api.nvim_win_close(overlay.winid, false)\n    log(event, (\"[%d] : overlay %d destroyed\"):format(winid, overlay.winid))\n    state.active_overlays[winid] = nil\n  end\nend\n\nshade.change_brightness = function(level)\n  local curr_winid = api.nvim_get_current_win()\n  state.overlay_opacity = level\n  for id, w in pairs(state.active_overlays) do\n    if id ~= curr_winid then\n      log(\"winblend: winid\" .. w.winid, level)\n      api.nvim_win_set_option(w.winid, \"winblend\", level)\n    end\n  end\n\n  local status_opts = {\n    relative = \"editor\",\n    style = \"minimal\",\n    focusable = false,\n    row       = 1,\n    col       = vim.o.columns - 18,\n    width     = 18,\n    height    = 4,\n  }\n\n  if state.notification_window == nil then\n    state.notification_window = create_floatwin(status_opts)\n    api.nvim_win_set_option(state.notification_window.winid, \"winhighlight\",\n      \"Normal:ShadeBrightnessPopup\")\n    api.nvim_win_set_option(state.notification_window.winid, \"winblend\", 10)\n    log(\"notification\", \"popup created\")\n  end\n\n  local output_lines = digitize(level)\n  api.nvim_buf_set_lines(state.notification_window.bufid, 0, 7, false, output_lines)\n\n  if state.notification_timer ~= nil then\n    state.notification_timer:stop()\n    state.notification_timer = nil\n    log(\"notification\", \"timer aborted\")\n  end\n  state.notification_timer = vim.loop.new_timer()\n  state.notification_timer:start(E.NOTIFICATION_TIMEOUT, 0, vim.schedule_wrap(\n    function()\n      if api.nvim_win_is_valid(state.notification_window.winid) then\n        api.nvim_win_close(state.notification_window.winid, true)\n        state.notification_window = nil\n        log(\"notification\", \"timer ended\")\n        log(\"notification\", \"popup closed\")\n      end\n    end))\n\nend\n\n-- Main\nlocal M = {}\n\nM.setup = function(opts)\n  if state.active == true then\n    return\n  end\n  shade.init(opts)\n  state.active = true\nend\n\nM.brightness_up = function()\n  if not state.active then\n    return\n  end\n\n  local adjusted = state.overlay_opacity + state.opacity_step\n  if adjusted > 99 then\n    adjusted = 99\n  end\n  shade.change_brightness(adjusted)\nend\n\nM.brightness_down = function()\n  if not state.active then\n    return\n  end\n\n  local adjusted = state.overlay_opacity - state.opacity_step\n  if adjusted < 0 then\n    adjusted = 0\n  end\n  shade.change_brightness(adjusted)\nend\n\nM.toggle = function()\n  if state.active then\n    remove_all_overlays()\n    print(\"off\")\n    state.active = false\n  else\n    create_tabpage_overlays(0)\n    print(\"on\")\n    state.active = true\n  end\nend\n\nM.autocmd = function(event, winid)\n  if not state.active then\n    return\n  end\n  log(\"AutoCmd: \" .. event .. \" : \" .. winid)\n\n  local event_fn = {\n    [\"WinEnter\"] = function()\n      shade.on_win_enter(event, winid)\n    end,\n    [\"WinClosed\"] = function()\n      shade.on_win_closed(event, winid)\n    end,\n    [\"TabEnter\"] = function()\n      remove_all_overlays()\n      create_tabpage_overlays(0)\n    end,\n    [\"OptionSet\"] = function()\n     local diff_enabled = api.nvim_get_vvar('option_new')\n     if diff_enabled then\n       unshade_window(winid)\n       shade_tabpage(winid)\n     end\n    end\n  }\n\n  local fn = event_fn[event]\n  if fn then\n    fn()\n  end\nend\n\nreturn M\n"
  }
]