diff --git a/lua/plugins/ai.lua b/lua/plugins/ai.lua new file mode 100644 index 0000000..a8f68ba --- /dev/null +++ b/lua/plugins/ai.lua @@ -0,0 +1,95 @@ +return { + -- 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, + }, +} diff --git a/lua/plugins/completion.lua b/lua/plugins/completion.lua new file mode 100644 index 0000000..952591a --- /dev/null +++ b/lua/plugins/completion.lua @@ -0,0 +1,35 @@ +return { + -- 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, + }, +} diff --git a/lua/plugins/editor.lua b/lua/plugins/editor.lua new file mode 100644 index 0000000..b438057 --- /dev/null +++ b/lua/plugins/editor.lua @@ -0,0 +1,48 @@ +return { + -- Commenting + { + "numToStr/Comment.nvim", + event = { "BufReadPre", "BufNewFile" }, + opts = {}, + }, + + -- Surround text objects + { + "kylechui/nvim-surround", + version = "*", + event = "VeryLazy", + opts = {}, + }, + + -- 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" }, + }, + }, + + -- 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, + }, +} diff --git a/lua/plugins/git.lua b/lua/plugins/git.lua new file mode 100644 index 0000000..44b15d2 --- /dev/null +++ b/lua/plugins/git.lua @@ -0,0 +1,42 @@ +return { + -- 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, + }, +} diff --git a/lua/plugins/lsp.lua b/lua/plugins/lsp.lua new file mode 100644 index 0000000..df99cdd --- /dev/null +++ b/lua/plugins/lsp.lua @@ -0,0 +1,45 @@ +return { + -- Mason (LSP installer) + { + "williamboman/mason.nvim", + build = ":MasonUpdate", + config = true, + }, + + -- Mason LSP config bridge + { + "williamboman/mason-lspconfig.nvim", + dependencies = { "williamboman/mason.nvim" }, + config = function() + require("mason-lspconfig").setup({ + ensure_installed = { "ts_ls", "eslint", "jsonls", "html", "cssls", "tailwindcss" }, + }) + end, + }, + + -- LSP config + { + "neovim/nvim-lspconfig", + dependencies = { + "williamboman/mason.nvim", + "williamboman/mason-lspconfig.nvim", + "hrsh7th/cmp-nvim-lsp", + }, + }, + + -- TypeScript tools + { + "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, + }, +} diff --git a/lua/plugins/navigation.lua b/lua/plugins/navigation.lua new file mode 100644 index 0000000..05d34f1 --- /dev/null +++ b/lua/plugins/navigation.lua @@ -0,0 +1,28 @@ +return { + -- 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, + }, + + -- File explorer + { + "nvim-tree/nvim-tree.lua", + dependencies = { "nvim-tree/nvim-web-devicons" }, + config = function() + require("nvim-tree").setup() + end, + }, +} diff --git a/lua/plugins/plugin.lua b/lua/plugins/plugin.lua deleted file mode 100644 index 4ad4838..0000000 --- a/lua/plugins/plugin.lua +++ /dev/null @@ -1,576 +0,0 @@ -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, - }, -} diff --git a/lua/plugins/telescope.lua b/lua/plugins/telescope.lua new file mode 100644 index 0000000..bccc7eb --- /dev/null +++ b/lua/plugins/telescope.lua @@ -0,0 +1,10 @@ +return { + -- Fuzzy finder + { + "nvim-telescope/telescope.nvim", + dependencies = { "nvim-lua/plenary.nvim" }, + config = function() + require("telescope").setup() + end, + }, +} diff --git a/lua/plugins/tools.lua b/lua/plugins/tools.lua new file mode 100644 index 0000000..81ad801 --- /dev/null +++ b/lua/plugins/tools.lua @@ -0,0 +1,53 @@ +return { + -- 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 = {}, + }, + + -- TODO comments + { + "folke/todo-comments.nvim", + event = { "BufReadPre", "BufNewFile" }, + dependencies = { "nvim-lua/plenary.nvim" }, + opts = {}, + keys = { + { "ft", "TodoTelescope", desc = "Find TODOs" }, + }, + }, + + -- Undo tree + { + "mbbill/undotree", + cmd = "UndotreeToggle", + keys = { + { "u", "UndotreeToggle", desc = "Toggle undotree" }, + }, + }, + + -- 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" }, + }, + }, + }, +} diff --git a/lua/plugins/treesitter.lua b/lua/plugins/treesitter.lua new file mode 100644 index 0000000..7ac2272 --- /dev/null +++ b/lua/plugins/treesitter.lua @@ -0,0 +1,17 @@ +return { + -- 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, + }, +} diff --git a/lua/plugins/ui.lua b/lua/plugins/ui.lua new file mode 100644 index 0000000..47cad72 --- /dev/null +++ b/lua/plugins/ui.lua @@ -0,0 +1,98 @@ +return { + -- Status line + { + "nvim-lualine/lualine.nvim", + dependencies = { "nvim-tree/nvim-web-devicons" }, + config = function() + require("lualine").setup({ + options = { theme = "kanagawa" }, + }) + 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, + }, + + -- 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, + }, + + -- Colorizer + { + "NvChad/nvim-colorizer.lua", + opts = { user_default_options = { names = false } } + }, + + -- Indent guides + { + "lukas-reineke/indent-blankline.nvim", + main = "ibl", + event = { "BufReadPre", "BufNewFile" }, + opts = { + indent = { char = "│" }, + scope = { enabled = true }, + }, + }, +}