# Treesitter Go error folding

I mostly use neovim for writing code. Especially Go code.
Two years ago I was presented with Goland which seemed a little bit overwhelming to me.

But there was a feature that looked quite interesting.
Goland could fold error handling blocks with some sense of context.

I've recently tried treesitter and it turned out that implementing such feature in neovim is trivial:

 1-- ftplugin/go.lua
 2local q = vim.treesitter.query.parse("go", [[
 3(if_statement 
 4    condition: (binary_expression
 5        left: (identifier) @val (#eq? @val "err")
 6        operator: ("!=")
 7        right: (nil))
 8    consequence: (block (return_statement (expression_list [
 9        (identifier)
10        (call_expression
11            function: (selector_expression 
12                field: (field_identifier))
13            arguments: (_))] @ret .))
14        ) @block)
15]])
16
17local bufnr = vim.api.nvim_get_current_buf()
18local ns = vim.api.nvim_create_namespace("go-err")
19
20vim.api.nvim_create_autocmd("CursorMoved", {
21    group = vim.api.nvim_create_augroup("CollapseErrors", {clear = true}),
22    buffer = bufnr,
23    callback = function()
24        local row, _ = unpack(vim.api.nvim_win_get_cursor(0))
25        local height = vim.fn.getwininfo(vim.api.nvim_get_current_win())[1].height
26        local root = vim.treesitter.get_parser(bufnr, "go"):parse()[1]:root()
27
28        vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1)
29
30        for _, matches in q:iter_matches(root, bufnr) do
31            local captures = {}
32            for id, nodes in pairs(matches) do
33                captures[q.captures[id]] = nodes[1]
34            end
35
36            local start_row, start_col, end_row = captures["block"]:range()
37            if start_row > row + height + height/2 then break end
38            if row-1 >= start_row and row-1 <= end_row then goto continue end
39
40            vim.api.nvim_buf_set_extmark(bufnr, ns, start_row, start_col+1, {
41                virt_text_pos = "inline",
42                virt_text = {{string.format(" -> %s", vim.treesitter.get_node_text(captures["ret"], bufnr)), "Comment"}},
43            })
44
45            vim.api.nvim_buf_set_extmark(bufnr, ns, start_row+1, 0, {
46                end_row = end_row,
47                conceal_lines = "",
48            })
49
50            ::continue::
51        end
52    end,
53})

Don't forget to vim.o.conceallevel >= 2.