return { -- Color scheme { "rebelot/kanagawa.nvim", name = "kanagawa", priority = 1000, config = function() require('kanagawa').setup({ compile = true, undercurl = true, commentStyle = { italic = true }, functionStyle = {}, keywordStyle = { italic = true }, statementStyle = { bold = true }, typeStyle = {}, transparent = false, dimInactive = false, terminalColors = true, colors = { palette = {}, theme = { wave = {}, lotus = {}, dragon = {}, all = {} }, }, overrides = function(colors) return {} end, theme = "wave", background = { dark = "wave", light = "lotus" }, }) -- only set kanagawa if pywal colors don't exist if vim.fn.filereadable(vim.fn.expand("~/.cache/wal/colors")) == 0 then vim.cmd("colorscheme kanagawa") end end, }, -- Pywal colorscheme (syncs with terminal) { "AlphaTechnolog/pywal.nvim", name = "pywal", priority = 1000, config = function() -- auto-load pywal if colors exist if vim.fn.filereadable(vim.fn.expand("~/.cache/wal/colors")) == 1 then vim.cmd("colorscheme pywal") end end, }, -- Theme switcher { "nvim-telescope/telescope.nvim", keys = { { "th", function() local pickers = require("telescope.pickers") local finders = require("telescope.finders") local conf = require("telescope.config").values local actions = require("telescope.actions") local action_state = require("telescope.actions.state") local themes = { { name = "pywal (from wallpaper)", value = "pywal" }, { name = "Pick new wallpaper...", value = "_wallpaper_picker" }, { name = "kanagawa", value = "kanagawa" }, { name = "kanagawa-wave", value = "kanagawa-wave" }, { name = "kanagawa-dragon", value = "kanagawa-dragon" }, { name = "kanagawa-lotus", value = "kanagawa-lotus" }, } pickers.new({}, { prompt_title = "Theme Switcher", finder = finders.new_table({ results = themes, entry_maker = function(entry) return { value = entry.value, display = entry.name, ordinal = entry.name, } end, }), sorter = conf.generic_sorter({}), attach_mappings = function(prompt_bufnr, map) actions.select_default:replace(function() actions.close(prompt_bufnr) local selection = action_state.get_selected_entry() if selection.value == "_wallpaper_picker" then -- launch wallpaper picker, then reload pywal vim.fn.jobstart( { "bash", "-c", "~/scripts/pywal/wallpapermenu.sh && sleep 1" }, { on_exit = function() vim.schedule(function() vim.cmd("colorscheme pywal") require("lualine").setup({ options = { theme = "pywal" } }) vim.notify("Pywal theme applied", vim.log.levels.INFO) end) end, } ) else vim.cmd("colorscheme " .. selection.value) -- update lualine theme local lualine_theme = selection.value:match("^kanagawa") and "kanagawa" or selection.value require("lualine").setup({ options = { theme = lualine_theme } }) vim.notify("Theme: " .. selection.value, vim.log.levels.INFO) end end) return true end, }):find() end, desc = "Theme switcher", }, }, }, -- File explorer { "nvim-tree/nvim-tree.lua", dependencies = { "nvim-tree/nvim-web-devicons" }, config = function() require("nvim-tree").setup() end, }, -- Git signs { "lewis6991/gitsigns.nvim", event = { "BufReadPre", "BufNewFile" }, config = function() require("gitsigns").setup({ on_attach = function(bufnr) local gs = package.loaded.gitsigns local map = function(mode, l, r, opts) opts = opts or {} opts.buffer = bufnr vim.keymap.set(mode, l, r, opts) end -- Navigation map("n", "]c", function() gs.next_hunk() end, { desc = "Next hunk" }) map("n", "[c", function() gs.prev_hunk() end, { desc = "Previous hunk" }) -- Actions map("n", "gs", gs.stage_hunk, { desc = "Stage hunk" }) map("n", "gr", gs.reset_hunk, { desc = "Reset hunk" }) map("n", "gS", gs.stage_buffer, { desc = "Stage buffer" }) map("n", "gu", gs.undo_stage_hunk, { desc = "Undo stage" }) map("n", "gp", gs.preview_hunk, { desc = "Preview hunk" }) map("n", "gb", function() gs.blame_line({ full = true }) end, { desc = "Blame line" }) map("n", "gd", gs.diffthis, { desc = "Diff this" }) end, }) end, }, -- Diff view { "sindrets/diffview.nvim", cmd = { "DiffviewOpen", "DiffviewFileHistory" }, keys = { { "gv", "DiffviewOpen", desc = "Diff view" }, { "gh", "DiffviewFileHistory %", desc = "File history" }, { "gx", "DiffviewClose", desc = "Close diff" }, }, config = true, }, -- Fuzzy finder { "nvim-telescope/telescope.nvim", dependencies = { "nvim-lua/plenary.nvim" }, config = function() require("telescope").setup() end, }, -- Formatter { "stevearc/conform.nvim", opts = { format_on_save = { timeout_ms = 1000, lsp_fallback = true }, formatters_by_ft = { javascript = { "prettierd", "prettier" }, javascriptreact = { "prettierd", "prettier" }, typescript = { "prettierd", "prettier" }, typescriptreact = { "prettierd", "prettier" }, json = { "prettierd", "prettier" }, css = { "prettierd", "prettier" }, html = { "prettierd", "prettier" }, markdown = { "prettierd", "prettier" }, }, }, }, -- Syntax highlighting { "nvim-treesitter/nvim-treesitter", build = ":TSUpdate", config = function() require("nvim-treesitter.configs").setup({ ensure_installed = { "lua", "vim", "bash", "javascript", "typescript", "tsx", "json", "yaml", "html", "css", "prisma", "graphql" }, highlight = { enable = true }, indent = { enable = true }, }) end, }, -- Autocompletion { "hrsh7th/nvim-cmp", dependencies = { "hrsh7th/cmp-buffer", "hrsh7th/cmp-path", "hrsh7th/cmp-nvim-lsp", "L3MON4D3/LuaSnip", "saadparwaiz1/cmp_luasnip", "rafamadriz/friendly-snippets", }, config = function() local cmp = require("cmp") local luasnip = require("luasnip") require("luasnip.loaders.from_vscode").lazy_load() cmp.setup({ snippet = { expand = function(args) luasnip.lsp_expand(args.body) end }, mapping = cmp.mapping.preset.insert({ [""] = cmp.mapping.complete(), [""] = cmp.mapping.confirm({ select = true }), [""] = cmp.mapping.select_next_item(), [""] = cmp.mapping.select_prev_item(), }), sources = { { name = "nvim_lsp" }, { name = "path" }, { name = "buffer" }, { name = "luasnip" }, }, }) end, }, -- Auto-pairs { "windwp/nvim-autopairs", event = "InsertEnter", opts = { check_ts = true, ts_config = { lua = { "string", "source" }, javascript = { "string", "template_string" }, typescript = { "string", "template_string" }, }, disable_filetype = { "TelescopePrompt", "spectre_panel" }, }, config = function(_, opts) require("nvim-autopairs").setup(opts) local cmp_autopairs = require("nvim-autopairs.completion.cmp") local cmp = require("cmp") cmp.event:on("confirm_done", cmp_autopairs.on_confirm_done()) end, }, -- LSP { "williamboman/mason.nvim", build = ":MasonUpdate", config = true, }, { "williamboman/mason-lspconfig.nvim", dependencies = { "williamboman/mason.nvim" }, config = function() require("mason-lspconfig").setup({ ensure_installed = { "ts_ls", "eslint", "jsonls", "html", "cssls", "tailwindcss" }, }) end, }, { "neovim/nvim-lspconfig", dependencies = { "williamboman/mason.nvim", "williamboman/mason-lspconfig.nvim", "hrsh7th/cmp-nvim-lsp", }, }, { "pmizio/typescript-tools.nvim", dependencies = { "nvim-lua/plenary.nvim", "neovim/nvim-lspconfig" }, config = function() require("typescript-tools").setup({ settings = { tsserver_file_preferences = { includeInlayParameterNameHints = "all", includeCompletionsForModuleExports = true }, }, }) end, }, -- Colorizer { "NvChad/nvim-colorizer.lua", opts = { user_default_options = { names = false } } }, -- Status line { "nvim-lualine/lualine.nvim", dependencies = { "nvim-tree/nvim-web-devicons" }, config = function() require("lualine").setup({ options = { theme = "kanagawa" }, }) end, }, -- Commenting { "numToStr/Comment.nvim", event = { "BufReadPre", "BufNewFile" }, opts = {}, }, -- Surround text objects { "kylechui/nvim-surround", version = "*", event = "VeryLazy", opts = {}, }, -- Quick file navigation { "ThePrimeagen/harpoon", branch = "harpoon2", dependencies = { "nvim-lua/plenary.nvim" }, config = function() local harpoon = require("harpoon") harpoon:setup() vim.keymap.set("n", "ha", function() harpoon:list():add() end, { desc = "Harpoon add" }) vim.keymap.set("n", "hh", function() harpoon.ui:toggle_quick_menu(harpoon:list()) end, { desc = "Harpoon menu" }) vim.keymap.set("n", "1", function() harpoon:list():select(1) end, { desc = "Harpoon 1" }) vim.keymap.set("n", "2", function() harpoon:list():select(2) end, { desc = "Harpoon 2" }) vim.keymap.set("n", "3", function() harpoon:list():select(3) end, { desc = "Harpoon 3" }) vim.keymap.set("n", "4", function() harpoon:list():select(4) end, { desc = "Harpoon 4" }) end, }, -- Flash motions { "folke/flash.nvim", event = "VeryLazy", opts = {}, keys = { { "s", mode = { "n", "x", "o" }, function() require("flash").jump() end, desc = "Flash" }, { "S", mode = { "n", "x", "o" }, function() require("flash").treesitter() end, desc = "Flash Treesitter" }, }, }, -- Indent guides { "lukas-reineke/indent-blankline.nvim", main = "ibl", event = { "BufReadPre", "BufNewFile" }, opts = { indent = { char = "│" }, scope = { enabled = true }, }, }, -- TODO comments { "folke/todo-comments.nvim", event = { "BufReadPre", "BufNewFile" }, dependencies = { "nvim-lua/plenary.nvim" }, opts = {}, keys = { { "ft", "TodoTelescope", desc = "Find TODOs" }, }, }, -- Diagnostics list { "folke/trouble.nvim", dependencies = { "nvim-tree/nvim-web-devicons" }, cmd = "Trouble", keys = { { "xx", "Trouble diagnostics toggle", desc = "Diagnostics" }, { "xX", "Trouble diagnostics toggle filter.buf=0", desc = "Buffer diagnostics" }, { "xl", "Trouble loclist toggle", desc = "Location list" }, { "xq", "Trouble qflist toggle", desc = "Quickfix list" }, }, opts = {}, }, -- Undo tree { "mbbill/undotree", cmd = "UndotreeToggle", keys = { { "u", "UndotreeToggle", desc = "Toggle undotree" }, }, }, -- Which-key { "folke/which-key.nvim", event = "VeryLazy", config = function() local wk = require("which-key") wk.setup({ preset = "modern", }) wk.add({ { "e", desc = "Toggle file explorer" }, { "f", group = "Find" }, { "ff", desc = "Find files" }, { "fg", desc = "Live grep" }, { "fb", desc = "Find buffers" }, { "ft", desc = "Find TODOs" }, { "g", group = "Git" }, { "h", group = "Harpoon" }, { "t", group = "Theme" }, { "th", desc = "Theme switcher" }, { "x", group = "Trouble" }, { "w", desc = "Save" }, { "q", desc = "Quit" }, { "u", desc = "Undo tree" }, { "9", group = "AI (99)" }, { "9f", desc = "Fill Function" }, { "9v", desc = "Visual AI" }, { "9s", desc = "Stop requests" }, }) end, }, -- Alpha (dashboard) { "goolord/alpha-nvim", config = function() local alpha = require('alpha') local dashboard = require("alpha.themes.dashboard") dashboard.section.header.val = { [[ ██╗ ██╗███████╗ ██████╗ ███████╗███████╗██╗ ██████╗ ███╗ ██╗ ]], [[ ██║ ██║╚════██║ ██╔══██╗██╔════╝██╔════╝██║██╔════╝ ████╗ ██║ ]], [[ ██║ ██║ ██╔╝ ██║ ██║█████╗ ███████╗██║██║ ███╗██╔██╗ ██║ ]], [[ ╚██╗ ██╔╝ ██╔╝ ██║ ██║██╔══╝ ╚════██║██║██║ ██║██║╚██╗██║ ]], [[ ╚████╔╝ ██║ ██████╔╝███████╗███████║██║╚██████╔╝██║ ╚████║ ]], [[ ╚═══╝ ╚═╝ ╚═════╝ ╚══════╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ]], } dashboard.section.buttons.val = { dashboard.button("e", " New file", ":ene startinsert "), dashboard.button("f", "󰍉 Find file", ":lua require('fzf-lua').files() "), dashboard.button("t", " Browse cwd", ":NvimTreeOpen"), dashboard.button("r", " Browse src", ":e ~/.local/src/"), dashboard.button("s", "󰯂 Browse scripts", ":e ~/scripts/"), dashboard.button("c", " Config", ":e ~/.config/nvim/"), dashboard.button("m", " Mappings", ":e ~/.config/nvim/lua/core/keymaps.lua"), dashboard.button("p", " Plugins", ":PlugInstall"), dashboard.button("q", "󰅙 Quit", ":q!"), } dashboard.section.footer.val = function() return vim.g.startup_time_ms or "[[ ]]" end dashboard.section.buttons.opts.hl = "Keyword" dashboard.opts.opts.noautocmd = true alpha.setup(dashboard.opts) end, }, -- AI agent (99) { "ThePrimeagen/99", config = function() local _99 = require("99") local Providers = require("99.providers") local cwd = vim.uv.cwd() local basename = vim.fs.basename(cwd) -- custom provider with --attach flag for tool use local CustomOpenCodeProvider = setmetatable({}, { __index = Providers.OpenCodeProvider }) function CustomOpenCodeProvider._build_command(_, query, request) return { "opencode", "run", "--attach", "http://localhost:4096", "-m", request.context.model, query } end _99.setup({ -- Auto-detect provider: OpenCode with server if available, else Claude Code provider = (function() -- Check if opencode is installed local opencode_installed = vim.fn.executable("opencode") == 1 if not opencode_installed then return Providers.ClaudeCodeProvider end -- Check if opencode serve is running on port 4096 local handle = io.popen("curl -s -o /dev/null -w '%{http_code}' http://localhost:4096/health 2>/dev/null || echo '000'") local result = handle:read("*a") handle:close() -- If server is responding (any 2xx or 404), use CustomOpenCodeProvider if result:match("^[24]%d%d") then return Providers.CustomOpenCodeProvider end -- Fallback to Claude Code return Providers.ClaudeCodeProvider end)(), model = (function() -- Use appropriate model format based on provider local opencode_installed = vim.fn.executable("opencode") == 1 if opencode_installed then local handle = io.popen("curl -s -o /dev/null -w '%{http_code}' http://localhost:4096/health 2>/dev/null || echo '000'") local result = handle:read("*a") handle:close() if result:match("^[24]%d%d") then return "anthropic/claude-sonnet-4-5" end end return "claude-sonnet-4-5" end)(), logger = { level = _99.DEBUG, path = "/tmp/" .. basename .. ".99.debug", print_on_error = true, }, completion = { -- custom_rules = { "~/.config/nvim/rules/" }, source = "cmp", }, md_files = { "AGENT.md", "CLAUDE.md", }, }) vim.keymap.set("n", "9f", function() _99.fill_in_function() end, { desc = "99: Fill function" }) vim.keymap.set("v", "9v", function() _99.visual() end, { desc = "99: Visual AI" }) vim.keymap.set("v", "9p", function() _99.visual_prompt() end, { desc = "99: Visual with prompt" }) vim.keymap.set("v", "9s", function() _99.stop_all_requests() end, { desc = "99: Stop requests" }) end, }, }