Skip to content

Commit

Permalink
Related to #75: (Lua-only atm) Adds ncmode='focus', VimadeFocus comma…
Browse files Browse the repository at this point in the history
…nds, VimadeMark commands. VimadeFocus is basically limelight with syntax highlighting. Created scope providers for VimadeFocus including treesitter (similar to twilight), blanks (similar to limelight), static (rolling static size), snacks.nvim, mini-indent, hlchunk.nvim. Scope providers can be configured based on filetype and can be used in any combination. Add VimadeMark, which allows you to select an area and prevent it from being faded.
  • Loading branch information
TaDaa committed Jan 20, 2025
1 parent 9fb08c8 commit 955a36c
Show file tree
Hide file tree
Showing 24 changed files with 2,134 additions and 219 deletions.
27 changes: 24 additions & 3 deletions lua/vimade/animator.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,24 @@ M.__init = function (args)
end)
args.FADER.on('tick:after', function ()
if animating then
-- TODO this isn't explicitly needed, but we should add something similar to this
-- animations should be set back to false if the window condition is no longer active
-- for winid, styles in pairs(animating) do
-- local scheduled_styles = (scheduled or {})[winid] or {}
-- for _, style in ipairs(styles) do
-- local found = false
-- for __, style2 in ipairs(scheduled_styles) do
-- if style == style2 then
-- found = true
-- break
-- end
-- end
-- -- ensure we unset animating for anything that didn't re-schedule
-- if not found and style._animating then
-- style._animating = false
-- end
-- end
-- end
animating = nil
end
if scheduled then
Expand All @@ -25,7 +43,7 @@ M.__init = function (args)
end)
end

M.schedule = function (win)
M.schedule = function (style)
-- this is also doable via lua code and then scheduling a vim-safe callback
-- but there appears to be no benefit. Sharing the code across all supported
-- versions seems much more maintainable currently AND
Expand All @@ -34,11 +52,14 @@ M.schedule = function (win)
if not scheduled then
scheduled = {}
end
scheduled[win.winid] = true
if not scheduled[style.win.winid] then
scheduled[style.win.winid] = {}
end
table.insert(scheduled[style.win.winid], style)
end

M.is_animating = function(winid)
return (scheduled[winid] or animating[winid]) or false
return (scheduled and scheduled[winid]) or (animating and animating[winid]) or false
end

return M
40 changes: 37 additions & 3 deletions lua/vimade/fader.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@ local ANIMATE = require('vimade.style.value.animate')
local ANIMATOR = require('vimade.animator')
local COMPAT = require('vimade.util.compat')
local FADE = require('vimade.style.fade')
local CONDITION = require('vimade.style.value.condition')
local DEFAULT = require('vimade.recipe.default')
local GLOBALS = require('vimade.state.globals')
local HIGHLIGHTER = require('vimade.highlighters.namespace')
local FOCUS = require('vimade.focus')
local NAMESPACE = require('vimade.state.namespace')
local REAL_NAMESPACE = require('vimade.state.real_namespace')
local TINT = require('vimade.style.tint')
local WIN_STATE = require('vimade.state.win')

local TABLE_INSERT = table.insert

local events = require('vimade.util.events')()

M.on = events.on
Expand All @@ -24,6 +29,12 @@ local update = function ()
local current = GLOBALS.current
local updated_cache = {}

areas = FOCUS.update({
winid = GLOBALS.current.winid,
tabnr = GLOBALS.current.tabnr,
prevent_events = false,
})

local style = GLOBALS.style
for i, s in ipairs(style) do
if s.tick then
Expand All @@ -38,8 +49,18 @@ local update = function ()
WIN_STATE.refresh_active(current.winid)
end
for i, winid in ipairs(windows) do
if current.winid ~= winid then
-- Refresh the non-area windows first. We need to know the state of the area_owner
-- before processing areas.
if current.winid ~= winid and not FOCUS.get(winid) then
WIN_STATE.refresh(winid)
end
end

-- Now check the areas.
for winid, _ in pairs(areas) do
if vim.api.nvim_win_is_valid(winid) then
WIN_STATE.refresh(winid)
TABLE_INSERT(windows, winid)
end
end

Expand All @@ -53,7 +74,7 @@ local update = function ()
local win = WIN_STATE.get(winid)
-- check if the namespace is owned by vimade and whether its currently active
-- ensures that the
if win.ns and win.current_ns and win.current_ns == win.ns.vimade_ns then
if win and win.ns and win.current_ns and win.current_ns == win.ns.vimade_ns then
local result = COMPAT.nvim_get_hl(win.ns.vimade_ns, {name = 'vimade_control'})
if result.fg ~= 0XFEDCBA or result.bg ~= 0X123456 then
if not corrupted_namespaces[win.ns.vimade_ns] then
Expand All @@ -72,12 +93,14 @@ local update = function ()
active_winids[winid] = true
end
WIN_STATE.cleanup(active_winids)
FOCUS.cleanup(active_winids)
end
end

-- external --
M.setup = function (config)
return GLOBALS.setup(config)
GLOBALS.setup(config)
FOCUS.setup(config.focus or {})
end

M.getInfo = function ()
Expand All @@ -94,6 +117,9 @@ M.animate = function ()
end

M.tick = function (override_tick_state)
if vim.g.vimade_running == 0 then
return
end
local last_ei = vim.go.ei
vim.go.ei ='all'

Expand All @@ -109,6 +135,7 @@ end

M.disable = function()
M.unhighlightAll()
FOCUS.disable()
end

M.unhighlightAll = function ()
Expand All @@ -126,10 +153,17 @@ M.unhighlightAll = function ()
end
end

FOCUS.on('focus:on', M.tick)
FOCUS.on('focus:off', M.tick)
FOCUS.on('focus:mark', M.tick)

ANIMATE.__init({FADER=M, GLOBALS=GLOBALS})
ANIMATOR.__init({FADER=M, GLOBALS=GLOBALS})
COMPAT.__init({FADER=M, GLOBALS=GLOBALS})
CONDITION.__init({FADER=M, GLOBALS=GLOBALS})
DEFAULT.__init({FADER=M, GLOBALS=GLOBALS})
FADE.__init({FADER=M, GLOBALS=GLOBALS})
FOCUS.__init({FADER=M, GLOBALS=GLOBALS})
HIGHLIGHTER.__init({FADER=M, GLOBALS=GLOBALS})
REAL_NAMESPACE.__init({FADER=M, GLOBALS=GLOBALS})
TINT.__init({FADER=M, GLOBALS=GLOBALS})
Expand Down
80 changes: 80 additions & 0 deletions lua/vimade/focus/api.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
local M = {}

local CORE = require('vimade.focus.core')

local events = require('vimade.util.events')()

M.__init = CORE.__init

M.on = events.on

M.get = CORE.get

M.update = CORE.update

M.cleanup = CORE.cleanup

M.setup = CORE.setup

M.global_focus_enabled = function()
return CORE.global_focus_enabled
end

M.toggle_on = function()
if vim.g.vimade_running == 0 then
return
end
CORE.activate_focus()
events.notify('focus:on')
end

M.toggle_off = function()
CORE.deactivate_focus()
events.notify('focus:off')
end

M.toggle = function()
if CORE.global_focus_enabled then
M.toggle_off()
else
M.toggle_on()
end
end

-- If no range is provided, any marks under the cursor will be removed.
-- If a range is provided, a new mark will be placed. Any marks overlapping the selection will be replaced.
-- config = {@optional range={start, end}}
M.mark_toggle = function(config)
CORE.mark_toggle(config)
events.notify('focus:mark')
end

-- Places a mark between the range of lines in the window.
-- config = {
-- @optional range={start, end} [default = cursor_location],
-- @optional winid: number [default = vim.api.nvim_get_current_win()]
-- }
M.mark_set = function(config)
CORE.mark_set(config)
events.notify('focus:mark')
end

-- removes all marks meeting the criteria. If no criteria is included,
-- all marks are removed.
-- config = {
-- @optional range: {start, end} -- NOTE: If range is provided without winid, the current window is assumed.
-- @optional winid: number
-- @optional bufnr: number
-- @optional tabnr: number
-- }
M.mark_remove = function(config)
CORE.mark_remove(config)
events.notify('focus:mark')
end

M.disable = function()
M.toggle_off()
CORE.cleanup({})
end

return M
111 changes: 111 additions & 0 deletions lua/vimade/focus/commands.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
local TYPE = require('vimade.util.type')
local API = require('vimade.focus.api')

local root_options = {
{'VimadeMark', {
{'', function(cmds, arg) API.mark_toggle({range={arg.line1, arg.line2}}) end},
{'set', function(cmds, arg) API.mark_set({range={arg.line1, arg.line2}}) end},
{'remove', function(cmds, arg) API.mark_remove({range={arg.line1, arg.line2}}) end},
{'remove-win', function(cmds, arg) API.mark_remove({winid=vim.api.nvim_get_current_win()}) end},
{'remove-buf', function(cmds, arg) API.mark_remove({bufnr=vim.api.nvim_get_current_buf()}) end},
{'remove-tab', function(cmds, arg) API.mark_remove({tabnr=vim.api.nvim_get_current_tabpage()}) end},
{'remove-all', function(cmds, arg) API.mark_remove({}) end},
}},
{'VimadeFocus', {
{'', API.toggle},
{'toggle', API.toggle},
{'toggle-on', API.toggle_on},
{'toggle-off', API.toggle_off},
}}
}

local options_of = function(input)
local result = {}
for i, value in ipairs(input) do
if value[1] ~= '' then
table.insert(result, value[1])
end
end
return result
end

local process_input = function(input, exact)
local inputs = input:gmatch('([^\\ ]+)')
local options = TYPE.deep_copy(root_options)
local result_options = {}
local remaining = {}
local run = true
local input_size = 0
local ends_sp = input:sub(-1) == ' '
local process = {}
for input in inputs do
input = input:lower()
input_size = input_size + 1
table.insert(process, input)
end
for i, key in ipairs(process) do
local found = false
for _, option in ipairs(options) do
if key == option[1]:lower() then
found = true
if type(option[2]) == 'function' then
table.insert(result_options, option)
elseif i < input_size or ends_sp or exact then
options = option[2]
else
table.insert(remaining, key)
end
break
end
end
if not exact and not found then
table.insert(remaining, key)
end
end
if #remaining > 1 then
return {}
end
if exact and #result_options > 0 then
options = result_options
end
remaining = remaining[1]
local r_ln = remaining and remaining:len() or 0
local indices = {}
for i, option in ipairs(options) do
indices[option[1]] = i
end
table.sort(options, function(a, b)
local a_is_remaining = a[1]:sub(1, r_ln) == remaining
local b_is_remaining = b[1]:sub(1, r_ln) == remaining
if (a_is_remaining and b_is_remaining) or (not a_is_remaining and not b_is_remaining) then
return indices[a[1]] < indices[b[1]]
elseif a_is_remaining then
return true
elseif b_is_remaining then
return false
end
end)
return options
end

for _, cmd in ipairs(root_options) do
vim.api.nvim_create_user_command(cmd[1], function(args)
local selection = process_input(args.name .. ' ' .. (args.fargs[1] or ''), true)
local same_level_commands = {}
for _, option in ipairs(selection) do
table.insert(same_level_commands, option[1])
end
-- only the first is THE selection
if selection[1] and type(selection[1][2]) == 'function' then
selection[1][2](same_level_commands, args)
end
end, {
nargs = '?',
range = true,
complete = function(last_cmd, input)
input = input or ''
input = input:gsub('^\'<,\'>','')
return options_of(process_input(input))
end
})
end
Loading

0 comments on commit 955a36c

Please sign in to comment.