numToStr / FTerm.nvim

:fire: No-nonsense floating terminal plugin for neovim :fire:
MIT License
721 stars 24 forks source link

Floating help buffers #92

Closed adigitoleo closed 8 months ago

adigitoleo commented 10 months ago

I always disliked the vim8 style help split, which destroys the window layout and is also quite brittle (e.g. try <C-O> too many times, or b# twice and now the listchars are visible...)

I think a much better UX would be to have the :help appear in a floating scratch window. It is a bit tricky, because (neo)vim doesn't really allow it, but it can be done in two ways. The first is a workaround: open a new instance of nvim inside the FTerm scratch window:

vim.api.nvim_create_user_command("H", function(opts)
    local arg = vim.fn.expand("<cword>")
    if opts.args ~= "" then arg = opts.args end
    require("FTerm").scratch({ cmd = { "nvim", "-c", "help " .. arg, "-c", "only" } })
end, { nargs = "?", complete = "help" })

The second would require a bit more work. I had some vimL function in my config before that would basically open a floating window with a temporary buffer, set the filetype to help and then run :help <arg> with the argument from the user. It worked quite well actually, but there may be some bugs with the temporary buffer hanging around.

Would you be interested in adding this feature? I can share my VimL code or even help work towards the lua implementation.

Cheers

adigitoleo commented 8 months ago

Inspired by this plugin I was actually able to get this to work quite well. In addition it cleans up the floating window if an error is encountered (I cheated and used VimL for that part). Goes in init.lua:

local helpbuf = -1
local helpwin = -1
command("H", function(opts)
        local arg = fn.expand("<cword>")
        if opts.args ~= "" then arg = opts.args end
        local wc = vim.o.columns
        local wl = vim.o.lines
        local width = math.ceil(wc * 0.8)
        local height = math.ceil(wl * 0.8 - 4)
        if not api.nvim_buf_is_valid(helpbuf) then
            helpbuf = api.nvim_create_buf(true, false)
        end
        api.nvim_buf_set_option(helpbuf, "buftype", "help")
        if not api.nvim_win_is_valid(helpwin) then
            helpwin = api.nvim_open_win(helpbuf, true, {
                border = "single",
                relative = "editor",
                style = "minimal",
                width = width,
                height = height,
                col = math.ceil((wc - width) * 0.5),
                row = math.ceil((wl - height) * 0.5 - 1)
            })
        end
        local cmdparts = {
            "try|help ",
            arg,
            "|catch /^Vim(help):E149/|call nvim_win_close(",
            helpwin,
            ", v:false)|echoerr v:exception|endtry",
        }
        vim.cmd(table.concat(cmdparts))
        api.nvim_buf_set_option(helpbuf, "filetype", "help")
    end,
    { nargs = "?", complete = "help", desc = "Open neovim help of argument or word under cursor in floating window" }
)
adigitoleo commented 8 months ago

For floating versions of :Man, pre-set the filetype=man instead of the buftype. Variant that gives commands :H and :M which are floating variants of :help and :Man:

-- Open or focus floating window and set {buf|file}type.
local function floating(buf, win, bt, ft)
    -- buf: possibly existing buffer
    -- win: possibly existing window
    -- bt: desired buftype
    -- ft: desired filetype
    local wc = vim.o.columns
    local wl = vim.o.lines
    local width = math.ceil(wc * 0.8)
    local height = math.ceil(wl * 0.8 - 4)
    if not api.nvim_buf_is_valid(buf) then
        buf = api.nvim_create_buf(true, false)
    end
    api.nvim_buf_set_option(buf, "buftype", bt)
    api.nvim_buf_set_option(buf, "filetype", ft)
    if not api.nvim_win_is_valid(win) then
        win = api.nvim_open_win(buf, true, {
            border = "single",
            relative = "editor",
            style = "minimal",
            width = width,
            height = height,
            col = math.ceil((wc - width) * 0.5),
            row = math.ceil((wl - height) * 0.5 - 1)
        })
    end
    return buf, win
end

local helpbuf = -1
local helpwin = -1
command("H", function(opts)
        local arg = fn.expand("<cword>")
        if opts.args ~= "" then arg = opts.args end
        helpbuf, helpwin = floating(helpbuf, helpwin, "help", "help")
        local cmdparts = {
            "try|help ",
            arg,
            "|catch /^Vim(help):E149/|call nvim_win_close(",
            helpwin,
            ", v:false)|echoerr v:exception|endtry",
        }
        vim.cmd(table.concat(cmdparts))
        api.nvim_buf_set_option(helpbuf, "filetype", "help")  -- Set ft again to redraw conceal formatting.
    end,
    { nargs = "?", complete = "help", desc = "Open neovim help of argument or word under cursor in floating window" }
)
local manbuf = -1
local manwin = -1
command("M", function(opts)
        local arg = fn.expand("<cword>")
        if opts.args ~= "" then arg = opts.args end
        manbuf, manwin = floating(manbuf, manwin, "nofile", "man")
        local cmdparts = {
            "try|Man ",
            arg,
            '|catch /^Vim:man.lua: "no manual entry for/|call nvim_win_close(',
            manwin,
            ", v:false)|echoerr v:exception|endtry",
        }
        vim.cmd(table.concat(cmdparts))
    end, { nargs = "?", desc = "Show man page of argument or word under cursor in floating window" }
)
adigitoleo commented 4 months ago

It's also possible to add the normal :Man completion to :M from above, using:

    complete = function(arg_lead, cmdline, cursor_pos)
        local man = load("man")
        if man then
            return man.man_complete(arg_lead, cmdline, cursor_pos)
        end
    end