commit a67da8c917b4f52d74a050c403fdd1e6277c45c5 Author: ulve Date: Wed Jun 3 05:23:07 2026 +0200 semi ok diff --git a/config/dottily/.luacheckrc b/config/dottily/.luacheckrc new file mode 100644 index 0000000..653f105 --- /dev/null +++ b/config/dottily/.luacheckrc @@ -0,0 +1 @@ +globals = { "vim" } diff --git a/config/dottily/init.lua b/config/dottily/init.lua new file mode 100644 index 0000000..11f55d4 --- /dev/null +++ b/config/dottily/init.lua @@ -0,0 +1,461 @@ +-- +-- CONFIGS +-- +vim.opt.termguicolors = true -- enable 24-bit RGB colors in the terminal + +-- Line numbers +vim.opt.number = true -- show absolute line number on current line +vim.opt.relativenumber = true -- show relative line numbers for all other lines +vim.opt.cursorline = true -- highlight the row the cursor is on +vim.opt.wrap = false -- do not wrap long lines +vim.opt.scrolloff = 10 -- keep at least 10 lines above/below the cursor +vim.opt.sidescrolloff = 10 -- keep at least 10 columns left/right of the cursor + +-- Indentation: 2-space soft tabs +vim.opt.tabstop = 2 -- a counts as 2 columns +vim.opt.shiftwidth = 2 -- >> / << shift by 2 columns +vim.opt.softtabstop = 2 -- in insert mode inserts 2 spaces +vim.opt.expandtab = true -- insert spaces instead of tab characters +vim.opt.smartindent = true +vim.opt.autoindent = true + +-- Search +vim.opt.ignorecase = true -- case-insensitive search by default +vim.opt.smartcase = true -- switch to case-sensitive when query contains an uppercase letter +vim.opt.hlsearch = true -- highlight all matches +vim.opt.incsearch = true -- show matches while typing the query + +-- UI +vim.opt.showmatch = true -- briefly jump to matching bracket when one is inserted +vim.opt.cmdheight = 1 -- always show the command line (1 row) +vim.opt.showmode = false -- hide "-- INSERT --" etc.; the statusline shows the mode instead +vim.opt.pumheight = 10 -- limit the completion popup to 10 items +vim.opt.pumblend = 10 -- make the completion popup slightly transparent +vim.opt.winblend = 0 -- floating windows are fully opaque +vim.opt.conceallevel = 0 -- never hide markup characters (e.g. Markdown asterisks) +vim.opt.concealcursor = "" -- never hide markup on the cursor line either +vim.opt.synmaxcol = 300 -- stop syntax highlighting for columns beyond 300 (perf guard) +vim.opt.fillchars = { eob = " " } -- replace the "~" placeholder on empty lines with a space + +-- Persistent undo: survive across sessions even after the buffer is closed +local undodir = vim.fn.expand("~/.vim/undodir") +if + vim.fn.isdirectory(undodir) == 0 -- create undodir if nonexistent +then + vim.fn.mkdir(undodir, "p") +end + +vim.opt.backup = false -- do not create a backup file +vim.opt.writebackup = false -- do not keep a backup while overwriting a file +vim.opt.swapfile = false -- do not create a swapfile +vim.opt.undofile = true -- persist undo history to disk +vim.opt.undodir = undodir -- where to store undo files +vim.opt.updatetime = 300 -- write swap file (and trigger CursorHold) after 300 ms of inactivity +vim.opt.timeoutlen = 500 -- wait 500 ms for a mapped key sequence to complete +vim.opt.ttimeoutlen = 50 -- wait 50 ms for a terminal key code to complete +vim.opt.autoread = true -- reload a file changed outside Neovim without prompting +vim.opt.autowrite = false -- do not silently save on :make / buffer switch + +-- Buffer and editing behaviour +vim.opt.hidden = true -- allow switching away from an unsaved buffer without closing it +vim.opt.errorbells = false -- disable the error bell +vim.opt.backspace = "indent,eol,start" -- allow backspace over auto-indent, line breaks, and insert start +vim.opt.autochdir = false -- keep the working directory fixed (don't follow the open file) +vim.opt.iskeyword:append("-") -- treat "foo-bar" as a single word for motions like w/b +vim.opt.path:append("**") -- make :find search recursively into subdirectories +vim.opt.selection = "inclusive" -- visual selection includes the character under the cursor +vim.opt.mouse = "a" -- enable mouse support in all modes +vim.opt.clipboard:append("unnamedplus") -- share the system clipboard (+ register) + +-- Over SSH, wl-clipboard is unavailable; use OSC 52 to write yanks to the local terminal clipboard. +if os.getenv("SSH_TTY") then + vim.g.clipboard = { + name = "OSC 52", + copy = { + ["+"] = require("vim.ui.clipboard.osc52").copy("+"), + ["*"] = require("vim.ui.clipboard.osc52").copy("*"), + }, + paste = { + ["+"] = require("vim.ui.clipboard.osc52").paste("+"), + ["*"] = require("vim.ui.clipboard.osc52").paste("*"), + }, + } +end +vim.opt.modifiable = true -- allow buffer modifications (safety default) +vim.opt.encoding = "utf-8" -- internal character encoding + +-- Folding: driven by Tree-sitter when available; foldlevel = 99 means all folds start open +vim.opt.foldmethod = "expr" +vim.opt.foldexpr = "v:lua.vim.treesitter.foldexpr()" +vim.opt.foldlevel = 99 -- a high value ensures every fold is open on buffer load + +-- Window splits +vim.opt.splitbelow = true -- :split opens the new window below +vim.opt.splitright = true -- :vsplit opens the new window to the right + +-- Miscellaneous +vim.opt.wildmenu = true -- enable enhanced command-line completion +vim.opt.wildmode = "longest:full,full" -- first Tab completes the longest common prefix and lists all; subsequent Tabs cycle +vim.opt.diffopt:append("linematch:60") -- use the improved diff algorithm for blocks up to 60 lines +vim.opt.redrawtime = 10000 -- allow up to 10 s for a full screen redraw (useful for large files) +vim.opt.maxmempattern = 20000 -- raise the memory cap for pattern matching (avoids "pattern too complex" errors) + +-- +-- KEYMAPS +-- +vim.g.mapleader = " " -- as the leader key +vim.g.maplocalleader = " " -- as the local leader key + +-- j/k navigate display lines when there is no count prefix, so wrapped lines +-- feel natural. With a count (e.g. 5j) they use real lines so relative jumps work. +vim.keymap.set("n", "j", function() + return vim.v.count == 0 and "gj" or "j" +end, { expr = true, silent = true, desc = "Down (wrap-aware)" }) +vim.keymap.set("n", "k", function() + return vim.v.count == 0 and "gk" or "k" +end, { expr = true, silent = true, desc = "Up (wrap-aware)" }) + +-- Keep search results and half-page jumps vertically centred +vim.keymap.set("n", "n", "nzzzv", { desc = "Next search result (centered)" }) +vim.keymap.set("n", "N", "Nzzzv", { desc = "Previous search result (centered)" }) +vim.keymap.set("n", "", "zz", { desc = "Half page down (centered)" }) +vim.keymap.set("n", "", "zz", { desc = "Half page up (centered)" }) + +-- Window navigation without the prefix +vim.keymap.set("n", "", "h", { desc = "Move to left window" }) +vim.keymap.set("n", "", "j", { desc = "Move to bottom window" }) +vim.keymap.set("n", "", "k", { desc = "Move to top window" }) +vim.keymap.set("n", "", "l", { desc = "Move to right window" }) + +-- Re-select visual block after indent so you can keep indenting with < or > +vim.keymap.set("v", "<", "", ">gv", { desc = "Indent right and reselect" }) + +-- J joins lines but restores the cursor position (mz saves, `z restores) +vim.keymap.set("n", "J", "mzJ`z", { desc = "Join lines and keep cursor position" }) + +vim.keymap.set("n", "H", "bprev", { desc = "Previous buffer" }) +vim.keymap.set("n", "L", "bnext", { desc = "Next buffer" }) + +-- Toggle LSP diagnostics on/off for the current session +vim.keymap.set("n", "td", function() + vim.diagnostic.enable(not vim.diagnostic.is_enabled()) +end, { desc = "Toggle diagnostics" }) + +-- Toggle inline virtual text only (keeps underlines and signs) +vim.keymap.set("n", "tv", function() + local current = vim.diagnostic.config().virtual_text + vim.diagnostic.config({ virtual_text = not current }) +end, { desc = "Toggle virtual text" }) + +-- +-- AUTOCOMMANDS +-- +local augroup = vim.api.nvim_create_augroup("UserConfig", { clear = true }) + +-- Format on save via the EFM language server. +-- Only runs when: the buffer is a real file, it is modifiable, it has a name, +-- and efm is attached. This avoids spurious save prompts on scratch buffers. +vim.api.nvim_create_autocmd("BufWritePre", { + group = augroup, + pattern = { + "*.cs", + "*.lua", + "*.py", + "*.go", + "*.js", + "*.jsx", + "*.ts", + "*.tsx", + "*.json", + "*.css", + "*.scss", + "*.html", + "*.sh", + "*.bash", + }, + callback = function(args) + -- avoid formatting non-file buffers (helps prevent weird write prompts) + if vim.bo[args.buf].buftype ~= "" then + return + end + if not vim.bo[args.buf].modifiable then + return + end + if vim.api.nvim_buf_get_name(args.buf) == "" then + return + end + + local has_efm = false + for _, c in ipairs(vim.lsp.get_clients({ bufnr = args.buf })) do + if c.name == "efm" then + has_efm = true + break + end + end + if not has_efm then + return + end + + pcall(vim.lsp.buf.format, { + bufnr = args.buf, + timeout_ms = 2000, + filter = function(c) + return c.name == "efm" + end, + }) + end, +}) + +-- Flash the yanked region briefly so it is clear what was copied +vim.api.nvim_create_autocmd("TextYankPost", { + group = augroup, + callback = function() + vim.hl.on_yank() + end, +}) + +-- Restore the cursor to where it was when the file was last closed +vim.api.nvim_create_autocmd("BufReadPost", { + group = augroup, + desc = "Restore last cursor position", + callback = function() + if vim.o.diff then -- except in diff mode + return + end + + local last_pos = vim.api.nvim_buf_get_mark(0, '"') -- {line, col} + local last_line = vim.api.nvim_buf_line_count(0) + + local row = last_pos[1] + if row < 1 or row > last_line then + return + end + + pcall(vim.api.nvim_win_set_cursor, 0, last_pos) + end, +}) + +-- Prose-friendly settings for Markdown, plain text, and commit messages +vim.api.nvim_create_autocmd("FileType", { + group = augroup, + pattern = { "markdown", "text", "gitcommit" }, + callback = function() + vim.opt_local.wrap = true -- soft-wrap at window edge + vim.opt_local.linebreak = true -- break at word boundaries, not mid-word + vim.opt_local.spell = true -- enable spell-checking + end, +}) + +-- +-- PLUGINS +-- +vim.pack.add({ + "https://github.com/folke/which-key.nvim", -- shows pending keymap hints + "https://github.com/folke/tokyonight.nvim", -- colorscheme + "https://github.com/nvim-lualine/lualine.nvim", -- statusline + "https://www.github.com/lewis6991/gitsigns.nvim", -- git hunk signs in the gutter + "https://www.github.com/echasnovski/mini.nvim", -- collection of small plugins + "https://www.github.com/ibhagwan/fzf-lua", -- fuzzy finder (files, grep, LSP, …) + "https://www.github.com/neovim/nvim-lspconfig", -- provides cmd/root_dir/filetypes for each LSP server + "https://github.com/mason-org/mason.nvim", -- LSP / tool installer UI + "https://github.com/creativenull/efmls-configs-nvim", -- pre-built efm-langserver tool configs + "https://github.com/WhoIsSethDaniel/mason-tool-installer.nvim", -- auto-install Mason tools on startup + "https://github.com/folke/flash.nvim", -- enhanced f/t/s jumps with labels + "https://github.com/L3MON4D3/LuaSnip", -- snippet engine (used by blink.cmp) + { + src = "https://github.com/saghen/blink.cmp", -- completion engine + version = vim.version.range("1.*"), -- pin to any 1.x release + }, + { + src = "https://github.com/nvim-treesitter/nvim-treesitter", + branch = "main", + build = ":TSUpdate", -- keep parser binaries up to date after install/update + }, + "https://github.com/nvim-treesitter/nvim-treesitter-textobjects", + "https://github.com/HakonHarnes/img-clip.nvim", -- paste images from clipboard (no Python required) +}) + +-- +-- PLUGIN CONFIGS +-- +require("lualine").setup() + +vim.cmd.colorscheme("tokyonight") + +-- which-key: shows a popup of available keymaps after a delay. +-- Groups give the prefixes human-readable titles in the popup. +require("which-key").setup({}) +require("which-key").add({ + { "f", group = "Find" }, -- fzf-lua pickers (files, grep, LSP symbols, …) + { "g", group = "Go to" }, -- LSP navigation (definition, split, …) + { "c", group = "Code" }, -- code actions + { "r", group = "Refactor" }, -- rename, extract, … + { "t", group = "Toggle" }, -- toggle settings (diagnostics, …) + { "o", group = "Organize" }, -- organize imports, sort, … + { "d", group = "Diagnostics" }, -- diagnostic float / list + { "n", group = "Next" }, -- jump to next item + { "p", group = "Prev" }, -- jump to previous item + { "]m", hidden = true }, + { "[m", hidden = true }, + { "]M", hidden = true }, + { "[M", hidden = true }, +}) + +-- Tree-sitter: start the parser for a filetype only when its grammar is +-- already installed. Missing grammars are installed asynchronously on first +-- launch so the editor never blocks at startup. +local setup_treesitter = function() + local treesitter = require("nvim-treesitter") + treesitter.setup({}) + local ensure_installed = { + "vim", + "vimdoc", + "go", + "html", + "css", + "javascript", + "json", + "lua", + "markdown", + "python", + "typescript", + "bash", + "c_sharp", + } + + local config = require("nvim-treesitter.config") + + -- Only install parsers that are not already present + local already_installed = config.get_installed() + local parsers_to_install = {} + + for _, parser in ipairs(ensure_installed) do + if not vim.tbl_contains(already_installed, parser) then + table.insert(parsers_to_install, parser) + end + end + + if #parsers_to_install > 0 then + treesitter.install(parsers_to_install) + end + + -- Start the Tree-sitter parser whenever a supported filetype is opened. + -- Using an autocmd instead of the built-in auto_install avoids the + -- blocking install prompt on first open. + local group = vim.api.nvim_create_augroup("TreeSitterConfig", { clear = true }) + vim.api.nvim_create_autocmd("FileType", { + group = group, + callback = function(args) + if vim.list_contains(treesitter.get_installed(), vim.treesitter.language.get_lang(args.match)) then + vim.treesitter.start(args.buf) + end + end, + }) +end + +setup_treesitter() + +require("nvim-treesitter-textobjects").setup({ move = { set_jumps = true } }) + +local move = require("nvim-treesitter-textobjects.move") +vim.keymap.set({ "n", "x", "o" }, "]f", function() + move.goto_next_start("@function.outer", "textobjects") +end, { desc = "Next function" }) +vim.keymap.set({ "n", "x", "o" }, "[f", function() + move.goto_previous_start("@function.outer", "textobjects") +end, { desc = "Prev function" }) +vim.keymap.set({ "n", "x", "o" }, "]c", function() + move.goto_next_start("@class.outer", "textobjects") +end, { desc = "Next class" }) +vim.keymap.set({ "n", "x", "o" }, "[c", function() + move.goto_previous_start("@class.outer", "textobjects") +end, { desc = "Prev class" }) + +-- fzf-lua: fuzzy finder keymaps +require("fzf-lua").setup({ + files = { + actions = { + ["ctrl-h"] = { require("fzf-lua").actions.toggle_hidden }, + }, + }, +}) +vim.keymap.set("n", "", function() + require("fzf-lua").files() +end, { desc = "Find files" }) +vim.keymap.set("n", "fg", function() + require("fzf-lua").live_grep() +end, { desc = "FZF Live Grep" }) +vim.keymap.set("n", "fb", function() + require("fzf-lua").buffers() +end, { desc = "FZF Buffers" }) +vim.keymap.set("n", "fh", function() + require("fzf-lua").help_tags() +end, { desc = "FZF Help Tags" }) +vim.keymap.set("n", "fx", function() + require("fzf-lua").diagnostics_document() +end, { desc = "FZF Diagnostics Document" }) +vim.keymap.set("n", "fX", function() + require("fzf-lua").diagnostics_workspace() +end, { desc = "FZF Diagnostics Workspace" }) + +-- mini.nvim: a suite of small, focused plugins. Each module is opted in explicitly. +require("mini.ai").setup({}) -- extended text objects (e.g. cia = inside any argument) +require("mini.comment").setup({}) -- gcc / gc to toggle comments +require("mini.move").setup({}) -- move lines/selections with Option+hjkl (macOS Option = Alt) +require("mini.surround").setup({}) -- sa/sd/sr to add/delete/replace surrounding chars +require("mini.cursorword").setup({}) -- highlight all occurrences of the word under the cursor +require("mini.indentscope").setup({}) -- animated indent-scope indicator line +require("mini.pairs").setup({}) -- auto-close brackets, quotes, etc. +require("mini.trailspace").setup({}) -- highlight and trim trailing whitespace +require("mini.notify").setup({}) -- non-blocking notification popups +require("mini.icons").setup({}) -- icon provider (replaces nvim-web-devicons calls) + +-- flash.nvim: label-based jumps for f/t and anywhere on screen +require("flash").setup({}) +vim.keymap.set({ "n", "x", "o" }, "s", function() + require("flash").jump() +end, { desc = "Flash jump" }) +vim.keymap.set({ "n", "x", "o" }, "S", function() + require("flash").treesitter() +end, { desc = "Flash treesitter" }) +vim.keymap.set("o", "r", function() + require("flash").remote() +end, { desc = "Flash remote" }) +vim.keymap.set({ "o", "x" }, "R", function() + require("flash").treesitter_search() +end, { desc = "Flash treesitter search" }) + +-- img-clip: paste images from clipboard into markdown files (no Python required) +require("img-clip").setup({ + default = { + use_absolute_path = false, + relative_to_current_file = true, + file_name = function() + return os.date("%Y-%m-%d_%H-%M-%S") + end, + }, + filetypes = { + markdown = { template = "![]($FILE_PATH)" }, + }, +}) +vim.keymap.set({ "n", "x" }, "pi", "PasteImage", { desc = "Paste image" }) + +-- gitsigns: decorates the sign column with added/changed/removed hunk markers +require("gitsigns").setup({ + signs = { + add = { text = "\u{2590}" }, -- ▏ + change = { text = "\u{2590}" }, -- ▐ + delete = { text = "\u{2590}" }, -- ◦ + topdelete = { text = "\u{25e6}" }, -- ◦ + changedelete = { text = "\u{25cf}" }, -- ● + untracked = { text = "\u{25cb}" }, -- ○ + }, + signcolumn = true, + current_line_blame = false, -- set to true to show inline git blame on the cursor line +}) + +-- +-- LSP - Too anoying to have in this file +-- +require("lsp-nix") diff --git a/config/dottily/lua/lsp-nix.lua b/config/dottily/lua/lsp-nix.lua new file mode 100644 index 0000000..fc8846e --- /dev/null +++ b/config/dottily/lua/lsp-nix.lua @@ -0,0 +1,211 @@ +local augroup = vim.api.nvim_create_augroup("UserConfig", { clear = false }) + +-- Diagnostic icons shown in the sign column and virtual text +local diagnostic_signs = { + Error = " ", + Warn = " ", + Hint = "", + Info = "", +} + +-- Global diagnostic display options +vim.diagnostic.config({ + virtual_text = { prefix = "●", spacing = 4 }, -- inline diagnostic text to the right + signs = { + text = { + [vim.diagnostic.severity.ERROR] = diagnostic_signs.Error, + [vim.diagnostic.severity.WARN] = diagnostic_signs.Warn, + [vim.diagnostic.severity.INFO] = diagnostic_signs.Info, + [vim.diagnostic.severity.HINT] = diagnostic_signs.Hint, + }, + }, + underline = true, + update_in_insert = false, -- do not update diagnostics while typing (reduces noise) + severity_sort = true, -- show errors before warnings in lists + float = { + border = "rounded", + source = "always", -- always show which LSP server produced the diagnostic + header = "", + prefix = "", + focusable = false, + style = "minimal", + }, +}) + +-- Patch vim.lsp.util.open_floating_preview so every LSP floating window +-- (hover, signature help, etc.) gets a rounded border without having to +-- configure it per-server. +do + local orig = vim.lsp.util.open_floating_preview + function vim.lsp.util.open_floating_preview(contents, syntax, opts, ...) + opts = opts or {} + opts.border = opts.border or "rounded" + return orig(contents, syntax, opts, ...) + end +end + +-- lsp_on_attach is called whenever an LSP server attaches to a buffer. +-- It sets up buffer-local keymaps that are only active when an LSP is present. +local function lsp_on_attach(ev) + local client = vim.lsp.get_client_by_id(ev.data.client_id) + if not client then + return + end + + local bufnr = ev.buf + local function map(key, fn, desc) + vim.keymap.set("n", key, fn, { noremap = true, silent = true, buffer = bufnr, desc = desc }) + end + + -- Navigation + map("gd", vim.lsp.buf.definition, "Go to definition") + map("gS", function() vim.cmd("vsplit") vim.lsp.buf.definition() end, "Go to definition (vsplit)") + + -- Code actions and refactoring + map("ca", vim.lsp.buf.code_action, "Code action") + map("rn", vim.lsp.buf.rename, "Rename symbol") + + -- Diagnostics + map("D", function() vim.diagnostic.open_float({ scope = "line" }) end, "Line diagnostics") + map("d", function() vim.diagnostic.open_float({ scope = "cursor" }) end, "Cursor diagnostic") + map("nd", function() vim.diagnostic.jump({ count = 1 }) end, "Next diagnostic") + map("pd", function() vim.diagnostic.jump({ count = -1 }) end, "Prev diagnostic") + + -- Hover documentation + map("K", vim.lsp.buf.hover, "Hover docs") + + -- fzf-lua LSP pickers + map("fd", function() require("fzf-lua").lsp_definitions({ jump_to_single_result = true }) end, "Definitions") + map("fr", function() require("fzf-lua").lsp_references() end, "References") + map("ft", function() require("fzf-lua").lsp_typedefs() end, "Type definitions") + map("fs", function() require("fzf-lua").lsp_document_symbols() end, "Document symbols") + map("fw", function() require("fzf-lua").lsp_workspace_symbols() end, "Workspace symbols") + map("fi", function() require("fzf-lua").lsp_implementations() end, "Implementations") + + -- Organise imports then format (only registered when the server supports codeAction) + if client:supports_method("textDocument/codeAction", bufnr) then + map("oi", function() + vim.lsp.buf.code_action({ + context = { only = { "source.organizeImports" }, diagnostics = {} }, + apply = true, + bufnr = bufnr, + }) + -- Small delay lets the import action settle before formatting + vim.defer_fn(function() + vim.lsp.buf.format({ bufnr = bufnr }) + end, 50) + end, "Organize imports") + end +end + +vim.api.nvim_create_autocmd("LspAttach", { group = augroup, callback = lsp_on_attach }) + +-- Global diagnostic keymaps (available without an LSP attached) +vim.keymap.set("n", "q", function() vim.diagnostic.setloclist({ open = true }) end, { desc = "Open diagnostic list" }) + +-- blink.cmp: completion engine wired to LuaSnip for snippet expansion +require("blink.cmp").setup({ + enabled = function() + return not vim.tbl_contains({ "markdown", "text" }, vim.bo.filetype) + end, + keymap = { + preset = "none", -- start from a blank slate; all bindings are explicit + [""] = { "show", "hide" }, -- toggle the completion menu + [""] = { "accept", "fallback" }, -- confirm selection + [""] = { "select_next", "fallback" }, + [""] = { "select_prev", "fallback" }, + [""] = { "snippet_forward", "fallback" }, -- jump to next snippet placeholder + [""] = { "snippet_backward", "fallback" }, -- jump to previous placeholder + }, + appearance = { nerd_font_variant = "mono" }, + completion = { menu = { auto_show = true } }, -- show the menu automatically (no manual trigger needed) + sources = { default = { "lsp", "path", "buffer", "snippets" } }, + snippets = { + -- Delegate snippet expansion to LuaSnip + expand = function(snippet) + require("luasnip").lsp_expand(snippet) + end, + }, + fuzzy = { + implementation = "prefer_rust", -- use the faster Rust fuzzy-matching backend when available + prebuilt_binaries = { download = true }, -- automatically download the pre-built binary + }, +}) + +-- Inject blink.cmp capabilities into every LSP server so they can offer +-- completions that respect snippet and LSP-specific completion item fields +vim.lsp.config["*"] = { + capabilities = require("blink.cmp").get_lsp_capabilities(), +} + +-- Server configurations +vim.lsp.config("lua_ls", { + settings = { + Lua = { + diagnostics = { globals = { "vim" } }, -- teach lua_ls that `vim` is a known global + telemetry = { enable = false }, + }, + }, +}) +vim.lsp.config("pyright", {}) -- Python type checker and language server +vim.lsp.config("bashls", {}) -- Bash language server +vim.lsp.config("vtsls", {}) -- TypeScript / JavaScript language server (VS Code's TS server) +vim.lsp.config("gopls", {}) -- Go language server +vim.lsp.config("omnisharp", {}) -- C# language server + +-- EFM (efm-langserver): a generic LSP adapter that runs command-line linters +-- and formatters and exposes them as LSP capabilities. +do + local luacheck = require("efmls-configs.linters.luacheck") + local stylua = require("efmls-configs.formatters.stylua") + local flake8 = require("efmls-configs.linters.flake8") + local black = require("efmls-configs.formatters.black") + local prettier_d = require("efmls-configs.formatters.prettier_d") + local eslint_d = require("efmls-configs.linters.eslint_d") + local fixjson = require("efmls-configs.formatters.fixjson") + local shellcheck = require("efmls-configs.linters.shellcheck") + local shfmt = require("efmls-configs.formatters.shfmt") + local go_revive = require("efmls-configs.linters.go_revive") + local gofumpt = require("efmls-configs.formatters.gofumpt") + local csharpier = { formatCommand = "dotnet csharpier --write-stdout", formatStdin = true } + + vim.lsp.config("efm", { + filetypes = { + "cs", "css", "go", "html", + "javascript", "javascriptreact", + "json", "jsonc", + "lua", "markdown", "python", "sh", + "typescript", "typescriptreact", + }, + init_options = { documentFormatting = true }, + settings = { + languages = { + cs = { csharpier }, + go = { gofumpt, go_revive }, + css = { prettier_d }, + html = { prettier_d }, + javascript = { eslint_d, prettier_d }, + javascriptreact = { eslint_d, prettier_d }, + json = { eslint_d, fixjson }, + jsonc = { eslint_d, fixjson }, + lua = { luacheck, stylua }, + markdown = { prettier_d }, + python = { flake8, black }, + sh = { shellcheck, shfmt }, + typescript = { eslint_d, prettier_d }, + typescriptreact = { eslint_d, prettier_d }, + }, + }, + }) +end + +-- Start all configured LSP servers +vim.lsp.enable({ + "lua_ls", + "pyright", + "bashls", + "vtsls", + "gopls", + "omnisharp", + "efm", +}) diff --git a/config/dottily/lua/lsp.lua b/config/dottily/lua/lsp.lua new file mode 100644 index 0000000..89b7fe6 --- /dev/null +++ b/config/dottily/lua/lsp.lua @@ -0,0 +1,242 @@ +-- mason: GUI for installing LSP servers, DAPs, linters and formatters +require("mason").setup({}) + +-- mason-tool-installer: automatically install all required tools on startup +require("mason-tool-installer").setup({ + ensure_installed = { + -- LSP servers + "lua-language-server", + "pyright", + "bash-language-server", + "vtsls", + "gopls", + "omnisharp", + "efm", + -- Formatters + "stylua", + "black", + "prettierd", + "shfmt", + "gofumpt", + "fixjson", + -- Linters + "flake8", + "eslint_d", + "shellcheck", + "revive", + }, + auto_update = false, + run_on_start = true, +}) + +local augroup = vim.api.nvim_create_augroup("UserConfig", { clear = false }) + +-- Diagnostic icons shown in the sign column and virtual text +local diagnostic_signs = { + Error = " ", + Warn = " ", + Hint = "", + Info = "", +} + +-- Global diagnostic display options +vim.diagnostic.config({ + virtual_text = { prefix = "●", spacing = 4 }, -- inline diagnostic text to the right + signs = { + text = { + [vim.diagnostic.severity.ERROR] = diagnostic_signs.Error, + [vim.diagnostic.severity.WARN] = diagnostic_signs.Warn, + [vim.diagnostic.severity.INFO] = diagnostic_signs.Info, + [vim.diagnostic.severity.HINT] = diagnostic_signs.Hint, + }, + }, + underline = true, + update_in_insert = false, -- do not update diagnostics while typing (reduces noise) + severity_sort = true, -- show errors before warnings in lists + float = { + border = "rounded", + source = "always", -- always show which LSP server produced the diagnostic + header = "", + prefix = "", + focusable = false, + style = "minimal", + }, +}) + +-- Patch vim.lsp.util.open_floating_preview so every LSP floating window +-- (hover, signature help, etc.) gets a rounded border without having to +-- configure it per-server. +do + local orig = vim.lsp.util.open_floating_preview + function vim.lsp.util.open_floating_preview(contents, syntax, opts, ...) + opts = opts or {} + opts.border = opts.border or "rounded" + return orig(contents, syntax, opts, ...) + end +end + +-- lsp_on_attach is called whenever an LSP server attaches to a buffer. +-- It sets up buffer-local keymaps that are only active when an LSP is present. +local function lsp_on_attach(ev) + local client = vim.lsp.get_client_by_id(ev.data.client_id) + if not client then + return + end + + local bufnr = ev.buf + local function map(key, fn, desc) + vim.keymap.set("n", key, fn, { noremap = true, silent = true, buffer = bufnr, desc = desc }) + end + + -- Navigation + map("gd", vim.lsp.buf.definition, "Go to definition") + map("gS", function() vim.cmd("vsplit") vim.lsp.buf.definition() end, "Go to definition (vsplit)") + + -- Code actions and refactoring + map("ca", vim.lsp.buf.code_action, "Code action") + map("rn", vim.lsp.buf.rename, "Rename symbol") + + -- Diagnostics + map("D", function() vim.diagnostic.open_float({ scope = "line" }) end, "Line diagnostics") + map("d", function() vim.diagnostic.open_float({ scope = "cursor" }) end, "Cursor diagnostic") + map("nd", function() vim.diagnostic.jump({ count = 1 }) end, "Next diagnostic") + map("pd", function() vim.diagnostic.jump({ count = -1 }) end, "Prev diagnostic") + + -- Hover documentation + map("K", vim.lsp.buf.hover, "Hover docs") + + -- fzf-lua LSP pickers + map("fd", function() require("fzf-lua").lsp_definitions({ jump_to_single_result = true }) end, "Definitions") + map("fr", function() require("fzf-lua").lsp_references() end, "References") + map("ft", function() require("fzf-lua").lsp_typedefs() end, "Type definitions") + map("fs", function() require("fzf-lua").lsp_document_symbols() end, "Document symbols") + map("fw", function() require("fzf-lua").lsp_workspace_symbols() end, "Workspace symbols") + map("fi", function() require("fzf-lua").lsp_implementations() end, "Implementations") + + -- Organise imports then format (only registered when the server supports codeAction) + if client:supports_method("textDocument/codeAction", bufnr) then + map("oi", function() + vim.lsp.buf.code_action({ + context = { only = { "source.organizeImports" }, diagnostics = {} }, + apply = true, + bufnr = bufnr, + }) + -- Small delay lets the import action settle before formatting + vim.defer_fn(function() + vim.lsp.buf.format({ bufnr = bufnr }) + end, 50) + end, "Organize imports") + end +end + +vim.api.nvim_create_autocmd("LspAttach", { group = augroup, callback = lsp_on_attach }) + +-- Global diagnostic keymaps (available without an LSP attached) +vim.keymap.set("n", "q", function() vim.diagnostic.setloclist({ open = true }) end, { desc = "Open diagnostic list" }) + +-- blink.cmp: completion engine wired to LuaSnip for snippet expansion +require("blink.cmp").setup({ + enabled = function() + return not vim.tbl_contains({ "markdown", "text" }, vim.bo.filetype) + end, + keymap = { + preset = "none", -- start from a blank slate; all bindings are explicit + [""] = { "show", "hide" }, -- toggle the completion menu + [""] = { "accept", "fallback" }, -- confirm selection + [""] = { "select_next", "fallback" }, + [""] = { "select_prev", "fallback" }, + [""] = { "snippet_forward", "fallback" }, -- jump to next snippet placeholder + [""] = { "snippet_backward", "fallback" }, -- jump to previous placeholder + }, + appearance = { nerd_font_variant = "mono" }, + completion = { menu = { auto_show = true } }, -- show the menu automatically (no manual trigger needed) + sources = { default = { "lsp", "path", "buffer", "snippets" } }, + snippets = { + -- Delegate snippet expansion to LuaSnip + expand = function(snippet) + require("luasnip").lsp_expand(snippet) + end, + }, + fuzzy = { + implementation = "prefer_rust", -- use the faster Rust fuzzy-matching backend when available + prebuilt_binaries = { download = true }, -- automatically download the pre-built binary + }, +}) + +-- Inject blink.cmp capabilities into every LSP server so they can offer +-- completions that respect snippet and LSP-specific completion item fields +vim.lsp.config["*"] = { + capabilities = require("blink.cmp").get_lsp_capabilities(), +} + +-- Server configurations +vim.lsp.config("lua_ls", { + settings = { + Lua = { + diagnostics = { globals = { "vim" } }, -- teach lua_ls that `vim` is a known global + telemetry = { enable = false }, + }, + }, +}) +vim.lsp.config("pyright", {}) -- Python type checker and language server +vim.lsp.config("bashls", {}) -- Bash language server +vim.lsp.config("vtsls", {}) -- TypeScript / JavaScript language server (VS Code's TS server) +vim.lsp.config("gopls", {}) -- Go language server +vim.lsp.config("omnisharp", {}) -- C# language server + +-- EFM (efm-langserver): a generic LSP adapter that runs command-line linters +-- and formatters and exposes them as LSP capabilities. +do + local luacheck = require("efmls-configs.linters.luacheck") + local stylua = require("efmls-configs.formatters.stylua") + local flake8 = require("efmls-configs.linters.flake8") + local black = require("efmls-configs.formatters.black") + local prettier_d = require("efmls-configs.formatters.prettier_d") + local eslint_d = require("efmls-configs.linters.eslint_d") + local fixjson = require("efmls-configs.formatters.fixjson") + local shellcheck = require("efmls-configs.linters.shellcheck") + local shfmt = require("efmls-configs.formatters.shfmt") + local go_revive = require("efmls-configs.linters.go_revive") + local gofumpt = require("efmls-configs.formatters.gofumpt") + local csharpier = { formatCommand = "dotnet csharpier --write-stdout", formatStdin = true } + + vim.lsp.config("efm", { + filetypes = { + "cs", "css", "go", "html", + "javascript", "javascriptreact", + "json", "jsonc", + "lua", "markdown", "python", "sh", + "typescript", "typescriptreact", + }, + init_options = { documentFormatting = true }, + settings = { + languages = { + cs = { csharpier }, + go = { gofumpt, go_revive }, + css = { prettier_d }, + html = { prettier_d }, + javascript = { eslint_d, prettier_d }, + javascriptreact = { eslint_d, prettier_d }, + json = { eslint_d, fixjson }, + jsonc = { eslint_d, fixjson }, + lua = { luacheck, stylua }, + markdown = { prettier_d }, + python = { flake8, black }, + sh = { shellcheck, shfmt }, + typescript = { eslint_d, prettier_d }, + typescriptreact = { eslint_d, prettier_d }, + }, + }, + }) +end + +-- Start all configured LSP servers +vim.lsp.enable({ + "lua_ls", + "pyright", + "bashls", + "vtsls", + "gopls", + "omnisharp", + "efm", +}) diff --git a/config/dottily/nvim-pack-lock.json b/config/dottily/nvim-pack-lock.json new file mode 100644 index 0000000..03f44a4 --- /dev/null +++ b/config/dottily/nvim-pack-lock.json @@ -0,0 +1,69 @@ +{ + "plugins": { + "LuaSnip": { + "rev": "a62e1083a3cfe8b6b206e7d3d33a51091df25357", + "src": "https://github.com/L3MON4D3/LuaSnip" + }, + "blink.cmp": { + "rev": "78336bc89ee5365633bcf754d93df01678b5c08f", + "src": "https://github.com/saghen/blink.cmp", + "version": "1.0.0 - 2.0.0" + }, + "efmls-configs-nvim": { + "rev": "5dc52088c231f2721f545570fcb541b04802ce6b", + "src": "https://github.com/creativenull/efmls-configs-nvim" + }, + "flash.nvim": { + "rev": "fcea7ff883235d9024dc41e638f164a450c14ca2", + "src": "https://github.com/folke/flash.nvim" + }, + "fzf-lua": { + "rev": "657c1bbb7357c61e26a20d868b53a460b05c18c0", + "src": "https://www.github.com/ibhagwan/fzf-lua" + }, + "gitsigns.nvim": { + "rev": "8d82c240f190fc33723d48c308ccc1ed8baad69d", + "src": "https://www.github.com/lewis6991/gitsigns.nvim" + }, + "img-clip.nvim": { + "rev": "b6ddfb97b5600d99afe3452d707444afda658aca", + "src": "https://github.com/HakonHarnes/img-clip.nvim" + }, + "lualine.nvim": { + "rev": "a905eeebc4e63fdc48b5135d3bf8aea5618fb21c", + "src": "https://github.com/nvim-lualine/lualine.nvim" + }, + "mason-tool-installer.nvim": { + "rev": "443f1ef8b5e6bf47045cb2217b6f748a223cf7dc", + "src": "https://github.com/WhoIsSethDaniel/mason-tool-installer.nvim" + }, + "mason.nvim": { + "rev": "b03fb0f20bc1d43daf558cda981a2be22e73ac42", + "src": "https://github.com/mason-org/mason.nvim" + }, + "mini.nvim": { + "rev": "4182d64727fb4876407af65c181b85428a4a70d1", + "src": "https://www.github.com/echasnovski/mini.nvim" + }, + "nvim-lspconfig": { + "rev": "cb5bc0b2b35a6d513e3298d285db81453e791f4f", + "src": "https://www.github.com/neovim/nvim-lspconfig" + }, + "nvim-treesitter": { + "rev": "4916d6592ede8c07973490d9322f187e07dfefac", + "src": "https://github.com/nvim-treesitter/nvim-treesitter" + }, + "nvim-treesitter-textobjects": { + "rev": "851e865342e5a4cb1ae23d31caf6e991e1c99f1e", + "src": "https://github.com/nvim-treesitter/nvim-treesitter-textobjects" + }, + "tokyonight.nvim": { + "rev": "cdc07ac78467a233fd62c493de29a17e0cf2b2b6", + "src": "https://github.com/folke/tokyonight.nvim" + }, + "which-key.nvim": { + "rev": "3aab2147e74890957785941f0c1ad87d0a44c15a", + "src": "https://github.com/folke/which-key.nvim" + } + } +} diff --git a/config/nvim/.gitignore b/config/nvim/.gitignore new file mode 100644 index 0000000..e033bc6 --- /dev/null +++ b/config/nvim/.gitignore @@ -0,0 +1 @@ +lazy-lock.json diff --git a/config/nvim/README.md b/config/nvim/README.md new file mode 100644 index 0000000..1779e97 --- /dev/null +++ b/config/nvim/README.md @@ -0,0 +1,117 @@ +# Neovim Keybinds Documentation + +This document provides a simple and organized overview of all the custom keybinds defined in my Neovim configuration. + +## General Keybinds + +| Mode | Key | Action | +|------|-----------------|---------------------------------------------------------------------------------------------| +| `n` | `cd` | Open Ex mode (`:Ex`) | +| `n` | `J` | Join lines while keeping the cursor in place | +| `n` | `` | Scroll half-page down and keep the cursor centered | +| `n` | `` | Scroll half-page up and keep the cursor centered | +| `n` | `n` | Move to next search result and keep it centered | +| `n` | `N` | Move to previous search result and keep it centered | +| `n` | `Q` | Disable Ex mode | +| `n` | `` | Jump to next quickfix entry and keep it centered | +| `n` | `` | Jump to previous quickfix entry and keep it centered | +| `n` | `k` | Jump to next location entry and keep it centered | +| `n` | `j` | Jump to previous location entry and keep it centered | +| `i` | `` | Exit insert mode (acts like `Esc`) | +| `n` | `x` | Make current file executable (`chmod +x`) | +| `n` | `u` | Toggle Undotree | +| `n` | `rl` | Reload the Neovim config (`~/.config/nvim/init.lua`) | +| `n` | `` | Source the current file (`:so`) | + +--- + +## Visual Mode Keybinds + +| Mode | Key | Action | +|------|-----------------|---------------------------------------------------------------------------------------------| +| `v` | `J` | Move selected block down | +| `v` | `K` | Move selected block up | +| `x` | `p` | Paste without overwriting clipboard | +| `v` | `y` | Yank into system clipboard (even on SSH) | + +--- + +## Linting and Formatting + +| Mode | Key | Action | +|------|-----------------|---------------------------------------------------------------------------------------------| +| `n` | `cc` | Run `php-cs-fixer` to lint and format PHP files | +| `n` | `` | Format code (`LSP`) | + +--- + +## Telescope Keybinds + +| Mode | Key | Action | +|------|-----------------|---------------------------------------------------------------------------------------------| +| `n` | `ff` | Find files | +| `n` | `fg` | Find git-tracked files | +| `n` | `fo` | Open recent files | +| `n` | `fq` | Open quickfix list | +| `n` | `fh` | Open help tags | +| `n` | `fb` | Open buffer list | +| `n` | `fs` | Grep current string | +| `n` | `fc` | Grep instances of the current file name without the extension | +| `n` | `fi` | Find files in Neovim configuration directory (`~/.config/nvim/`) | + +--- + +## Harpoon Integration + +| Mode | Key | Action | +|------|-----------------|---------------------------------------------------------------------------------------------| +| `n` | `a` | Add current file to Harpoon list | +| `n` | `` | Toggle Harpoon quick menu | +| `n` | `fl` | Open Harpoon window with Telescope | +| `n` | `` | Go to previous Harpoon mark | +| `n` | `` | Go to next Harpoon mark | + +--- + +## LSP Keybinds + +| Mode | Key | Action | +|-----------|------------|---------------------------------------------------------------------------------------------| +| `n` | `K` | Show hover information | +| `n` | `gd` | Go to definition | +| `n` | `gD` | Go to declaration | +| `n` | `gi` | Go to implementation | +| `n` | `go` | Go to type definition | +| `n` | `gr` | Show references | +| `n` | `gs` | Show signature help | +| `n` | `gl` | Show diagnostics in a floating window | +| `n` | `` | Rename symbol | +| `n`, `x` | `` | Format code asynchronously | +| `n` | `` | Show code actions | + +--- + +## Miscellaneous + +| Mode | Key | Action | +|------|-----------------|---------------------------------------------------------------------------------------------| +| `n` | `dg` | Run `DogeGenerate` (comment documentation generation) | +| `n` | `s` | Replace all instances of the word under the cursor on the current line | + +--- + +# LSP servers: + +I am migrating my lsp config to /lua/plugins/lsp.lua because nvim v0.11 allows a very minimal debloated way to setup language server protocols. + +Below is a running list of what and how to install the lsp's that are going to be configured in this build. I will avoid mason for now because I think its better to have full control over your system, and not outsource it to mason. Just uncommonet `return {` in /plugins/lsp.lua from the original lspconfig if you want to go that route. + +1. { lua-language-server } + - refer to distro ( pacman -Ss lua-language-server ) +2. { css-language-server --studio, html-language-server } + - npm install -g vscode-langservers-extracted +3. { intelephense } + - npm install -g intelephense +4. { typescript-language-server } + - npm install -g typescript-language-server typescript + diff --git a/config/nvim/after/ftplugin/goon.lua b/config/nvim/after/ftplugin/goon.lua new file mode 100644 index 0000000..2059624 --- /dev/null +++ b/config/nvim/after/ftplugin/goon.lua @@ -0,0 +1,2 @@ +vim.bo.commentstring = "// %s" +vim.bo.comments = "s:/*,m: *,ex:*/,://" diff --git a/config/nvim/after/ftplugin/hare.lua b/config/nvim/after/ftplugin/hare.lua new file mode 100644 index 0000000..539428b --- /dev/null +++ b/config/nvim/after/ftplugin/hare.lua @@ -0,0 +1,12 @@ +local set = vim.opt_local + +vim.bo.commentstring = "// %s" +vim.bo.comments = "s:/*,m: *,ex:*/,://" + +set.shiftwidth = 4 +set.tabstop = 4 +set.softtabstop = 4 +set.expandtab = true + +set.number = true +set.relativenumber = true diff --git a/config/nvim/after/ftplugin/jsonc.lua b/config/nvim/after/ftplugin/jsonc.lua new file mode 100644 index 0000000..e1833b8 --- /dev/null +++ b/config/nvim/after/ftplugin/jsonc.lua @@ -0,0 +1,9 @@ +local set = vim.opt_local + +set.shiftwidth = 2 +set.tabstop = 2 +set.softtabstop = 2 +set.expandtab = true + +set.number = true +set.relativenumber = true diff --git a/config/nvim/after/ftplugin/man.lua b/config/nvim/after/ftplugin/man.lua new file mode 100644 index 0000000..f21d675 --- /dev/null +++ b/config/nvim/after/ftplugin/man.lua @@ -0,0 +1,8 @@ +local set = vim.opt_local +set.number = true +set.relativenumber = true +set.number = true +set.relativenumber = false +set.wrap = true +set.linebreak = true +set.conceallevel = 0 diff --git a/config/nvim/after/ftplugin/nix.lua b/config/nvim/after/ftplugin/nix.lua new file mode 100644 index 0000000..6425eda --- /dev/null +++ b/config/nvim/after/ftplugin/nix.lua @@ -0,0 +1,8 @@ +local set = vim.opt_local + +set.shiftwidth = 2 +set.tabstop = 2 +set.softtabstop = 2 +set.expandtab = true +set.number = true +set.relativenumber = true diff --git a/config/nvim/after/plugin/colors.lua b/config/nvim/after/plugin/colors.lua new file mode 100644 index 0000000..e9af665 --- /dev/null +++ b/config/nvim/after/plugin/colors.lua @@ -0,0 +1,6 @@ +vim.cmd.colorscheme("tokyonight") +vim.cmd("hi Directory guibg=NONE") +vim.cmd("hi SignColumn guibg=NONE") +vim.api.nvim_set_hl(0, "Normal", { bg = "none" }) +vim.api.nvim_set_hl(0, "NormalFloat", { bg = "none" }) +vim.api.nvim_set_hl(0, "LineNr", { bg = "none" }) diff --git a/config/nvim/after/plugin/completion.lua b/config/nvim/after/plugin/completion.lua new file mode 100644 index 0000000..1b4c780 --- /dev/null +++ b/config/nvim/after/plugin/completion.lua @@ -0,0 +1,33 @@ +local cmp = require("cmp") +require("cmp_nvim_lsp").setup() +cmp.register_source("path", require("cmp_path").new()) +cmp.register_source("buffer", require("cmp_buffer")) + +cmp.setup({ + preselect = cmp.PreselectMode.Item, + completion = { + completeopt = "menu,menuone,noinsert", + autocomplete = { cmp.TriggerEvent.TextChanged }, + }, + window = { documentation = cmp.config.window.bordered() }, + mapping = cmp.mapping.preset.insert({ + [""] = cmp.mapping.confirm({ select = false }), + [""] = cmp.mapping.abort(), + [""] = cmp.mapping.complete(), + [""] = cmp.mapping.select_next_item({ behavior = cmp.SelectBehavior.Select }), + [""] = cmp.mapping.select_prev_item({ behavior = cmp.SelectBehavior.Select }), + [""] = cmp.mapping.scroll_docs(4), + [""] = cmp.mapping.scroll_docs(-4), + [""] = cmp.mapping(function(fallback) + if cmp.visible() then cmp.select_next_item() else fallback() end + end, { "i", "s" }), + [""] = cmp.mapping(function() + if cmp.visible() then cmp.select_prev_item() end + end, { "i", "s" }), + }), + sources = { + { name = "nvim_lsp" }, + { name = "path" }, + { name = "buffer", keyword_length = 3 }, + }, +}) diff --git a/config/nvim/after/plugin/harpoon.lua b/config/nvim/after/plugin/harpoon.lua new file mode 100644 index 0000000..cb0ddfc --- /dev/null +++ b/config/nvim/after/plugin/harpoon.lua @@ -0,0 +1,21 @@ +local harpoon = require("harpoon") +harpoon:setup() + +vim.keymap.set("n", "a", function() harpoon:list():add() end) +vim.keymap.set("n", "", function() harpoon.ui:toggle_quick_menu(harpoon:list()) end) +vim.keymap.set("n", "", function() harpoon:list():prev() end) +vim.keymap.set("n", "", function() harpoon:list():next() end) + +vim.keymap.set("n", "fl", function() + local conf = require("telescope.config").values + local themes = require("telescope.themes") + local file_paths = {} + for _, item in ipairs(harpoon:list().items) do + table.insert(file_paths, item.value) + end + require("telescope.pickers").new(themes.get_ivy({ prompt_title = "Working List" }), { + finder = require("telescope.finders").new_table({ results = file_paths }), + previewer = conf.file_previewer({}), + sorter = conf.generic_sorter({}), + }):find() +end, { desc = "Open harpoon window" }) diff --git a/config/nvim/after/plugin/one-liners.lua b/config/nvim/after/plugin/one-liners.lua new file mode 100644 index 0000000..f292066 --- /dev/null +++ b/config/nvim/after/plugin/one-liners.lua @@ -0,0 +1,2 @@ +require("lualine").setup({ options = { theme = "tokyonight" } }) +require("nvim-highlight-colors").setup({}) diff --git a/config/nvim/after/plugin/telescope.lua b/config/nvim/after/plugin/telescope.lua new file mode 100644 index 0000000..07232f5 --- /dev/null +++ b/config/nvim/after/plugin/telescope.lua @@ -0,0 +1,34 @@ +local actions = require("telescope.actions") +require("telescope").setup({ + defaults = { + mappings = { + i = { + [""] = actions.move_selection_previous, + [""] = actions.move_selection_next, + [""] = actions.smart_send_to_qflist + actions.open_qflist, + }, + }, + }, +}) + +local builtin = require("telescope.builtin") +vim.keymap.set("n", "ff", builtin.find_files) +vim.keymap.set("n", "fo", builtin.oldfiles) +vim.keymap.set("n", "fq", builtin.quickfix) +vim.keymap.set("n", "fh", builtin.help_tags, { desc = "Telescope help tags" }) +vim.keymap.set("n", "fm", function() + builtin.man_pages({ sections = { "ALL" } }) +end, { desc = "Telescope man pages" }) +vim.keymap.set("n", "fb", builtin.buffers, { desc = "Telescope buffers" }) +vim.keymap.set("n", "fg", function() + builtin.grep_string({ search = vim.fn.input("Grep > ") }) +end) +vim.keymap.set("n", "fc", function() + builtin.grep_string({ search = vim.fn.expand("%:t:r") }) +end, { desc = "Find current file" }) +vim.keymap.set("n", "fs", function() + builtin.grep_string({}) +end, { desc = "Find current string" }) +vim.keymap.set("n", "fi", function() + builtin.find_files({ cwd = "~/.config/nvim/" }) +end) diff --git a/config/nvim/after/plugin/treesitter.lua b/config/nvim/after/plugin/treesitter.lua new file mode 100644 index 0000000..b5bd5ea --- /dev/null +++ b/config/nvim/after/plugin/treesitter.lua @@ -0,0 +1,6 @@ +-- Treesitter config lives in plugin/tonysitter.lua. +-- The only thing left here is the goon filetype mapping; +-- goon's parser is at ~/.local/share/nvim/site/parser/goon.so +-- and queries are at ~/.config/nvim/queries/goon/, both auto-loaded. + +vim.filetype.add({ extension = { goon = "goon" } }) diff --git a/config/nvim/init.lua b/config/nvim/init.lua new file mode 100644 index 0000000..36a1d08 --- /dev/null +++ b/config/nvim/init.lua @@ -0,0 +1,3 @@ +require("config.options") +require("config.keybinds") +require("manage").setup() diff --git a/config/nvim/lua/config/keybinds.lua b/config/nvim/lua/config/keybinds.lua new file mode 100644 index 0000000..5f9801c --- /dev/null +++ b/config/nvim/lua/config/keybinds.lua @@ -0,0 +1,64 @@ +-- KEYBINDS +vim.g.mapleader = " " +vim.keymap.set("n", "cd", vim.cmd.Ex) + +vim.keymap.set("v", "J", ":m '>+1gv=gv") -- Alt Up/Down in vscode +vim.keymap.set("v", "K", ":m '<-2gv=gv") + +vim.keymap.set("n", "J", "mzJ`z") -- Remap joining lines +vim.keymap.set("n", "", "zz") -- Keep cursor in place while moving up/down page +vim.keymap.set("n", "", "zz") +vim.keymap.set("n", "n", "nzzzv") -- center screen when looping search results +vim.keymap.set("n", "N", "Nzzzv") + +-- paste and don't replace clipboard over deleted text +vim.keymap.set("x", "p", [["_dP]]) +vim.keymap.set({ "n", "v" }, "d", [["_d]]) + + +-- sometimes in insert mode, control-c doesn't exactly work like escape +vim.keymap.set("i", "", "") + +-- add binds for Control J/K to scroll thru quickfix list +vim.keymap.set("n", "", "cnextzz") +vim.keymap.set("n", "", "cprevzz") + +-- What the heck is Ex mode? +vim.keymap.set("n", "Q", "") + +vim.keymap.set("n", "k", "lnextzz") +vim.keymap.set("n", "j", "lprevzz") + + +-- lint / format php files for LC +vim.keymap.set("n", "cc", "!php-cs-fixer fix % --using-cache=no") + +-- Replace all instances of whatever is under cursor (on line) +vim.keymap.set("n", "s", [[:s/\<\>//gI]]) + +-- make file executable +vim.keymap.set("n", "x", "!chmod +x %", { silent = true }) + +-- yank into clipboard even if on ssh +vim.keymap.set('n', 'y', 'OSCYankOperator') +vim.keymap.set('v', 'y', 'OSCYankVisual') + +-- reload without exiting vim +vim.keymap.set("n", "rl", "source ~/.config/nvim/init.lua") + +vim.keymap.set("n", "u", vim.cmd.UndotreeToggle) + +-- Quickfix list stuff +vim.keymap.set("n", "cl", ":cclose", { silent = true }) +vim.keymap.set("n", "co", ":copen", { silent = true }) +vim.keymap.set("n", "cn", ":cnextzz") +vim.keymap.set("n", "cp", ":cprevzz") +vim.keymap.set("n", "li", ":checkhealth vim.lsp", { desc = "LSP Info" }) + +-- run make in current working directory +vim.keymap.set("n", "mm", "make") + +-- source file +vim.keymap.set("n", "", function() + vim.cmd("so") +end) diff --git a/config/nvim/lua/config/options.lua b/config/nvim/lua/config/options.lua new file mode 100644 index 0000000..52de0a7 --- /dev/null +++ b/config/nvim/lua/config/options.lua @@ -0,0 +1,55 @@ +-- OPTIONS +local set = vim.opt + +--line nums +set.relativenumber = true +set.number = true + +-- indentation and tabs +set.tabstop = 4 +set.shiftwidth = 4 +set.autoindent = true +set.expandtab = true + +-- search settings +set.ignorecase = true +set.smartcase = true + +-- appearance +set.termguicolors = true +set.background = "dark" +set.signcolumn = "yes" + +-- cursor line +set.cursorline = true + +-- 80th column +set.colorcolumn = "80" + +-- clipboard +set.clipboard:append("unnamedplus") + +-- backspace +set.backspace = "indent,eol,start" + +-- split windows +set.splitbelow = true +set.splitright = true + +-- dw/diw/ciw works on full-word +set.iskeyword:append("-") + +-- keep cursor at least 8 rows from top/bot +set.scrolloff = 8 + +-- undo dir settings +set.swapfile = false +set.backup = false +set.undodir = os.getenv("HOME") .. "/.vim/undodir" +set.undofile = true + +-- incremental search +set.incsearch = true + +-- faster cursor hold +set.updatetime = 50 diff --git a/config/nvim/lua/manage.lua b/config/nvim/lua/manage.lua new file mode 100644 index 0000000..1a9fcf1 --- /dev/null +++ b/config/nvim/lua/manage.lua @@ -0,0 +1,36 @@ +local M = {} +local plug_dir = vim.fn.stdpath("data") .. "/plugins" + +local function ensure(spec) + local repo = type(spec) == "string" and spec or spec[1] + local name = repo:match(".+/(.+)$") + local path = plug_dir .. "/" .. name + + if not vim.uv.fs_stat(path) then + vim.fn.mkdir(plug_dir, "p") + local cmd = { "git", "clone", "--depth=1" } + if spec.branch then + table.insert(cmd, "-b") + table.insert(cmd, spec.branch) + end + table.insert(cmd, "https://github.com/" .. repo) + table.insert(cmd, path) + print("Installing " .. name .. "...") + vim.fn.system(cmd) + end + + vim.opt.rtp:prepend(path) + vim.opt.rtp:append(path .. "/after") + local lua_path = path .. "/lua" + if vim.uv.fs_stat(lua_path) then + package.path = package.path .. ";" .. lua_path .. "/?.lua;" .. lua_path .. "/?/init.lua" + end +end + +function M.setup() + for _, spec in ipairs(require("plugin-list")) do + ensure(spec) + end +end + +return M diff --git a/config/nvim/lua/plugin-list.lua b/config/nvim/lua/plugin-list.lua new file mode 100644 index 0000000..dd98c3d --- /dev/null +++ b/config/nvim/lua/plugin-list.lua @@ -0,0 +1,17 @@ +return { + "nvim-lua/plenary.nvim", + "nvim-tree/nvim-web-devicons", + "hrsh7th/nvim-cmp", + "hrsh7th/cmp-nvim-lsp", + "hrsh7th/cmp-path", + "hrsh7th/cmp-buffer", + "nvim-telescope/telescope.nvim", + { "ThePrimeagen/harpoon", branch = "harpoon2" }, + "folke/tokyonight.nvim", + "nvim-lualine/lualine.nvim", + "brenoprata10/nvim-highlight-colors", + "tpope/vim-fugitive", + "mbbill/undotree", + "ojroques/vim-oscyank", + "captbaritone/better-indent-support-for-php-with-html", +} diff --git a/config/nvim/parser/c.so b/config/nvim/parser/c.so new file mode 100755 index 0000000..c10ef65 Binary files /dev/null and b/config/nvim/parser/c.so differ diff --git a/config/nvim/parser/go.so b/config/nvim/parser/go.so new file mode 100755 index 0000000..e765db9 Binary files /dev/null and b/config/nvim/parser/go.so differ diff --git a/config/nvim/parser/javascript.so b/config/nvim/parser/javascript.so new file mode 100755 index 0000000..4d91c76 Binary files /dev/null and b/config/nvim/parser/javascript.so differ diff --git a/config/nvim/parser/lua.so b/config/nvim/parser/lua.so new file mode 100755 index 0000000..590ecba Binary files /dev/null and b/config/nvim/parser/lua.so differ diff --git a/config/nvim/parser/nix.so b/config/nvim/parser/nix.so new file mode 100755 index 0000000..a1a9261 Binary files /dev/null and b/config/nvim/parser/nix.so differ diff --git a/config/nvim/parser/php.so b/config/nvim/parser/php.so new file mode 100755 index 0000000..2d85c9a Binary files /dev/null and b/config/nvim/parser/php.so differ diff --git a/config/nvim/parser/rust.so b/config/nvim/parser/rust.so new file mode 100755 index 0000000..9c8b621 Binary files /dev/null and b/config/nvim/parser/rust.so differ diff --git a/config/nvim/parser/zig.so b/config/nvim/parser/zig.so new file mode 100755 index 0000000..4a3833f Binary files /dev/null and b/config/nvim/parser/zig.so differ diff --git a/config/nvim/plugin/docgen.lua b/config/nvim/plugin/docgen.lua new file mode 100644 index 0000000..e133fad --- /dev/null +++ b/config/nvim/plugin/docgen.lua @@ -0,0 +1,224 @@ +local M = {} + +-- C/kernel-doc style: /** ... @param: ... Return: ... */ +local function generate_c_doc(bufnr, row, line) + -- strip static/inline/extern prefixes + local stripped = line:gsub("^%s*static%s+", ""):gsub("^%s*inline%s+", ""):gsub("^%s*extern%s+", "") + + -- match: type *func_name(params) or type* func_name(params) + local ret, name, params = stripped:match("^%s*([%w_]+%s*%**)%s*([%w_]+)%s*%((.*)%)%s*{?%s*$") + if not name then + return nil, "No C function signature found on current line" + end + + local doc = { "/**", " * " .. name .. "() - " } + + -- parse parameters + if params and params:match("%S") and not params:match("^%s*void%s*$") then + for param in params:gmatch("([^,]+)") do + local pname = param:match("([%w_]+)%s*$") + or param:match("%*%s*([%w_]+)") + or param:match("([%w_]+)%s*%[") + if pname then + table.insert(doc, " * @" .. pname .. ": ") + end + end + end + + table.insert(doc, " *") + + -- add Return: if not void + ret = ret and ret:gsub("%s+", " "):gsub("^%s*", ""):gsub("%s*$", "") or "" + if ret ~= "void" and ret ~= "" then + table.insert(doc, " * Return: ") + end + + table.insert(doc, " */") + return doc, nil +end + +-- Go style: // FunctionName does something. +local function generate_go_doc(bufnr, row, line) + -- match: func (receiver) name(params) return or func name(params) return + local name, params, ret + + -- method with receiver: func (r *Receiver) Name(params) return + name, params, ret = line:match("^%s*func%s+%([^)]+%)%s+([%w_]+)%s*%((.-)%)%s*(.-)%s*{?%s*$") + + -- regular function: func Name(params) return + if not name then + name, params, ret = line:match("^%s*func%s+([%w_]+)%s*%((.-)%)%s*(.-)%s*{?%s*$") + end + + if not name then + return nil, "No Go function signature found on current line" + end + + local doc = { "// " .. name .. " " } + + -- add parameter hints if present + if params and params:match("%S") then + local param_names = {} + for param in params:gmatch("([^,]+)") do + -- Go params: name type or name, name2 type + local pname = param:match("^%s*([%w_]+)") + if pname then + table.insert(param_names, pname) + end + end + if #param_names > 0 then + table.insert(doc, "//") + table.insert(doc, "// Parameters:") + for _, pname in ipairs(param_names) do + table.insert(doc, "// - " .. pname .. ": ") + end + end + end + + -- add return hint if present + ret = ret and ret:gsub("^%s*", ""):gsub("%s*$", "") or "" + if ret ~= "" and ret ~= "error" then + table.insert(doc, "//") + table.insert(doc, "// Returns: ") + end + + return doc, nil +end + +-- Rust style: /// Description +local function generate_rust_doc(bufnr, row, line) + -- match: fn name(params) -> return or pub fn name... + local name, params, ret = line:match("^%s*pub%s+fn%s+([%w_]+)%s*%((.-)%)%s*%->%s*(.-)%s*{?%s*$") + if not name then + name, params, ret = line:match("^%s*fn%s+([%w_]+)%s*%((.-)%)%s*%->%s*(.-)%s*{?%s*$") + end + if not name then + name, params = line:match("^%s*pub%s+fn%s+([%w_]+)%s*%((.-)%)%s*{?%s*$") + end + if not name then + name, params = line:match("^%s*fn%s+([%w_]+)%s*%((.-)%)%s*{?%s*$") + end + + if not name then + return nil, "No Rust function signature found on current line" + end + + local doc = { "/// " } + + -- add parameter hints if present + if params and params:match("%S") then + local param_names = {} + for param in params:gmatch("([^,]+)") do + local pname = param:match("^%s*([%w_]+)%s*:") + if pname and pname ~= "self" and pname ~= "&self" and pname ~= "&mut" then + table.insert(param_names, pname) + end + end + if #param_names > 0 then + table.insert(doc, "///") + table.insert(doc, "/// # Arguments") + table.insert(doc, "///") + for _, pname in ipairs(param_names) do + table.insert(doc, "/// * `" .. pname .. "` - ") + end + end + end + + -- add return hint if present + ret = ret and ret:gsub("^%s*", ""):gsub("%s*$", "") or "" + if ret ~= "" then + table.insert(doc, "///") + table.insert(doc, "/// # Returns") + table.insert(doc, "///") + table.insert(doc, "/// ") + end + + return doc, nil +end + +-- Python style: """docstring""" +local function generate_python_doc(bufnr, row, line) + local name, params = line:match("^%s*def%s+([%w_]+)%s*%((.-)%)%s*:?%s*$") + if not name then + name, params = line:match("^%s*async%s+def%s+([%w_]+)%s*%((.-)%)%s*:?%s*$") + end + + if not name then + return nil, "No Python function signature found on current line" + end + + local indent = line:match("^(%s*)") or "" + local doc = { indent .. ' """' } + + -- parse parameters + if params and params:match("%S") then + local param_names = {} + for param in params:gmatch("([^,]+)") do + local pname = param:match("^%s*([%w_]+)") + if pname and pname ~= "self" and pname ~= "cls" then + table.insert(param_names, pname) + end + end + if #param_names > 0 then + table.insert(doc, indent .. "") + table.insert(doc, indent .. " Args:") + for _, pname in ipairs(param_names) do + table.insert(doc, indent .. " " .. pname .. ": ") + end + end + end + + table.insert(doc, indent .. "") + table.insert(doc, indent .. " Returns:") + table.insert(doc, indent .. " ") + table.insert(doc, indent .. ' """') + + return doc, nil +end + +-- filetype to generator mapping +local generators = { + c = generate_c_doc, + cpp = generate_c_doc, + h = generate_c_doc, + go = generate_go_doc, + rust = generate_rust_doc, + python = generate_python_doc, +} + +function M.generate_doc() + local bufnr = vim.api.nvim_get_current_buf() + local row = vim.api.nvim_win_get_cursor(0)[1] + local line = vim.api.nvim_buf_get_lines(bufnr, row - 1, row, false)[1] + local ft = vim.bo[bufnr].filetype + + local generator = generators[ft] + if not generator then + vim.notify("No doc generator for filetype: " .. ft, vim.log.levels.WARN) + return + end + + local doc, err = generator(bufnr, row, line) + if err then + vim.notify(err, vim.log.levels.ERROR) + return + end + + vim.api.nvim_buf_set_lines(bufnr, row - 1, row - 1, false, doc) + + -- position cursor at first empty description spot + local cursor_row = row + local cursor_col = #doc[1] + for i, docline in ipairs(doc) do + if docline:match("%s$") or docline:match(":%s*$") or docline:match("%-%s*$") then + cursor_row = row + i - 1 + cursor_col = #docline + break + end + end + + vim.api.nvim_win_set_cursor(0, { cursor_row, cursor_col }) + vim.cmd("startinsert!") +end + +vim.keymap.set("n", "dg", M.generate_doc) diff --git a/config/nvim/plugin/flterm.lua b/config/nvim/plugin/flterm.lua new file mode 100644 index 0000000..d766181 --- /dev/null +++ b/config/nvim/plugin/flterm.lua @@ -0,0 +1,55 @@ +-- Remap leaving 'terminal mode' to double tap esc +vim.keymap.set("t", "", "") + +local state = { + floating = { + buf = -1, + win = -1, + } +} + +local function open_floating_terminal(opts) + opts = opts or {} + local width = opts.width or math.floor(vim.o.columns * 0.8) + local height = opts.height or math.floor(vim.o.lines * 0.8) + + local row = math.floor((vim.o.lines - height) / 2) + local col = math.floor((vim.o.columns - width) / 2) + + local buf = nil + if vim.api.nvim_buf_is_valid(opts.buf) then + buf = opts.buf + else + buf = vim.api.nvim_create_buf(false, true) + end + if not buf then + error("Failed to create buffer") + end + + local win = vim.api.nvim_open_win(buf, true, { + relative = 'editor', + width = width, + height = height, + row = row, + col = col, + style = 'minimal', + border = 'rounded', + }) + + return { buf = buf, win = win } +end + +local toggle_terminal = function() + if not vim.api.nvim_win_is_valid(state.floating.win) then + state.floating = open_floating_terminal({ buf = state.floating.buf }); + if vim.bo[state.floating.buf].buftype ~= "terminal" then + vim.cmd.terminal() + vim.cmd("startinsert!") + end + else + vim.api.nvim_win_hide(state.floating.win) + end +end + +vim.api.nvim_create_user_command("Flterm", toggle_terminal, {}) +vim.api.nvim_set_keymap('n', 'ft', [[:Flterm]], { noremap = true, silent = true }) diff --git a/config/nvim/plugin/lsp.lua b/config/nvim/plugin/lsp.lua new file mode 100644 index 0000000..c2457ae --- /dev/null +++ b/config/nvim/plugin/lsp.lua @@ -0,0 +1,283 @@ +vim.lsp.config('*', { + root_markers = { '.git' }, +}) + +vim.diagnostic.config({ + virtual_text = true, + severity_sort = true, + float = { + style = 'minimal', + border = 'rounded', + source = 'if_many', + header = '', + prefix = '', + }, + signs = { + text = { + [vim.diagnostic.severity.ERROR] = '✘', + [vim.diagnostic.severity.WARN] = '▲', + [vim.diagnostic.severity.HINT] = '⚑', + [vim.diagnostic.severity.INFO] = '»', + }, + }, +}) + +local orig = vim.lsp.util.open_floating_preview +---@diagnostic disable-next-line: duplicate-set-field +function vim.lsp.util.open_floating_preview(contents, syntax, opts, ...) + opts = opts or {} + opts.border = opts.border or 'rounded' + opts.max_width = opts.max_width or 80 + opts.max_height = opts.max_height or 24 + opts.wrap = opts.wrap ~= false + return orig(contents, syntax, opts, ...) +end + +vim.api.nvim_create_autocmd('LspAttach', { + group = vim.api.nvim_create_augroup('my.lsp', {}), + callback = function(args) + local client = assert(vim.lsp.get_client_by_id(args.data.client_id)) + local buf = args.buf + local map = function(mode, lhs, rhs) vim.keymap.set(mode, lhs, rhs, { buffer = buf }) end + + map('n', 'K', vim.lsp.buf.hover) + map('n', 'gd', vim.lsp.buf.definition) + map('n', 'gD', vim.lsp.buf.declaration) + map('n', 'gi', vim.lsp.buf.implementation) + map('n', 'go', vim.lsp.buf.type_definition) + map('n', 'gr', vim.lsp.buf.references) + map('n', 'gs', vim.lsp.buf.signature_help) + map('n', 'gl', vim.diagnostic.open_float) + map('n', '', vim.lsp.buf.rename) + map({ 'n', 'x' }, '', function() vim.lsp.buf.format({ async = true }) end) + map('n', '', vim.lsp.buf.code_action) + + if client:supports_method('textDocument/documentHighlight') then + local highlight_augroup = vim.api.nvim_create_augroup('my.lsp.highlight', { clear = false }) + vim.api.nvim_create_autocmd({ 'CursorHold', 'CursorHoldI' }, { + buffer = buf, + group = highlight_augroup, + callback = vim.lsp.buf.document_highlight, + }) + vim.api.nvim_create_autocmd({ 'CursorMoved', 'CursorMovedI' }, { + buffer = buf, + group = highlight_augroup, + callback = vim.lsp.buf.clear_references, + }) + end + + local excluded_filetypes = { php = true, c = true, cpp = true } + if not client:supports_method('textDocument/willSaveWaitUntil') + and client:supports_method('textDocument/formatting') + and not excluded_filetypes[vim.bo[buf].filetype] + then + vim.api.nvim_create_autocmd('BufWritePre', { + group = vim.api.nvim_create_augroup('my.lsp.format', { clear = false }), + buffer = buf, + callback = function() + vim.lsp.buf.format({ bufnr = buf, id = client.id, timeout_ms = 1000 }) + end, + }) + end + end, +}) +local caps = require("cmp_nvim_lsp").default_capabilities() +vim.lsp.config['luals'] = { + cmd = { 'lua-language-server' }, + filetypes = { 'lua' }, + root_markers = { { '.luarc.json', '.luarc.jsonc' }, '.git' }, + capabilities = caps, + settings = { + Lua = { + runtime = { version = 'LuaJIT' }, + diagnostics = { globals = { 'vim' } }, + workspace = { + checkThirdParty = false, + library = vim.list_extend( + vim.api.nvim_get_runtime_file('', true), + { '/home/tony/repos/oxwm/templates' } + ), + }, + telemetry = { enable = false }, + }, + }, +} + +vim.lsp.config['cssls'] = { + cmd = { 'vscode-css-language-server', '--stdio' }, + filetypes = { 'css', 'scss', 'less' }, + root_markers = { 'package.json', '.git' }, + capabilities = caps, + settings = { + css = { validate = true }, + scss = { validate = true }, + less = { validate = true }, + }, +} + +vim.lsp.config['phpls'] = { + cmd = { 'intelephense', '--stdio' }, + filetypes = { 'php' }, + root_markers = { 'composer.json', '.git' }, + capabilities = caps, + settings = { + intelephense = { + files = { + maxSize = 5000000, -- default 5MB + }, + }, + }, +} + +vim.lsp.config['ts_ls'] = { + cmd = { 'typescript-language-server', '--stdio' }, + filetypes = { + 'javascript', 'javascriptreact', 'javascript.jsx', + 'typescript', 'typescriptreact', 'typescript.tsx', + }, + root_markers = { 'package.json', 'tsconfig.json', 'jsconfig.json', '.git' }, + capabilities = caps, + settings = { + completions = { + completeFunctionCalls = true, + }, + }, +} + +vim.lsp.config['zls'] = { + cmd = { 'zls' }, + filetypes = { 'zig', 'zir' }, + root_markers = { 'zls.json', 'build.zig', '.git' }, + capabilities = caps, + settings = { + zls = { + enable_build_on_save = true, + build_on_save_step = "install", + warn_style = false, + enable_snippets = true, + } + } +} + +vim.lsp.config['nil_ls'] = { + cmd = { 'nil' }, + filetypes = { 'nix' }, + root_markers = { 'flake.nix', 'default.nix', '.git' }, + capabilities = caps, + settings = { + ['nil'] = { + formatting = { + command = { "alejandra" } + } + } + } +} + +vim.lsp.config['rust_analyzer'] = { + cmd = { 'rust-analyzer' }, + filetypes = { 'rust' }, + root_markers = { 'Cargo.toml', 'rust-project.json', '.git' }, + capabilities = caps, + settings = { + ['rust-analyzer'] = { + cargo = { allFeatures = true }, + formatting = { + command = { "rustfmt" } + }, + }, + }, +} + +-- C / C++ via clangd +vim.lsp.config['clangd'] = { + cmd = { + 'clangd', + -- '--background-index', + -- '--clang-tidy', + -- '--header-insertion=never', + -- '--completion-style=detailed', + -- '--query-driver=/nix/store/*-gcc-*/bin/gcc*,/nix/store/*-clang-*/bin/clang*,/run/current-system/sw/bin/cc*', + }, + filetypes = { 'c', 'cpp', 'objc', 'objcpp' }, + root_markers = { 'compile_commands.json', '.clangd', 'configure.ac', 'Makefile', '.git' }, + capabilities = caps, + -- init_options = { + -- fallbackFlags = { '-std=c23' }, -- Default to C23 + -- }, +} + +vim.lsp.config['c3lsp'] = { + cmd = { 'c3-lsp' }, + filetypes = { 'c3' }, + root_markers = { 'project.json', '.git' }, + capabilities = caps, +} + +vim.lsp.config['serve_d'] = { + cmd = { 'serve-d' }, + filetypes = { 'd' }, + root_markers = { 'dub.sdl', 'dub.json', '.git' }, + capabilities = caps, +} + +vim.lsp.config['jsonls'] = { + cmd = { 'vscode-json-languageserver', '--stdio' }, + filetypes = { 'json', 'jsonc' }, + root_markers = { 'package.json', '.git', 'config.jsonc' }, + capabilities = caps, +} + +vim.lsp.config['hls'] = { + cmd = { 'haskell-language-server-wrapper', '--lsp' }, + filetypes = { 'haskell', 'lhaskell' }, + root_markers = { 'stack.yaml', 'cabal.project', 'package.yaml', '*.cabal', 'hie.yaml', '.git' }, + capabilities = caps, + settings = { + haskell = { + formattingProvider = 'fourmolu', + plugin = { + semanticTokens = { globalOn = false } + }, + }, + }, +} + +vim.lsp.config['gopls'] = { + cmd = { 'gopls' }, + filetypes = { 'go', 'gomod', 'gowork', 'gotmpl' }, + root_markers = { 'go.mod', 'go.work', '.git' }, + capabilities = caps, + settings = { + gopls = { + analyses = { + unusedparams = false, + ST1003 = false, + ST1000 = false, + }, + staticcheck = true, + }, + }, +} + +vim.lsp.config['templ'] = { + cmd = { 'templ', 'lsp' }, + filetypes = { 'templ' }, + root_markers = { 'go.mod', '.git' }, + capabilities = caps, +} + +vim.filetype.add({ + extension = { + h = 'c', + c3 = 'c3', + d = 'd', + templ = 'templ', + }, +}) + +---@diagnostic disable-next-line: invisible +for name, _ in pairs(vim.lsp.config._configs) do + if name ~= '*' then + vim.lsp.enable(name) + end +end diff --git a/config/nvim/plugin/quickformat.lua b/config/nvim/plugin/quickformat.lua new file mode 100644 index 0000000..038dadf --- /dev/null +++ b/config/nvim/plugin/quickformat.lua @@ -0,0 +1,40 @@ +local function reformat_parenthesized_content() + local bufnr = vim.api.nvim_get_current_buf() + local row = vim.api.nvim_win_get_cursor(0)[1] + local line = vim.api.nvim_buf_get_lines(bufnr, row - 1, row, false)[1] + + local inside = line:match("%((.-)%)") + if not inside then + vim.notify( + "No content found inside parentheses", + vim.log.levels.ERROR + ) + return + end + + local prefix = line:match("^(.-)%(") or "" + local suffix = line:match("%)(.*)$") or "" + + local parts = vim.split(inside, ",%s*") + if #parts == 0 then + vim.notify("No comma-separated content found", vim.log.levels.ERROR) + return + end + + local new_lines = {} + table.insert(new_lines, prefix .. "(") + for i, part in ipairs(parts) do + if i < #parts then + table.insert(new_lines, " " .. part .. ",") + else + table.insert(new_lines, " " .. part) + end + end + table.insert(new_lines, " )" .. suffix) + + vim.api.nvim_buf_set_lines(bufnr, row - 1, row, false, new_lines) +end + +vim.keymap.set("n", "qq", function() + reformat_parenthesized_content() +end) diff --git a/config/nvim/plugin/tonycontext.lua b/config/nvim/plugin/tonycontext.lua new file mode 100644 index 0000000..e2c7d02 --- /dev/null +++ b/config/nvim/plugin/tonycontext.lua @@ -0,0 +1,110 @@ +-- Minimal sticky context header. Replaces nvim-treesitter-context. +-- Shows the first line of the enclosing function/class pinned to the +-- top of the window when that line has scrolled offscreen above. +-- +-- One level of context only (innermost enclosing). Extend CONTEXT_TYPES +-- to support more languages or richer node kinds. +-- +-- Toggle: +-- th hide +-- tu unhide + +local M = { enabled = true } +local ctx_buf, ctx_win + +local CONTEXT_TYPES = { + -- C / PHP + function_definition = true, + method_declaration = true, + class_declaration = true, + -- Lua + function_declaration = true, + -- Rust + function_item = true, + impl_item = true, + trait_item = true, + -- Go (function_declaration shared) + -- JS + method_definition = true, + arrow_function = true, + -- Zig + fn_proto = true, + -- Nix + function_expression = true, +} + +local function close_ctx() + if ctx_win and vim.api.nvim_win_is_valid(ctx_win) then + vim.api.nvim_win_close(ctx_win, true) + end + ctx_win = nil +end + +local function update_ctx() + if not M.enabled then close_ctx() return end + + local bufnr = vim.api.nvim_get_current_buf() + local node = vim.treesitter.get_node() + if not node then close_ctx() return end + + while node and not CONTEXT_TYPES[node:type()] do + node = node:parent() + end + if not node then close_ctx() return end + + local sr = node:start() + local top_visible = vim.fn.line("w0") - 1 + if sr >= top_visible then close_ctx() return end + + local lines = vim.api.nvim_buf_get_lines(bufnr, sr, sr + 1, false) + if #lines == 0 then close_ctx() return end + + if not ctx_buf or not vim.api.nvim_buf_is_valid(ctx_buf) then + ctx_buf = vim.api.nvim_create_buf(false, true) + vim.bo[ctx_buf].buftype = "nofile" + end + vim.api.nvim_buf_set_lines(ctx_buf, 0, -1, false, lines) + vim.bo[ctx_buf].filetype = vim.bo[bufnr].filetype + + local config = { + relative = "win", + win = vim.api.nvim_get_current_win(), + row = 0, + col = 0, + width = vim.api.nvim_win_get_width(0), + height = 1, + focusable = false, + style = "minimal", + zindex = 20, + } + if ctx_win and vim.api.nvim_win_is_valid(ctx_win) then + vim.api.nvim_win_set_config(ctx_win, config) + else + ctx_win = vim.api.nvim_open_win(ctx_buf, false, config) + vim.wo[ctx_win].winhighlight = "Normal:TonyContext,NormalFloat:TonyContext" + end +end + +vim.api.nvim_set_hl(0, "TonyContext", { link = "NormalFloat", default = true }) + +local group = vim.api.nvim_create_augroup("TonyContext", { clear = true }) +vim.api.nvim_create_autocmd({ "CursorMoved", "CursorMovedI", "WinScrolled", "BufEnter" }, { + group = group, + callback = update_ctx, +}) +vim.api.nvim_create_autocmd({ "BufLeave", "WinLeave" }, { + group = group, + callback = close_ctx, +}) + +vim.keymap.set("n", "th", function() + M.enabled = false + close_ctx() +end, { desc = "Hide context header" }) + +vim.keymap.set("n", "tu", function() + M.enabled = true + update_ctx() +end, { desc = "Unhide context header" }) + +return M diff --git a/config/nvim/plugin/tonysitter.lua b/config/nvim/plugin/tonysitter.lua new file mode 100644 index 0000000..b79a150 --- /dev/null +++ b/config/nvim/plugin/tonysitter.lua @@ -0,0 +1,85 @@ +-- Local treesitter setup. Replaces nvim-treesitter + nvim-treesitter-textobjects. +-- Parsers: ~/.config/nvim/parser/.so +-- Queries: ~/.config/nvim/queries// + +-- Start treesitter highlighting on FileType when a parser is available. +vim.api.nvim_create_autocmd("FileType", { + callback = function(args) + pcall(vim.treesitter.start, args.buf) + end, +}) + +-- Function text objects: af / if +-- Walks the textobjects query for the buffer's language and selects the +-- smallest @function.outer / @function.inner range containing the cursor. +local function select_function(capture) + local bufnr = vim.api.nvim_get_current_buf() + local ok, parser = pcall(vim.treesitter.get_parser, bufnr) + if not ok or not parser then return end + + local lang = parser:lang() + local query = vim.treesitter.query.get(lang, "textobjects") + if not query then return end + + local tree = parser:parse()[1] + if not tree then return end + local root = tree:root() + + local cur = vim.api.nvim_win_get_cursor(0) + local crow, ccol = cur[1] - 1, cur[2] + + -- A single textobject (e.g. @function.inner) is often split across + -- multiple captures within the same match (e.g. one per statement in + -- the body). Use iter_matches and union the ranges of all captures + -- with our target name within each match. + local best, best_size + for _, match in query:iter_matches(root, bufnr, 0, -1, { all = true }) do + local min_sr, min_sc, max_er, max_ec + for id, nodes in pairs(match) do + if query.captures[id] == capture then + if type(nodes) ~= "table" then nodes = { nodes } end + for _, node in ipairs(nodes) do + local sr, sc, er, ec = node:range() + if not min_sr or sr < min_sr or (sr == min_sr and sc < min_sc) then + min_sr, min_sc = sr, sc + end + if not max_er or er > max_er or (er == max_er and ec > max_ec) then + max_er, max_ec = er, ec + end + end + end + end + if min_sr then + local contains = (min_sr < crow or (min_sr == crow and min_sc <= ccol)) + and (max_er > crow or (max_er == crow and max_ec >= ccol)) + if contains then + local size = (max_er - min_sr) * 1e6 + (max_ec - min_sc) + if not best_size or size < best_size then + best_size = size + best = { min_sr, min_sc, max_er, max_ec } + end + end + end + end + + if not best then return end + local sr, sc, er, ec = best[1], best[2], best[3], best[4] + + -- If we were invoked from visual mode (`vif`, `vaf`, etc.), exit it + -- first — otherwise `normal! v` below would toggle visual off instead + -- of re-entering it, and we'd end up only moving the cursor. + local mode = vim.fn.mode() + if mode == "v" or mode == "V" or mode == "\22" then + vim.api.nvim_feedkeys( + vim.api.nvim_replace_termcodes("", true, false, true), "nx", false) + end + + vim.api.nvim_win_set_cursor(0, { sr + 1, sc }) + vim.cmd("normal! v") + vim.api.nvim_win_set_cursor(0, { er + 1, math.max(0, ec - 1) }) +end + +vim.keymap.set({ "x", "o" }, "af", function() select_function("function.outer") end, + { desc = "around function" }) +vim.keymap.set({ "x", "o" }, "if", function() select_function("function.inner") end, + { desc = "inside function" }) diff --git a/config/nvim/queries/c/folds.scm b/config/nvim/queries/c/folds.scm new file mode 100644 index 0000000..bb26a62 --- /dev/null +++ b/config/nvim/queries/c/folds.scm @@ -0,0 +1,23 @@ +[ + (for_statement) + (if_statement) + (while_statement) + (do_statement) + (switch_statement) + (case_statement) + (function_definition) + (struct_specifier) + (enum_specifier) + (comment) + (preproc_if) + (preproc_elif) + (preproc_else) + (preproc_ifdef) + (preproc_function_def) + (initializer_list) + (gnu_asm_expression) + (preproc_include)+ +] @fold + +(compound_statement + (compound_statement) @fold) diff --git a/config/nvim/queries/c/highlights.scm b/config/nvim/queries/c/highlights.scm new file mode 100644 index 0000000..ea65075 --- /dev/null +++ b/config/nvim/queries/c/highlights.scm @@ -0,0 +1,341 @@ +; Lower priority to prefer @variable.parameter when identifier appears in parameter_declaration. +((identifier) @variable + (#set! priority 95)) + +(preproc_def + (preproc_arg) @variable) + +[ + "default" + "goto" + "asm" + "__asm__" +] @keyword + +[ + "enum" + "struct" + "union" + "typedef" +] @keyword.type + +[ + "sizeof" + "offsetof" +] @keyword.operator + +(alignof_expression + . + _ @keyword.operator) + +"return" @keyword.return + +[ + "while" + "for" + "do" + "continue" + "break" +] @keyword.repeat + +[ + "if" + "else" + "case" + "switch" +] @keyword.conditional + +[ + "#if" + "#ifdef" + "#ifndef" + "#else" + "#elif" + "#endif" + "#elifdef" + "#elifndef" + (preproc_directive) +] @keyword.directive + +"#define" @keyword.directive.define + +"#include" @keyword.import + +[ + ";" + ":" + "," + "." + "::" +] @punctuation.delimiter + +"..." @punctuation.special + +[ + "(" + ")" + "[" + "]" + "{" + "}" +] @punctuation.bracket + +[ + "=" + "-" + "*" + "/" + "+" + "%" + "~" + "|" + "&" + "^" + "<<" + ">>" + "->" + "<" + "<=" + ">=" + ">" + "==" + "!=" + "!" + "&&" + "||" + "-=" + "+=" + "*=" + "/=" + "%=" + "|=" + "&=" + "^=" + ">>=" + "<<=" + "--" + "++" +] @operator + +; Make sure the comma operator is given a highlight group after the comma +; punctuator so the operator is highlighted properly. +(comma_expression + "," @operator) + +[ + (true) + (false) +] @boolean + +(conditional_expression + [ + "?" + ":" + ] @keyword.conditional.ternary) + +(string_literal) @string + +(system_lib_string) @string + +(escape_sequence) @string.escape + +(null) @constant.builtin + +(number_literal) @number + +(char_literal) @character + +(preproc_defined) @function.macro + +((field_expression + (field_identifier) @property) @_parent + (#not-has-parent? @_parent template_method function_declarator call_expression)) + +(field_designator) @property + +((field_identifier) @property + (#has-ancestor? @property field_declaration) + (#not-has-ancestor? @property function_declarator)) + +(statement_identifier) @label + +(declaration + type: (type_identifier) @_type + declarator: (identifier) @label + (#eq? @_type "__label__")) + +[ + (type_identifier) + (type_descriptor) +] @type + +(storage_class_specifier) @keyword.modifier + +[ + (type_qualifier) + (gnu_asm_qualifier) + "__extension__" +] @keyword.modifier + +(linkage_specification + "extern" @keyword.modifier) + +(type_definition + declarator: (type_identifier) @type.definition) + +(primitive_type) @type.builtin + +(sized_type_specifier + _ @type.builtin + type: _?) + +((identifier) @constant + (#lua-match? @constant "^[A-Z][A-Z0-9_]+$")) + +(preproc_def + (preproc_arg) @constant + (#lua-match? @constant "^[A-Z][A-Z0-9_]+$")) + +(enumerator + name: (identifier) @constant) + +(case_statement + value: (identifier) @constant) + +((identifier) @constant.builtin + ; format-ignore + (#any-of? @constant.builtin + "stderr" "stdin" "stdout" + "__FILE__" "__LINE__" "__DATE__" "__TIME__" + "__STDC__" "__STDC_VERSION__" "__STDC_HOSTED__" + "__cplusplus" "__OBJC__" "__ASSEMBLER__" + "__BASE_FILE__" "__FILE_NAME__" "__INCLUDE_LEVEL__" + "__TIMESTAMP__" "__clang__" "__clang_major__" + "__clang_minor__" "__clang_patchlevel__" + "__clang_version__" "__clang_literal_encoding__" + "__clang_wide_literal_encoding__" + "__FUNCTION__" "__func__" "__PRETTY_FUNCTION__" + "__VA_ARGS__" "__VA_OPT__")) + +(preproc_def + (preproc_arg) @constant.builtin + ; format-ignore + (#any-of? @constant.builtin + "stderr" "stdin" "stdout" + "__FILE__" "__LINE__" "__DATE__" "__TIME__" + "__STDC__" "__STDC_VERSION__" "__STDC_HOSTED__" + "__cplusplus" "__OBJC__" "__ASSEMBLER__" + "__BASE_FILE__" "__FILE_NAME__" "__INCLUDE_LEVEL__" + "__TIMESTAMP__" "__clang__" "__clang_major__" + "__clang_minor__" "__clang_patchlevel__" + "__clang_version__" "__clang_literal_encoding__" + "__clang_wide_literal_encoding__" + "__FUNCTION__" "__func__" "__PRETTY_FUNCTION__" + "__VA_ARGS__" "__VA_OPT__")) + +(attribute_specifier + (argument_list + (identifier) @variable.builtin)) + +(attribute_specifier + (argument_list + (call_expression + function: (identifier) @variable.builtin))) + +((call_expression + function: (identifier) @function.builtin) + (#lua-match? @function.builtin "^__builtin_")) + +((call_expression + function: (identifier) @function.builtin) + (#has-ancestor? @function.builtin attribute_specifier)) + +; Preproc def / undef +(preproc_def + name: (_) @constant.macro) + +(preproc_call + directive: (preproc_directive) @_u + argument: (_) @constant.macro + (#eq? @_u "#undef")) + +(preproc_ifdef + name: (identifier) @constant.macro) + +(preproc_elifdef + name: (identifier) @constant.macro) + +(preproc_defined + (identifier) @constant.macro) + +(call_expression + function: (identifier) @function.call) + +(call_expression + function: (field_expression + field: (field_identifier) @function.call)) + +(function_declarator + declarator: (identifier) @function) + +(function_declarator + declarator: (parenthesized_declarator + (pointer_declarator + declarator: (field_identifier) @function))) + +(preproc_function_def + name: (identifier) @function.macro) + +(comment) @comment @spell + +((comment) @comment.documentation + (#lua-match? @comment.documentation "^/[*][*][^*].*[*]/$")) + +; Parameters +(parameter_declaration + declarator: (identifier) @variable.parameter) + +(parameter_declaration + declarator: (array_declarator) @variable.parameter) + +(parameter_declaration + declarator: (pointer_declarator) @variable.parameter) + +; K&R functions +; To enable support for K&R functions, +; add the following lines to your own query config and uncomment them. +; They are commented out as they'll conflict with C++ +; Note that you'll need to have `; extends` at the top of your query file. +; +; (parameter_list (identifier) @variable.parameter) +; +; (function_definition +; declarator: _ +; (declaration +; declarator: (identifier) @variable.parameter)) +; +; (function_definition +; declarator: _ +; (declaration +; declarator: (array_declarator) @variable.parameter)) +; +; (function_definition +; declarator: _ +; (declaration +; declarator: (pointer_declarator) @variable.parameter)) +(preproc_params + (identifier) @variable.parameter) + +[ + "__attribute__" + "__declspec" + "__based" + "__cdecl" + "__clrcall" + "__stdcall" + "__fastcall" + "__thiscall" + "__vectorcall" + (ms_pointer_modifier) + (attribute_declaration) +] @attribute diff --git a/config/nvim/queries/c/injections.scm b/config/nvim/queries/c/injections.scm new file mode 100644 index 0000000..77b4d7a --- /dev/null +++ b/config/nvim/queries/c/injections.scm @@ -0,0 +1,128 @@ +((preproc_arg) @injection.content + (#set! injection.language "c")) + +((comment) @injection.content + (#set! injection.language "comment")) + +((comment) @injection.content + (#match? @injection.content "/\\*!([a-zA-Z]+:)?re2c") + (#set! injection.language "re2c")) + +((comment) @injection.content + (#lua-match? @injection.content "/[*\/][!*\/]" + ">" + ">=" + ">>" + "||" + "%" + "%=" + "*" + "**" + ">>>" + "&" + "|" + "^" + "??" + "*=" + ">>=" + ">>>=" + "^=" + "|=" + "&&=" + "||=" + "??=" + "..." +] @operator + +(binary_expression + "/" @operator) + +(ternary_expression + [ + "?" + ":" + ] @keyword.conditional.ternary) + +(unary_expression + [ + "!" + "~" + "-" + "+" + ] @operator) + +(unary_expression + [ + "delete" + "void" + ] @keyword.operator) + +[ + "(" + ")" + "[" + "]" + "{" + "}" +] @punctuation.bracket + +(template_substitution + [ + "${" + "}" + ] @punctuation.special) @none + +; Imports +;---------- +(namespace_import + "*" @character.special + (identifier) @module) + +(namespace_export + "*" @character.special + (identifier) @module) + +(export_statement + "*" @character.special) + +; Keywords +;---------- +[ + "if" + "else" + "switch" + "case" +] @keyword.conditional + +[ + "import" + "from" + "as" + "export" +] @keyword.import + +[ + "for" + "of" + "do" + "while" + "continue" +] @keyword.repeat + +[ + "break" + "const" + "debugger" + "extends" + "get" + "let" + "set" + "static" + "target" + "var" + "with" +] @keyword + +"class" @keyword.type + +[ + "async" + "await" +] @keyword.coroutine + +[ + "return" + "yield" +] @keyword.return + +"function" @keyword.function + +[ + "new" + "delete" + "in" + "instanceof" + "typeof" +] @keyword.operator + +[ + "throw" + "try" + "catch" + "finally" +] @keyword.exception + +(export_statement + "default" @keyword) + +(switch_default + "default" @keyword.conditional) diff --git a/config/nvim/queries/ecma/injections.scm b/config/nvim/queries/ecma/injections.scm new file mode 100644 index 0000000..04abafc --- /dev/null +++ b/config/nvim/queries/ecma/injections.scm @@ -0,0 +1,203 @@ +(((comment) @_jsdoc_comment + (#lua-match? @_jsdoc_comment "^/[*][*][^*].*[*]/$")) @injection.content + (#set! injection.language "jsdoc")) + +((comment) @injection.content + (#set! injection.language "comment")) + +; html(`...`), html`...`, sql(`...`), etc. +(call_expression + function: (identifier) @injection.language + arguments: [ + (arguments + (template_string) @injection.content) + (template_string) @injection.content + ] + (#lua-match? @injection.language "^[a-zA-Z][a-zA-Z0-9]*$") + (#offset! @injection.content 0 1 0 -1) + (#set! injection.include-children) + ; Languages excluded from auto-injection due to special rules + ; - svg uses the html parser + ; - css uses the styled parser + (#not-any-of? @injection.language "svg" "css")) + +; svg`...` or svg(`...`) +(call_expression + function: (identifier) @_name + (#eq? @_name "svg") + arguments: [ + (arguments + (template_string) @injection.content) + (template_string) @injection.content + ] + (#offset! @injection.content 0 1 0 -1) + (#set! injection.include-children) + (#set! injection.language "html")) + +; Vercel PostgreSQL +; foo.sql`...` or foo.sql(`...`) +(call_expression + function: (member_expression + property: (property_identifier) @injection.language) + arguments: [ + (arguments + (template_string) @injection.content) + (template_string) @injection.content + ] + (#eq? @injection.language "sql") + (#offset! @injection.content 0 1 0 -1) + (#set! injection.include-children)) + +(call_expression + function: (identifier) @_name + (#eq? @_name "gql") + arguments: (template_string) @injection.content + (#offset! @injection.content 0 1 0 -1) + (#set! injection.include-children) + (#set! injection.language "graphql")) + +(call_expression + function: (identifier) @_name + (#eq? @_name "hbs") + arguments: (template_string) @injection.content + (#offset! @injection.content 0 1 0 -1) + (#set! injection.include-children) + (#set! injection.language "glimmer")) + +; css``, keyframes`` +(call_expression + function: (identifier) @_name + (#any-of? @_name "css" "keyframes") + arguments: (template_string) @injection.content + (#offset! @injection.content 0 1 0 -1) + (#set! injection.include-children) + (#set! injection.language "styled")) + +; styled.div`` +(call_expression + function: (member_expression + object: (identifier) @_name + (#eq? @_name "styled")) + arguments: ((template_string) @injection.content + (#offset! @injection.content 0 1 0 -1) + (#set! injection.include-children) + (#set! injection.language "styled"))) + +; styled(Component)`` +(call_expression + function: (call_expression + function: (identifier) @_name + (#eq? @_name "styled")) + arguments: ((template_string) @injection.content + (#offset! @injection.content 0 1 0 -1) + (#set! injection.include-children) + (#set! injection.language "styled"))) + +; styled.div.attrs({ prop: "foo" })`` +(call_expression + function: (call_expression + function: (member_expression + object: (member_expression + object: (identifier) @_name + (#eq? @_name "styled")))) + arguments: ((template_string) @injection.content + (#offset! @injection.content 0 1 0 -1) + (#set! injection.include-children) + (#set! injection.language "styled"))) + +; styled(Component).attrs({ prop: "foo" })`` +(call_expression + function: (call_expression + function: (member_expression + object: (call_expression + function: (identifier) @_name + (#eq? @_name "styled")))) + arguments: ((template_string) @injection.content + (#offset! @injection.content 0 1 0 -1) + (#set! injection.include-children) + (#set! injection.language "styled"))) + +((regex_pattern) @injection.content + (#set! injection.language "regex")) + +; ((comment) @_gql_comment +; (#eq? @_gql_comment "/* GraphQL */") +; (template_string) @injection.content +; (#set! injection.language "graphql")) +((template_string) @injection.content + (#lua-match? @injection.content "^`#graphql") + (#offset! @injection.content 0 1 0 -1) + (#set! injection.include-children) + (#set! injection.language "graphql")) + +; el.innerHTML = `` +(assignment_expression + left: (member_expression + property: (property_identifier) @_prop + (#any-of? @_prop "outerHTML" "innerHTML")) + right: (template_string) @injection.content + (#offset! @injection.content 0 1 0 -1) + (#set! injection.include-children) + (#set! injection.language "html")) + +; el.innerHTML = '' +(assignment_expression + left: (member_expression + property: (property_identifier) @_prop + (#any-of? @_prop "outerHTML" "innerHTML")) + right: (string + (string_fragment) @injection.content) + (#set! injection.language "html")) + +;---- Angular injections ----- +; @Component({ +; template: `` +; }) +(decorator + (call_expression + function: ((identifier) @_name + (#eq? @_name "Component")) + arguments: (arguments + (object + (pair + key: ((property_identifier) @_prop + (#eq? @_prop "template")) + value: ((template_string) @injection.content + (#offset! @injection.content 0 1 0 -1) + (#set! injection.include-children) + (#set! injection.language "angular"))))))) + +; @Component({ +; styles: [``] +; }) +(decorator + (call_expression + function: ((identifier) @_name + (#eq? @_name "Component")) + arguments: (arguments + (object + (pair + key: ((property_identifier) @_prop + (#eq? @_prop "styles")) + value: (array + ((template_string) @injection.content + (#offset! @injection.content 0 1 0 -1) + (#set! injection.include-children) + (#set! injection.language "css")))))))) + +; @Component({ +; styles: `` +; }) +(decorator + (call_expression + function: ((identifier) @_name + (#eq? @_name "Component")) + arguments: (arguments + (object + (pair + key: ((property_identifier) @_prop + (#eq? @_prop "styles")) + value: ((template_string) @injection.content + (#set! injection.include-children) + (#offset! @injection.content 0 1 0 -1) + (#set! injection.language "css"))))))) diff --git a/config/nvim/queries/ecma/locals.scm b/config/nvim/queries/ecma/locals.scm new file mode 100644 index 0000000..24ea7c0 --- /dev/null +++ b/config/nvim/queries/ecma/locals.scm @@ -0,0 +1,42 @@ +; Scopes +;------- +(statement_block) @local.scope + +(function_expression) @local.scope + +(arrow_function) @local.scope + +(function_declaration) @local.scope + +(method_definition) @local.scope + +(for_statement) @local.scope + +(for_in_statement) @local.scope + +(catch_clause) @local.scope + +; Definitions +;------------ +(variable_declarator + name: (identifier) @local.definition.var) + +(import_specifier + (identifier) @local.definition.import) + +(namespace_import + (identifier) @local.definition.import) + +(function_declaration + (identifier) @local.definition.function + (#set! definition.var.scope parent)) + +(method_definition + (property_identifier) @local.definition.function + (#set! definition.var.scope parent)) + +; References +;------------ +(identifier) @local.reference + +(shorthand_property_identifier) @local.reference diff --git a/config/nvim/queries/ecma/textobjects.scm b/config/nvim/queries/ecma/textobjects.scm new file mode 100644 index 0000000..01c944c --- /dev/null +++ b/config/nvim/queries/ecma/textobjects.scm @@ -0,0 +1,372 @@ +(function_declaration + body: (statement_block)) @function.outer + +(generator_function_declaration + body: (statement_block)) @function.outer + +(function_expression + body: (statement_block)) @function.outer + +(function_declaration + body: (statement_block + . + "{" + _+ @function.inner + "}")) + +(generator_function_declaration + body: (statement_block + . + "{" + _+ @function.inner + "}")) + +(function_expression + body: (statement_block + . + "{" + _+ @function.inner + "}")) + +(export_statement + (function_declaration)) @function.outer + +(arrow_function + body: (_) @function.inner) @function.outer + +(arrow_function + body: (statement_block + . + "{" + _+ @function.inner + "}")) + +(method_definition + body: (statement_block)) @function.outer + +(method_definition + body: (statement_block + . + "{" + _+ @function.inner + "}")) + +(class_declaration + body: (class_body)) @class.outer + +(class_declaration + body: (class_body + . + "{" + _+ @class.inner + "}")) + +(export_statement + (class_declaration)) @class.outer + +(for_in_statement + body: (statement_block + . + "{" + _+ @loop.inner + "}")) @loop.outer + +(for_statement + body: (statement_block + . + "{" + _+ @loop.inner + "}")) @loop.outer + +(while_statement + body: (statement_block + . + "{" + _+ @loop.inner + "}")) @loop.outer + +(do_statement + body: (statement_block + . + "{" + _+ @loop.inner + "}")) @loop.outer + +(if_statement + consequence: (statement_block + . + "{" + _+ @conditional.inner + "}")) @conditional.outer + +(if_statement + alternative: (else_clause + (statement_block + . + "{" + _+ @conditional.inner + "}"))) @conditional.outer + +(if_statement) @conditional.outer + +(switch_statement + body: (_)? @conditional.inner) @conditional.outer + +(call_expression) @call.outer + +(call_expression + arguments: (arguments + . + "(" + _+ @call.inner + ")")) + +(new_expression + constructor: (identifier) @call.outer + arguments: (arguments + . + "(" + _+ @call.inner + ")") @call.outer) + +; blocks +(statement_block + (_)* @block.inner) @block.outer + +; parameters +; function ({ x }) ... +; function ([ x ]) ... +; function (v = default_value) +(formal_parameters + "," @parameter.outer + . + (_) @parameter.inner @parameter.outer) + +(formal_parameters + . + (_) @parameter.inner @parameter.outer + . + ","? @parameter.outer) + +; last element with trailing comma +(formal_parameters + (_) @parameter.outer + . + "," @parameter.outer .) + +; If the array/object pattern is the first parameter, treat its elements as the argument list +(formal_parameters + . + (_ + [ + (object_pattern + "," @parameter.outer + . + (_) @parameter.inner @parameter.outer) + (array_pattern + "," @parameter.outer + . + (_) @parameter.inner @parameter.outer) + ])) + +(formal_parameters + . + (_ + [ + (object_pattern + . + (_) @parameter.inner @parameter.outer + . + ","? @parameter.outer) + (array_pattern + . + (_) @parameter.inner @parameter.outer + . + ","? @parameter.outer) + ])) + +; last element with trailing comma +(formal_parameters + . + (_ + [ + (object_pattern + (_) @parameter.outer + . + "," @parameter.outer .) + (array_pattern + (_) @parameter.outer + . + "," @parameter.outer .) + ])) + +; arguments +(arguments + "," @parameter.outer + . + (_) @parameter.inner @parameter.outer) + +(arguments + . + (_) @parameter.inner @parameter.outer + . + ","? @parameter.outer) + +; last element with trailing comma +(arguments + (_) @parameter.outer + . + "," @parameter.outer .) + +; comment +(comment) @comment.outer + +; regex +(regex + (regex_pattern) @regex.inner) @regex.outer + +; number +(number) @number.inner + +(lexical_declaration + (variable_declarator + name: (_) @assignment.lhs + value: (_) @assignment.inner @assignment.rhs)) @assignment.outer + +(variable_declarator + name: (_) @assignment.inner) + +(object + (pair + key: (_) @assignment.lhs + value: (_) @assignment.inner @assignment.rhs) @assignment.outer) + +(return_statement + (_) @return.inner) @return.outer + +(return_statement) @statement.outer + +[ + (if_statement) + (expression_statement) + (for_statement) + (while_statement) + (do_statement) + (for_in_statement) + (export_statement) + (lexical_declaration) +] @statement.outer + +; 1. default import +(import_statement + (import_clause + (identifier) @parameter.inner @parameter.outer)) + +; 2. namespace import e.g. `* as React` +(import_statement + (import_clause + (namespace_import + (identifier) @parameter.inner) @parameter.outer)) + +; 3. named import e.g. `import { Bar, Baz } from ...` +(import_statement + (import_clause + (named_imports + (import_specifier) @parameter.inner))) + +; 3‑A. named import followed by a comma +(import_statement + (import_clause + (named_imports + (import_specifier) @parameter.outer + . + "," @parameter.outer))) + +; 3‑B. comma followed by named import +(import_statement + (import_clause + (named_imports + "," @parameter.outer + . + (import_specifier) @parameter.outer))) + +; 3-C. only one named import without a comma +(import_statement + (import_clause + (named_imports + . + (import_specifier) @parameter.outer .))) + +; Treat list or object elements as @parameter +; 1. parameter.inner +(object + (_) @parameter.inner) + +(array + (_) @parameter.inner) + +(object_pattern + (_) @parameter.inner) + +(array_pattern + (_) @parameter.inner) + +; 2. parameter.outer: Only one element, no comma +(object + . + (_) @parameter.outer .) + +(array + . + (_) @parameter.outer .) + +(object_pattern + . + (_) @parameter.outer .) + +(array_pattern + . + (_) @parameter.outer .) + +; 3. parameter.outer: Comma before or after +[ + (object + "," @parameter.outer + . + (_) @parameter.outer) + (array + "," @parameter.outer + . + (_) @parameter.outer) + (object_pattern + "," @parameter.outer + . + (_) @parameter.outer) + (array_pattern + "," @parameter.outer + . + (_) @parameter.outer) +] + +[ + (object + . + (_) @parameter.outer + . + "," @parameter.outer) + (array + . + (_) @parameter.outer + . + "," @parameter.outer) + (object_pattern + . + (_) @parameter.outer + . + "," @parameter.outer) + (array_pattern + . + (_) @parameter.outer + . + "," @parameter.outer) +] diff --git a/config/nvim/queries/go/folds.scm b/config/nvim/queries/go/folds.scm new file mode 100644 index 0000000..44b452d --- /dev/null +++ b/config/nvim/queries/go/folds.scm @@ -0,0 +1,19 @@ +[ + (const_declaration) + (expression_switch_statement) + (expression_case) + (default_case) + (type_switch_statement) + (type_case) + (for_statement) + (func_literal) + (function_declaration) + (if_statement) + (import_declaration) + (method_declaration) + (type_declaration) + (var_declaration) + (composite_literal) + (literal_element) + (block) +] @fold diff --git a/config/nvim/queries/go/highlights.scm b/config/nvim/queries/go/highlights.scm new file mode 100644 index 0000000..7675cb7 --- /dev/null +++ b/config/nvim/queries/go/highlights.scm @@ -0,0 +1,254 @@ +; Forked from tree-sitter-go +; Copyright (c) 2014 Max Brunsfeld (The MIT License) +; +; Identifiers +(type_identifier) @type + +(type_spec + name: (type_identifier) @type.definition) + +(field_identifier) @property + +(identifier) @variable + +(package_identifier) @module + +(parameter_declaration + (identifier) @variable.parameter) + +(variadic_parameter_declaration + (identifier) @variable.parameter) + +(label_name) @label + +(const_spec + name: (identifier) @constant) + +; Function calls +(call_expression + function: (identifier) @function.call) + +(call_expression + function: (selector_expression + field: (field_identifier) @function.method.call)) + +; Function definitions +(function_declaration + name: (identifier) @function) + +(method_declaration + name: (field_identifier) @function.method) + +(method_elem + name: (field_identifier) @function.method) + +; Constructors +((call_expression + (identifier) @constructor) + (#lua-match? @constructor "^[nN]ew.+$")) + +((call_expression + (identifier) @constructor) + (#lua-match? @constructor "^[mM]ake.+$")) + +; Operators +[ + "--" + "-" + "-=" + ":=" + "!" + "!=" + "..." + "*" + "*" + "*=" + "/" + "/=" + "&" + "&&" + "&=" + "&^" + "&^=" + "%" + "%=" + "^" + "^=" + "+" + "++" + "+=" + "<-" + "<" + "<<" + "<<=" + "<=" + "=" + "==" + ">" + ">=" + ">>" + ">>=" + "|" + "|=" + "||" + "~" +] @operator + +; Keywords +[ + "break" + "const" + "continue" + "default" + "defer" + "goto" + "range" + "select" + "var" + "fallthrough" +] @keyword + +[ + "type" + "struct" + "interface" +] @keyword.type + +"func" @keyword.function + +"return" @keyword.return + +"go" @keyword.coroutine + +"for" @keyword.repeat + +[ + "import" + "package" +] @keyword.import + +[ + "else" + "case" + "switch" + "if" +] @keyword.conditional + +; Builtin types +[ + "chan" + "map" +] @type.builtin + +((type_identifier) @type.builtin + (#any-of? @type.builtin + "any" "bool" "byte" "comparable" "complex128" "complex64" "error" "float32" "float64" "int" + "int16" "int32" "int64" "int8" "rune" "string" "uint" "uint16" "uint32" "uint64" "uint8" + "uintptr")) + +; Builtin functions +((identifier) @function.builtin + (#any-of? @function.builtin + "append" "cap" "clear" "close" "complex" "copy" "delete" "imag" "len" "make" "max" "min" "new" + "panic" "print" "println" "real" "recover")) + +; Delimiters +"." @punctuation.delimiter + +"," @punctuation.delimiter + +":" @punctuation.delimiter + +";" @punctuation.delimiter + +"(" @punctuation.bracket + +")" @punctuation.bracket + +"{" @punctuation.bracket + +"}" @punctuation.bracket + +"[" @punctuation.bracket + +"]" @punctuation.bracket + +; Literals +(interpreted_string_literal) @string + +(raw_string_literal) @string + +(rune_literal) @string + +(escape_sequence) @string.escape + +(int_literal) @number + +(float_literal) @number.float + +(imaginary_literal) @number + +[ + (true) + (false) +] @boolean + +[ + (nil) + (iota) +] @constant.builtin + +(keyed_element + . + (literal_element + (identifier) @variable.member)) + +(field_declaration + name: (field_identifier) @variable.member) + +; Comments +(comment) @comment @spell + +; Doc Comments +(source_file + . + (comment)+ @comment.documentation) + +(source_file + (comment)+ @comment.documentation + . + (const_declaration)) + +(source_file + (comment)+ @comment.documentation + . + (function_declaration)) + +(source_file + (comment)+ @comment.documentation + . + (type_declaration)) + +(source_file + (comment)+ @comment.documentation + . + (var_declaration)) + +; Spell +((interpreted_string_literal) @spell + (#not-has-parent? @spell import_spec)) + +; Regex +(call_expression + (selector_expression) @_function + (#any-of? @_function + "regexp.Match" "regexp.MatchReader" "regexp.MatchString" "regexp.Compile" "regexp.CompilePOSIX" + "regexp.MustCompile" "regexp.MustCompilePOSIX") + (argument_list + . + [ + (raw_string_literal + (raw_string_literal_content) @string.regexp) + (interpreted_string_literal + (interpreted_string_literal_content) @string.regexp) + ])) diff --git a/config/nvim/queries/go/injections.scm b/config/nvim/queries/go/injections.scm new file mode 100644 index 0000000..4e914a3 --- /dev/null +++ b/config/nvim/queries/go/injections.scm @@ -0,0 +1,42 @@ +((comment) @injection.content + (#set! injection.language "comment")) + +(call_expression + (selector_expression) @_function + (#any-of? @_function + "regexp.Match" "regexp.MatchReader" "regexp.MatchString" "regexp.Compile" "regexp.CompilePOSIX" + "regexp.MustCompile" "regexp.MustCompilePOSIX") + (argument_list + . + [ + (raw_string_literal + (raw_string_literal_content) @injection.content) + (interpreted_string_literal + (interpreted_string_literal_content) @injection.content) + ] + (#set! injection.language "regex"))) + +((comment) @injection.content + (#match? @injection.content "/\\*!([a-zA-Z]+:)?re2c") + (#set! injection.language "re2c")) + +((call_expression + function: (selector_expression + field: (field_identifier) @_method) + arguments: (argument_list + . + (interpreted_string_literal + (interpreted_string_literal_content) @injection.content))) + (#any-of? @_method "Printf" "Sprintf" "Fatalf" "Scanf" "Errorf" "Skipf" "Logf") + (#set! injection.language "printf")) + +((call_expression + function: (selector_expression + field: (field_identifier) @_method) + arguments: (argument_list + (_) + . + (interpreted_string_literal + (interpreted_string_literal_content) @injection.content))) + (#any-of? @_method "Fprintf" "Fscanf" "Appendf" "Sscanf") + (#set! injection.language "printf")) diff --git a/config/nvim/queries/go/locals.scm b/config/nvim/queries/go/locals.scm new file mode 100644 index 0000000..608c458 --- /dev/null +++ b/config/nvim/queries/go/locals.scm @@ -0,0 +1,88 @@ +((function_declaration + name: (identifier) @local.definition.function) ; @function + ) + +((method_declaration + name: (field_identifier) @local.definition.method) ; @function.method + ) + +(short_var_declaration + left: (expression_list + (identifier) @local.definition.var)) + +(var_spec + name: (identifier) @local.definition.var) + +(parameter_declaration + (identifier) @local.definition.var) + +(variadic_parameter_declaration + (identifier) @local.definition.var) + +(for_statement + (range_clause + left: (expression_list + (identifier) @local.definition.var))) + +(const_declaration + (const_spec + name: (identifier) @local.definition.var)) + +(type_declaration + (type_spec + name: (type_identifier) @local.definition.type)) + +; reference +(identifier) @local.reference + +(type_identifier) @local.reference + +(field_identifier) @local.reference + +((package_identifier) @local.reference + (#set! reference.kind "namespace")) + +(package_clause + (package_identifier) @local.definition.namespace) + +(import_spec_list + (import_spec + name: (package_identifier) @local.definition.namespace)) + +; Call references +((call_expression + function: (identifier) @local.reference) + (#set! reference.kind "call")) + +((call_expression + function: (selector_expression + field: (field_identifier) @local.reference)) + (#set! reference.kind "call")) + +((call_expression + function: (parenthesized_expression + (identifier) @local.reference)) + (#set! reference.kind "call")) + +((call_expression + function: (parenthesized_expression + (selector_expression + field: (field_identifier) @local.reference))) + (#set! reference.kind "call")) + +; Scopes +(func_literal) @local.scope + +(source_file) @local.scope + +(function_declaration) @local.scope + +(if_statement) @local.scope + +(block) @local.scope + +(expression_switch_statement) @local.scope + +(for_statement) @local.scope + +(method_declaration) @local.scope diff --git a/config/nvim/queries/go/textobjects.scm b/config/nvim/queries/go/textobjects.scm new file mode 100644 index 0000000..ce11749 --- /dev/null +++ b/config/nvim/queries/go/textobjects.scm @@ -0,0 +1,153 @@ +; inner function textobject +(function_declaration + body: (block + . + "{" + _+ @function.inner + "}")) + +; inner function literals +(func_literal + body: (block + . + "{" + _+ @function.inner + "}")) + +; method as inner function textobject +(method_declaration + body: (block + . + "{" + _+ @function.inner + "}")) + +; outer function textobject +(function_declaration) @function.outer + +; outer function literals +(func_literal + (_)?) @function.outer + +; method as outer function textobject +(method_declaration + body: (block)?) @function.outer + +; struct and interface declaration as class textobject? +(type_declaration + (type_spec + (type_identifier) + (struct_type + (field_declaration_list + (_)?) @class.inner))) @class.outer + +(type_declaration + (type_spec + (type_identifier) + (interface_type) @class.inner)) @class.outer + +; struct literals as class textobject +(composite_literal + (type_identifier)? + (struct_type + (_))? + (literal_value + (_)) @class.inner) @class.outer + +; conditionals +(if_statement + alternative: (_ + (_) @conditional.inner)?) @conditional.outer + +(if_statement + consequence: (block)? @conditional.inner) + +(if_statement + condition: (_) @conditional.inner) + +; loops +(for_statement + body: (block)? @loop.inner) @loop.outer + +; blocks +(_ + (block) @block.inner) @block.outer + +; statements +(block + (_) @statement.outer) + +; comments +(comment) @comment.outer + +; calls +(call_expression) @call.outer + +(call_expression + arguments: (argument_list + . + "(" + _+ @call.inner + ")")) + +; parameters +(parameter_list + "," @parameter.outer + . + (parameter_declaration) @parameter.inner @parameter.outer) + +(parameter_list + . + (parameter_declaration) @parameter.inner @parameter.outer + . + ","? @parameter.outer) + +(parameter_declaration + name: (identifier) + type: (_)) @parameter.inner + +(parameter_declaration + name: (identifier) + type: (_)) @parameter.inner + +(parameter_list + "," @parameter.outer + . + (variadic_parameter_declaration) @parameter.inner @parameter.outer) + +; arguments +(argument_list + "," @parameter.outer + . + (_) @parameter.inner @parameter.outer) + +(argument_list + . + (_) @parameter.inner @parameter.outer + . + ","? @parameter.outer) + +; assignments +(short_var_declaration + left: (_) @assignment.lhs + right: (_) @assignment.rhs @assignment.inner) @assignment.outer + +(assignment_statement + left: (_) @assignment.lhs + right: (_) @assignment.rhs @assignment.inner) @assignment.outer + +(var_spec + name: (_) @assignment.lhs + value: (_) @assignment.rhs @assignment.inner) @assignment.outer + +(var_spec + name: (_) @assignment.inner + type: (_)) @assignment.outer + +(const_spec + name: (_) @assignment.lhs + value: (_) @assignment.rhs @assignment.inner) @assignment.outer + +(const_spec + name: (_) @assignment.inner + type: (_)) @assignment.outer diff --git a/config/nvim/queries/goon/highlights.scm b/config/nvim/queries/goon/highlights.scm new file mode 100644 index 0000000..893c012 --- /dev/null +++ b/config/nvim/queries/goon/highlights.scm @@ -0,0 +1,55 @@ +; Keywords +"let" @keyword +"if" @keyword.conditional +"then" @keyword.conditional +"else" @keyword.conditional +"import" @keyword.import + +; Literals +(string) @string +(string_content) @string +(escape_sequence) @string.escape +(number) @number +(boolean) @boolean + +; Identifiers +(identifier) @variable + +; Types +(type_annotation) @type + +; Fields +(record_field + key: (identifier) @property) + +(field_access + (identifier) @property) + +; Functions +(call_expression + function: (identifier) @function.call) + +; Operators +"=" @operator +":" @punctuation.delimiter +"?" @operator +"..." @operator +"." @punctuation.delimiter + +; Punctuation +"{" @punctuation.bracket +"}" @punctuation.bracket +"[" @punctuation.bracket +"]" @punctuation.bracket +"(" @punctuation.bracket +")" @punctuation.bracket +";" @punctuation.delimiter +"," @punctuation.delimiter + +; Interpolation +(interpolation + "${" @punctuation.special + "}" @punctuation.special) + +; Comments +(comment) @comment diff --git a/config/nvim/queries/javascript/folds.scm b/config/nvim/queries/javascript/folds.scm new file mode 100644 index 0000000..b6d9b28 --- /dev/null +++ b/config/nvim/queries/javascript/folds.scm @@ -0,0 +1 @@ +; inherits: ecma,jsx diff --git a/config/nvim/queries/javascript/highlights.scm b/config/nvim/queries/javascript/highlights.scm new file mode 100644 index 0000000..257a731 --- /dev/null +++ b/config/nvim/queries/javascript/highlights.scm @@ -0,0 +1,56 @@ +; inherits: ecma,jsx + +; Parameters +(formal_parameters + (identifier) @variable.parameter) + +(formal_parameters + (rest_pattern + (identifier) @variable.parameter)) + +; ({ a }) => null +(formal_parameters + (object_pattern + (shorthand_property_identifier_pattern) @variable.parameter)) + +; ({ a = b }) => null +(formal_parameters + (object_pattern + (object_assignment_pattern + (shorthand_property_identifier_pattern) @variable.parameter))) + +; ({ a: b }) => null +(formal_parameters + (object_pattern + (pair_pattern + value: (identifier) @variable.parameter))) + +; ([ a ]) => null +(formal_parameters + (array_pattern + (identifier) @variable.parameter)) + +; ({ a } = { a }) => null +(formal_parameters + (assignment_pattern + (object_pattern + (shorthand_property_identifier_pattern) @variable.parameter))) + +; ({ a = b } = { a }) => null +(formal_parameters + (assignment_pattern + (object_pattern + (object_assignment_pattern + (shorthand_property_identifier_pattern) @variable.parameter)))) + +; a => null +(arrow_function + parameter: (identifier) @variable.parameter) + +; optional parameters +(formal_parameters + (assignment_pattern + left: (identifier) @variable.parameter)) + +; punctuation +(optional_chain) @punctuation.delimiter diff --git a/config/nvim/queries/javascript/injections.scm b/config/nvim/queries/javascript/injections.scm new file mode 100644 index 0000000..b6d9b28 --- /dev/null +++ b/config/nvim/queries/javascript/injections.scm @@ -0,0 +1 @@ +; inherits: ecma,jsx diff --git a/config/nvim/queries/javascript/locals.scm b/config/nvim/queries/javascript/locals.scm new file mode 100644 index 0000000..6d6846f --- /dev/null +++ b/config/nvim/queries/javascript/locals.scm @@ -0,0 +1,69 @@ +; inherits: ecma,jsx + +; Both properties are matched here. +; +; class Foo { +; this.#bar = "baz"; +; this.quuz = "qux"; +; } +(field_definition + property: [ + (property_identifier) + (private_property_identifier) + ] @local.definition.var) + +; this.foo = "bar" +(assignment_expression + left: (member_expression + object: (this) + property: (property_identifier) @local.definition.var)) + +(formal_parameters + (identifier) @local.definition.parameter) + +; function(arg = []) { +(formal_parameters + (assignment_pattern + left: (identifier) @local.definition.parameter)) + +; x => x +(arrow_function + parameter: (identifier) @local.definition.parameter) + +; ({ a }) => null +(formal_parameters + (object_pattern + (shorthand_property_identifier_pattern) @local.definition.parameter)) + +; ({ a: b }) => null +(formal_parameters + (object_pattern + (pair_pattern + value: (identifier) @local.definition.parameter))) + +; ([ a ]) => null +(formal_parameters + (array_pattern + (identifier) @local.definition.parameter)) + +(formal_parameters + (rest_pattern + (identifier) @local.definition.parameter)) + +; Both methods are matched here. +; +; class Foo { +; #bar(x) { x } +; baz(y) { y } +; } +(method_definition + [ + (property_identifier) + (private_property_identifier) + ] @local.definition.function + (#set! definition.var.scope parent)) + +; this.foo() +(member_expression + object: (this) + property: (property_identifier) @local.reference) diff --git a/config/nvim/queries/javascript/textobjects.scm b/config/nvim/queries/javascript/textobjects.scm new file mode 100644 index 0000000..b6d9b28 --- /dev/null +++ b/config/nvim/queries/javascript/textobjects.scm @@ -0,0 +1 @@ +; inherits: ecma,jsx diff --git a/config/nvim/queries/jsx/folds.scm b/config/nvim/queries/jsx/folds.scm new file mode 100644 index 0000000..93c3d9c --- /dev/null +++ b/config/nvim/queries/jsx/folds.scm @@ -0,0 +1 @@ +(jsx_element) @fold diff --git a/config/nvim/queries/jsx/highlights.scm b/config/nvim/queries/jsx/highlights.scm new file mode 100644 index 0000000..0615d25 --- /dev/null +++ b/config/nvim/queries/jsx/highlights.scm @@ -0,0 +1,157 @@ +(jsx_element + open_tag: (jsx_opening_element + [ + "<" + ">" + ] @tag.delimiter)) + +(jsx_element + close_tag: (jsx_closing_element + [ + "" + ] @tag.delimiter)) + +(jsx_self_closing_element + [ + "<" + "/>" + ] @tag.delimiter) + +(jsx_attribute + (property_identifier) @tag.attribute) + +(jsx_opening_element + name: (identifier) @tag.builtin) + +(jsx_closing_element + name: (identifier) @tag.builtin) + +(jsx_self_closing_element + name: (identifier) @tag.builtin) + +(jsx_opening_element + ((identifier) @tag + (#lua-match? @tag "^[A-Z]"))) + +; Handle the dot operator effectively - +(jsx_opening_element + (member_expression + (identifier) @tag.builtin + (property_identifier) @tag)) + +(jsx_closing_element + ((identifier) @tag + (#lua-match? @tag "^[A-Z]"))) + +; Handle the dot operator effectively - +(jsx_closing_element + (member_expression + (identifier) @tag.builtin + (property_identifier) @tag)) + +(jsx_self_closing_element + ((identifier) @tag + (#lua-match? @tag "^[A-Z]"))) + +; Handle the dot operator effectively - +(jsx_self_closing_element + (member_expression + (identifier) @tag.builtin + (property_identifier) @tag)) + +(html_character_reference) @tag + +(jsx_text) @none @spell + +(html_character_reference) @character.special + +((jsx_element + (jsx_opening_element + name: (identifier) @_tag) + (jsx_text) @markup.heading) + (#eq? @_tag "title")) + +((jsx_element + (jsx_opening_element + name: (identifier) @_tag) + (jsx_text) @markup.heading.1) + (#eq? @_tag "h1")) + +((jsx_element + (jsx_opening_element + name: (identifier) @_tag) + (jsx_text) @markup.heading.2) + (#eq? @_tag "h2")) + +((jsx_element + (jsx_opening_element + name: (identifier) @_tag) + (jsx_text) @markup.heading.3) + (#eq? @_tag "h3")) + +((jsx_element + (jsx_opening_element + name: (identifier) @_tag) + (jsx_text) @markup.heading.4) + (#eq? @_tag "h4")) + +((jsx_element + (jsx_opening_element + name: (identifier) @_tag) + (jsx_text) @markup.heading.5) + (#eq? @_tag "h5")) + +((jsx_element + (jsx_opening_element + name: (identifier) @_tag) + (jsx_text) @markup.heading.6) + (#eq? @_tag "h6")) + +((jsx_element + (jsx_opening_element + name: (identifier) @_tag) + (jsx_text) @markup.strong) + (#any-of? @_tag "strong" "b")) + +((jsx_element + (jsx_opening_element + name: (identifier) @_tag) + (jsx_text) @markup.italic) + (#any-of? @_tag "em" "i")) + +((jsx_element + (jsx_opening_element + name: (identifier) @_tag) + (jsx_text) @markup.strikethrough) + (#any-of? @_tag "s" "del")) + +((jsx_element + (jsx_opening_element + name: (identifier) @_tag) + (jsx_text) @markup.underline) + (#eq? @_tag "u")) + +((jsx_element + (jsx_opening_element + name: (identifier) @_tag) + (jsx_text) @markup.raw) + (#any-of? @_tag "code" "kbd")) + +((jsx_element + (jsx_opening_element + name: (identifier) @_tag) + (jsx_text) @markup.link.label) + (#eq? @_tag "a")) + +((jsx_attribute + (property_identifier) @_attr + (string + (string_fragment) @string.special.url)) + (#any-of? @_attr "href" "src")) + +((jsx_element) @_jsx_element + (#set! @_jsx_element bo.commentstring "{/* %s */}")) + +((jsx_attribute) @_jsx_attribute + (#set! @_jsx_attribute bo.commentstring "// %s")) diff --git a/config/nvim/queries/jsx/injections.scm b/config/nvim/queries/jsx/injections.scm new file mode 100644 index 0000000..269ee3f --- /dev/null +++ b/config/nvim/queries/jsx/injections.scm @@ -0,0 +1,11 @@ +; Styled Jsx