Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,14 @@ vim.g.neominimap = {
enabled = true, ---@type boolean
},

viewport = {
-- Highlight the visible source range on the minimap.
-- Only supported when layout == "float".
enabled = false, ---@type boolean
priority = 5, ---@type integer
hl_group = "NeominimapViewport", ---@type string
},

--- Override the default window options
---@param opt vim.wo
---@param winid integer the window id of the source window, NOT the minimap window
Expand Down Expand Up @@ -989,6 +997,7 @@ Checkout the wiki page for more details. [wiki](https://github.com/Isrothy/neomi
| `NeominimapCursorLineNr` | To replace `CursorLineNr` in minimaps. |
| `NeominimapCursorLineSign` | To replace `CursorLineSign` in minimaps. |
| `NeominimapCursorLineFold` | To replace `CursorLineFold` in minimaps. |
| `NeominimapViewport` | Visible source range overlay on the minimap. (float only) |

### Highlight Groups of Diagnostic Annotations

Expand Down Expand Up @@ -1086,11 +1095,10 @@ Checkout the wiki page for more details. [wiki](https://github.com/Isrothy/neomi
Use [satellite.nvim](https://github.com/lewis6991/satellite.nvim),
[nvim-scrollview](https://github.com/dstein64/nvim-scrollview)
or other plugins.
- Display screen bounds like
[codewindow.nvim](https://github.com/gorbit99/codewindow.nvim).
For performance, this plugin creates a minimap buffer for each buffer.
Since a screen bound is a windowwise thing,
it's not impossible to display them by highlights.
- Display screen bounds in split layout.
Viewport overlay is supported in float layout.
For split layout, the minimap is shared across all windows in a tab,
making window-wise viewport display infeasible.

## Limitations

Expand Down
22 changes: 16 additions & 6 deletions doc/neominimap.nvim.txt
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,15 @@ Default configuration ~
-- Consider folds when rendering the minimap
enabled = true, ---@type boolean
},


viewport = {
-- Highlight the visible source range on the minimap.
-- Only supported when layout == "float".
enabled = false, ---@type boolean
priority = 5, ---@type integer
hl_group = "NeominimapViewport", ---@type string
},

--- Override the default window options
---@param opt vim.wo
---@param winid integer the window id of the source window, NOT the minimap window
Expand Down Expand Up @@ -927,6 +935,9 @@ HIGHLIGHT GROUPS OF NEOMINIMAP WINDOWS ~
NeominimapCursorLineSign To replace CursorLineSign in minimaps.

NeominimapCursorLineFold To replace CursorLineFold in minimaps.

NeominimapViewport Visible source range overlay on the
minimap. (float only)
-----------------------------------------------------------------------

HIGHLIGHT GROUPS OF DIAGNOSTIC ANNOTATIONS ~
Expand Down Expand Up @@ -1021,11 +1032,10 @@ NON-GOALS *neominimap.nvim-neominimap-non-goals*
Use satellite.nvim <https://github.com/lewis6991/satellite.nvim>,
nvim-scrollview <https://github.com/dstein64/nvim-scrollview>
or other plugins.
- Display screen bounds like
codewindow.nvim <https://github.com/gorbit99/codewindow.nvim>.
For performance, this plugin creates a minimap buffer for each buffer.
Since a screen bound is a windowwise thing,
it’s not impossible to display them by highlights.
- Display screen bounds in split layout.
Viewport overlay is supported in float layout.
For split layout, the minimap is shared across all windows in a tab,
making window-wise viewport display infeasible.


LIMITATIONS *neominimap.nvim-neominimap-limitations*
Expand Down
6 changes: 6 additions & 0 deletions lua/neominimap/config/internal.lua
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,12 @@ local M = {
enabled = true, ---@type boolean
},

viewport = {
enabled = false, ---@type boolean
priority = 5, ---@type integer
hl_group = "NeominimapViewport", ---@type string
},

--- Override the default window options
---@param opt vim.wo
---@param winid integer the window id of the source window, NOT the minimap window
Expand Down
6 changes: 6 additions & 0 deletions lua/neominimap/config/meta.lua
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ local M = {}
---@field search? Neominimap.SearchConfig
---@field mark? Neominimap.MarkConfig
---@field fold? Neominimap.FoldConfig
---@field viewport? Neominimap.ViewportConfig
---@field winopt? fun(opt: vim.wo, winid: integer)
---@field bufopt? fun(opt: vim.bo, bufnr: integer)
---@field handler? Neominimap.Map.Handler[]
Expand Down Expand Up @@ -92,6 +93,11 @@ local M = {}
---@class (exact) Neominimap.FoldConfig
---@field enabled? boolean

---@class (exact) Neominimap.ViewportConfig
---@field enabled? boolean
---@field priority? integer
---@field hl_group? string

---@type Neominimap.UserConfig | fun():Neominimap.UserConfig | nil
vim.g.neominimap = vim.g.neominimap

Expand Down
5 changes: 5 additions & 0 deletions lua/neominimap/config/validator.lua
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,11 @@ M.validate_config = function(cfg)
fold = { cfg.fold, "table" },
["fold.enabled"] = { cfg.fold.enabled, "boolean" },

viewport = { cfg.viewport, "table" },
["viewport.enabled"] = { cfg.viewport.enabled, "boolean" },
["viewport.priority"] = { cfg.viewport.priority, "number" },
["viewport.hl_group"] = { cfg.viewport.hl_group, "string" },

winopt = { cfg.winopt, { "table", "function" } },
bufopt = { cfg.bufopt, { "table", "function" } },

Expand Down
22 changes: 13 additions & 9 deletions lua/neominimap/window/float/autocmds.lua
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,16 @@ M.on_win_closed = function(args)
return
end
local window_map = require("neominimap.window.float.window_map")
if window_map.is_minimap_window(winid) and not config.float.persist then
logger.log.trace("This is a minimap window. Close minimap for current window")
local var = require("neominimap.variables")
local swinid = window_map.get_parent_winid(winid)
if swinid then
var.w[swinid].enabled = false
if window_map.is_minimap_window(winid) then
logger.log.trace("This is a minimap window. Clearing viewport for %d", winid)
require("neominimap.window.viewport").clear(winid)
if not config.float.persist then
logger.log.trace("Close minimap for current window")
local var = require("neominimap.variables")
local swinid = window_map.get_parent_winid(winid)
if swinid then
var.w[swinid].enabled = false
end
end
end
vim.schedule(function()
Expand Down Expand Up @@ -149,9 +153,9 @@ M.on_minimap_buffer_text_changed = function(args)
local win_list = require("neominimap.util").get_attached_window(bufnr)
vim.schedule(function()
for _, winid in ipairs(win_list) do
logger.log.trace("Resetting cursor line for window %d.", winid)
require("neominimap.window.float.internal").reset_mwindow_cursor_line(winid)
logger.log.trace("Cursor line reset for window %d.", winid)
logger.log.trace("Handling minimap buffer text change for window %d.", winid)
require("neominimap.window.float.internal").on_minimap_buffer_text_changed(winid)
logger.log.trace("Minimap buffer text change handled for window %d.", winid)
end
end)
end
Expand Down
20 changes: 20 additions & 0 deletions lua/neominimap/window/float/internal.lua
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ M.close_minimap_window = function(winid)
logger.log.trace("Attempting to close minimap for window %d", winid)
if mwinid and api.nvim_win_is_valid(mwinid) then
logger.log.trace("Deleting minimap window %d", mwinid)
require("neominimap.window.viewport").clear(mwinid)
local util = require("neominimap.util")
util.noautocmd(api.nvim_win_close)(mwinid, true)
return mwinid
Expand Down Expand Up @@ -242,6 +243,8 @@ M.refresh_minimap_window = function(winid)

M.reset_mwindow_cursor_line(winid)

require("neominimap.window.viewport").refresh(winid, mwinid)

logger.log.trace("Minimap for window %d refreshed", winid)
return mwinid
end
Expand Down Expand Up @@ -296,6 +299,23 @@ M.reset_mwindow_cursor_line = function(winid)
return true
end

--- Called when minimap buffer text is updated.
--- Refreshes cursor line and viewport overlay without reconfiguring the window.
---@param winid integer
M.on_minimap_buffer_text_changed = function(winid)
local logger = require("neominimap.logger")
local window_map = require("neominimap.window.float.window_map")
logger.log.trace("Handling minimap buffer text change for window %d", winid)
local mwinid = window_map.get_minimap_winid(winid)
if not mwinid or not api.nvim_win_is_valid(mwinid) then
logger.log.trace("Minimap window is not valid for %d", winid)
return
end
require("neominimap.window.util").sync_to_source(winid, mwinid)
require("neominimap.window.viewport").refresh(winid, mwinid)
logger.log.trace("Minimap buffer text change handled for window %d", winid)
end

---@param mwinid integer
---@return boolean
M.reset_parent_window_cursor_line = function(mwinid)
Expand Down
1 change: 1 addition & 0 deletions lua/neominimap/window/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ api.nvim_set_hl(0, "NeominimapCursorLine", { link = "CursorLine", default = true
api.nvim_set_hl(0, "NeominimapCursorLineSign", { link = "CursorLineSign", default = true })
api.nvim_set_hl(0, "NeominimapCursorLineNr", { link = "CursorLineSign", default = true })
api.nvim_set_hl(0, "NeominimapCursorLineFold", { link = "CursorLineSign", default = true })
api.nvim_set_hl(0, "NeominimapViewport", { link = "Visual", default = true })

---@class Neominimap.Window
---@field create_autocmds fun(group: string | integer)
Expand Down
102 changes: 102 additions & 0 deletions lua/neominimap/window/viewport.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
local M = {}
local api = vim.api
local config = require("neominimap.config")
local coord = require("neominimap.map.coord")
local fold = require("neominimap.map.fold")
local logger = require("neominimap.logger")

---@class Neominimap.Viewport.Cache
---@field w0 integer
---@field w_dollar integer
---@field sbufnr integer
---@field mbufnr integer
---@field line_count integer

---@type table<integer, Neominimap.Viewport.Cache>
local cache = {}

---@type table<integer, integer>
local match_id_map = {}

---@param swinid integer
---@param mwinid integer
M.refresh = function(swinid, mwinid)
if not config.viewport.enabled then
return
end
if not api.nvim_win_is_valid(swinid) or not api.nvim_win_is_valid(mwinid) then
return
end

local mbufnr = api.nvim_win_get_buf(mwinid)
if not mbufnr or not api.nvim_buf_is_valid(mbufnr) then
return
end

local sbufnr = api.nvim_win_get_buf(swinid)
if not sbufnr or not api.nvim_buf_is_valid(sbufnr) then
return
end

local w0 = vim.fn.line("w0", swinid)
local w_dollar = vim.fn.line("w$", swinid)
if w0 == 0 or w_dollar == 0 then
return
end

local cached_folds = fold.get_cached_folds(sbufnr) or {}
local start_row, end_row = fold.get_visible_range(cached_folds, w0, w_dollar)
local m_start_row = coord.codepoint_to_mcodepoint(start_row, 1)
local m_end_row = coord.codepoint_to_mcodepoint(end_row, 1)
local line_count = api.nvim_buf_line_count(mbufnr)

local cached = cache[mwinid]
if
cached
and cached.w0 == w0
and cached.w_dollar == w_dollar
and cached.sbufnr == sbufnr
and cached.mbufnr == mbufnr
and cached.line_count == line_count
then
return
end

logger.log.trace("Refreshing viewport overlay for minimap window %d", mwinid)

local old_match_id = match_id_map[mwinid]
if old_match_id then
pcall(vim.fn.matchdelete, old_match_id, mwinid)
end

if m_start_row > line_count then
cache[mwinid] = { w0 = w0, w_dollar = w_dollar, sbufnr = sbufnr, mbufnr = mbufnr, line_count = line_count }
logger.log.trace("Viewport overlay cleared (out of range) for minimap window %d", mwinid)
return
end

local end_line = math.min(m_end_row, line_count)
local pattern = string.format([[\m\%%>%dl\%%<%dl.*]], m_start_row - 1, end_line + 1)
local ok, match_id =
pcall(vim.fn.matchadd, config.viewport.hl_group, pattern, config.viewport.priority, -1, { window = mwinid })
if ok and match_id ~= -1 then
match_id_map[mwinid] = match_id
else
logger.log.warn("Failed to add viewport match for window %d: %s", mwinid, tostring(match_id))
end

cache[mwinid] = { w0 = w0, w_dollar = w_dollar, sbufnr = sbufnr, mbufnr = mbufnr, line_count = line_count }
logger.log.trace("Viewport overlay refreshed for minimap window %d", mwinid)
end

---@param mwinid integer
M.clear = function(mwinid)
local match_id = match_id_map[mwinid]
if match_id then
pcall(vim.fn.matchdelete, match_id, mwinid)
end
match_id_map[mwinid] = nil
cache[mwinid] = nil
end

return M