theHamsta / nvim-semantic-tokens

Apache License 2.0
84 stars 12 forks source link

Semantic token under cursor #15

Closed tiagovla closed 2 years ago

tiagovla commented 2 years ago

I wanted a command like the one treesitter has to debug semantic tokens. So, I came up with this:

local function get_bit(n, k)
    if _G.bit then
        return _G.bit.band(_G.bit.rshift(n, k), 1)
    else
        return math.floor((n / math.pow(2, k)) % 2)
    end
end

local function modifiers_from_number(x, modifiers_table)
    local modifiers = {}
    for i = 0, #modifiers_table - 1 do
        local bit = get_bit(x, i)
        if bit == 1 then
            table.insert(modifiers, 1, modifiers_table[i + 1])
        end
    end
    return modifiers
end

local function handler(err, response, ctx, config)
    local client = vim.lsp.get_client_by_id(ctx.client_id)
    if not client then
        return
    end
    local legend = client.server_capabilities.semanticTokensProvider.legend
    local token_types = legend.tokenTypes
    local token_modifiers = legend.tokenModifiers
    local data = response.data

    local line
    local start_char = 0
    for i = 1, #data, 5 do
        local delta_line = data[i]
        line = line and line + delta_line or delta_line
        local delta_start = data[i + 1]
        start_char = delta_line == 0 and start_char + delta_start or delta_start
        local token_type = token_types[data[i + 3] + 1]
        local modifiers = modifiers_from_number(data[i + 4], token_modifiers)

        local token = {
            line = line,
            start_char = start_char,
            length = data[i + 2],
            type = token_type,
            modifiers = modifiers,
            offset_encoding = client.offset_encoding,
        }

        local line, col = unpack(config.cursor)
        if token.line == line - 1 then
            if token.start_char <= col and col <= token.start_char + token.length then
                vim.pretty_print(token)
            end
        end
    end
end

local function semantic_token_under_cursor()
    local cursor = vim.api.nvim_win_get_cursor(0)
    local params = { textDocument = require("vim.lsp.util").make_text_document_params() }
    local h = vim.lsp.with(handler, { cursor = cursor })
    vim.lsp.buf_request(0, "textDocument/semanticTokens/full", params, h)
end

vim.api.nvim_create_user_command("SemanticTokenUnderCursor", semantic_token_under_cursor, {})

It would be better if I could reuse the on_full handler with a custom on_token, but it returns at https://github.com/theHamsta/nvim-semantic-tokens/blob/668ca37a73f1afa42e1675dd72c0630b81f69f81/lua/nvim-semantic-tokens/semantic_tokens.lua#L69. Is there any workaround for this?

rebelot commented 2 years ago

I have this stupid function which sends a range request and prints the token type and modifiers using vim.notify.

local on_full = require('nvim-semantic-tokens.semantic_tokens').on_full

function vim.lsp.buf.semantic_tokens_range(start_pos, end_pos)
    local params = vim.lsp.util.make_given_range_params(start_pos, end_pos)
    vim.lsp.buf_request(
        0,
        "textDocument/semanticTokens/range",
        params,
        vim.lsp.with(on_full, {
            on_token = function(ctx, token)
                vim.notify(token.type .. "." .. table.concat(token.modifiers, "."))
            end,
        })
    )
end

then you can do something like

nnoremap <silent> <leader>lM viw:lua vim.lsp.semantic_tokens_range()<cr>
command! LspInspectTokenCursor execute "norm viw\<esc>" | lua vim.lsp.buf.semantic_tokens_range()
tiagovla commented 2 years ago
"textDocument/semanticTokens/range"

I was trying something like that with the textDocument/semanticTokens/full method instead. Somehow my on_token was never called due to the tick check. I will try this out. Thanks!

jdknox commented 1 year ago

then you can do something like

nnoremap <silent> <leader>lM viw:lua vim.lsp.semantic_tokens_range()<cr>
command! LspInspectTokenCursor execute "norm viw\<esc>" | lua vim.lsp.buf.semantic_tokens_range()

When I try your code snippet, nothing happens. Is there supposed to be output in the command window?

Even if I manually viw a word and run :lua vim.lsp.semantic_tokens_range(), I get no output.

lsp.log shows: reply:textDocument/semanticTokens/range(4) 0 ms, error: -32601: method not found