elixir-lang / tree-sitter-elixir

Elixir grammar for tree-sitter
https://elixir-lang.org/tree-sitter-elixir
Apache License 2.0
245 stars 24 forks source link

Indentation broken for curly braces? #52

Closed dsincl12 closed 9 months ago

dsincl12 commented 1 year ago

When I install elixir for treesitter I get the syntax highlighting I expect, the issue is that if I enter a curly brace it loses the indentation.

Screenshot 2023-05-14 at 9 12 13 AM

Screenshot 2023-05-14 at 9 12 30 AM

I'll be honest and say that I'm not sure where to look for what is causing this. tree-sitter-elixir seems like a likely culprit at least. I've tried several Neovim distros (LazyVim, NvChad, kickstart.nvim) all with the same result.

josevalim commented 1 year ago

Can you please paste the code here and the Neovim versions you are using?

josevalim commented 1 year ago

You can also try this out: https://elixir-lang.org/tree-sitter-elixir/ - I pasted something similar and it looks correct to me. :)

dsincl12 commented 1 year ago

Sure,

NVIM v0.9.0 Build type: Release LuaJIT 2.1.0-beta3

system vimrc file: "$VIM/sysinit.vim" fall-back for $VIM: "/opt/homebrew/Cellar/neovim/0.9.0/share/nvim"

Run :checkhealth for more info

My init.lua file:

vim.g.mapleader = ' '
vim.g.maplocalleader = ' '
-- vim.o.guicursor = 'n-v-c-sm-i-ci-ve:block,r-cr-o:hor20,a:blinkwait700-blinkoff400-blinkon250-Cursor/lCursor'

local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
    vim.fn.system({
        "git",
        "clone",
        "--filter=blob:none",
        "https://github.com/folke/lazy.nvim.git",
        "--branch=stable", -- latest stable release
        lazypath,
    })
end
vim.opt.rtp:prepend(lazypath)

----------------
--- plugins ---
----------------
require("lazy").setup({

    -- colorscheme
    {
        'folke/tokyonight.nvim',
        enabled = true,
        priority = 1000,
        config = function()
            vim.cmd.colorscheme 'tokyonight-night'
        end,
    },

    -- Telescope
    {
        "nvim-telescope/telescope.nvim",
        dependencies = {
            { 'nvim-telescope/telescope-fzf-native.nvim', build = 'make' },
            'nvim-telescope/telescope-ui-select.nvim',
            "nvim-lua/plenary.nvim",
            build = "make",
            config = function()
                require("telescope").load_extension("fzf")
                require("telescope").load_extension("ui-select")
            end,
        },
        keys = {
            { "<leader>st",       "<cmd>Telescope live_grep<cr>",                 desc = "Live Grep" },
            { "<leader>:",        "<cmd>Telescope command_history<cr>",           desc = "Command History" },
            { "<leader>fb",       "<cmd>Telescope buffers<cr>",                   desc = "Buffers" },
            { "<leader><leader>", "<cmd>Telescope buffers<cr>",                   desc = "Buffers" },
            { "<leader>ff",       "<cmd>Telescope find_files<cr>",                desc = "Find Files" },
            { "<C-f>",            "<cmd>Telescope find_files<cr>",                desc = "Find Files" },
            { "<C-p>",            "<cmd>Telescope git_files<cr>",                 desc = "Git Files" },
            { "<leader>sa",       "<cmd>Telescope autocommands<cr>",              desc = "Auto Commands" },
            { "<leader>sb",       "<cmd>Telescope current_buffer_fuzzy_find<cr>", desc = "Buffer" },
            { "<leader>sc",       "<cmd>Telescope command_history<cr>",           desc = "Command History" },
            { "<leader>sC",       "<cmd>Telescope commands<cr>",                  desc = "Commands" },
            { "<leader>sd",       "<cmd>Telescope diagnostics bufnr=0<cr>",       desc = "Document diagnostics" },
            { "<leader>sD",       "<cmd>Telescope diagnostics<cr>",               desc = "Workspace diagnostics" },
            { "<leader>sh",       "<cmd>Telescope help_tags<cr>",                 desc = "Help Pages" },
            { "<leader>sH",       "<cmd>Telescope highlights<cr>",                desc = "Search Highlight Groups" },
            { "<leader>sk",       "<cmd>Telescope keymaps<cr>",                   desc = "Key Maps" },
            { "<leader>sM",       "<cmd>Telescope man_pages<cr>",                 desc = "Man Pages" },
            { "<leader>sm",       "<cmd>Telescope marks<cr>",                     desc = "Jump to Mark" },
            { "<leader>so",       "<cmd>Telescope vim_options<cr>",               desc = "Options" },
            { "<leader>sR",       "<cmd>Telescope resume<cr>",                    desc = "Resume" },
            { "<leader>sw",       "<cmd>Telescope grep_string<cr>",               desc = "Grep String" },
        },
        opts = function()
            local telescope = require('telescope')
            telescope.setup({
                defaults = {
                    file_ignore_patterns = { 'node_modules', 'package-lock.json' },
                    initial_mode         = 'insert',
                    select_strategy      = 'reset',
                    sorting_strategy     = 'ascending',
                    layout_config        = {
                        width = 0.75,
                        height = 0.75,
                        prompt_position = "top",
                        preview_cutoff = 120,
                    },
                },
                pickers = {
                    find_files = {
                        hidden           = true,
                        previewer        = false,
                        initial_mode     = 'insert',
                        select_strategy  = 'reset',
                        sorting_strategy = 'ascending',
                        layout_strategy  = 'horizontal',
                        layout_config    = {
                            prompt_position = "top",
                            preview_cutoff = 120,
                            horizontal = {
                                width = 0.5,
                                height = 0.4,
                                preview_width = 0.6,
                            },
                        },
                    },
                    git_files = {
                        hidden           = true,
                        previewer        = false,
                        initial_mode     = 'insert',
                        select_strategy  = 'reset',
                        sorting_strategy = 'ascending',
                        layout_strategy  = 'horizontal',
                        layout_config    = {
                            prompt_position = "top",
                            preview_cutoff = 120,
                            horizontal = {
                                width = 0.5,
                                height = 0.4,
                                preview_width = 0.6,
                            },
                        },
                    },
                    buffers = {
                        hidden           = true,
                        previewer        = false,
                        initial_mode     = 'insert',
                        select_strategy  = 'reset',
                        sorting_strategy = 'ascending',
                        layout_strategy  = 'horizontal',
                        layout_config    = {
                            prompt_position = "top",
                            preview_cutoff = 120,
                            horizontal = {
                                width = 0.5,
                                height = 0.4,
                                preview_width = 0.6,
                            },
                        },
                    },
                    live_grep = {
                        --@usage don't include the filename in the search results
                        only_sort_text = true,
                        previewer = true,
                        layout_config = {
                            horizontal = {
                                width = 0.9,
                                height = 0.75,
                                preview_width = 0.6,
                            },
                        },
                    },
                    grep_string = {
                        --@usage don't include the filename in the search results
                        only_sort_text = true,
                        previewer = true,
                        layout_config = {
                            horizontal = {
                                width = 0.9,
                                height = 0.75,
                                preview_width = 0.6,
                            },
                        },
                    },
                    lsp_references = {
                        show_line = false,
                        layout_config = {
                            horizontal = {
                                width = 0.9,
                                height = 0.75,
                                preview_width = 0.6,
                            },
                        },
                    },
                },
                extensions = {
                    fzf = {
                        fuzzy = true,                   -- false will only do exact matching
                        override_generic_sorter = true, -- override the generic sorter
                        override_file_sorter = true,    -- override the file sorter
                        case_mode = "smart_case",       -- or "ignore_case" or "respect_case"
                    },
                    ["ui-select"] = {
                        require("telescope.themes").get_dropdown({
                            previewer        = false,
                            initial_mode     = "normal",
                            sorting_strategy = 'ascending',
                            layout_strategy  = 'horizontal',
                            layout_config    = {
                                horizontal = {
                                    width = 0.5,
                                    height = 0.4,
                                    preview_width = 0.6,
                                },
                            },
                        })
                    },
                }
            })
        end
    },

    -- autopairs
    {
        "windwp/nvim-autopairs",
        config = function()
            require("nvim-autopairs").setup {
                check_ts = true,
            }
        end
    },

    -- Treesitter
    {
        "nvim-treesitter/nvim-treesitter",
        version = false, -- last release is way too old and doesn't work on Windows
        build = ":TSUpdate",
        event = { "BufReadPost", "BufNewFile" },
        dependencies = {
            "nvim-treesitter/nvim-treesitter-textobjects",
        },
        opts = function(_, opts)
            if type(opts.ensure_installed) == "table" then
                vim.list_extend(opts.ensure_installed, { "typescript", "tsx" })
            end
        end,
        config = function()
            require("nvim-treesitter.configs").setup {
                highlight = {
                    enable = true,
                    additional_vim_regex_highlighting = false,
                },
                indent = { enable = true },
                context_commentstring = { enable = true, enable_autocmd = false },
                auto_install = true,
                ensure_installed = {
                    "javascript",
                    "typescript",
                    "yaml",
                    "rust",
                    "go",
                    "elixir"
                },
                incremental_selection = {
                    enable = true,
                    keymaps = {
                        init_selection = "<leader>vv",
                        node_incremental = "+",
                        scope_incremental = false,
                        node_decremental = "_",
                    },
                },
                textobjects = {
                    select = {
                        enable = true,
                        lookahead = true,

                        keymaps = {
                            -- You can use the capture groups defined in textobjects.scm
                            ["af"] = { query = "@function.outer", desc = "around a function" },
                            ["if"] = { query = "@function.inner", desc = "inner part of a function" },
                            ["ac"] = { query = "@class.outer", desc = "around a class" },
                            ["ic"] = { query = "@class.inner", desc = "inner part of a class" },
                            ["ai"] = { query = "@conditional.outer", desc = "around an if statement" },
                            ["ii"] = { query = "@conditional.inner", desc = "inner part of an if statement" },
                            ["al"] = { query = "@loop.outer", desc = "around a loop" },
                            ["il"] = { query = "@loop.inner", desc = "inner part of a loop" },
                            ["ap"] = { query = "@parameter.outer", desc = "around parameter" },
                            ["ip"] = { query = "@parameter.inner", desc = "inside a parameter" },
                        },
                        selection_modes = {
                            ["@parameter.outer"] = "v",   -- charwise
                            ["@parameter.inner"] = "v",   -- charwise
                            ["@function.outer"] = "v",    -- charwise
                            ["@conditional.outer"] = "V", -- linewise
                            ["@loop.outer"] = "V",        -- linewise
                            ["@class.outer"] = "<c-v>",   -- blockwise
                        },
                        include_surrounding_whitespace = false,
                    },
                    move = {
                        enable = true,
                        set_jumps = true, -- whether to set jumps in the jumplist
                        goto_previous_start = {
                            ["[f"] = { query = "@function.outer", desc = "Previous function" },
                            ["[c"] = { query = "@class.outer", desc = "Previous class" },
                            ["[p"] = { query = "@parameter.inner", desc = "Previous parameter" },
                        },
                        goto_next_start = {
                            ["]f"] = { query = "@function.outer", desc = "Next function" },
                            ["]c"] = { query = "@class.outer", desc = "Next class" },
                            ["]p"] = { query = "@parameter.inner", desc = "Next parameter" },
                        },
                    },
                    swap = {
                        enable = true,
                        swap_next = {
                            ['<leader>a'] = '@parameter.inner',
                        },
                        swap_previous = {
                            ['<leader>A'] = '@parameter.inner',
                        },
                    },
                },
            }
        end
    },

    -- comments
    {
        'numToStr/Comment.nvim',
        opts = {},
        config = function()
            require('Comment').setup()
        end
    },
})

----------------
--- SETTINGS ---
----------------
local options = {
    incsearch = true,                        -- make search act like search in modern browsers
    backup = false,                          -- creates a backup file
    clipboard = "unnamedplus",               -- allows neovim to access the system clipboard
    cmdheight = 1,                           -- more space in the neovim command line for displaying messages
    completeopt = { "menuone", "noselect" }, -- mostly just for cmp
    conceallevel = 0,                        -- so that `` is visible in markdown files
    fileencoding = "utf-8",                  -- the encoding written to a file
    hlsearch = true,                         -- highlight all matches on previous search pattern
    ignorecase = true,                       -- ignore case in search patterns
    mouse = "a",                             -- allow the mouse to be used in neovim
    pumheight = 10,                          -- pop up menu height
    showmode = false,                        -- we don't need to see things like -- INSERT -- anymore
    showtabline = 0,                         -- always show tabs
    smartcase = true,                        -- smart case
    smartindent = true,                      -- make indenting smarter again
    splitbelow = true,                       -- force all horizontal splits to go below current window
    splitright = true,                       -- force all vertical splits to go to the right of current window
    swapfile = false,                        -- creates a swapfile
    termguicolors = true,                    -- set term gui colors (most terminals support this)
    timeoutlen = 1000,                       -- time to wait for a mapped sequence to complete (in milliseconds)
    undofile = true,                         -- enable persistent undo
    updatetime = 100,                        -- faster completion (4000ms default)
    writebackup = false,                     -- if a file is being edited by another program (or was written to file while editing with another program), it is not allowed to be edited
    expandtab = true,                        -- convert tabs to spaces
    shiftwidth = 2,                          -- the number of spaces inserted for each indentation
    tabstop = 2,                             -- insert 2 spaces for a tab
    cursorline = true,                       -- highlight the current line
    number = true,                           -- set numbered lines
    breakindent = true,                      -- wrap lines with indent
    relativenumber = true,                   -- set relative numbered lines
    numberwidth = 4,                         -- set number column width to 2 {default 4}
    signcolumn = "yes",                      -- always show the sign column, otherwise it would shift the text each time
    wrap = false,                            -- display lines as one long line
    scrolloff = 8,                           -- Makes sure there are always eight lines of context
    sidescrolloff = 8,                       -- Makes sure there are always eight lines of context
    laststatus = 3,                          -- Always display the status line
    showcmd = false,                         -- Don't show the command in the last line
    ruler = false,                           -- Don't show the ruler
    guifont = "monospace:h17",               -- the font used in graphical neovim applications
    title = true,                            -- set the title of window to the value of the titlestring
    modifiable = true,                       -- allow a buffer to be modified
}

for k, v in pairs(options) do
  vim.opt[k] = v
end

-- Keymaps --
vim.keymap.set("n", "<leader>e", vim.cmd.Ex)

-- Fast saving
vim.keymap.set('n', '<Leader>w', ':write!<CR>')
vim.keymap.set('n', '<Leader>q', ':q!<CR>', { silent = true })

-- Some useful quickfix shortcuts for quickfix
vim.keymap.set('n', '<C-n>', '<cmd>cnext<CR>zz')
vim.keymap.set('n', '<C-m>', '<cmd>cprev<CR>zz')
vim.keymap.set('n', '<leader>a', '<cmd>cclose<CR>')

-- Exit on jj and jk
vim.keymap.set('n', 'j', 'gj')
vim.keymap.set('n', 'k', 'gk')

-- Exit on jj and jk
vim.keymap.set('i', 'jj', '<ESC>')
vim.keymap.set('i', 'jk', '<ESC>')

-- Remove search highlight
vim.keymap.set('n', '<Leader>h', ':nohlsearch<CR>')

vim.keymap.set('n', 'n', 'nzzzv', { noremap = true })
vim.keymap.set('n', 'N', 'Nzzzv', { noremap = true })
vim.keymap.set("n", "J", "mzJ`z")
vim.keymap.set("n", "<C-d>", "<C-d>zz")
vim.keymap.set("n", "<C-u>", "<C-u>zz")

-- If I visually select words and paste from clipboard, don't replace my
-- clipboard with the selected word, instead keep my old word in the
-- clipboard
vim.keymap.set("x", "p", "\"_dP")

-- Better split switching
vim.keymap.set('', '<C-j>', '<C-W>j')
vim.keymap.set('', '<C-k>', '<C-W>k')
vim.keymap.set('', '<C-h>', '<C-W>h')
vim.keymap.set('', '<C-l>', '<C-W>l')

-- Visually select lines, and move them up/down
vim.keymap.set('v', 'J', ":m '>+1<CR>gv=gv")
vim.keymap.set('v', 'K', ":m '<-2<CR>gv=gv")

vim.keymap.set('n', '<leader>c', ":bd<cr>")

-- autocommands
-- don't auto comment new line
vim.api.nvim_create_autocmd("BufEnter", { command = [[set formatoptions-=cro]] })

--- Remove all trailing whitespace on save
local TrimWhiteSpaceGrp = vim.api.nvim_create_augroup("TrimWhiteSpaceGrp", { clear = true })
vim.api.nvim_create_autocmd("BufWritePre", {
    command = [[:%s/\s\+$//e]],
    group = TrimWhiteSpaceGrp,
})

-- wrap words "softly" (no carriage return) in mail buffer
vim.api.nvim_create_autocmd("Filetype", {
    pattern = "mail",
    callback = function()
        vim.opt.textwidth = 0
        vim.opt.wrapmargin = 0
        vim.opt.wrap = true
        vim.opt.linebreak = true
        vim.opt.columns = 80
        vim.opt.colorcolumn = "80"
    end,
})

-- Highlight on yank
vim.api.nvim_create_autocmd("TextYankPost", {
    callback = function()
        vim.highlight.on_yank()
    end,
})

-- go to last loc when opening a buffer
vim.api.nvim_create_autocmd("BufReadPost", {
    callback = function()
        local mark = vim.api.nvim_buf_get_mark(0, '"')
        local lcount = vim.api.nvim_buf_line_count(0)
        if mark[1] > 0 and mark[1] <= lcount then
            pcall(vim.api.nvim_win_set_cursor, 0, mark)
        end
    end,
})

vim.api.nvim_create_autocmd("FileType", { pattern = "man", command = [[nnoremap <buffer><silent> q :quit<CR>]] })

-- show cursor line only in active window
local cursorGrp = vim.api.nvim_create_augroup("CursorLine", { clear = true })
vim.api.nvim_create_autocmd({ "InsertLeave", "WinEnter" }, {
    pattern = "*",
    command = "set cursorline",
    group = cursorGrp,
})
vim.api.nvim_create_autocmd(
    { "InsertEnter", "WinLeave" },
    { pattern = "*", command = "set nocursorline", group = cursorGrp }
)

-- Enable spell checking for certain file types
vim.api.nvim_create_autocmd(
    { "BufRead", "BufNewFile" },
    -- { pattern = { "*.txt", "*.md", "*.tex" }, command = [[setlocal spell<cr> setlocal spelllang=en,de<cr>]] }
    {
        pattern = { "*.txt", "*.md", "*.tex" },
        callback = function()
            vim.opt.spell = true
            vim.opt.spelllang = "en,de"
        end,
    }
)

-- Use LspAttach autocommand to only map the following keys
-- after the language server attaches to the current buffer
vim.api.nvim_create_autocmd('LspAttach', {
    callback = function(ev)
        local opts = { buffer = ev.buf }
        vim.keymap.set('n', '<leader>v', "<cmd>vsplit | lua vim.lsp.buf.definition()<CR>", opts)
    end,
})

And the Elixir sample module:

defmodule ColdRiverWeb.PageLive do
  use ColdRiverWeb, :live_view

  def mount(_params, _session, socket) do
    {:ok, socket}
  end

  def handle_event("save", %{"user" => user}, socket) do
    IO.inspect(user)
  end
end

So the issue is that when I enter { to write {:noreply, socket} it jumps to column 0 for some reason.

josevalim commented 1 year ago

So the issue is that when I enter { to write {:noreply, socket} it jumps to column 0 for some reason.

Thanks, I see. I believe this is an issue on neovim/tree-sitter side of things but I will let people familiar with the projects confirm. :)

dsincl12 commented 1 year ago

With some help from @scottming on Discord I created this autocmd that fixes the issue:

vim.api.nvim_create_autocmd("FileType", { pattern = "elixir", command = [[setlocal indentkeys-=0{]] })

jonatanklosko commented 1 year ago

cc @connorlay :)

georgeguimaraes commented 9 months ago

Got the same problem here in Neovim 0.9.4. I'll try to dig deeper

georgeguimaraes commented 9 months ago

Yeah, the filetype detection for Elixir in vim (and neovim) is minimal, so it uses the default indentkeys, which is built for C.

So, when you type {, it uses treesitter to indent the line, and since the line is only a { for now and not yet a full tuple, vim/neovim will indent it back to the first column.

I've created this PR in vim: https://github.com/vim/vim/pull/13771. After merging, I'll also ask the Neovim people to port it over.

georgeguimaraes commented 9 months ago

PRs in vim and neovim merged:

I've tested with Neovim head and this issue is fixed. I'm gonna close this issue now.