2026-05-11 21:57:05 +00:00
|
|
|
-- lua/nuwiki/folding.lua — `foldexpr` fallback when the LSP isn't
|
|
|
|
|
-- attached. Same heading-block model as the server-side `foldingRange`
|
2026-06-24 12:57:15 +00:00
|
|
|
-- provider in the nuwiki-rs LSP server, but driven from a
|
2026-05-11 21:57:05 +00:00
|
|
|
-- pure regex over `getline()` so it works without a running server.
|
|
|
|
|
--
|
|
|
|
|
-- Activated by `setlocal foldmethod=expr foldexpr=v:lua.require'nuwiki.folding'.expr()`
|
|
|
|
|
-- in `ftplugin/nuwiki.vim` when `folding` is enabled in the user config.
|
|
|
|
|
|
|
|
|
|
local M = {}
|
|
|
|
|
|
|
|
|
|
--- Heading level from one line of source. Returns 0 for non-headings.
|
|
|
|
|
local function heading_level(line)
|
|
|
|
|
local lead = line:match('^%s*(=+)%s')
|
|
|
|
|
if not lead then return 0 end
|
|
|
|
|
local lvl = #lead
|
|
|
|
|
if lvl > 6 then return 0 end
|
|
|
|
|
-- Require a matching trailing run of `=`s.
|
|
|
|
|
local trail = line:match('%s(=+)%s*$')
|
|
|
|
|
if not trail or #trail ~= lvl then return 0 end
|
|
|
|
|
return lvl
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
--- `foldexpr` body. Returns a fold-marker string per `:help fold-expr`.
|
|
|
|
|
function M.expr()
|
|
|
|
|
local lnum = vim.v.lnum
|
|
|
|
|
local line = vim.fn.getline(lnum)
|
|
|
|
|
local lvl = heading_level(line)
|
|
|
|
|
if lvl > 0 then
|
|
|
|
|
return '>' .. lvl
|
|
|
|
|
end
|
|
|
|
|
return '='
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
--- Build a heading-only `foldtext` showing the heading title trimmed.
|
|
|
|
|
function M.foldtext()
|
|
|
|
|
local fs = vim.v.foldstart
|
|
|
|
|
local line = vim.fn.getline(fs):gsub('=', ''):gsub('^%s+', ''):gsub('%s+$', '')
|
|
|
|
|
local count = vim.v.foldend - vim.v.foldstart + 1
|
|
|
|
|
return string.format('▸ %s … (%d lines)', line, count)
|
|
|
|
|
end
|
|
|
|
|
|
2026-05-12 01:22:35 +00:00
|
|
|
--- LSP-backed foldexpr with a safe fallback.
|
|
|
|
|
---
|
|
|
|
|
--- Wraps `vim.lsp.foldexpr` so a missing client / un-attached buffer /
|
|
|
|
|
--- transient evaluation error falls back to the regex implementation
|
|
|
|
|
--- instead of throwing E5108 for every line in the buffer.
|
|
|
|
|
function M.lsp_expr()
|
|
|
|
|
if not (vim.lsp and vim.lsp.foldexpr) then
|
|
|
|
|
return M.expr()
|
|
|
|
|
end
|
|
|
|
|
local ok, val = pcall(vim.lsp.foldexpr)
|
|
|
|
|
if ok then
|
|
|
|
|
return val
|
|
|
|
|
end
|
|
|
|
|
return M.expr()
|
|
|
|
|
end
|
|
|
|
|
|
2026-05-11 21:57:05 +00:00
|
|
|
return M
|