mirror of
https://github.com/neovim/neovim.git
synced 2025-10-09 11:26:37 +00:00
feat(runtime): undotree #35627
Problem No builtin way to visualize and navigate the undo-tree. Solution Include an "opt" plugin.
This commit is contained in:
@@ -196,6 +196,7 @@ EDITOR
|
||||
"(v)iew" then run `:trust`.
|
||||
• |gx| in help buffers opens the online documentation for the tag under the
|
||||
cursor.
|
||||
• |:Undotree| for visually navigating the |undo-tree|
|
||||
|
||||
EVENTS
|
||||
|
||||
|
@@ -38,6 +38,7 @@ Help-link Loaded Short description ~
|
||||
|pi_zip.txt| Yes Zip archive explorer
|
||||
|spellfile.vim| Yes Install spellfile if missing
|
||||
|tohtml| Yes Convert buffer to html, syntax included
|
||||
|undotree| No Interactive textual undotree
|
||||
|
||||
==============================================================================
|
||||
Builtin plugin: editorconfig *editorconfig*
|
||||
@@ -157,4 +158,33 @@ tohtml({winid}, {opt}) *tohtml.tohtml()*
|
||||
(`string[]`)
|
||||
|
||||
|
||||
==============================================================================
|
||||
Builtin plugin: undotree *undotree*
|
||||
|
||||
open({opts}) *undotree.open()*
|
||||
Open a window that displays a textual representation of the undotree.
|
||||
|
||||
While in the window, moving the cursor changes the undo.
|
||||
|
||||
Load the plugin with this command: >
|
||||
packadd nvim.undotree
|
||||
<
|
||||
|
||||
Can also be shown with `:Undotree`. *:Undotree*
|
||||
|
||||
Parameters: ~
|
||||
• {opts} (`table?`) A table with the following fields:
|
||||
• {bufnr} (`integer?`) Buffer to draw the tree into. If
|
||||
omitted, a new buffer is created.
|
||||
• {winid} (`integer?`) Window id to display the tree buffer
|
||||
in. If omitted, a new window is created with {command}.
|
||||
• {command} (`string?`) Vimscript command to create the
|
||||
window. Default value is "30vnew". Only used when {winid} is
|
||||
nil.
|
||||
• {title} (`string|fun(bufnr:integer):string?`) Title of the
|
||||
window. If a function, it accepts the buffer number of the
|
||||
source buffer as its only argument and should return a
|
||||
string.
|
||||
|
||||
|
||||
vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl:
|
||||
|
406
runtime/pack/dist/opt/nvim.undotree/lua/undotree.lua
vendored
Normal file
406
runtime/pack/dist/opt/nvim.undotree/lua/undotree.lua
vendored
Normal file
@@ -0,0 +1,406 @@
|
||||
--- @class (private) vim.undotree.tree.entry
|
||||
--- @field child integer[]
|
||||
--- @field time integer
|
||||
|
||||
--- @alias vim.undotree.tree {[integer]: vim.undotree.tree.entry}
|
||||
|
||||
local M = {}
|
||||
|
||||
local ns = vim.api.nvim_create_namespace('nvim.undotree')
|
||||
|
||||
--- @param buf integer
|
||||
--- @return vim.fn.undotree.entry[]
|
||||
--- @return integer
|
||||
local function get_undotree_entries(buf)
|
||||
local undotree = vim.fn.undotree(buf)
|
||||
local entries = undotree.entries
|
||||
|
||||
--Maybe: `:undo 0` and then `undotree` to get seq 0 time
|
||||
table.insert(entries, 1, { seq = 0, time = -1 })
|
||||
|
||||
return entries, undotree.seq_cur
|
||||
end
|
||||
|
||||
--- @param ent vim.fn.undotree.entry[]
|
||||
--- @param _tree vim.undotree.tree?
|
||||
--- @param _last integer?
|
||||
--- @return vim.undotree.tree
|
||||
local function treefy(ent, _tree, _last)
|
||||
local tree = _tree or {}
|
||||
local last = _last or nil
|
||||
|
||||
for idx, v in ipairs(ent) do
|
||||
local seq = v.seq
|
||||
|
||||
if last then
|
||||
table.insert(tree[last].child, seq)
|
||||
else
|
||||
assert(idx == 1 and not _tree)
|
||||
end
|
||||
|
||||
tree[seq] = { child = {}, time = v.time }
|
||||
if v.alt then
|
||||
assert(last)
|
||||
treefy(v.alt, tree, last)
|
||||
end
|
||||
last = seq
|
||||
end
|
||||
|
||||
return tree
|
||||
end
|
||||
|
||||
--- @class (private) vim.undotree.graph_line
|
||||
--- @field kind 'node'|'remove'|'branch'|'remove+branch'|'nochange_remove'
|
||||
--- @field index integer
|
||||
--- @field node_count integer
|
||||
--- @field node integer|integer[]
|
||||
--- @field index2 integer? -- for branch-index in `remove+branch`
|
||||
|
||||
--- @param tree vim.undotree.tree
|
||||
--- @return vim.undotree.graph_line[]
|
||||
local function tree_to_graph_lines(tree)
|
||||
--- @type vim.undotree.graph_line[]
|
||||
local graph_lines = {}
|
||||
|
||||
assert(tree[0], "tree doesn't have 0-th node")
|
||||
--- @type (integer[]|integer)[]
|
||||
local nodes = { 0 }
|
||||
|
||||
while #nodes > 0 do
|
||||
local minseq = math.huge
|
||||
--- @type integer
|
||||
local index
|
||||
--- @type integer
|
||||
local node_index
|
||||
|
||||
for k, v in ipairs(nodes) do
|
||||
if type(v) == 'table' then
|
||||
for i, j in ipairs(v) do
|
||||
if j < minseq then
|
||||
minseq = j
|
||||
index = k
|
||||
node_index = i
|
||||
end
|
||||
end
|
||||
elseif v < minseq then
|
||||
assert(type(v) == 'number')
|
||||
minseq = v
|
||||
index = k
|
||||
end
|
||||
end
|
||||
|
||||
local node = nodes[index]
|
||||
|
||||
--- @param kind 'node'|'remove'|'branch'|'nochange_remove'
|
||||
local function add_graph_line(kind)
|
||||
table.insert(graph_lines, { kind = kind, index = index, node_count = #nodes, node = node })
|
||||
end
|
||||
|
||||
if type(node) == 'number' then
|
||||
add_graph_line('node')
|
||||
|
||||
local child = tree[node].child
|
||||
if #child == 0 then
|
||||
if index ~= #nodes then
|
||||
add_graph_line('remove')
|
||||
else
|
||||
add_graph_line('nochange_remove')
|
||||
end
|
||||
|
||||
table.remove(nodes, index)
|
||||
elseif #child == 1 then
|
||||
nodes[index] = child[1]
|
||||
else
|
||||
nodes[index] = child
|
||||
end
|
||||
else
|
||||
assert(type(node) == 'table')
|
||||
|
||||
add_graph_line('branch')
|
||||
|
||||
table.remove(nodes, index)
|
||||
if #node == 2 then
|
||||
table.insert(nodes, index, math.min(unpack(node)))
|
||||
table.insert(nodes, index, math.max(unpack(node)))
|
||||
elseif #node > 2 then
|
||||
table.insert(nodes, index, node[node_index])
|
||||
table.insert(nodes, index, node)
|
||||
table.remove(node, node_index)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for k, v in ipairs(graph_lines) do
|
||||
if v.kind == 'remove' and (graph_lines[k + 1] or {}).kind == 'branch' then
|
||||
v.kind = 'remove+branch'
|
||||
v.index2 = graph_lines[k + 1].index
|
||||
table.remove(graph_lines, k + 1)
|
||||
end
|
||||
end
|
||||
|
||||
return graph_lines
|
||||
end
|
||||
|
||||
--- @param time integer
|
||||
--- @return string
|
||||
local function undo_fmt_time(time)
|
||||
if time == -1 then
|
||||
return 'origin'
|
||||
end
|
||||
|
||||
local diff = os.time() - time
|
||||
|
||||
if diff >= 100 then
|
||||
if diff < (60 * 60 * 12) then
|
||||
return os.date('%H:%M:%S', time) --[[@as string]]
|
||||
else
|
||||
return os.date('%Y/%m/%d %H:%M:%S', time) --[[@as string]]
|
||||
end
|
||||
else
|
||||
return ('%d second%s ago'):format(diff, diff == 1 and '' or 's')
|
||||
end
|
||||
end
|
||||
|
||||
--- @param tree vim.undotree.tree
|
||||
--- @param graph_lines vim.undotree.graph_line[]
|
||||
--- @param buf integer
|
||||
--- @param meta {[integer]:integer}
|
||||
--- @param find_seq? integer
|
||||
--- @return integer?
|
||||
local function buf_apply_graph_lines(tree, graph_lines, buf, meta, find_seq)
|
||||
-- As in io-buffer, not vim-buffer
|
||||
local line_buffer = {}
|
||||
local extmark_buffer = {}
|
||||
|
||||
--- @type integer?
|
||||
local found_seq
|
||||
|
||||
for k, v in ipairs(graph_lines) do
|
||||
local is_last = k == #graph_lines
|
||||
|
||||
--- @type string?
|
||||
local line
|
||||
if v.kind == 'node' then
|
||||
line = ('| '):rep(v.index - 1)
|
||||
.. '*'
|
||||
.. (' |'):rep(v.node_count - v.index)
|
||||
.. ' '
|
||||
.. v.node
|
||||
.. ' ('
|
||||
.. undo_fmt_time(tree[v.node].time)
|
||||
.. ')'
|
||||
elseif v.kind == 'remove' then
|
||||
line = ('| '):rep(v.index - 1) .. (' /'):rep(v.node_count - v.index)
|
||||
elseif v.kind == 'branch' then
|
||||
line = ('| '):rep(v.index - 1) .. '|\\' .. (' \\'):rep(v.node_count - v.index)
|
||||
elseif v.kind == 'remove+branch' then
|
||||
if v.index2 < v.index then
|
||||
line = ('| '):rep(v.index2 - 1)
|
||||
.. '|\\'
|
||||
.. (' \\'):rep(v.index - v.index2 - 1)
|
||||
.. ' '
|
||||
.. (' |'):rep(v.node_count - v.index)
|
||||
else
|
||||
line = ('| '):rep(v.index - 1)
|
||||
.. (' /'):rep(v.index2 - v.index)
|
||||
.. ' /|'
|
||||
.. (' |'):rep(v.node_count - v.index2 - 1)
|
||||
end
|
||||
elseif v.kind == 'nochange_remove' then
|
||||
line = nil
|
||||
else
|
||||
error 'unreachable'
|
||||
end
|
||||
|
||||
if v.kind == 'node' then
|
||||
table.insert(line_buffer, line)
|
||||
table.insert(meta, v.node)
|
||||
|
||||
if v.node == find_seq then
|
||||
found_seq = #meta
|
||||
end
|
||||
elseif line then
|
||||
table.insert(extmark_buffer, { { line, 'Comment' } })
|
||||
end
|
||||
|
||||
if next(extmark_buffer) and (v.kind == 'node' or is_last) then
|
||||
local row = vim.api.nvim_buf_line_count(buf)
|
||||
vim.api.nvim_buf_set_extmark(buf, ns, row - 1, 0, { virt_lines = extmark_buffer })
|
||||
extmark_buffer = {}
|
||||
end
|
||||
|
||||
if next(line_buffer) and (v.kind ~= 'node' or is_last) then
|
||||
vim.api.nvim_buf_set_lines(buf, -1, -1, true, line_buffer)
|
||||
|
||||
if #line_buffer > 3 then
|
||||
local end_ = vim.api.nvim_buf_line_count(buf) - 1
|
||||
local start = end_ - #line_buffer + 3
|
||||
vim.api.nvim_buf_call(buf, function()
|
||||
vim.cmd.fold { range = { start, end_ } }
|
||||
end)
|
||||
end
|
||||
|
||||
line_buffer = {}
|
||||
end
|
||||
end
|
||||
|
||||
vim.api.nvim_buf_set_lines(buf, 0, 1, true, {})
|
||||
|
||||
return found_seq
|
||||
end
|
||||
|
||||
---@param inbuf integer
|
||||
---@param outbuf integer
|
||||
---@return {[integer]:integer}
|
||||
local function draw(inbuf, outbuf)
|
||||
local entries, curseq = get_undotree_entries(inbuf)
|
||||
local tree = treefy(entries)
|
||||
local graph_lines = tree_to_graph_lines(tree)
|
||||
|
||||
local meta = {}
|
||||
vim.bo[outbuf].modifiable = true
|
||||
vim.api.nvim_buf_set_lines(outbuf, 0, -1, true, {})
|
||||
vim.api.nvim_buf_clear_namespace(outbuf, ns, 0, -1)
|
||||
local curseq_line = buf_apply_graph_lines(tree, graph_lines, outbuf, meta, curseq)
|
||||
vim.bo[outbuf].modifiable = false
|
||||
|
||||
if vim.api.nvim_win_is_valid(vim.b[outbuf].nvim_is_undotree) then
|
||||
vim.api.nvim_win_set_cursor(vim.b[outbuf].nvim_is_undotree, { curseq_line, 0 })
|
||||
end
|
||||
|
||||
return meta
|
||||
end
|
||||
|
||||
--- @class vim.undotree.opts
|
||||
--- @inlinedoc
|
||||
---
|
||||
--- Buffer to draw the tree into. If omitted, a new buffer is created.
|
||||
--- @field bufnr integer?
|
||||
---
|
||||
--- Window id to display the tree buffer in. If omitted, a new window is
|
||||
--- created with {command}.
|
||||
--- @field winid integer?
|
||||
---
|
||||
--- Vimscript command to create the window. Default value is "30vnew".
|
||||
--- Only used when {winid} is nil.
|
||||
--- @field command string?
|
||||
---
|
||||
--- Title of the window. If a function, it accepts the buffer number of the
|
||||
--- source buffer as its only argument and should return a string.
|
||||
--- @field title (string|fun(bufnr:integer):string|nil)
|
||||
|
||||
--- Open a window that displays a textual representation of the undotree.
|
||||
---
|
||||
--- While in the window, moving the cursor changes the undo.
|
||||
---
|
||||
--- Load the plugin with this command:
|
||||
--- ```
|
||||
--- packadd nvim.undotree
|
||||
--- ```
|
||||
---
|
||||
--- Can also be shown with `:Undotree`. [:Undotree]()
|
||||
---
|
||||
--- @param opts vim.undotree.opts?
|
||||
function M.open(opts)
|
||||
-- The following lines of code was copied from
|
||||
-- `vim.treesitter.dev.inspect_tree` and then modified to fit
|
||||
|
||||
vim.validate('opts', opts, 'table', true)
|
||||
|
||||
opts = opts or {}
|
||||
|
||||
local buf = vim.api.nvim_get_current_buf()
|
||||
|
||||
if vim.b[buf].nvim_undotree then
|
||||
local w = vim.b[buf].nvim_undotree
|
||||
if vim.api.nvim_win_is_valid(w) then
|
||||
vim.api.nvim_win_close(w, true)
|
||||
return true
|
||||
end
|
||||
elseif vim.b[buf].nvim_is_undotree then
|
||||
local w = vim.b[buf].nvim_is_undotree
|
||||
if vim.api.nvim_win_is_valid(w) then
|
||||
vim.api.nvim_win_close(w, true)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
local w = opts.winid
|
||||
if not w then
|
||||
vim.cmd(opts.command or '30vnew')
|
||||
w = vim.api.nvim_get_current_win()
|
||||
end
|
||||
|
||||
local b = opts.bufnr
|
||||
if b then
|
||||
vim.api.nvim_win_set_buf(w, b)
|
||||
else
|
||||
b = vim.api.nvim_win_get_buf(w)
|
||||
end
|
||||
|
||||
vim.b[buf].nvim_undotree = w
|
||||
vim.b[b].nvim_is_undotree = w
|
||||
|
||||
local title --- @type string?
|
||||
local opts_title = opts.title
|
||||
if not opts_title then
|
||||
local bufname = vim.api.nvim_buf_get_name(buf)
|
||||
title = string.format('Undo tree for %s', vim.fn.fnamemodify(bufname, ':.'))
|
||||
elseif type(opts_title) == 'function' then
|
||||
title = opts_title(buf)
|
||||
end
|
||||
|
||||
assert(type(title) == 'string', 'Window title must be a string')
|
||||
vim.api.nvim_buf_set_name(b, title)
|
||||
|
||||
vim.wo[w][0].scrolloff = 5
|
||||
vim.wo[w][0].wrap = false
|
||||
vim.wo[w][0].foldmethod = 'manual'
|
||||
vim.wo[w][0].foldenable = true
|
||||
vim.wo[w][0].cursorline = true
|
||||
vim.bo[b].buflisted = false
|
||||
vim.bo[b].buftype = 'nofile'
|
||||
vim.bo[b].bufhidden = 'wipe'
|
||||
vim.bo[b].swapfile = false
|
||||
|
||||
local meta = draw(buf, b)
|
||||
|
||||
vim.api.nvim_win_set_cursor(w, { vim.api.nvim_buf_line_count(b), 0 })
|
||||
|
||||
local group = vim.api.nvim_create_augroup('nvim.undotree', { clear = false })
|
||||
vim.api.nvim_clear_autocmds({ buffer = b })
|
||||
vim.api.nvim_clear_autocmds({ buffer = buf })
|
||||
|
||||
vim.api.nvim_win_call(w, function()
|
||||
vim.cmd.syntax('region Comment start="(" end=")"')
|
||||
end)
|
||||
|
||||
vim.api.nvim_create_autocmd('CursorMoved', {
|
||||
group = group,
|
||||
buffer = b,
|
||||
callback = function()
|
||||
local row = vim.fn.line('.')
|
||||
vim.api.nvim_buf_call(buf, function()
|
||||
vim.cmd.undo { meta[row], mods = { silent = true } }
|
||||
end)
|
||||
end,
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd({ 'TextChanged', 'InsertLeave' }, {
|
||||
group = group,
|
||||
buffer = buf,
|
||||
callback = function()
|
||||
if not vim.api.nvim_buf_is_valid(b) then
|
||||
return true
|
||||
end
|
||||
|
||||
meta = draw(buf, b)
|
||||
|
||||
if vim.api.nvim_win_is_valid(w) then
|
||||
vim.wo[w][0].foldlevel = 99
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
return M
|
8
runtime/pack/dist/opt/nvim.undotree/plugin/undotree.lua
vendored
Normal file
8
runtime/pack/dist/opt/nvim.undotree/plugin/undotree.lua
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
if vim.g.loaded_undotree_plugin ~= nil then
|
||||
return
|
||||
end
|
||||
vim.g.loaded_undotree_plugin = true
|
||||
|
||||
vim.api.nvim_create_user_command('Undotree', function()
|
||||
require 'undotree'.open()
|
||||
end, {})
|
@@ -415,10 +415,12 @@ local config = {
|
||||
section_order = {
|
||||
'editorconfig.lua',
|
||||
'tohtml.lua',
|
||||
'undotree.lua',
|
||||
},
|
||||
files = {
|
||||
'runtime/lua/editorconfig.lua',
|
||||
'runtime/lua/tohtml.lua',
|
||||
'runtime/pack/dist/opt/nvim.undotree/lua/undotree.lua',
|
||||
},
|
||||
fn_xform = function(fun)
|
||||
if fun.module == 'editorconfig' then
|
||||
|
136
test/functional/plugin/undotree_spec.lua
Normal file
136
test/functional/plugin/undotree_spec.lua
Normal file
@@ -0,0 +1,136 @@
|
||||
local t = require('test.testutil')
|
||||
local n = require('test.functional.testnvim')()
|
||||
|
||||
local clear = n.clear
|
||||
local eq = t.eq
|
||||
local exec = n.exec
|
||||
local api = n.api
|
||||
local dedent = t.dedent
|
||||
|
||||
---@param reverse_tree {[integer]:integer}
|
||||
local function generate_undo_tree_from_rev(reverse_tree)
|
||||
for k, v in ipairs(reverse_tree) do
|
||||
exec('undo ' .. v)
|
||||
api.nvim_buf_set_lines(0, 0, -1, true, { tostring(k) })
|
||||
end
|
||||
end
|
||||
---@param buf integer
|
||||
---@return string
|
||||
local function buf_get_lines_and_extmark(buf)
|
||||
local lines = api.nvim_buf_get_lines(buf, 0, -1, true)
|
||||
local ns = api.nvim_create_namespace('nvim.undotree')
|
||||
local extmarks = api.nvim_buf_get_extmarks(buf, ns, 0, -1, { details = true })
|
||||
for i = #extmarks, 1, -1 do
|
||||
local extmark = extmarks[i]
|
||||
---@type nil,integer,nil,vim.api.keyset.extmark_details
|
||||
local _, row, _, opts = unpack(extmark)
|
||||
local virt_lines = assert(opts.virt_lines)
|
||||
for _, v in ipairs(virt_lines) do
|
||||
local virt_line = v[1][1]
|
||||
table.insert(lines, row + 2, virt_line)
|
||||
end
|
||||
end
|
||||
return table.concat(lines, '\n')
|
||||
end
|
||||
|
||||
local function strip_time(text)
|
||||
return text:gsub('%s-%(.-%)', '')
|
||||
end
|
||||
|
||||
describe(':Undotree', function()
|
||||
before_each(function()
|
||||
clear({ args = { '--clean' } })
|
||||
exec 'packadd nvim.undotree'
|
||||
end)
|
||||
|
||||
it('works', function()
|
||||
api.nvim_set_current_line('foo')
|
||||
exec 'Undotree'
|
||||
local buf = api.nvim_get_current_buf()
|
||||
local win = api.nvim_get_current_win()
|
||||
eq(
|
||||
dedent [[
|
||||
* 0
|
||||
* 1]],
|
||||
strip_time(buf_get_lines_and_extmark(buf))
|
||||
)
|
||||
eq(2, api.nvim_win_get_cursor(win)[1])
|
||||
exec 'wincmd w'
|
||||
|
||||
-- Doing changes moves cursor in undotree
|
||||
exec 'undo'
|
||||
eq(1, api.nvim_win_get_cursor(win)[1])
|
||||
api.nvim_set_current_line('bar')
|
||||
eq(3, api.nvim_win_get_cursor(win)[1])
|
||||
|
||||
eq(
|
||||
dedent [[
|
||||
* 0
|
||||
|\
|
||||
| * 1
|
||||
* 2]],
|
||||
strip_time(buf_get_lines_and_extmark(buf))
|
||||
)
|
||||
|
||||
-- Moving the cursor in undotree changes the buffer
|
||||
eq('bar', api.nvim_get_current_line())
|
||||
exec 'wincmd w'
|
||||
exec '2'
|
||||
exec 'wincmd w'
|
||||
eq('foo', api.nvim_get_current_line())
|
||||
end)
|
||||
|
||||
describe('branch+remove is correctly graphed', function()
|
||||
it('when branching left', function()
|
||||
generate_undo_tree_from_rev({ 0, 1, 2, 3, 1, 3, 4, 3, 2, 0 })
|
||||
exec 'Undotree'
|
||||
eq(
|
||||
dedent([[
|
||||
* 0
|
||||
|\
|
||||
| * 1
|
||||
| |\
|
||||
| | * 2
|
||||
| | |\
|
||||
| | | * 3
|
||||
| | | |\
|
||||
| | | | * 4
|
||||
| * | | | 5
|
||||
| / /| |]] --[[This is the line being tested, e.g. remove&branch left]] .. '\n' .. [[
|
||||
| | | * | 6
|
||||
| | | /
|
||||
| | | * 7
|
||||
| | * 8
|
||||
| * 9
|
||||
* 10]]),
|
||||
strip_time(buf_get_lines_and_extmark(0))
|
||||
)
|
||||
end)
|
||||
|
||||
it('when branching right', function()
|
||||
generate_undo_tree_from_rev({ 0, 1, 2, 3, 3, 1, 4, 2, 1, 0 })
|
||||
exec 'Undotree'
|
||||
eq(
|
||||
dedent([[
|
||||
* 0
|
||||
|\
|
||||
| * 1
|
||||
| |\
|
||||
| | * 2
|
||||
| | |\
|
||||
| | | * 3
|
||||
| | | |\
|
||||
| | | | * 4
|
||||
| | | * | 5
|
||||
| |\ \ |]] --[[This is the line being tested, e.g. remove&branch right]] .. '\n' .. [[
|
||||
| | * | | 6
|
||||
| | / /
|
||||
| | | * 7
|
||||
| | * 8
|
||||
| * 9
|
||||
* 10]]),
|
||||
strip_time(buf_get_lines_and_extmark(0))
|
||||
)
|
||||
end)
|
||||
end)
|
||||
end)
|
@@ -138,7 +138,7 @@ endfunc
|
||||
|
||||
func Test_help_completion()
|
||||
call feedkeys(":help :undo\<C-A>\<C-B>\"\<CR>", 'tx')
|
||||
call assert_equal('"help :undo :undoj :undol :undojoin :undolist', @:)
|
||||
call assert_equal('"help :undo :undoj :undol :undojoin :undolist :Undotree', @:)
|
||||
endfunc
|
||||
|
||||
" Test for the :helptags command
|
||||
|
Reference in New Issue
Block a user