diff options
Diffstat (limited to '.config/nvim/init.lua')
| -rw-r--r-- | .config/nvim/init.lua | 1141 |
1 files changed, 1136 insertions, 5 deletions
diff --git a/.config/nvim/init.lua b/.config/nvim/init.lua index 75ae3e9..fb59850 100644 --- a/.config/nvim/init.lua +++ b/.config/nvim/init.lua @@ -1,5 +1,1136 @@ -vim.g.bullets_enable_in_empty_buffers = 0 -require("config.lazy") -require("options") -require("mappings") -require("autocmds") +_G._startup_time = vim.loop.hrtime() +-- ============================================================================ +-- OPTIONS +-- ============================================================================ +vim.opt.termguicolors = true +vim.opt.number = true -- line number +vim.opt.relativenumber = true -- relative line numbers +vim.opt.cursorline = true -- highlight current line +vim.opt.wrap = false -- do not wrap lines by default +vim.opt.scrolloff = 8 -- keep 8 lines above/below cursor +vim.opt.sidescrolloff = 8 -- keep 8 lines to left/right of cursor + +vim.opt.tabstop = 4 -- tabwidth +vim.opt.shiftwidth = 4 -- indent width +vim.opt.softtabstop = 4 -- soft tab stop not tabs on tab/backspace +vim.opt.expandtab = true -- use spaces instead of tabs +vim.opt.smartindent = true -- smart auto-indent +vim.opt.autoindent = true -- copy indent from current line + +vim.opt.ignorecase = true -- case insensitive search +vim.opt.smartcase = true -- case sensitive if uppercase in string +vim.opt.hlsearch = true -- highlight search matches +vim.opt.incsearch = true -- show matches as you type + +vim.opt.signcolumn = "yes" -- always show a sign column +vim.opt.showmatch = true -- highlights matching brackets +vim.opt.cmdheight = 1 -- single line command line +vim.opt.completeopt = "menuone,noinsert,noselect" -- completion options +vim.opt.showmode = false -- do not show the mode, instead have it in statusline +vim.opt.pumheight = 10 -- popup menu height +vim.opt.pumblend = 10 -- popup menu transparency +vim.opt.winblend = 0 -- floating window transparency +vim.opt.conceallevel = 0 -- do not hide markup +vim.opt.concealcursor = "" -- do not hide cursorline in markup +vim.opt.lazyredraw = true -- do not redraw during macros +vim.opt.synmaxcol = 300 -- syntax highlighting limit +vim.opt.fillchars = { eob = " " } -- hide "~" on empty lines + +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 write to a backup file +vim.opt.swapfile = false -- do not create a swapfile +vim.opt.undofile = true -- do create an undo file +vim.opt.undodir = undodir -- set the undo directory +vim.opt.updatetime = 300 -- faster completion +vim.opt.timeoutlen = 500 -- timeout duration +vim.opt.ttimeoutlen = 0 -- key code timeout +vim.opt.autoread = true -- auto-reload changes if outside of neovim +vim.opt.autowrite = false -- do not auto-save + +vim.opt.hidden = true -- allow hidden buffers +vim.opt.errorbells = false -- no error sounds +vim.opt.backspace = "indent,eol,start" -- better backspace behaviour +vim.opt.autochdir = false -- do not autochange directories +vim.opt.iskeyword:append("-") -- include - in words +vim.opt.path:append("**") -- include subdirs in search +vim.opt.selection = "inclusive" -- include last char in selection +vim.opt.mouse = "a" -- enable mouse support +vim.opt.clipboard:append("unnamedplus") -- use system clipboard +vim.opt.modifiable = true -- allow buffer modifications +vim.opt.encoding = "utf-8" -- set encoding +vim.opt.listchars = { tab = "» ", trail = "·", nbsp = "␣" } + +-- vim.opt.guicursor = +-- "n-v-c:block,i-ci-ve:block,r-cr:hor20,o:hor50,a:blinkwait700-blinkoff400-blinkon250-Cursor/lCursor,sm:block-blinkwait175-blinkoff150-blinkon175" -- cursor blinking and settings + +-- Folding: requires treesitter available at runtime; safe fallback if not +vim.opt.foldmethod = "expr" -- use expression for folding +vim.opt.foldexpr = "v:lua.vim.treesitter.foldexpr()" -- use treesitter for folding +vim.opt.foldlevel = 99 -- start with all folds open + +vim.opt.splitbelow = true -- horizontal splits go below +vim.opt.splitright = true -- vertical splits go right + +vim.opt.wildmenu = true -- tab completion +vim.opt.wildmode = "longest:full,full" -- complete longest common match, full completion list, cycle through with Tab +vim.opt.diffopt:append("linematch:60") -- improve diff display +vim.opt.redrawtime = 10000 -- increase neovim redraw tolerance +vim.opt.maxmempattern = 20000 -- increase max memory +-- ============================================================================ +-- STATUSLINE +-- ============================================================================ + +-- Git branch function with caching and Nerd Font icon +local cached_branch = "" +local last_check = 0 +local function git_branch() + local now = vim.loop.now() + if now - last_check > 5000 then -- Check every 5 seconds + cached_branch = vim.fn.system("git branch --show-current 2>/dev/null | tr -d '\n'") + last_check = now + end + if cached_branch ~= "" then + return "\u{e725} " .. cached_branch .. " " -- nf-dev-git_branch + end + return "" +end + +-- File type with Nerd Font icon +local function file_type() + local ft = vim.bo.filetype + local icons = { + lua = "\u{e620} ", -- nf-dev-lua + python = "\u{e73c} ", -- nf-dev-python + javascript = "\u{e74e} ", -- nf-dev-javascript + typescript = "\u{e628} ", -- nf-dev-typescript + javascriptreact = "\u{e7ba} ", + typescriptreact = "\u{e7ba} ", + html = "\u{e736} ", -- nf-dev-html5 + css = "\u{e749} ", -- nf-dev-css3 + scss = "\u{e749} ", + json = "\u{e60b} ", -- nf-dev-json + markdown = "\u{e73e} ", -- nf-dev-markdown + vim = "\u{e62b} ", -- nf-dev-vim + sh = "\u{f489} ", -- nf-oct-terminal + bash = "\u{f489} ", + zsh = "\u{f489} ", + rust = "\u{e7a8} ", -- nf-dev-rust + go = "\u{e724} ", -- nf-dev-go + c = "\u{e61e} ", -- nf-dev-c + cpp = "\u{e61d} ", -- nf-dev-cplusplus + java = "\u{e738} ", -- nf-dev-java + php = "\u{e73d} ", -- nf-dev-php + ruby = "\u{e739} ", -- nf-dev-ruby + swift = "\u{e755} ", -- nf-dev-swift + kotlin = "\u{e634} ", + dart = "\u{e798} ", + elixir = "\u{e62d} ", + haskell = "\u{e777} ", + sql = "\u{e706} ", + yaml = "\u{f481} ", + toml = "\u{e615} ", + xml = "\u{f05c} ", + dockerfile = "\u{f308} ", -- nf-linux-docker + gitcommit = "\u{f418} ", -- nf-oct-git_commit + gitconfig = "\u{f1d3} ", -- nf-fa-git + vue = "\u{fd42} ", -- nf-md-vuejs + svelte = "\u{e697} ", + astro = "\u{e628} ", + } + + if ft == "" then + return " \u{f15b} " -- nf-fa-file_o + end + + return ((icons[ft] or " \u{f15b} ") .. ft) +end + +-- File size with Nerd Font icon +local function file_size() + local size = vim.fn.getfsize(vim.fn.expand("%")) + if size < 0 then + return "" + end + local size_str + if size < 1024 then + size_str = size .. "B" + elseif size < 1024 * 1024 then + size_str = string.format("%.1fK", size / 1024) + else + size_str = string.format("%.1fM", size / 1024 / 1024) + end + return " \u{f016} " .. size_str .. " " -- nf-fa-file_o +end + +-- Mode indicators with Nerd Font icons +local function mode_icon() + local mode = vim.fn.mode() + local modes = { + n = " \u{f121} NORMAL", + i = " \u{f11c} INSERT", + v = " \u{f0168} VISUAL", + V = " \u{f0168} V-LINE", + ["\22"] = " \u{f0168} V-BLOCK", + c = " \u{f120} COMMAND", + s = " \u{f0c5} SELECT", + S = " \u{f0c5} S-LINE", + ["\19"] = " \u{f0c5} S-BLOCK", + R = " \u{f044} REPLACE", + r = " \u{f044} REPLACE", + ["!"] = " \u{f489} SHELL", + t = " \u{f120} TERMINAL", + } + return modes[mode] or (" \u{f059} " .. mode) +end + +_G.mode_icon = mode_icon +_G.git_branch = git_branch +_G.file_type = file_type +_G.file_size = file_size + +vim.cmd([[ + highlight StatusLineBold gui=bold cterm=bold +]]) + +-- Function to change statusline based on window focus +local function setup_dynamic_statusline() + vim.api.nvim_create_autocmd({ "WinEnter", "BufEnter" }, { + callback = function() + vim.opt_local.statusline = table.concat({ + " ", + "%#StatusLineBold#", + "%{v:lua.mode_icon()}", + "%#StatusLine#", + " | %f %h%m%r", -- nf-pl-left_hard_divider + " %{v:lua.git_branch()}", + " | ", -- nf-pl-left_hard_divider + "%{v:lua.file_type()}", + " | ", -- nf-pl-left_hard_divider + "%{v:lua.file_size()}", + "%=", -- Right-align everything after this + "%l:%c %P ", -- nf-fa-clock_o for line/col + }) + end, + }) + vim.api.nvim_set_hl(0, "StatusLineBold", { bold = true }) + + vim.api.nvim_create_autocmd({ "WinLeave", "BufLeave" }, { + callback = function() + vim.opt_local.statusline = " %f %h%m%r | %{v:lua.file_type()} %= %l:%c %P " + end, + }) +end + +setup_dynamic_statusline() + +-- ============================================================================ +-- KEYMAPS +-- ============================================================================ +vim.g.mapleader = " " -- space for leader +vim.g.maplocalleader = " " -- space for localleader + +-- better movement in wrapped text +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)" }) + +vim.keymap.set("n", "<Esc>", ":nohlsearch<CR>", { desc = "Clear search highlights", silent = true }) + +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", "<C-d>", "<C-d>zz", { desc = "Half page down (centered)" }) +vim.keymap.set("n", "<C-u>", "<C-u>zz", { desc = "Half page up (centered)" }) + +vim.keymap.set("n", "<C-h>", "<C-w>h", { desc = "Move to left window" }) +vim.keymap.set("n", "<C-j>", "<C-w>j", { desc = "Move to bottom window" }) +vim.keymap.set("n", "<C-k>", "<C-w>k", { desc = "Move to top window" }) +vim.keymap.set("n", "<C-l>", "<C-w>l", { desc = "Move to right window" }) + +-- vim.keymap.set("n", "<leader>sv", ":vsplit<CR>", { desc = "Split window vertically" }) +-- vim.keymap.set("n", "<leader>sh", ":split<CR>", { desc = "Split window horizontally" }) +-- vim.keymap.set("n", "<C-Up>", ":resize +2<CR>", { desc = "Increase window height" }) +-- vim.keymap.set("n", "<C-Down>", ":resize -2<CR>", { desc = "Decrease window height" }) +-- vim.keymap.set("n", "<C-Left>", ":vertical resize -2<CR>", { desc = "Decrease window width" }) +-- vim.keymap.set("n", "<C-Right>", ":vertical resize +2<CR>", { desc = "Increase window width" }) + +vim.keymap.set("n", "<A-j>", ":m .+1<CR>==", { desc = "Move line down" }) +vim.keymap.set("n", "<A-k>", ":m .-2<CR>==", { desc = "Move line up" }) +vim.keymap.set("v", "<A-j>", ":m '>+1<CR>gv=gv", { desc = "Move selection down" }) +vim.keymap.set("v", "<A-k>", ":m '<-2<CR>gv=gv", { desc = "Move selection up" }) + +vim.keymap.set("v", "<", "<gv", { desc = "Indent left and reselect" }) +vim.keymap.set("v", ">", ">gv", { desc = "Indent right and reselect" }) + +vim.keymap.set("n", "J", "mzJ`z", { desc = "Join lines and keep cursor position" }) + +vim.keymap.set("n", "x", '"_x', { noremap = true, silent = true }) +vim.keymap.set("x", "x", '"_x', { noremap = true, silent = true }) +vim.keymap.set("v", "x", '"_x', { noremap = true, silent = true }) +vim.keymap.set("n", "c", '"_c') +vim.keymap.set("n", "C", '"_C') +vim.keymap.set("n", "cc", '"_cc') +vim.keymap.set("x", "c", '"_c') + +vim.keymap.set("n", "<leader>pa", function() -- show file path + local path = vim.fn.expand("%:p") + vim.fn.setreg("+", path) + print("file:", path) +end, { desc = "Copy full file path" }) + +vim.keymap.set("n", "<leader>td", function() + vim.diagnostic.enable(not vim.diagnostic.is_enabled()) +end, { desc = "Toggle diagnostics" }) +vim.keymap.set("n", "<leader>tw", function() + vim.diagnostic.enable(not vim.diagnostic.is_enabled()) + vim.wo.wrap = not vim.wo.wrap +end, { desc = "Toggle wrap" }) + +-- ============================================================================ +-- AUTOCMDS +-- ============================================================================ + +local augroup = vim.api.nvim_create_augroup("UserConfig", { clear = true }) + +-- Format on save (ONLY real file buffers, ONLY when efm is attached) +vim.api.nvim_create_autocmd("BufWritePre", { + group = augroup, + pattern = { + "*.lua", + "*.py", + "*.go", + "*.json", + "*.sh", + "*.bash", + "*.zsh", + "*.c", + "*.cpp", + "*.h", + "*.hpp", + "*.rs", + }, + 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, +}) + +-- highlight yanked text +vim.api.nvim_create_autocmd("TextYankPost", { + group = augroup, + callback = function() + vim.hl.on_yank() + end, +}) + +-- return to last cursor position +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, +}) + +-- wrap, linebreak and spellcheck on markdown and text files +vim.api.nvim_create_autocmd("FileType", { + group = augroup, + pattern = { "markdown", "text", "gitcommit" }, + callback = function() + vim.opt_local.wrap = true + vim.opt_local.linebreak = true + vim.opt_local.spell = true + end, +}) +-- ============================================================================ +-- PLUGINS (vim.pack) +-- ============================================================================ +vim.pack.add({ + "https://www.github.com/lewis6991/gitsigns.nvim", + "https://www.github.com/echasnovski/mini.nvim", + "https://www.github.com/ibhagwan/fzf-lua", + "https://www.github.com/nvim-tree/nvim-tree.lua", + { + src = "https://github.com/nvim-treesitter/nvim-treesitter", + branch = "main", + build = ":TSUpdate", + }, + -- Language Server Protocols + "https://www.github.com/neovim/nvim-lspconfig", + "https://github.com/mason-org/mason.nvim", + "https://github.com/creativenull/efmls-configs-nvim", + { + src = "https://github.com/saghen/blink.cmp", + version = vim.version.range("1.*"), + }, + "https://github.com/L3MON4D3/LuaSnip", + "https://github.com/shaunsingh/nord.nvim", + "https://github.com/kawre/leetcode.nvim", + "https://github.com/nvim-lua/plenary.nvim", + "https://github.com/MunifTanjim/nui.nvim", + "https://github.com/mrcjkb/rustaceanvim", + "https://github.com/folke/which-key.nvim", + "https://github.com/hedyhli/outline.nvim", + "https://github.com/stevearc/oil.nvim", + "https://github.com/folke/flash.nvim", + "https://github.com/goolord/alpha-nvim", +}) + +local function packadd(name) + vim.cmd("packadd " .. name) +end +packadd("nvim-treesitter") +packadd("gitsigns.nvim") +packadd("mini.nvim") +packadd("fzf-lua") +packadd("nvim-tree.lua") +-- LSP +packadd("nvim-lspconfig") +packadd("mason.nvim") +packadd("efmls-configs-nvim") +packadd("blink.cmp") +packadd("LuaSnip") +packadd("plenary.nvim") +packadd("nui.nvim") +packadd("rustaceanvim") + +-- ============================================================================ +-- TRANSPARENCY +-- ============================================================================ + +local function set_transparent() -- set UI component to transparent + local groups = { + "Normal", + "NormalNC", + "EndOfBuffer", + "NormalFloat", + "FloatBorder", + "SignColumn", + "StatusLine", + "StatusLineNC", + "TabLine", + "TabLineFill", + "TabLineSel", + "ColorColumn", + } + for _, g in ipairs(groups) do + vim.api.nvim_set_hl(0, g, { bg = "none" }) + end + vim.api.nvim_set_hl(0, "TabLineFill", { bg = "none", fg = "#767676" }) +end + +set_transparent() +vim.cmd.colorscheme("nord") +vim.cmd.hi("Comment gui=none") +-- Set transparency +vim.cmd("hi Normal guibg=NONE ctermbg=NONE") +vim.cmd("hi NormalFloat guibg=NONE ctermbg=NONE") +vim.cmd("hi SignColumn guibg=NONE") +vim.cmd("hi FloatBorder guibg=NONE") +vim.cmd("hi CursorLine guibg=NONE") + +-- ============================================================================ +-- PLUGIN CONFIGS +-- ============================================================================ + +-- ALPHA +local setup_alpha = function() + local alpha = require("alpha") + local dashboard = require("alpha.themes.dashboard") + dashboard.section.header.val = { + [[ ]], + [[ ]], + [[ ]], + [[ ]], + [[ ]], + [[ ]], + [[ __ ]], + [[ ___ ___ ___ __ __ /\_\ ___ ___ ]], + [[ / _ `\ / __`\ / __`\/\ \/\ \\/\ \ / __` __`\ ]], + [[/\ \/\ \/\ __//\ \_\ \ \ \_/ |\ \ \/\ \/\ \/\ \ ]], + [[\ \_\ \_\ \____\ \____/\ \___/ \ \_\ \_\ \_\ \_\]], + [[ \/_/\/_/\/____/\/___/ \/__/ \/_/\/_/\/_/\/_/]], + } + + dashboard.section.buttons.val = { + dashboard.button("e", " New file", ":ene <BAR> startinsert <CR>"), + dashboard.button("s", " Find file ", ":FzfLua files<CR>"), + dashboard.button("g", " Grep ", ":FzfLua live_grep<CR>"), + dashboard.button("q", " Quit", ":qa<CR>"), + } + local elapsed_ms = (vim.loop.hrtime() - _G._startup_time) / 1e6 + + dashboard.section.footer.val = { + ("Startup: %.2f ms"):format(elapsed_ms), + } + + dashboard.config.opts.noautocmd = true + + alpha.setup(dashboard.config) +end + +setup_alpha() + +-- FLASH +require("flash").setup({ + modes = { + search = { enabled = false }, + char = { enabled = false }, + treesitter = { enabled = false }, + remote = { enabled = false }, + }, +}) +vim.keymap.set({ "n", "x", "o" }, "s", function() + require("flash").jump() +end, { desc = "Flash" }) + +vim.keymap.set({ "n", "x", "o" }, "R", function() + require("flash").treesitter() +end, { desc = "Flash Treesitter" }) + +vim.keymap.set("o", "r", function() + require("flash").remote() +end, { desc = "Remote Flash" }) + +-- OIL +require("oil").setup({ + default_file_explorer = true, + keymaps = { + ["l"] = "actions.select", + ["h"] = "actions.parent", + ["<CR>"] = "actions.select", + ["<C-s>"] = nil, + ["<C-h>"] = nil, + ["<C-l>"] = nil, + }, + delete_to_trash = true, + view_options = { + show_hidden = true, + }, + skip_confirm_for_simple_edits = true, + float = { + max_width = 80, + max_height = 30, + override = function(conf) + return conf + end, + }, +}) +vim.keymap.set("n", "-", "<CMD>Oil<CR>", { desc = "Open parent directory" }) + +vim.api.nvim_create_autocmd("FileType", { + pattern = "oil", + callback = function() + vim.opt_local.cursorline = true + end, +}) + +-- OUTLINE +require("outline").setup({}) + +-- TREESITTER +local setup_treesitter = function() + local treesitter = require("nvim-treesitter") + treesitter.setup({}) + local ensure_installed = { + "vim", + "vimdoc", + "rust", + "c", + "cpp", + "go", + "json", + "lua", + "markdown", + "python", + "bash", + "lua", + "python", + } + + local config = require("nvim-treesitter.config") + + 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 + + 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() + +-- NVIM-TREE + +require("nvim-tree").setup({ + view = { + side = "right", + width = 35, + }, + filters = { + dotfiles = false, + }, + renderer = { + group_empty = true, + }, +}) +vim.keymap.set("n", "<leader>e", function() + require("nvim-tree.api").tree.toggle() +end, { desc = "Toggle NvimTree" }) + +vim.api.nvim_set_hl(0, "NvimTreeNormalNC", { bg = "none" }) +vim.api.nvim_set_hl(0, "SignColumn", { bg = "none" }) +vim.api.nvim_set_hl(0, "NvimTreeSignColumn", { bg = "none" }) +vim.api.nvim_set_hl(0, "NvimTreeNormal", { bg = "none" }) +vim.api.nvim_set_hl(0, "NvimTreeWinSeparator", { fg = "#2a2a2a", bg = "none" }) +vim.api.nvim_set_hl(0, "NvimTreeEndOfBuffer", { bg = "none" }) + +-- WHICH-KEY +require("which-key").setup({ + win = { + wo = { + winblend = 0, + }, + }, + preset = "helix", + icons = { + rules = false, + }, + delay = 500, + -- spec = { + -- { "<leader>c", group = "Code", mode = { "n", "x" } }, + -- { "<leader>d", group = "Document" }, + -- { "<leader>g", group = "Git" }, + -- { "<leader>m", group = "Marks" }, + -- { "<leader>r", group = "Rename" }, + -- { "<leader>s", group = "Search" }, + -- { "<leader>t", group = "Toggle" }, + -- { "<leader>w", group = "Workspace" }, + -- { "<leader>l", group = "LSP" }, + -- }, +}) + +-- NOTIFICATION HISTORY +vim.keymap.set("n", "<leader>n", function() + local notify = require("mini.notify") + + local history = notify.get_all and notify.get_all() or {} + + local lines = {} + + for _, item in ipairs(history) do + local time = item.time and os.date("%H:%M:%S", item.time) or "" + table.insert(lines, time .. " | " .. (item.msg or "")) + end + + local buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines) + + local width = math.floor(vim.o.columns * 0.7) + local height = math.floor(vim.o.lines * 0.7) + + local win = vim.api.nvim_open_win(buf, true, { + relative = "editor", + width = width, + height = height, + row = math.floor((vim.o.lines - height) / 2), + col = math.floor((vim.o.columns - width) / 2), + style = "minimal", + border = "rounded", + }) + + vim.bo[buf].buftype = "nofile" + vim.bo[buf].swapfile = false + + vim.keymap.set("n", "q", function() + if vim.api.nvim_win_is_valid(win) then + vim.api.nvim_win_close(win, true) + end + if vim.api.nvim_buf_is_valid(buf) then + vim.api.nvim_buf_delete(buf, { force = true }) + end + end, { buffer = buf, silent = true }) +end, { desc = "Show notification history" }) + +-- FZF-LUA + +require("fzf-lua").setup({ + keymap = { + builtin = { + ["<S-j>"] = "preview-page-down", + ["<S-k>"] = "preview-page-up", + }, + }, + winopts = { + row = 1, + col = 0, + width = 1, + height = 0.4, + title_pos = "left", + preview = { + layout = "horizontal", + title_pos = "right", + }, + }, +}) + +vim.keymap.set("n", "<leader>s", function() + require("fzf-lua").files() +end, { desc = "FZF Files" }) +vim.keymap.set("n", "<leader>/", function() + require("fzf-lua").live_grep() +end, { desc = "FZF Live Grep" }) +vim.keymap.set("n", "<leader><leader>", function() + require("fzf-lua").buffers() +end, { desc = "FZF Buffers" }) +vim.keymap.set("n", "<leader>fh", function() + require("fzf-lua").help_tags() +end, { desc = "FZF Help Tags" }) +vim.keymap.set("n", "<leader>fx", function() + require("fzf-lua").diagnostics_document() +end, { desc = "FZF Diagnostics Document" }) +vim.keymap.set("n", "<leader>fX", function() + require("fzf-lua").diagnostics_workspace() +end, { desc = "FZF Diagnostics Workspace" }) + +-- MINI +require("mini.surround").setup({}) +require("mini.pairs").setup({}) +require("mini.ai").setup({}) +require("mini.comment").setup({}) +local MiniNotify = require("mini.notify") + +MiniNotify.setup({ + window = { + winblend = 0, + }, +}) + +vim.notify = MiniNotify.make_notify({ + ERROR = { duration = 4000 }, + WARN = { duration = 3000 }, + INFO = { duration = 2000 }, + DEBUG = { duration = 0 }, + TRACE = { duration = 0 }, +}) +require("mini.icons").setup({}) + +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, +}) + +-- MASON +require("mason").setup({}) + +-- GIT +vim.keymap.set("n", "]h", function() + require("gitsigns").next_hunk() +end, { desc = "Next git hunk" }) +vim.keymap.set("n", "[h", function() + require("gitsigns").prev_hunk() +end, { desc = "Previous git hunk" }) +vim.keymap.set("n", "<leader>hs", function() + require("gitsigns").stage_hunk() +end, { desc = "Stage hunk" }) +vim.keymap.set("n", "<leader>hr", function() + require("gitsigns").reset_hunk() +end, { desc = "Reset hunk" }) +vim.keymap.set("n", "<leader>hp", function() + require("gitsigns").preview_hunk() +end, { desc = "Preview hunk" }) +vim.keymap.set("n", "<leader>hb", function() + require("gitsigns").blame_line({ full = true }) +end, { desc = "Blame line" }) +vim.keymap.set("n", "<leader>hB", function() + require("gitsigns").toggle_current_line_blame() +end, { desc = "Toggle inline blame" }) +vim.keymap.set("n", "<leader>hd", function() + require("gitsigns").diffthis() +end, { desc = "Diff this" }) + +-- LEET CODE +require("leetcode").setup({ + lang = "rust", + storage = { + home = vim.fn.expand("~/leetcode"), + cache = vim.fn.stdpath("cache") .. "/leetcode", + }, + image_support = false, +}) + +-- ============================================================================ +-- LSP, Linting, Formatting & Completion +-- ============================================================================ +local diagnostic_signs = { + Error = " ", + Warn = " ", + Hint = "", + Info = "", +} + +vim.diagnostic.config({ + virtual_text = { prefix = "●", spacing = 4 }, + 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, + severity_sort = true, + float = { + border = "rounded", + source = "always", + header = "", + prefix = "", + focusable = false, + style = "minimal", + }, +}) + +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 + +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 opts = { noremap = true, silent = true, buffer = bufnr } + + vim.keymap.set("n", "<leader>gd", function() + require("fzf-lua").lsp_definitions({ jump_to_single_result = true }) + end, opts) + + vim.keymap.set("n", "<leader>gD", vim.lsp.buf.definition, opts) + + vim.keymap.set("n", "<leader>gS", function() + vim.cmd("vsplit") + vim.lsp.buf.definition() + end, opts) + + vim.keymap.set("n", "<leader>ca", vim.lsp.buf.code_action, opts) + vim.keymap.set("n", "<leader>rn", vim.lsp.buf.rename, opts) + + vim.keymap.set("n", "<leader>D", function() + vim.diagnostic.open_float({ scope = "line" }) + end, opts) + vim.keymap.set("n", "<leader>d", function() + vim.diagnostic.open_float({ scope = "cursor" }) + end, opts) + vim.keymap.set("n", "<leader>nd", function() + vim.diagnostic.jump({ count = 1 }) + end, opts) + + vim.keymap.set("n", "<leader>pd", function() + vim.diagnostic.jump({ count = -1 }) + end, opts) + + vim.keymap.set("n", "K", vim.lsp.buf.hover, opts) + + vim.keymap.set("n", "<leader>fd", function() + require("fzf-lua").lsp_definitions({ jump_to_single_result = true }) + end, opts) + vim.keymap.set("n", "<leader>fr", function() + require("fzf-lua").lsp_references() + end, opts) + vim.keymap.set("n", "<leader>ft", function() + require("fzf-lua").lsp_typedefs() + end, opts) + vim.keymap.set("n", "<leader>fs", function() + require("fzf-lua").lsp_document_symbols() + end, opts) + vim.keymap.set("n", "<leader>fw", function() + require("fzf-lua").lsp_workspace_symbols() + end, opts) + vim.keymap.set("n", "<leader>fi", function() + require("fzf-lua").lsp_implementations() + end, opts) + + if client:supports_method("textDocument/codeAction", bufnr) then + vim.keymap.set("n", "<leader>oi", function() + vim.lsp.buf.code_action({ + context = { only = { "source.organizeImports" }, diagnostics = {} }, + apply = true, + bufnr = bufnr, + }) + vim.defer_fn(function() + vim.lsp.buf.format({ bufnr = bufnr }) + end, 50) + end, opts) + end +end + +vim.api.nvim_create_autocmd("LspAttach", { group = augroup, callback = lsp_on_attach }) + +vim.keymap.set("n", "<leader>q", function() + vim.diagnostic.setloclist({ open = true }) +end, { desc = "Open diagnostic list" }) +vim.keymap.set("n", "<leader>dl", vim.diagnostic.open_float, { desc = "Show line diagnostics" }) + +-- AUTOCOMPLETE +require("blink.cmp").setup({ + keymap = { + preset = "default", + }, + appearance = { nerd_font_variant = "mono" }, + completion = { menu = { auto_show = true } }, + sources = { default = { "lsp", "path", "buffer", "snippets" } }, + snippets = { + expand = function(snippet) + require("luasnip").lsp_expand(snippet) + end, + }, + + fuzzy = { + implementation = "prefer_rust", + prebuilt_binaries = { download = true }, + }, +}) + +vim.lsp.config["*"] = { + capabilities = require("blink.cmp").get_lsp_capabilities(), +} + +vim.lsp.config("lua_ls", { + settings = { + Lua = { + diagnostics = { globals = { "vim" } }, + telemetry = { enable = false }, + }, + }, +}) +vim.lsp.config("pyright", { + settings = { + python = { + pythonPath = vim.fn.getcwd() .. "/.venv/bin/python", + + analysis = { + autoSearchPaths = true, + useLibraryCodeForTypes = true, + diagnosticMode = "workspace", + }, + }, + }, +}) +vim.lsp.config("bashls", {}) +vim.lsp.config("ts_ls", {}) +vim.lsp.config("gopls", {}) +vim.lsp.config("clangd", {}) + +local capabilities = vim.lsp.protocol.make_client_capabilities() + +local ok, blink = pcall(require, "blink.cmp") +if ok then + capabilities = blink.get_lsp_capabilities(capabilities) +end + +vim.g.rustaceanvim = { + tools = { + enable_clippy = false, + }, + + server = { + capabilities = capabilities, + + standalone = true, + + status_notify_level = false, + + load_vscode_settings = false, + + default_settings = { + ["rust-analyzer"] = { + checkOnSave = false, + }, + }, + }, +} + +do + local luacheck = require("efmls-configs.linters.luacheck") + local stylua = require("efmls-configs.formatters.stylua") + + local flake8_base = require("efmls-configs.linters.flake8") + + local function resolve_flake8() + local cwd = vim.fn.getcwd() + local venv_flake8 = cwd .. "/.venv/bin/flake8" + + if vim.fn.executable(venv_flake8) == 1 then + vim.notify("efm: using project flake8 → " .. venv_flake8, vim.log.levels.INFO) + return venv_flake8 + end + + if vim.fn.executable("flake8") == 1 then + return "flake8" + end + + return nil + end + + local flake8_cmd = resolve_flake8() + + local flake8 = nil + if flake8_cmd then + flake8 = vim.tbl_extend("force", flake8_base, { + lintCommand = flake8_cmd .. " --max-line-length=999 --stdin-display-name ${INPUT} -", + }) + end + 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 cpplint = require("efmls-configs.linters.cpplint") + local clangfmt = require("efmls-configs.formatters.clang_format") + + local go_revive = require("efmls-configs.linters.go_revive") + local gofumpt = require("efmls-configs.formatters.gofumpt") + + local rustfmt = require("efmls-configs.formatters.rustfmt") + + vim.lsp.config("efm", { + filetypes = { + "c", + "cpp", + "css", + "go", + "html", + "javascript", + "javascriptreact", + "json", + "jsonc", + "lua", + "markdown", + "python", + "sh", + "typescript", + "typescriptreact", + "vue", + "svelte", + }, + init_options = { documentFormatting = true }, + settings = { + languages = { + c = { clangfmt, cpplint }, + go = { gofumpt, go_revive }, + cpp = { clangfmt, cpplint }, + 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 }, + vue = { eslint_d, prettier_d }, + svelte = { eslint_d, prettier_d }, + rust = { rustfmt }, + }, + }, + }) +end + +vim.lsp.enable({ + "lua_ls", + "pyright", + "bashls", + "ts_ls", + "gopls", + "clangd", + "efm", +}) |
