diff --git a/README.md b/README.md index a2f4d97..5335989 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,5 @@ Then reload ``.tmux.conf`` (``bind + shift + I`` in tmux) and the plugins specif This repository uses HackGen35 Console font. Download it via homebrew: ``` -$ brew tap homebrew/cask-fonts $ brew install font-hackgen font-hackgen-nerd ``` diff --git a/alacritty/alacritty.toml b/alacritty/alacritty.toml index 23cf944..2a1a362 100644 --- a/alacritty/alacritty.toml +++ b/alacritty/alacritty.toml @@ -2,7 +2,7 @@ size = 16.0 [font.normal] -family = "HackGen35 Console" +family = "HackGen35 Console NF" style = "Regular" [window] diff --git a/envrc.sample b/envrc.sample index 4ebbac5..f0f6ae7 100644 --- a/envrc.sample +++ b/envrc.sample @@ -14,4 +14,6 @@ if [ -d "$PWD/.venv/bin" ]; then source "$PWD/.venv/bin/activate" fi -dotenv +if [ -e "$PWD/.env" ]; then + dotenv +fi diff --git a/nvim/init.lua b/nvim/init.lua index b758a83..119e9cf 100644 --- a/nvim/init.lua +++ b/nvim/init.lua @@ -261,3 +261,4 @@ end vim.opt.rtp:prepend(lazypath) require("lazy").setup("plugins") +require("spinner"):init() diff --git a/nvim/lua/plugins.lua b/nvim/lua/plugins.lua index 5c32bcd..b579406 100644 --- a/nvim/lua/plugins.lua +++ b/nvim/lua/plugins.lua @@ -4,15 +4,6 @@ return { lazy = false, -- make sure we load this during startup if it is your main colorscheme priority = 1000, -- make sure to load this before all the other start plugins config = function() - require("tokyonight").setup({ - style = "storm", - transparent = true, - styles = { - sidebars = "transparent", - floats = "transparent", - }, - }) - -- load the colorscheme here vim.cmd([[colorscheme tokyonight]]) end, @@ -31,12 +22,14 @@ return { { "nvim-lua/plenary.nvim" }, -- A plugin for fancy fonts. - { "nvim-tree/nvim-web-devicons", lazy = true }, + { "nvim-tree/nvim-web-devicons", default = true }, -- Language Server config. { "neovim/nvim-lspconfig", - dependencies = { "aerial.nvim", "coq_nvim" }, + dependencies = { + "aerial.nvim", + }, config = function() local lspconfig = require("lspconfig") @@ -65,34 +58,25 @@ return { }, } - -- Update opts if coq is available. - local ok, coq = pcall(require, "coq") + -- Update opts if nvim-cmp is available. + local ok, nvim_lsp = pcall(require, "cmp_nvim_lsp") if not ok then - print("LspSetup: coq not found") + print("LspSetup: nvim-cmp not found") else - print("LspSetup: coq found. use its functionality") - opts = coq.lsp_ensure_capabilities(opts) + opts["capabilities"] = nvim_lsp.default_capabilities() end -- Setup LSP servers. -- if vim.fn.executable("terraform-ls") ~= 0 then - print("LspSetup: setting up for terraform-ls") lspconfig.terraformls.setup(opts) end - if vim.fn.executable("rust-analyzer") ~= 0 then - print("LspSetup: setting up for rust-analyzer") - lspconfig.rust_analyzer.setup(opts) - end - if vim.fn.executable("solargraph") ~= 0 then - print("LspSetup: setting up for solargraph") lspconfig.solargraph.setup(opts) end if vim.fn.executable("pyright") ~= 0 then - print("LspSetup: setting up for pyright") local pythonPath = vim.fn.system("which python") opts["python"] = { pythonPath = pythonPath, @@ -101,17 +85,18 @@ return { end if vim.fn.executable("gopls") ~= 0 then - print("LspSetup: setting up for gopls") lspconfig.gopls.setup(opts) end if vim.fn.executable("tsserver") ~= 0 then - print("LspSetup: setting up for tsserver") lspconfig.ts_ls.setup(opts) end + if vim.fn.executable("rust-analyzer") ~= 0 then + lspconfig.rust_analyzer.setup(opts) + end + if vim.fn.executable("lua-language-server") ~= 0 then - print("LspSetup: setting up for lua-language-server") opts["settings"] = { Lua = { runtime = { @@ -139,24 +124,115 @@ return { end, }, - -- Completion plugin. + ---- noice.nvim { - "ms-jpq/coq_nvim", - branch = "coq", - init = function() - -- coq settings. This has to precede require('coq'). - vim.g.coq_settings = { - ["auto_start"] = true, - ["keymap.manual_complete"] = "", - ["xdg"] = true, - } - end, + "folke/noice.nvim", + event = "VeryLazy", + opts = { + lsp = { + -- override markdown rendering so that **cmp** and other plugins use **Treesitter** + override = { + ["vim.lsp.util.convert_input_to_markdown_lines"] = true, + ["vim.lsp.util.stylize_markdown"] = true, + ["cmp.entry.get_documentation"] = true, -- requires hrsh7th/nvim-cmp + }, + }, + }, + dependencies = { + -- if you lazy-load any plugin below, make sure to add proper `module="..."` entries + "MunifTanjim/nui.nvim", + -- OPTIONAL: + -- `nvim-notify` is only needed, if you want to use the notification view. + -- If not available, we use `mini` as the fallback + --"rcarriga/nvim-notify", + }, }, - -- Bundled data for coq_nvim. { - "ms-jpq/coq.artifacts", - branch = "artifacts", + "hrsh7th/nvim-cmp", + event = "VeryLazy", + dependencies = { + "hrsh7th/cmp-nvim-lsp", + "hrsh7th/cmp-buffer", + "hrsh7th/cmp-path", + "hrsh7th/cmp-cmdline", + "hrsh7th/cmp-calc", + "hrsh7th/cmp-emoji", + }, + config = function() + local has_words_before = function() + unpack = unpack or table.unpack + 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 + + local cmp = require("cmp") + + cmp.setup({ + mapping = cmp.mapping.preset.insert({ + [""] = cmp.mapping.scroll_docs(-4), + [""] = cmp.mapping.scroll_docs(4), + [""] = cmp.mapping.complete(), + [""] = cmp.mapping.abort(), + [""] = cmp.mapping.confirm({ select = true }), + [""] = function(fallback) + if not cmp.select_next_item() then + if vim.bo.buftype ~= "prompt" and has_words_before() then + cmp.complete() + else + fallback() + end + end + end, + [""] = function(fallback) + if not cmp.select_prev_item() then + if vim.bo.buftype ~= "prompt" and has_words_before() then + cmp.complete() + else + fallback() + end + end + end, + }), + sources = cmp.config.sources( -- sources + { + { name = "nvim_lsp" }, + }, + { + { name = "buffer", keyword_length = 3 }, + }, + { + { name = "emoji" }, + }, + { + { name = "calc" }, + }, + { + { name = "path" }, + }, + { + { name = "codecompanion" }, + } + ), + }) + cmp.setup.cmdline(":", { + mapping = cmp.mapping.preset.cmdline(), + sources = cmp.config.sources( -- sources + { + { name = "path" }, + }, + { + { name = "cmdline" }, + } + ), + }) + cmp.setup.cmdline({ "/", "?" }, { + mapping = cmp.mapping.preset.cmdline(), + sources = { + { name = "buffer" }, + }, + }) + end, }, -- Outline viewer. @@ -184,24 +260,119 @@ return { end, }, - -- Copilot chat. { - "CopilotC-Nvim/CopilotChat.nvim", - branch = "main", + "MeanderingProgrammer/render-markdown.nvim", + dependencies = { "nvim-treesitter/nvim-treesitter", "echasnovski/mini.icons" }, + ft = { "codecompanion" }, + opts = { + file_types = { "codecompanion" }, + }, + }, + + { + "echasnovski/mini.diff", + config = function() + local diff = require("mini.diff") + diff.setup({ + -- Disabled by default + source = diff.gen_source.none(), + }) + end, + }, + + { + "Davidyz/VectorCode", + version = "*", + dependencies = { "nvim-lua/plenary.nvim" }, + }, + + { + "ravitemer/mcphub.nvim", + dependencies = { + "nvim-lua/plenary.nvim", + }, + build = "bundled_build.lua", -- Bundles `mcp-hub` binary along with the neovim plugin + config = function() + require("mcphub").setup({ + use_bundled_binary = true, -- Use local `mcp-hub` binary + }) + end, + }, + + { + "olimorris/codecompanion.nvim", dependencies = { - { "github/copilot.vim" }, -- or github/copilot.vim - { "nvim-lua/plenary.nvim" }, -- for curl, log wrapper + "echasnovski/mini.diff", + "ravitemer/mcphub.nvim", + "banjo/contextfiles.nvim", + "nvim-lua/plenary.nvim", + "nvim-treesitter/nvim-treesitter", + }, + keys = { + { "aa", ":CodeCompanionChat toggle", mode = { "n", "v" }, noremap = true, silent = true }, + { "ae", ":CodeCompanionActions", mode = { "n", "v" }, noremap = true, silent = true }, + { "af", ":CodeCompanion", mode = { "n", "v" }, noremap = true, silent = true }, }, - build = "make tiktoken", opts = { - debug = true, - window = { - layout = "float", + opts = { + language = "Japanese", + }, + adapters = { + copilot = function() + return require("codecompanion.adapters").extend("copilot", { + schema = { model = { default = "claude-sonnet-4" } }, + }) + end, + }, + display = { + diff = { + provider = "mini_diff", + }, + chat = { + auto_scroll = false, + show_header_separator = true, + -- show_settings = true, + window = { + width = 0.35, + position = "right", + }, + }, + }, + strategies = { + chat = { + adapter = "copilot", + roles = { + llm = function(adapter) + return " CodeCompanion (" .. adapter.formatted_name .. ")" + end, + user = " Me", + }, + }, + }, + extensions = { + vectorcode = { + opts = { + add_tool = true, + }, + }, + contextfiles = { + opts = { + slash_command = { + enabled = true, + name = "cursorrules", + }, + }, + }, + mcphub = { + callback = "mcphub.extensions.codecompanion", + opts = { + show_result_in_chat = true, -- Show mcp tool results in chat + make_vars = true, -- Convert resources to #variables + make_slash_commands = true, -- Add prompts as /slash commands + }, + }, }, }, - init = function() - vim.keymap.set("n", "cc", ":CopilotChatToggle", { silent = true }) - end, }, -- A fuzzy finder. @@ -410,182 +581,10 @@ return { -- from LazyVim -- https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/plugins/treesitter.lua - -- Treesitter is a new parser generator tool that we can - -- use in Neovim to power faster and more accurate - -- syntax highlighting. - { - "nvim-treesitter/nvim-treesitter", - version = false, -- last release is way too old and doesn't work on Windows - build = ":TSUpdate", - event = { "BufReadPost", "BufNewFile", "BufWritePre" }, - init = function(plugin) - -- PERF: add nvim-treesitter queries to the rtp and it's custom query predicates early - -- This is needed because a bunch of plugins no longer `require("nvim-treesitter")`, which - -- no longer trigger the **nvim-treesitter** module to be loaded in time. - -- Luckily, the only things that those plugins need are the custom queries, which we make available - -- during startup. - require("lazy.core.loader").add_to_rtp(plugin) - require("nvim-treesitter.query_predicates") - end, - dependencies = { - { - "nvim-treesitter/nvim-treesitter-textobjects", - config = function() - -- When in diff mode, we want to use the default - -- vim text objects c & C instead of the treesitter ones. - local move = require("nvim-treesitter.textobjects.move") ---@type table - local configs = require("nvim-treesitter.configs") - for name, fn in pairs(move) do - if name:find("goto") == 1 then - move[name] = function(q, ...) - if vim.wo.diff then - local config = configs.get_module("textobjects.move")[name] ---@type table - for key, query in pairs(config or {}) do - if q == query and key:find("[%]%[][cC]") then - vim.cmd("normal! " .. key) - return - end - end - end - return fn(q, ...) - end - end - end - end, - }, - }, - cmd = { "TSUpdateSync", "TSUpdate", "TSInstall" }, - keys = { - { "", desc = "Increment selection" }, - { "", desc = "Decrement selection", mode = "x" }, - }, - ---@type TSConfig - ---@diagnostic disable-next-line: missing-fields - opts = { - highlight = { enable = true }, - indent = { enable = true }, - ensure_installed = { - "bash", - "c", - "diff", - "html", - "javascript", - "jsdoc", - "json", - "jsonc", - "lua", - "luadoc", - "luap", - "markdown", - "markdown_inline", - "python", - "query", - "regex", - "rust", - "toml", - "tsx", - "typescript", - "vim", - "vimdoc", - "xml", - "yaml", - }, - incremental_selection = { - enable = true, - keymaps = { - init_selection = "", - node_incremental = "", - scope_incremental = false, - node_decremental = "", - }, - }, - textobjects = { - move = { - enable = true, - goto_next_start = { ["]f"] = "@function.outer", ["]c"] = "@class.outer" }, - goto_next_end = { ["]F"] = "@function.outer", ["]C"] = "@class.outer" }, - goto_previous_start = { ["[f"] = "@function.outer", ["[c"] = "@class.outer" }, - goto_previous_end = { ["[F"] = "@function.outer", ["[C"] = "@class.outer" }, - }, - }, - }, - ---@param opts TSConfig - config = function(_, opts) - if type(opts.ensure_installed) == "table" then - ---@type table - local added = {} - opts.ensure_installed = vim.tbl_filter(function(lang) - if added[lang] then - return false - end - added[lang] = true - return true - end, opts.ensure_installed) - end - require("nvim-treesitter.configs").setup(opts) - end, - }, -- Automatically add closing tags for HTML and JSX { "windwp/nvim-ts-autotag", event = { "BufReadPost", "BufNewFile", "BufWritePre" }, opts = {}, }, - -- Jupyter Notebook (ipynb -> py) - { - "GCBallesteros/jupytext.nvim", - config = true, - lazy = false, - }, - { - "benlubas/molten-nvim", - -- version = "^1.0.0", -- use version <2.0.0 to avoid breaking changes - dependencies = { - { - "3rd/image.nvim", - opts = { - backend = "kitty", -- whatever backend you would like to use - max_width = 500, - max_height = 500, - max_height_window_percentage = math.huge, - max_width_window_percentage = math.huge, - window_overlap_clear_enabled = true, -- toggles images when windows are overlapped - window_overlap_clear_ft_ignore = { "cmp_menu", "cmp_docs", "" }, - }, - }, - }, - build = ":UpdateRemotePlugins", - init = function() - vim.g.molten_image_provider = "image.nvim" - end, - }, - { - "GCBallesteros/NotebookNavigator.nvim", - keys = { - { - "]h", - function() - require("notebook-navigator").move_cell("d") - end, - }, - { - "[h", - function() - require("notebook-navigator").move_cell("u") - end, - }, - { "rc", "lua require('notebook-navigator').run_cell()" }, - { "rm", "lua require('notebook-navigator').run_and_move()" }, - }, - dependencies = { - "echasnovski/mini.comment", - "anuvyklack/hydra.nvim", - "benlubas/molten-nvim", - }, - event = "VeryLazy", - config = function() - local nn = require("notebook-navigator") - nn.setup({ activate_hydra_keys = "h", repl_provider = "molten" }) - end, - }, } diff --git a/nvim/lua/spinner.lua b/nvim/lua/spinner.lua new file mode 100644 index 0000000..253c42f --- /dev/null +++ b/nvim/lua/spinner.lua @@ -0,0 +1,110 @@ +local M = { + processing = false, + spinner_index = 1, + namespace_id = nil, + timer = nil, + spinner_symbols = { + "⠋", + "⠙", + "⠹", + "⠸", + "⠼", + "⠴", + "⠦", + "⠧", + "⠇", + "⠏", + }, + filetype = "codecompanion", +} + +function M:get_buf(filetype) + for _, buf in ipairs(vim.api.nvim_list_bufs()) do + if vim.api.nvim_buf_is_valid(buf) and vim.bo[buf].filetype == filetype then + return buf + end + end + return nil +end + +function M:update_spinner() + if not self.processing then + self:stop_spinner() + return + end + + self.spinner_index = (self.spinner_index % #self.spinner_symbols) + 1 + + local buf = self:get_buf(self.filetype) + if buf == nil then + return + end + + -- Clear previous virtual text + vim.api.nvim_buf_clear_namespace(buf, self.namespace_id, 0, -1) + + local last_line = vim.api.nvim_buf_line_count(buf) - 1 + vim.api.nvim_buf_set_extmark(buf, self.namespace_id, last_line, 0, { + virt_lines = { { { self.spinner_symbols[self.spinner_index] .. " Processing...", "Comment" } } }, + virt_lines_above = true, -- false means below the line + }) +end + +function M:start_spinner() + self.processing = true + self.spinner_index = 0 + + if self.timer then + self.timer:stop() + self.timer:close() + self.timer = nil + end + + self.timer = vim.loop.new_timer() + self.timer:start( + 0, + 100, + vim.schedule_wrap(function() + self:update_spinner() + end) + ) +end + +function M:stop_spinner() + self.processing = false + + if self.timer then + self.timer:stop() + self.timer:close() + self.timer = nil + end + + local buf = self:get_buf(self.filetype) + if buf == nil then + return + end + + vim.api.nvim_buf_clear_namespace(buf, self.namespace_id, 0, -1) +end + +function M:init() + -- Create namespace for virtual text + self.namespace_id = vim.api.nvim_create_namespace("CodeCompanionSpinner") + + vim.api.nvim_create_augroup("CodeCompanionHooks", { clear = true }) + local group = vim.api.nvim_create_augroup("CodeCompanionHooks", {}) + + vim.api.nvim_create_autocmd({ "User" }, { + pattern = "CodeCompanionRequest*", + group = group, + callback = function(request) + if request.match == "CodeCompanionRequestStarted" then + self:start_spinner() + elseif request.match == "CodeCompanionRequestFinished" then + self:stop_spinner() + end + end, + }) +end + +return M