Regenerate nvim config
This commit is contained in:
196
config/neovim/store/lazy-plugins/nvim-ufo/lua/ufo.lua
Normal file
196
config/neovim/store/lazy-plugins/nvim-ufo/lua/ufo.lua
Normal file
@ -0,0 +1,196 @@
|
||||
local api = vim.api
|
||||
|
||||
---Export methods to the users, `require('ufo').method(...)`
|
||||
---@class Ufo
|
||||
local M = {}
|
||||
|
||||
---Peek the folded line under cursor, any motions in the normal window will close the floating window.
|
||||
---@param enter? boolean enter the floating window, default value is false
|
||||
---@param nextLineIncluded? boolean include the next line of last line of closed fold, default is true
|
||||
---@return number? winid return the winid if successful, otherwise return nil
|
||||
function M.peekFoldedLinesUnderCursor(enter, nextLineIncluded)
|
||||
return require('ufo.preview'):peekFoldedLinesUnderCursor(enter, nextLineIncluded)
|
||||
end
|
||||
|
||||
---Go to previous start fold. Neovim can't go to previous start fold directly, it's an extra motion.
|
||||
function M.goPreviousStartFold()
|
||||
require('ufo.action').goPreviousStartFold()
|
||||
end
|
||||
|
||||
---Go to previous closed fold. It's an extra motion.
|
||||
function M.goPreviousClosedFold()
|
||||
require('ufo.action').goPreviousClosedFold()
|
||||
end
|
||||
|
||||
---Go to next closed fold. It's an extra motion.
|
||||
function M.goNextClosedFold()
|
||||
return require('ufo.action').goNextClosedFold()
|
||||
end
|
||||
|
||||
---Close all folds but keep foldlevel
|
||||
function M.closeAllFolds()
|
||||
return M.closeFoldsWith(0)
|
||||
end
|
||||
|
||||
---Open all folds but keep foldlevel
|
||||
function M.openAllFolds()
|
||||
return require('ufo.action').openFoldsExceptKinds()
|
||||
end
|
||||
|
||||
---Close the folds with a higher level,
|
||||
---Like execute `set foldlevel=level` but keep foldlevel
|
||||
---@param level? number fold level, `v:count` by default
|
||||
function M.closeFoldsWith(level)
|
||||
return require('ufo.action').closeFolds(level or vim.v.count)
|
||||
end
|
||||
|
||||
---Open folds except specified kinds
|
||||
---@param kinds? UfoFoldingRangeKind[] kind in ranges, get default kinds from `config.close_fold_kinds_for_ft`
|
||||
function M.openFoldsExceptKinds(kinds)
|
||||
if not kinds then
|
||||
local c = require('ufo.config')
|
||||
if c.close_fold_kinds and not vim.tbl_isempty(c.close_fold_kinds) then
|
||||
kinds = c.close_fold_kinds
|
||||
else
|
||||
kinds = c.close_fold_kinds_for_ft[vim.bo.ft] or c.close_fold_kinds_for_ft.default
|
||||
end
|
||||
end
|
||||
return require('ufo.action').openFoldsExceptKinds(kinds)
|
||||
end
|
||||
|
||||
---Inspect ufo information by bufnr
|
||||
---@param bufnr? number buffer number, current buffer by default
|
||||
function M.inspect(bufnr)
|
||||
bufnr = bufnr or api.nvim_get_current_buf()
|
||||
local msg = require('ufo.main').inspectBuf(bufnr)
|
||||
if not msg then
|
||||
vim.notify(('Buffer %d has not been attached.'):format(bufnr), vim.log.levels.ERROR)
|
||||
else
|
||||
vim.notify(table.concat(msg, '\n'), vim.log.levels.INFO)
|
||||
end
|
||||
end
|
||||
|
||||
---Enable ufo
|
||||
function M.enable()
|
||||
require('ufo.main').enable()
|
||||
end
|
||||
|
||||
---Disable ufo
|
||||
function M.disable()
|
||||
require('ufo.main').disable()
|
||||
end
|
||||
|
||||
---Check whether the buffer has been attached
|
||||
---@param bufnr? number buffer number, current buffer by default
|
||||
---@return boolean
|
||||
function M.hasAttached(bufnr)
|
||||
return require('ufo.main').hasAttached(bufnr)
|
||||
end
|
||||
|
||||
---Attach bufnr to enable all features
|
||||
---@param bufnr? number buffer number, current buffer by default
|
||||
function M.attach(bufnr)
|
||||
require('ufo.main').attach(bufnr)
|
||||
end
|
||||
|
||||
---Detach bufnr to disable all features
|
||||
---@param bufnr? number buffer number, current buffer by default
|
||||
function M.detach(bufnr)
|
||||
require('ufo.main').detach(bufnr)
|
||||
end
|
||||
|
||||
---Enable to get folds and update them at once
|
||||
---@param bufnr? number buffer number, current buffer by default
|
||||
---@return string|'start'|'pending'|'stop' status
|
||||
function M.enableFold(bufnr)
|
||||
return require('ufo.main').enableFold(bufnr)
|
||||
end
|
||||
|
||||
---Disable to get folds
|
||||
---@param bufnr? number buffer number, current buffer by default
|
||||
---@return string|'start'|'pending'|'stop' status
|
||||
function M.disableFold(bufnr)
|
||||
return require('ufo.main').disableFold(bufnr)
|
||||
end
|
||||
|
||||
---Get foldingRange from the ufo internal providers by name
|
||||
---@param bufnr number
|
||||
---@param providerName string|'lsp'|'treesitter'|'indent'
|
||||
---@return UfoFoldingRange[]|Promise
|
||||
function M.getFolds(bufnr, providerName)
|
||||
if type(bufnr) == 'string' and type(providerName) == 'number' then
|
||||
--TODO signature is changed (swap parameters), notify deprecated in next released
|
||||
---@deprecated
|
||||
---@diagnostic disable-next-line: cast-local-type
|
||||
bufnr, providerName = providerName, bufnr
|
||||
end
|
||||
local func = require('ufo.provider'):getFunction(providerName)
|
||||
return func(bufnr)
|
||||
end
|
||||
|
||||
---Apply foldingRange at once.
|
||||
---ufo always apply folds asynchronously, this function can apply folds synchronously.
|
||||
---Note: Get ranges from 'lsp' provider is asynchronous.
|
||||
---@param bufnr number
|
||||
---@param ranges UfoFoldingRange[]
|
||||
---@return number winid return the winid if successful, otherwise return -1
|
||||
function M.applyFolds(bufnr, ranges)
|
||||
vim.validate({bufnr = {bufnr, 'number', true}, ranges = {ranges, 'table'}})
|
||||
return require('ufo.fold').apply(bufnr, ranges, true)
|
||||
end
|
||||
|
||||
---Setup configuration and enable ufo
|
||||
---@param opts? UfoConfig
|
||||
function M.setup(opts)
|
||||
opts = opts or {}
|
||||
M._config = opts
|
||||
M.enable()
|
||||
end
|
||||
|
||||
---------------------------------------setFoldVirtTextHandler---------------------------------------
|
||||
|
||||
---@class UfoFoldVirtTextHandlerContext
|
||||
---@field bufnr number buffer for closed fold
|
||||
---@field winid number window for closed fold
|
||||
---@field text string text for the first line of closed fold
|
||||
---@field get_fold_virt_text fun(lnum: number): UfoExtmarkVirtTextChunk[] a function to get virtual text by lnum
|
||||
|
||||
---@class UfoExtmarkVirtTextChunk
|
||||
---@field [1] string text
|
||||
---@field [2] string|number highlight
|
||||
|
||||
---Set a fold virtual text handler for a buffer, will override global handler if it's existed.
|
||||
---Ufo actually uses a virtual text with \`nvim_buf_set_extmark\` to overlap the first line of closed fold
|
||||
---run \`:h nvim_buf_set_extmark | call search('virt_text')\` for detail.
|
||||
---Return `{}` will not render folded line but only keep a extmark for providers.
|
||||
---@diagnostic disable: undefined-doc-param
|
||||
---Detail for handler function:
|
||||
---@param virtText UfoExtmarkVirtTextChunk[] contained text and highlight captured by Ufo, export to caller
|
||||
---@param lnum number first line of closed fold, like \`v:foldstart\` in foldtext()
|
||||
---@param endLnum number last line of closed fold, like \`v:foldend\` in foldtext()
|
||||
---@param width number text area width, exclude foldcolumn, signcolumn and numberwidth
|
||||
---@param truncate fun(str: string, width: number): string truncate the str to become specific width,
|
||||
---return width of string is equal or less than width (2nd argument).
|
||||
---For example: '1': 1 cell, '你': 2 cells, '2': 1 cell, '好': 2 cells
|
||||
---truncate('1你2好', 1) return '1'
|
||||
---truncate('1你2好', 2) return '1'
|
||||
---truncate('1你2好', 3) return '1你'
|
||||
---truncate('1你2好', 4) return '1你2'
|
||||
---truncate('1你2好', 5) return '1你2'
|
||||
---truncate('1你2好', 6) return '1你2好'
|
||||
---truncate('1你2好', 7) return '1你2好'
|
||||
---@param ctx UfoFoldVirtTextHandlerContext the context used by ufo, export to caller
|
||||
|
||||
---@alias UfoFoldVirtTextHandler fun(virtText: UfoExtmarkVirtTextChunk[], lnum: number, endLnum: number, width: number, truncate: fun(str: string, width: number), ctx: UfoFoldVirtTextHandlerContext): UfoExtmarkVirtTextChunk[]
|
||||
|
||||
---@param bufnr number
|
||||
---@param handler UfoFoldVirtTextHandler
|
||||
function M.setFoldVirtTextHandler(bufnr, handler)
|
||||
vim.validate({bufnr = {bufnr, 'number', true}, handler = {handler, 'function'}})
|
||||
require('ufo.decorator'):setVirtTextHandler(bufnr, handler)
|
||||
end
|
||||
|
||||
---@diagnostic disable: undefined-doc-param
|
||||
---------------------------------------setFoldVirtTextHandler---------------------------------------
|
||||
|
||||
return M
|
||||
196
config/neovim/store/lazy-plugins/nvim-ufo/lua/ufo/action.lua
Normal file
196
config/neovim/store/lazy-plugins/nvim-ufo/lua/ufo/action.lua
Normal file
@ -0,0 +1,196 @@
|
||||
local api = vim.api
|
||||
local cmd = vim.cmd
|
||||
local fn = vim.fn
|
||||
|
||||
local utils = require('ufo.utils')
|
||||
local fold = require('ufo.fold')
|
||||
|
||||
local M = {}
|
||||
|
||||
function M.goPreviousStartFold()
|
||||
local function getCurLnum()
|
||||
return api.nvim_win_get_cursor(0)[1]
|
||||
end
|
||||
|
||||
local cnt = vim.v.count1
|
||||
local view = utils.saveView(0)
|
||||
local curLnum = getCurLnum()
|
||||
cmd('norm! m`')
|
||||
local previousLnum
|
||||
local previousLnumList = {}
|
||||
while cnt > 0 do
|
||||
cmd([[keepj norm! zk]])
|
||||
local tLnum = getCurLnum()
|
||||
cmd([[keepj norm! [z]])
|
||||
if tLnum == getCurLnum() then
|
||||
local foldStartLnum = utils.foldClosed(0, tLnum)
|
||||
if foldStartLnum > 0 then
|
||||
cmd(('keepj norm! %dgg'):format(foldStartLnum))
|
||||
end
|
||||
end
|
||||
local nextLnum = getCurLnum()
|
||||
while curLnum > nextLnum do
|
||||
tLnum = nextLnum
|
||||
table.insert(previousLnumList, nextLnum)
|
||||
cmd([[keepj norm! zj]])
|
||||
nextLnum = getCurLnum()
|
||||
if nextLnum == tLnum then
|
||||
break
|
||||
end
|
||||
end
|
||||
if #previousLnumList == 0 then
|
||||
break
|
||||
end
|
||||
if #previousLnumList < cnt then
|
||||
cnt = cnt - #previousLnumList
|
||||
curLnum = previousLnumList[1]
|
||||
previousLnum = curLnum
|
||||
cmd(('keepj norm! %dgg'):format(curLnum))
|
||||
previousLnumList = {}
|
||||
else
|
||||
while cnt > 0 do
|
||||
previousLnum = table.remove(previousLnumList)
|
||||
cnt = cnt - 1
|
||||
end
|
||||
end
|
||||
end
|
||||
utils.restView(0, view)
|
||||
if previousLnum then
|
||||
cmd(('norm! %dgg_'):format(previousLnum))
|
||||
end
|
||||
end
|
||||
|
||||
function M.goPreviousClosedFold()
|
||||
local count = vim.v.count1
|
||||
local curLnum = api.nvim_win_get_cursor(0)[1]
|
||||
local cnt = 0
|
||||
local lnum
|
||||
for i = curLnum - 1, 1, -1 do
|
||||
if utils.foldClosed(0, i) == i then
|
||||
cnt = cnt + 1
|
||||
lnum = i
|
||||
if cnt == count then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if lnum then
|
||||
cmd('norm! m`')
|
||||
api.nvim_win_set_cursor(0, {lnum, 0})
|
||||
end
|
||||
end
|
||||
|
||||
function M.goNextClosedFold()
|
||||
local count = vim.v.count1
|
||||
local curLnum = api.nvim_win_get_cursor(0)[1]
|
||||
local lineCount = api.nvim_buf_line_count(0)
|
||||
local cnt = 0
|
||||
local lnum
|
||||
for i = curLnum + 1, lineCount do
|
||||
if utils.foldClosed(0, i) == i then
|
||||
cnt = cnt + 1
|
||||
lnum = i
|
||||
if cnt == count then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if lnum then
|
||||
cmd('norm! m`')
|
||||
api.nvim_win_set_cursor(0, {lnum, 0})
|
||||
end
|
||||
end
|
||||
|
||||
function M.closeFolds(level)
|
||||
cmd('silent! %foldclose!')
|
||||
local curBufnr = api.nvim_get_current_buf()
|
||||
local fb = fold.get(curBufnr)
|
||||
if not fb then
|
||||
return
|
||||
end
|
||||
for _, range in ipairs(fb.foldRanges) do
|
||||
fb:closeFold(range.startLine + 1, range.endLine + 1)
|
||||
end
|
||||
if level == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local lineCount = fb:lineCount()
|
||||
local stack = {}
|
||||
local lastLevel = 0
|
||||
local lastEndLnum = -1
|
||||
local lnum = 1
|
||||
while lnum <= lineCount do
|
||||
local l = fn.foldlevel(lnum)
|
||||
if lastLevel < l or l > 0 and lnum == lastEndLnum + 1 then
|
||||
local endLnum = utils.foldClosedEnd(0, lnum)
|
||||
table.insert(stack, {endLnum, false})
|
||||
if l <= level then
|
||||
local cmds = {}
|
||||
for i = #stack, 1, -1 do
|
||||
local opened = stack[i][2]
|
||||
if opened then
|
||||
break
|
||||
end
|
||||
stack[i][2] = true
|
||||
table.insert(cmds, lnum .. 'foldopen')
|
||||
fb:openFold(lnum)
|
||||
end
|
||||
if #cmds > 0 then
|
||||
cmd(table.concat(cmds, '|'))
|
||||
-- A line may contain multiple folds, make sure lnum is opened.
|
||||
while lnum == utils.foldClosed(0, lnum) do
|
||||
cmd(lnum .. 'foldopen')
|
||||
end
|
||||
end
|
||||
else
|
||||
--TODO
|
||||
-- (#184)
|
||||
--`%foldclose!` doesn't close all folds for window
|
||||
--endLnum may return -1, look like upstream issue.
|
||||
if lnum < endLnum then
|
||||
lnum = endLnum
|
||||
end
|
||||
end
|
||||
end
|
||||
lastLevel = l
|
||||
lnum = lnum + 1
|
||||
while #stack > 0 do
|
||||
local endLnum = stack[#stack][1]
|
||||
if lnum <= endLnum then
|
||||
break
|
||||
end
|
||||
table.remove(stack)
|
||||
lastEndLnum = math.max(lastEndLnum, endLnum)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M.openFoldsExceptKinds(kinds)
|
||||
cmd('silent! %foldopen!')
|
||||
local curBufnr = api.nvim_get_current_buf()
|
||||
local fb = fold.get(curBufnr)
|
||||
if not fb or type(kinds) ~= 'table' or #kinds == 0 then
|
||||
return
|
||||
end
|
||||
local res = {}
|
||||
for _, range in ipairs(fb.foldRanges) do
|
||||
if range.kind and vim.tbl_contains(kinds, range.kind) then
|
||||
local startLnum, endLnum = range.startLine + 1, range.endLine + 1
|
||||
fb:closeFold(startLnum, endLnum)
|
||||
table.insert(res, {startLnum, endLnum})
|
||||
end
|
||||
end
|
||||
table.sort(res, function(a, b)
|
||||
return a[1] == b[1] and a[2] < b[2] or a[1] > b[1]
|
||||
end)
|
||||
local cmds = {}
|
||||
for _, range in ipairs(res) do
|
||||
table.insert(cmds, range[1] .. 'foldclose')
|
||||
end
|
||||
if #cmds > 0 then
|
||||
cmd(table.concat(cmds, '|'))
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
113
config/neovim/store/lazy-plugins/nvim-ufo/lua/ufo/bufmanager.lua
Normal file
113
config/neovim/store/lazy-plugins/nvim-ufo/lua/ufo/bufmanager.lua
Normal file
@ -0,0 +1,113 @@
|
||||
local api = vim.api
|
||||
|
||||
local buffer = require('ufo.model.buffer')
|
||||
local event = require('ufo.lib.event')
|
||||
local disposable = require('ufo.lib.disposable')
|
||||
local promise = require('promise')
|
||||
local utils = require('ufo.utils')
|
||||
|
||||
---@class UfoBufferManager
|
||||
---@field initialized boolean
|
||||
---@field buffers UfoBuffer[]
|
||||
---@field disposables UfoDisposable[]
|
||||
local BufferManager = {}
|
||||
|
||||
local function attach(self, bufnr)
|
||||
if not self.buffers[bufnr] and not self.bufDetachSet[bufnr] then
|
||||
local buf = buffer:new(bufnr)
|
||||
self.buffers[bufnr] = buf
|
||||
if not buf:attach() then
|
||||
self.buffers[bufnr] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function BufferManager:initialize()
|
||||
if self.initialized then
|
||||
return self
|
||||
end
|
||||
self.initialized = true
|
||||
self.buffers = {}
|
||||
self.bufDetachSet = {}
|
||||
self.disposables = {}
|
||||
table.insert(self.disposables, disposable:create(function()
|
||||
for _, b in pairs(self.buffers) do
|
||||
b:dispose()
|
||||
end
|
||||
self.initialized = false
|
||||
self.buffers = {}
|
||||
self.bufDetachSet = {}
|
||||
end))
|
||||
---@diagnostic disable-next-line: unused-local
|
||||
event:on('BufWinEnter', function(bufnr, winid)
|
||||
attach(self, bufnr or api.nvim_get_current_buf())
|
||||
end, self.disposables)
|
||||
event:on('BufDetach', function(bufnr)
|
||||
local b = self.buffers[bufnr]
|
||||
if b then
|
||||
b:dispose()
|
||||
self.buffers[bufnr] = nil
|
||||
end
|
||||
end, self.disposables)
|
||||
event:on('BufTypeChanged', function(bufnr, new, old)
|
||||
local b = self.buffers[bufnr]
|
||||
if b and old ~= new then
|
||||
if new == 'terminal' or new == 'prompt' then
|
||||
event:emit('BufDetach', bufnr)
|
||||
else
|
||||
b.bt = new
|
||||
end
|
||||
end
|
||||
end, self.disposables)
|
||||
event:on('FileTypeChanged', function(bufnr, new, old)
|
||||
local b = self.buffers[bufnr]
|
||||
if b and old ~= new then
|
||||
b.ft = new
|
||||
end
|
||||
end, self.disposables)
|
||||
event:on('SyntaxChanged', function(bufnr, new, old)
|
||||
local b = self.buffers[bufnr]
|
||||
if b and old ~= new then
|
||||
b._syntax = new
|
||||
end
|
||||
end, self.disposables)
|
||||
|
||||
for _, winid in ipairs(api.nvim_tabpage_list_wins(0)) do
|
||||
local bufnr = api.nvim_win_get_buf(winid)
|
||||
if utils.isBufLoaded(bufnr) then
|
||||
attach(self, bufnr)
|
||||
else
|
||||
-- the first buffer is unloaded while firing `BufWinEnter`
|
||||
promise.resolve():thenCall(function()
|
||||
if utils.isBufLoaded(bufnr) then
|
||||
attach(self, bufnr)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
---@param bufnr number
|
||||
---@return UfoBuffer
|
||||
function BufferManager:get(bufnr)
|
||||
return self.buffers[bufnr]
|
||||
end
|
||||
|
||||
function BufferManager:dispose()
|
||||
disposable.disposeAll(self.disposables)
|
||||
self.disposables = {}
|
||||
end
|
||||
|
||||
function BufferManager:attach(bufnr)
|
||||
self.bufDetachSet[bufnr] = nil
|
||||
attach(self, bufnr)
|
||||
end
|
||||
|
||||
function BufferManager:detach(bufnr)
|
||||
self.bufDetachSet[bufnr] = true
|
||||
event:emit('BufDetach', bufnr)
|
||||
end
|
||||
|
||||
return BufferManager
|
||||
88
config/neovim/store/lazy-plugins/nvim-ufo/lua/ufo/config.lua
Normal file
88
config/neovim/store/lazy-plugins/nvim-ufo/lua/ufo/config.lua
Normal file
@ -0,0 +1,88 @@
|
||||
---@class UfoConfig
|
||||
---@field provider_selector? function
|
||||
---@field open_fold_hl_timeout number
|
||||
---@field close_fold_kinds_for_ft table<string, UfoFoldingRangeKind[]>
|
||||
---@field fold_virt_text_handler? UfoFoldVirtTextHandler A global virtual text handler, reference to `ufo.setFoldVirtTextHandler`
|
||||
---@field enable_get_fold_virt_text boolean
|
||||
---@field preview table
|
||||
local def = {
|
||||
open_fold_hl_timeout = 400,
|
||||
provider_selector = nil,
|
||||
close_fold_kinds_for_ft = {default = {}},
|
||||
fold_virt_text_handler = nil,
|
||||
enable_get_fold_virt_text = false,
|
||||
preview = {
|
||||
win_config = {
|
||||
border = 'rounded',
|
||||
winblend = 12,
|
||||
winhighlight = 'Normal:Normal',
|
||||
maxheight = 20
|
||||
},
|
||||
mappings = {
|
||||
scrollB = '',
|
||||
scrollF = '',
|
||||
scrollU = '',
|
||||
scrollD = '',
|
||||
scrollE = '<C-E>',
|
||||
scrollY = '<C-Y>',
|
||||
jumpTop = '',
|
||||
jumpBot = '',
|
||||
close = 'q',
|
||||
switch = '<Tab>',
|
||||
trace = '<CR>'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
---@type UfoConfig
|
||||
local Config = {}
|
||||
|
||||
|
||||
---@alias UfoProviderEnum
|
||||
---| 'lsp'
|
||||
---| 'treesitter'
|
||||
---| 'indent'
|
||||
|
||||
---
|
||||
---@param bufnr number
|
||||
---@param filetype string file type
|
||||
---@param buftype string buffer type
|
||||
---@return UfoProviderEnum|string[]|function|nil
|
||||
---return a string type use ufo providers
|
||||
---return a string in a table like a string type
|
||||
---return empty string '' will disable any providers
|
||||
---return `nil` will use default value {'lsp', 'indent'}
|
||||
---@diagnostic disable-next-line: unused-function, unused-local
|
||||
function Config.provider_selector(bufnr, filetype, buftype) end
|
||||
|
||||
local function init()
|
||||
local ufo = require('ufo')
|
||||
---@type UfoConfig
|
||||
Config = vim.tbl_deep_extend('keep', ufo._config or {}, def)
|
||||
vim.validate({
|
||||
open_fold_hl_timeout = {Config.open_fold_hl_timeout, 'number'},
|
||||
provider_selector = {Config.provider_selector, 'function', true},
|
||||
close_fold_kinds_for_ft = {Config.close_fold_kinds_for_ft, 'table'},
|
||||
fold_virt_text_handler = {Config.fold_virt_text_handler, 'function', true},
|
||||
preview_mappings = {Config.preview.mappings, 'table'}
|
||||
})
|
||||
|
||||
local preview = Config.preview
|
||||
for msg, key in pairs(preview.mappings) do
|
||||
if key == '' then
|
||||
preview.mappings[msg] = nil
|
||||
end
|
||||
end
|
||||
if Config.close_fold_kinds and not vim.tbl_isempty(Config.close_fold_kinds) then
|
||||
vim.notify('Option `close_fold_kinds` in `nvim-ufo` is deprecated, use `close_fold_kinds_for_ft` instead.',
|
||||
vim.log.levels.WARN)
|
||||
if not Config.close_fold_kinds_for_ft.default then
|
||||
Config.close_fold_kinds_for_ft.default = Config.close_fold_kinds
|
||||
end
|
||||
end
|
||||
ufo._config = nil
|
||||
end
|
||||
|
||||
init()
|
||||
|
||||
return Config
|
||||
390
config/neovim/store/lazy-plugins/nvim-ufo/lua/ufo/decorator.lua
Normal file
390
config/neovim/store/lazy-plugins/nvim-ufo/lua/ufo/decorator.lua
Normal file
@ -0,0 +1,390 @@
|
||||
local api = vim.api
|
||||
local fn = vim.fn
|
||||
local cmd = vim.cmd
|
||||
|
||||
local utils = require('ufo.utils')
|
||||
local config = require('ufo.config')
|
||||
local log = require('ufo.lib.log')
|
||||
local disposable = require('ufo.lib.disposable')
|
||||
local event = require('ufo.lib.event')
|
||||
|
||||
local window = require('ufo.model.window')
|
||||
local fold = require('ufo.fold')
|
||||
local render = require('ufo.render')
|
||||
|
||||
---@class UfoDecorator
|
||||
---@field initialized boolean
|
||||
---@field ns number
|
||||
---@field hlNs number
|
||||
---@field virtTextHandler? UfoFoldVirtTextHandler[]
|
||||
---@field enableFoldEndVirtText boolean
|
||||
---@field openFoldHlTimeout number
|
||||
---@field openFoldHlEnabled boolean
|
||||
---@field curWinid number
|
||||
---@field lastWinid number
|
||||
---@field virtTextHandlers table<number, function>
|
||||
---@field winSessions table<number, UfoWindow>
|
||||
---@field disposables UfoDisposable[]
|
||||
local Decorator = {}
|
||||
|
||||
local collection
|
||||
local bufnrSet
|
||||
local namespaces
|
||||
local handlerErrorMsg
|
||||
|
||||
---@diagnostic disable-next-line: unused-local
|
||||
local function onStart(name, tick)
|
||||
collection = {}
|
||||
bufnrSet = {}
|
||||
namespaces = {}
|
||||
end
|
||||
|
||||
---@diagnostic disable-next-line: unused-local
|
||||
local function onWin(name, winid, bufnr, topRow, botRow)
|
||||
local fb = fold.get(bufnr)
|
||||
if bufnrSet[bufnr] or not fb or fb.foldedLineCount == 0 and not vim.wo[winid].foldenable then
|
||||
collection[winid] = nil
|
||||
return false
|
||||
end
|
||||
local self = Decorator
|
||||
local wses = self.winSessions[winid]
|
||||
wses:onWin(bufnr, fb)
|
||||
collection[winid] = {
|
||||
winid = winid,
|
||||
bufnr = bufnr,
|
||||
rows = {}
|
||||
}
|
||||
bufnrSet[bufnr] = winid
|
||||
end
|
||||
|
||||
---@diagnostic disable-next-line: unused-local
|
||||
local function onLine(name, winid, bufnr, row)
|
||||
table.insert(collection[winid].rows, row)
|
||||
end
|
||||
|
||||
---@diagnostic disable-next-line: unused-local
|
||||
local function onEnd(name, tick)
|
||||
local needRedraw = false
|
||||
local self = Decorator
|
||||
self.curWinid = api.nvim_get_current_win()
|
||||
for winid, data in pairs(collection or {}) do
|
||||
if #data.rows > 0 then
|
||||
local wses = self.winSessions[winid]
|
||||
local fb = wses.foldbuffer
|
||||
local foldedPairs = wses.foldedPairs
|
||||
if self.curWinid == winid and not next(foldedPairs) then
|
||||
foldedPairs = self:computeFoldedPairs(data.rows)
|
||||
end
|
||||
local shared
|
||||
for _, row in ipairs(data.rows) do
|
||||
local lnum = row + 1
|
||||
if not foldedPairs[lnum] and fb:lineIsClosed(lnum) then
|
||||
if shared == nil then
|
||||
local _, winids = utils.getWinByBuf(fb.bufnr)
|
||||
shared = winids ~= nil
|
||||
end
|
||||
self:highlightOpenFold(fb, winid, lnum, shared)
|
||||
local didOpen = fb:openFold(lnum)
|
||||
if not shared then
|
||||
needRedraw = didOpen or needRedraw
|
||||
end
|
||||
end
|
||||
end
|
||||
local cursor = wses:cursor()
|
||||
local curLnum = cursor[1]
|
||||
if needRedraw then
|
||||
fb:syncFoldedLines(winid)
|
||||
end
|
||||
local curFoldStart, curFoldEnd = 0, 0
|
||||
for fs, fe in pairs(foldedPairs) do
|
||||
local _, didClose = self:getVirtTextAndCloseFold(winid, fs, fe)
|
||||
if not utils.has10() then
|
||||
needRedraw = needRedraw or didClose
|
||||
end
|
||||
if curFoldStart == 0 and fs <= curLnum and curLnum <= fe then
|
||||
curFoldStart, curFoldEnd = fs, fe
|
||||
end
|
||||
end
|
||||
if not needRedraw then
|
||||
local lastCurLnum = wses.lastCurLnum
|
||||
local lastCurFoldStart, lastCurFoldEnd = wses.lastCurFoldStart, wses.lastCurFoldEnd
|
||||
if lastCurFoldStart ~= curFoldStart and
|
||||
lastCurFoldStart < lastCurLnum and lastCurLnum <= lastCurFoldEnd and
|
||||
lastCurFoldStart < curLnum and curLnum <= lastCurFoldEnd then
|
||||
log.debug('Curosr under the stale fold range, should open fold.')
|
||||
needRedraw = fb:openFold(lastCurFoldStart) or needRedraw
|
||||
end
|
||||
end
|
||||
local didHighlight = false
|
||||
if curLnum == curFoldStart then
|
||||
didHighlight = wses:setCursorFoldedLineHighlight()
|
||||
else
|
||||
didHighlight = wses:clearCursorFoldedLineHighlight()
|
||||
end
|
||||
needRedraw = needRedraw or didHighlight
|
||||
wses.lastCurFoldStart, wses.lastCurFoldEnd = curFoldStart, curFoldEnd
|
||||
wses.lastCurLnum = curLnum
|
||||
end
|
||||
end
|
||||
if needRedraw then
|
||||
log.debug('Need redraw.')
|
||||
cmd('redraw')
|
||||
end
|
||||
self.lastWinid = self.curWinid
|
||||
end
|
||||
|
||||
local function silentOnEnd(...)
|
||||
local ok, msg = pcall(onEnd, ...)
|
||||
if not ok and (type(msg) ~= 'string' or not msg:match('Keyboard interrupt\n')) then
|
||||
error(msg, 0)
|
||||
end
|
||||
end
|
||||
|
||||
function Decorator:resetCurosrFoldedLineHighlightByBuf(bufnr)
|
||||
-- TODO
|
||||
-- exit cmd window will throw E315: ml_get: invalid lnum: 1
|
||||
if api.nvim_buf_line_count(bufnr) == 0 then
|
||||
return
|
||||
end
|
||||
local id, winids = utils.getWinByBuf(bufnr)
|
||||
if id == -1 then
|
||||
return
|
||||
end
|
||||
for _, winid in ipairs(winids or {id}) do
|
||||
local wses = self.winSessions[winid]
|
||||
wses:clearCursorFoldedLineHighlight()
|
||||
end
|
||||
end
|
||||
|
||||
function Decorator:highlightOpenFold(fb, winid, lnum, shared)
|
||||
if self.openFoldHlEnabled and winid == self.lastWinid and api.nvim_get_mode().mode ~= 'c' then
|
||||
local endLnum
|
||||
if not shared then
|
||||
local fl = fb:foldedLine(lnum)
|
||||
local _
|
||||
_, endLnum = fl:range()
|
||||
if endLnum == 0 then
|
||||
return
|
||||
end
|
||||
else
|
||||
endLnum = utils.foldClosedEnd(winid, lnum)
|
||||
if endLnum < 0 then
|
||||
return
|
||||
end
|
||||
end
|
||||
render.highlightLinesWithTimeout(shared and winid or fb.bufnr, 'UfoFoldedBg', self.hlNs,
|
||||
lnum, endLnum, self.openFoldHlTimeout, shared)
|
||||
end
|
||||
end
|
||||
|
||||
function Decorator:computeFoldedPairs(rows)
|
||||
local lastRow = rows[1]
|
||||
local res = {}
|
||||
for i = 2, #rows do
|
||||
local lnum = lastRow + 1
|
||||
local curRow = rows[i]
|
||||
if curRow > lnum and utils.foldClosed(0, lnum) == lnum then
|
||||
res[lnum] = curRow
|
||||
end
|
||||
lastRow = curRow
|
||||
end
|
||||
|
||||
local lnum = lastRow + 1
|
||||
if utils.foldClosed(0, lnum) == lnum then
|
||||
res[lnum] = utils.foldClosedEnd(0, lnum)
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
function Decorator:getVirtTextAndCloseFold(winid, lnum, endLnum, doRender)
|
||||
local didClose = false
|
||||
local wses = self.winSessions[winid]
|
||||
if not wses then
|
||||
return {}, didClose
|
||||
end
|
||||
local bufnr, fb = wses.bufnr, wses.foldbuffer
|
||||
if endLnum then
|
||||
wses.foldedPairs[lnum] = endLnum
|
||||
end
|
||||
local width = wses:textWidth()
|
||||
local ok, res = true, wses.foldedTextMaps[lnum]
|
||||
local fl = fb:foldedLine(lnum)
|
||||
local rendered = false
|
||||
if fl then
|
||||
if not res and not fl:widthChanged(width) then
|
||||
res = fl.virtText
|
||||
end
|
||||
rendered = fl:hasRendered()
|
||||
end
|
||||
if not res or not rendered then
|
||||
if not endLnum then
|
||||
endLnum = wses:foldEndLnum(lnum)
|
||||
end
|
||||
local text = fb:lines(lnum)[1]
|
||||
if not res then
|
||||
local handler = self:getVirtTextHandler(bufnr)
|
||||
local virtText
|
||||
local syntax = fb:syntax() ~= ''
|
||||
local concealLevel = wses:concealLevel()
|
||||
if not next(namespaces) then
|
||||
for _, ns in pairs(api.nvim_get_namespaces()) do
|
||||
if self.ns ~= ns then
|
||||
table.insert(namespaces, ns)
|
||||
end
|
||||
end
|
||||
end
|
||||
virtText = render.captureVirtText(bufnr, text, lnum, syntax, namespaces, concealLevel)
|
||||
local getFoldVirtText
|
||||
if self.enableGetFoldVirtText then
|
||||
getFoldVirtText = function(l)
|
||||
local t = type(l)
|
||||
assert(t == 'number', 'expected a number, got ' .. t)
|
||||
assert(lnum <= l and l <= endLnum,
|
||||
('expected lnum range from %d to %d, got %d'):format(lnum, endLnum, l))
|
||||
local line = fb:lines(l)[1]
|
||||
return render.captureVirtText(bufnr, line, l, syntax, namespaces, concealLevel)
|
||||
end
|
||||
end
|
||||
ok, res = pcall(handler, virtText, lnum, endLnum, width, utils.truncateStrByWidth, {
|
||||
bufnr = bufnr,
|
||||
winid = winid,
|
||||
text = text,
|
||||
get_fold_kind = function(l)
|
||||
l = l == nil and lnum or l
|
||||
local t = type(l)
|
||||
assert(t == 'number', 'expected a number, got ' .. t)
|
||||
return fb:lineKind(winid, l)
|
||||
end,
|
||||
get_fold_virt_text = getFoldVirtText
|
||||
})
|
||||
wses.foldedTextMaps[lnum] = res
|
||||
end
|
||||
if doRender == nil then
|
||||
doRender = true
|
||||
end
|
||||
if ok then
|
||||
if bufnrSet[bufnr] == winid then
|
||||
if doRender then
|
||||
log.debug('Window:', winid, 'need add/update folded lnum:', lnum)
|
||||
didClose = true
|
||||
else
|
||||
log.debug('Window:', winid, 'will add/update folded lnum:', lnum)
|
||||
end
|
||||
fb:closeFold(lnum, endLnum, text, res, width, doRender)
|
||||
end
|
||||
else
|
||||
fb:closeFold(lnum, endLnum, text, {{handlerErrorMsg, 'Error'}}, width, doRender)
|
||||
log.error(res)
|
||||
end
|
||||
end
|
||||
return res, didClose
|
||||
end
|
||||
|
||||
---@diagnostic disable-next-line: unused-local
|
||||
function Decorator.defaultVirtTextHandler(virtText, lnum, endLnum, width, truncate, ctx)
|
||||
local newVirtText = {}
|
||||
local suffix = ' ⋯ '
|
||||
local sufWidth = fn.strdisplaywidth(suffix)
|
||||
local targetWidth = width - sufWidth
|
||||
local curWidth = 0
|
||||
for _, chunk in ipairs(virtText) do
|
||||
local chunkText = chunk[1]
|
||||
local chunkWidth = fn.strdisplaywidth(chunkText)
|
||||
if targetWidth > curWidth + chunkWidth then
|
||||
table.insert(newVirtText, chunk)
|
||||
else
|
||||
chunkText = truncate(chunkText, targetWidth - curWidth)
|
||||
local hlGroup = chunk[2]
|
||||
table.insert(newVirtText, {chunkText, hlGroup})
|
||||
chunkWidth = fn.strdisplaywidth(chunkText)
|
||||
-- str width returned from truncate() may less than 2nd argument, need padding
|
||||
if curWidth + chunkWidth < targetWidth then
|
||||
suffix = suffix .. (' '):rep(targetWidth - curWidth - chunkWidth)
|
||||
end
|
||||
break
|
||||
end
|
||||
curWidth = curWidth + chunkWidth
|
||||
end
|
||||
table.insert(newVirtText, {suffix, 'UfoFoldedEllipsis'})
|
||||
return newVirtText
|
||||
end
|
||||
|
||||
function Decorator:setVirtTextHandler(bufnr, handler)
|
||||
bufnr = bufnr or api.nvim_get_current_buf()
|
||||
self.virtTextHandlers[bufnr] = handler
|
||||
end
|
||||
|
||||
---
|
||||
---@param bufnr number
|
||||
---@return UfoFoldVirtTextHandler
|
||||
function Decorator:getVirtTextHandler(bufnr)
|
||||
bufnr = bufnr or api.nvim_get_current_buf()
|
||||
return self.virtTextHandlers[bufnr]
|
||||
end
|
||||
|
||||
---
|
||||
---@param namespace number
|
||||
---@return UfoDecorator
|
||||
function Decorator:initialize(namespace)
|
||||
if self.initialized then
|
||||
return self
|
||||
end
|
||||
self.initialized = true
|
||||
api.nvim_set_decoration_provider(namespace, {
|
||||
on_start = onStart,
|
||||
on_win = onWin,
|
||||
on_line = onLine,
|
||||
on_end = silentOnEnd
|
||||
})
|
||||
self.ns = namespace
|
||||
self.hlNs = self.hlNs or api.nvim_create_namespace('')
|
||||
self.disposables = {}
|
||||
table.insert(self.disposables, disposable:create(function()
|
||||
self.initialized = false
|
||||
api.nvim_set_decoration_provider(namespace, {})
|
||||
for bufnr in ipairs(fold.buffers()) do
|
||||
self:resetCurosrFoldedLineHighlightByBuf(bufnr)
|
||||
end
|
||||
end))
|
||||
self.enableGetFoldVirtText = config.enable_get_fold_virt_text
|
||||
self.openFoldHlTimeout = config.open_fold_hl_timeout
|
||||
self.openFoldHlEnabled = self.openFoldHlTimeout > 0
|
||||
event:on('setOpenFoldHl', function(val)
|
||||
if type(val) == 'boolean' then
|
||||
self.openFoldHlEnabled = val
|
||||
else
|
||||
self.openFoldHlEnabled = self.openFoldHlTimeout > 0
|
||||
end
|
||||
end, self.disposables)
|
||||
|
||||
local virtTextHandler = config.fold_virt_text_handler or self.defaultVirtTextHandler
|
||||
self.virtTextHandlers = setmetatable({}, {
|
||||
__index = function(tbl, bufnr)
|
||||
rawset(tbl, bufnr, virtTextHandler)
|
||||
return virtTextHandler
|
||||
end
|
||||
})
|
||||
handlerErrorMsg = ([[!Error in user's handler, check out `%s`]]):format(log.path)
|
||||
self.winSessions = setmetatable({}, {
|
||||
__index = function(tbl, winid)
|
||||
local o = window:new(winid)
|
||||
rawset(tbl, winid, o)
|
||||
return o
|
||||
end
|
||||
})
|
||||
event:on('WinClosed', function(winid)
|
||||
self.winSessions[winid] = nil
|
||||
end, self.disposables)
|
||||
event:on('BufDetach', function(bufnr)
|
||||
self:resetCurosrFoldedLineHighlightByBuf(bufnr)
|
||||
self.virtTextHandlers[bufnr] = nil
|
||||
end, self.disposables)
|
||||
return self
|
||||
end
|
||||
|
||||
function Decorator:dispose()
|
||||
disposable.disposeAll(self.disposables)
|
||||
self.disposables = {}
|
||||
end
|
||||
|
||||
return Decorator
|
||||
@ -0,0 +1,107 @@
|
||||
local cmd = vim.cmd
|
||||
|
||||
local utils = require('ufo.utils')
|
||||
|
||||
local function convertToFoldRanges(ranges, rowPairs)
|
||||
-- just check the last range only to filter out same ranges
|
||||
local lastStartLine, lastEndLine
|
||||
local foldRanges = {}
|
||||
local minLines = vim.wo.foldminlines
|
||||
for _, r in ipairs(ranges) do
|
||||
local startLine, endLine = r.startLine, r.endLine
|
||||
if not rowPairs[startLine] and endLine - startLine >= minLines and
|
||||
(lastStartLine ~= startLine or lastEndLine ~= endLine) then
|
||||
lastStartLine, lastEndLine = startLine, endLine
|
||||
table.insert(foldRanges, {startLine + 1, endLine + 1})
|
||||
end
|
||||
end
|
||||
return foldRanges
|
||||
end
|
||||
|
||||
---@type UfoFoldDriverNonFFI|UfoFoldDriverFFI
|
||||
local FoldDriver
|
||||
|
||||
---@class UfoFoldDriverFFI
|
||||
local FoldDriverFFI = {}
|
||||
|
||||
function FoldDriverFFI:new(wffi)
|
||||
local o = setmetatable({}, self)
|
||||
self.__index = self
|
||||
self._wffi = wffi
|
||||
return o
|
||||
end
|
||||
|
||||
---
|
||||
---@param winid number
|
||||
---@param ranges UfoFoldingRange
|
||||
---@param rowPairs table<number, number>
|
||||
function FoldDriverFFI:createFolds(winid, ranges, rowPairs)
|
||||
utils.winCall(winid, function()
|
||||
local wo = vim.wo
|
||||
local level = wo.foldlevel
|
||||
self._wffi.clearFolds(winid)
|
||||
local foldRanges = convertToFoldRanges(ranges, rowPairs)
|
||||
self._wffi.createFolds(winid, foldRanges)
|
||||
wo.foldmethod = 'manual'
|
||||
wo.foldenable = true
|
||||
wo.foldlevel = level
|
||||
foldRanges = {}
|
||||
for row, endRow in pairs(rowPairs) do
|
||||
table.insert(foldRanges, {row + 1, endRow + 1})
|
||||
end
|
||||
self._wffi.createFolds(winid, foldRanges)
|
||||
end)
|
||||
end
|
||||
|
||||
---@class UfoFoldDriverNonFFI
|
||||
local FoldDriverNonFFI = {}
|
||||
|
||||
function FoldDriverNonFFI:new()
|
||||
local o = setmetatable({}, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end
|
||||
|
||||
---
|
||||
---@param winid number
|
||||
---@param ranges UfoFoldingRange
|
||||
---@param rowPairs table<number, number>
|
||||
function FoldDriverNonFFI:createFolds(winid, ranges, rowPairs)
|
||||
utils.winCall(winid, function()
|
||||
local level = vim.wo.foldlevel
|
||||
local cmds = {}
|
||||
local foldRanges = convertToFoldRanges(ranges, rowPairs)
|
||||
for _, r in ipairs(foldRanges) do
|
||||
table.insert(cmds, ('%d,%d:fold'):format(r[1], r[2]))
|
||||
end
|
||||
local view = utils.saveView(0)
|
||||
cmd('norm! zE')
|
||||
utils.restView(0, view)
|
||||
table.insert(cmds, 'setl foldmethod=manual')
|
||||
table.insert(cmds, 'setl foldenable')
|
||||
table.insert(cmds, 'setl foldlevel=' .. level)
|
||||
foldRanges = {}
|
||||
for row, endRow in pairs(rowPairs) do
|
||||
table.insert(foldRanges, {row + 1, endRow + 1})
|
||||
end
|
||||
table.sort(foldRanges, function(a, b)
|
||||
return a[1] == b[1] and a[2] < b[2] or a[1] > b[1]
|
||||
end)
|
||||
for _, r in ipairs(foldRanges) do
|
||||
table.insert(cmds, ('%d,%dfold'):format(r[1], r[2]))
|
||||
end
|
||||
cmd(table.concat(cmds, '|'))
|
||||
end)
|
||||
end
|
||||
|
||||
local function init()
|
||||
if jit ~= nil then
|
||||
FoldDriver = FoldDriverFFI:new(require('ufo.wffi'))
|
||||
else
|
||||
FoldDriver = FoldDriverNonFFI:new()
|
||||
end
|
||||
end
|
||||
|
||||
init()
|
||||
|
||||
return FoldDriver
|
||||
229
config/neovim/store/lazy-plugins/nvim-ufo/lua/ufo/fold/init.lua
Normal file
229
config/neovim/store/lazy-plugins/nvim-ufo/lua/ufo/fold/init.lua
Normal file
@ -0,0 +1,229 @@
|
||||
local api = vim.api
|
||||
local cmd = vim.cmd
|
||||
|
||||
local config = require('ufo.config')
|
||||
local promise = require('promise')
|
||||
local async = require('async')
|
||||
local utils = require('ufo.utils')
|
||||
local provider = require('ufo.provider')
|
||||
local log = require('ufo.lib.log')
|
||||
local event = require('ufo.lib.event')
|
||||
local manager = require('ufo.fold.manager')
|
||||
local disposable = require('ufo.lib.disposable')
|
||||
|
||||
---@class UfoFold
|
||||
---@field initialized boolean
|
||||
---@field disposables UfoDisposable[]
|
||||
local Fold = {}
|
||||
|
||||
local updateFoldDebounced
|
||||
|
||||
---@param bufnr number
|
||||
---@return Promise
|
||||
local function tryUpdateFold(bufnr)
|
||||
return async(function()
|
||||
local winid = utils.getWinByBuf(bufnr)
|
||||
if not utils.isWinValid(winid) then
|
||||
return
|
||||
end
|
||||
-- some plugins may change foldmethod to diff
|
||||
await(utils.wait(50))
|
||||
if not utils.isWinValid(winid) or utils.isDiffOrMarkerFold(winid) then
|
||||
return
|
||||
end
|
||||
await(Fold.update(bufnr))
|
||||
end)
|
||||
end
|
||||
|
||||
local function setFoldText(bufnr)
|
||||
api.nvim_buf_call(bufnr, function()
|
||||
cmd([[
|
||||
setl foldtext=v:lua.require'ufo.main'.foldtext()
|
||||
setl fillchars+=fold:\ ]])
|
||||
end)
|
||||
end
|
||||
|
||||
function Fold.update(bufnr)
|
||||
bufnr = bufnr or api.nvim_get_current_buf()
|
||||
local fb = manager:get(bufnr)
|
||||
if not fb then
|
||||
return promise.resolve()
|
||||
end
|
||||
if manager:isFoldMethodsDisabled(fb) then
|
||||
if not pcall(fb.getRangesFromExtmarks, fb) then
|
||||
fb:resetFoldedLines(true)
|
||||
end
|
||||
return promise.resolve()
|
||||
end
|
||||
if fb.status == 'pending' and manager:applyFoldRanges(bufnr) ~= -1 then
|
||||
return promise.resolve()
|
||||
end
|
||||
|
||||
local changedtick, ft, bt = fb:changedtick(), fb:filetype(), fb:buftype()
|
||||
fb:acquireRequest()
|
||||
|
||||
local function dispose(resolved)
|
||||
---@diagnostic disable-next-line: redefined-local
|
||||
local fb = manager:get(bufnr)
|
||||
if not fb then
|
||||
return false
|
||||
end
|
||||
fb:releaseRequest()
|
||||
local ok = ft == fb:filetype() and bt == fb:buftype()
|
||||
if ok then
|
||||
if resolved then
|
||||
ok = changedtick == fb:changedtick()
|
||||
end
|
||||
end
|
||||
local requested = fb:requested()
|
||||
if not ok and not requested then
|
||||
log.debug('update fold again for bufnr:', bufnr)
|
||||
updateFoldDebounced(bufnr)
|
||||
end
|
||||
return ok and not requested
|
||||
end
|
||||
|
||||
log.info('providers:', fb.providers)
|
||||
return provider:requestFoldingRange(fb.providers, bufnr):thenCall(function(res)
|
||||
if not dispose(true) then
|
||||
return
|
||||
end
|
||||
local selected, ranges = res[1], res[2]
|
||||
fb.selectedProvider = type(selected) == 'string' and selected or 'external'
|
||||
log.info('selected provider:', fb.selectedProvider)
|
||||
if not ranges or #ranges == 0 or not utils.isBufLoaded(bufnr) then
|
||||
return
|
||||
end
|
||||
fb.status = manager:applyFoldRanges(bufnr, ranges) == -1 and 'pending' or 'start'
|
||||
end, function(err)
|
||||
if not dispose(false) then
|
||||
return
|
||||
end
|
||||
return promise.reject(err)
|
||||
end)
|
||||
end
|
||||
|
||||
---
|
||||
---@param bufnr number
|
||||
function Fold.get(bufnr)
|
||||
return manager:get(bufnr)
|
||||
end
|
||||
|
||||
function Fold.buffers()
|
||||
return manager.buffers
|
||||
end
|
||||
|
||||
function Fold.apply(bufnr, ranges, manual)
|
||||
return manager:applyFoldRanges(bufnr, ranges, manual)
|
||||
end
|
||||
|
||||
function Fold.attach(bufnr)
|
||||
bufnr = bufnr or api.nvim_get_current_buf()
|
||||
if not manager:attach(bufnr) then
|
||||
return
|
||||
end
|
||||
log.debug('attach bufnr:', bufnr)
|
||||
setFoldText(bufnr)
|
||||
tryUpdateFold(bufnr)
|
||||
end
|
||||
|
||||
function Fold.setStatus(bufnr, status)
|
||||
local fb = manager:get(bufnr)
|
||||
local old = ''
|
||||
if fb then
|
||||
old = fb.status
|
||||
fb.status = status
|
||||
end
|
||||
return old
|
||||
end
|
||||
|
||||
updateFoldDebounced = (function()
|
||||
local lastBufnr
|
||||
local debounced = require('ufo.lib.debounce')(Fold.update, 300)
|
||||
return function(bufnr, flush, onlyPending)
|
||||
bufnr = bufnr or api.nvim_get_current_buf()
|
||||
local fb = manager:get(bufnr)
|
||||
if not fb or utils.mode() ~= 'n' or
|
||||
onlyPending and fb.status ~= 'pending' or fb.status == 'stop' then
|
||||
return
|
||||
end
|
||||
if lastBufnr ~= bufnr then
|
||||
debounced:flush()
|
||||
end
|
||||
lastBufnr = bufnr
|
||||
debounced(bufnr)
|
||||
if flush then
|
||||
debounced:flush()
|
||||
end
|
||||
end
|
||||
end)()
|
||||
|
||||
local function handleDiffMode(winid, new, old)
|
||||
if old ~= new and new == 0 then
|
||||
local bufnr = api.nvim_win_get_buf(winid)
|
||||
local fb = manager:get(bufnr)
|
||||
|
||||
if fb then
|
||||
fb:resetFoldedLines(true)
|
||||
|
||||
-- TODO
|
||||
-- buffer go back normal mode from diff mode will disable `foldenable` if the foldmethod was
|
||||
-- `manual` before entering diff mode. Unfortunately, foldmethod will always be `manual` if
|
||||
-- enable ufo, `foldenable` will be disabled.
|
||||
|
||||
-- `set foldenable` forcedly, feel free to open an issue if ufo is evil.
|
||||
promise.resolve():thenCall(function()
|
||||
if utils.isWinValid(winid) and vim.wo[winid].foldmethod == 'manual' then
|
||||
utils.winCall(winid, function()
|
||||
cmd('silent! %foldopen!')
|
||||
end)
|
||||
vim.wo[winid].foldenable = true
|
||||
end
|
||||
end)
|
||||
tryUpdateFold(bufnr)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---
|
||||
---@param ns number
|
||||
---@return UfoFold
|
||||
function Fold:initialize(ns)
|
||||
if self.initialized then
|
||||
return self
|
||||
end
|
||||
self.initialized = true
|
||||
self.disposables = {}
|
||||
table.insert(self.disposables, disposable:create(function()
|
||||
self.initialized = false
|
||||
end))
|
||||
event:on('BufWinEnter', function(bufnr)
|
||||
bufnr = bufnr or api.nvim_get_current_buf()
|
||||
local fb = manager:get(bufnr)
|
||||
if not fb then
|
||||
return
|
||||
end
|
||||
setFoldText(bufnr)
|
||||
updateFoldDebounced(bufnr, true, true)
|
||||
end, self.disposables)
|
||||
event:on('BufWritePost', function(bufnr)
|
||||
updateFoldDebounced(bufnr, true)
|
||||
end, self.disposables)
|
||||
event:on('TextChanged', updateFoldDebounced, self.disposables)
|
||||
event:on('ModeChangedToNormal', function(bufnr, oldMode)
|
||||
local onlyPending = oldMode ~= 'i' and oldMode ~= 't'
|
||||
updateFoldDebounced(bufnr, true, onlyPending)
|
||||
end, self.disposables)
|
||||
event:on('BufAttach', Fold.attach, self.disposables)
|
||||
event:on('DiffModeChanged', handleDiffMode, self.disposables)
|
||||
table.insert(self.disposables, manager:initialize(ns, config.provider_selector,
|
||||
config.close_fold_kinds_for_ft))
|
||||
return self
|
||||
end
|
||||
|
||||
function Fold:dispose()
|
||||
disposable.disposeAll(self.disposables)
|
||||
self.disposables = {}
|
||||
end
|
||||
|
||||
return Fold
|
||||
@ -0,0 +1,211 @@
|
||||
local buffer = require('ufo.model.foldbuffer')
|
||||
local event = require('ufo.lib.event')
|
||||
local disposable = require('ufo.lib.disposable')
|
||||
local bufmanager = require('ufo.bufmanager')
|
||||
local utils = require('ufo.utils')
|
||||
local driver = require('ufo.fold.driver')
|
||||
local log = require('ufo.lib.log')
|
||||
|
||||
---@class UfoFoldBufferManager
|
||||
---@field initialized boolean
|
||||
---@field buffers UfoFoldBuffer[]
|
||||
---@field providerSelector function
|
||||
---@field closeKindsMap table<string,UfoFoldingRangeKind[]>
|
||||
---@field disposables UfoDisposable[]
|
||||
local FoldBufferManager = {}
|
||||
|
||||
---
|
||||
---@param namespace number
|
||||
---@param selector function
|
||||
---@param closeKindsMap <table, string,UfoFoldingRangeKind[]>
|
||||
---@return UfoFoldBufferManager
|
||||
function FoldBufferManager:initialize(namespace, selector, closeKindsMap)
|
||||
if self.initialized then
|
||||
return self
|
||||
end
|
||||
self.ns = namespace
|
||||
self.providerSelector = selector
|
||||
self.closeKindsMap = closeKindsMap
|
||||
self.buffers = {}
|
||||
self.initialized = true
|
||||
self.disposables = {}
|
||||
table.insert(self.disposables, disposable:create(function()
|
||||
for _, fb in pairs(self.buffers) do
|
||||
fb:dispose()
|
||||
end
|
||||
self.buffers = {}
|
||||
self.initialized = false
|
||||
end))
|
||||
event:on('BufDetach', function(bufnr)
|
||||
local fb = self:get(bufnr)
|
||||
if fb then
|
||||
fb:dispose()
|
||||
end
|
||||
self.buffers[bufnr] = nil
|
||||
end, self.disposables)
|
||||
event:on('BufReload', function(bufnr)
|
||||
local fb = self:get(bufnr)
|
||||
if fb then
|
||||
fb:dispose()
|
||||
end
|
||||
end, self.disposables)
|
||||
|
||||
local function optChanged(bufnr, new, old)
|
||||
if old ~= new then
|
||||
local fb = self:get(bufnr)
|
||||
if fb then
|
||||
fb.providers = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
event:on('BufTypeChanged', optChanged, self.disposables)
|
||||
event:on('FileTypeChanged', optChanged, self.disposables)
|
||||
event:on('BufLinesChanged', function(bufnr, _, firstLine, lastLine, lastLineUpdated)
|
||||
local fb = self:get(bufnr)
|
||||
if fb then
|
||||
fb:handleFoldedLinesChanged(firstLine, lastLine, lastLineUpdated)
|
||||
end
|
||||
end, self.disposables)
|
||||
self.providerSelector = selector
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
---@param bufnr number
|
||||
---@return boolean
|
||||
function FoldBufferManager:attach(bufnr)
|
||||
local fb = self:get(bufnr)
|
||||
if not fb then
|
||||
self.buffers[bufnr] = buffer:new(bufmanager:get(bufnr), self.ns)
|
||||
end
|
||||
return not fb
|
||||
end
|
||||
|
||||
---
|
||||
---@param bufnr number
|
||||
---@return UfoFoldBuffer
|
||||
function FoldBufferManager:get(bufnr)
|
||||
return self.buffers[bufnr]
|
||||
end
|
||||
|
||||
function FoldBufferManager:dispose()
|
||||
disposable.disposeAll(self.disposables)
|
||||
self.disposables = {}
|
||||
end
|
||||
|
||||
function FoldBufferManager:parseBufferProviders(fb, selector)
|
||||
if not utils.isBufLoaded(fb.bufnr) then
|
||||
return
|
||||
end
|
||||
if not selector then
|
||||
fb.providers = {'lsp', 'indent'}
|
||||
return
|
||||
end
|
||||
local res
|
||||
local providers = selector(fb.bufnr, fb:filetype(), fb:buftype())
|
||||
local t = type(providers)
|
||||
if t == 'nil' then
|
||||
res = {'lsp', 'indent'}
|
||||
elseif t == 'string' or t == 'function' then
|
||||
res = {providers}
|
||||
elseif t == 'table' then
|
||||
res = {}
|
||||
for _, m in ipairs(providers) do
|
||||
if #res == 2 then
|
||||
error('Return value of `provider_selector` only supports {`main`, `fallback`} ' ..
|
||||
[[combo, don't add providers more than two into return table.]])
|
||||
end
|
||||
table.insert(res, m)
|
||||
end
|
||||
else
|
||||
res = {''}
|
||||
end
|
||||
fb.providers = res
|
||||
end
|
||||
|
||||
function FoldBufferManager:isFoldMethodsDisabled(fb)
|
||||
if not fb.providers then
|
||||
self:parseBufferProviders(fb, self.providerSelector)
|
||||
end
|
||||
return not fb.providers or fb.providers[1] == ''
|
||||
end
|
||||
|
||||
function FoldBufferManager:getRowPairsByScanning(fb, winid)
|
||||
local rowPairs = {}
|
||||
for _, range in ipairs(fb:scanFoldedRanges(winid)) do
|
||||
local row, endRow = range[1], range[2]
|
||||
rowPairs[row] = endRow
|
||||
end
|
||||
return rowPairs
|
||||
end
|
||||
|
||||
---
|
||||
---@param bufnr number
|
||||
---@param ranges? UfoFoldingRange[]
|
||||
---@param manual? boolean
|
||||
---@return number
|
||||
function FoldBufferManager:applyFoldRanges(bufnr, ranges, manual)
|
||||
local fb = self:get(bufnr)
|
||||
if not fb then
|
||||
return -1
|
||||
end
|
||||
local changedtick = fb:changedtick()
|
||||
if ranges then
|
||||
fb.foldRanges = ranges
|
||||
fb.version = changedtick
|
||||
elseif changedtick ~= fb.version then
|
||||
return -1
|
||||
end
|
||||
local winid, windows = utils.getWinByBuf(bufnr)
|
||||
if winid == -1 or not utils.isWinValid(winid) then
|
||||
return -1
|
||||
elseif not vim.wo[winid].foldenable or utils.isDiffOrMarkerFold(winid) then
|
||||
return -1
|
||||
elseif utils.mode() ~= 'n' then
|
||||
return -1
|
||||
end
|
||||
local rowPairs = {}
|
||||
local isFirstApply = not fb.scanned
|
||||
if not manual and not fb.scanned or windows then
|
||||
rowPairs = self:getRowPairsByScanning(fb, winid)
|
||||
local kinds = self.closeKindsMap[fb:filetype()] or self.closeKindsMap.default
|
||||
for _, range in ipairs(fb.foldRanges) do
|
||||
if range.kind and vim.tbl_contains(kinds, range.kind) then
|
||||
local startLine, endLine = range.startLine, range.endLine
|
||||
rowPairs[startLine] = endLine
|
||||
fb:closeFold(startLine + 1, endLine + 1)
|
||||
end
|
||||
end
|
||||
fb.scanned = true
|
||||
else
|
||||
local ok, res = pcall(function()
|
||||
for _, range in ipairs(fb:getRangesFromExtmarks()) do
|
||||
local row, endRow = range[1], range[2]
|
||||
rowPairs[row] = endRow
|
||||
end
|
||||
end)
|
||||
if not ok then
|
||||
log.info(res)
|
||||
fb:resetFoldedLines(true)
|
||||
rowPairs = self:getRowPairsByScanning(fb, winid)
|
||||
end
|
||||
end
|
||||
|
||||
local view, wrow
|
||||
-- topline may changed after applying folds, restore topline to save our eyes
|
||||
if isFirstApply and not vim.tbl_isempty(rowPairs) then
|
||||
view = utils.saveView(winid)
|
||||
wrow = utils.wrow(winid)
|
||||
end
|
||||
log.info('apply fold ranges:', fb.foldRanges)
|
||||
log.info('apply fold rowPairs:', rowPairs)
|
||||
driver:createFolds(winid, fb.foldRanges, rowPairs)
|
||||
if view and utils.wrow(winid) ~= wrow then
|
||||
view.topline, view.topfill = utils.evaluateTopline(winid, view.lnum, wrow)
|
||||
utils.restView(winid, view)
|
||||
end
|
||||
return winid
|
||||
end
|
||||
|
||||
return FoldBufferManager
|
||||
116
config/neovim/store/lazy-plugins/nvim-ufo/lua/ufo/highlight.lua
Normal file
116
config/neovim/store/lazy-plugins/nvim-ufo/lua/ufo/highlight.lua
Normal file
@ -0,0 +1,116 @@
|
||||
local cmd = vim.cmd
|
||||
local api = vim.api
|
||||
local fn = vim.fn
|
||||
|
||||
local event = require('ufo.lib.event')
|
||||
local disposable = require('ufo.lib.disposable')
|
||||
|
||||
---@class UfoHighlight
|
||||
local Highlight = {}
|
||||
local initialized
|
||||
|
||||
---@type table<number|string, table>
|
||||
local hlGroups
|
||||
|
||||
---@type table<string, string>
|
||||
local signNames
|
||||
|
||||
local function resetHighlightGroup()
|
||||
local termguicolors = vim.o.termguicolors
|
||||
hlGroups = setmetatable({}, {
|
||||
__index = function(tbl, k)
|
||||
local ok, hl
|
||||
if type(k) == 'number' then
|
||||
ok, hl = pcall(api.nvim_get_hl_by_id, k, termguicolors)
|
||||
else
|
||||
ok, hl = pcall(api.nvim_get_hl_by_name, k, termguicolors)
|
||||
end
|
||||
if not ok or hl[vim.type_idx] == vim.types.dictionary then
|
||||
hl = {}
|
||||
end
|
||||
rawset(tbl, k, hl)
|
||||
return hl
|
||||
end
|
||||
})
|
||||
local ok, hl = pcall(api.nvim_get_hl_by_name, 'Folded', termguicolors)
|
||||
if ok and hl.background then
|
||||
if termguicolors then
|
||||
cmd(('hi default UfoFoldedBg guibg=#%x'):format(hl.background))
|
||||
else
|
||||
cmd(('hi default UfoFoldedBg ctermbg=%d'):format(hl.background))
|
||||
end
|
||||
else
|
||||
cmd('hi default link UfoFoldedBg Visual')
|
||||
end
|
||||
ok, hl = pcall(api.nvim_get_hl_by_name, 'Normal', termguicolors)
|
||||
if ok and hl.foreground then
|
||||
if termguicolors then
|
||||
cmd(('hi default UfoFoldedFg guifg=#%x'):format(hl.foreground))
|
||||
else
|
||||
cmd(('hi default UfoFoldedFg ctermfg=%d'):format(hl.foreground))
|
||||
end
|
||||
else
|
||||
cmd('hi default UfoFoldedFg ctermfg=None guifg=None')
|
||||
end
|
||||
|
||||
cmd([[
|
||||
hi default link UfoPreviewSbar PmenuSbar
|
||||
hi default link UfoPreviewThumb PmenuThumb
|
||||
hi default link UfoPreviewWinBar UfoFoldedBg
|
||||
hi default link UfoPreviewCursorLine Visual
|
||||
hi default link UfoFoldedEllipsis Comment
|
||||
hi default link UfoCursorFoldedLine CursorLine
|
||||
]])
|
||||
end
|
||||
|
||||
local function resetSignGroup()
|
||||
signNames = setmetatable({}, {
|
||||
__index = function(tbl, k)
|
||||
assert(fn.sign_define(k, {linehl = k}) == 0,
|
||||
'Define sign name ' .. k .. 'failed')
|
||||
rawset(tbl, k, k)
|
||||
return k
|
||||
end
|
||||
})
|
||||
return disposable:create(function()
|
||||
for _, name in pairs(signNames) do
|
||||
pcall(fn.sign_undefine, name)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function Highlight.hlGroups()
|
||||
if not initialized then
|
||||
Highlight:initialize()
|
||||
end
|
||||
return hlGroups
|
||||
end
|
||||
|
||||
function Highlight.signNames()
|
||||
if not initialized then
|
||||
Highlight:initialize()
|
||||
end
|
||||
return signNames
|
||||
end
|
||||
|
||||
---
|
||||
---@return UfoHighlight
|
||||
function Highlight:initialize()
|
||||
if initialized then
|
||||
return self
|
||||
end
|
||||
self.disposables = {}
|
||||
event:on('ColorScheme', resetHighlightGroup, self.disposables)
|
||||
resetHighlightGroup()
|
||||
table.insert(self.disposables, resetSignGroup())
|
||||
initialized = true
|
||||
return self
|
||||
end
|
||||
|
||||
function Highlight:dispose()
|
||||
disposable.disposeAll(self.disposables)
|
||||
self.disposables = {}
|
||||
initialized = false
|
||||
end
|
||||
|
||||
return Highlight
|
||||
@ -0,0 +1,78 @@
|
||||
local uv = vim.loop
|
||||
|
||||
---@class UfoDebounce
|
||||
---@field timer userdata
|
||||
---@field fn function
|
||||
---@field args table
|
||||
---@field wait number
|
||||
---@field leading? boolean
|
||||
---@overload fun(fn: function, wait: number, leading?: boolean): UfoDebounce
|
||||
local Debounce = {}
|
||||
|
||||
---
|
||||
---@param fn function
|
||||
---@param wait number
|
||||
---@param leading? boolean
|
||||
---@return UfoDebounce
|
||||
function Debounce:new(fn, wait, leading)
|
||||
vim.validate({
|
||||
fn = {fn, 'function'},
|
||||
wait = {wait, 'number'},
|
||||
leading = {leading, 'boolean', true}
|
||||
})
|
||||
local o = setmetatable({}, self)
|
||||
o.timer = nil
|
||||
o.fn = vim.schedule_wrap(fn)
|
||||
o.args = nil
|
||||
o.wait = wait
|
||||
o.leading = leading
|
||||
return o
|
||||
end
|
||||
|
||||
function Debounce:call(...)
|
||||
local timer = self.timer
|
||||
self.args = {...}
|
||||
if not timer then
|
||||
---@type userdata
|
||||
timer = uv.new_timer()
|
||||
self.timer = timer
|
||||
local wait = self.wait
|
||||
timer:start(wait, wait, self.leading and function()
|
||||
self:cancel()
|
||||
end or function()
|
||||
self:flush()
|
||||
end)
|
||||
if self.leading then
|
||||
self.fn(...)
|
||||
end
|
||||
else
|
||||
timer:again()
|
||||
end
|
||||
end
|
||||
|
||||
function Debounce:cancel()
|
||||
local timer = self.timer
|
||||
if timer then
|
||||
if timer:has_ref() then
|
||||
timer:stop()
|
||||
if not timer:is_closing() then
|
||||
timer:close()
|
||||
end
|
||||
end
|
||||
self.timer = nil
|
||||
end
|
||||
end
|
||||
|
||||
function Debounce:flush()
|
||||
if self.timer then
|
||||
self:cancel()
|
||||
self.fn(unpack(self.args))
|
||||
end
|
||||
end
|
||||
|
||||
Debounce.__index = Debounce
|
||||
Debounce.__call = Debounce.call
|
||||
|
||||
return setmetatable(Debounce, {
|
||||
__call = Debounce.new
|
||||
})
|
||||
@ -0,0 +1,36 @@
|
||||
---@class UfoDisposable
|
||||
---@field func fun()
|
||||
local Disposable = {}
|
||||
|
||||
---
|
||||
---@param disposables UfoDisposable[]
|
||||
function Disposable.disposeAll(disposables)
|
||||
for _, item in ipairs(disposables) do
|
||||
if item.dispose then
|
||||
item:dispose()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---
|
||||
---@param func fun()
|
||||
---@return UfoDisposable
|
||||
function Disposable:new(func)
|
||||
local o = setmetatable({}, self)
|
||||
self.__index = self
|
||||
o.func = func
|
||||
return o
|
||||
end
|
||||
|
||||
---
|
||||
---@param func fun()
|
||||
---@return UfoDisposable
|
||||
function Disposable:create(func)
|
||||
return self:new(func)
|
||||
end
|
||||
|
||||
function Disposable:dispose()
|
||||
self.func()
|
||||
end
|
||||
|
||||
return Disposable
|
||||
@ -0,0 +1,58 @@
|
||||
local disposable = require('ufo.lib.disposable')
|
||||
local log = require('ufo.lib.log')
|
||||
|
||||
---@class UfoEvent
|
||||
local Event = {
|
||||
_collection = {}
|
||||
}
|
||||
|
||||
---@param name string
|
||||
---@param listener function
|
||||
function Event:off(name, listener)
|
||||
local listeners = self._collection[name]
|
||||
if not listeners then
|
||||
return
|
||||
end
|
||||
for i = 1, #listeners do
|
||||
if listeners[i] == listener then
|
||||
table.remove(listeners, i)
|
||||
break
|
||||
end
|
||||
end
|
||||
if #listeners == 0 then
|
||||
self._collection[name] = nil
|
||||
end
|
||||
end
|
||||
|
||||
---@param name string
|
||||
---@param listener function
|
||||
---@param disposables? UfoDisposable[]
|
||||
---@return UfoDisposable
|
||||
function Event:on(name, listener, disposables)
|
||||
if not self._collection[name] then
|
||||
self._collection[name] = {}
|
||||
end
|
||||
table.insert(self._collection[name], listener)
|
||||
local d = disposable:create(function()
|
||||
self:off(name, listener)
|
||||
end)
|
||||
if type(disposables) == 'table' then
|
||||
table.insert(disposables, d)
|
||||
end
|
||||
return d
|
||||
end
|
||||
|
||||
---@param name string
|
||||
---@vararg any
|
||||
function Event:emit(name, ...)
|
||||
local listeners = self._collection[name]
|
||||
if not listeners then
|
||||
return
|
||||
end
|
||||
log.trace('event:', name, 'listeners:', listeners, 'args:', ...)
|
||||
for _, listener in ipairs(listeners) do
|
||||
listener(...)
|
||||
end
|
||||
end
|
||||
|
||||
return Event
|
||||
106
config/neovim/store/lazy-plugins/nvim-ufo/lua/ufo/lib/log.lua
Normal file
106
config/neovim/store/lazy-plugins/nvim-ufo/lua/ufo/lib/log.lua
Normal file
@ -0,0 +1,106 @@
|
||||
--- Singleton
|
||||
---@class UfoLog
|
||||
---@field trace fun(...)
|
||||
---@field debug fun(...)
|
||||
---@field info fun(...)
|
||||
---@field warn fun(...)
|
||||
---@field error fun(...)
|
||||
---@field path string
|
||||
local Log = {}
|
||||
local fn = vim.fn
|
||||
local uv = vim.loop
|
||||
|
||||
---@type table<string, number>
|
||||
local levelMap
|
||||
local levelNr
|
||||
local defaultLevel
|
||||
|
||||
local function getLevelNr(level)
|
||||
local nr
|
||||
if type(level) == 'number' then
|
||||
nr = level
|
||||
elseif type(level) == 'string' then
|
||||
nr = levelMap[level:upper()]
|
||||
else
|
||||
nr = defaultLevel
|
||||
end
|
||||
return nr
|
||||
end
|
||||
|
||||
---
|
||||
---@param l number|string
|
||||
function Log.setLevel(l)
|
||||
levelNr = getLevelNr(l)
|
||||
end
|
||||
|
||||
---
|
||||
---@param l number|string
|
||||
---@return boolean
|
||||
function Log.isEnabled(l)
|
||||
return getLevelNr(l) >= levelNr
|
||||
end
|
||||
|
||||
---
|
||||
---@return string|'trace'|'debug'|'info'|'warn'|'error'
|
||||
function Log.level()
|
||||
for l, nr in pairs(levelMap) do
|
||||
if nr == levelNr then
|
||||
return l
|
||||
end
|
||||
end
|
||||
return 'UNDEFINED'
|
||||
end
|
||||
|
||||
local function inspect(v)
|
||||
local s
|
||||
local t = type(v)
|
||||
if t == 'nil' then
|
||||
s = 'nil'
|
||||
elseif t ~= 'string' then
|
||||
s = vim.inspect(v)
|
||||
else
|
||||
s = tostring(v)
|
||||
end
|
||||
return s
|
||||
end
|
||||
|
||||
local function pathSep()
|
||||
return uv.os_uname().sysname == 'Windows_NT' and [[\]] or '/'
|
||||
end
|
||||
|
||||
local function init()
|
||||
local logDir = fn.stdpath('cache')
|
||||
Log.path = table.concat({logDir, 'ufo.log'}, pathSep())
|
||||
local logDateFmt = '%y-%m-%d %T'
|
||||
|
||||
fn.mkdir(logDir, 'p')
|
||||
levelMap = {TRACE = 0, DEBUG = 1, INFO = 2, WARN = 3, ERROR = 4}
|
||||
defaultLevel = 3
|
||||
Log.setLevel(vim.env.UFO_LOG)
|
||||
|
||||
for l in pairs(levelMap) do
|
||||
Log[l:lower()] = function(...)
|
||||
local argc = select('#', ...)
|
||||
if argc == 0 or levelMap[l] < levelNr then
|
||||
return
|
||||
end
|
||||
local msgTbl = {}
|
||||
for i = 1, argc do
|
||||
local arg = select(i, ...)
|
||||
table.insert(msgTbl, inspect(arg))
|
||||
end
|
||||
local msg = table.concat(msgTbl, ' ')
|
||||
local info = debug.getinfo(2, 'Sl')
|
||||
local linfo = info.short_src:match('[^/]*$') .. ':' .. info.currentline
|
||||
|
||||
local fp = assert(io.open(Log.path, 'a+'))
|
||||
local str = string.format('[%s] [%s] %s : %s\n', os.date(logDateFmt), l, linfo, msg)
|
||||
fp:write(str)
|
||||
fp:close()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
init()
|
||||
|
||||
return Log
|
||||
204
config/neovim/store/lazy-plugins/nvim-ufo/lua/ufo/main.lua
Normal file
204
config/neovim/store/lazy-plugins/nvim-ufo/lua/ufo/main.lua
Normal file
@ -0,0 +1,204 @@
|
||||
local M = {}
|
||||
local cmd = vim.cmd
|
||||
local api = vim.api
|
||||
|
||||
local event = require('ufo.lib.event')
|
||||
local utils = require('ufo.utils')
|
||||
local provider = require('ufo.provider')
|
||||
local fold = require('ufo.fold')
|
||||
local decorator = require('ufo.decorator')
|
||||
local highlight = require('ufo.highlight')
|
||||
local preview = require('ufo.preview')
|
||||
local disposable = require('ufo.lib.disposable')
|
||||
local bufmanager = require('ufo.bufmanager')
|
||||
|
||||
local enabled
|
||||
|
||||
---@type UfoDisposable[]
|
||||
local disposables = {}
|
||||
|
||||
local function createEvents()
|
||||
local gid = api.nvim_create_augroup('Ufo', {})
|
||||
api.nvim_create_autocmd({'BufWinEnter', 'TextChanged', 'BufWritePost'}, {
|
||||
group = gid,
|
||||
callback = function(ev)
|
||||
event:emit(ev.event, ev.buf)
|
||||
end
|
||||
})
|
||||
api.nvim_create_autocmd('WinClosed', {
|
||||
group = gid,
|
||||
callback = function(ev)
|
||||
event:emit(ev.event, tonumber(ev.file))
|
||||
end
|
||||
})
|
||||
api.nvim_create_autocmd('ModeChanged', {
|
||||
group = gid,
|
||||
pattern = '*:n',
|
||||
callback = function(ev)
|
||||
local previousMode = ev.match:match('(%a):')
|
||||
event:emit('ModeChangedToNormal', ev.buf, previousMode)
|
||||
end
|
||||
})
|
||||
api.nvim_create_autocmd('ColorScheme', {
|
||||
group = gid,
|
||||
callback = function(ev)
|
||||
event:emit(ev.event)
|
||||
end
|
||||
})
|
||||
api.nvim_create_autocmd('OptionSet', {
|
||||
group = gid,
|
||||
pattern = {'buftype', 'filetype', 'syntax', 'diff'},
|
||||
callback = function(ev)
|
||||
local bufnr = api.nvim_get_current_buf()
|
||||
local match = ev.match
|
||||
local e
|
||||
if match == 'buftype' then
|
||||
e = 'BufTypeChanged'
|
||||
elseif match == 'filetype' then
|
||||
e = 'FileTypeChanged'
|
||||
elseif match == 'syntax' then
|
||||
e = 'SyntaxChanged'
|
||||
elseif match == 'diff' then
|
||||
event:emit('DiffModeChanged', api.nvim_get_current_win(), vim.v.option_new, vim.v.option_old)
|
||||
return
|
||||
else
|
||||
error([[Didn't match any events!]])
|
||||
end
|
||||
event:emit(e, bufnr, vim.v.option_new, vim.v.option_old)
|
||||
end
|
||||
})
|
||||
return disposable:create(function()
|
||||
api.nvim_del_augroup_by_id(gid)
|
||||
end)
|
||||
end
|
||||
|
||||
local function createCommand()
|
||||
cmd([[
|
||||
com! UfoEnable lua require('ufo').enable()
|
||||
com! UfoDisable lua require('ufo').disable()
|
||||
com! UfoInspect lua require('ufo').inspect()
|
||||
com! UfoAttach lua require('ufo').attach()
|
||||
com! UfoDetach lua require('ufo').detach()
|
||||
com! UfoEnableFold lua require('ufo').enableFold()
|
||||
com! UfoDisableFold lua require('ufo').disableFold()
|
||||
]])
|
||||
end
|
||||
|
||||
function M.enable()
|
||||
if enabled then
|
||||
return false
|
||||
end
|
||||
local ns = api.nvim_create_namespace('ufo')
|
||||
createCommand()
|
||||
disposables = {}
|
||||
table.insert(disposables, createEvents())
|
||||
table.insert(disposables, highlight:initialize())
|
||||
table.insert(disposables, provider:initialize())
|
||||
table.insert(disposables, decorator:initialize(ns))
|
||||
table.insert(disposables, fold:initialize(ns))
|
||||
table.insert(disposables, preview:initialize(ns))
|
||||
table.insert(disposables, bufmanager:initialize())
|
||||
enabled = true
|
||||
return true
|
||||
end
|
||||
|
||||
function M.disable()
|
||||
if not enabled then
|
||||
return false
|
||||
end
|
||||
disposable.disposeAll(disposables)
|
||||
enabled = false
|
||||
return true
|
||||
end
|
||||
|
||||
function M.inspectBuf(bufnr)
|
||||
bufnr = bufnr or api.nvim_get_current_buf()
|
||||
local fb = fold.get(bufnr)
|
||||
if not fb then
|
||||
return
|
||||
end
|
||||
local msg = {}
|
||||
table.insert(msg, 'Buffer: ' .. bufnr)
|
||||
table.insert(msg, 'Fold Status: ' .. fb.status)
|
||||
local main = fb.providers[1]
|
||||
table.insert(msg, 'Main provider: ' .. (type(main) == 'function' and 'external' or main))
|
||||
if fb.providers[2] then
|
||||
table.insert(msg, 'Fallback provider: ' .. fb.providers[2])
|
||||
end
|
||||
table.insert(msg, 'Selected provider: ' .. (fb.selectedProvider or 'nil'))
|
||||
local winid = utils.getWinByBuf(bufnr)
|
||||
local curKind
|
||||
local curStartLine, curEndLine = 0, 0
|
||||
local kindSet = {}
|
||||
local lnum = api.nvim_win_get_cursor(winid)[1]
|
||||
for _, range in ipairs(fb.foldRanges) do
|
||||
local sl, el = range.startLine, range.endLine
|
||||
if curStartLine < sl and sl < lnum and lnum <= el + 1 then
|
||||
curStartLine, curEndLine = sl, el
|
||||
curKind = range.kind
|
||||
end
|
||||
if range.kind then
|
||||
kindSet[range.kind] = true
|
||||
end
|
||||
end
|
||||
local kinds = {}
|
||||
for kind in pairs(kindSet) do
|
||||
table.insert(kinds, kind)
|
||||
end
|
||||
table.insert(msg, 'Fold kinds: ' .. table.concat(kinds, ', '))
|
||||
if curStartLine ~= 0 or curEndLine ~= 0 then
|
||||
table.insert(msg, ('Cursor range: [%d, %d]'):format(curStartLine + 1, curEndLine + 1))
|
||||
end
|
||||
if curKind then
|
||||
table.insert(msg, 'Cursor kind: ' .. curKind)
|
||||
end
|
||||
return msg
|
||||
end
|
||||
|
||||
function M.hasAttached(bufnr)
|
||||
bufnr = bufnr or api.nvim_get_current_buf()
|
||||
local buf = bufmanager:get(bufnr)
|
||||
return buf and buf.attached
|
||||
end
|
||||
|
||||
function M.attach(bufnr)
|
||||
bufnr = bufnr or api.nvim_get_current_buf()
|
||||
bufmanager:attach(bufnr)
|
||||
end
|
||||
|
||||
function M.detach(bufnr)
|
||||
bufnr = bufnr or api.nvim_get_current_buf()
|
||||
bufmanager:detach(bufnr)
|
||||
end
|
||||
|
||||
function M.enableFold(bufnr)
|
||||
bufnr = bufnr or api.nvim_get_current_buf()
|
||||
local old = fold.setStatus(bufnr, 'start')
|
||||
fold.update(bufnr)
|
||||
return old
|
||||
end
|
||||
|
||||
function M.disableFold(bufnr)
|
||||
bufnr = bufnr or api.nvim_get_current_buf()
|
||||
return fold.setStatus(bufnr, 'stop')
|
||||
end
|
||||
|
||||
function M.foldtext()
|
||||
local fs, fe = vim.v.foldstart, vim.v.foldend
|
||||
local winid = api.nvim_get_current_win()
|
||||
local virtText = decorator:getVirtTextAndCloseFold(winid, fs, fe, false)
|
||||
if utils.has10() then
|
||||
return virtText
|
||||
end
|
||||
local text
|
||||
if next(virtText) then
|
||||
text = ''
|
||||
for _, chunk in ipairs(virtText) do
|
||||
text = text .. chunk[1]
|
||||
end
|
||||
text = utils.expandTab(text, vim.bo.ts)
|
||||
end
|
||||
return text or utils.expandTab(api.nvim_buf_get_lines(0, fs - 1, fs, true)[1], vim.bo.ts)
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,246 @@
|
||||
local event = require('ufo.lib.event')
|
||||
|
||||
local api = vim.api
|
||||
|
||||
---@class UfoBuffer
|
||||
---@field bufnr number
|
||||
---@field attached boolean
|
||||
---@field bt string
|
||||
---@field ft string
|
||||
---@field _syntax string
|
||||
---@field _lines table<number, string|boolean> A list of string or boolean
|
||||
local Buffer = {}
|
||||
|
||||
function Buffer:new(bufnr)
|
||||
local o = setmetatable({}, self)
|
||||
self.__index = self
|
||||
o.bufnr = bufnr
|
||||
o:reload()
|
||||
return o
|
||||
end
|
||||
|
||||
function Buffer:reload()
|
||||
self._changedtick = api.nvim_buf_get_changedtick(self.bufnr)
|
||||
self._lines = {}
|
||||
for _ = 1, api.nvim_buf_line_count(self.bufnr) do
|
||||
table.insert(self._lines, false)
|
||||
end
|
||||
end
|
||||
|
||||
function Buffer:dispose()
|
||||
self.attached = false
|
||||
self.bt = nil
|
||||
self.ft = nil
|
||||
end
|
||||
|
||||
function Buffer:attach()
|
||||
local bt = self:buftype()
|
||||
if bt == 'terminal' or bt == 'prompt' then
|
||||
return false
|
||||
end
|
||||
---@diagnostic disable: redefined-local, unused-local
|
||||
self.attached = api.nvim_buf_attach(self.bufnr, false, {
|
||||
on_lines = function(name, bufnr, changedtick, firstLine, lastLine,
|
||||
lastLineUpdated, byteCount)
|
||||
if not self.attached then
|
||||
return true
|
||||
end
|
||||
if firstLine == lastLine and lastLine == lastLineUpdated and byteCount == 0 then
|
||||
return
|
||||
end
|
||||
-- TODO upstream bug
|
||||
-- set foldmethod=expr && change lines from floating window will fire `on_lines`,
|
||||
-- skip if changedtick is unchanged
|
||||
if self._changedtick == changedtick then
|
||||
return
|
||||
end
|
||||
self._changedtick = changedtick
|
||||
self._lines = self:handleLinesChanged(self._lines, firstLine, lastLine, lastLineUpdated)
|
||||
event:emit('BufLinesChanged', bufnr, changedtick, firstLine, lastLine,
|
||||
lastLineUpdated, byteCount)
|
||||
end,
|
||||
on_changedtick = function(name, bufnr, changedtick)
|
||||
self._changedtick = changedtick
|
||||
end,
|
||||
on_detach = function(name, bufnr)
|
||||
event:emit('BufDetach', bufnr)
|
||||
end,
|
||||
on_reload = function(name, bufnr)
|
||||
self:reload()
|
||||
event:emit('BufReload', bufnr)
|
||||
end
|
||||
})
|
||||
---@diagnostic enable: redefined-local, unused-local
|
||||
if self.attached then
|
||||
event:emit('BufAttach', self.bufnr)
|
||||
end
|
||||
return self.attached
|
||||
end
|
||||
|
||||
---lower is less than or equal to lnum
|
||||
---@param lnum number
|
||||
---@param endLnum number
|
||||
---@return table[]
|
||||
function Buffer:buildMissingHunk(lnum, endLnum)
|
||||
local hunks = {}
|
||||
local s, e
|
||||
local cnt = 0
|
||||
for i = lnum, endLnum do
|
||||
if not self._lines[i] then
|
||||
cnt = cnt + 1
|
||||
if not s then
|
||||
s = i
|
||||
end
|
||||
e = i
|
||||
elseif e then
|
||||
table.insert(hunks, {s, e})
|
||||
s, e = nil, nil
|
||||
end
|
||||
end
|
||||
if e then
|
||||
table.insert(hunks, {s, e})
|
||||
end
|
||||
-- scan backward
|
||||
if #hunks > 0 then
|
||||
local firstHunk = hunks[1]
|
||||
local fhLnum = firstHunk[1]
|
||||
if fhLnum == lnum then
|
||||
local i = lnum - 1
|
||||
while i > 0 do
|
||||
if self._lines[i] then
|
||||
break
|
||||
end
|
||||
i = i - 1
|
||||
end
|
||||
fhLnum = i + 1
|
||||
cnt = cnt + lnum - fhLnum
|
||||
firstHunk[1] = fhLnum
|
||||
lnum = fhLnum
|
||||
end
|
||||
end
|
||||
if cnt > (endLnum - lnum) / 4 and #hunks > 2 then
|
||||
hunks = {{lnum, endLnum}}
|
||||
end
|
||||
return hunks
|
||||
end
|
||||
|
||||
function Buffer:sliceLines(lines, firstLine, lastLine, lastLineUpdated)
|
||||
local newLines = {}
|
||||
for i = 1, firstLine do
|
||||
table.insert(newLines, lines[i])
|
||||
end
|
||||
for _ = firstLine + 1, lastLineUpdated do
|
||||
table.insert(newLines, false)
|
||||
end
|
||||
for i = lastLine + 1, #lines do
|
||||
table.insert(newLines, lines[i])
|
||||
end
|
||||
return newLines
|
||||
end
|
||||
|
||||
---
|
||||
---@param lines any[]
|
||||
---@param firstLine number
|
||||
---@param lastLine number
|
||||
---@param lastLineUpdated number
|
||||
---@return any[]
|
||||
function Buffer:handleLinesChanged(lines, firstLine, lastLine, lastLineUpdated)
|
||||
local delta = lastLineUpdated - lastLine
|
||||
if delta == 0 then
|
||||
for i = firstLine + 1, lastLine do
|
||||
lines[i] = false
|
||||
end
|
||||
elseif delta > 0 then
|
||||
if #lines > 800 and delta > 10 then
|
||||
lines = self:sliceLines(lines, firstLine, lastLine, lastLineUpdated)
|
||||
else
|
||||
for i = firstLine + 1, lastLine do
|
||||
lines[i] = false
|
||||
end
|
||||
for i = firstLine + 1, firstLine + delta do
|
||||
table.insert(lines, i, false)
|
||||
end
|
||||
end
|
||||
else
|
||||
if #lines > 800 and -delta > 10 then
|
||||
lines = self:sliceLines(lines, firstLine, lastLine, lastLineUpdated)
|
||||
else
|
||||
for i = lastLine, lastLineUpdated + 1, -1 do
|
||||
table.remove(lines, i)
|
||||
end
|
||||
for i = firstLine + 1, lastLineUpdated do
|
||||
lines[i] = false
|
||||
end
|
||||
end
|
||||
if #lines == 0 then
|
||||
lines = {false}
|
||||
end
|
||||
end
|
||||
return lines
|
||||
end
|
||||
|
||||
---
|
||||
---@return number
|
||||
function Buffer:changedtick()
|
||||
return self._changedtick
|
||||
end
|
||||
|
||||
---
|
||||
---@return string
|
||||
function Buffer:filetype()
|
||||
if self.attached and not self.ft then
|
||||
self.ft = vim.bo[self.bufnr].ft
|
||||
end
|
||||
return self.ft
|
||||
end
|
||||
|
||||
---
|
||||
---@return string
|
||||
function Buffer:buftype()
|
||||
if self.attached and not self.bt then
|
||||
self.bt = vim.bo[self.bufnr].bt
|
||||
end
|
||||
return self.bt
|
||||
end
|
||||
|
||||
function Buffer:syntax()
|
||||
if self.attached and not self.syntax then
|
||||
self._syntax = vim.bo[self.bufnr].syntax
|
||||
end
|
||||
return self._syntax
|
||||
end
|
||||
|
||||
---
|
||||
---@return number
|
||||
function Buffer:lineCount()
|
||||
if self:buftype() == 'quickfix' then
|
||||
self:reload()
|
||||
end
|
||||
return #self._lines
|
||||
end
|
||||
|
||||
---@param lnum number
|
||||
---@param endLnum? number
|
||||
---@return string[]
|
||||
function Buffer:lines(lnum, endLnum)
|
||||
local lineCount = self:lineCount()
|
||||
assert(lineCount >= lnum, 'index out of bounds')
|
||||
local res = {}
|
||||
endLnum = endLnum and endLnum or lnum
|
||||
if endLnum < 0 then
|
||||
endLnum = lineCount + endLnum + 1
|
||||
end
|
||||
for _, hunk in ipairs(self:buildMissingHunk(lnum, endLnum)) do
|
||||
local hs, he = hunk[1], hunk[2]
|
||||
local lines = api.nvim_buf_get_lines(self.bufnr, hs - 1, he, true)
|
||||
for i = hs, he do
|
||||
self._lines[i] = lines[i - hs + 1]
|
||||
end
|
||||
end
|
||||
for i = lnum, endLnum do
|
||||
table.insert(res, self._lines[i])
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
return Buffer
|
||||
@ -0,0 +1,260 @@
|
||||
local api = vim.api
|
||||
local cmd = vim.cmd
|
||||
|
||||
local utils = require('ufo.utils')
|
||||
local buffer = require('ufo.model.buffer')
|
||||
local foldedline = require('ufo.model.foldedline')
|
||||
|
||||
---@class UfoFoldBuffer
|
||||
---@field bufnr number
|
||||
---@field buf UfoBuffer
|
||||
---@field ns number
|
||||
---@field status string|'start'|'pending'|'stop'
|
||||
---@field version number
|
||||
---@field requestCount number
|
||||
---@field foldRanges UfoFoldingRange[]
|
||||
---@field foldedLines table<number, UfoFoldedLine|boolean> A list of UfoFoldedLine or boolean
|
||||
---@field foldedLineCount number
|
||||
---@field providers table
|
||||
---@field scanned boolean
|
||||
---@field selectedProvider string
|
||||
local FoldBuffer = setmetatable({}, buffer)
|
||||
FoldBuffer.__index = FoldBuffer
|
||||
|
||||
---@param buf UfoBuffer
|
||||
---@return UfoFoldBuffer
|
||||
function FoldBuffer:new(buf, ns)
|
||||
local o = setmetatable({}, self)
|
||||
self.__index = self
|
||||
o.bufnr = buf.bufnr
|
||||
o.buf = buf
|
||||
o.ns = ns
|
||||
o:reset()
|
||||
return o
|
||||
end
|
||||
|
||||
function FoldBuffer:dispose()
|
||||
self:resetFoldedLines(true)
|
||||
self:reset()
|
||||
end
|
||||
|
||||
function FoldBuffer:changedtick()
|
||||
return self.buf:changedtick()
|
||||
end
|
||||
|
||||
function FoldBuffer:filetype()
|
||||
return self.buf:filetype()
|
||||
end
|
||||
|
||||
function FoldBuffer:buftype()
|
||||
return self.buf:buftype()
|
||||
end
|
||||
|
||||
function FoldBuffer:syntax()
|
||||
return self.buf:syntax()
|
||||
end
|
||||
|
||||
function FoldBuffer:lineCount()
|
||||
return self.buf:lineCount()
|
||||
end
|
||||
|
||||
---
|
||||
---@param lnum number
|
||||
---@param endLnum? number
|
||||
---@return string[]
|
||||
function FoldBuffer:lines(lnum, endLnum)
|
||||
return self.buf:lines(lnum, endLnum)
|
||||
end
|
||||
|
||||
function FoldBuffer:reset()
|
||||
self.status = 'start'
|
||||
self.providers = nil
|
||||
self.selectedProvider = nil
|
||||
self.version = 0
|
||||
self.requestCount = 0
|
||||
self.foldRanges = {}
|
||||
self:resetFoldedLines()
|
||||
self.scanned = false
|
||||
end
|
||||
|
||||
function FoldBuffer:resetFoldedLines(clear)
|
||||
self.foldedLines = {}
|
||||
self.foldedLineCount = 0
|
||||
for _ = 1, self:lineCount() do
|
||||
table.insert(self.foldedLines, false)
|
||||
end
|
||||
if clear then
|
||||
pcall(api.nvim_buf_clear_namespace, self.bufnr, self.ns, 0, -1)
|
||||
end
|
||||
end
|
||||
|
||||
function FoldBuffer:foldedLine(lnum)
|
||||
local fl = self.foldedLines[lnum]
|
||||
if not fl then
|
||||
return
|
||||
end
|
||||
return fl
|
||||
end
|
||||
|
||||
---
|
||||
---@param winid number
|
||||
---@param lnum number 1-index
|
||||
---@return UfoFoldingRangeKind|''
|
||||
function FoldBuffer:lineKind(winid, lnum)
|
||||
if utils.isDiffOrMarkerFold(winid) then
|
||||
return ''
|
||||
end
|
||||
local row = lnum - 1
|
||||
for _, range in ipairs(self.foldRanges) do
|
||||
if row >= range.startLine and row <= range.endLine then
|
||||
return range.kind
|
||||
end
|
||||
end
|
||||
return ''
|
||||
end
|
||||
|
||||
function FoldBuffer:handleFoldedLinesChanged(first, last, lastUpdated)
|
||||
if self.foldedLineCount == 0 then
|
||||
return
|
||||
end
|
||||
local didOpen = false
|
||||
for i = first + 1, last do
|
||||
didOpen = self:openFold(i) or didOpen
|
||||
end
|
||||
if didOpen and lastUpdated > first then
|
||||
local winid = utils.getWinByBuf(self.bufnr)
|
||||
if winid ~= -1 then
|
||||
utils.winCall(winid, function()
|
||||
cmd(('sil! %d,%dfoldopen!'):format(first + 1, lastUpdated))
|
||||
end)
|
||||
end
|
||||
end
|
||||
self.foldedLines = self.buf:handleLinesChanged(self.foldedLines, first, last, lastUpdated)
|
||||
end
|
||||
|
||||
function FoldBuffer:acquireRequest()
|
||||
self.requestCount = self.requestCount + 1
|
||||
end
|
||||
|
||||
function FoldBuffer:releaseRequest()
|
||||
if self.requestCount > 0 then
|
||||
self.requestCount = self.requestCount - 1
|
||||
end
|
||||
end
|
||||
|
||||
function FoldBuffer:requested()
|
||||
return self.requestCount > 0
|
||||
end
|
||||
|
||||
---
|
||||
---@param lnum number
|
||||
---@return boolean
|
||||
function FoldBuffer:lineIsClosed(lnum)
|
||||
return self:foldedLine(lnum) ~= nil
|
||||
end
|
||||
|
||||
---
|
||||
---@param winid number
|
||||
function FoldBuffer:syncFoldedLines(winid)
|
||||
for lnum, fl in ipairs(self.foldedLines) do
|
||||
if fl and utils.foldClosed(winid, lnum) == -1 then
|
||||
self:openFold(lnum)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function FoldBuffer:getRangesFromExtmarks()
|
||||
local res = {}
|
||||
if self.foldedLineCount == 0 then
|
||||
return res
|
||||
end
|
||||
local marks = api.nvim_buf_get_extmarks(self.bufnr, self.ns, 0, -1, {details = true})
|
||||
for _, m in ipairs(marks) do
|
||||
local row, endRow = m[2], m[4].end_row
|
||||
-- extmark may give backward range
|
||||
if row > endRow then
|
||||
error(('expected forward range, got row: %d, endRow: %d'):format(row, endRow))
|
||||
end
|
||||
table.insert(res, {row, endRow})
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
---
|
||||
---@param lnum number
|
||||
---@return boolean
|
||||
function FoldBuffer:openFold(lnum)
|
||||
local folded = false
|
||||
local fl = self.foldedLines[lnum]
|
||||
if fl then
|
||||
folded = self.foldedLines[lnum] ~= nil
|
||||
fl:deleteExtmark()
|
||||
self.foldedLineCount = self.foldedLineCount - 1
|
||||
self.foldedLines[lnum] = false
|
||||
end
|
||||
return folded
|
||||
end
|
||||
|
||||
---
|
||||
---@param lnum number
|
||||
---@param endLnum number
|
||||
---@param text? string
|
||||
---@param virtText? string
|
||||
---@param width? number
|
||||
---@param doRender? boolean
|
||||
---@return boolean
|
||||
function FoldBuffer:closeFold(lnum, endLnum, text, virtText, width, doRender)
|
||||
local lineCount = self:lineCount()
|
||||
endLnum = math.min(endLnum, lineCount)
|
||||
if endLnum < lnum then
|
||||
return false
|
||||
end
|
||||
local fl = self.foldedLines[lnum]
|
||||
if fl then
|
||||
if width and fl:widthChanged(width) then
|
||||
fl.width = width
|
||||
end
|
||||
if text and fl:textChanged(text) then
|
||||
fl.text = text
|
||||
end
|
||||
if not width and not text then
|
||||
return false
|
||||
end
|
||||
else
|
||||
if self.foldedLineCount == 0 and lineCount ~= #self.foldedLines then
|
||||
self:resetFoldedLines()
|
||||
end
|
||||
fl = foldedline:new(self.bufnr, self.ns, text, width)
|
||||
self.foldedLineCount = self.foldedLineCount + 1
|
||||
self.foldedLines[lnum] = fl
|
||||
end
|
||||
fl:updateVirtText(lnum, endLnum, virtText, doRender)
|
||||
return true
|
||||
end
|
||||
|
||||
function FoldBuffer:scanFoldedRanges(winid, s, e)
|
||||
local res = {}
|
||||
local stack = {}
|
||||
s, e = s or 1, e or self:lineCount()
|
||||
utils.winCall(winid, function()
|
||||
for i = s, e do
|
||||
local skip = false
|
||||
while #stack > 0 and i >= stack[#stack] do
|
||||
local endLnum = table.remove(stack)
|
||||
cmd(endLnum .. 'foldclose')
|
||||
skip = true
|
||||
end
|
||||
if not skip then
|
||||
local endLnum = utils.foldClosedEnd(winid, i)
|
||||
if endLnum ~= -1 then
|
||||
table.insert(stack, endLnum)
|
||||
table.insert(res, {i - 1, endLnum - 1})
|
||||
cmd(i .. 'foldopen')
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
return res
|
||||
end
|
||||
|
||||
return FoldBuffer
|
||||
@ -0,0 +1,77 @@
|
||||
local utils = require('ufo.utils')
|
||||
local api = vim.api
|
||||
|
||||
---@class UfoFoldedLine
|
||||
---@field id number
|
||||
---@field bufnr number
|
||||
---@field ns number
|
||||
---@field rendered boolean
|
||||
---@field text? string
|
||||
---@field width? number
|
||||
---@field virtText? UfoExtmarkVirtTextChunk[]
|
||||
local FoldedLine = {}
|
||||
|
||||
function FoldedLine:new(bufnr, ns, text, width)
|
||||
local o = setmetatable({}, self)
|
||||
self.__index = self
|
||||
o.id = nil
|
||||
o.bufnr = bufnr
|
||||
o.ns = ns
|
||||
o.text = text
|
||||
o.width = width
|
||||
o.rendered = false
|
||||
o.virtText = nil
|
||||
return o
|
||||
end
|
||||
|
||||
---
|
||||
---@param width number
|
||||
---@return boolean
|
||||
function FoldedLine:widthChanged(width)
|
||||
return self.width ~= width
|
||||
end
|
||||
|
||||
function FoldedLine:textChanged(text)
|
||||
return self.text ~= text
|
||||
end
|
||||
|
||||
function FoldedLine:hasRendered()
|
||||
return self.rendered == true
|
||||
end
|
||||
|
||||
function FoldedLine:deleteExtmark()
|
||||
if self.id then
|
||||
api.nvim_buf_del_extmark(self.bufnr, self.ns, self.id)
|
||||
end
|
||||
end
|
||||
|
||||
function FoldedLine:updateVirtText(lnum, endLnum, virtText, doRender)
|
||||
if doRender then
|
||||
local opts = {
|
||||
id = self.id,
|
||||
end_row = endLnum - 1,
|
||||
end_col = 0,
|
||||
priority = 10,
|
||||
hl_mode = 'combine'
|
||||
}
|
||||
if not utils.has10() then
|
||||
opts.virt_text = virtText
|
||||
opts.virt_text_win_col = 0
|
||||
end
|
||||
self.id = api.nvim_buf_set_extmark(self.bufnr, self.ns, lnum - 1, 0, opts)
|
||||
end
|
||||
self.rendered = doRender
|
||||
self.virtText = virtText
|
||||
end
|
||||
|
||||
function FoldedLine:range()
|
||||
if not self.id then
|
||||
return 0, 0
|
||||
end
|
||||
local mark = api.nvim_buf_get_extmark_by_id(self.bufnr, self.ns, self.id, {details = true})
|
||||
local row, details = mark[1], mark[3]
|
||||
local endRow = details.end_row
|
||||
return row + 1, endRow + 1
|
||||
end
|
||||
|
||||
return FoldedLine
|
||||
@ -0,0 +1,35 @@
|
||||
---@alias UfoFoldingRangeKind
|
||||
---| 'comment'
|
||||
---| 'imports'
|
||||
---| 'region'
|
||||
|
||||
---@class UfoFoldingRange
|
||||
---@field startLine number
|
||||
---@field startCharacter? number
|
||||
---@field endLine number
|
||||
---@field endCharacter? number
|
||||
---@field kind? UfoFoldingRangeKind
|
||||
local FoldingRange = {}
|
||||
|
||||
function FoldingRange.new(startLine, endLine, startCharacter, endCharacter, kind)
|
||||
local o = {}
|
||||
o.startLine = startLine
|
||||
o.endLine = endLine
|
||||
o.startCharacter = startCharacter
|
||||
o.endCharacter = endCharacter
|
||||
o.kind = kind
|
||||
return o
|
||||
end
|
||||
|
||||
---
|
||||
---@param ranges UfoFoldingRange
|
||||
function FoldingRange.sortRanges(ranges)
|
||||
if jit then
|
||||
return
|
||||
end
|
||||
table.sort(ranges, function(a, b)
|
||||
return a.startLine == b.startLine and a.endLine < b.endLine or a.startLine > b.startLine
|
||||
end)
|
||||
end
|
||||
|
||||
return FoldingRange
|
||||
@ -0,0 +1,119 @@
|
||||
local api = vim.api
|
||||
local fn = vim.fn
|
||||
local utils = require('ufo.utils')
|
||||
|
||||
local LSize
|
||||
|
||||
---
|
||||
---@class UfoLineSizeBase
|
||||
---@field winid number
|
||||
---@field foldenable boolean
|
||||
---@field foldClosePairs table<number, number[]>
|
||||
---@field sizes table<number, number>
|
||||
local LBase = {}
|
||||
|
||||
---
|
||||
---@param sizes table<number, number>
|
||||
---@return UfoLineSizeBase
|
||||
function LBase:new(winid, sizes)
|
||||
local o = setmetatable({}, self)
|
||||
self.__index = self
|
||||
o.winid = winid
|
||||
o.foldenable = vim.wo[winid].foldenable
|
||||
o.foldClosePairs = {}
|
||||
o.sizes = sizes
|
||||
return o
|
||||
end
|
||||
|
||||
---
|
||||
---@param lnum number
|
||||
---@return number
|
||||
function LBase:size(lnum)
|
||||
return self.sizes[lnum]
|
||||
end
|
||||
|
||||
---
|
||||
---@class UfoLineSizeFFI : UfoLineSizeBase
|
||||
---@field private _wffi UfoWffi
|
||||
local LFFI = setmetatable({}, {__index = LBase})
|
||||
|
||||
---
|
||||
---@return UfoLineSizeFFI
|
||||
function LFFI:new(winid)
|
||||
local super = LBase:new(winid, setmetatable({}, {
|
||||
__index = function(t, i)
|
||||
local v = self._wffi.plinesWin(winid, i)
|
||||
rawset(t, i, v)
|
||||
return v
|
||||
end
|
||||
}))
|
||||
local o = setmetatable(super, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end
|
||||
|
||||
---
|
||||
---@param lnum number
|
||||
---@param winheight boolean
|
||||
---@return number
|
||||
function LFFI:nofillSize(lnum, winheight)
|
||||
winheight = winheight or true
|
||||
return self._wffi.plinesWinNofill(self.winid, lnum, winheight)
|
||||
end
|
||||
|
||||
---
|
||||
---@param lnum number
|
||||
---@return number
|
||||
function LFFI:fillSize(lnum)
|
||||
return self:size(lnum) - self:nofillSize(lnum, true)
|
||||
end
|
||||
|
||||
---
|
||||
---@class UfoLineSizeNonFFI : UfoLineSizeBase
|
||||
---@field perLineWidth number
|
||||
local LNonFFI = setmetatable({}, {__index = LBase})
|
||||
|
||||
---
|
||||
---@return UfoLineSizeNonFFI
|
||||
function LNonFFI:new(winid)
|
||||
local wrap = vim.wo[winid].wrap
|
||||
local perLineWidth = api.nvim_win_get_width(winid) - utils.textoff(winid)
|
||||
local super = LBase:new(winid, setmetatable({}, {
|
||||
__index = function(t, i)
|
||||
local v
|
||||
if wrap then
|
||||
v = math.ceil(math.max(fn.virtcol({i, '$'}) - 1, 1) / perLineWidth)
|
||||
else
|
||||
v = 1
|
||||
end
|
||||
rawset(t, i, v)
|
||||
return v
|
||||
end
|
||||
}))
|
||||
local o = setmetatable(super, self)
|
||||
o.perLineWidth = perLineWidth
|
||||
self.__index = self
|
||||
return o
|
||||
end
|
||||
|
||||
LNonFFI.nofillSize = LNonFFI.size
|
||||
|
||||
---
|
||||
---@param _ any
|
||||
---@return number
|
||||
function LNonFFI.fillSize(_)
|
||||
return 0
|
||||
end
|
||||
|
||||
local function init()
|
||||
if jit ~= nil then
|
||||
LFFI._wffi = require('ufo.wffi')
|
||||
LSize = LFFI
|
||||
else
|
||||
LSize = LNonFFI
|
||||
end
|
||||
end
|
||||
|
||||
init()
|
||||
|
||||
return LSize
|
||||
@ -0,0 +1,111 @@
|
||||
local utils = require('ufo.utils')
|
||||
|
||||
local api = vim.api
|
||||
local cmd = vim.cmd
|
||||
|
||||
---@class UfoWindow
|
||||
---@field winid number
|
||||
---@field bufnr number
|
||||
---@field lastBufnr number
|
||||
---@field foldbuffer UfoFoldBuffer
|
||||
---@field lastCurLnum number
|
||||
---@field lastCurFoldStart number
|
||||
---@field lastCurFoldEnd number
|
||||
---@field isCurFoldHighlighted boolean
|
||||
---@field foldedPairs table<number,number>
|
||||
---@field foldedTextMaps table<number, table>
|
||||
---@field _cursor number[]
|
||||
---@field _width number
|
||||
---@field _concealLevel boolean
|
||||
local Window = {}
|
||||
Window.__index = Window
|
||||
|
||||
function Window:new(winid)
|
||||
local o = self == Window and setmetatable({}, self) or self
|
||||
o.winid = winid
|
||||
o.bufnr = 0
|
||||
o.lastCurLnum = -1
|
||||
o.lastCurFoldStart = 0
|
||||
o.lastCurFoldEnd = 0
|
||||
o.isCurFoldHighlighted = false
|
||||
return o
|
||||
end
|
||||
|
||||
--- Must invoke in on_win cycle
|
||||
---@param bufnr number
|
||||
---@param fb UfoFoldBuffer
|
||||
function Window:onWin(bufnr, fb)
|
||||
self.lastBufnr = self.bufnr
|
||||
self.bufnr = bufnr
|
||||
self.foldbuffer = fb
|
||||
self.foldedPairs = {}
|
||||
self.foldedTextMaps = {}
|
||||
self._cursor = nil
|
||||
self._width = nil
|
||||
self._concealLevel = nil
|
||||
end
|
||||
|
||||
function Window:cursor()
|
||||
if not self._cursor then
|
||||
self._cursor = api.nvim_win_get_cursor(self.winid)
|
||||
end
|
||||
return self._cursor
|
||||
end
|
||||
|
||||
function Window:textWidth()
|
||||
if not self._width then
|
||||
local textoff = utils.textoff(self.winid)
|
||||
self._width = api.nvim_win_get_width(self.winid) - textoff
|
||||
end
|
||||
return self._width
|
||||
end
|
||||
|
||||
function Window:concealLevel()
|
||||
if not self._concealLevel then
|
||||
self._concealLevel = vim.wo[self.winid].conceallevel
|
||||
end
|
||||
return self._concealLevel
|
||||
end
|
||||
|
||||
function Window:foldEndLnum(fs)
|
||||
local fe = self.foldedPairs[fs]
|
||||
if not fe then
|
||||
fe = utils.foldClosedEnd(self.winid, fs)
|
||||
self.foldedPairs[fs] = fe
|
||||
end
|
||||
return fe
|
||||
end
|
||||
|
||||
function Window:setCursorFoldedLineHighlight()
|
||||
local res = false
|
||||
if not self.isCurFoldHighlighted then
|
||||
-- TODO
|
||||
-- Upstream bug: Error in decoration provider (UNKNOWN PLUGIN).end
|
||||
require('promise').resolve():thenCall(function()
|
||||
utils.winCall(self.winid, function()
|
||||
-- TODO
|
||||
-- Upstream bug: `setl winhl` change curswant
|
||||
local view = utils.saveView(0)
|
||||
cmd('setl winhl+=CursorLine:UfoCursorFoldedLine')
|
||||
utils.restView(0, view)
|
||||
end)
|
||||
end)
|
||||
self.isCurFoldHighlighted = true
|
||||
res = true
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
function Window:clearCursorFoldedLineHighlight()
|
||||
local res = false
|
||||
if self.isCurFoldHighlighted or self.lastBufnr ~= 0 and self.lastBufnr ~= self.bufnr then
|
||||
utils.winCall(self.winid, function()
|
||||
cmd('setl winhl-=CursorLine:UfoCursorFoldedLine')
|
||||
end)
|
||||
self.isCurFoldHighlighted = false
|
||||
res = true
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
return Window
|
||||
@ -0,0 +1,247 @@
|
||||
local api = vim.api
|
||||
local fn = vim.fn
|
||||
local cmd = vim.cmd
|
||||
|
||||
local utils = require('ufo.utils')
|
||||
|
||||
--- Singleton
|
||||
---@class UfoPreviewFloatWin
|
||||
---@field config table
|
||||
---@field ns number
|
||||
---@field winid number
|
||||
---@field bufnr number
|
||||
---@field bufferName string
|
||||
---@field width number
|
||||
---@field height number
|
||||
---@field anchor string|'SW'|'NW'
|
||||
---@field winblend number
|
||||
---@field border string|'none'|'single'|'double'|'rounded'|'solid'|'shadow'|string[]
|
||||
---@field lineCount number
|
||||
---@field showScrollBar boolean
|
||||
---@field topline number
|
||||
---@field virtText UfoExtmarkVirtTextChunk[]
|
||||
local FloatWin = {}
|
||||
|
||||
local defaultBorder = {
|
||||
none = {'', '', '', '', '', '', '', ''},
|
||||
single = {'┌', '─', '┐', '│', '┘', '─', '└', '│'},
|
||||
double = {'╔', '═', '╗', '║', '╝', '═', '╚', '║'},
|
||||
rounded = {'╭', '─', '╮', '│', '╯', '─', '╰', '│'},
|
||||
solid = {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '},
|
||||
shadow = {'', '', {' ', 'FloatShadowThrough'}, {' ', 'FloatShadow'},
|
||||
{' ', 'FloatShadow'}, {' ', 'FloatShadow'}, {' ', 'FloatShadowThrough'}, ''},
|
||||
}
|
||||
|
||||
local function borderHasLine(border, index)
|
||||
local s = border[index]
|
||||
if type(s) == 'string' then
|
||||
return s ~= ''
|
||||
else
|
||||
return s[1] ~= ''
|
||||
end
|
||||
end
|
||||
|
||||
function FloatWin:borderHasUpLine()
|
||||
return borderHasLine(self.border, 2)
|
||||
end
|
||||
|
||||
function FloatWin:borderHasRightLine()
|
||||
return borderHasLine(self.border, 4)
|
||||
end
|
||||
|
||||
function FloatWin:borderHasBottomLine()
|
||||
return borderHasLine(self.border, 6)
|
||||
end
|
||||
|
||||
function FloatWin:borderHasLeftLine()
|
||||
return borderHasLine(self.border, 8)
|
||||
end
|
||||
|
||||
function FloatWin:build(winid, height, border, isAbove)
|
||||
local winfo = utils.getWinInfo(winid)
|
||||
local aboveLine = utils.winCall(winid, fn.winline) - 1
|
||||
local belowLine = winfo.height - aboveLine
|
||||
border = type(border) == 'string' and defaultBorder[border] or border
|
||||
self.border = vim.deepcopy(border)
|
||||
if fn.screencol() == 1 then
|
||||
self.border[1], self.border[7], self.border[8] = '', '', ''
|
||||
end
|
||||
local row, col = 0, 0
|
||||
if isAbove then
|
||||
if aboveLine < height and belowLine > aboveLine then
|
||||
self.height = math.min(height, belowLine)
|
||||
row = self.height - aboveLine
|
||||
if self:borderHasBottomLine() then
|
||||
row = row + 1
|
||||
end
|
||||
if self:borderHasUpLine() then
|
||||
row = row + 1
|
||||
end
|
||||
else
|
||||
self.height = math.min(height, aboveLine)
|
||||
row = 1
|
||||
end
|
||||
else
|
||||
if belowLine < height and belowLine < aboveLine then
|
||||
self.height = math.min(height, aboveLine)
|
||||
row = belowLine - self.height
|
||||
else
|
||||
if self:borderHasUpLine() and fn.screenrow() == 1 and aboveLine == 0 then
|
||||
self.border[1], self.border[2], self.border[3] = '', '', ''
|
||||
end
|
||||
self.height = math.min(height, belowLine)
|
||||
row = 0
|
||||
end
|
||||
if self:borderHasUpLine() then
|
||||
row = row - 1
|
||||
end
|
||||
end
|
||||
self.width = winfo.width - winfo.textoff
|
||||
if self:borderHasLeftLine() then
|
||||
col = col - 1
|
||||
end
|
||||
if self:borderHasRightLine() then
|
||||
self.width = self.width - 1
|
||||
end
|
||||
local anchor = isAbove and 'SW' or 'NW'
|
||||
return {
|
||||
border = self.border,
|
||||
relative = 'cursor',
|
||||
focusable = true,
|
||||
width = self.width,
|
||||
height = self.height,
|
||||
anchor = anchor,
|
||||
row = row,
|
||||
col = col,
|
||||
noautocmd = true,
|
||||
zindex = 51
|
||||
}
|
||||
end
|
||||
|
||||
function FloatWin:validate()
|
||||
return utils.isWinValid(rawget(self, 'winid'))
|
||||
end
|
||||
|
||||
function FloatWin.getConfig()
|
||||
local config = api.nvim_win_get_config(FloatWin.winid)
|
||||
local row, col = config.row, config.col
|
||||
-- row and col are a table value converted from the floating-point
|
||||
if type(row) == 'table' then
|
||||
---@diagnostic disable-next-line: need-check-nil, inject-field
|
||||
config.row, config.col = tonumber(row[vim.val_idx]), tonumber(col[vim.val_idx])
|
||||
end
|
||||
return config
|
||||
end
|
||||
|
||||
function FloatWin:open(wopts, enter)
|
||||
if enter == nil then
|
||||
enter = false
|
||||
end
|
||||
self.winid = api.nvim_open_win(self:getBufnr(), enter, wopts)
|
||||
return self.winid
|
||||
end
|
||||
|
||||
function FloatWin:close()
|
||||
if self:validate() then
|
||||
api.nvim_win_close(self.winid, true)
|
||||
end
|
||||
rawset(self, 'winid', nil)
|
||||
end
|
||||
|
||||
function FloatWin:call(executor)
|
||||
utils.winCall(self.winid, executor)
|
||||
end
|
||||
|
||||
function FloatWin:getBufnr()
|
||||
if utils.isBufLoaded(rawget(self, 'bufnr')) then
|
||||
return self.bufnr
|
||||
end
|
||||
local bufnr = fn.bufnr('^' .. self.bufferName .. '$')
|
||||
if bufnr > 0 then
|
||||
self.bufnr = bufnr
|
||||
else
|
||||
self.bufnr = api.nvim_create_buf(false, true)
|
||||
api.nvim_buf_set_name(self.bufnr, self.bufferName)
|
||||
vim.bo[self.bufnr].bufhidden = 'hide'
|
||||
end
|
||||
return self.bufnr
|
||||
end
|
||||
|
||||
function FloatWin:setContent(text)
|
||||
vim.bo[self.bufnr].modifiable = true
|
||||
api.nvim_buf_set_lines(self.bufnr, 0, -1, true, text)
|
||||
vim.bo[self.bufnr].modifiable = false
|
||||
self.lineCount = #text
|
||||
self.showScrollBar = self.lineCount > self.height
|
||||
api.nvim_win_set_cursor(self.winid, {1, 0})
|
||||
cmd('norm! ze')
|
||||
end
|
||||
|
||||
---
|
||||
---@param winid number
|
||||
---@param targetHeight number
|
||||
---@param enter boolean
|
||||
---@param isAbove boolean
|
||||
---@param postHandle? fun()
|
||||
---@return number
|
||||
function FloatWin:display(winid, targetHeight, enter, isAbove, postHandle)
|
||||
local height = math.min(self.config.maxheight, targetHeight)
|
||||
local wopts = self:build(winid, height, self.config.border, isAbove)
|
||||
if self:validate() then
|
||||
wopts.noautocmd = nil
|
||||
api.nvim_win_set_config(self.winid, wopts)
|
||||
if enter == true then
|
||||
api.nvim_set_current_win(self.winid)
|
||||
end
|
||||
else
|
||||
self:open(wopts, enter)
|
||||
self.winblend = self.config.winblend
|
||||
local wo = vim.wo[self.winid]
|
||||
wo.wrap = false
|
||||
wo.spell, wo.list = false, true
|
||||
wo.nu, wo.rnu = false, false
|
||||
wo.fen, wo.fdm, wo.fdc = false, 'manual', '0'
|
||||
wo.cursorline = enter == true
|
||||
wo.signcolumn, wo.colorcolumn = 'no', ''
|
||||
if wo.so == 0 then
|
||||
wo.so = 1
|
||||
end
|
||||
wo.winhl = self.config.winhighlight
|
||||
wo.winblend = self.winblend
|
||||
end
|
||||
if type(postHandle) == 'function' then
|
||||
postHandle()
|
||||
end
|
||||
return self.winid
|
||||
end
|
||||
|
||||
function FloatWin:refreshTopline()
|
||||
self.topline = fn.line('w0', self.winid)
|
||||
end
|
||||
|
||||
function FloatWin:initialize(ns, config)
|
||||
self.ns = ns
|
||||
local border = config.border
|
||||
local tBorder = type(border)
|
||||
if tBorder == 'string' then
|
||||
if not defaultBorder[border] then
|
||||
error(([[border string must be one of {%s}]])
|
||||
:format(table.concat(vim.tbl_keys(defaultBorder), ',')))
|
||||
end
|
||||
elseif tBorder == 'table' then
|
||||
assert(#border == 8, 'only support 8 chars for the border')
|
||||
else
|
||||
error('error border config')
|
||||
end
|
||||
self.bufferName = 'UfoPreviewFloatWin'
|
||||
self.config = config
|
||||
return self
|
||||
end
|
||||
|
||||
function FloatWin:dispose()
|
||||
self:close()
|
||||
pcall(api.nvim_buf_delete, self.bufnr, {force = true})
|
||||
self.bufnr = nil
|
||||
end
|
||||
|
||||
return FloatWin
|
||||
@ -0,0 +1,364 @@
|
||||
local api = vim.api
|
||||
local cmd = vim.cmd
|
||||
local fn = vim.fn
|
||||
|
||||
local promise = require('promise')
|
||||
local render = require('ufo.render')
|
||||
local utils = require('ufo.utils')
|
||||
local floatwin = require('ufo.preview.floatwin')
|
||||
local scrollbar = require('ufo.preview.scrollbar')
|
||||
local winbar = require('ufo.preview.winbar')
|
||||
local keymap = require('ufo.preview.keymap')
|
||||
local event = require('ufo.lib.event')
|
||||
local disposable = require('ufo.lib.disposable')
|
||||
local config = require('ufo.config')
|
||||
local fold = require('ufo.fold')
|
||||
local highlight = require('ufo.highlight')
|
||||
|
||||
---@class UfoPreview
|
||||
---@field initialized boolean
|
||||
---@field disposables UfoDisposable[]
|
||||
---@field detachDisposables UfoDisposable[]
|
||||
---@field ns number
|
||||
---@field winid number
|
||||
---@field bufnr number
|
||||
---@field lnum number
|
||||
---@field col number
|
||||
---@field topline number
|
||||
---@field foldedLnum number
|
||||
---@field foldedEndLnum number
|
||||
---@field isAbove boolean
|
||||
---@field cursorSignName string
|
||||
---@field cursorSignId number
|
||||
---@field keyMessages table<string, string>
|
||||
local Preview = {}
|
||||
|
||||
function Preview:trace(bufnr)
|
||||
local fb = fold.get(self.bufnr)
|
||||
if not fb then
|
||||
return
|
||||
end
|
||||
local fWinConfig = floatwin.getConfig()
|
||||
local wrow = fWinConfig.row
|
||||
if fWinConfig.anchor == 'SW' then
|
||||
wrow = wrow - fWinConfig.height
|
||||
if wrow < 0 then
|
||||
wrow = floatwin:borderHasUpLine() and 1 or 0
|
||||
else
|
||||
if floatwin:borderHasBottomLine() then
|
||||
wrow = wrow - 1
|
||||
end
|
||||
end
|
||||
else
|
||||
if floatwin:borderHasUpLine() then
|
||||
wrow = wrow + 1
|
||||
end
|
||||
end
|
||||
local fLnum, fWrow, col
|
||||
if bufnr == self.bufnr then
|
||||
fLnum, fWrow = floatwin.topline, 0
|
||||
-- did scroll, do trace base on 2nd line
|
||||
if fLnum > 1 then
|
||||
fLnum = fLnum + 1
|
||||
fWrow = 1
|
||||
end
|
||||
else
|
||||
local floatCursor = api.nvim_win_get_cursor(floatwin.winid)
|
||||
fLnum = floatCursor[1]
|
||||
fWrow = fLnum - floatwin.topline
|
||||
col = floatCursor[2]
|
||||
end
|
||||
local cursor = api.nvim_win_get_cursor(self.winid)
|
||||
api.nvim_set_current_win(self.winid)
|
||||
local lnum = utils.foldClosed(0, cursor[1]) + fLnum - 1
|
||||
local lineSize = fWrow + wrow
|
||||
cmd('norm! m`zO')
|
||||
fb:syncFoldedLines(self.winid)
|
||||
if bufnr == self.bufnr then
|
||||
local s
|
||||
s, col = fb:lines(lnum)[1]:find('^%s+%S')
|
||||
col = s and col - 1 or 0
|
||||
end
|
||||
local topline, topfill = utils.evaluateTopline(self.winid, lnum, lineSize)
|
||||
utils.restView(0, {
|
||||
lnum = lnum,
|
||||
col = col,
|
||||
topline = topline,
|
||||
topfill = topfill,
|
||||
curswant = utils.curswant(self.bufnr, lnum, col + 1)
|
||||
})
|
||||
end
|
||||
|
||||
function Preview:winCall(executor)
|
||||
local res = false
|
||||
if self.validate() then
|
||||
floatwin:call(executor)
|
||||
res = true
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
function Preview:scroll(char, toTopLeft)
|
||||
if self:winCall(function()
|
||||
local ctrlTbl = {B = 0x02, D = 0x04, E = 0x05, F = 0x06, U = 0x15, Y = 0x19}
|
||||
cmd(('norm! %c%s'):format(ctrlTbl[char], toTopLeft and 'H_' or ''))
|
||||
end) then
|
||||
self:viewChanged()
|
||||
end
|
||||
end
|
||||
|
||||
function Preview:jumpView(toBottom)
|
||||
if self:winCall(function()
|
||||
cmd(('norm! %s'):format(toBottom and 'GH_' or 'gg'))
|
||||
end) then
|
||||
self:viewChanged()
|
||||
end
|
||||
end
|
||||
|
||||
function Preview:toggleCursor()
|
||||
local bufnr = api.nvim_get_current_buf()
|
||||
local floatBufnr = floatwin:getBufnr()
|
||||
if self.bufnr == bufnr and self.lnum - self.foldedLnum > 0 then
|
||||
self.cursorSignId = fn.sign_place(self.cursorSignId or 0, 'UfoPreview',
|
||||
self.cursorSignName, floatBufnr, {lnum = self.lnum - self.foldedLnum + 1, priority = 1})
|
||||
elseif self.cursorSignId then
|
||||
pcall(fn.sign_unplace, 'UfoPreview', {buffer = floatBufnr})
|
||||
self.cursorSignId = nil
|
||||
end
|
||||
end
|
||||
|
||||
local function onBufRemap(bufnr, str)
|
||||
local self = Preview
|
||||
local isNormalBuf = bufnr == self.bufnr
|
||||
if str == 'switch' then
|
||||
if isNormalBuf then
|
||||
api.nvim_set_current_win(floatwin.winid)
|
||||
vim.wo.cul = true
|
||||
else
|
||||
vim.wo.cul = false
|
||||
api.nvim_set_current_win(self.winid)
|
||||
end
|
||||
self:toggleCursor()
|
||||
elseif str == 'trace' or str == '2click' then
|
||||
self:trace(bufnr)
|
||||
elseif str == 'close' then
|
||||
self:close()
|
||||
elseif str == 'jumpTop' then
|
||||
self:jumpView(false)
|
||||
elseif str == 'jumpBot' then
|
||||
self:jumpView(true)
|
||||
elseif str == 'scrollB' then
|
||||
self:scroll('B', isNormalBuf)
|
||||
elseif str == 'scrollF' then
|
||||
self:scroll('F', isNormalBuf)
|
||||
elseif str == 'scrollU' then
|
||||
self:scroll('U', isNormalBuf)
|
||||
elseif str == 'scrollD' then
|
||||
self:scroll('D', isNormalBuf)
|
||||
elseif str == 'scrollE' then
|
||||
self:scroll('E', isNormalBuf)
|
||||
elseif str == 'scrollY' then
|
||||
self:scroll('Y', isNormalBuf)
|
||||
elseif str == 'wheelUp' or str == 'wheelDown' then
|
||||
promise.resolve():thenCall(function()
|
||||
self:viewChanged()
|
||||
end)
|
||||
elseif str == 'onKey' then
|
||||
promise.resolve():thenCall(function()
|
||||
Preview:afterKey()
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function Preview:attach(bufnr, winid, foldedLnum, foldedEndLnum, isAbove)
|
||||
self:detach()
|
||||
local disposables = {}
|
||||
event:on('WinClosed', function()
|
||||
promise.resolve():thenCall(function()
|
||||
if not self.validate() then
|
||||
self:detach()
|
||||
self.close()
|
||||
end
|
||||
end)
|
||||
end, disposables)
|
||||
event:on('onBufRemap', onBufRemap, disposables)
|
||||
event:emit('setOpenFoldHl', false)
|
||||
table.insert(disposables, disposable:create(function()
|
||||
event:emit('setOpenFoldHl')
|
||||
end))
|
||||
|
||||
local view = utils.saveView(winid)
|
||||
self.winid = winid
|
||||
self.bufnr = bufnr
|
||||
self.lnum = view.lnum
|
||||
self.col = view.col
|
||||
self.topline = view.topline
|
||||
self.foldedLnum = foldedLnum
|
||||
self.foldedEndLnum = foldedEndLnum
|
||||
self.isAbove = isAbove
|
||||
local floatBufnr = floatwin:getBufnr()
|
||||
vim.bo[floatBufnr].iskeyword = vim.bo[bufnr].iskeyword
|
||||
vim.bo[floatBufnr].tabstop = vim.bo[bufnr].tabstop
|
||||
vim.bo[floatBufnr].shiftwidth = vim.bo[bufnr].shiftwidth
|
||||
table.insert(disposables, disposable:create(function()
|
||||
self.winid = nil
|
||||
self.bufnr = nil
|
||||
self.lnum = nil
|
||||
self.col = nil
|
||||
self.topline = nil
|
||||
self.foldedLnum = nil
|
||||
self.foldedEndLnum = nil
|
||||
self.isAbove = nil
|
||||
self.cursorSignId = nil
|
||||
self.detachDisposables = nil
|
||||
if utils.isBufLoaded(floatBufnr) then
|
||||
api.nvim_buf_clear_namespace(floatBufnr, self.ns, 0, -1)
|
||||
pcall(fn.sign_unplace, 'UfoPreview', {buffer = floatBufnr})
|
||||
if floatwin:validate() then
|
||||
fn.clearmatches(floatwin.winid)
|
||||
end
|
||||
pcall(api.nvim_buf_call, floatBufnr, function()
|
||||
cmd('setl iskeyword<')
|
||||
cmd('setl topstop<')
|
||||
cmd('setl shiftwidth<')
|
||||
end)
|
||||
end
|
||||
end))
|
||||
table.insert(disposables, keymap:attach(bufnr, floatBufnr, self.ns, self.keyMessages, {
|
||||
trace = self.keyMessages.trace,
|
||||
switch = self.keyMessages.switch,
|
||||
close = self.keyMessages.close,
|
||||
['2click'] = '<2-LeftMouse>'
|
||||
}))
|
||||
self.detachDisposables = disposables
|
||||
end
|
||||
|
||||
function Preview:detach()
|
||||
if self.detachDisposables then
|
||||
disposable.disposeAll(self.detachDisposables)
|
||||
end
|
||||
end
|
||||
|
||||
function Preview:viewChanged()
|
||||
floatwin:refreshTopline()
|
||||
scrollbar:update()
|
||||
winbar:update()
|
||||
end
|
||||
|
||||
function Preview:display(enter, handler)
|
||||
local height = self.foldedEndLnum - self.foldedLnum + 1
|
||||
floatwin:display(self.winid, height, enter, self.isAbove, handler)
|
||||
scrollbar:display()
|
||||
winbar:display()
|
||||
end
|
||||
|
||||
---
|
||||
---@param enter? boolean
|
||||
---@param nextLineIncluded? boolean
|
||||
---@return number? floatWinId
|
||||
function Preview:peekFoldedLinesUnderCursor(enter, nextLineIncluded)
|
||||
local bufnr = api.nvim_get_current_buf()
|
||||
local fb = fold.get(bufnr)
|
||||
if not fb then
|
||||
-- buffer is detached
|
||||
return
|
||||
end
|
||||
local oLnum, oCol = unpack(api.nvim_win_get_cursor(0))
|
||||
local lnum = utils.foldClosed(0, oLnum)
|
||||
local fl = fb.foldedLines[lnum]
|
||||
if lnum == -1 or not fl then
|
||||
return
|
||||
end
|
||||
local winid = api.nvim_get_current_win()
|
||||
local endLnum = utils.foldClosedEnd(0, lnum)
|
||||
local kind = fb:lineKind(winid, lnum)
|
||||
local isAbove = kind == 'comment'
|
||||
if not isAbove and nextLineIncluded ~= false then
|
||||
endLnum = fb:lineCount() == endLnum and endLnum or (endLnum + 1)
|
||||
end
|
||||
self:attach(bufnr, winid, lnum, endLnum, isAbove)
|
||||
floatwin.virtText = fl.virtText
|
||||
local text = fb:lines(lnum, endLnum)
|
||||
self:display(enter, function()
|
||||
floatwin:setContent(text)
|
||||
api.nvim_win_set_cursor(floatwin.winid, {oLnum - lnum + 1, oCol})
|
||||
if oLnum > lnum then
|
||||
floatwin:call(utils.zz)
|
||||
end
|
||||
floatwin:refreshTopline()
|
||||
end)
|
||||
self:toggleCursor()
|
||||
render.mapHighlightLimitByRange(bufnr, floatwin:getBufnr(),
|
||||
{lnum - 1, 0}, {endLnum - 1, #text[endLnum - lnum + 1]}, text, self.ns)
|
||||
render.mapMatchByLnum(winid, floatwin.winid, lnum, endLnum)
|
||||
vim.wo[floatwin.winid].listchars = vim.wo[winid].listchars
|
||||
return floatwin.winid
|
||||
end
|
||||
|
||||
function Preview.validate()
|
||||
local res = floatwin:validate()
|
||||
if floatwin.showScrollBar then
|
||||
res = res and scrollbar:validate()
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
function Preview.close()
|
||||
floatwin:close()
|
||||
scrollbar:close()
|
||||
winbar:close()
|
||||
end
|
||||
|
||||
function Preview.floatWinid()
|
||||
return floatwin.winid
|
||||
end
|
||||
|
||||
function Preview:afterKey()
|
||||
local winid = api.nvim_get_current_win()
|
||||
if floatwin.winid == winid then
|
||||
self:viewChanged()
|
||||
return
|
||||
end
|
||||
if winid == self.winid then
|
||||
local view = utils.saveView(winid)
|
||||
if self.lnum ~= view.lnum or
|
||||
self.col ~= view.col then
|
||||
self.close()
|
||||
elseif self.foldedLnum ~= utils.foldClosed(self.winid, self.foldedLnum) then
|
||||
self.close()
|
||||
elseif self.topline ~= view.topline then
|
||||
if floatwin:validate() then
|
||||
self:display(false)
|
||||
self.topline = view.topline
|
||||
end
|
||||
end
|
||||
else
|
||||
self.close()
|
||||
end
|
||||
end
|
||||
|
||||
function Preview:initialize(namespace)
|
||||
if self.initialized then
|
||||
return
|
||||
end
|
||||
self.initialized = true
|
||||
local conf = vim.deepcopy(config.preview)
|
||||
self.keyMessages = conf.mappings
|
||||
self.disposables = {}
|
||||
table.insert(self.disposables, disposable:create(function()
|
||||
self.initialized = false
|
||||
end))
|
||||
table.insert(self.disposables, floatwin:initialize(namespace, conf.win_config))
|
||||
table.insert(self.disposables, scrollbar:initialize())
|
||||
table.insert(self.disposables, winbar:initialize())
|
||||
self.ns = namespace
|
||||
self.cursorSignName = highlight.signNames()['UfoPreviewCursorLine']
|
||||
return self
|
||||
end
|
||||
|
||||
function Preview:dispose()
|
||||
disposable.disposeAll(self.disposables)
|
||||
self.disposables = {}
|
||||
end
|
||||
|
||||
return Preview
|
||||
@ -0,0 +1,97 @@
|
||||
local event = require('ufo.lib.event')
|
||||
local utils = require('ufo.utils')
|
||||
|
||||
local api = vim.api
|
||||
|
||||
---@class UfoPreviewKeymap
|
||||
---@field ns number
|
||||
---@field bufnr number
|
||||
---@field keyMessages table
|
||||
---@field keyMapsBackup table
|
||||
local Keymap = {
|
||||
keyBackup = {}
|
||||
}
|
||||
|
||||
local function setKeymaps(bufnr, keyMessages)
|
||||
local opts = {noremap = true, nowait = true}
|
||||
local rhsFmt = [[<Cmd>lua require('ufo.lib.event'):emit('onBufRemap', %d, %q)<CR>]]
|
||||
for msg, key in pairs(keyMessages) do
|
||||
local lhs = key
|
||||
local rhs = rhsFmt:format(bufnr, msg)
|
||||
api.nvim_buf_set_keymap(bufnr, 'n', lhs, rhs, opts)
|
||||
end
|
||||
end
|
||||
|
||||
function Keymap:setKeymaps()
|
||||
setKeymaps(self.bufnr, self.keyMessages)
|
||||
end
|
||||
|
||||
function Keymap:restoreKeymaps()
|
||||
if utils.isBufLoaded(self.bufnr) then
|
||||
for _, key in pairs(self.keyMessages) do
|
||||
pcall(api.nvim_buf_del_keymap, self.bufnr, 'n', key)
|
||||
end
|
||||
for _, k in ipairs(self.keyBackup) do
|
||||
api.nvim_buf_set_keymap(self.bufnr, 'n', k.lhs, k.rhs, k.opts)
|
||||
end
|
||||
end
|
||||
self.keyBackup = {}
|
||||
end
|
||||
|
||||
function Keymap:saveKeymaps()
|
||||
local keys = {}
|
||||
for _, v in pairs(self.keyMessages) do
|
||||
if v:match('^<.*>$') then
|
||||
v = v:upper()
|
||||
end
|
||||
keys[v] = true
|
||||
end
|
||||
for _, k in ipairs(api.nvim_buf_get_keymap(self.bufnr, 'n')) do
|
||||
if keys[k.lhs] then
|
||||
local opts = {
|
||||
callback = k.callback,
|
||||
expr = k.expr == 1,
|
||||
noremap = k.noremap == 1,
|
||||
nowait = k.nowait == 1,
|
||||
silent = k.silent == 1
|
||||
}
|
||||
table.insert(self.keyBackup, {lhs = k.lhs, rhs = k.rhs or '', opts = opts})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---
|
||||
---@param bufnr number
|
||||
---@param namespace number
|
||||
---@param keyMessages table
|
||||
---@param floatKeyMessages table
|
||||
---@return UfoPreviewKeymap
|
||||
function Keymap:attach(bufnr, floatBufnr, namespace, keyMessages, floatKeyMessages)
|
||||
self.bufnr = bufnr
|
||||
self.ns = namespace
|
||||
self.keyMessages = keyMessages
|
||||
self:saveKeymaps()
|
||||
self:setKeymaps()
|
||||
setKeymaps(floatBufnr, floatKeyMessages)
|
||||
vim.on_key(function(char)
|
||||
local b1, b2, b3 = char:byte(1, -1)
|
||||
-- 0x80, 0xfd, 0x4b <ScrollWheelUp>
|
||||
-- 0x80, 0xfd, 0x4c <ScrollWheelDown>
|
||||
if b1 == 0x80 and b2 == 0xfd then
|
||||
if b3 == 0x4b then
|
||||
event:emit('onBufRemap', bufnr, 'wheelUp')
|
||||
elseif b3 == 0x4c then
|
||||
event:emit('onBufRemap', bufnr, 'wheelDown')
|
||||
end
|
||||
end
|
||||
event:emit('onBufRemap', bufnr, 'onKey')
|
||||
end, namespace)
|
||||
return self
|
||||
end
|
||||
|
||||
function Keymap:dispose()
|
||||
vim.on_key(nil, self.ns)
|
||||
self:restoreKeymaps()
|
||||
end
|
||||
|
||||
return Keymap
|
||||
@ -0,0 +1,108 @@
|
||||
local api = vim.api
|
||||
|
||||
local extmark = require('ufo.render.extmark')
|
||||
local FloatWin = require('ufo.preview.floatwin')
|
||||
|
||||
--- Singleton
|
||||
---@class UfoPreviewScrollBar : UfoPreviewFloatWin
|
||||
---@field winid number
|
||||
---@field bufnr number
|
||||
---@field bufferName string
|
||||
local ScrollBar = setmetatable({}, {__index = FloatWin})
|
||||
|
||||
function ScrollBar:build()
|
||||
local config = FloatWin.getConfig()
|
||||
local row, col, height = config.row, config.col + config.width, config.height
|
||||
local anchor, zindex = config.anchor, config.zindex
|
||||
if anchor == 'NW' then
|
||||
row = self:borderHasUpLine() and row + 1 or row
|
||||
else
|
||||
row = (self:borderHasBottomLine() and row - 1 or row) - height
|
||||
row = math.max(row, self:borderHasUpLine() and 1 or 0)
|
||||
end
|
||||
return vim.tbl_extend('force', config, {
|
||||
anchor = 'NW',
|
||||
width = 1,
|
||||
row = row,
|
||||
col = self:borderHasLeftLine() and col + 1 or col,
|
||||
style = 'minimal',
|
||||
noautocmd = true,
|
||||
focusable = false,
|
||||
border = 'none',
|
||||
zindex = zindex + 2
|
||||
})
|
||||
end
|
||||
|
||||
function ScrollBar:floatWinid()
|
||||
return FloatWin.winid
|
||||
end
|
||||
|
||||
function ScrollBar:update()
|
||||
if not self.showScrollBar then
|
||||
self.winid = nil
|
||||
return
|
||||
end
|
||||
local barSize = math.ceil(self.height * self.height / self.lineCount)
|
||||
if barSize == self.height and barSize < self.lineCount then
|
||||
barSize = self.height - 1
|
||||
end
|
||||
|
||||
local barPos = math.ceil(self.height * self.topline / self.lineCount)
|
||||
local size = barPos + barSize - 1
|
||||
if size == self.height then
|
||||
if self.topline + self.height - 1 < self.lineCount then
|
||||
barPos = barPos - 1
|
||||
end
|
||||
elseif size > self.height then
|
||||
barPos = self.height - barSize + 1
|
||||
end
|
||||
|
||||
if self:borderHasRightLine() then
|
||||
local wopts = self:build()
|
||||
wopts.height = math.max(1, barSize)
|
||||
wopts.row = wopts.row + barPos - 1
|
||||
wopts.noautocmd = nil
|
||||
api.nvim_win_set_config(self.winid, wopts)
|
||||
vim.wo[self.winid].winhl = 'Normal:UfoPreviewThumb'
|
||||
else
|
||||
api.nvim_buf_clear_namespace(self.bufnr, self.ns, 0, -1)
|
||||
for i = 1, self.height do
|
||||
if i >= barPos and i < barPos + barSize then
|
||||
extmark.setHighlight(self.bufnr, self.ns, i - 1, 0, i - 1, 1, 'UfoPreviewThumb')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ScrollBar:display()
|
||||
if not self.showScrollBar then
|
||||
self:close()
|
||||
return
|
||||
end
|
||||
local wopts = self:build()
|
||||
if self:validate() then
|
||||
wopts.noautocmd = nil
|
||||
api.nvim_win_set_config(self.winid, wopts)
|
||||
else
|
||||
ScrollBar:open(wopts)
|
||||
local wo = vim.wo[self.winid]
|
||||
wo.winhl = 'Normal:UfoPreviewSbar'
|
||||
wo.winblend = self.winblend
|
||||
end
|
||||
local lines = {}
|
||||
for _ = 1, self.height do
|
||||
table.insert(lines, ' ')
|
||||
end
|
||||
vim.bo[self.bufnr].modifiable = true
|
||||
api.nvim_buf_set_lines(self.bufnr, 0, -1, true, lines)
|
||||
vim.bo[self.bufnr].modifiable = false
|
||||
self:update()
|
||||
return self.winid
|
||||
end
|
||||
|
||||
function ScrollBar:initialize()
|
||||
self.bufferName = 'UfoPreviewScrollBar'
|
||||
return self
|
||||
end
|
||||
|
||||
return ScrollBar
|
||||
@ -0,0 +1,80 @@
|
||||
local api = vim.api
|
||||
|
||||
local render = require('ufo.render')
|
||||
local FloatWin = require('ufo.preview.floatwin')
|
||||
|
||||
--- Singleton
|
||||
---@class UfoPreviewWinBar : UfoPreviewFloatWin
|
||||
---@field winid number
|
||||
---@field bufnr number
|
||||
---@field bufferName string
|
||||
---@field virtTextId number
|
||||
---@field virtText UfoExtmarkVirtTextChunk[]
|
||||
local WinBar = setmetatable({}, {__index = FloatWin})
|
||||
|
||||
function WinBar:build()
|
||||
local config = FloatWin.getConfig()
|
||||
local row, col, height = config.row, config.col, config.height
|
||||
local anchor, zindex = config.anchor, config.zindex
|
||||
if anchor == 'NW' then
|
||||
row = self:borderHasUpLine() and row + 1 or row
|
||||
else
|
||||
row = (self:borderHasBottomLine() and row - 1 or row) - height
|
||||
row = math.max(row, self:borderHasUpLine() and 1 or 0)
|
||||
end
|
||||
return vim.tbl_extend('force', config, {
|
||||
anchor = 'NW',
|
||||
height = 1,
|
||||
row = row,
|
||||
col = self:borderHasLeftLine() and col + 1 or col,
|
||||
style = 'minimal',
|
||||
noautocmd = true,
|
||||
focusable = false,
|
||||
border = 'none',
|
||||
zindex = zindex + 1
|
||||
})
|
||||
end
|
||||
|
||||
function WinBar:floatWinid()
|
||||
return FloatWin.winid
|
||||
end
|
||||
|
||||
function WinBar:update()
|
||||
if self.topline == 1 then
|
||||
self:close()
|
||||
return
|
||||
end
|
||||
if not self:validate() then
|
||||
self:display()
|
||||
end
|
||||
self.virtTextId = render.setVirtText(self.bufnr, self.ns, 0, 0, self.virtText, {
|
||||
id = self.virtTextId
|
||||
})
|
||||
end
|
||||
|
||||
function WinBar:display()
|
||||
if self.topline == 1 then
|
||||
self:close()
|
||||
return
|
||||
end
|
||||
local wopts = self:build()
|
||||
if self:validate() then
|
||||
wopts.noautocmd = nil
|
||||
api.nvim_win_set_config(self.winid, wopts)
|
||||
else
|
||||
WinBar:open(wopts)
|
||||
local wo = vim.wo[self.winid]
|
||||
wo.winhl = 'Normal:UfoPreviewWinBar'
|
||||
wo.winblend = self.winblend
|
||||
end
|
||||
self:update()
|
||||
return self.winid
|
||||
end
|
||||
|
||||
function WinBar:initialize()
|
||||
self.bufferName = 'UfoPreviewWinBar'
|
||||
self.virtTextId = nil
|
||||
return self
|
||||
end
|
||||
|
||||
return WinBar
|
||||
@ -0,0 +1,69 @@
|
||||
local foldingrange = require('ufo.model.foldingrange')
|
||||
local bufmanager = require('ufo.bufmanager')
|
||||
|
||||
local Indent = {}
|
||||
|
||||
function Indent.getFolds(bufnr)
|
||||
local buf = bufmanager:get(bufnr)
|
||||
if not buf then
|
||||
return
|
||||
end
|
||||
local lines = buf:lines(1, -1)
|
||||
local ts = vim.bo[bufnr].ts
|
||||
local sw = vim.bo[bufnr].sw
|
||||
sw = sw == 0 and ts or sw
|
||||
local levels = {}
|
||||
for _, line in ipairs(lines) do
|
||||
local level = -1
|
||||
local n = 0
|
||||
for col = 1, #line do
|
||||
-- compare byte is slightly faster than a char in the string
|
||||
local b = line:byte(col, col)
|
||||
if b == 0x20 then
|
||||
-- ' '
|
||||
n = n + 1
|
||||
elseif b == 0x09 then
|
||||
-- '\t'
|
||||
n = n + (ts - (n % ts))
|
||||
else
|
||||
level = math.ceil(n / sw)
|
||||
break
|
||||
end
|
||||
end
|
||||
table.insert(levels, level)
|
||||
end
|
||||
|
||||
local ranges = {}
|
||||
local stack = {}
|
||||
|
||||
local function pop(curLevel, lastLnum)
|
||||
while #stack > 0 do
|
||||
local data = stack[#stack]
|
||||
local level, lnum = data[1], data[2]
|
||||
if level >= curLevel then
|
||||
table.insert(ranges, foldingrange.new(lnum - 1, lastLnum - 1))
|
||||
table.remove(stack)
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local lastLnum = 1
|
||||
local lastLevel = levels[1]
|
||||
for i, level in ipairs(levels) do
|
||||
if level >= 0 then
|
||||
if level > 0 and level > lastLevel then
|
||||
table.insert(stack, {lastLevel, lastLnum})
|
||||
elseif level < lastLevel then
|
||||
pop(level, lastLnum)
|
||||
end
|
||||
lastLevel = level
|
||||
lastLnum = i
|
||||
end
|
||||
end
|
||||
pop(0, lastLnum)
|
||||
return ranges
|
||||
end
|
||||
|
||||
return Indent
|
||||
@ -0,0 +1,82 @@
|
||||
local uv = vim.loop
|
||||
|
||||
local promise = require('promise')
|
||||
local log = require('ufo.lib.log')
|
||||
|
||||
---@class Provider UfoProvider
|
||||
---@field modulePathPrefix string
|
||||
---@field innerProviders string[]
|
||||
---@field modules table
|
||||
local Provider = {
|
||||
modulePathPrefix = 'ufo.provider.',
|
||||
innerProviders = {'lsp', 'treesitter', 'indent'}
|
||||
}
|
||||
|
||||
local function needFallback(reason)
|
||||
return type(reason) == 'string' and reason:match('UfoFallbackException')
|
||||
end
|
||||
|
||||
function Provider:getFunction(m)
|
||||
return type(m) == 'string' and self.modules[m].getFolds or m
|
||||
end
|
||||
|
||||
---
|
||||
---@param providers table
|
||||
---@param bufnr number
|
||||
---@return Promise
|
||||
function Provider:requestFoldingRange(providers, bufnr)
|
||||
local main, fallback = providers[1], providers[2]
|
||||
local mainFunc = self:getFunction(main)
|
||||
|
||||
local s
|
||||
if log.isEnabled('debug') then
|
||||
s = uv.hrtime()
|
||||
end
|
||||
local p = promise(function(resolve)
|
||||
resolve(mainFunc(bufnr))
|
||||
end):thenCall(function(value)
|
||||
return {main, value}
|
||||
end, function(reason)
|
||||
if needFallback(reason) then
|
||||
local fallbackFunc = self:getFunction(fallback)
|
||||
if fallbackFunc then
|
||||
return {fallback, fallbackFunc(bufnr)}
|
||||
else
|
||||
return {main, nil}
|
||||
end
|
||||
else
|
||||
return promise.reject(reason)
|
||||
end
|
||||
end)
|
||||
if log.isEnabled('debug') then
|
||||
p = p:finally(function()
|
||||
log.debug(('requestFoldingRange(%s, %d) has elapsed: %dms')
|
||||
:format(vim.inspect(providers, {indent = '', newline = ' '}),
|
||||
bufnr, (uv.hrtime() - s) / 1e6))
|
||||
end)
|
||||
end
|
||||
return p
|
||||
end
|
||||
|
||||
function Provider:initialize()
|
||||
self.modules = setmetatable({}, {
|
||||
__index = function(t, k)
|
||||
local ok, res = pcall(require, self.modulePathPrefix .. k)
|
||||
assert(ok, ([[Can't find a module in `%s%s`]]):format(self.modulePathPrefix, k))
|
||||
rawset(t, k, res)
|
||||
return res
|
||||
end
|
||||
})
|
||||
return self
|
||||
end
|
||||
|
||||
function Provider:dispose()
|
||||
for _, name in ipairs(self.innerProviders) do
|
||||
local module = _G.package.loaded[self.modulePathPrefix .. name]
|
||||
if module and module.dispose then
|
||||
module:dispose()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Provider
|
||||
@ -0,0 +1,68 @@
|
||||
local fn = vim.fn
|
||||
|
||||
local promise = require('promise')
|
||||
|
||||
---@class UfoLspCocClient
|
||||
---@field initialized boolean
|
||||
---@field enabled boolean
|
||||
local CocClient = {
|
||||
initialized = false,
|
||||
enabled = false,
|
||||
}
|
||||
|
||||
---@param action string
|
||||
---@vararg any
|
||||
---@return Promise
|
||||
function CocClient.action(action, ...)
|
||||
local args = {...}
|
||||
return promise(function(resolve, reject)
|
||||
table.insert(args, function(err, res)
|
||||
if err ~= vim.NIL then
|
||||
if type(err) == 'string' and
|
||||
(err:match('service not started') or err:match('Plugin not ready')) then
|
||||
resolve()
|
||||
else
|
||||
reject(err)
|
||||
end
|
||||
else
|
||||
if res == vim.NIL then
|
||||
res = nil
|
||||
end
|
||||
resolve(res)
|
||||
end
|
||||
end)
|
||||
fn.CocActionAsync(action, unpack(args))
|
||||
end)
|
||||
end
|
||||
|
||||
---@param name string
|
||||
---@vararg any
|
||||
---@return Promise
|
||||
function CocClient.runCommand(name, ...)
|
||||
return CocClient.action('runCommand', name, ...)
|
||||
end
|
||||
|
||||
---
|
||||
---@param bufnr number
|
||||
---@param kind? string|'comment'|'imports'|'region'
|
||||
---@return Promise
|
||||
function CocClient.requestFoldingRange(bufnr, kind)
|
||||
if not CocClient.initialized or not CocClient.enabled then
|
||||
return promise.reject('UfoFallbackException')
|
||||
end
|
||||
return CocClient.runCommand('ufo.foldingRange', bufnr, kind)
|
||||
end
|
||||
|
||||
function CocClient.handleInitNotify()
|
||||
if not CocClient.initialized then
|
||||
CocClient.initialized = true
|
||||
end
|
||||
CocClient.enabled = true
|
||||
end
|
||||
|
||||
function CocClient.handleDisposeNotify()
|
||||
CocClient.initialized = false
|
||||
CocClient.enabled = false
|
||||
end
|
||||
|
||||
return CocClient
|
||||
@ -0,0 +1,18 @@
|
||||
local promise = require('promise')
|
||||
|
||||
---@class UfoLspFastFailure
|
||||
---@field initialized boolean
|
||||
local FastFailure = {
|
||||
initialized = false
|
||||
}
|
||||
|
||||
---
|
||||
---@param bufnr number
|
||||
---@param kind? string|'comment'|'imports'|'region'
|
||||
---@return Promise
|
||||
---@diagnostic disable-next-line: unused-local
|
||||
function FastFailure.requestFoldingRange(bufnr, kind)
|
||||
return promise.reject('UfoFallbackException')
|
||||
end
|
||||
|
||||
return FastFailure
|
||||
@ -0,0 +1,114 @@
|
||||
local uv = vim.loop
|
||||
|
||||
local promise = require('promise')
|
||||
local utils = require('ufo.utils')
|
||||
local log = require('ufo.lib.log')
|
||||
local bufmanager = require('ufo.bufmanager')
|
||||
|
||||
---@class UfoLSPProviderContext
|
||||
---@field timestamp number
|
||||
---@field count number
|
||||
|
||||
---@class UfoLSPProvider
|
||||
---@field provider table
|
||||
---@field hasProviders table<string, boolean>
|
||||
---@field providerContext table<string, UfoLSPProviderContext>
|
||||
local LSP = {
|
||||
hasProviders = {},
|
||||
providerContext = {}
|
||||
}
|
||||
|
||||
|
||||
function LSP:hasInitialized()
|
||||
return self.provider and self.provider.initialized
|
||||
end
|
||||
|
||||
function LSP:initialize()
|
||||
return utils.wait(1500):thenCall(function()
|
||||
local cocInitlized = vim.g.coc_service_initialized
|
||||
local module
|
||||
if _G.package.loaded['vim.lsp'] and (not cocInitlized or cocInitlized ~= 1) then
|
||||
module = 'nvim'
|
||||
elseif cocInitlized and cocInitlized == 1 then
|
||||
module = 'coc'
|
||||
else
|
||||
module = 'fastfailure'
|
||||
end
|
||||
log.debug(('using %s as a lsp provider'):format(module))
|
||||
self.provider = require('ufo.provider.lsp.' .. module)
|
||||
end)
|
||||
end
|
||||
|
||||
function LSP:request(bufnr)
|
||||
local buf = bufmanager:get(bufnr)
|
||||
if not buf then
|
||||
return promise.resolve()
|
||||
end
|
||||
local bt = buf:buftype()
|
||||
if bt ~= '' and bt ~= 'acwrite' then
|
||||
return bt == 'nofile' and promise.reject('UfoFallbackException') or promise.resolve()
|
||||
end
|
||||
local ft = buf:filetype()
|
||||
local hasProvider = self.hasProviders[ft]
|
||||
local firstCheckFt = false
|
||||
if hasProvider == nil then
|
||||
local context = self.providerContext[ft]
|
||||
if not context then
|
||||
firstCheckFt = true
|
||||
self.providerContext[ft] = {timestamp = uv.hrtime(), count = 0}
|
||||
else
|
||||
-- after 120 seconds and count is equal or greater than 5
|
||||
if uv.hrtime() - context.timestamp > 1.2e11 and context.count >= 5 then
|
||||
self.hasProviders[ft] = false
|
||||
hasProvider = false
|
||||
self.providerContext[ft] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
local provider = self.provider
|
||||
if provider.initialized and hasProvider ~= false then
|
||||
local p
|
||||
if firstCheckFt then
|
||||
-- wait for the server, is 500ms enough?
|
||||
p = utils.wait(500):thenCall(function()
|
||||
return provider.requestFoldingRange(bufnr)
|
||||
end)
|
||||
else
|
||||
p = provider.requestFoldingRange(bufnr)
|
||||
end
|
||||
if hasProvider == nil then
|
||||
p = p:thenCall(function(value)
|
||||
self.hasProviders[ft] = true
|
||||
self.providerContext[ft] = nil
|
||||
return value
|
||||
end, function(reason)
|
||||
local context = self.providerContext[ft]
|
||||
if context then
|
||||
self.providerContext[ft].count = context.count + 1
|
||||
end
|
||||
return promise.reject(reason)
|
||||
end)
|
||||
end
|
||||
return p
|
||||
else
|
||||
return promise.reject('UfoFallbackException')
|
||||
end
|
||||
end
|
||||
|
||||
function LSP.getFolds(bufnr)
|
||||
local self = LSP
|
||||
if not self:hasInitialized() then
|
||||
return self:initialize():thenCall(function()
|
||||
return self:request(bufnr)
|
||||
end)
|
||||
end
|
||||
return self:request(bufnr)
|
||||
end
|
||||
|
||||
function LSP:dispose()
|
||||
self.provider = nil
|
||||
self.hasProviders = {}
|
||||
self.providerContext = {}
|
||||
end
|
||||
|
||||
return LSP
|
||||
@ -0,0 +1,95 @@
|
||||
local util = require('vim.lsp.util')
|
||||
local promise = require('promise')
|
||||
local utils = require('ufo.utils')
|
||||
local async = require('async')
|
||||
local log = require('ufo.lib.log')
|
||||
local foldingrange = require('ufo.model.foldingrange')
|
||||
|
||||
---@class UfoLspNvimClient
|
||||
---@field initialized boolean
|
||||
local NvimClient = {
|
||||
initialized = true
|
||||
}
|
||||
|
||||
local errorCodes = {
|
||||
-- Defined by JSON RPC
|
||||
ParseError = -32700,
|
||||
InvalidRequest = -32600,
|
||||
MethodNotFound = -32601,
|
||||
InvalidParams = -32602,
|
||||
InternalError = -32603,
|
||||
serverErrorStart = -32099,
|
||||
serverErrorEnd = -32000,
|
||||
ServerNotInitialized = -32002,
|
||||
UnknownErrorCode = -32001,
|
||||
-- Defined by the protocol.
|
||||
RequestCancelled = -32800,
|
||||
RequestFailed = -32803,
|
||||
ContentModified = -32801,
|
||||
}
|
||||
|
||||
local vimLspGetClients = vim.lsp.get_clients and vim.lsp.get_clients or vim.lsp.get_active_clients
|
||||
|
||||
function NvimClient.request(client, method, params, bufnr)
|
||||
return promise(function(resolve, reject)
|
||||
client.request(method, params, function(err, res)
|
||||
if err then
|
||||
log.error('Received error in callback', err)
|
||||
log.error('Client:', client)
|
||||
log.error('All clients:', vimLspGetClients({bufnr = bufnr}))
|
||||
local code = err.code
|
||||
if code == errorCodes.RequestCancelled or code == errorCodes.ContentModified or code == errorCodes.RequestFailed then
|
||||
reject('UfoFallbackException')
|
||||
else
|
||||
reject(err)
|
||||
end
|
||||
else
|
||||
resolve(res)
|
||||
end
|
||||
end, bufnr)
|
||||
end)
|
||||
end
|
||||
|
||||
local function getClients(bufnr)
|
||||
local clients = vimLspGetClients({bufnr = bufnr})
|
||||
return vim.tbl_filter(function(client)
|
||||
if vim.tbl_get(client.server_capabilities, 'foldingRangeProvider') then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end, clients)
|
||||
end
|
||||
|
||||
function NvimClient.requestFoldingRange(bufnr, kind)
|
||||
return async(function()
|
||||
if not utils.isBufLoaded(bufnr) then
|
||||
return
|
||||
end
|
||||
local clients = getClients(bufnr)
|
||||
if #clients == 0 then
|
||||
await(utils.wait(500))
|
||||
clients = getClients(bufnr)
|
||||
end
|
||||
-- TODO
|
||||
-- How to get the highest priority for the client?
|
||||
local client = clients[1]
|
||||
if not client then
|
||||
error('UfoFallbackException')
|
||||
end
|
||||
local params = {textDocument = util.make_text_document_params(bufnr)}
|
||||
return NvimClient.request(client, 'textDocument/foldingRange', params, bufnr)
|
||||
:thenCall(function(ranges)
|
||||
if not ranges then
|
||||
return {}
|
||||
end
|
||||
ranges = vim.tbl_filter(function(o)
|
||||
return (not kind or kind == o.kind) and o.startLine < o.endLine
|
||||
end, ranges)
|
||||
foldingrange.sortRanges(ranges)
|
||||
return ranges
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
return NvimClient
|
||||
@ -0,0 +1,193 @@
|
||||
local bufmanager = require('ufo.bufmanager')
|
||||
local foldingrange = require('ufo.model.foldingrange')
|
||||
|
||||
---@class UfoTreesitterProvider
|
||||
---@field hasProviders table<string, boolean>
|
||||
local Treesitter = {
|
||||
hasProviders = {}
|
||||
}
|
||||
|
||||
---@diagnostic disable: deprecated
|
||||
---@return vim.treesitter.LanguageTree|nil parser for the buffer, or nil if parser is not available
|
||||
local function getParser(bufnr, lang)
|
||||
local ok, parser = pcall(vim.treesitter.get_parser, bufnr, lang)
|
||||
if not ok then
|
||||
return nil
|
||||
end
|
||||
return parser
|
||||
end
|
||||
local get_query = assert(vim.treesitter.query.get or vim.treesitter.query.get_query)
|
||||
local get_query_files = assert(vim.treesitter.query.get_files or vim.treesitter.query.get_query_files)
|
||||
---@diagnostic enable: deprecated
|
||||
|
||||
|
||||
-- Backward compatibility for the dummy directive (#make-range!),
|
||||
-- which no longer exists in nvim-treesitter v1.0+
|
||||
if not vim.tbl_contains(vim.treesitter.query.list_directives(), "make-range!") then
|
||||
vim.treesitter.query.add_directive("make-range!", function() end, {})
|
||||
end
|
||||
|
||||
local MetaNode = {}
|
||||
MetaNode.__index = MetaNode
|
||||
|
||||
function MetaNode:new(range)
|
||||
local o = self == MetaNode and setmetatable({}, self) or self
|
||||
o.value = range
|
||||
return o
|
||||
end
|
||||
|
||||
function MetaNode:range()
|
||||
local range = self.value
|
||||
return range[1], range[2], range[3], range[4]
|
||||
end
|
||||
|
||||
--- Return a meta node that represents a range between two nodes, i.e., (#make-range!),
|
||||
--- that is similar to the legacy TSRange.from_node() from nvim-treesitter.
|
||||
function MetaNode.from_nodes(start_node, end_node)
|
||||
local start_pos = { start_node:start() }
|
||||
local end_pos = { end_node:end_() }
|
||||
return MetaNode:new({
|
||||
[1] = start_pos[1],
|
||||
[2] = start_pos[2],
|
||||
[3] = end_pos[1],
|
||||
[4] = end_pos[2],
|
||||
})
|
||||
end
|
||||
|
||||
local function prepareQuery(bufnr, parser, root, rootLang, queryName)
|
||||
if not root then
|
||||
local firstTree = parser:trees()[1]
|
||||
if firstTree then
|
||||
root = firstTree:root()
|
||||
else
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local range = {root:range()}
|
||||
|
||||
if not rootLang then
|
||||
local langTree = parser:language_for_range(range)
|
||||
if langTree then
|
||||
rootLang = langTree:lang()
|
||||
else
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
return get_query(rootLang, queryName), {
|
||||
root = root,
|
||||
source = bufnr,
|
||||
start = range[1],
|
||||
-- The end row is exclusive so we need to add 1 to it.
|
||||
stop = range[3] + 1,
|
||||
}
|
||||
end
|
||||
|
||||
local function iterFoldMatches(bufnr, parser, root, rootLang)
|
||||
local q, p = prepareQuery(bufnr, parser, root, rootLang, 'folds')
|
||||
if not q then
|
||||
return function() end
|
||||
end
|
||||
---@diagnostic disable-next-line: need-check-nil
|
||||
local iter = q:iter_matches(p.root, p.source, p.start, p.stop)
|
||||
return function()
|
||||
local pattern, match, metadata = iter()
|
||||
local matches = {}
|
||||
if pattern == nil then
|
||||
return pattern
|
||||
end
|
||||
|
||||
-- Extract capture names from each match
|
||||
for id, node in pairs(match) do
|
||||
local m = metadata[id]
|
||||
if m and m.range then
|
||||
node = MetaNode:new(m.range)
|
||||
end
|
||||
table.insert(matches, node)
|
||||
end
|
||||
|
||||
-- Add some predicates for testing
|
||||
local preds = q.info.patterns[pattern]
|
||||
if preds then
|
||||
for _, pred in pairs(preds) do
|
||||
if pred[1] == 'make-range!' and type(pred[2]) == 'string' and #pred == 4 then
|
||||
local node = MetaNode.from_nodes(match[pred[3]], match[pred[4]])
|
||||
table.insert(matches, node)
|
||||
end
|
||||
end
|
||||
end
|
||||
return matches
|
||||
end
|
||||
end
|
||||
|
||||
local function getFoldMatches(res, bufnr, parser, root, lang)
|
||||
for matches in iterFoldMatches(bufnr, parser, root, lang) do
|
||||
for _, node in ipairs(matches) do
|
||||
table.insert(res, node)
|
||||
end
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local function getCpatureMatchesRecursively(bufnr, parser)
|
||||
local noQuery = true
|
||||
local res = {}
|
||||
parser:for_each_tree(function(tree, langTree)
|
||||
local lang = langTree:lang()
|
||||
local has_folds = #get_query_files(lang, 'folds', nil) > 0
|
||||
if has_folds then
|
||||
noQuery = false
|
||||
getFoldMatches(res, bufnr, parser, tree:root(), lang)
|
||||
end
|
||||
end)
|
||||
return not noQuery, res
|
||||
end
|
||||
|
||||
function Treesitter.getFolds(bufnr)
|
||||
local buf = bufmanager:get(bufnr)
|
||||
if not buf then
|
||||
return
|
||||
end
|
||||
local bt = buf:buftype()
|
||||
if bt ~= '' and bt ~= 'acwrite' then
|
||||
if bt == 'nofile' then
|
||||
error('UfoFallbackException')
|
||||
end
|
||||
return
|
||||
end
|
||||
local self = Treesitter
|
||||
local ft = buf:filetype()
|
||||
if self.hasProviders[ft] == false then
|
||||
error('UfoFallbackException')
|
||||
end
|
||||
local parser = getParser(bufnr)
|
||||
if not parser then
|
||||
self.hasProviders[ft] = false
|
||||
error('UfoFallbackException')
|
||||
end
|
||||
|
||||
local ranges = {}
|
||||
local ok, matches = getCpatureMatchesRecursively(bufnr, parser)
|
||||
if not ok then
|
||||
self.hasProviders[ft] = false
|
||||
error('UfoFallbackException')
|
||||
end
|
||||
for _, node in ipairs(matches) do
|
||||
local start, _, stop, stop_col = node:range()
|
||||
if stop_col == 0 then
|
||||
stop = stop - 1
|
||||
end
|
||||
if stop > start then
|
||||
table.insert(ranges, foldingrange.new(start, stop))
|
||||
end
|
||||
end
|
||||
foldingrange.sortRanges(ranges)
|
||||
return ranges
|
||||
end
|
||||
|
||||
function Treesitter:dispose()
|
||||
self.hasProviders = {}
|
||||
end
|
||||
|
||||
return Treesitter
|
||||
@ -0,0 +1,63 @@
|
||||
local api = vim.api
|
||||
|
||||
local M = {}
|
||||
|
||||
---
|
||||
---@param bufnr number
|
||||
---@param startRange number[]
|
||||
---@param endRange number[]
|
||||
---@param namespaces number[]
|
||||
---@return table, table
|
||||
function M.getHighlightsAndInlayByRange(bufnr, startRange, endRange, namespaces)
|
||||
local hlRes, inlayRes = {}, {}
|
||||
local endRow, endCol = endRange[1], endRange[2]
|
||||
for _, ns in pairs(namespaces) do
|
||||
local marks = api.nvim_buf_get_extmarks(bufnr, ns, startRange, endRange, {details = true})
|
||||
for _, m in ipairs(marks) do
|
||||
local sr, sc, details = m[2], m[3], m[4]
|
||||
local er = details.end_row or sr
|
||||
local ec = details.end_col or (sc + 1)
|
||||
local hlGroup = details.hl_group
|
||||
local priority = details.priority
|
||||
local conceal = details.conceal
|
||||
local virtTextPos = details.virt_text_pos
|
||||
if hlGroup then
|
||||
if er > endRow then
|
||||
er, ec = endRow, endCol
|
||||
elseif er == endRow and ec > endCol then
|
||||
er = endCol
|
||||
end
|
||||
table.insert(hlRes, {sr, sc, er, ec, hlGroup, priority, conceal})
|
||||
end
|
||||
if virtTextPos == 'inline' then
|
||||
table.insert(inlayRes, {sr, sc, details.virt_text, priority})
|
||||
end
|
||||
end
|
||||
end
|
||||
return hlRes, inlayRes
|
||||
end
|
||||
|
||||
function M.setHighlight(bufnr, ns, row, col, endRow, endCol, hlGroup, priority)
|
||||
return api.nvim_buf_set_extmark(bufnr, ns, row, col, {
|
||||
end_row = endRow,
|
||||
end_col = endCol,
|
||||
hl_group = hlGroup,
|
||||
priority = priority
|
||||
})
|
||||
end
|
||||
|
||||
function M.setVirtText(bufnr, ns, row, col, virtText, opts)
|
||||
opts = opts or {}
|
||||
local textPos = opts.virt_text_pos
|
||||
local winCol = not textPos and col == 0 and 0 or nil
|
||||
return api.nvim_buf_set_extmark(bufnr, ns, row, col, {
|
||||
id = opts.id,
|
||||
virt_text = virtText,
|
||||
virt_text_win_col = winCol,
|
||||
virt_text_pos = textPos or 'eol',
|
||||
priority = opts.priority or 10,
|
||||
hl_mode = opts.hl_mode or 'combine'
|
||||
})
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,297 @@
|
||||
local api = vim.api
|
||||
local fn = vim.fn
|
||||
|
||||
local highlight = require('ufo.highlight')
|
||||
local extmark = require('ufo.render.extmark')
|
||||
local treesitter = require('ufo.render.treesitter')
|
||||
local match = require('ufo.render.match')
|
||||
local utils = require('ufo.utils')
|
||||
|
||||
local M = {}
|
||||
|
||||
local function fillSlots(marks, len, concealLevel)
|
||||
local res = {}
|
||||
local prioritySlots = {}
|
||||
local hlGroups = highlight.hlGroups()
|
||||
local concealEabnled = concealLevel > 0
|
||||
for _, m in ipairs(marks) do
|
||||
---@type 'string'|'number'
|
||||
local hlGroup = m[5]
|
||||
local cchar = m[7]
|
||||
local isConcealSlot = concealEabnled and cchar
|
||||
if isConcealSlot or hlGroup and hlGroups[hlGroup].foreground then
|
||||
local col, endCol, priority = m[2], m[4], m[6]
|
||||
if endCol == -1 then
|
||||
endCol = len
|
||||
end
|
||||
if isConcealSlot and concealLevel == 3 then
|
||||
cchar = ''
|
||||
end
|
||||
local e = isConcealSlot and {col + 1, cchar, hlGroup} or hlGroup
|
||||
for i = col + 1, endCol do
|
||||
local oPriority = prioritySlots[i]
|
||||
local oType = type(res[i])
|
||||
if oType == 'nil' then
|
||||
res[i], prioritySlots[i] = e, priority
|
||||
elseif oType == 'string' or oType == 'number' then
|
||||
if isConcealSlot or oPriority <= priority then
|
||||
res[i], prioritySlots[i] = e, priority
|
||||
end
|
||||
else
|
||||
if isConcealSlot and oPriority <= priority then
|
||||
res[i], prioritySlots[i] = e, priority
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local function handleSyntaxSlot(slotData, slotLen, bufnr, lnum, syntax, concealEnabled)
|
||||
if not syntax and not concealEnabled then
|
||||
return
|
||||
end
|
||||
api.nvim_buf_call(bufnr, function()
|
||||
local lastConcealId = -1
|
||||
local lastConcealCol = 0
|
||||
for i = 1, slotLen do
|
||||
if concealEnabled then
|
||||
local concealed = fn.synconcealed(lnum, i)
|
||||
if concealed[1] == 1 then
|
||||
local cchar, concealId = concealed[2], concealed[3]
|
||||
if concealId ~= lastConcealId then
|
||||
if type(slotData[i]) ~= 'table' or slotData[i][1] ~= i then
|
||||
slotData[i] = {i, cchar, 'Conceal'}
|
||||
end
|
||||
lastConcealCol = i
|
||||
lastConcealId = concealId
|
||||
else
|
||||
slotData[i] = {lastConcealCol, cchar, 'Conceal'}
|
||||
end
|
||||
end
|
||||
end
|
||||
if syntax and not slotData[i] then
|
||||
local hlGroupId = fn.synID(lnum, i, true)
|
||||
if hlGroupId ~= 0 then
|
||||
slotData[i] = hlGroupId
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- 1-indexed
|
||||
local function syntaxToRowHighlightRange(res, lnum, startCol, endCol)
|
||||
local lastIndex = 1
|
||||
local lastHlId
|
||||
for c = startCol, endCol do
|
||||
local hlId = fn.synID(lnum, c, true)
|
||||
if lastHlId and lastHlId ~= hlId then
|
||||
table.insert(res, {lnum, lastIndex, c - 1, lastHlId})
|
||||
lastIndex = c
|
||||
end
|
||||
lastHlId = hlId
|
||||
end
|
||||
table.insert(res, {lnum, lastIndex, endCol, lastHlId})
|
||||
end
|
||||
|
||||
local function mapHighlightMarkers(bufnr, startRow, marks, hlGroups, ns)
|
||||
for _, m in ipairs(marks) do
|
||||
local hlGroup = m[5]
|
||||
if next(hlGroups[hlGroup]) then
|
||||
local sr, sc = m[1] - startRow, m[2]
|
||||
local er, ec = m[3] - startRow, m[4]
|
||||
extmark.setHighlight(bufnr, ns, sr, sc, er, ec, hlGroup, m[6])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function mapInlayMarkers(bufnr, startRow, marks, ns)
|
||||
for _, m in ipairs(marks) do
|
||||
local sr, sc = m[1] - startRow, m[2]
|
||||
extmark.setVirtText(bufnr, ns, sr, sc, m[3], {
|
||||
priority = m[4],
|
||||
virt_text_pos = 'inline'
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function M.mapHighlightLimitByRange(srcBufnr, dstBufnr, startRange, endRange, text, ns)
|
||||
local startRow, startCol = startRange[1], startRange[1]
|
||||
local endRow, endCol = endRange[1], endRange[2]
|
||||
local nss = {}
|
||||
for _, namespace in pairs(api.nvim_get_namespaces()) do
|
||||
if ns ~= namespace then
|
||||
table.insert(nss, namespace)
|
||||
end
|
||||
end
|
||||
local hlGroups = highlight.hlGroups()
|
||||
local hlMarks, inlayMarks = extmark.getHighlightsAndInlayByRange(srcBufnr, startRange, endRange, nss)
|
||||
mapHighlightMarkers(dstBufnr, startRow, hlMarks, hlGroups, ns)
|
||||
hlMarks = treesitter.getHighlightsByRange(srcBufnr, startRange, endRange, hlGroups)
|
||||
mapHighlightMarkers(dstBufnr, startRow, hlMarks, hlGroups, ns)
|
||||
if vim.bo[srcBufnr].syntax ~= '' then
|
||||
api.nvim_buf_call(srcBufnr, function()
|
||||
local res = {}
|
||||
local lnum, endLnum = startRow + 1, endRow + 1
|
||||
if lnum == endLnum then
|
||||
syntaxToRowHighlightRange(res, lnum, startCol + 1, endCol)
|
||||
else
|
||||
for l = lnum, endLnum - 1 do
|
||||
syntaxToRowHighlightRange(res, l, 1, #text[l - lnum + 1])
|
||||
end
|
||||
syntaxToRowHighlightRange(res, endLnum, 1, endCol)
|
||||
end
|
||||
for _, r in ipairs(res) do
|
||||
local row = r[1] - lnum
|
||||
extmark.setHighlight(dstBufnr, ns, row, r[2] - 1, row, r[3], r[4], 1)
|
||||
end
|
||||
end)
|
||||
end
|
||||
mapInlayMarkers(dstBufnr, startRow, inlayMarks, ns)
|
||||
end
|
||||
|
||||
function M.mapMatchByLnum(srcWinid, dstWinid, lnum, endLnum)
|
||||
local res = match.mapHighlightsByLnum(srcWinid, lnum, endLnum)
|
||||
if not vim.tbl_isempty(res) then
|
||||
fn.setmatches(res, dstWinid)
|
||||
end
|
||||
end
|
||||
|
||||
function M.setVirtText(bufnr, ns, row, col, virtText, opts)
|
||||
return extmark.setVirtText(bufnr, ns, row, col, virtText, opts)
|
||||
end
|
||||
|
||||
function M.captureVirtText(bufnr, text, lnum, syntax, namespaces, concealLevel)
|
||||
local len = #text
|
||||
if len == 0 then
|
||||
return {{'', 'UfoFoldedFg'}}
|
||||
end
|
||||
|
||||
local extMarks, inlayMarks = extmark.getHighlightsAndInlayByRange(bufnr, {lnum - 1, 0}, {lnum - 1, len}, namespaces)
|
||||
local tsMarks = treesitter.getHighlightsByRange(bufnr, {lnum - 1, 0}, {lnum - 1, len})
|
||||
|
||||
local marks = {}
|
||||
for _, m in ipairs(extMarks) do
|
||||
table.insert(marks, m)
|
||||
end
|
||||
for _, m in ipairs(tsMarks) do
|
||||
table.insert(marks, m)
|
||||
end
|
||||
|
||||
table.sort(inlayMarks, function(a, b)
|
||||
local aCol, bCol, aPriority, bPriority = a[2], b[2], a[4], b[4]
|
||||
if aCol == bCol then
|
||||
return aPriority > bPriority
|
||||
else
|
||||
return aCol > bCol
|
||||
end
|
||||
end)
|
||||
|
||||
local sData = fillSlots(marks, len, concealLevel)
|
||||
handleSyntaxSlot(sData, len, bufnr, lnum, syntax, concealLevel > 0)
|
||||
local virtText = {}
|
||||
local inlayMark = table.remove(inlayMarks)
|
||||
local shouldInsertChunk = true
|
||||
for i = 1, len do
|
||||
local e = sData[i] or 'UfoFoldedFg'
|
||||
local eType = type(e)
|
||||
if eType == 'table' then
|
||||
local startCol, cchar, hlGroup = e[1], e[2], e[3]
|
||||
if startCol == i then
|
||||
table.insert(virtText, {cchar, hlGroup})
|
||||
end
|
||||
shouldInsertChunk = true
|
||||
else
|
||||
local lastChunk = virtText[#virtText] or {}
|
||||
if shouldInsertChunk or e ~= lastChunk[2] then
|
||||
table.insert(virtText, {{i, i}, e})
|
||||
shouldInsertChunk = false
|
||||
else
|
||||
lastChunk[1][2] = i
|
||||
end
|
||||
end
|
||||
|
||||
-- insert inlay hints
|
||||
while inlayMark and inlayMark[2] == i do
|
||||
for _, chunk in ipairs(inlayMark[3]) do
|
||||
table.insert(virtText, chunk)
|
||||
end
|
||||
inlayMark = table.remove(inlayMarks)
|
||||
shouldInsertChunk = true
|
||||
end
|
||||
end
|
||||
|
||||
for _, chunk in ipairs(virtText) do
|
||||
local e1, e2 = chunk[1], chunk[2]
|
||||
if type(e1) == 'table' then
|
||||
local sc, ec = e1[1], e1[2]
|
||||
chunk[1] = text:sub(sc, ec)
|
||||
end
|
||||
if e2 == 'Normal' then
|
||||
chunk[2] = 'UfoFoldedFg'
|
||||
end
|
||||
end
|
||||
return virtText
|
||||
end
|
||||
|
||||
---Prefer use nvim_buf_set_extmark rather than matchaddpos, only use matchaddpos if buffer is shared
|
||||
---with multiple windows in current tabpage.
|
||||
---Check out https://github.com/neovim/neovim/issues/20208 for detail.
|
||||
---@param handle number
|
||||
---@param hlGroup string
|
||||
---@param ns number
|
||||
---@param start number
|
||||
---@param finish number
|
||||
---@param delay? number
|
||||
---@param shared? boolean
|
||||
---@return Promise
|
||||
function M.highlightLinesWithTimeout(handle, hlGroup, ns, start, finish, delay, shared)
|
||||
vim.validate({
|
||||
handle = {handle, 'number'},
|
||||
hlGroup = {hlGroup, 'string'},
|
||||
ns = {ns, 'number'},
|
||||
start = {start, 'number'},
|
||||
finish = {finish, 'number'},
|
||||
delay = {delay, 'number', true},
|
||||
shared = {shared, 'boolean', true},
|
||||
})
|
||||
local ids = {}
|
||||
local onFulfilled
|
||||
if shared then
|
||||
local prior = 10
|
||||
local l = {}
|
||||
for i = start, finish do
|
||||
table.insert(l, {i})
|
||||
if i % 8 == 0 then
|
||||
table.insert(ids, fn.matchaddpos(hlGroup, l, prior))
|
||||
l = {}
|
||||
end
|
||||
end
|
||||
if #l > 0 then
|
||||
table.insert(ids, fn.matchaddpos(hlGroup, l, prior))
|
||||
end
|
||||
onFulfilled = function()
|
||||
for _, id in ipairs(ids) do
|
||||
pcall(fn.matchdelete, id, handle)
|
||||
end
|
||||
end
|
||||
else
|
||||
local o = {hl_group = hlGroup}
|
||||
for i = start, finish do
|
||||
local row, col = i - 1, 0
|
||||
o.end_row = i
|
||||
o.end_col = 0
|
||||
table.insert(ids, api.nvim_buf_set_extmark(handle, ns, row, col, o))
|
||||
end
|
||||
onFulfilled = function()
|
||||
for _, id in ipairs(ids) do
|
||||
pcall(api.nvim_buf_del_extmark, handle, ns, id)
|
||||
end
|
||||
end
|
||||
end
|
||||
return utils.wait(delay or 300):thenCall(onFulfilled)
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,49 @@
|
||||
local fn = vim.fn
|
||||
|
||||
local M = {}
|
||||
|
||||
---
|
||||
---@param winid number
|
||||
---@param lnum number
|
||||
---@param endLnum number
|
||||
---@return table
|
||||
function M.mapHighlightsByLnum(winid, lnum, endLnum)
|
||||
local res = {}
|
||||
for _, m in pairs(fn.getmatches(winid)) do
|
||||
if m.pattern then
|
||||
table.insert(res, m)
|
||||
else
|
||||
local added = false
|
||||
local function add(match)
|
||||
if not added then
|
||||
table.insert(res, match)
|
||||
added = true
|
||||
end
|
||||
end
|
||||
|
||||
for i = 1, 8 do
|
||||
local k = 'pos' .. i
|
||||
local p = m[k]
|
||||
local pType = type(p)
|
||||
if pType == 'nil' then
|
||||
break
|
||||
end
|
||||
if pType == 'number' then
|
||||
if p >= lnum and p <= endLnum then
|
||||
m[k] = p - lnum + 1
|
||||
add(m)
|
||||
end
|
||||
else
|
||||
local l = p[1]
|
||||
if l >= lnum and l <= endLnum then
|
||||
m[k][1] = l - lnum + 1
|
||||
add(m)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,77 @@
|
||||
local highlighter = require('vim.treesitter.highlighter')
|
||||
|
||||
local M = {}
|
||||
|
||||
---
|
||||
---@param bufnr number
|
||||
---@param startRange number
|
||||
---@param endRange number
|
||||
---@param hlGroups? table<number|string, table>
|
||||
---@return table
|
||||
function M.getHighlightsByRange(bufnr, startRange, endRange, hlGroups)
|
||||
local data = highlighter.active[bufnr]
|
||||
if not data then
|
||||
return {}
|
||||
end
|
||||
local res = {}
|
||||
local row, col = startRange[1], startRange[2]
|
||||
local endRow, endCol = endRange[1], endRange[2]
|
||||
data.tree:for_each_tree(function(tstree, tree)
|
||||
if not tstree then
|
||||
return
|
||||
end
|
||||
local root = tstree:root()
|
||||
local rootStartRow, _, rootEndRow, _ = root:range()
|
||||
if rootEndRow < row or rootStartRow > endRow then
|
||||
return
|
||||
end
|
||||
local query = data:get_query(tree:lang())
|
||||
-- Some injected languages may not have highlight queries.
|
||||
if not query:query() then
|
||||
return
|
||||
end
|
||||
local iter = query:query():iter_captures(root, data.bufnr, row, endRow + 1)
|
||||
-- Record the last range and priority
|
||||
local lsr, lsc, ler, lec, lpriority, last
|
||||
for capture, node, metadata in iter do
|
||||
if not capture then
|
||||
break
|
||||
end
|
||||
local hlId = assert((function()
|
||||
if query.get_hl_from_capture then -- nvim 0.10+ #26675
|
||||
return query:get_hl_from_capture(capture)
|
||||
else
|
||||
return query.hl_cache[capture]
|
||||
end
|
||||
end)())
|
||||
local priority = tonumber(metadata.priority) or 100
|
||||
local conceal = metadata.conceal
|
||||
local sr, sc, er, ec = node:range()
|
||||
if row <= er and endRow >= sr then
|
||||
if sr < row or sr == row and sc < col then
|
||||
sr, sc = row, col
|
||||
end
|
||||
if er > endRow or er == endRow and ec > endCol then
|
||||
er, ec = endRow, endCol
|
||||
end
|
||||
if hlGroups then
|
||||
-- Overlap highlighting if range is equal to last's
|
||||
if lsr == sr and lsc == sc and ler == er and lec == ec then
|
||||
if hlGroups[hlId].foreground and lpriority <= priority then
|
||||
last[5], last[6], last[7] = hlId, priority, conceal
|
||||
end
|
||||
else
|
||||
last = {sr, sc, er, ec, hlId, priority, conceal}
|
||||
table.insert(res, last)
|
||||
end
|
||||
lsr, lsc, ler, lec, lpriority = sr, sc, er, ec, priority
|
||||
else
|
||||
table.insert(res, {sr, sc, er, ec, hlId, priority, conceal})
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
return res
|
||||
end
|
||||
|
||||
return M
|
||||
344
config/neovim/store/lazy-plugins/nvim-ufo/lua/ufo/utils.lua
Normal file
344
config/neovim/store/lazy-plugins/nvim-ufo/lua/ufo/utils.lua
Normal file
@ -0,0 +1,344 @@
|
||||
---@class UfoUtils
|
||||
local M = {}
|
||||
local api = vim.api
|
||||
local fn = vim.fn
|
||||
local cmd = vim.cmd
|
||||
local uv = vim.loop
|
||||
|
||||
---
|
||||
---@return fun(): boolean
|
||||
M.has10 = (function()
|
||||
local has10
|
||||
return function()
|
||||
if has10 == nil then
|
||||
has10 = fn.has('nvim-0.10') == 1
|
||||
end
|
||||
return has10
|
||||
end
|
||||
end)()
|
||||
|
||||
---
|
||||
---@return fun(): boolean
|
||||
M.has08 = (function()
|
||||
local has08
|
||||
return function()
|
||||
if has08 == nil then
|
||||
has08 = fn.has('nvim-0.8') == 1
|
||||
end
|
||||
return has08
|
||||
end
|
||||
end)()
|
||||
|
||||
---@return fun(): boolean
|
||||
M.isWindows = (function()
|
||||
local isWin
|
||||
return function()
|
||||
if isWin == nil then
|
||||
isWin = uv.os_uname().sysname == 'Windows_NT'
|
||||
end
|
||||
return isWin
|
||||
end
|
||||
end)()
|
||||
|
||||
---
|
||||
---@return string
|
||||
function M.mode()
|
||||
return api.nvim_get_mode().mode
|
||||
end
|
||||
|
||||
---
|
||||
---@param bufnr number
|
||||
---@return number, number[]?
|
||||
function M.getWinByBuf(bufnr)
|
||||
local curBufnr
|
||||
if not bufnr then
|
||||
curBufnr = api.nvim_get_current_buf()
|
||||
bufnr = curBufnr
|
||||
end
|
||||
local winids = {}
|
||||
for _, winid in ipairs(api.nvim_list_wins()) do
|
||||
if bufnr == api.nvim_win_get_buf(winid) then
|
||||
table.insert(winids, winid)
|
||||
end
|
||||
end
|
||||
if #winids == 0 then
|
||||
return -1
|
||||
elseif #winids == 1 then
|
||||
return winids[1]
|
||||
else
|
||||
if not curBufnr then
|
||||
curBufnr = api.nvim_get_current_buf()
|
||||
end
|
||||
local winid = curBufnr == bufnr and api.nvim_get_current_win() or winids[1]
|
||||
return winid, winids
|
||||
end
|
||||
end
|
||||
|
||||
---
|
||||
---@param winid number
|
||||
---@param f fun(): any
|
||||
---@return any
|
||||
function M.winCall(winid, f)
|
||||
if winid == 0 or winid == api.nvim_get_current_win() then
|
||||
return f()
|
||||
else
|
||||
return api.nvim_win_call(winid, f)
|
||||
end
|
||||
end
|
||||
|
||||
---
|
||||
---@param winid number
|
||||
---@param lnum number
|
||||
---@return number
|
||||
function M.foldClosed(winid, lnum)
|
||||
return M.winCall(winid, function()
|
||||
return fn.foldclosed(lnum)
|
||||
end)
|
||||
end
|
||||
|
||||
---
|
||||
---@param winid number
|
||||
---@param lnum number
|
||||
---@return number
|
||||
function M.foldClosedEnd(winid, lnum)
|
||||
return M.winCall(winid, function()
|
||||
return fn.foldclosedend(lnum)
|
||||
end)
|
||||
end
|
||||
|
||||
---
|
||||
---@param str string
|
||||
---@param ts number
|
||||
---@param start? number
|
||||
---@return string
|
||||
function M.expandTab(str, ts, start)
|
||||
start = start or 1
|
||||
local new = str:sub(1, start - 1)
|
||||
local pad = ' '
|
||||
local ti = start - 1
|
||||
local i = start
|
||||
while true do
|
||||
i = str:find('\t', i, true)
|
||||
if not i then
|
||||
if ti == 0 then
|
||||
new = str
|
||||
else
|
||||
new = new .. str:sub(ti + 1)
|
||||
end
|
||||
break
|
||||
end
|
||||
if ti + 1 == i then
|
||||
new = new .. pad:rep(ts)
|
||||
else
|
||||
local append = str:sub(ti + 1, i - 1)
|
||||
new = new .. append .. pad:rep(ts - api.nvim_strwidth(append) % ts)
|
||||
end
|
||||
ti = i
|
||||
i = i + 1
|
||||
end
|
||||
return new
|
||||
end
|
||||
|
||||
---@param ms number
|
||||
---@return Promise
|
||||
function M.wait(ms)
|
||||
return require('promise')(function(resolve)
|
||||
local timer = uv.new_timer()
|
||||
timer:start(ms, 0, function()
|
||||
timer:close()
|
||||
resolve()
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
---
|
||||
---@param callback function
|
||||
---@param ms number
|
||||
---@return userdata
|
||||
function M.setTimeout(callback, ms)
|
||||
---@type userdata
|
||||
local timer = uv.new_timer()
|
||||
timer:start(ms, 0, function()
|
||||
timer:close()
|
||||
callback()
|
||||
end)
|
||||
return timer
|
||||
end
|
||||
|
||||
function M.zz()
|
||||
local lnum1, lcount = api.nvim_win_get_cursor(0)[1], api.nvim_buf_line_count(0)
|
||||
local zb = 'keepj norm! %dzb'
|
||||
if lnum1 == lcount then
|
||||
cmd(zb:format(lnum1))
|
||||
return
|
||||
end
|
||||
cmd('norm! zvzz')
|
||||
lnum1 = api.nvim_win_get_cursor(0)[1]
|
||||
cmd('norm! L')
|
||||
local lnum2 = api.nvim_win_get_cursor(0)[1]
|
||||
if lnum2 + fn.getwinvar(0, '&scrolloff') >= lcount then
|
||||
cmd(zb:format(lnum2))
|
||||
end
|
||||
if lnum1 ~= lnum2 then
|
||||
cmd('keepj norm! ``')
|
||||
end
|
||||
end
|
||||
|
||||
---
|
||||
---@param bufnr number
|
||||
---@param name? string
|
||||
---@param off? number
|
||||
---@return boolean
|
||||
function M.isUnNameBuf(bufnr, name, off)
|
||||
name = name or api.nvim_buf_get_name(bufnr)
|
||||
off = off or api.nvim_buf_get_offset(bufnr, 1)
|
||||
return name == '' and off <= 0
|
||||
end
|
||||
|
||||
---
|
||||
---@param winid number
|
||||
---@return boolean
|
||||
function M.isDiffOrMarkerFold(winid)
|
||||
local method = vim.wo[winid].foldmethod
|
||||
return method == 'diff' or method == 'marker'
|
||||
end
|
||||
|
||||
---
|
||||
---@param winid number
|
||||
---@return table<string, number>
|
||||
function M.getWinInfo(winid)
|
||||
local winfos = fn.getwininfo(winid)
|
||||
assert(type(winfos) == 'table' and #winfos == 1,
|
||||
'`getwininfo` expected 1 table with single element.')
|
||||
return winfos[1]
|
||||
end
|
||||
|
||||
---@param str string
|
||||
---@param targetWidth number
|
||||
---@return string
|
||||
function M.truncateStrByWidth(str, targetWidth)
|
||||
-- str in `strdisplaywidth` need to be converted from Lua to VimScript
|
||||
-- If a Lua string contains a NUL byte, it will be converted to a |Blob|.
|
||||
str = str:gsub('%z', '^@')
|
||||
if fn.strdisplaywidth(str) <= targetWidth then
|
||||
return str
|
||||
end
|
||||
local width = 0
|
||||
local byteOff = 0
|
||||
while true do
|
||||
local part = fn.strpart(str, byteOff, 1, true)
|
||||
width = width + fn.strdisplaywidth(part)
|
||||
if width > targetWidth then
|
||||
break
|
||||
end
|
||||
byteOff = byteOff + #part
|
||||
end
|
||||
return str:sub(1, byteOff)
|
||||
end
|
||||
|
||||
---
|
||||
---@param winid number
|
||||
---@return number
|
||||
function M.textoff(winid)
|
||||
return M.getWinInfo(winid).textoff
|
||||
end
|
||||
|
||||
---
|
||||
---@param bufnr number
|
||||
---@param lnum number 1-indexed
|
||||
---@param col number 1-indexed
|
||||
---@return number 0-indexed
|
||||
function M.curswant(bufnr, lnum, col)
|
||||
if col == 0 then
|
||||
return 0
|
||||
end
|
||||
local text = api.nvim_buf_get_lines(bufnr, lnum - 1, lnum, true)[1]
|
||||
text = M.expandTab(text:sub(1, col), vim.bo[bufnr].ts)
|
||||
return #text - 1
|
||||
end
|
||||
|
||||
---
|
||||
---@param winid number
|
||||
---@return boolean
|
||||
function M.isWinValid(winid)
|
||||
return type(winid) == 'number' and winid > 0 and api.nvim_win_is_valid(winid)
|
||||
end
|
||||
|
||||
---
|
||||
---@param bufnr number
|
||||
---@return boolean
|
||||
function M.isBufLoaded(bufnr)
|
||||
return type(bufnr) == 'number' and bufnr > 0 and api.nvim_buf_is_loaded(bufnr)
|
||||
end
|
||||
|
||||
---
|
||||
---@param winid number
|
||||
---@param line number
|
||||
---@param lsizes number
|
||||
---@return number, number
|
||||
function M.evaluateTopline(winid, line, lsizes)
|
||||
local log = require('ufo.lib.log')
|
||||
local topline
|
||||
local iStart = M.foldClosed(winid, line)
|
||||
iStart = iStart == -1 and line or iStart
|
||||
local lsizeSum = 0
|
||||
local i = iStart - 1
|
||||
local lsizeObj = require('ufo.model.linesize'):new(winid)
|
||||
local len = lsizes - lsizeObj:fillSize(line)
|
||||
log.info('winid:', winid, 'line:', line, 'lsizes:', lsizes, 'len:', len)
|
||||
local size
|
||||
while lsizeSum < len and i > 0 do
|
||||
local lnum = M.foldClosed(winid, i)
|
||||
log.info('lnum:', lnum, 'i:', i)
|
||||
if lnum == -1 then
|
||||
size = lsizeObj:size(i)
|
||||
else
|
||||
size = 1
|
||||
i = lnum
|
||||
end
|
||||
lsizeSum = lsizeSum + size
|
||||
log.info('size:', size, 'lsizeSum:', lsizeSum)
|
||||
topline = i
|
||||
i = i - 1
|
||||
end
|
||||
if not topline then
|
||||
topline = iStart
|
||||
end
|
||||
-- extraOff lines is need to be showed near the topline
|
||||
local topfill = lsizeObj:fillSize(topline)
|
||||
local extraOff = lsizeSum - len
|
||||
if extraOff > 0 then
|
||||
if topfill < extraOff then
|
||||
topline = topline + 1
|
||||
else
|
||||
topfill = topfill - extraOff
|
||||
end
|
||||
end
|
||||
log.info('topline:', topline, 'topfill:', topfill)
|
||||
return topline, topfill
|
||||
end
|
||||
|
||||
---
|
||||
---@param winid number
|
||||
---@return table
|
||||
function M.saveView(winid)
|
||||
return M.winCall(winid, fn.winsaveview)
|
||||
end
|
||||
|
||||
---
|
||||
---@param winid number
|
||||
---@param view table
|
||||
function M.restView(winid, view)
|
||||
M.winCall(winid, function()
|
||||
fn.winrestview(view)
|
||||
end)
|
||||
end
|
||||
|
||||
---
|
||||
---@param winid number
|
||||
---@return number
|
||||
function M.wrow(winid)
|
||||
return M.winCall(winid, fn.winline) - 1
|
||||
end
|
||||
|
||||
return M
|
||||
95
config/neovim/store/lazy-plugins/nvim-ufo/lua/ufo/wffi.lua
Normal file
95
config/neovim/store/lazy-plugins/nvim-ufo/lua/ufo/wffi.lua
Normal file
@ -0,0 +1,95 @@
|
||||
---@diagnostic disable: undefined-field
|
||||
---@class UfoWffi
|
||||
local M = {}
|
||||
|
||||
local utils
|
||||
local C
|
||||
local ffi
|
||||
|
||||
local CPos_T
|
||||
|
||||
local function findWin(winid)
|
||||
local err = ffi.new('Error')
|
||||
return C.find_window_by_handle(winid, err)
|
||||
end
|
||||
|
||||
---
|
||||
---@param winid number
|
||||
---@param lnum number
|
||||
---@return number
|
||||
function M.plinesWin(winid, lnum)
|
||||
local wp = findWin(winid)
|
||||
return C.plines_win(wp, lnum, true)
|
||||
end
|
||||
|
||||
---
|
||||
---@param winid number
|
||||
---@param lnum number
|
||||
---@param winheight boolean
|
||||
---@return number
|
||||
function M.plinesWinNofill(winid, lnum, winheight)
|
||||
local wp = findWin(winid)
|
||||
return C.plines_win_nofill(wp, lnum, winheight)
|
||||
end
|
||||
|
||||
---
|
||||
---@param winid number
|
||||
function M.clearFolds(winid)
|
||||
local wp = findWin(winid)
|
||||
C.clearFolding(wp)
|
||||
C.changed_window_setting(wp)
|
||||
end
|
||||
|
||||
---
|
||||
---@param winid number
|
||||
---@param ranges table<number, number[]> list of line range
|
||||
function M.createFolds(winid, ranges)
|
||||
local wp = findWin(winid)
|
||||
local s, e = CPos_T(), CPos_T()
|
||||
for _, p in ipairs(ranges) do
|
||||
s.lnum = p[1]
|
||||
e.lnum = p[2]
|
||||
C.foldCreate(wp, s, e)
|
||||
end
|
||||
end
|
||||
|
||||
local function init()
|
||||
ffi = require('ffi')
|
||||
setmetatable(M, {__index = ffi})
|
||||
C = ffi.C
|
||||
utils = require('ufo.utils')
|
||||
if utils.has08() then
|
||||
ffi.cdef([[
|
||||
typedef int32_t linenr_T;
|
||||
]])
|
||||
else
|
||||
ffi.cdef([[
|
||||
typedef long linenr_T;
|
||||
]])
|
||||
end
|
||||
ffi.cdef([[
|
||||
typedef struct window_S win_T;
|
||||
typedef int colnr_T;
|
||||
|
||||
typedef struct {} Error;
|
||||
win_T *find_window_by_handle(int window, Error *err);
|
||||
|
||||
typedef struct {
|
||||
linenr_T lnum;
|
||||
colnr_T col;
|
||||
colnr_T coladd;
|
||||
} pos_T;
|
||||
|
||||
void clearFolding(win_T *win);
|
||||
void changed_window_setting(win_T *wp);
|
||||
void foldCreate(win_T *wp, pos_T start, pos_T end);
|
||||
|
||||
int plines_win(win_T *wp, linenr_T lnum, bool winheight);
|
||||
int plines_win_nofill(win_T *wp, linenr_T lnum, bool winheight);
|
||||
]])
|
||||
CPos_T = ffi.typeof('pos_T')
|
||||
end
|
||||
|
||||
init()
|
||||
|
||||
return M
|
||||
Reference in New Issue
Block a user