From da6106c77470b336783951ff2bce795688e9663d Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Wed, 15 Apr 2026 21:01:43 -0400 Subject: [PATCH] init --- README.md | 73 ++++++++ init.lua | 13 ++ lua/config/keymaps.lua | 23 +++ lua/config/lazy.lua | 35 ++++ lua/config/options.lua | 48 +++++ lua/plugins/bufferline.lua | 40 ++++ lua/plugins/colorscheme.lua | 109 +++++++++++ lua/plugins/disabled.lua | 5 + lua/plugins/gitsigns.lua | 59 ++++++ lua/plugins/indent-blankline.lua | 22 +++ lua/plugins/lsp.lua | 310 +++++++++++++++++++++++++++++++ lua/plugins/lualine.lua | 60 ++++++ lua/plugins/neotree.lua | 102 ++++++++++ lua/plugins/telescope.lua | 62 +++++++ lua/plugins/which-key.lua | 89 +++++++++ 15 files changed, 1050 insertions(+) create mode 100644 README.md create mode 100644 init.lua create mode 100644 lua/config/keymaps.lua create mode 100644 lua/config/lazy.lua create mode 100644 lua/config/options.lua create mode 100644 lua/plugins/bufferline.lua create mode 100644 lua/plugins/colorscheme.lua create mode 100644 lua/plugins/disabled.lua create mode 100644 lua/plugins/gitsigns.lua create mode 100644 lua/plugins/indent-blankline.lua create mode 100644 lua/plugins/lsp.lua create mode 100644 lua/plugins/lualine.lua create mode 100644 lua/plugins/neotree.lua create mode 100644 lua/plugins/telescope.lua create mode 100644 lua/plugins/which-key.lua diff --git a/README.md b/README.md new file mode 100644 index 0000000..144eb56 --- /dev/null +++ b/README.md @@ -0,0 +1,73 @@ +# Neovim Configuration + +Portable Neovim configuration using lazy.nvim and native LSP (Neovim 0.11+, tested on 0.12.1). + +## Installation + +### Standalone (any system) +```bash +git clone git@github.com:itme-brain/nvim.git ~/.config/nvim +``` + +### As part of NixOS config +```bash +git clone --recurse-submodules git@github.com:itme-brain/nixos.git +``` + +## Features + +- **Native LSP** (`vim.lsp.config` / `vim.lsp.enable`) - no manual server list needed +- **Smart LSP picker** (`css`) - auto-detects installed servers for current filetype +- **Neovim 0.12 compatible** - uses built-in `:lsp` commands and keeps legacy `:Lsp*` aliases working +- **Portable** - works on NixOS (LSPs via extraPackages) or any system (LSPs via Mason) +- **Mason** - auto-disabled on NixOS, auto-installs essentials elsewhere + +## LSP Setup + +On NixOS, LSPs come from: +- `neovim.extraPackages` (global essentials) +- Project `devShell` (project-specific) + +On other systems, Mason auto-installs: +- `lua_ls` - Lua/Neovim +- `nil_ls` - Nix +- `bashls` - Bash/Shell +- `jsonls` - JSON +- `html` - HTML +- `cssls` - CSS +- `marksman` - Markdown +- `taplo` - TOML +- `yamlls` - YAML + +The smart picker (`css`) scans all lspconfig servers and shows only those with binaries installed. +On Neovim 0.12+, start/stop/restart uses the built-in `:lsp` commands under the hood. + +## Key Bindings + +| Key | Action | +|-----|--------| +| `css` | Start LSP (smart picker) | +| `csx` | Stop LSP | +| `csr` | Restart LSP | +| `cf` | Format code | +| `ca` | Code action | +| `cr` | Rename symbol | +| `gd` | Go to definition | +| `gr` | Find references | +| `e` | Toggle file explorer | +| `bd` | Delete buffer | +| `/` | Live grep from git root | +| `ff` | Find files from git root | + +## Plugins + +- **lazy.nvim** - plugin manager +- **nvim-lspconfig** - LSP configurations +- **nvim-cmp** - completion +- **telescope.nvim** - fuzzy finder +- **nvim-treesitter** - syntax highlighting +- **neo-tree.nvim** - file explorer +- **gitsigns.nvim** - git integration +- **lualine.nvim** - statusline +- **bufferline.nvim** - buffer tabs +- **which-key.nvim** - keybinding hints diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..fad0bd2 --- /dev/null +++ b/init.lua @@ -0,0 +1,13 @@ +local function load_config_directory(directory) + local config_path = vim.fn.stdpath("config") .. "/lua/" .. directory + local files = vim.fn.readdir(config_path, function(name) + return name:sub(-4) == ".lua" + end) + + for _, file in ipairs(files) do + local file_name = file:sub(1, -5) + require(directory .. "." .. file_name) + end +end + +load_config_directory("config") diff --git a/lua/config/keymaps.lua b/lua/config/keymaps.lua new file mode 100644 index 0000000..a12a224 --- /dev/null +++ b/lua/config/keymaps.lua @@ -0,0 +1,23 @@ +-- Keep cursor centered while navigating document +vim.keymap.set("n", "", "zz", { silent = true }) +vim.keymap.set("n", "", "zz", { silent = true }) + +-- Remap Ctrl + J/K/H/L to navigate between windows +vim.keymap.set('n', '', 'j', { noremap = true, silent = true }) +vim.keymap.set('n', '', 'k', { noremap = true, silent = true }) +vim.keymap.set('n', '', 'h', { noremap = true, silent = true }) +vim.keymap.set('n', '', 'l', { noremap = true, silent = true }) + +vim.keymap.set('n', '', ':vertical resize +10', { noremap = true, silent = true }) +vim.keymap.set('n', '', ':vertical resize -10', { noremap = true, silent = true }) +vim.keymap.set('n', '', ':horizontal resize +10', { noremap = true, silent = true }) +vim.keymap.set('n', '', ':horizontal resize -10', { noremap = true, silent = true }) + +-- Remap Shift + H/L to switch between buffers +vim.keymap.set('n', '', ':bprevious', { noremap = true, silent = true }) +vim.keymap.set('n', '', ':bnext', { noremap = true, silent = true }) + +vim.keymap.set("v", "<", "", ">gv") + +vim.keymap.set("n", "", ':nohlsearchlet @/=""', { noremap = true, silent = true}) diff --git a/lua/config/lazy.lua b/lua/config/lazy.lua new file mode 100644 index 0000000..89c356d --- /dev/null +++ b/lua/config/lazy.lua @@ -0,0 +1,35 @@ +-- Bootstrap lazy.nvim +local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" +if not (vim.uv or vim.loop).fs_stat(lazypath) then + local lazyrepo = "https://github.com/folke/lazy.nvim.git" + local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath }) + if vim.v.shell_error ~= 0 then + vim.api.nvim_echo({ + { "Failed to clone lazy.nvim:\n", "ErrorMsg" }, + { out, "WarningMsg" }, + { "\nPress any key to exit..." }, + }, true, {}) + vim.fn.getchar() + os.exit(1) + end +end +vim.opt.rtp:prepend(lazypath) + +-- Make sure to setup `mapleader` and `maplocalleader` before +-- loading lazy.nvim so that mappings are correct. +-- This is also a good place to setup other settings (vim.opt) +vim.g.mapleader = " " +vim.g.maplocalleader = "\\" + +-- Setup lazy.nvim +require("lazy").setup({ + spec = { + -- import your plugins + { import = "plugins" }, + }, + -- Configure any other settings here. See the documentation for more details. + -- colorscheme that will be used when installing plugins. + install = { colorscheme = { "onedark" } }, + -- automatically check for plugin updates + checker = { enabled = false }, +}) diff --git a/lua/config/options.lua b/lua/config/options.lua new file mode 100644 index 0000000..3bb30b8 --- /dev/null +++ b/lua/config/options.lua @@ -0,0 +1,48 @@ +vim.o.clipboard = "unnamedplus" +vim.g.autoformat = false + +vim.opt.number = true +vim.opt.relativenumber = true +vim.opt.cursorline = true +-- Enable true color if terminal supports it (disabled in TTY/headless) +if vim.env.COLORTERM == "truecolor" or vim.env.COLORTERM == "24bit" then + vim.opt.termguicolors = true +end + +vim.opt.tabstop = 2 +vim.opt.shiftwidth = 2 +vim.opt.softtabstop = 2 +vim.opt.expandtab = true +vim.opt.smartindent = true +vim.opt.ignorecase = true +vim.opt.smartcase = true +vim.opt.incsearch = false + +vim.opt.swapfile = false +vim.opt.backup = false +vim.opt.undofile = true + +vim.opt.guicursor = "n-v-c:block,i:block,r:block" + +vim.opt.fillchars = { eob = " " } + +local options_group = vim.api.nvim_create_augroup("config_options", { clear = true }) + +vim.api.nvim_create_autocmd("FileType", { + group = options_group, + pattern = { "python", "haskell", "c", "cpp" }, + callback = function() + local opt = vim.opt_local + opt.tabstop = 4 + opt.shiftwidth = 4 + opt.softtabstop = 4 + end, +}) + +vim.api.nvim_create_autocmd({ "BufRead", "BufNewFile" }, { + group = options_group, + pattern = "*.purs", + callback = function(event) + vim.bo[event.buf].filetype = "purescript" + end, +}) diff --git a/lua/plugins/bufferline.lua b/lua/plugins/bufferline.lua new file mode 100644 index 0000000..8ddec03 --- /dev/null +++ b/lua/plugins/bufferline.lua @@ -0,0 +1,40 @@ +return { + { + "akinsho/bufferline.nvim", + version = "*", + dependencies = "nvim-tree/nvim-web-devicons", + config = function() + require("bufferline").setup{ + options = { + separator_style = "thin", + show_buffer_close_buttons = false, + show_close_icon = false, + }, + highlights = { + -- Force all icon backgrounds to transparent + buffer_selected = { bg = "NONE" }, + buffer_visible = { bg = "NONE" }, + background = { bg = "NONE" }, + fill = { bg = "NONE" }, + separator = { bg = "NONE" }, + separator_selected = { bg = "NONE" }, + separator_visible = { bg = "NONE" }, + close_button = { bg = "NONE" }, + close_button_selected = { bg = "NONE" }, + close_button_visible = { bg = "NONE" }, + modified = { bg = "NONE" }, + modified_selected = { bg = "NONE" }, + modified_visible = { bg = "NONE" }, + duplicate = { bg = "NONE" }, + duplicate_selected = { bg = "NONE" }, + duplicate_visible = { bg = "NONE" }, + indicator_selected = { bg = "NONE" }, + indicator_visible = { bg = "NONE" }, + pick = { bg = "NONE" }, + pick_selected = { bg = "NONE" }, + pick_visible = { bg = "NONE" }, + }, + } + end + } +} diff --git a/lua/plugins/colorscheme.lua b/lua/plugins/colorscheme.lua new file mode 100644 index 0000000..a5ab11e --- /dev/null +++ b/lua/plugins/colorscheme.lua @@ -0,0 +1,109 @@ +return { + { + "chriskempson/base16-vim", + config = function() + local color_group = vim.api.nvim_create_augroup("config_colorscheme", { clear = true }) + + local highlights = { + Normal = { bg = "NONE", fg = "#FFFFFF" }, + Visual = { bg = "Gray", fg = "Black" }, + NonText = { bg = "NONE" }, + LineNr = { bg = "NONE" }, + CursorLine = { bg = "NONE" }, + CursorLineNr = { bg = "NONE", fg = "#E5C07B", bold = true }, + Search = { bg = "#FFCC66", fg = "#000000" }, + Pmenu = { bg = "Black", fg = "White" }, + PmenuSel = { bg = "Green", fg = "Black" }, + PmenuThumb = { bg = "Green" }, + PmenuSbar = { bg = "Black" }, + WinSeparator = { bg = "NONE" }, + GitGutterChange = { bg = "NONE" }, + GitGutterAdd = { bg = "NONE" }, + GitGutterDelete = { bg = "NONE" }, + GitSignsAddNr = { bg = "NONE", fg = "#98c379" }, + GitSignsChangeNr = { bg = "NONE", fg = "#61afef" }, + GitSignsDeleteNr = { bg = "NONE", fg = "#e06c75" }, + SignColumn = { bg = "NONE" }, + NeoTreeGitAdded = { bg = "NONE", fg = "#98c379" }, + NeoTreeGitModified = { bg = "NONE", fg = "#e5c07b" }, + NeoTreeGitDeleted = { bg = "NONE", fg = "#e06c75" }, + NeoTreeGitConflict = { bg = "NONE", fg = "#e06c75" }, + NeoTreeGitUntracked = { bg = "NONE", fg = "#61afef" }, + TelescopeSelection = { bg = "Gray", fg = "Green", bold = true }, + TelescopePreviewMatch = { bg = "Yellow", fg = "Black" }, + TreesitterContext = { bg = "NONE" }, + LazyH1 = { bg = "Black", fg = "Green" }, + IblScope = { bg = "NONE", fg = "Yellow" }, + ConflictMarker = { fg = "red" }, + DiffAdd = { bg = "NONE" }, + DiffChange = { bg = "NONE" }, + DiffDelete = { bg = "NONE" }, + DiffText = { bg = "NONE" }, + BufferLineFill = { bg = "NONE" }, + BufferLineBackground = { bg = "NONE", fg = "#5c6370" }, + BufferLineBuffer = { bg = "NONE", fg = "#5c6370" }, + BufferLineBufferSelected = { bg = "NONE", fg = "#FFFFFF", bold = true }, + BufferLineBufferVisible = { bg = "NONE", fg = "#abb2bf" }, + BufferLineCloseButton = { bg = "NONE", fg = "#5c6370" }, + BufferLineCloseButtonSelected = { bg = "NONE", fg = "#e06c75" }, + BufferLineCloseButtonVisible = { bg = "NONE", fg = "#5c6370" }, + BufferLineModified = { bg = "NONE", fg = "#e5c07b" }, + BufferLineModifiedSelected = { bg = "NONE", fg = "#e5c07b" }, + BufferLineModifiedVisible = { bg = "NONE", fg = "#e5c07b" }, + BufferLineSeparator = { bg = "NONE", fg = "#3e4452" }, + BufferLineSeparatorSelected = { bg = "NONE", fg = "#3e4452" }, + BufferLineSeparatorVisible = { bg = "NONE", fg = "#3e4452" }, + BufferLineIndicatorSelected = { bg = "NONE", fg = "#61afef" }, + YankHighlight = { bg = "yellow", fg = "black" }, + } + + local function apply_highlights() + for group, spec in pairs(highlights) do + vim.api.nvim_set_hl(0, group, spec) + end + end + + local conflict_pattern = [[<<<<<<< HEAD\|=======\|>>>>>>> .\+]] + local function apply_conflict_match(win) + if vim.w[win].conflict_marker_match_id then + pcall(vim.fn.matchdelete, vim.w[win].conflict_marker_match_id, win) + end + vim.w[win].conflict_marker_match_id = vim.fn.matchadd("ConflictMarker", conflict_pattern, 10, -1, { + window = win, + }) + end + + vim.cmd.colorscheme("base16-onedark") + apply_highlights() + + vim.api.nvim_create_autocmd("ColorScheme", { + group = color_group, + callback = apply_highlights, + }) + + vim.api.nvim_create_autocmd({ "BufWinEnter", "WinEnter" }, { + group = color_group, + callback = function(event) + apply_conflict_match(vim.api.nvim_get_current_win()) + end, + }) + + vim.api.nvim_create_autocmd("TextYankPost", { + group = color_group, + callback = function() + vim.highlight.on_yank({ higroup = "YankHighlight", timeout = 150 }) + end, + }) + end, + }, + + { + "folke/todo-comments.nvim", + dependencies = { "nvim-lua/plenary.nvim" }, + }, + + { + "fei6409/log-highlight.nvim" + } + +} diff --git a/lua/plugins/disabled.lua b/lua/plugins/disabled.lua new file mode 100644 index 0000000..64e75ec --- /dev/null +++ b/lua/plugins/disabled.lua @@ -0,0 +1,5 @@ +return { + { "williamboman/mason.nvim", enabled = false }, + { "williamboman/mason-lspconfig.nvim", enabled = false }, + { "jay-babu/mason-nvim-dap.nvim", enabled = false }, +} diff --git a/lua/plugins/gitsigns.lua b/lua/plugins/gitsigns.lua new file mode 100644 index 0000000..b34952f --- /dev/null +++ b/lua/plugins/gitsigns.lua @@ -0,0 +1,59 @@ +return { + { + "lewis6991/gitsigns.nvim", + config = function() + require('gitsigns').setup { + signs = { + add = { text = '+' }, + change = { text = '~' }, + delete = { text = '-' }, + topdelete = { text = '‾' }, + changedelete = { text = '~' }, + untracked = { text = '┆' }, + }, + signs_staged = { + add = { text = '+' }, + change = { text = '~' }, + delete = { text = '-' }, + topdelete = { text = '‾' }, + changedelete = { text = '~' }, + untracked = { text = '┆' }, + }, + signs_staged_enable = true, + signcolumn = false, -- Toggle with `:Gitsigns toggle_signs` + numhl = true, -- Toggle with `:Gitsigns toggle_numhl` + linehl = false, -- Toggle with `:Gitsigns toggle_linehl` + word_diff = false, -- Toggle with `:Gitsigns toggle_word_diff` + watch_gitdir = { + follow_files = true + }, + auto_attach = true, + attach_to_untracked = false, + current_line_blame = false, -- Toggle with `:Gitsigns toggle_current_line_blame` + current_line_blame_opts = { + virt_text = true, + virt_text_pos = 'eol', -- 'eol' | 'overlay' | 'right_align' + delay = 0, + ignore_whitespace = false, + virt_text_priority = 100, + }, + current_line_blame_formatter = ', - ', + sign_priority = 6, + update_debounce = 100, + status_formatter = nil, -- Use default + max_file_length = 40000, -- Disable if file is longer than this (in lines) + preview_config = { + -- Options passed to nvim_open_win + border = 'single', + style = 'minimal', + relative = 'cursor', + row = 0, + col = 1 + }, + } + require("which-key").add({ + { "Gb", ":Gitsigns toggle_current_line_blame", desc = "Git blame" } + }) + end + } +} diff --git a/lua/plugins/indent-blankline.lua b/lua/plugins/indent-blankline.lua new file mode 100644 index 0000000..27fae5a --- /dev/null +++ b/lua/plugins/indent-blankline.lua @@ -0,0 +1,22 @@ +return { + { + "lukas-reineke/indent-blankline.nvim", + config = function() + local hooks = require("ibl.hooks") + + hooks.register(hooks.type.HIGHLIGHT_SETUP, function() + vim.api.nvim_set_hl(0, "IblIndent", { link = "Comment" }) + end) + + require("ibl").setup({ + indent = { + char = "|", + highlight = "IblIndent", + }, + scope = { + enabled = false + }, + }) + end, + } +} diff --git a/lua/plugins/lsp.lua b/lua/plugins/lsp.lua new file mode 100644 index 0000000..c31339b --- /dev/null +++ b/lua/plugins/lsp.lua @@ -0,0 +1,310 @@ +-- Neovim 0.11+ LSP configuration +-- Uses nvim-lspconfig for server definitions + vim.lsp.enable() API + +-- Detect NixOS (Mason doesn't work on NixOS due to FHS issues) +local is_nixos = vim.fn.filereadable("/etc/NIXOS") == 1 + +-- Servers to ensure are installed via Mason (non-NixOS only) +-- On NixOS, install these via extraPackages or per-project devShells +local mason_ensure_installed = { + "lua_ls", -- Neovim config + "nil_ls", -- Nix (nixd not available in Mason) + "bashls", -- Shell scripts + "jsonls", -- JSON configs + "html", -- HTML + "cssls", -- CSS + "marksman", -- Markdown + "taplo", -- TOML + "yamlls", -- YAML +} + +return { + { + "nvim-treesitter/nvim-treesitter", + build = ":TSUpdate", + config = function() + require('nvim-treesitter.configs').setup({ + ensure_installed = { + "lua", + "c", + "cpp", + "python", + "nix", + "rust", + "bash", + "markdown", + "html", + "javascript", + "css", + + "vim", + + "git_config", + "git_rebase", + "gitattributes", + "gitcommit", + "gitignore" + }, + auto_install = true, + sync_install = true, + highlight = { + enable = true, + } + }) + end + }, + + { + "m4xshen/autoclose.nvim", + config = function() + require("autoclose").setup() + end + }, + + { + "hrsh7th/nvim-cmp", + dependencies = { + { + "L3MON4D3/LuaSnip", + version = "v2.*", + build = "make install_jsregexp", + }, + "saadparwaiz1/cmp_luasnip", + "hrsh7th/cmp-nvim-lsp" + }, + + config = function() + local cmp = require("cmp") + cmp.setup({ + enabled = function() + local context = require("cmp.config.context") + if vim.api.nvim_get_mode().mode == "c" then + return true + else + return not context.in_treesitter_capture("comment") and not context.in_syntax_group("comment") + end + end, + + snippet = { + expand = function(args) + require('luasnip').lsp_expand(args.body) + end + }, + + mapping = cmp.mapping.preset.insert({ + [""] = cmp.mapping.select_prev_item(), + [""] = cmp.mapping.select_next_item(), + [""] = cmp.mapping.scroll_docs(-4), + [""] = cmp.mapping.scroll_docs(4), + [""] = cmp.mapping.abort(), + [""] = cmp.mapping.confirm(), + [""] = cmp.mapping(function(fallback) + fallback() + end, { "i", "s" }), + }), + + sources = cmp.config.sources({ + { name = 'nvim_lsp' }, + { name = 'luasnip' }, + }, { + { name = 'buffer' } + }), + }) + end + }, + + -- Mason: portable LSP installer (disabled on NixOS where it doesn't work) + { + "williamboman/mason.nvim", + enabled = not is_nixos, + config = function() + require("mason").setup() + end + }, + { + "williamboman/mason-lspconfig.nvim", + enabled = not is_nixos, + dependencies = { "williamboman/mason.nvim" }, + config = function() + require("mason-lspconfig").setup({ + ensure_installed = mason_ensure_installed, + automatic_installation = false, -- Only install what's in ensure_installed + }) + end + }, + + { + "neovim/nvim-lspconfig", + dependencies = { + "hrsh7th/cmp-nvim-lsp", + }, + config = function() + local lspconfig = require('lspconfig') + + -- Neovim 0.12 exposes built-in :lsp commands and skips lspconfig's legacy + -- :Lsp* aliases. Recreate the old names so existing mappings keep working. + if vim.fn.exists(':lsp') == 2 and vim.fn.exists(':LspStart') == 0 then + vim.api.nvim_create_user_command('LspStart', function(info) + vim.cmd('lsp enable ' .. table.concat(info.fargs, ' ')) + end, { nargs = '*' }) + + vim.api.nvim_create_user_command('LspRestart', function(info) + vim.cmd('lsp restart ' .. table.concat(info.fargs, ' ')) + end, { nargs = '*', bang = true }) + + vim.api.nvim_create_user_command('LspStop', function(info) + local suffix = info.bang and '!' or '' + vim.cmd('lsp stop' .. suffix .. ' ' .. table.concat(info.fargs, ' ')) + end, { nargs = '*', bang = true }) + end + + -- Diagnostic display configuration + vim.diagnostic.config({ + virtual_text = { + prefix = '●', + spacing = 2, + current_line = true; + }, + float = { + border = 'rounded', + source = true, + }, + signs = { + text = { + [vim.diagnostic.severity.ERROR] = '', + [vim.diagnostic.severity.WARN] = '', + [vim.diagnostic.severity.INFO] = '', + [vim.diagnostic.severity.HINT] = '', + }, + }, + underline = true, + update_in_insert = false, + severity_sort = true, + }) + + -- Add border to hover and signature help windows. + local hover_handler = vim.lsp.handlers.hover + vim.lsp.handlers['textDocument/hover'] = function(err, result, ctx, config) + return hover_handler(err, result, ctx, vim.tbl_extend('force', config or {}, { + border = 'rounded', + })) + end + + local signature_help_handler = vim.lsp.handlers.signature_help + vim.lsp.handlers['textDocument/signatureHelp'] = function(err, result, ctx, config) + return signature_help_handler(err, result, ctx, vim.tbl_extend('force', config or {}, { + border = 'rounded', + })) + end + + -- Get all known server names by scanning lspconfig's lsp directory + local function get_all_servers() + local servers = {} + local lsp_path = vim.fn.stdpath('data') .. '/lazy/nvim-lspconfig/lsp' + local files = vim.fn.readdir(lsp_path, function(name) + return name:sub(-4) == '.lua' + end) + for _, file in ipairs(files) do + local server = file:sub(1, -5) + table.insert(servers, server) + end + return servers + end + + local all_servers = get_all_servers() + + -- local navic = require('nvim-navic') + local capabilities = require('cmp_nvim_lsp').default_capabilities() + + -- Global config applied to all servers + vim.lsp.config('*', { + autostart = false, -- Don't auto-attach, use css to start manually + capabilities = capabilities, + -- on_attach = function(client, bufnr) + -- if client.server_capabilities.documentSymbolProvider then + -- navic.attach(client, bufnr) + -- end + -- end, + }) + + -- Server-specific settings (merged with lspconfig defaults) + vim.lsp.config.lua_ls = { + settings = { + Lua = { + diagnostics = { + globals = { 'vim' } + } + } + } + } + + -- Check if server binary is available + local function is_server_installed(config) + if config.default_config and config.default_config.cmd then + local cmd = config.default_config.cmd[1] + return vim.fn.executable(cmd) == 1 + end + return false + end + + -- Find and start LSP server(s) for current filetype + local function lsp_start_smart() + local ft = vim.bo.filetype + if ft == '' then + vim.notify("No filetype detected", vim.log.levels.WARN) + return + end + + -- Find all matching servers (filetype match + binary installed) + local matching = {} + for _, server in ipairs(all_servers) do + local ok, config = pcall(require, 'lspconfig.configs.' .. server) + if ok and config.default_config and config.default_config.filetypes then + if vim.tbl_contains(config.default_config.filetypes, ft) and is_server_installed(config) then + table.insert(matching, server) + end + end + end + + -- Sort for consistent ordering + table.sort(matching) + + local function start_server(server) + vim.lsp.enable(server) + end + + if #matching == 0 then + vim.notify("No LSP server installed for filetype: " .. ft, vim.log.levels.WARN) + elseif #matching == 1 then + start_server(matching[1]) + else + vim.ui.select(matching, { + prompt = "Select LSP server:", + }, function(choice) + if choice then + start_server(choice) + end + end) + end + end + + -- LSP keybindings + require("which-key").add({ + { "cs", group = "LSP Commands" }, + { "cf", function() vim.lsp.buf.format() end, desc = "Code Format" }, + { "csi", ":checkhealth vim.lsp", desc = "LSP Info" }, + { "csr", ":lsp restart", desc = "LSP Restart" }, + { "css", lsp_start_smart, desc = "LSP Start" }, + { "csx", ":lsp stop", desc = "LSP Stop" }, + }) + end + }, + + { + "taproot-wizards/bitcoin-script-hints.nvim", + dependencies = { "nvim-treesitter/nvim-treesitter" }, + config = function() + require("bitcoin-script-hints").setup() + end + } +} diff --git a/lua/plugins/lualine.lua b/lua/plugins/lualine.lua new file mode 100644 index 0000000..1c09d05 --- /dev/null +++ b/lua/plugins/lualine.lua @@ -0,0 +1,60 @@ +return { + { + "nvim-lualine/lualine.nvim", + dependencies = { + "nvim-tree/nvim-web-devicons", + "SmiteshP/nvim-navic" + }, + config = function() + require("lualine").setup ({ + options = { + icons_enabled = true, + theme = 'material', + component_separators = { left = '>', right = '|'}, + section_separators = { left = '', right = ''}, + disabled_filetypes = { + statusline = {}, + winbar = {}, + }, + ignore_focus = {}, + always_divide_middle = true, + globalstatus = true, + refresh = { + statusline = 1000, + tabline = 1000, + winbar = 1000, + } + }, + sections = { + lualine_a = {'mode'}, + lualine_b = {'branch', 'diff', 'diagnostics'}, + lualine_c = { + {'filename'}, + { function() return require("nvim-navic").get_location() end, cond = function() + return require("nvim-navic").is_available() + end, + }, + }, + lualine_x = {'filetype'}, + lualine_y = {'progress'}, + lualine_z = {'location'} + }, + inactive_sections = { + lualine_a = {}, + lualine_b = {}, + lualine_c = {}, + lualine_x = {}, + lualine_y = {}, + lualine_z = {} + }, + tabline = {}, + winbar = {}, + inactive_winbar = {}, + extensions = { + 'lazy', + 'neo-tree', + } + }) + end + } +} diff --git a/lua/plugins/neotree.lua b/lua/plugins/neotree.lua new file mode 100644 index 0000000..c7de716 --- /dev/null +++ b/lua/plugins/neotree.lua @@ -0,0 +1,102 @@ +return { + { + "nvim-neo-tree/neo-tree.nvim", + dependencies = { + "nvim-lua/plenary.nvim", + "nvim-tree/nvim-web-devicons", -- not strictly required, but recommended + "MunifTanjim/nui.nvim", + }, + config = function() + require("neo-tree").setup({ + enable_diagnostics = false, + default_component_configs = { + git_status = { + symbols = { + added = "+", + modified = "~", + deleted = "-", + renamed = ">", + untracked = "?", + ignored = "!", + unstaged = "U", + staged = "S", + conflict = "C", + }, + }, + }, + window = { + position = "left", + width = 20, + }, + event_handlers = { + { + event = "neo_tree_window_after_open", + handler = function() + local win = vim.api.nvim_get_current_win() + vim.wo[win].winfixwidth = true + vim.wo[win].winfixbuf = true + vim.wo[win].cursorline = true + end + }, + }, + }) + + -- Keep the selected entry readable without a solid row background. + vim.api.nvim_set_hl(0, "NeoTreeCursorLine", { bg = "NONE", fg = "#a6e3a1" }) + + -- Apply highlight and re-apply on colorscheme change + vim.api.nvim_create_autocmd({ "FileType", "ColorScheme" }, { + pattern = { "neo-tree", "*" }, + callback = function(ev) + if ev.event == "ColorScheme" then + vim.api.nvim_set_hl(0, "NeoTreeCursorLine", { bg = "NONE", fg = "#a6e3a1" }) + end + local win = vim.api.nvim_get_current_win() + local buf = vim.api.nvim_win_get_buf(win) + if vim.bo[buf].filetype == "neo-tree" then + vim.wo[win].winhighlight = "CursorLine:NeoTreeCursorLine" + end + end, + }) + + -- Lock cursor to leftmost column in neo-tree + vim.api.nvim_create_autocmd("CursorMoved", { + pattern = "neo-tree*", + callback = function() + local col = vim.api.nvim_win_get_cursor(0)[2] + if col ~= 0 then + vim.api.nvim_win_set_cursor(0, { vim.api.nvim_win_get_cursor(0)[1], 0 }) + end + end, + }) + + local function toggle_neotree() + local api = vim.api + local bufs = api.nvim_list_bufs() + + for _, buf in ipairs(bufs) do + local name = api.nvim_buf_get_name(buf) + if name:match("neo%-tree filesystem") then + vim.cmd("Neotree close") + return + end + end + + vim.cmd("Neotree") + end + + require("which-key").add({ + { "e", toggle_neotree, desc = "File Explorer" } + }) + + --vim.fn.sign_define("DiagnosticSignError", + -- {text = " ", texthl = "DiagnosticSignError"}) + --vim.fn.sign_define("DiagnosticSignWarn", + -- {text = " ", texthl = "DiagnosticSignWarn"}) + --vim.fn.sign_define("DiagnosticSignInfo", + -- {text = " ", texthl = "DiagnosticSignInfo"}) + --vim.fn.sign_define("DiagnosticSignHint", + -- {text = "󰌵", texthl = "DiagnosticSignHint"}) + end, + }, +} diff --git a/lua/plugins/telescope.lua b/lua/plugins/telescope.lua new file mode 100644 index 0000000..06d1e0c --- /dev/null +++ b/lua/plugins/telescope.lua @@ -0,0 +1,62 @@ +local function get_root() + local result = vim.system({ "git", "rev-parse", "--show-toplevel" }, { text = true }):wait() + if result.code == 0 and result.stdout then + local git_dir = vim.trim(result.stdout) + if git_dir ~= "" then + return git_dir + end + end + + return vim.fn.getcwd() +end + +return { + { + "nvim-telescope/telescope.nvim", + branch = '0.1.x', + dependencies = { + { 'nvim-lua/plenary.nvim' }, + { 'nvim-tree/nvim-web-devicons' } + }, + config = function() + -- Custom Telescope command to grep from Git root + require("which-key").add({ + { "/", function() + require('telescope.builtin').live_grep({ cwd = get_root() }) + end, + desc = "grep" }, + { "ff", function() + require('telescope.builtin').find_files({ cwd = get_root() }) + end, + desc = "Search for Files" }, + { "fp", ":Telescope oldfiles", desc = "Oldfiles" }, + { "?", ":Telescope command_history", desc = "Command History" }, + { "cm", ":Telescope man_pages", desc = "Manpages" }, + + -- Code + { "gd", + function() + local attached = vim.lsp.get_clients({ bufnr = 0 }) + if next(attached) ~= nil then + require('telescope.builtin').lsp_definitions() + else + vim.api.nvim_feedkeys("gd", "n", false) + end + end, + mode = "n", + desc = "Go to Definition" + }, + { "gd", ":Telescope lsp_definitions", desc = "Go to Definition" }, + { "gr", ":Telescope lsp_references", desc = "Goto References" }, + { "gi", ":Telescope lsp_implementations", desc = "Go to Implementations" }, + { "gt", ":Telescope lsp_type_definitions", desc = "Go to Type Definition" }, + { "cv", ":Telescope treesitter", desc = "Function Names & Variables" }, + { "cd", ":Telescope diagnostics", desc = "Code Diagnostics" }, + + -- Git + { "Gt", ":Telescope git_branches", desc = "Git Branches" }, + { "Gc", ":Telescope git_commits", desc = "Git Commits" }, + }) + end + } +} diff --git a/lua/plugins/which-key.lua b/lua/plugins/which-key.lua new file mode 100644 index 0000000..94aac0a --- /dev/null +++ b/lua/plugins/which-key.lua @@ -0,0 +1,89 @@ +return { + { + "folke/which-key.nvim", + event = "VeryLazy", + opts = { + spec = { + { "l", ":Lazy", desc = "Lazy" }, + { "t", + function() + vim.cmd.botright("new") + vim.opt_local.number = false + vim.opt_local.relativenumber = false + vim.cmd.resize(10) + vim.cmd.terminal() + vim.cmd.startinsert() + end, + mode = "n", + desc = "Open Terminal" + }, + + --{ "wd", "execute 'bd' | execute 'close'", desc = "Delete window & buffer" }, + -- Window & Buffer Management + { "w", group = "Windows"}, + { "wc", ":close", desc = "Close Window" }, + { "ws", ":split", desc = "Horizontal Window Split" }, + { "wv", ":vsplit", desc = "Vertial Window Split" }, + { "wm", "_", desc = "Maximize Window" }, + + { "b", group = "Buffers"}, + { "bd", function() + local function is_neotree(bufnr) + return vim.bo[bufnr].filetype == "neo-tree" + end + + local current_buf = vim.api.nvim_get_current_buf() + + -- Skip if in neo-tree + if is_neotree(current_buf) then + vim.notify("Cannot delete buffer from neo-tree", vim.log.levels.WARN) + return + end + local buflisted = vim.fn.getbufinfo({ buflisted = 1 }) + -- Prevent deleting last buffer + if #buflisted <= 1 then + vim.notify("Cannot delete last buffer", vim.log.levels.WARN) + return + end + vim.cmd.bprevious() + vim.cmd.bdelete({ args = { tostring(current_buf) } }) + -- If we ended up in neo-tree, move back to a regular window + local new_buf = vim.api.nvim_get_current_buf() + if is_neotree(new_buf) then + vim.cmd.wincmd("l") + end + end, desc = "Delete Buffer" }, + { "bD", function() + local current_buf = vim.api.nvim_get_current_buf() + local current_win = vim.api.nvim_get_current_win() + + if vim.bo[current_buf].filetype == "neo-tree" then + vim.notify("Cannot delete neo-tree buffer", vim.log.levels.WARN) + return + end + + local wins = vim.fn.win_findbuf(current_buf) + if #wins > 1 then + vim.api.nvim_win_close(current_win, false) + end + + if vim.api.nvim_buf_is_valid(current_buf) then + vim.cmd('bdelete! ' .. current_buf) + end + end, desc = "Delete Window & Buffer" }, + + { "ca", vim.lsp.buf.code_action, desc = "Code Action" }, + { "cr", vim.lsp.buf.rename, desc = "Rename Variable" }, + { "ch", vim.lsp.buf.hover, desc = "Hover Info" }, + { "ce", vim.diagnostic.open_float, desc = "Show Diagnostic" }, + { "]d", vim.diagnostic.goto_next, desc = "Next Diagnostic" }, + { "[d", vim.diagnostic.goto_prev, desc = "Prev Diagnostic" }, + + { "G", group = "Git"}, + { "f", group = "Files"}, + { "c", group = "Code"}, + { "g", group = "Goto"}, + }, + }, + } +}