-- lua/nuwiki/folding.lua — `foldexpr` fallback when the LSP isn't -- attached. Same heading-block model as the server-side `foldingRange` -- provider in `crates/nuwiki-lsp/src/folding.rs`, but driven from a -- 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 --- 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 return M