From cb3e80b4a78fb3e3d8b423807b61dcfe1b07ca6c Mon Sep 17 00:00:00 2001 From: Jannik Buhr Date: Thu, 4 Apr 2024 12:07:45 +0200 Subject: [PATCH] refactor(modules): refactore plugins for quarto into sensible modules --- README.md | 6 +- lua/plugins/completion.lua | 177 ++++++++++++ lua/plugins/editing.lua | 61 +++- lua/plugins/lsp.lua | 249 ++++++++++++++++ lua/plugins/quarto.lua | 575 +------------------------------------ lua/plugins/treesitter.lua | 85 ++++++ 6 files changed, 579 insertions(+), 574 deletions(-) create mode 100644 lua/plugins/completion.lua create mode 100644 lua/plugins/lsp.lua create mode 100644 lua/plugins/treesitter.lua diff --git a/README.md b/README.md index 1748da0..7e1ce29 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,9 @@ https://youtube.com/playlist?list=PLabWm-zCaD1axcMGvf7wFxJz8FZmyHSJ7 Clone this repo into `~/.config/nvim/` or copy-paste just the parts you like. If you already have your own configuration, check out `lua/plugins/quarto.lua` -for the configuration of all plugins directly relevant to your Quarto experience. +for the configuration of plugins directly relevant to your Quarto experience. +The comments in this file will also point to to other plugins required for +the full functionality. This configuration can make use of a "Nerd Font" for icons and symbols. Download one here: and set it as your terminal font. @@ -48,10 +50,8 @@ rm -r ~/.local/state/nvim ## Screenshots -![image](https://user-images.githubusercontent.com/17450586/210392216-a99815ac-1872-4c48-bf24-5a50df14c6d2.png) ![image](https://user-images.githubusercontent.com/17450586/210392419-3ee2b3e3-e805-4e36-99ab-6922abe3a66b.png) ![image](https://user-images.githubusercontent.com/17450586/210392573-57c0ad1c-5db0-4f2a-9119-608bd2398494.png) -![image](https://user-images.githubusercontent.com/17450586/210392838-1c643a65-e792-4a54-bbdb-3ae959995a79.png) Use the integrated neovim terminal to execute code chunks: diff --git a/lua/plugins/completion.lua b/lua/plugins/completion.lua new file mode 100644 index 0000000..86da564 --- /dev/null +++ b/lua/plugins/completion.lua @@ -0,0 +1,177 @@ +return { + { + 'windwp/nvim-autopairs', + config = function() + require('nvim-autopairs').setup {} + require('nvim-autopairs').remove_rule '`' + end, + }, + + { -- completion + 'hrsh7th/nvim-cmp', + event = 'InsertEnter', + dependencies = { + { 'hrsh7th/cmp-nvim-lsp' }, + { 'hrsh7th/cmp-nvim-lsp-signature-help' }, + { 'hrsh7th/cmp-buffer' }, + { 'hrsh7th/cmp-path' }, + { 'hrsh7th/cmp-calc' }, + { 'hrsh7th/cmp-emoji' }, + { 'saadparwaiz1/cmp_luasnip' }, + { 'f3fora/cmp-spell' }, + { 'ray-x/cmp-treesitter' }, + { 'kdheepak/cmp-latex-symbols' }, + { 'jmbuhr/cmp-pandoc-references' }, + { 'L3MON4D3/LuaSnip' }, + { 'rafamadriz/friendly-snippets' }, + { 'onsails/lspkind-nvim' }, + }, + config = function() + local cmp = require 'cmp' + local luasnip = require 'luasnip' + local lspkind = require 'lspkind' + + local has_words_before = function() + local line, col = unpack(vim.api.nvim_win_get_cursor(0)) + return col ~= 0 and vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1]:sub(col, col):match '%s' == nil + end + + cmp.setup { + snippet = { + expand = function(args) + luasnip.lsp_expand(args.body) + end, + }, + completion = { completeopt = 'menu,menuone,noinsert' }, + mapping = { + [''] = cmp.mapping.scroll_docs(-4), + [''] = cmp.mapping.scroll_docs(4), + + [''] = cmp.mapping(function(fallback) + if luasnip.expand_or_jumpable() then + luasnip.expand_or_jump() + fallback() + end + end, { 'i', 's' }), + [''] = cmp.mapping(function(fallback) + if luasnip.jumpable(-1) then + luasnip.jump(-1) + else + fallback() + end + end, { 'i', 's' }), + [''] = cmp.mapping.abort(), + [''] = cmp.mapping.confirm { + select = true, + }, + [''] = cmp.mapping.confirm { + select = true, + }, + + [''] = cmp.mapping(function(fallback) + if cmp.visible() then + cmp.select_next_item() + elseif has_words_before() then + cmp.complete() + else + fallback() + end + end, { 'i', 's' }), + [''] = cmp.mapping(function(fallback) + if cmp.visible() then + cmp.select_prev_item() + else + fallback() + end + end, { 'i', 's' }), + + [''] = cmp.mapping(function() + if luasnip.expand_or_locally_jumpable() then + luasnip.expand_or_jump() + end + end, { 'i', 's' }), + [''] = cmp.mapping(function() + if luasnip.locally_jumpable(-1) then + luasnip.jump(-1) + end + end, { 'i', 's' }), + }, + autocomplete = false, + + ---@diagnostic disable-next-line: missing-fields + formatting = { + format = lspkind.cmp_format { + mode = 'symbol', + menu = { + otter = '[🦦]', + nvim_lsp = '[LSP]', + luasnip = '[snip]', + buffer = '[buf]', + path = '[path]', + spell = '[spell]', + pandoc_references = '[ref]', + tags = '[tag]', + treesitter = '[TS]', + calc = '[calc]', + latex_symbols = '[tex]', + emoji = '[emoji]', + }, + }, + }, + sources = { + { name = 'otter' }, -- for code chunks in quarto + { name = 'path' }, + { name = 'nvim_lsp' }, + { name = 'nvim_lsp_signature_help' }, + { name = 'luasnip', keyword_length = 3, max_item_count = 3 }, + { name = 'pandoc_references' }, + { name = 'buffer', keyword_length = 5, max_item_count = 3 }, + { name = 'spell' }, + { name = 'treesitter', keyword_length = 5, max_item_count = 3 }, + { name = 'calc' }, + { name = 'latex_symbols' }, + { name = 'emoji' }, + }, + view = { + entries = 'native', + }, + window = { + documentation = { + border = require('misc.style').border, + }, + }, + } + + -- for friendly snippets + require('luasnip.loaders.from_vscode').lazy_load() + -- for custom snippets + require('luasnip.loaders.from_vscode').lazy_load { paths = { vim.fn.stdpath 'config' .. '/snips' } } + -- link quarto and rmarkdown to markdown snippets + luasnip.filetype_extend('quarto', { 'markdown' }) + luasnip.filetype_extend('rmarkdown', { 'markdown' }) + end, + }, + + { -- gh copilot + 'zbirenbaum/copilot.lua', + enabled = false, + config = function() + require('copilot').setup { + suggestion = { + enabled = true, + auto_trigger = true, + debounce = 75, + keymap = { + accept = '', + accept_word = false, + accept_line = false, + next = '', + prev = '', + dismiss = '', + }, + }, + panel = { enabled = false }, + } + end, + }, +} diff --git a/lua/plugins/editing.lua b/lua/plugins/editing.lua index 4a53c44..60a7073 100644 --- a/lua/plugins/editing.lua +++ b/lua/plugins/editing.lua @@ -11,14 +11,6 @@ return { opts = {}, }, - { - 'windwp/nvim-autopairs', - config = function() - require('nvim-autopairs').setup {} - require('nvim-autopairs').remove_rule '`' - end, - }, - -- commenting with e.g. `gcc` or `gcip` -- respects TS, so it works in quarto documents { @@ -28,6 +20,59 @@ return { config = true, }, + { -- Autoformat + 'stevearc/conform.nvim', + enabled = true, + config = function() + require('conform').setup { + notify_on_error = false, + format_on_save = { + timeout_ms = 500, + lsp_fallback = true, + }, + formatters_by_ft = { + lua = { 'mystylua' }, + python = { 'isort', 'black' }, + }, + formatters = { + mystylua = { + command = 'stylua', + args = { '--indent-type', 'Spaces', '--indent-width', '2', '-' }, + }, + }, + } + -- Customize the "injected" formatter + require('conform').formatters.injected = { + -- Set the options field + options = { + -- Set to true to ignore errors + ignore_errors = false, + -- Map of treesitter language to file extension + -- A temporary file name with this extension will be generated during formatting + -- because some formatters care about the filename. + lang_to_ext = { + bash = 'sh', + c_sharp = 'cs', + elixir = 'exs', + javascript = 'js', + julia = 'jl', + latex = 'tex', + markdown = 'md', + python = 'py', + ruby = 'rb', + rust = 'rs', + teal = 'tl', + r = 'r', + typescript = 'ts', + }, + -- Map of treesitter language to formatters to use + -- (defaults to the value from formatters_by_ft) + lang_to_formatters = {}, + }, + } + end, + }, + { -- generate docstrings 'danymat/neogen', cmd = { 'Neogen' }, diff --git a/lua/plugins/lsp.lua b/lua/plugins/lsp.lua new file mode 100644 index 0000000..f0e5509 --- /dev/null +++ b/lua/plugins/lsp.lua @@ -0,0 +1,249 @@ +return { + { + 'neovim/nvim-lspconfig', + dependencies = { + { 'williamboman/mason.nvim' }, + { 'williamboman/mason-lspconfig.nvim' }, + { 'WhoIsSethDaniel/mason-tool-installer.nvim' }, + { -- nice loading notifications + -- PERF: but can slow down startup + 'j-hui/fidget.nvim', + enabled = false, + opts = {}, + }, + { 'folke/neodev.nvim', opts = {}, enabled = true }, + { 'folke/neoconf.nvim', opts = {}, enabled = false }, + }, + config = function() + local lspconfig = require 'lspconfig' + local util = require 'lspconfig.util' + + require('mason').setup() + require('mason-lspconfig').setup { + automatic_installation = true, + } + require('mason-tool-installer').setup { + ensure_installed = { + 'black', + 'stylua', + 'shfmt', + 'isort', + 'tree-sitter-cli', + }, + } + + vim.api.nvim_create_autocmd('LspAttach', { + group = vim.api.nvim_create_augroup('kickstart-lsp-attach', { clear = true }), + callback = function(event) + local telescope = require 'telescope.builtin' + local function map(keys, func, desc) + vim.keymap.set('n', keys, func, { buffer = event.buf, desc = 'LSP: ' .. desc }) + end + + local client = vim.lsp.get_client_by_id(event.data.client_id) + assert(client, 'LSP client not found') + + ---@diagnostic disable-next-line: inject-field + client.server_capabilities.document_formatting = true + + map('gS', telescope.lsp_document_symbols, '[g]o so [S]ymbols') + map('gD', telescope.lsp_type_definitions, '[g]o to type [D]efinition') + map('gd', telescope.lsp_definitions, '[g]o to [d]efinition') + map('K', 'lua vim.lsp.buf.hover()', '[K] hover documentation') + map('gh', 'lua vim.lsp.buf.signature_help()', '[g]o to signature [h]elp') + map('gI', telescope.lsp_implementations, '[g]o to [I]mplementation') + map('gr', telescope.lsp_references, '[g]o to [r]eferences') + map('[d', vim.diagnostic.goto_prev, 'previous [d]iagnostic ') + map(']d', vim.diagnostic.goto_next, 'next [d]iagnostic ') + map('ll', vim.lsp.codelens.run, '[l]ens run') + map('lR', vim.lsp.buf.rename, '[l]sp [R]ename') + map('lf', vim.lsp.buf.format, '[l]sp [f]ormat') + map('lq', vim.diagnostic.setqflist, '[l]sp diagnostic [q]uickfix') + end, + }) + + local lsp_flags = { + allow_incremental_sync = true, + debounce_text_changes = 150, + } + vim.lsp.handlers['textDocument/hover'] = vim.lsp.with(vim.lsp.handlers.hover, { border = require('misc.style').border }) + vim.lsp.handlers['textDocument/signatureHelp'] = vim.lsp.with(vim.lsp.handlers.signature_help, { border = require('misc.style').border }) + + local capabilities = vim.lsp.protocol.make_client_capabilities() + capabilities = vim.tbl_deep_extend('force', capabilities, require('cmp_nvim_lsp').default_capabilities()) + capabilities.textDocument.completion.completionItem.snippetSupport = true + + -- also needs: + -- $home/.config/marksman/config.toml : + -- [core] + -- markdown.file_extensions = ["md", "markdown", "qmd"] + lspconfig.marksman.setup { + capabilities = capabilities, + filetypes = { 'markdown', 'quarto' }, + root_dir = util.root_pattern('.git', '.marksman.toml', '_quarto.yml'), + } + + lspconfig.r_language_server.setup { + capabilities = capabilities, + flags = lsp_flags, + settings = { + r = { + lsp = { + rich_documentation = false, + }, + }, + }, + } + + lspconfig.cssls.setup { + capabilities = capabilities, + flags = lsp_flags, + } + + lspconfig.html.setup { + capabilities = capabilities, + flags = lsp_flags, + } + + lspconfig.emmet_language_server.setup { + capabilities = capabilities, + flags = lsp_flags, + } + + lspconfig.yamlls.setup { + capabilities = capabilities, + flags = lsp_flags, + settings = { + yaml = { + schemaStore = { + enable = true, + url = '', + }, + }, + }, + } + + lspconfig.dotls.setup { + capabilities = capabilities, + flags = lsp_flags, + } + + lspconfig.tsserver.setup { + capabilities = capabilities, + flags = lsp_flags, + filetypes = { 'js', 'javascript', 'typescript', 'ojs' }, + } + + local function get_quarto_resource_path() + local function strsplit(s, delimiter) + local result = {} + for match in (s .. delimiter):gmatch('(.-)' .. delimiter) do + table.insert(result, match) + end + return result + end + + local f = assert(io.popen('quarto --paths', 'r')) + local s = assert(f:read '*a') + f:close() + return strsplit(s, '\n')[2] + end + + local lua_library_files = vim.api.nvim_get_runtime_file('', true) + local lua_plugin_paths = {} + local resource_path = get_quarto_resource_path() + if resource_path == nil then + vim.notify_once 'quarto not found, lua library files not loaded' + else + table.insert(lua_library_files, resource_path .. '/lua-types') + table.insert(lua_plugin_paths, resource_path .. '/lua-plugin/plugin.lua') + end + + lspconfig.lua_ls.setup { + capabilities = capabilities, + flags = lsp_flags, + settings = { + Lua = { + completion = { + callSnippet = 'Replace', + }, + runtime = { + version = 'LuaJIT', + plugin = lua_plugin_paths, + }, + diagnostics = { + globals = { 'vim', 'quarto', 'pandoc', 'io', 'string', 'print', 'require', 'table' }, + disable = { 'trailing-space' }, + }, + workspace = { + library = lua_library_files, + checkThirdParty = false, + }, + telemetry = { + enable = false, + }, + }, + }, + } + + lspconfig.julials.setup { + capabilities = capabilities, + flags = lsp_flags, + } + + lspconfig.bashls.setup { + capabilities = capabilities, + flags = lsp_flags, + filetypes = { 'sh', 'bash' }, + } + + -- Add additional languages here. + -- See `:h lspconfig-all` for the configuration. + -- Like e.g. Haskell: + -- lspconfig.hls.setup { + -- on_attach = on_attach, + -- capabilities = capabilities, + -- flags = lsp_flags + -- } + + -- lspconfig.rust_analyzer.setup{ + -- on_attach = on_attach, + -- capabilities = capabilities, + -- settings = { + -- ['rust-analyzer'] = { + -- diagnostics = { + -- enable = false; + -- } + -- } + -- } + -- } + + -- See https://github.com/neovim/neovim/issues/23291 + -- disable lsp watcher. + -- Too lags on linux for python projects + -- because pyright and nvim both create too many watchers otherwise + if capabilities.workspace == nil then + capabilities.workspace = {} + capabilities.workspace.didChangeWatchedFiles = {} + end + capabilities.workspace.didChangeWatchedFiles.dynamicRegistration = false + + lspconfig.pyright.setup { + capabilities = capabilities, + flags = lsp_flags, + settings = { + python = { + analysis = { + autoSearchPaths = true, + useLibraryCodeForTypes = true, + diagnosticMode = 'workspace', + }, + }, + }, + root_dir = function(fname) + return util.root_pattern('.git', 'setup.py', 'setup.cfg', 'pyproject.toml', 'requirements.txt')(fname) or util.path.dirname(fname) + end, + } + end, + }, +} diff --git a/lua/plugins/quarto.lua b/lua/plugins/quarto.lua index e09d3e3..cb5f963 100644 --- a/lua/plugins/quarto.lua +++ b/lua/plugins/quarto.lua @@ -1,6 +1,6 @@ return { - { + { -- requires plugins in treesitter.lua and lsp.lua 'quarto-dev/quarto-nvim', ft = { 'quarto' }, dev = false, @@ -15,10 +15,16 @@ return { }, dependencies = { { + -- for language features in code cells + -- specifically needs its source added for nvim-cmp in completion.lua 'jmbuhr/otter.nvim', dev = false, dependencies = { - { 'neovim/nvim-lspconfig' }, + { + 'neovim/nvim-lspconfig', + 'nvim-treesitter/nvim-treesitter', + 'hrsh7th/nvim-cmp', + }, }, opts = { lsp = { @@ -35,563 +41,8 @@ return { }, }, - { - 'nvim-treesitter/nvim-treesitter', - dependencies = { - { 'nvim-treesitter/nvim-treesitter-textobjects' }, - }, - run = ':TSUpdate', - config = function() - ---@diagnostic disable-next-line: missing-fields - require('nvim-treesitter.configs').setup { - auto_install = true, - ensure_installed = { - 'r', - 'python', - 'markdown', - 'markdown_inline', - 'julia', - 'bash', - 'yaml', - 'lua', - 'vim', - 'query', - 'vimdoc', - 'latex', -- requires tree-sitter-cli (installed automatically via Mason) - 'html', - 'css', - 'dot', - 'javascript', - 'mermaid', - 'norg', - 'typescript', - }, - highlight = { - enable = true, - additional_vim_regex_highlighting = false, - }, - indent = { - enable = true, - }, - incremental_selection = { - enable = true, - keymaps = { - init_selection = 'gnn', - node_incremental = 'grn', - scope_incremental = 'grc', - node_decremental = 'grm', - }, - }, - textobjects = { - select = { - enable = true, - lookahead = true, - keymaps = { - -- You can use the capture groups defined in textobjects.scm - ['af'] = '@function.outer', - ['if'] = '@function.inner', - ['ac'] = '@class.outer', - ['ic'] = '@class.inner', - }, - }, - move = { - enable = true, - set_jumps = true, -- whether to set jumps in the jumplist - goto_next_start = { - [']m'] = '@function.outer', - [']]'] = '@class.inner', - }, - goto_next_end = { - [']M'] = '@function.outer', - [']['] = '@class.outer', - }, - goto_previous_start = { - ['[m'] = '@function.outer', - ['[['] = '@class.inner', - }, - goto_previous_end = { - ['[M'] = '@function.outer', - ['[]'] = '@class.outer', - }, - }, - }, - } - end, - }, - - { - 'neovim/nvim-lspconfig', - dependencies = { - { 'williamboman/mason.nvim' }, - { 'williamboman/mason-lspconfig.nvim' }, - { 'WhoIsSethDaniel/mason-tool-installer.nvim' }, - { -- nice loading notifications - -- PERF: but can slow down startup - 'j-hui/fidget.nvim', - enabled = false, - opts = {}, - }, - { 'folke/neodev.nvim', opts = {}, enabled = true }, - { 'folke/neoconf.nvim', opts = {}, enabled = false }, - }, - config = function() - local lspconfig = require 'lspconfig' - local util = require 'lspconfig.util' - - require('mason').setup() - require('mason-lspconfig').setup { - automatic_installation = true, - } - require('mason-tool-installer').setup { - ensure_installed = { - 'black', - 'stylua', - 'shfmt', - 'isort', - 'tree-sitter-cli', - }, - } - - vim.api.nvim_create_autocmd('LspAttach', { - group = vim.api.nvim_create_augroup('kickstart-lsp-attach', { clear = true }), - callback = function(event) - local telescope = require 'telescope.builtin' - local function map(keys, func, desc) - vim.keymap.set('n', keys, func, { buffer = event.buf, desc = 'LSP: ' .. desc }) - end - - local client = vim.lsp.get_client_by_id(event.data.client_id) - assert(client, 'LSP client not found') - - ---@diagnostic disable-next-line: inject-field - client.server_capabilities.document_formatting = true - - map('gS', telescope.lsp_document_symbols, '[g]o so [S]ymbols') - map('gD', telescope.lsp_type_definitions, '[g]o to type [D]efinition') - map('gd', telescope.lsp_definitions, '[g]o to [d]efinition') - map('K', 'lua vim.lsp.buf.hover()', '[K] hover documentation') - map('gh', 'lua vim.lsp.buf.signature_help()', '[g]o to signature [h]elp') - map('gI', telescope.lsp_implementations, '[g]o to [I]mplementation') - map('gr', telescope.lsp_references, '[g]o to [r]eferences') - map('[d', vim.diagnostic.goto_prev, 'previous [d]iagnostic ') - map(']d', vim.diagnostic.goto_next, 'next [d]iagnostic ') - map('ll', vim.lsp.codelens.run, '[l]ens run') - map('lR', vim.lsp.buf.rename, '[l]sp [R]ename') - map('lf', vim.lsp.buf.format, '[l]sp [f]ormat') - map('lq', vim.diagnostic.setqflist, '[l]sp diagnostic [q]uickfix') - end, - }) - - local lsp_flags = { - allow_incremental_sync = true, - debounce_text_changes = 150, - } - vim.lsp.handlers['textDocument/hover'] = vim.lsp.with(vim.lsp.handlers.hover, { border = require('misc.style').border }) - vim.lsp.handlers['textDocument/signatureHelp'] = vim.lsp.with(vim.lsp.handlers.signature_help, { border = require('misc.style').border }) - - local capabilities = vim.lsp.protocol.make_client_capabilities() - capabilities = vim.tbl_deep_extend('force', capabilities, require('cmp_nvim_lsp').default_capabilities()) - capabilities.textDocument.completion.completionItem.snippetSupport = true - - -- also needs: - -- $home/.config/marksman/config.toml : - -- [core] - -- markdown.file_extensions = ["md", "markdown", "qmd"] - lspconfig.marksman.setup { - capabilities = capabilities, - filetypes = { 'markdown', 'quarto' }, - root_dir = util.root_pattern('.git', '.marksman.toml', '_quarto.yml'), - } - - lspconfig.r_language_server.setup { - capabilities = capabilities, - flags = lsp_flags, - settings = { - r = { - lsp = { - rich_documentation = false, - }, - }, - }, - } - - lspconfig.cssls.setup { - capabilities = capabilities, - flags = lsp_flags, - } - - lspconfig.html.setup { - capabilities = capabilities, - flags = lsp_flags, - } - - lspconfig.emmet_language_server.setup { - capabilities = capabilities, - flags = lsp_flags, - } - - lspconfig.yamlls.setup { - capabilities = capabilities, - flags = lsp_flags, - settings = { - yaml = { - schemaStore = { - enable = true, - url = '', - }, - }, - }, - } - - lspconfig.dotls.setup { - capabilities = capabilities, - flags = lsp_flags, - } - - lspconfig.tsserver.setup { - capabilities = capabilities, - flags = lsp_flags, - filetypes = { 'js', 'javascript', 'typescript', 'ojs' }, - } - - local function get_quarto_resource_path() - local function strsplit(s, delimiter) - local result = {} - for match in (s .. delimiter):gmatch('(.-)' .. delimiter) do - table.insert(result, match) - end - return result - end - - local f = assert(io.popen('quarto --paths', 'r')) - local s = assert(f:read '*a') - f:close() - return strsplit(s, '\n')[2] - end - - local lua_library_files = vim.api.nvim_get_runtime_file('', true) - local lua_plugin_paths = {} - local resource_path = get_quarto_resource_path() - if resource_path == nil then - vim.notify_once 'quarto not found, lua library files not loaded' - else - table.insert(lua_library_files, resource_path .. '/lua-types') - table.insert(lua_plugin_paths, resource_path .. '/lua-plugin/plugin.lua') - end - - lspconfig.lua_ls.setup { - capabilities = capabilities, - flags = lsp_flags, - settings = { - Lua = { - completion = { - callSnippet = 'Replace', - }, - runtime = { - version = 'LuaJIT', - plugin = lua_plugin_paths, - }, - diagnostics = { - globals = { 'vim', 'quarto', 'pandoc', 'io', 'string', 'print', 'require', 'table' }, - disable = { 'trailing-space' }, - }, - workspace = { - library = lua_library_files, - checkThirdParty = false, - }, - telemetry = { - enable = false, - }, - }, - }, - } - - lspconfig.julials.setup { - capabilities = capabilities, - flags = lsp_flags, - } - - lspconfig.bashls.setup { - capabilities = capabilities, - flags = lsp_flags, - filetypes = { 'sh', 'bash' }, - } - - -- Add additional languages here. - -- See `:h lspconfig-all` for the configuration. - -- Like e.g. Haskell: - -- lspconfig.hls.setup { - -- on_attach = on_attach, - -- capabilities = capabilities, - -- flags = lsp_flags - -- } - - -- lspconfig.rust_analyzer.setup{ - -- on_attach = on_attach, - -- capabilities = capabilities, - -- settings = { - -- ['rust-analyzer'] = { - -- diagnostics = { - -- enable = false; - -- } - -- } - -- } - -- } - - -- See https://github.com/neovim/neovim/issues/23291 - -- disable lsp watcher. - -- Too lags on linux for python projects - -- because pyright and nvim both create too many watchers otherwise - if capabilities.workspace == nil then - capabilities.workspace = {} - capabilities.workspace.didChangeWatchedFiles = {} - end - capabilities.workspace.didChangeWatchedFiles.dynamicRegistration = false - - lspconfig.pyright.setup { - capabilities = capabilities, - flags = lsp_flags, - settings = { - python = { - analysis = { - autoSearchPaths = true, - useLibraryCodeForTypes = true, - diagnosticMode = 'workspace', - }, - }, - }, - root_dir = function(fname) - return util.root_pattern('.git', 'setup.py', 'setup.cfg', 'pyproject.toml', 'requirements.txt')(fname) or util.path.dirname(fname) - end, - } - end, - }, - - { -- Autoformat - 'stevearc/conform.nvim', - enabled = true, - config = function() - require('conform').setup { - notify_on_error = false, - format_on_save = { - timeout_ms = 500, - lsp_fallback = true, - }, - formatters_by_ft = { - lua = { 'mystylua' }, - python = { 'isort', 'black' }, - }, - formatters = { - mystylua = { - command = 'stylua', - args = { '--indent-type', 'Spaces', '--indent-width', '2', '-' }, - }, - }, - } - -- Customize the "injected" formatter - require('conform').formatters.injected = { - -- Set the options field - options = { - -- Set to true to ignore errors - ignore_errors = false, - -- Map of treesitter language to file extension - -- A temporary file name with this extension will be generated during formatting - -- because some formatters care about the filename. - lang_to_ext = { - bash = 'sh', - c_sharp = 'cs', - elixir = 'exs', - javascript = 'js', - julia = 'jl', - latex = 'tex', - markdown = 'md', - python = 'py', - ruby = 'rb', - rust = 'rs', - teal = 'tl', - r = 'r', - typescript = 'ts', - }, - -- Map of treesitter language to formatters to use - -- (defaults to the value from formatters_by_ft) - lang_to_formatters = {}, - }, - } - end, - }, - - -- completion - { - 'hrsh7th/nvim-cmp', - event = 'InsertEnter', - dependencies = { - { 'hrsh7th/cmp-nvim-lsp' }, - { 'hrsh7th/cmp-nvim-lsp-signature-help' }, - { 'hrsh7th/cmp-buffer' }, - { 'hrsh7th/cmp-path' }, - { 'hrsh7th/cmp-calc' }, - { 'hrsh7th/cmp-emoji' }, - { 'saadparwaiz1/cmp_luasnip' }, - { 'f3fora/cmp-spell' }, - { 'ray-x/cmp-treesitter' }, - { 'kdheepak/cmp-latex-symbols' }, - { 'jmbuhr/cmp-pandoc-references' }, - { 'L3MON4D3/LuaSnip' }, - { 'rafamadriz/friendly-snippets' }, - { 'onsails/lspkind-nvim' }, - }, - config = function() - local cmp = require 'cmp' - local luasnip = require 'luasnip' - local lspkind = require 'lspkind' - - local has_words_before = function() - local line, col = unpack(vim.api.nvim_win_get_cursor(0)) - return col ~= 0 and vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1]:sub(col, col):match '%s' == nil - end - - cmp.setup { - snippet = { - expand = function(args) - luasnip.lsp_expand(args.body) - end, - }, - completion = { completeopt = 'menu,menuone,noinsert' }, - mapping = { - [''] = cmp.mapping.scroll_docs(-4), - [''] = cmp.mapping.scroll_docs(4), - - [''] = cmp.mapping(function(fallback) - if luasnip.expand_or_jumpable() then - luasnip.expand_or_jump() - fallback() - end - end, { 'i', 's' }), - [''] = cmp.mapping(function(fallback) - if luasnip.jumpable(-1) then - luasnip.jump(-1) - else - fallback() - end - end, { 'i', 's' }), - [''] = cmp.mapping.abort(), - [''] = cmp.mapping.confirm { - select = true, - }, - [''] = cmp.mapping.confirm { - select = true, - }, - - [''] = cmp.mapping(function(fallback) - if cmp.visible() then - cmp.select_next_item() - elseif has_words_before() then - cmp.complete() - else - fallback() - end - end, { 'i', 's' }), - [''] = cmp.mapping(function(fallback) - if cmp.visible() then - cmp.select_prev_item() - else - fallback() - end - end, { 'i', 's' }), - - [''] = cmp.mapping(function() - if luasnip.expand_or_locally_jumpable() then - luasnip.expand_or_jump() - end - end, { 'i', 's' }), - [''] = cmp.mapping(function() - if luasnip.locally_jumpable(-1) then - luasnip.jump(-1) - end - end, { 'i', 's' }), - }, - autocomplete = false, - - ---@diagnostic disable-next-line: missing-fields - formatting = { - format = lspkind.cmp_format { - mode = 'symbol', - menu = { - otter = '[🦦]', - nvim_lsp = '[LSP]', - luasnip = '[snip]', - buffer = '[buf]', - path = '[path]', - spell = '[spell]', - pandoc_references = '[ref]', - tags = '[tag]', - treesitter = '[TS]', - calc = '[calc]', - latex_symbols = '[tex]', - emoji = '[emoji]', - }, - }, - }, - sources = { - { name = 'otter' }, -- for code chunks in quarto - { name = 'path' }, - { name = 'nvim_lsp' }, - { name = 'nvim_lsp_signature_help' }, - { name = 'luasnip', keyword_length = 3, max_item_count = 3 }, - { name = 'pandoc_references' }, - { name = 'buffer', keyword_length = 5, max_item_count = 3 }, - { name = 'spell' }, - { name = 'treesitter', keyword_length = 5, max_item_count = 3 }, - { name = 'calc' }, - { name = 'latex_symbols' }, - { name = 'emoji' }, - }, - view = { - entries = 'native', - }, - window = { - documentation = { - border = require('misc.style').border, - }, - }, - } - - -- for friendly snippets - require('luasnip.loaders.from_vscode').lazy_load() - -- for custom snippets - require('luasnip.loaders.from_vscode').lazy_load { paths = { vim.fn.stdpath 'config' .. '/snips' } } - -- link quarto and rmarkdown to markdown snippets - luasnip.filetype_extend('quarto', { 'markdown' }) - luasnip.filetype_extend('rmarkdown', { 'markdown' }) - end, - }, - - { -- gh copilot - 'zbirenbaum/copilot.lua', - enabled = false, - config = function() - require('copilot').setup { - suggestion = { - enabled = true, - auto_trigger = true, - debounce = 75, - keymap = { - accept = '', - accept_word = false, - accept_line = false, - next = '', - prev = '', - dismiss = '', - }, - }, - panel = { enabled = false }, - } - end, - }, - - -- send code from python/r/qmd documets to a terminal or REPL - -- like ipython, R, bash - { + { -- send code from python/r/qmd documets to a terminal or REPL + -- like ipython, R, bash 'jpalardy/vim-slime', init = function() vim.b['quarto_is_python_chunk'] = false @@ -631,8 +82,7 @@ return { end, }, - -- paste an image from the clipboard or drag-and-drop - { + { -- paste an image from the clipboard or drag-and-drop 'HakonHarnes/img-clip.nvim', event = 'BufEnter', ft = { 'markdown', 'quarto', 'latex' }, @@ -660,8 +110,7 @@ return { end, }, - -- preview equations - { + { -- preview equations 'jbyuki/nabla.nvim', keys = { { 'qm', ':lua require"nabla".toggle_virt()', desc = 'toggle [m]ath equations' }, diff --git a/lua/plugins/treesitter.lua b/lua/plugins/treesitter.lua new file mode 100644 index 0000000..82f6f58 --- /dev/null +++ b/lua/plugins/treesitter.lua @@ -0,0 +1,85 @@ +return { + { + 'nvim-treesitter/nvim-treesitter', + dependencies = { + { 'nvim-treesitter/nvim-treesitter-textobjects' }, + }, + run = ':TSUpdate', + config = function() + ---@diagnostic disable-next-line: missing-fields + require('nvim-treesitter.configs').setup { + auto_install = true, + ensure_installed = { + 'r', + 'python', + 'markdown', + 'markdown_inline', + 'julia', + 'bash', + 'yaml', + 'lua', + 'vim', + 'query', + 'vimdoc', + 'latex', -- requires tree-sitter-cli (installed automatically via Mason) + 'html', + 'css', + 'dot', + 'javascript', + 'mermaid', + 'norg', + 'typescript', + }, + highlight = { + enable = true, + additional_vim_regex_highlighting = false, + }, + indent = { + enable = true, + }, + incremental_selection = { + enable = true, + keymaps = { + init_selection = 'gnn', + node_incremental = 'grn', + scope_incremental = 'grc', + node_decremental = 'grm', + }, + }, + textobjects = { + select = { + enable = true, + lookahead = true, + keymaps = { + -- You can use the capture groups defined in textobjects.scm + ['af'] = '@function.outer', + ['if'] = '@function.inner', + ['ac'] = '@class.outer', + ['ic'] = '@class.inner', + }, + }, + move = { + enable = true, + set_jumps = true, -- whether to set jumps in the jumplist + goto_next_start = { + [']m'] = '@function.outer', + [']]'] = '@class.inner', + }, + goto_next_end = { + [']M'] = '@function.outer', + [']['] = '@class.outer', + }, + goto_previous_start = { + ['[m'] = '@function.outer', + ['[['] = '@class.inner', + }, + goto_previous_end = { + ['[M'] = '@function.outer', + ['[]'] = '@class.outer', + }, + }, + }, + } + end, + }, +}