jose-elias-alvarez / nvim-lsp-ts-utils

Utilities to improve the TypeScript development experience for Neovim's built-in LSP client.
The Unlicense
437 stars 18 forks source link

Support Inlay Hints #40

Closed rockerBOO closed 2 years ago

rockerBOO commented 3 years ago

Inlay hints just merged into typescript. https://github.com/microsoft/TypeScript/pull/42089

An example implementation: https://github.com/nvim-lua/lsp_extensions.nvim/blob/master/lua/lsp_extensions/inlay_hints.lua

I believe this will release in Typescript 4.4.

Thank you!

jose-elias-alvarez commented 3 years ago

This looks great! I think we'd have to get this integrated into typescript-language-server first, though. Optimistically, it seems like it would be pretty simple to wrap the tsserver method, but that might just be ignorance.

I also don't know how long it would take to get this merged, since the repo hasn't seen a lot of activity (though it looks like there's been some changes in the past week or so) but I'd be happy to work on a PR once 4.4 is released, since it looks like a really useful feature.

entropitor commented 3 years ago

@jose-elias-alvarez Should we open a ticket on typescript-language-server to expose the inlay hints?

jose-elias-alvarez commented 3 years ago

@entropitor Sure, I think it's worth opening up an issue to show interest (even though progress is likely to be slow).

entropitor commented 3 years ago

@jose-elias-alvarez I've implemented the inlay hints in the LSP (https://github.com/typescript-language-server/typescript-language-server/pull/259)

" To get all the inlay hints for the current buffer
lua print(vim.inspect(vim.lsp.buf_request_sync(0, 'typescript/inlayHints', {textDocument = vim.lsp.util.make_text_document_params()})))

" To get the inlay hints for the last selected region (if you pass a range, it will respect it)
lua print(vim.inspect(vim.lsp.buf_request_sync(0, 'typescript/inlayHints', vim.lsp.util.make_given_range_params())))

You need to pass the correct preferences to tsserver though, to maximally get hints for example:

    init_options = {
        hostInfo = "neovim",
        preferences = {
            includeInlayParameterNameHints = "all",
            includeInlayParameterNameHintsWhenArgumentMatchesName = true,
            includeInlayFunctionParameterTypeHints = true,
            includeInlayVariableTypeHints = true,
            includeInlayPropertyDeclarationTypeHints = true,
            includeInlayFunctionLikeReturnTypeHints = true,
            includeInlayEnumMemberValueHints = true
        }
    },
entropitor commented 3 years ago

Using some code from https://github.com/nvim-lua/lsp_extensions.nvim/blob/master/lua/lsp_extensions/inlay_hints.lua:

local util = {}
function util.mk_handler(fn)
    return function(...)
        local config_or_client_id = select(4, ...)
        local is_new = type(config_or_client_id) ~= "number"
        if is_new then
            fn(...)
        else
            local err = select(1, ...)
            local method = select(2, ...)
            local result = select(3, ...)
            local client_id = select(4, ...)
            local bufnr = select(5, ...)
            local config = select(6, ...)
            fn(err, result, {method = method, client_id = client_id, bufnr = bufnr}, config)
        end
    end
end
local inlay_hints = {}
local inlay_hints_ns = vim.api.nvim_create_namespace("lsp_extensions.inlay_hints")
local inlay_hints_callback = function()
    opts = opts or {}

    local highlight = opts.highlight or "Comment"
    local prefix = opts.prefix or ""
    local aligned = opts.aligned or false

    local enabled = opts.enabled or {"Type", "Enum", "Parameter"}

    local only_current_line = opts.only_current_line
    if only_current_line == nil then
        only_current_line = false
    end

    return util.mk_handler(
        function(err, result, ctx, _)
            if err then
                return
            end

            if not result or vim.tbl_isempty(result) then
                return
            end

            vim.api.nvim_buf_clear_namespace(ctx.bufnr, inlay_hints_ns, 0, -1)

            local hint_store = {}

            local longest_line = -1

            -- Check if something is in the list
            -- in_list({"ChainingHint"})("ChainingHint")
            local in_list = function(list)
                return function(item)
                    for _, f in ipairs(list) do
                        if f == item then
                            return true
                        end
                    end

                    return false
                end
            end

            for _, hint in ipairs(result.inlayHints) do
                local finish = hint.position.line
                if not hint_store[finish] and in_list(enabled)(hint.kind) then
                    hint_store[finish] = hint

                    if aligned then
                        longest_line =
                            math.max(longest_line, #vim.api.nvim_buf_get_lines(ctx.bufnr, finish, finish + 1, false)[1])
                    end
                end
            end

            local display_virt_text = function(hint)
                local end_line = hint.position.line

                -- Check for any existing / more important virtual text on the line.
                -- TODO: Figure out how stackable virtual text works? What happens if there is more than one??
                local existing_virt_text =
                    vim.api.nvim_buf_get_extmarks(ctx.bufnr, inlay_hints_ns, {end_line, 0}, {end_line, 0}, {})
                if not vim.tbl_isempty(existing_virt_text) then
                    return
                end

                local text
                if aligned then
                    local line_length = #vim.api.nvim_buf_get_lines(ctx.bufnr, end_line, end_line + 1, false)[1]
                    text = string.format("%s %s", (" "):rep(longest_line - line_length), prefix .. hint.text)
                else
                    text = prefix .. hint.text
                end
                vim.api.nvim_buf_set_virtual_text(ctx.bufnr, inlay_hints_ns, end_line, {{text, highlight}}, {})
            end

            if only_current_line then
                local hint = hint_store[vim.api.nvim_win_get_cursor(0)[1] - 1]

                if not hint then
                    return
                else
                    display_virt_text(hint)
                end
            else
                for _, hint in pairs(hint_store) do
                    display_virt_text(hint)
                end
            end
        end
    )
end
function M.inlay_hints()
    local params = {
        textDocument = vim.lsp.util.make_text_document_params()
    }
    vim.lsp.buf_request(0, "typescript/inlayHints", params, inlay_hints_callback())
end

It seems to work just fine 👍

image
jose-elias-alvarez commented 3 years ago

@entropitor Great! I hope they'll merge the PR soon. I'll (hopefully) find time to put together an implementation soon.

Gelio commented 3 years ago

typescript-language-server 0.6.3 is out with experimental support for inlay hints :tada:

https://github.com/typescript-language-server/typescript-language-server/releases/tag/v0.6.3

rockerBOO commented 2 years ago

Now implemented #79 Thanks @simrat39 @entropitor