diff options
| author | srdusr <trevorgray@srdusr.com> | 2025-09-24 02:55:49 +0200 |
|---|---|---|
| committer | srdusr <trevorgray@srdusr.com> | 2025-09-24 02:55:49 +0200 |
| commit | 3cf613ec7c90ab4933728b0f19e49b0c955c17bb (patch) | |
| tree | 765e58766936b5228ad473ad77dfbf4353f173e9 /common/nvim/lua/plugins | |
| parent | ef51a60993197ed3bbd1003522f98f0a898d34c6 (diff) | |
| parent | 966d12ac730c83da90d60ab24eae539b2ea69441 (diff) | |
| download | dotfiles-3cf613ec7c90ab4933728b0f19e49b0c955c17bb.tar.gz dotfiles-3cf613ec7c90ab4933728b0f19e49b0c955c17bb.zip | |
Add 'common/nvim/' from commit '966d12ac730c83da90d60ab24eae539b2ea69441'
git-subtree-dir: common/nvim
git-subtree-mainline: ef51a60993197ed3bbd1003522f98f0a898d34c6
git-subtree-split: 966d12ac730c83da90d60ab24eae539b2ea69441
Diffstat (limited to 'common/nvim/lua/plugins')
50 files changed, 6370 insertions, 0 deletions
diff --git a/common/nvim/lua/plugins/auto-session.lua b/common/nvim/lua/plugins/auto-session.lua new file mode 100755 index 0000000..d982e08 --- /dev/null +++ b/common/nvim/lua/plugins/auto-session.lua @@ -0,0 +1,39 @@ +local M = {} + +function M.setup() + local auto = pcall(require, 'auto-session') and require('auto-session') + if not auto then + return false + end + + local nvim_version = vim.version() + if nvim_version.major == 0 and nvim_version.minor < 5 then + return false + end + + -- Configure session options + vim.opt.sessionoptions:append("localoptions") -- Add localoptions to sessionoptions + + -- Set up auto-session + auto.setup({ + log_level = 'info', + auto_session_suppress_dirs = { '~/', '~/Projects', '~/projects', '~/Downloads', '~/downloads' }, + auto_session_use_git_branch = true, + bypass_save_filetypes = { "dashboard" }, + + -- Additional configuration to handle session options + pre_save_cmds = { + -- Ensure local options are saved with the session + function() vim.opt.sessionoptions:append("localoptions") end, + }, + + -- Post restore hook to ensure local options are properly set + post_restore = function() + vim.opt.sessionoptions:append("localoptions") + end, + }) + + return true +end + +return M diff --git a/common/nvim/lua/plugins/autopairs.lua b/common/nvim/lua/plugins/autopairs.lua new file mode 100755 index 0000000..22dcf27 --- /dev/null +++ b/common/nvim/lua/plugins/autopairs.lua @@ -0,0 +1,99 @@ +local M = {} + +--- Setup and configure nvim-autopairs +-- This function initializes and configures the autopairs plugin +-- @return boolean True if setup was successful, false otherwise +function M.setup() + local ok, autopairs = pcall(require, "nvim-autopairs") + if not ok then + return false + end + + -- Configure autopairs + autopairs.setup({ + check_ts = true, + ts_config = { + lua = { "string", "source" }, + javascript = { "string", "template_string" }, + java = false, + }, + map = "<M-e>", + pairs_map = { + ["<"] = ">", + }, + disable_filetype = { "TelescopePrompt", "spectre_panel" }, + disable_in_macro = true, + disable_in_visualblock = true, + enable_moveright = true, + enable_afterquote = true, -- add bracket pairs after quote + enable_check_bracket_line = false, --- check bracket in same line + enable_bracket_in_quote = true, -- + break_undo = true, -- switch for basic rule break undo sequence + --fast_wrap = { + -- chars = { "{", "[", "(", '"', "'" }, + -- pattern = string.gsub([[ [%'%"%)%>%]%)%}%,] ]], "%s+", ""), + -- offset = 0, -- Offset from pattern match + -- end_key = "$", + -- keys = "qwertyuiopzxcvbnmasdfghjkl", + -- check_comma = true, + -- highlight = "PmenuSel", + -- highlight_grey = "LineNr", + --}, +}) +local Rule = require("nvim-autopairs.rule") + +local cond = require("nvim-autopairs.conds") + +autopairs.add_rules({ + Rule("`", "'", "tex"), + Rule("$", "$", "tex"), + Rule(" ", " ") + :with_pair(function(opts) + local pair = opts.line:sub(opts.col, opts.col + 1) + return vim.tbl_contains({ "$$", "()", "{}", "[]", "<>" }, pair) + end) + :with_move(cond.none()) + :with_cr(cond.none()) + :with_del(function(opts) + local col = vim.api.nvim_win_get_cursor(0)[2] + local context = opts.line:sub(col - 1, col + 2) + return vim.tbl_contains({ "$ $", "( )", "{ }", "[ ]", "< >" }, context) + end), + Rule("$ ", " ", "tex"):with_pair(cond.not_after_regex(" ")):with_del(cond.none()), + Rule("[ ", " ", "tex"):with_pair(cond.not_after_regex(" ")):with_del(cond.none()), + Rule("{ ", " ", "tex"):with_pair(cond.not_after_regex(" ")):with_del(cond.none()), + Rule("( ", " ", "tex"):with_pair(cond.not_after_regex(" ")):with_del(cond.none()), + Rule("< ", " ", "tex"):with_pair(cond.not_after_regex(" ")):with_del(cond.none()), +}) + +autopairs.get_rule("$"):with_move(function(opts) + return opts.char == opts.next_char:sub(1, 1) +end) + +-- import nvim-cmp plugin (completions plugin) +local cmp = require("cmp") + +-- import nvim-autopairs completion functionality +local cmp_autopairs = require("nvim-autopairs.completion.cmp") + +-- make autopairs and completion work together +cmp.event:on( + "confirm_done", + cmp_autopairs.on_confirm_done({ + filetypes = { + tex = false, -- Disable for tex + }, + }) +) + +--local cmp_autopairs = require "nvim-autopairs.completion.cmp" +--local cmp_status_ok, cmp = pcall(require, "cmp") +--if not cmp_status_ok then +-- return +--end +--cmp.event:on("confirm_done", cmp_autopairs.on_confirm_done { map_char = { tex = "" } }) + + return true +end + +return M diff --git a/common/nvim/lua/plugins/cmp-gh-source.lua b/common/nvim/lua/plugins/cmp-gh-source.lua new file mode 100755 index 0000000..4990c35 --- /dev/null +++ b/common/nvim/lua/plugins/cmp-gh-source.lua @@ -0,0 +1,70 @@ +local ok, Job = pcall(require, 'plenary.job') +if not ok then + return +end + +local source = {} + +source.new = function() + local self = setmetatable({ cache = {} }, { __index = source }) + + return self +end + +source.complete = function(self, _, callback) + local bufnr = vim.api.nvim_get_current_buf() + + -- This just makes sure that we only hit the GH API once per session. + -- + -- You could remove this if you wanted, but this just makes it so we're + -- good programming citizens. + if not self.cache[bufnr] then + Job:new({ + -- Uses `gh` executable to request the issues from the remote repository. + 'gh', + 'issue', + 'list', + '--limit', + '1000', + '--json', + 'title,number,body', + + on_exit = function(job) + local result = job:result() + local ok, parsed = pcall(vim.json.decode, table.concat(result, '')) + if not ok then + vim.notify('Failed to parse gh result') + return + end + + local items = {} + for _, gh_item in ipairs(parsed) do + gh_item.body = string.gsub(gh_item.body or '', '\r', '') + + table.insert(items, { + label = string.format('#%s', gh_item.number), + documentation = { + kind = 'markdown', + value = string.format('# %s\n\n%s', gh_item.title, gh_item.body), + }, + }) + end + + callback({ items = items, isIncomplete = false }) + self.cache[bufnr] = items + end, + }):start() + else + callback({ items = self.cache[bufnr], isIncomplete = false }) + end +end + +source.get_trigger_characters = function() + return { '#' } +end + +source.is_available = function() + return vim.bo.filetype == 'gitcommit' +end + +require('cmp').register_source('gh_issues', source.new()) diff --git a/common/nvim/lua/plugins/cmp.lua b/common/nvim/lua/plugins/cmp.lua new file mode 100755 index 0000000..7de04ad --- /dev/null +++ b/common/nvim/lua/plugins/cmp.lua @@ -0,0 +1,67 @@ +local M = {} + +--- Setup and configure nvim-cmp +-- This function initializes and configures the completion plugin +-- @return boolean True if setup was successful, false otherwise +function M.setup() + -- Check Neovim version + local nvim_version = vim.version() + if nvim_version.major == 0 and nvim_version.minor < 5 then + return false + end + + -- Try to load required modules + local cmp = pcall(require, 'cmp') and require('cmp') + if not cmp then + return false + end + + local luasnip_ok, luasnip = pcall(require, 'luasnip') + if not luasnip_ok then + vim.notify("luasnip not found, some features may be limited", vim.log.levels.WARN) + end + + -- Setup nvim-cmp + cmp.setup({ + snippet = { + expand = function(args) + if luasnip_ok then luasnip.lsp_expand(args.body) end + end, + }, + mapping = cmp.mapping.preset.insert({ + ['<C-Space>'] = cmp.mapping.complete(), + ['<CR>'] = cmp.mapping.confirm({ select = true }), + ['<Tab>'] = cmp.mapping.select_next_item(), + ['<S-Tab>'] = cmp.mapping.select_prev_item(), + }), + sources = cmp.config.sources({ + { name = 'nvim_lsp' }, + { name = 'luasnip' }, + { name = 'buffer' }, + }), +}) + +vim.cmd([[ + highlight! link CmpItemMenu Comment + " gray + highlight! CmpItemAbbrDeprecated guibg=NONE gui=strikethrough guifg=#808080 + " blue + highlight! CmpItemAbbrMatch guibg=NONE guifg=#569CD6 + highlight! CmpItemAbbrMatchFuzzy guibg=NONE guifg=#569CD6 + " light blue + highlight! CmpItemKindVariable guibg=NONE guifg=#9CDCFE + highlight! CmpItemKindInterface guibg=NONE guifg=#9CDCFE + highlight! CmpItemKindText guibg=NONE guifg=#9CDCFE + " pink + highlight! CmpItemKindFunction guibg=NONE guifg=#C586C0 + highlight! CmpItemKindMethod guibg=NONE guifg=#C586C0 + " front + highlight! CmpItemKindKeyword guibg=NONE guifg=#D4D4D4 + highlight! CmpItemKindProperty guibg=NONE guifg=#D4D4D4 + highlight! CmpItemKindUnit guibg=NONE guifg=#D4D4D4 + ]]) + + return true +end + +return M diff --git a/common/nvim/lua/plugins/colorizer.lua b/common/nvim/lua/plugins/colorizer.lua new file mode 100755 index 0000000..6019bc5 --- /dev/null +++ b/common/nvim/lua/plugins/colorizer.lua @@ -0,0 +1,8 @@ +local M = {} + +function M.setup() + -- No-op if colorizer is not installed + return true +end + +return M diff --git a/common/nvim/lua/plugins/colorscheme.lua b/common/nvim/lua/plugins/colorscheme.lua new file mode 100755 index 0000000..7fbabc1 --- /dev/null +++ b/common/nvim/lua/plugins/colorscheme.lua @@ -0,0 +1,24 @@ +local M = {} + +-- List of preferred colorschemes in order of preference +local preferred_colorschemes = { + 'tokyonight', + 'desert', + 'default' +} + +function M.setup() + -- Try each colorscheme in order of preference + for _, scheme in ipairs(preferred_colorschemes) do + local ok = pcall(vim.cmd, 'colorscheme ' .. scheme) + if ok then + return true + end + end + + -- If all else fails, use the built-in default + vim.cmd('colorscheme default') + return true +end + +return M diff --git a/common/nvim/lua/plugins/comment.lua b/common/nvim/lua/plugins/comment.lua new file mode 100755 index 0000000..392b279 --- /dev/null +++ b/common/nvim/lua/plugins/comment.lua @@ -0,0 +1,125 @@ +local M = {} + +--- Setup and configure comment.nvim +-- This function initializes and configures the comment plugin +-- @return boolean True if setup was successful, false otherwise +function M.setup() + local ok, comment = pcall(require, 'Comment') + if not ok then + vim.notify("Comment.nvim not found", vim.log.levels.WARN) + return false + end + + -- Configure comment.nvim + comment.setup({ + -- Add a space b/w comment and the line + padding = true, + + -- Whether the cursor should stay at its position + sticky = true, + + -- Lines to be ignored while (un)commenting + ignore = '^$', + + -- LHS of toggle mappings in NORMAL mode + toggler = { + -- Line-comment toggle keymap + line = 'gcc', + -- Block-comment toggle keymap + block = 'gbc', + }, + + -- LHS of operator-pending mappings in NORMAL and VISUAL mode + opleader = { + -- Line-comment keymap + line = 'gc', + -- Block-comment keymap + block = 'gb', + }, + + -- LHS of extra mappings + extra = { + -- Add comment on the line above + above = 'gcO', + -- Add comment on the line below + below = 'gco', + -- Add comment at the end of line + eol = 'gcA', + }, + + -- Enable keybindings + -- NOTE: If given `false` then the plugin won't create any mappings + mappings = { + -- Operator-pending mapping; `gcc` `gbc` `gc[count]{motion}` `gb[count]{motion}` + basic = true, + -- Extra mapping; `gco`, `gcO`, `gcA` + extra = true, + -- Extended mapping; `g>` `g<` `g>[count]{motion}` `g<[count]{motion}` + extended = false, + }, + + -- Function to call before (un)comment + pre_hook = nil, + + -- Function to call after (un)comment + post_hook = nil, + }) + + -- Additional keymaps for better UX + local keymap = vim.keymap.set + local opts = { noremap = true, silent = true } + + -- Toggle comment for current line or visual selection + keymap('n', '<leader>cc', '<Plug>(comment_toggle_linewise_current)', opts) + keymap('n', '<leader>bc', '<Plug>(comment_toggle_blockwise_current)', opts) + + -- Toggle comment for current line or visual selection and add new line + keymap('n', '<leader>cO', '<Plug>(comment_toggle_linewise_above)', opts) + keymap('n', '<leader>co', '<Plug>(comment_toggle_linewise_below)', opts) + + -- Toggle comment for visual selection + keymap('v', '<leader>cc', '<Plug>(comment_toggle_linewise_visual)', { noremap = false }) + keymap('v', '<leader>bc', '<Plug>(comment_toggle_blockwise_visual)', { noremap = false }) + + -- Filetype specific settings + local ft = require('Comment.ft') + + -- Set comment string for specific filetypes + ft.set('lua', { '--%s', '--[[%s]]' }) + ft.set('vim', { '" %s' }) + ft.set('python', { '# %s', '"""%s"""' }) + ft.set('javascript', { '// %s', '/*%s*/' }) + ft.set('typescript', { '// %s', '/*%s*/' }) + ft.set('css', { '/* %s */' }) + ft.set('html', { '<!-- %s -->' }) + + -- Set up autocommands for specific filetypes + local group = vim.api.nvim_create_augroup('CommentCustom', { clear = true }) + + -- Disable comment plugin for certain filetypes + vim.api.nvim_create_autocmd('FileType', { + group = group, + pattern = { + 'qf', 'help', 'man', 'notify', 'lspinfo', 'packer', + 'checkhealth', 'startuptime', 'Trouble', 'alpha', 'dashboard' + }, + callback = function() + vim.b.comment_disable = true + end, + }) + + -- Re-enable comment plugin for normal files + vim.api.nvim_create_autocmd('FileType', { + group = group, + pattern = { '*' }, + callback = function() + if vim.bo.buftype == '' then + vim.b.comment_disable = nil + end + end, + }) + + return true +end + +return M diff --git a/common/nvim/lua/plugins/dap.lua b/common/nvim/lua/plugins/dap.lua new file mode 100755 index 0000000..7de032c --- /dev/null +++ b/common/nvim/lua/plugins/dap.lua @@ -0,0 +1,265 @@ +local M = {} + +function M.setup() + local ok, dap = pcall(require, "dap") + if not ok or not dap then + return false + end + +-- options +dap.defaults.fallback.switchbuf = "uselast" +dap.defaults.fallback.focus_terminal = true +dap.defaults.fallback.external_terminal = { + command = "/usr/bin/wezterm", + args = { "-e" }, +} + +-- Autocmds +vim.api.nvim_create_autocmd("FileType", { + pattern = { "dap-float" }, + callback = function(event) + vim.keymap.set("n", "<Tab>", "", { buffer = event.buf, silent = true }) + vim.keymap.set("n", "<S-Tab>", "", { buffer = event.buf, silent = true }) + end, +}) + +dap.adapters.cppdbg = { + id = "cppdbg", + type = "executable", + --command = vim.fn.stdpath('data') .. '/mason/bin/OpenDebugAD7', + command = os.getenv("HOME") .. "/apps/cpptools/extension/debugAdapters/bin/OpenDebugAD7", + --command = cpptools:get_install_path() .. '/extension/debugAdapters/bin/OpenDebugAD7' +} + +dap.adapters.codelldb = { + type = "server", + port = "${port}", + --host = "localhost", + --host = '127.0.0.1', + --port = 13000, -- 💀 Use the port printed out or specified with `--port` + executable = { + --command = os.getenv("HOME") .. '/apps/codelldb/extension/adapter/codelldb', + command = os.getenv("HOME") .. "/.vscode-oss/extensions/vadimcn.vscode-lldb-1.9.0-universal/adapter/codelldb", + args = { "--port", "${port}" }, + }, + --detached = true, +} + +dap.adapters.lldb = { + type = "executable", + command = "/usr/bin/lldb-vscode", + name = "lldb", +} +dap.configurations.cpp = { + { + name = "Debugger", + --type = "lldb", + --type = "cppdbg", + type = "codelldb", + request = "launch", + cwd = "${workspaceFolder}", + program = function() + return vim.fn.input("Path to executable: ", vim.fn.getcwd() .. "/", "file") + end, + --program = '${file}', + --program = function() + -- -- First, check if exists CMakeLists.txt + -- local cwd = vim.fn.getcwd() + -- if (file.exists(cwd, "CMakeLists.txt")) then + -- -- Todo. Then invoke cmake commands + -- -- Then ask user to provide execute file + -- return vim.fn.input("Path to executable: ", vim.fn.getcwd() .. "/", "file") + -- else + -- local fileName = vim.fn.expand("%:t:r") + -- if (not file.exists(cwd, "bin")) then + -- -- create this directory + -- os.execute("mkdir " .. "bin") + -- end + -- local cmd = "!gcc -g % -o bin/" .. fileName + -- -- First, compile it + -- vim.cmd(cmd) + -- -- Then, return it + -- return "${fileDirname}/bin/" .. fileName + -- end + --end, + stopAtEntry = true, + args = {}, + --runInTerminal = true, + --runInTerminal = false, + --console = 'integratedTerminal', + + --MIMode = 'gdb', + --miDebuggerServerAddress = 'localhost:1234', + --miDebuggerPath = 'gdb-oneapi', + --miDebuggerPath = '/usr/bin/gdb', + externalConsole = true, + --setupCommands = { + -- { + -- text = '-enable-pretty-printing', + -- description = 'enable pretty printing', + -- ignoreFailures = false + -- } + --}, + }, +} + +-- If you want to use this for Rust and C, add something like this: +dap.configurations.c = dap.configurations.cpp +dap.configurations.rust = dap.configurations.cpp + +-- javascript +--dap.adapters.node2 = { +-- type = 'executable', +-- command = 'node-debug2-adapter', +-- args = {}, +--} + +--dap.configurations.javascript = { +-- { +-- name = 'Launch', +-- type = 'node2', +-- request = 'attach', +-- program = '${file}', +-- cwd = vim.fn.getcwd(), +-- sourceMaps = true, +-- protocol = 'inspector', +-- console = 'integratedTerminal', +-- }, +--} + +dap.adapters.python = { + type = "executable", + command = vim.trim(vim.fn.system("which python")), + args = { "-m", "debugpy.adapter" }, +} + +dap.configurations.python = { + { + -- The first three options are required by nvim-dap + type = "python", -- the type here established the link to the adapter definition: `dap.adapters.python` + request = "launch", + name = "Launch file", + -- Options below are for debugpy, see https://github.com/microsoft/debugpy/wiki/Debug-configuration-settings for supported options + program = "${file}", -- This configuration will launch the current file if used. + stopOnEntry = true, + }, +} + +local dapui = require("dapui") +--local dap_ui_status_ok, dapui = pcall(require, "dapui") +--if not dap_ui_status_ok then +-- return +--end + +-- setup repl +--dap.repl.commands = vim.tbl_extend('force', dap.repl.commands, { +-- exit = { 'q', 'exit' }, +-- custom_commands = { +-- ['.run_to_cursor'] = dap.run_to_cursor, +-- ['.restart'] = dap.run_last +-- } +--}) + +-- Load dapui configuration only if it hasn't been loaded before + local dapui_ok, dapui = pcall(require, "dapui") + if dapui_ok and dapui then + dapui.setup({ + mappings = { + expand = "<CR>", + open = "o", + remove = "D", + edit = "e", + repl = "r", + toggle = "t", + }, + controls = { + enabled = true, + }, + layouts = { + { + elements = { + -- Elements can be strings or table with id and size keys. + { id = "watches", size = 0.25 }, + { id = "scopes", size = 0.25 }, + { id = "breakpoints", size = 0.25 }, + { id = "stacks", size = 0.25 }, + }, + size = 50, -- 40 columns + position = "left", + }, + { + elements = { + { id = "console", size = 0.6 }, + { id = "repl", size = 0.4 }, + }, + size = 0.3, + position = "bottom", + }, + }, + render = { + max_value_lines = 3, + }, + floating = { + max_height = nil, -- These can be integers or a float between 0 and 1. + max_width = nil, -- Floats will be treated as percentage of your screen. + border = "single", -- Border style. Can be "single", "double" or "rounded" + mappings = { + close = { "q", "<Esc>" }, + }, + }, + --icons = { expanded = "-", collapsed = "$" }, + icons = { + expanded = "", + collapsed = "", + current_frame = "", + }, + }) + vim.g.loaded_dapui = true +end + +-- Signs +local sign = vim.fn.sign_define +sign("DapBreakpoint", { text = "●", texthl = "DapBreakpoint", linehl = "", numhl = "" }) +sign("DapBreakpointCondition", { text = "◆", texthl = "DapBreakpointCondition", linehl = "", numhl = "" }) -- +sign("DapBreakpointRejected", { text = "R", texthl = "DiagnosticError", numhl = "DiagnosticError" }) +sign("DapLogPoint", { text = "L", texthl = "DapLogPoint", linehl = "", numhl = "" }) +sign("DapStopped", { text = "", texthl = "DiagnosticSignHint", numbhl = "", linehl = "" }) + +--sign('DapBreakpoint', { text = '', texthl = 'DiagnosticSignError', numbhl = '', linehl = '' }) +--sign("DapLogPoint", { text = '.>', texthl = 'DiagnosticInfo', numhl = 'DiagnosticInfo' }) +--vim.fn.sign_define("DapBreakpointCondition", { text = '?>', texthl = 'DiagnosticInfo', numhl = 'DiagnosticInfo' }) +--vim.fn.sign_define("DapStopped", { text = '=>', texthl = 'DiagnosticWarn', numhl = 'DiagnosticWarn' }) +--vim.fn.sign_define("DapBreakpoint", { text = '<>', texthl = 'DiagnosticInfo', numhl = 'DiagnosticInfo' }) + +dap.listeners.after.event_initialized["dapui_config"] = function() + dapui.open() +end +dap.listeners.before.event_terminated["dapui_config"] = function() + dapui.close() +end +dap.listeners.before.event_exited["dapui_config"] = function() + dapui.close() +end +dap.listeners.before.disconnect["dapui_config"] = function() + dapui.close() +end + +require("nvim-dap-virtual-text").setup({ + enabled = true, + enabled_commands = true, + highlight_changed_variables = true, + highlight_new_as_changed = false, + show_stop_reason = true, + commented = true, + only_first_definition = true, + all_references = false, + filter_references_pattern = "<module", + virt_text_pos = "eol", + all_frames = false, + virt_text_win_col = nil, + }) + + return true +end + +return M diff --git a/common/nvim/lua/plugins/dashboard.lua b/common/nvim/lua/plugins/dashboard.lua new file mode 100755 index 0000000..43a3461 --- /dev/null +++ b/common/nvim/lua/plugins/dashboard.lua @@ -0,0 +1,126 @@ +local M = {} + +--- Setup and configure dashboard.nvim +-- This function initializes and configures the dashboard plugin +-- @return boolean True if setup was successful, false otherwise +function M.setup() + local ok, db = pcall(require, 'dashboard') + if not ok then + return false + end + + local messages = { + "The only way to do great work is to love what you do. - Steve Jobs", + "Code is like humor. When you have to explain it, it's bad. - Cory House", + "First, solve the problem. Then, write the code. - John Johnson", + "Any fool can write code that a computer can understand. Good programmers write code that humans can understand. - Martin Fowler", + "The most disastrous thing that you can ever learn is your first programming language. - Alan Kay", + "The most important property of a program is whether it accomplishes the intention of its user. - C.A.R. Hoare", + "The best error message is the one that never shows up. - Thomas Fuchs", + "The most important skill for a programmer is the ability to effectively communicate ideas. - Gastón Jorquera", + "The only way to learn a new programming language is by writing programs in it. - Dennis Ritchie", + "The most damaging phrase in the language is 'We've always done it this way!' - Grace Hopper" + } + + local function get_random_message() + local random_index = math.random(1, #messages) + return messages[random_index] + end + +--vim.api.nvim_create_autocmd("VimEnter", { +-- callback = function() +-- -- disable line numbers +-- vim.opt_local.number = false +-- vim.opt_local.relativenumber = false +-- -- always start in insert mode +-- end, +--}) + + -- Configure dashboard + db.setup({ + theme = "hyper", + config = { + mru = { limit = 20, label = "" }, + project = { limit = 10 }, + header = { + [[ ███╗ ██╗ ███████╗ ██████╗ ██╗ ██╗ ██╗ ███╗ ███╗]], + [[ ████╗ ██║ ██╔════╝██╔═══██╗ ██║ ██║ ██║ ████╗ ████║]], + [[ ██╔██╗ ██║ █████╗ ██║ ██║ ██║ ██║ ██║ ██╔████╔██║]], + [[ ██║╚██╗██║ ██╔══╝ ██║ ██║ ╚██╗ ██╔╝ ██║ ██║╚██╔╝██║]], + [[ ██║ ╚████║ ███████╗╚██████╔╝ ╚████╔╝ ██║ ██║ ╚═╝ ██║]], + [[ ╚═╝ ╚═══╝ ╚══════╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝]], + }, + disable_move = false, + shortcut = { + { desc = " Plugins", group = "Number", action = "PackerStatus", key = "p" }, + { + desc = " Files", + group = "Number", + action = "Telescope find_files", + key = "f", + }, + { + desc = " TODO", + group = "Number", + action = ":edit ~/documents/main/inbox/tasks/TODO.md", + key = "t", + }, + { + desc = " New", + group = "Number", + action = "enew", + key = "e", + }, + { + desc = " Grep", + group = "Number", + action = "Telescope live_grep", + key = "g", + }, + { + desc = " Scheme", + group = "Number", + action = "Telescope colorscheme", + key = "s", + }, + { + desc = " Config", + group = "Number", + action = ":edit ~/.config/nvim/init.lua", + key = "c", + }, + }, + footer = function() + return { "", "" } + --return { "", GetRandomMessage() } + end, + }, + hide = { + statusline = false, + tabline = false, + winbar = false, + }, +}) + +-- Set keymaps only when dashboard is active +vim.api.nvim_create_autocmd("FileType", { + group = vim.api.nvim_create_augroup("DashboardMappings", { clear = true }), + pattern = "dashboard", + callback = function() + vim.keymap.set("n", "e", "<Cmd>DashboardNewFile<CR>", { buffer = true }) + vim.keymap.set("n", "q", "<Cmd>q!<CR>", { buffer = true }) + vim.keymap.set("n", "<C-o>", "<C-o><C-o>", { buffer = true }) -- Allow Ctrl + o to act normally + end, +}) +---- General +--DashboardHeader DashboardFooter +---- Hyper theme +--DashboardProjectTitle DashboardProjectTitleIcon DashboardProjectIcon +--DashboardMruTitle DashboardMruIcon DashboardFiles DashboardShotCutIcon +---- Doome theme +--DashboardDesc DashboardKey DashboardIcon DashboardShotCut + + return true +end + +return M diff --git a/common/nvim/lua/plugins/fidget.lua b/common/nvim/lua/plugins/fidget.lua new file mode 100755 index 0000000..d401c5f --- /dev/null +++ b/common/nvim/lua/plugins/fidget.lua @@ -0,0 +1,34 @@ +require("fidget").setup({ + --event = "LspAttach", + text = { + --spinner = "pipe", -- (Default) animation shown when tasks are ongoing + --spinner = "hamburger", -- animation shown when tasks are ongoing + --spinner = "dots_pulse", -- animation shown when tasks are ongoing + spinner = "dots", -- animation shown when tasks are ongoing + done = "✔", -- character shown when all tasks are complete + commenced = "Started", -- message shown when task starts + completed = "Completed", -- message shown when task completes + }, + fmt = { + task = function(task_name, message, percentage) + if task_name == "diagnostics" then + return false + end + return string.format( + "%s%s [%s]", + message, + percentage and string.format(" (%s%%)", percentage) or "", + task_name + ) + end, + }, + --sources = { -- Sources to configure + --["null-ls"] = { -- Name of source + --ignore = true, -- Ignore notifications from this source + --}, + --}, + debug = { + logging = false, -- whether to enable logging, for debugging + strict = false, -- whether to interpret LSP strictly + }, +}) diff --git a/common/nvim/lua/plugins/friendly-snippets.lua b/common/nvim/lua/plugins/friendly-snippets.lua new file mode 100755 index 0000000..2a7695e --- /dev/null +++ b/common/nvim/lua/plugins/friendly-snippets.lua @@ -0,0 +1,3 @@ +-- friendly-snippets plugin config (modular, robust) +local ok, _ = pcall(require, 'friendly-snippets') +-- No config needed, loaded by LuaSnip
\ No newline at end of file diff --git a/common/nvim/lua/plugins/fugitive.lua b/common/nvim/lua/plugins/fugitive.lua new file mode 100755 index 0000000..22620e3 --- /dev/null +++ b/common/nvim/lua/plugins/fugitive.lua @@ -0,0 +1,8 @@ +local M = {} + +function M.setup() + -- No-op if fugitive is not installed + return true +end + +return M diff --git a/common/nvim/lua/plugins/fzf.lua b/common/nvim/lua/plugins/fzf.lua new file mode 100755 index 0000000..9e62c48 --- /dev/null +++ b/common/nvim/lua/plugins/fzf.lua @@ -0,0 +1,43 @@ +local M = {} + +if not fzfLua then + return M +end + +local ok_fzfLua, actions = pcall(require, "fzf-lua") +if not ok_fzfLua then + return +end + +local ok_fzfLua, actions = pcall(require, "fzf-lua.actions") +if not ok_fzfLua then + return +end + + +local ok, fzfLua = pcall(require, "fzf-lua") +if not ok then + vim.notify("fzf-lua not found", vim.log.levels.WARN) + return M +end + +fzfLua.setup({ + defaults = { + file_icons = "mini", + }, + winopts = { + row = 0.5, + height = 0.7, + }, + files = { + previewer = false, + }, +}) + +vim.keymap.set("n", "<leader>fz", "<cmd>FzfLua files<cr>", { desc = "Fuzzy find files" }) +vim.keymap.set("n", "<leader>fzg", "<cmd>FzfLua live_grep<cr>", { desc = "Fuzzy grep files" }) +vim.keymap.set("n", "<leader>fzh", "<cmd>FzfLua helptags<cr>", { desc = "Fuzzy grep tags in help files" }) +vim.keymap.set("n", "<leader>fzt", "<cmd>FzfLua btags<cr>", { desc = "Fuzzy search buffer tags" }) +vim.keymap.set("n", "<leader>fzb", "<cmd>FzfLua buffers<cr>", { desc = "Fuzzy search opened buffers" }) + +return M diff --git a/common/nvim/lua/plugins/git.lua b/common/nvim/lua/plugins/git.lua new file mode 100755 index 0000000..24a0871 --- /dev/null +++ b/common/nvim/lua/plugins/git.lua @@ -0,0 +1,8 @@ +local M = {} + +function M.setup() + -- No-op if git plugin is not installed + return true +end + +return M diff --git a/common/nvim/lua/plugins/gitsigns.lua b/common/nvim/lua/plugins/gitsigns.lua new file mode 100755 index 0000000..7bbe637 --- /dev/null +++ b/common/nvim/lua/plugins/gitsigns.lua @@ -0,0 +1,85 @@ +local M = {} + +--- Setup and configure gitsigns +-- This function initializes and configures the git signs in the gutter +-- @return boolean True if setup was successful, false otherwise +function M.setup() + local ok, gitsigns = pcall(require, 'gitsigns') + if not ok then + return false + end + + gitsigns.setup({ + signs = { + --add = { + -- hl = "GitSignsAdd", + -- text = "▍", --│ + -- numhl = "GitSignsAddNr", + -- linehl = "GitSignsAddLn", + --}, + --change = { + -- hl = "GitSignsChange", + -- text = "▍", --│ + -- numhl = "GitSignsChangeNr", + -- linehl = "GitSignsChangeLn", + --}, + delete = { + hl = "GitSignsDelete", + text = "▁", --_━─ + numhl = "GitSignsDeleteNr", + linehl = "GitSignsDeleteLn", + }, + topdelete = { + hl = "GitSignsDelete", + text = "▔", --‾ + numhl = "GitSignsDeleteNr", + linehl = "GitSignsDeleteLn", + }, + changedelete = { + hl = "GitSignsDelete", + text = "~", + numhl = "GitSignsChangeNr", + linehl = "GitSignsChangeLn", + }, + }, + current_line_blame = true, + }) + +vim.api.nvim_command("highlight DiffAdd guibg=none guifg=#21c7a8") +vim.api.nvim_command("highlight DiffModified guibg=none guifg=#82aaff") +vim.api.nvim_command("highlight DiffDelete guibg=none guifg=#fc514e") +vim.api.nvim_command("highlight DiffText guibg=none guifg=#fc514e") +vim.cmd([[ +hi link GitSignsAdd DiffAdd +hi link GitSignsChange DiffModified +hi link GitSignsDelete DiffDelete +hi link GitSignsTopDelete DiffDelete +hi link GitSignsChangedDelete DiffDelete +]]) + -- Set up highlights + vim.cmd([[ + highlight DiffAdd guibg=none guifg=#21c7a8 + highlight DiffModified guibg=none guifg=#82aaff + highlight DiffDelete guibg=none guifg=#fc514e + highlight DiffText guibg=none guifg=#fc514e + + hi link GitSignsAdd DiffAdd + hi link GitSignsChange DiffModified + hi link GitSignsDelete DiffDelete + hi link GitSignsTopDelete DiffDelete + hi link GitSignsChangedelete DiffDelete + hi link GitSignsChangedeleteLn DiffDelete + hi link GitSignsChangedeleteNr DiffDeleteNr + ]]) + + return true +end + +return M +--'signs.delete.hl' is now deprecated, please define highlight 'GitSignsDelete' +--'signs.delete.linehl' is now deprecated, please define highlight 'GitSignsDeleteLn' +--'signs.delete.numhl' is now deprecated, please define highlight 'GitSignsDeleteNr' +--'signs.topdelete.hl' is now deprecated, please define highlight 'GitSignsTopdelete' +--'signs.topdelete.linehl' is now deprecated, please define highlight 'GitSignsTopdeleteLn' +--'signs.topdelete.numhl' is now deprecated, please define highlight 'GitSignsTopdeleteNr' + diff --git a/common/nvim/lua/plugins/goto-preview.lua b/common/nvim/lua/plugins/goto-preview.lua new file mode 100755 index 0000000..eb54a8c --- /dev/null +++ b/common/nvim/lua/plugins/goto-preview.lua @@ -0,0 +1,31 @@ +local M = {} + +function M.setup() + local ok, gp = pcall(require, 'goto-preview') + if not ok or not gp then + return false + end + + gp.setup { + width = 120; -- Width of the floating window + height = 15; -- Height of the floating window + border = {"↖", "─" ,"┐", "│", "┘", "─", "└", "│"}; -- Border characters of the floating window + default_mappings = false; -- Bind default mappings + debug = false; -- Print debug information + opacity = nil; -- 0-100 opacity level of the floating window where 100 is fully transparent. + resizing_mappings = false; -- Binds arrow keys to resizing the floating window. + post_open_hook = nil; -- A function taking two arguments, a buffer and a window to be ran as a hook. + references = { -- Configure the telescope UI for slowing the references cycling window. + telescope = require("telescope.themes").get_dropdown({ hide_preview = false }) + }; + -- These two configs can also be passed down to the goto-preview definition and implementation calls for one off "peak" functionality. + focus_on_open = true; -- Focus the floating window when opening it. + dismiss_on_move = false; -- Dismiss the floating window when moving the cursor. + force_close = true, -- passed into vim.api.nvim_win_close's second argument. See :h nvim_win_close + bufhidden = "wipe", -- the bufhidden option to set on the floating window. See :h bufhidden + } + + return true +end + +return M diff --git a/common/nvim/lua/plugins/hardtime.lua b/common/nvim/lua/plugins/hardtime.lua new file mode 100755 index 0000000..b440334 --- /dev/null +++ b/common/nvim/lua/plugins/hardtime.lua @@ -0,0 +1,29 @@ +-- Function to toggle the hardtime state and echo a message +local hardtime_enabled = true + +local hardtime = require("hardtime") + +hardtime.setup({ + -- hardtime config here + enabled = true, + restriction_mode = "hint", + disabled_filetypes = { "qf", "netrw", "NvimTree", "NvimTree_1", "lazy", "mason", "oil", "dashboard" }, + disable_mouse = false, + disabled_keys = { + ["<Up>"] = {}, + ["<Down>"] = {}, + ["<Left>"] = {}, + ["<Right>"] = {}, + }, +}) + +function ToggleHardtime() + hardtime.toggle() + hardtime_enabled = not hardtime_enabled + local message = hardtime_enabled and "hardtime on" or "hardtime off" + vim.cmd('echo "' .. message .. '"') +end + +return { + ToggleHardtime = ToggleHardtime, +} diff --git a/common/nvim/lua/plugins/harpoon.lua b/common/nvim/lua/plugins/harpoon.lua new file mode 100755 index 0000000..8e842b3 --- /dev/null +++ b/common/nvim/lua/plugins/harpoon.lua @@ -0,0 +1,50 @@ +local M = {} + +function M.setup() + local ok, harpoon = pcall(require, "harpoon") + if not ok or not harpoon then + return false + end + + harpoon.setup({ + menu = { + width = vim.api.nvim_win_get_width(0) - 4, + }, + --keys = { + -- { "mt", function() require("harpoon.mark").toggle_file() end, desc = "Toggle File" }, + -- { "mm", function() require("harpoon.ui").toggle_quick_menu() end, desc = "Harpoon Menu" }, + -- { "mc", function() require("harpoon.cmd-ui").toggle_quick_menu() end, desc = "Command Menu" }, + -- --{ "<leader>1", function() require("harpoon.ui").nav_file(1) end, desc = "File 1" }, + -- --{ "<leader>2", function() require("harpoon.ui").nav_file(2) end, desc = "File 2" }, + -- --{ "<leader>3", function() require("harpoon.term").gotoTerminal(1) end, desc = "Terminal 1" }, + -- --{ "<leader>4", function() require("harpoon.term").gotoTerminal(2) end, desc = "Terminal 2" }, + -- --{ "<leader>5", function() require("harpoon.term").sendCommand(1,1) end, desc = "Command 1" }, + -- --{ "<leader>6", function() require("harpoon.term").sendCommand(1,2) end, desc = "Command 2" }, + }) + + -- Set up keymaps safely + local function safe_keymap(mode, lhs, rhs, opts) + local opts_with_noremap = vim.tbl_extend('force', {noremap = true, silent = true}, opts or {}) + vim.keymap.set(mode, lhs, rhs, opts_with_noremap) + end + + safe_keymap("n", "<leader>ma", function() require('harpoon.mark').add_file() end, { desc = "Harpoon: Add file" }) + safe_keymap("n", "<leader>mt", function() require('harpoon.mark').toggle_file() end, { desc = "Harpoon: Toggle file" }) + safe_keymap("n", "<leader>mq", function() require('harpoon.ui').toggle_quick_menu() end, { desc = "Harpoon: Toggle quick menu" }) + safe_keymap("n", "<leader>mh", function() require('harpoon.ui').nav_file(1) end, { desc = "Harpoon: Navigate to file 1" }) + safe_keymap("n", "<leader>mj", function() require('harpoon.ui').nav_file(2) end, { desc = "Harpoon: Navigate to file 2" }) + safe_keymap("n", "<leader>mk", function() require('harpoon.ui').nav_file(3) end, { desc = "Harpoon: Navigate to file 3" }) + safe_keymap("n", "<leader>ml", function() require('harpoon.ui').nav_file(4) end, { desc = "Harpoon: Navigate to file 4" }) + + return true +end + +return M +-- +--vim.keymap.set("n", "<leader>a", mark.add_file) +--vim.keymap.set("n", "<C-e>", ui.toggle_quick_menu) +-- +--vim.keymap.set("n", "<C-h>", function() ui.nav_file(1) end) +--vim.keymap.set("n", "<C-t>", function() ui.nav_file(2) end) +--vim.keymap.set("n", "<C-n>", function() ui.nav_file(3) end) +--vim.keymap.set("n", "<C-s>", function() ui.nav_file(4) end) diff --git a/common/nvim/lua/plugins/heirline.lua b/common/nvim/lua/plugins/heirline.lua new file mode 100755 index 0000000..a4c2fc3 --- /dev/null +++ b/common/nvim/lua/plugins/heirline.lua @@ -0,0 +1,1497 @@ +local M = {} + +-- Safe require function to handle missing dependencies +local function safe_require(module) + local ok, result = pcall(require, module) + return ok and result or nil +end + +-- These will be initialized in M.setup() +local heirline = nil +local conditions = {} +local utils = {} +local colors = {} + +function M.setup() + heirline = safe_require("heirline") + if not heirline then + return + end + + -- Initialize conditions and utils after heirline is loaded + conditions = require("heirline.conditions") or {} + utils = require("heirline.utils") or {} + + + -- Initialize colors after safe_fg is defined + colors = { + bg = "NONE", + nobg = "NONE", + white = "#f8f8f2", + black = "#000000", + darkgray = "#23232e", + gray = "#2d2b3a", + lightgray = "#d6d3ea", + pink = "#f92672", + green = "#50fa7b", + blue = "#39BAE6", + yellow = "#f1fa8c", + orange = "#ffb86c", + purple = "#BF40BF", + violet = "#7F00FF", + red = "#ff5555", + cyan = "#66d9eC", + --diag = { + -- warn = safe_fg("DiagnosticSignWarn", "#ffb86c"), + -- error = safe_fg("DiagnosticSignError", "#ff5555"), + -- hint = safe_fg("DiagnosticSignHint", "#50fa7b"), + -- info = safe_fg("DiagnosticSignInfo", "#66d9eC"), + --}, + diag = { + warn = utils.get_highlight("DiagnosticSignWarn").fg, + error = utils.get_highlight("DiagnosticSignError").fg, + hint = utils.get_highlight("DiagnosticSignHint").fg, + info = utils.get_highlight("DiagnosticSignInfo").fg, + }, + git = { + active = "#f34f29", + del = "#ff5555", + add = "#50fa7b", + change = "#ae81ff", + }, + } + + -- Only load colors if heirline is available + if heirline.load_colors then + local ok, err = pcall(heirline.load_colors, colors) + if not ok then + vim.notify("Failed to load Heirline colors: " .. tostring(err), vim.log.levels.ERROR) + end + end + + local function get_icon(icon, fallback) + -- Check if we have Nerd Fonts available + local has_nerd_fonts = vim.g.statusline_has_nerd_fonts + if has_nerd_fonts == nil then + -- Cache the result to avoid repeated checks + if vim.fn.has('unix') == 1 and vim.fn.executable('fc-list') == 1 then + local handle = io.popen('fc-list | grep -i nerd') + local result = handle:read('*a') + handle:close() + has_nerd_fonts = result ~= "" + else + -- On non-Unix systems or if fc-list isn't available, assume no Nerd Fonts + has_nerd_fonts = false + end + vim.g.statusline_has_nerd_fonts = has_nerd_fonts + end + + -- Return the appropriate string based on font availability + local result = has_nerd_fonts and icon or (fallback or '') + -- Trim any whitespace to prevent layout issues + return vim.trim(result) + end + + -- Define all components after colors and utils are initialized + + --local Signs = { + -- Error = "✘", + -- Warn = "", + -- Hint = "◉", + -- Info = "", + --} + local Icons = { + Signs = { + Error = "✘", + Warn = "", + Hint = "◉", + Info = "", + LSP = get_icon("⚙️", "LSP"), + }, + ---- LSP/Debug + Error = get_icon("✘", "E"), + Warn = get_icon("", "W"), + Hint = get_icon("◉", "H"),-- + Info = get_icon("ℹ", "I"), + --LSP = get_icon("⚙️", "LSP"), + + -- Diagnostic + Diagnostic = { + error = get_icon("✘", "E"), + warn = get_icon("", "W"), + hint = get_icon("", "H"), + info = get_icon("ℹ", "I"), + }, + + +--local GitIcons = { +-- added = "✚", -- plus in diff style +-- modified = "", -- nf-oct-diff_modified +-- removed = "", -- nf-oct-diff_removed +--} +--added = "", -- nf-oct-diff_added +--modified = "", -- nf-oct-diff_modified +--removed = "", -- nf-oct-diff_removed +--local GitIcons = { +-- added = "", -- nf-fa-plus_square +-- modified = "", -- nf-fa-file_text_o +-- removed = "", -- nf-fa-minus_square +--} + -- Git + Git = { + branch = get_icon(" ", "⎇ "), + added = get_icon("+ ", "+"), + removed = get_icon("- ", "-"), + modified = get_icon("~ ", "~"), + renamed = get_icon("", "r"), + untracked = get_icon("", "?"), + ignored = get_icon("", "."), + }, + + -- UI Elements + UI = { + left_separator = get_icon("", ""), + right_separator = get_icon("", ""), + thin_separator = get_icon("▏", "|"), + ellipsis = get_icon("…", "..."), + arrow_left = get_icon("◀", "<"), + arrow_right = get_icon("▶", ">"), + close = get_icon("✕", "x"), + big_close = get_icon(" ", "x "), + modified = get_icon(" + ", "*"), + readonly = get_icon("", "RO"), + lock = get_icon("", "[L]"), + clock = get_icon("🕒", "[TIME]"), + buffer = get_icon("", "[BUF]"), + tab = get_icon("", "[TAB]"), + search = get_icon("🔍", "[SEARCH]"), + spell = get_icon("暈", "[SPELL]"), + whitespace = get_icon("␣", "[WS]"), + newline = get_icon("↵", "[NL]"), + indent = get_icon("▏", "|"), + fold = get_icon("", ">"), + fold_open = get_icon("", "v"), + fold_closed = get_icon("", ">"), + }, + + -- File types + File = { + default = get_icon("", "[F]"), + directory = get_icon("", "[D]"), + symlink = get_icon("", "[L]"), + executable = get_icon("", "[X]"), + image = get_icon("", "[IMG]"), + archive = get_icon("", "[ARC]"), + audio = get_icon("", "[AUD]"), + video = get_icon("", "[VID]"), + document = get_icon("", "[DOC]"), + config = get_icon("", "[CFG]"), + code = get_icon("", "[CODE]"), + terminal = get_icon("", "[TERM]"), + }, + + -- File format indicators + Format = { + unix = get_icon("", "[UNIX]"), + dos = get_icon("", "[DOS]"), + mac = get_icon("", "[MAC]"), + }, + + -- Version control + VCS = { + branch = get_icon("", "[BR]"), + git = get_icon("", "[GIT]"), + github = get_icon("", "[GH]"), + gitlab = get_icon("", "[GL]"), + bitbucket = get_icon("", "[BB]"), + }, + + -- Programming languages + Lang = { + lua = get_icon("", "[LUA]"), + python = get_icon("", "[PY]"), + javascript = get_icon("", "[JS]"), + typescript = get_icon("", "[TS]"), + html = get_icon("", "[HTML]"), + css = get_icon("", "[CSS]"), + json = get_icon("", "[JSON]"), + markdown = get_icon("", "[MD]"), + docker = get_icon("", "[DKR]"), + rust = get_icon("", "[RS]"), + go = get_icon("", "[GO]"), + java = get_icon("", "[JAVA]"), + c = get_icon("", "[C]"), + cpp = get_icon("", "[C++]"), + ruby = get_icon("", "[RB]"), + php = get_icon("", "[PHP]"), + haskell = get_icon("", "[HS]"), + scala = get_icon("", "[SCALA]"), + elixir = get_icon("", "[EXS]"), + clojure = get_icon("", "[CLJ]"), + }, + + -- UI Indicators + Indicator = { + error = get_icon("✘", "[E]"), + warning = get_icon("⚠", "[W]"), + info = get_icon("ℹ", "[I]"), + hint = get_icon("", "[H]"), + success = get_icon("✓", "[OK]"), + question = get_icon("?", "[?]"), + star = get_icon("★", "[*]"), + heart = get_icon("❤", "<3"), + lightning = get_icon("⚡", "[!]"), + check = get_icon("✓", "[√]"), + cross = get_icon("✗", "[x]"), + plus = get_icon("+", "[+]"), + recording = get_icon(" ", "q") + }, + + -- File operations + FileOp = { + new = get_icon("", "[NEW]"), + save = get_icon("💾", "[SAVE]"), + open = get_icon("📂", "[OPEN]"), + close = get_icon("✕", "[X]"), + undo = get_icon("↩", "[UNDO]"), + redo = get_icon("↪", "[REDO]"), + cut = get_icon("✂", "[CUT]"), + copy = get_icon("⎘", "[COPY]"), + paste = get_icon("📋", "[PASTE]"), + search = get_icon("🔍", "[FIND]"), + replace = get_icon("🔄", "[REPLACE]"), + }, + + -- Navigation + Nav = { + left = get_icon("←", "[<]"), + right = get_icon("→", "[>]"), + up = get_icon("↑", "[^]"), + down = get_icon("↓", "[v]"), + first = get_icon("⏮", "[<<]"), + last = get_icon("⏭", "[>>]"), + prev = get_icon("◀", "[<]"), + next = get_icon("▶", "[>]"), + back = get_icon("↩", "[B]"), + forward = get_icon("↪", "[F]"), + }, + + -- Editor states + State = { + insert = get_icon("", "[INS]"), + normal = get_icon("🚀", "[NOR]"), + visual = get_icon("👁", "[VIS]"), + replace = get_icon("🔄", "[REP]"), + command = get_icon("", "[CMD]"), + terminal = get_icon("", "[TERM]"), + select = get_icon("🔍", "[SEL]"), + }, + + -- Common symbols + Symbol = { + dot = get_icon("•", "•"), + bullet = get_icon("•", "•"), + middle_dot = get_icon("·", "·"), + ellipsis = get_icon("…", "..."), + check = get_icon("✓", "[OK]"), + cross = get_icon("✗", "[X]"), + arrow_right = get_icon(" ", "->"), + arrow_left = get_icon(" ", "<-"), + double_arrow_right = get_icon("»", ">>"), + double_arrow_left = get_icon("«", "<<"), + chevron_right = get_icon("›", ">"), + chevron_left = get_icon("‹", "<"), + }, + + -- Document symbols + DocSymbol = { + class = get_icon("", "[C]"), + function_icon = get_icon("", "[F]"), + method = get_icon("", "[M]"), + property = get_icon("", "[P]"), + field = get_icon("ﰠ", "[F]"), + constructor = get_icon("", "[C]"), + enum = get_icon("", "[E]"), + interface = get_icon("", "[I]"), + variable = get_icon("", "[V]"), + constant = get_icon("", "[C]"), + string = get_icon("", "[S]"), + number = get_icon("", "[N]"), + boolean = get_icon("◩", "[B]"), + array = get_icon("", "[A]"), + object = get_icon("⦿", "[O]"), + key = get_icon("🔑", "[K]"), + null = get_icon("NULL", "Ø"), + enum_member = get_icon("", "[E]"), + struct = get_icon("פּ", "[S]"), + event = get_icon("", "[E]"), + operator = get_icon("", "[O]"), + type_parameter = get_icon("", "[T]"), + }, + } + local Align = { provider = "%=", hl = { bg = colors.bg } } + local Space = { provider = " ", hl = { bg = colors.bg } } + local Tab = { provider = " " } + local LeftSpace = { provider = "" } + local RightSpace = { provider = "" } + + local ViMode = { + init = function(self) + self.mode = vim.fn.mode(1) + -- Store the initial mode + self.prev_mode = self.mode + + -- Set up autocommand to force redraw on mode change + vim.api.nvim_create_autocmd("ModeChanged", { + pattern = "*:*", + callback = function() + -- Only redraw if the mode actually changed + local current_mode = vim.fn.mode(1) + if current_mode ~= self.prev_mode then + self.prev_mode = current_mode + vim.schedule(function() + vim.cmd("redrawstatus") + end) + end + end, + }) + end, + static = { + mode_names = { + n = " NORMAL ", + no = "PENDING ", + nov = " N? ", + noV = " N? ", + ["no\22"] = " N? ", + niI = " Ni ", + niR = " Nr ", + niV = " Nv ", + nt = "TERMINAL", + v = " VISUAL ", + vs = " Vs ", + V = " V·LINE ", + ["\22"] = "V·BLOCK ", + ["\22s"] = "V·BLOCK ", + s = " SELECT ", + S = " S·LINE ", + ["\19"] = "S·BLOCK ", + i = " INSERT ", + ix = "insert x", + ic = "insert c", + R = "REPLACE ", + Rc = " Rc ", + Rx = " Rx ", + Rv = "V·REPLACE ", + Rvc = " Rv ", + Rvx = " Rv ", + c = "COMMAND ", + cv = " VIM EX ", + ce = " EX ", + r = " PROMPT ", + rm = " MORE ", + ["r?"] = "CONFIRM ", + ["!"] = " SHELL ", + t = "TERMINAL", + }, + }, + provider = function(self) + return " %2(" .. self.mode_names[self.mode] .. "%) " + end, + hl = function(self) + return { fg = "colors.black", bg = self.mode_color, bold = true } + end, + update = { + "ModeChanged", + "VimEnter", + "BufEnter", + "WinEnter", + "TabEnter", + pattern = "*:*", + callback = vim.schedule_wrap(function() + vim.cmd("redrawstatus") + end), + }, + } + + -- LSP + local LSPActive = { + condition = function() + local ok, _ = pcall(function() + local buf = vim.api.nvim_get_current_buf() + return #vim.lsp.get_clients({ bufnr = buf }) > 0 + end) + return ok or false + end, + update = { "LspAttach", "LspDetach", "BufEnter" }, + provider = function() + local ok, result = pcall(function() + local buf = vim.api.nvim_get_current_buf() + if not vim.api.nvim_buf_is_valid(buf) then return "" end + + local clients = vim.lsp.get_clients({ bufnr = buf }) + if not clients or #clients == 0 then return "" end + + local client_names = {} + for _, client in ipairs(clients) do + if client and client.name and client.name ~= "null-ls" then + table.insert(client_names, client.name) + end + end + + if #client_names > 0 then + return Icons.Signs.LSP .. " " .. table.concat(client_names, "/") .. " " + end + + return "" + end) + + if not ok then + vim.schedule(function() + vim.notify_once("Error in LSPActive provider: " .. tostring(result), vim.log.levels.DEBUG) + end) + return "" + end + + return result or "" + end, + hl = { fg = "lightgray", bold = false }, + } + + local Navic = { + condition = function() + local ok, navic = pcall(require, "nvim-navic") + return ok and navic.is_available() + end, + static = { + type_hl = { + File = "Directory", + Module = "@include", + Namespace = "@namespace", + Package = "@include", + Class = "@structure", + Method = "@method", + Property = "@property", + Field = "@field", + Constructor = "@constructor", + Enum = "@field", + Interface = "@type", + Function = "@function", + Variable = "@variable", + Constant = "@constant", + String = "@string", + Number = "@number", + Boolean = "@boolean", + Array = "@field", + Object = "@type", + Key = "@keyword", + Null = "@comment", + EnumMember = "@field", + Struct = "@structure", + Event = "@keyword", + Operator = "@operator", + TypeParameter = "@type", + }, + enc = function(line, col, winnr) + return bit.bor(bit.lshift(line, 16), bit.lshift(col, 6), winnr) + end, + dec = function(c) + local line = bit.rshift(c, 16) + local col = bit.band(bit.rshift(c, 6), 1023) + local winnr = bit.band(c, 63) + return line, col, winnr + end, + }, + init = function(self) + local data = require("nvim-navic").get_data() or {} + local children = {} + for i, d in ipairs(data) do + local pos = self.enc(d.scope.start.line, d.scope.start.character, self.winnr) + local child = { + { + provider = d.icon, + hl = self.type_hl[d.type], + }, + { + provider = d.name:gsub("%%", "%%%%"):gsub("%s*->%s*", ""), + on_click = { + minwid = pos, + callback = function(_, minwid) + local line, col, winnr = self.dec(minwid) + vim.api.nvim_win_set_cursor(vim.fn.win_getid(winnr), { line, col }) + end, + name = "heirline_navic", + }, + }, + } + if #data > 1 and i < #data then + table.insert(child, { + provider = " > ", + hl = { fg = "bright_fg" }, + }) + end + table.insert(children, child) + end + self.child = self:new(children, 1) + end, + provider = function(self) + return self.child:eval() + end, + hl = { fg = "gray" }, + update = "CursorMoved", + } + + -- Diagnostics + local Diagnostics = { + condition = conditions.has_diagnostics, + static = { + error_icon = Icons.Error, + warn_icon = Icons.Warn, + info_icon = Icons.Info, + hint_icon = Icons.Hint, + }, + init = function(self) + self.errors = #vim.diagnostic.get(0, { severity = vim.diagnostic.severity.ERROR }) + self.warnings = #vim.diagnostic.get(0, { severity = vim.diagnostic.severity.WARN }) + self.hints = #vim.diagnostic.get(0, { severity = vim.diagnostic.severity.HINT }) + self.info = #vim.diagnostic.get(0, { severity = vim.diagnostic.severity.INFO }) + end, + update = { "DiagnosticChanged", "BufEnter" }, + { + provider = function(self) + return self.errors > 0 and (self.error_icon .. " " .. self.errors .. " ") + end, + hl = { fg = colors.diag.error, bg = colors.bg }, + }, + { + provider = function(self) + return self.warnings > 0 and (self.warn_icon .. " " .. self.warnings .. " ") + end, + hl = { fg = colors.diag.warn, bg = colors.bg }, + }, + { + provider = function(self) + return self.info > 0 and (self.info_icon .. " " .. self.info .. " ") + end, + hl = { fg = colors.diag.info, bg = colors.bg }, + }, + { + provider = function(self) + return self.hints > 0 and (self.hint_icon .. " " .. self.hints .. " ") + end, + hl = { fg = colors.diag.hint, bg = colors.bg }, + }, + on_click = { + callback = function() + local ok, _ = pcall(require, "trouble") + if ok then + require("trouble").toggle({ mode = "document_diagnostics" }) + else + vim.diagnostic.setqflist() + end + end, + name = "heirline_diagnostics", + }, + } + + -- Git + local Git = { + condition = conditions.is_git_repo, + init = function(self) + self.status_dict = vim.b.gitsigns_status_dict or {} + self.has_changes = (self.status_dict.added or 0) ~= 0 or + (self.status_dict.removed or 0) ~= 0 or + (self.status_dict.changed or 0) ~= 0 + end, + { + provider = function() + return " " .. Icons.Git.branch .. " " + end, + hl = { fg = colors.git.active, bg = colors.bg }, + }, + { + provider = function(self) + return self.status_dict.head or "" + end, + hl = { fg = colors.white, bg = colors.bg }, + }, + { + condition = function(self) + return self.has_changes + end, + provider = "", + }, + { + provider = function(self) + local count = self.status_dict.added or 0 + return count > 0 and (" " .. Icons.Git.added .. count) or "" + end, + hl = { fg = colors.git.add, bg = colors.bg }, + }, + { + provider = function(self) + local count = self.status_dict.removed or 0 + return count > 0 and (" " .. Icons.Git.removed .. count) or "" + end, + hl = { fg = colors.git.del, bg = colors.bg }, + }, + { + provider = function(self) + local count = self.status_dict.changed or 0 + return count > 0 and (" " .. Icons.Git.modified .. count) or "" + end, + hl = { fg = colors.git.change, bg = colors.bg }, + }, + on_click = { + callback = function() + vim.defer_fn(function() + vim.cmd("Lazygit") + end, 100) + end, + name = "heirline_git", + }, + } + + -- FileNameBlock: FileIcon, FileName and friends + local FileNameBlock = { + init = function(self) + self.filename = vim.api.nvim_buf_get_name(0) + end, + hl = { bg = colors.bg }, + } + + local FileIcon = { + init = function(self) + local filename = self.filename or vim.api.nvim_buf_get_name(0) + local extension = vim.fn.fnamemodify(filename, ":e") + + local has_nerd_fonts = vim.g.statusline_has_nerd_fonts + if has_nerd_fonts == nil then + if vim.fn.has('unix') == 1 and vim.fn.executable('fc-list') == 1 then + local handle = io.popen('fc-list | grep -i nerd') + local result = handle:read('*a') + handle:close() + has_nerd_fonts = result ~= "" + else + has_nerd_fonts = false + end + vim.g.statusline_has_nerd_fonts = has_nerd_fonts + end + + local icon, icon_color + if has_nerd_fonts then + icon, icon_color = require("nvim-web-devicons").get_icon_color(filename, extension, { default = true }) + end + + if vim.fn.isdirectory(filename) == 1 then + self.icon = has_nerd_fonts and Icons.File.directory or "[DIR]" + self.icon_color = colors.blue + else + if has_nerd_fonts and icon then + self.icon = icon .. " " + self.icon_color = icon_color or colors.blue + else + if extension == "" then + self.icon = Icons.File.default + else + local file_icon = Icons.File[extension:lower()] or Icons.File.default + if type(file_icon) == "table" then + self.icon = file_icon[1] or Icons.File.default + else + self.icon = file_icon + end + end + self.icon_color = colors.blue + end + end + end, + provider = function(self) + return self.icon + end, + hl = function(self) + return { fg = self.icon_color, bold = true } + end, + } + + local FileName = { + provider = function(self) + local filename = vim.fn.fnamemodify(self.filename, ":.") + if filename == "" then + return "No Name" + end + if not conditions.width_percent_below(#filename, 0.25) then + filename = vim.fn.pathshorten(filename) + end + return filename + end, + hl = { fg = colors.white, bold = false, bg = colors.bg }, + } + + local FileFlags = { + { + provider = function() + if vim.bo.modified then + return " +" + end + end, + hl = { fg = colors.green, bg = colors.bg }, + }, + { + provider = function() + if not vim.bo.modifiable or vim.bo.readonly then + return " " .. Icons.UI.lock + end + end, + hl = { fg = colors.orange, bold = true, bg = colors.bg }, + }, + } + + local FileNameModifier = { + hl = function() + if vim.bo.modified then + return { fg = colors.green, bold = false, force = true } + end + end, + } + + -- FileType, FileEncoding and FileFormat + local FileType = { + provider = function() + return vim.bo.filetype + end, + hl = { fg = colors.white, bold = false, bg = colors.bg }, + } + + local FileEncoding = { + Space, + provider = function() + local enc = (vim.bo.fenc ~= "" and vim.bo.fenc) or vim.o.enc + return enc:lower() + end, + hl = { bg = colors.bg, bold = false }, + } + + local FileFormat = { + provider = function() + local fmt = vim.bo.fileformat + return fmt ~= "unix" and fmt:lower() or "" + end, + hl = { fg = utils.get_highlight("Statusline").fg, bold = true, bg = colors.bg }, + } + + local FileSize = { + provider = function() + local suffix = { "b", "k", "M", "G", "T", "P", "E" } + local filename = vim.api.nvim_buf_get_name(0) + local fsize = vim.fn.getfsize(filename) + fsize = (fsize < 0 and 0) or fsize + if fsize < 1024 then + return fsize .. suffix[1] + end + local i = math.floor((math.log(fsize) / math.log(1024))) + return string.format("%.2g%s", fsize / math.pow(1024, i), suffix[i + 1]) + end, + hl = { fg = utils.get_highlight("Statusline").fg, bold = true, bg = colors.bg }, + } + + local FileLastModified = { + provider = function() + local filename = vim.api.nvim_buf_get_name(0) + local ftime = vim.fn.getftime(filename) + return (ftime > 0) and os.date("%c", ftime) or "" + end, + hl = { fg = utils.get_highlight("Statusline").fg, bold = true, bg = colors.bg }, + } + + local Spell = { + condition = function() + return vim.wo.spell + end, + provider = function() + return " " .. Icons.Indicator.spell .. " " + end, + hl = { bold = true, fg = colors.yellow }, + } + + local HelpFileName = { + condition = function() + return vim.bo.filetype == "help" + end, + provider = function() + local filename = vim.api.nvim_buf_get_name(0) + return vim.fn.fnamemodify(filename, ":t") + end, + hl = { fg = colors.blue }, + } + + local SearchCount = { + condition = function() + return vim.v.hlsearch ~= 0 and vim.o.cmdheight == 0 + end, + init = function(self) + local ok, search = pcall(vim.fn.searchcount, { recompute = 1, maxcount = -1 }) + if ok and search.total then + self.search = search + end + end, + provider = function(self) + local search = self.search or { current = 0, total = 0, maxcount = 0 } + return string.format("[%d/%d]", search.current, math.min(search.total, search.maxcount)) + end, + update = { "CursorMoved", "CursorMovedI", "SearchWrapped" }, + } + + local MacroRec = { + condition = function() + return vim.fn.reg_recording() ~= "" and vim.o.cmdheight == 0 + end, + provider = function() + return Icons.Indicator.recording .. " " + end, + hl = { fg = "orange", bold = true }, + utils.surround({ "[", "]" }, nil, { + provider = function() + return vim.fn.reg_recording() + end, + hl = { fg = "green", bold = true }, + }), + update = { + "RecordingEnter", + "RecordingLeave", + callback = vim.schedule_wrap(function() + vim.cmd("redrawstatus") + end), + }, + } + + local ShowCmd = { + condition = function() + return vim.o.cmdheight == 0 + end, + provider = ":%3.5(%S%)", + update = { "CmdlineChanged" }, + } + + local cursor_location = { + { provider = "%1(%4l:%-3(%c%)%) %*", hl = { fg = colors.black, bold = true } }, + } + + local Ruler = { cursor_location } + + local WordCount = { + condition = function() + return conditions.buffer_matches({ + filetype = { + "markdown", + "txt", + "vimwiki", + }, + }) + end, + Space, + { + provider = function() + local ok, wordcount = pcall(vim.fn.wordcount) + return ok and wordcount.words and ("W:%d"):format(wordcount.words) or "" + end, + update = { "CursorMoved", "CursorMovedI", "InsertEnter", "TextChanged", "TextChangedI" }, + }, + } + + local WorkDir = { + init = function(self) + local is_local = vim.fn.haslocaldir(0) == 1 + self.icon = (is_local and "l" or "g") .. " " .. Icons.File.directory + local cwd = vim.fn.getcwd(0) + self.cwd = vim.fn.fnamemodify(cwd, ":~") + end, + hl = { fg = colors.blue, bold = true }, + on_click = { + callback = function() + vim.cmd("Telescope find_files cwd=" .. vim.fn.getcwd(0)) + end, + name = "heirline_workdir", + }, + flexible = 1, + { + provider = function(self) + local trail = self.cwd:sub(-1) == "/" and "" or "/" + return self.icon .. " " .. self.cwd .. trail .. " " + end, + }, + { + provider = function(self) + local cwd = vim.fn.pathshorten(self.cwd) + local trail = self.cwd:sub(-1) == "/" and "" or "/" + return self.icon .. " " .. cwd .. trail .. " " + end, + }, + { + provider = function(self) + return self.icon .. " " + end, + }, + } + + -- Build FileNameBlock + FileNameBlock = utils.insert( + FileNameBlock, + FileIcon, + utils.insert(FileNameModifier, FileName), + unpack(FileFlags), + { provider = "%<" } + ) + + local FileInfoBlock = { + init = function(self) + self.filename = vim.api.nvim_buf_get_name(0) + end, + } + + FileInfoBlock = utils.insert( + FileInfoBlock, + Space, + FileIcon, + FileType, + { provider = "%<" } + ) + + -- Create surrounded components with proper mode color functions + LeftSpace = utils.surround({ "", Icons.UI.right_separator }, function(self) + return self:mode_color() + end, { LeftSpace, hl = { fg = utils.get_highlight("statusline").bg, force = true } }) + + RightSpace = utils.surround({ Icons.UI.left_separator, "" }, function(self) + return self:mode_color() + end, { RightSpace, hl = { fg = utils.get_highlight("statusline").bg, force = true } }) + + LSPActive = utils.surround({ "", "" }, function(self) + return self:mode_color() + end, { Space, LSPActive, hl = { bg = colors.darkgray, force = true } }) + + FileInfoBlock = utils.surround({ "", "" }, function(self) + return self:mode_color() + end, { FileInfoBlock, Space, hl = { bg = colors.black, force = true } }) + + ViMode = utils.surround({ "", "" }, function(self) + return self:mode_color() + end, { ViMode, hl = { fg = colors.black, force = true } }) + + Ruler = utils.surround({ "", "" }, function(self) + return self:mode_color() + end, { Ruler, hl = { fg = colors.black, force = true } }) + + -- Statusline sections - FIXED: Removed duplicate LeftSpace from right section + local left = { + { RightSpace, hl = { bg = colors.nobg, force = true } }, + { ViMode, hl = { bg = utils.get_highlight("statusline").bg, bold = false } }, + { LeftSpace, hl = { bg = colors.nobg, force = true } }, + { Space, hl = { bg = colors.nobg, force = true } }, + { FileNameBlock, hl = { bg = colors.nobg, force = true } }, + { Space, hl = { bg = colors.nobg, force = true } }, + { Git, hl = { bg = colors.nobg, force = true } }, + } + + local middle = { + { Align, hl = { bg = colors.nobg, force = true } }, + { Align, hl = { bg = colors.nobg, force = true } }, + } + + -- FIXED: Right section now has proper sequence without duplicate LeftSpace + local right = { + { Diagnostics, hl = { bg = colors.nobg, force = true } }, + { Space, hl = { bg = colors.nobg, force = true } }, + { LSPActive, hl = { bg = colors.nobg, force = true } }, + { Space, hl = { bg = colors.nobg, force = true } }, + { FileInfoBlock, hl = { bg = colors.nobg, force = true } }, + { RightSpace, hl = { bg = colors.nobg, force = true } }, + { Ruler, hl = { fg = utils.get_highlight("statusline").bg, bold = false } }, + { LeftSpace, hl = { bg = colors.nobg, force = true } }, + } + + local sections = { left, middle, right } + local DefaultStatusline = { sections } + + -- Special statuslines for inactive/special buffers + local specialleft = { + { RightSpace, hl = { bg = colors.nobg, force = true } }, + { ViMode, hl = { bg = utils.get_highlight("statusline").bg, bold = false } }, + { LeftSpace, hl = { bg = colors.nobg, force = true } }, + } + + local specialmiddle = { + { Align, hl = { bg = colors.nobg, force = true } }, + { Align, hl = { bg = colors.nobg, force = true } }, + } + + local specialright = { + { RightSpace, hl = { bg = colors.nobg, force = true } }, + { Ruler, hl = { fg = utils.get_highlight("statusline").bg, bold = false } }, + { LeftSpace, hl = { bg = colors.nobg, force = true } }, + } + + local specialsections = { specialleft, specialmiddle, specialright } + + local InactiveStatusline = { + condition = conditions.is_not_active, + specialsections, + } + + local SpecialStatusline = { + condition = function() + return conditions.buffer_matches({ + buftype = { "nofile", "prompt", "help", "quickfix" }, + filetype = { "^git.*", "fugitive", "dashboard" }, + }) + end, + specialsections, + } + + local TerminalStatusline = { + condition = function() + return conditions.buffer_matches({ buftype = { "terminal" } }) + end, + specialsections, + } + + -- FIXED: Main StatusLine with better mode handling + local StatusLine = { + static = { + mode_colors = { + n = colors.blue, + no = colors.blue, + nov = colors.blue, + noV = colors.blue, + ["no\22"] = colors.blue, + niI = colors.blue, + niR = colors.blue, + niV = colors.blue, + nt = colors.blue, + v = colors.purple, + vs = colors.purple, + V = colors.purple, + ["\22"] = colors.purple, + ["\22s"] = colors.purple, + s = colors.purple, + S = colors.purple, + ["\19"] = colors.purple, + i = colors.green, + ix = colors.green, + ic = colors.green, + R = colors.red, + Rc = colors.red, + Rx = colors.red, + Rv = colors.red, + Rvc = colors.red, + Rvx = colors.red, + c = colors.orange, + cv = colors.orange, + ce = colors.orange, + r = colors.red, + rm = colors.red, + ["r?"] = colors.red, + ["!"] = colors.orange, + t = colors.orange, + }, + mode_color = function(self) + -- FIXED: Always get current mode to ensure updates + local mode = vim.fn.mode() + return self.mode_colors[mode] or colors.blue + end, + }, + -- FIXED: Add update triggers to ensure statusline refreshes properly + update = { + "ModeChanged", + "BufEnter", + "WinEnter", + "WinLeave", + "BufWinEnter", + "CmdlineLeave", + callback = vim.schedule_wrap(function() + vim.cmd("redrawstatus") + end), + }, + fallthrough = false, + SpecialStatusline, + TerminalStatusline, + InactiveStatusline, + DefaultStatusline, + } + + -- WinBar components + local WinbarFileNameBlock = { + init = function(self) + self.filename = vim.api.nvim_buf_get_name(0) + end, + hl = { bg = colors.bg }, + } + + local WinbarFileName = { + provider = function(self) + local filename = vim.fn.fnamemodify(self.filename, ":.") + if filename == "" then + return "No Name" + end + if not conditions.width_percent_below(#filename, 0.25) then + filename = vim.fn.pathshorten(filename) + end + return filename + end, + hl = { fg = colors.gray, bold = false, bg = colors.bg }, + } + + WinbarFileNameBlock = utils.insert( + WinbarFileNameBlock, + FileIcon, + utils.insert(WinbarFileName), + unpack(FileFlags), + { provider = "%<" } + ) + + vim.api.nvim_create_autocmd("User", { + pattern = "HeirlineInitWinbar", + callback = function(args) + local buf = args.buf + local buftype = vim.tbl_contains({ "prompt", "nofile", "help", "quickfix" }, vim.bo[buf].buftype) + local filetype = vim.tbl_contains({ "gitcommit", "fugitive" }, vim.bo[buf].filetype) + if buftype or filetype then + vim.opt_local.winbar = nil + end + end, + }) + + local On_click = { + minwid = function() + return vim.api.nvim_get_current_win() + end, + callback = function(_, minwid) + local winid = minwid + local buf = vim.api.nvim_win_get_buf(winid) + end, + } + + local CloseButton = { + condition = function(self) + return not vim.bo.modified + end, + update = { "WinNew", "WinClosed", "BufEnter" }, + { provider = " " }, + { + provider = Icons.UI.close, + hl = { fg = "gray" }, + On_click = { + minwid = function() + return vim.api.nvim_get_current_win() + end, + callback = function(_, minwid) + vim.api.nvim_win_close(minwid, true) + end, + name = "heirline_winbar_close_button", + }, + }, + } + + local Center = { + fallthrough = false, + { + condition = function() + return conditions.buffer_matches({ + buftype = { "terminal", "nofile", "prompt", "help", "quickfix" }, + filetype = { "dap-ui", "NvimTree", "^git.*", "fugitive", "dashboard" }, + }) + end, + init = function() + vim.opt_local.winbar = nil + end, + }, + { + condition = function() + return conditions.buffer_matches({ buftype = { "terminal" } }) + end, + FileType, + Space, + }, + { + condition = function() + return not conditions.is_active() + end, + utils.surround({ "", "" }, colors.nobg, { WinbarFileNameBlock }), + }, + utils.surround({ "", "" }, colors.nobg, { FileNameBlock }), + } + + local WinBar = { Space, Center } + + -- Tabline components + local TablineFileIcon = { + init = function(self) + local filename = self.filename + local extension = vim.fn.fnamemodify(filename, ":e") + + local has_nerd_fonts = vim.g.statusline_has_nerd_fonts + if has_nerd_fonts == nil then + if vim.fn.has('unix') == 1 and vim.fn.executable('fc-list') == 1 then + local handle = io.popen('fc-list | grep -i nerd') + local result = handle:read('*a') + handle:close() + has_nerd_fonts = result ~= "" + else + has_nerd_fonts = false + end + vim.g.statusline_has_nerd_fonts = has_nerd_fonts + end + + if has_nerd_fonts then + self.icon, self.icon_color = require("nvim-web-devicons").get_icon_color(filename, extension, { default = true }) + else + self.icon = "" + self.icon_color = colors.blue + + if vim.fn.isdirectory(filename) == 1 then + self.icon = "[DIR]" + else + local file_icon = Icons.File[extension:lower()] or Icons.File.default + if type(file_icon) == "table" then + self.icon = file_icon[1] or Icons.File.default + else + self.icon = file_icon + end + end + end + + if self.icon ~= "" then + self.icon = self.icon .. " " + end + end, + provider = function(self) + return self.icon or "" + end, + hl = function(self) + return { fg = self.icon_color or colors.blue } + end, + } + + local TablineFileName = { + provider = function(self) + local filename = vim.fn.fnamemodify(self.filename, ":t") + if filename == "" then + return "[No Name]" + end + return filename + end, + } + + local TablineFileFlags = { + { + condition = function(self) + return vim.api.nvim_buf_get_option(self.bufnr, "modified") + end, + provider = "%X " .. Icons.Indicator.plus .. " %X", + hl = { fg = "green" }, + }, + { + condition = function(self) + return not vim.api.nvim_buf_get_option(self.bufnr, "modifiable") or vim.api.nvim_buf_get_option(self.bufnr, "readonly") + end, + provider = function() + if vim.bo.readonly then + return " " .. Icons.UI.lock + end + return "" + end, + hl = { fg = "orange" }, + }, + } + + local TablineFileNameBlock = { + init = function(self) + self.filename = vim.api.nvim_buf_get_name(self.bufnr) + end, + hl = function(self) + if self.is_active then + return "TabLineSel" + else + return "TabLineFill" + end + end, + on_click = { + callback = function(_, minwid, _, button) + if button == "m" then + vim.api.nvim_buf_delete(minwid, { force = false }) + else + vim.api.nvim_win_set_buf(0, minwid) + end + end, + minwid = function(self) + return self.bufnr + end, + name = "heirline_tabline_buffer_callback", + }, + TablineFileIcon, + TablineFileName, + TablineFileFlags, + } + + local TablineCloseButton = { + condition = function(self) + return not vim.api.nvim_buf_get_option(self.bufnr, "modified") + end, + { provider = " " }, + { + provider = "" .. Icons.UI.close .. " %X", + hl = { fg = colors.red }, + on_click = { + callback = function(_, minwid) + vim.api.nvim_buf_delete(minwid, { force = false }) + end, + minwid = function(self) + return self.bufnr + end, + name = "heirline_tabline_close_buffer_callback", + }, + }, + } + + local TablineBufferBlock = utils.surround({ "", "" }, function(self) + if self.is_active then + return utils.get_highlight("TabLineSel").bg + else + return utils.get_highlight("TabLineFill").bg + end + end, { Tab, TablineFileNameBlock, TablineCloseButton }) + + local BufferLine = utils.make_buflist( + TablineBufferBlock, + { provider = Icons.Symbol.arrow_left, hl = { fg = colors.gray } }, + { provider = Icons.Symbol.arrow_right, hl = { fg = colors.gray } } + ) + + local Tabpage = { + provider = function(self) + return "%" .. self.tabnr .. "T " .. self.tabnr .. " %T" + end, + hl = function(self) + return self.is_active and "TabLineSel" or "TabLineFill" + end, + } + + local TabpageClose = { + provider = "%999X " .. Icons.UI.close .. " %X", + hl = { fg = colors.red, bg = colors.bg }, + } + + local TabPages = { + condition = function() + return #vim.api.nvim_list_tabpages() >= 2 + end, + { provider = "%=" }, + utils.make_tablist(Tabpage), + TabpageClose, + } + + local TabLineOffset = { + condition = function(self) + local win = vim.api.nvim_tabpage_list_wins(0)[1] + local bufnr = vim.api.nvim_win_get_buf(win) + self.winid = win + + if vim.api.nvim_buf_get_option(bufnr, "filetype") == "NvimTree" then + self.title = "NvimTree" + return true + end + end, + provider = function(self) + local title = self.title + local width = vim.api.nvim_win_get_width(self.winid) + local pad = math.ceil((width - #title) / 2) + return string.rep(" ", pad) .. title .. string.rep(" ", pad) + end, + hl = { fg = colors.white, bg = "#333842", bold = true }, + } + + local TabLine = { + TabLineOffset, + BufferLine, + TabPages, + } + + -- Buffer navigation functions + local function get_bufs() + return vim.tbl_filter(function(bufnr) + return vim.api.nvim_buf_is_loaded(bufnr) and vim.bo[bufnr].buflisted + end, vim.api.nvim_list_bufs()) + end + + local function goto_buf(index) + local bufs = get_bufs() + if index > #bufs then + index = #bufs + end + vim.api.nvim_win_set_buf(0, bufs[index]) + end + + local function add_key(key, index) + vim.keymap.set("n", "<A-" .. key .. ">", function() + goto_buf(index) + end, { noremap = true, silent = true }) + end + + for i = 1, 9 do + add_key(i, i) + end + add_key("0", 10) + + vim.o.showtabline = 2 + vim.cmd([[au FileType * if index(['wipe', 'delete', 'unload'], &bufhidden) >= 0 | set nobuflisted | endif]]) + + -- FIXED: Add proper autocmds for better statusline updates + local augroup = vim.api.nvim_create_augroup("HeirlineStatusline", { clear = true }) + + -- Force statusline refresh on mode changes and buffer events + vim.api.nvim_create_autocmd({ + "ModeChanged", + "BufEnter", + "BufWinEnter", + "WinEnter", + "WinLeave", + "CmdlineLeave", + "TermEnter", + "TermLeave" + }, { + group = augroup, + callback = function() + vim.schedule(function() + if vim.o.laststatus > 0 then + vim.cmd("redrawstatus!") + end + end) + end, + }) + + -- Final heirline setup + heirline.setup({ + statusline = StatusLine, + winbar = WinBar, + tabline = TabLine, + opts = { + disable_winbar_cb = function(args) + local buf = args.buf + if not vim.api.nvim_buf_is_valid(buf) then + return true + end + + local buftype = vim.tbl_contains( + { "prompt", "nofile", "help", "quickfix" }, + vim.bo[buf].buftype + ) + local filetype = vim.tbl_contains( + { "gitcommit", "fugitive" }, + vim.bo[buf].filetype + ) + return buftype or filetype + end, + } + }) + +end + +return M diff --git a/common/nvim/lua/plugins/indent-blankline.lua b/common/nvim/lua/plugins/indent-blankline.lua new file mode 100755 index 0000000..cbbcf27 --- /dev/null +++ b/common/nvim/lua/plugins/indent-blankline.lua @@ -0,0 +1,73 @@ +local M = {} + +--- Setup and configure indent-blankline.nvim +-- This function initializes and configures the indent guides +-- @return boolean True if setup was successful, false otherwise +function M.setup() + local ok, ibl = pcall(require, 'ibl') + if not ok then + return false + end + + local highlight = { + "RainbowRed", + "RainbowYellow", + "RainbowBlue", + "RainbowOrange", + "RainbowGreen", + "RainbowViolet", + "RainbowCyan", + } + + local hooks = require("ibl.hooks") + -- create the highlight groups in the highlight setup hook, so they are reset + -- every time the colorscheme changes + hooks.register(hooks.type.HIGHLIGHT_SETUP, function() + vim.api.nvim_set_hl(0, "RainbowRed", { fg = "#E06C75" }) + vim.api.nvim_set_hl(0, "RainbowYellow", { fg = "#E5C07B" }) + vim.api.nvim_set_hl(0, "RainbowBlue", { fg = "#61AFEF" }) + vim.api.nvim_set_hl(0, "RainbowOrange", { fg = "#D19A66" }) + vim.api.nvim_set_hl(0, "RainbowGreen", { fg = "#98C379" }) + vim.api.nvim_set_hl(0, "RainbowViolet", { fg = "#C678DD" }) + vim.api.nvim_set_hl(0, "RainbowCyan", { fg = "#56B6C2" }) + end) + + ibl.setup({ + indent = { highlight = highlight }, + exclude = { + filetypes = { + "", -- for all buffers without a file type + "NvimTree", + "Trouble", + "TelescopePrompt", + "TelescopeResults", + "mason", + "help", + "dashboard", + "packer", + "neogitstatus", + "Trouble", + "text", + "terminal", + "lazy", + }, + buftypes = { + "terminal", + "nofile", + "quickfix", + "prompt", + }, + }, + }) + + -- Toggle indent blankline with <leader>ti + vim.keymap.set('n', '<leader>ti', '<cmd>IBLToggle<CR>', { + noremap = true, + silent = true, + desc = 'Toggle indent guides' + }) + + return true +end + +return M diff --git a/common/nvim/lua/plugins/interestingwords.lua b/common/nvim/lua/plugins/interestingwords.lua new file mode 100755 index 0000000..655ed42 --- /dev/null +++ b/common/nvim/lua/plugins/interestingwords.lua @@ -0,0 +1,499 @@ +local interestingwords = (function() + local api = vim.api + local fn = vim.fn + local uv = vim.loop + + local m = {} + + m.words = {} + m.colors = {} + m.limits = {} + m.capcity = 0 + m.next = 1 + + local get_default_config = function() + return { + colors = { "#aeee00", "#ff0000", "#0000ff", "#b88823", "#ffa724", "#ff2c4b" }, + search_count = true, + navigation = true, + scroll_center = true, + search_key = "<leader>hl", + cancel_search_key = "<leader>lh", + color_key = "<leader>ih", + cancel_color_key = "<leader>hi", + select_mode = "random", -- random or loop + } + end + + local init_colors = function() + for i, v in pairs(m.config.colors) do + local color = "InterestingWord" .. i + + api.nvim_set_hl(0, color, { bg = v, fg = "Black" }) + m.colors[color] = 595129 + i + m.capcity = m.capcity + 1 + end + m.limits.min = 595129 + 1 + m.limits.max = 595129 + #m.config.colors + end + + local get_reg_ex = function(word) + if vim.o.ignorecase and (not vim.o.smartcase or fn.match(word, "\\u") == -1) then + return "\\c\\V" .. word + else + return "\\C\\V" .. word + end + end + + local get_visual_selection = function() + local lines + local start_row, start_col = fn.getpos("v")[2], fn.getpos("v")[3] + local end_row, end_col = fn.getpos(".")[2], fn.getpos(".")[3] + if end_row < start_row then + start_row, end_row = end_row, start_row + start_col, end_col = end_col, start_col + elseif end_row == start_row and end_col < start_col then + start_col, end_col = end_col, start_col + end + start_row = start_row - 1 + start_col = start_col - 1 + end_row = end_row - 1 + if api.nvim_get_mode().mode == "V" then + lines = api.nvim_buf_get_text(0, start_row, 0, end_row, -1, {}) + elseif api.nvim_get_mode().mode == "v" then + lines = api.nvim_buf_get_text(0, start_row, start_col, end_row, end_col, {}) + end + vim.cmd("normal! ") + if lines == nil then + return "" + end + + local line = "" + for i, v in ipairs(lines) do + if i == 1 then + line = line .. fn.escape(v, "\\") + else + line = line .. "\\n" .. fn.escape(v, "\\") + end + end + + return line + end + + local uncolor = function(word) + if m.words[word] then + local windows = api.nvim_list_wins() + for _, i in ipairs(windows) do + pcall(function() + fn.matchdelete(m.words[word].mid, i) + end) + end + m.colors[m.words[word].color] = m.words[word].mid + m.words[word] = nil + end + end + + local get_rest_color_random = function() + local res = {} + for k, v in pairs(m.colors) do + if v ~= 0 then + table.insert(res, { color = k, mid = v }) + end + end + if #res == 0 then + return nil + end + + return res[math.random(#res)] + end + + local find_who_use_this = function(target_color) + for word, color in pairs(m.words) do + if color.color == target_color then + return word + end + end + return nil + end + + local get_rest_color_loop = function() + if m.next > m.capcity then + m.next = 1 + end + local color = "InterestingWord" .. m.next + if m.colors[color] == 0 then + local word = find_who_use_this(color) + if word ~= nil then + uncolor(word) + else + return nil + end + end + m.next = m.next + 1 + return { color = color, mid = m.colors[color] } + end + + local get_rest_color = function() + local selector = { + ["random"] = get_rest_color_random, + ["loop"] = get_rest_color_loop, + } + return selector[m.config.select_mode]() + end + + local color = function(word) + local color = get_rest_color() + if not color then + vim.notify("InterestingWords: max number of highlight groups reached") + return + end + + m.words[word] = {} + m.words[word].color = color.color + m.words[word].mid = color.mid + m.colors[color.color] = 0 + + local windows = api.nvim_list_wins() + for _, i in ipairs(windows) do + pcall(function() + fn.matchadd(m.words[word].color, word, 1, m.words[word].mid, { window = i }) + end) + end + end + + local recolorAllWords = function() + for k, v in pairs(m.words) do + pcall(function() + fn.matchadd(v.color, k, 1, v.mid, { window = 0 }) + end) + end + end + + local nearest_word_at_cursor = function() + for _, match_item in pairs(fn.getmatches()) do + if match_item.id >= m.limits.min or match_item.id <= m.limits.max then + local buf_content = fn.join(api.nvim_buf_get_lines(0, 0, -1, {}), "\n") + local cur_pos = #fn.join(api.nvim_buf_get_lines(0, 0, fn.line(".") - 1, {}), "\n") + + ((fn.line(".") == 1) and 0 or 1) + fn.col(".") - 1 + local lst_pos = 0 + while true do + local mat_pos = fn.matchstrpos(buf_content, match_item.pattern, lst_pos, 1) + if mat_pos[1] == "" then + break + end + if cur_pos >= mat_pos[2] and cur_pos < mat_pos[3] then + return match_item.pattern + end + lst_pos = mat_pos[3] + end + end + end + end + + local filter = function(word) + if #word <= 4 or (string.sub(word, 1, 4) ~= "\\c\\V" and string.sub(word, 1, 4) ~= "\\C\\V") then + return word + else + return string.sub(word, 5, -1) + end + end + + local display_search_count = function(word, count) + local icon = "" + m.search_count_extmark_id = api.nvim_buf_set_extmark(0, m.search_count_namespace, fn.line(".") - 1, 0, { + virt_text_pos = "eol", + virt_text = { + { icon .. count, "NonText" }, + }, + hl_mode = "combine", + }) + m.search_count_cache = icon .. " " .. filter(word) .. count + m.search_count_timer:again() + end + + local hide_search_count = function(bufnr) + if m.search_count_namespace then + api.nvim_buf_del_extmark(bufnr, m.search_count_namespace, m.search_count_extmark_id) + end + end + + local scroll_timer = vim.loop.new_timer() + local function scroll_up(cnt) + return vim.cmd("normal! " .. cnt .. "") + end + + local function scroll_down(cnt) + return vim.cmd("normal! " .. cnt .. "") + end + + local function stop_scrolling() + scroll_timer:stop() + end + + local scroll_to_center = function() + local window_height = api.nvim_win_get_height(0) + local lines = fn.winline() - math.floor(window_height / 2) + if lines == 0 then + return + end + local up = lines > 0 + lines = math.abs(lines) + + local move_lines = function(n) + return math.floor(n / 5) + 1 + end + + local each_time = function() + local lines_bak = lines + local circles = 0 + while lines_bak ~= 0 do + lines_bak = lines_bak - move_lines(lines_bak) + circles = circles + 1 + end + local pseudo_total_time = 300 + 15 * math.min((lines - 11), 10) + lines + return math.floor(pseudo_total_time / circles) + end + local t = each_time() + local time_total = 0 + + local scroll_callback = function() + local cnt = move_lines(lines) + if lines == 0 then + stop_scrolling() + return + else + lines = lines - cnt + end + + if up then + scroll_up(cnt) + else + scroll_down(cnt) + end + time_total = time_total + t + end + + scroll_timer:start(t, t, vim.schedule_wrap(scroll_callback)) + end + + m.lualine_get = function() + return m.search_count_cache + end + + m.lualine_has = function() + return m.search_count_cache ~= "" + end + + m.init_search_count = function() + m.search_count_extmark_id = 0 + m.search_count_namespace = api.nvim_create_namespace("custom/search_count") + m.search_count_timer = vim.loop.new_timer() + m.search_count_timer:start(0, 5000, function() + m.search_count_cache = "" + vim.defer_fn(function() + hide_search_count(0) + end, 100) + m.search_count_timer:stop() + end) + + vim.api.nvim_create_autocmd({ "CmdlineLeave" }, { + pattern = { "*" }, + callback = function(event) + if vim.v.event.abort then + return + end + if event.match == "/" or event.match == "?" then + vim.defer_fn(function() + local searched = m.search_count(fn.getreg("/")) + if searched and m.config.scroll_center then + scroll_to_center() + end + end, 100) + end + end, + }) + end + + m.search_count = function(word) + hide_search_count(0) + if word == "" then + return false + end + + local cur_cnt = 0 + local total_cnt = 0 + local buf_content = fn.join(api.nvim_buf_get_lines(0, 0, -1, {}), "\n") + local cur_pos = #fn.join(api.nvim_buf_get_lines(0, 0, fn.line(".") - 1, {}), "\n") + ((fn.line(".") == 1) and 0 or 1) + + fn.col(".") - 1 + local lst_pos = 0 + while true do + local mat_pos = fn.matchstrpos(buf_content, word, lst_pos, 1) + if mat_pos[1] == "" then + break + end + total_cnt = total_cnt + 1 + if cur_pos >= mat_pos[2] and cur_pos < mat_pos[3] then + cur_cnt = total_cnt + end + lst_pos = mat_pos[3] + end + + if total_cnt == 0 or cur_cnt == 0 then + return false + end + + local count = " [" .. cur_cnt .. "/" .. total_cnt .. "]" + display_search_count(word, count) + + return true + end + + m.NavigateToWord = function(forward) + local word = nearest_word_at_cursor() + if not word then + word = fn.getreg("/") + end + if word == "" then + return + end + + local search_flag = "" + if not forward then + search_flag = "b" + end + local n = fn.search(word, search_flag) + if n ~= 0 then + if m.config.scroll_center then + scroll_to_center() + end + else + vim.notify("Pattern not found: " .. filter(word)) + return + end + + if m.config.search_count then + m.search_count(word) + end + end + + m.InterestingWord = function(mode, search) + local word = "" + if mode == "v" then + word = get_visual_selection() + else + word = "\\<" .. fn.expand("<cword>") .. "\\>" + end + if #word == 0 then + return + end + word = get_reg_ex(word) + + if search then + if word == fn.getreg("/") then + fn.setreg("/", "") + word = "" + else + fn.setreg("/", word) + vim.cmd("set hls") + end + else + if m.words[word] then + uncolor(word) + word = "" + else + color(word) + end + end + + if m.config.search_count then + m.search_count(word) + end + end + + m.UncolorAllWords = function(search) + m.search_count("") + if search then + fn.setreg("/", "") + else + local windows = api.nvim_list_wins() + for _, v in pairs(m.words) do + for _, i in ipairs(windows) do + pcall(function() + fn.matchdelete(v.mid, i) + end) + end + m.colors[v.color] = v.mid + end + + m.words = {} + end + end + + m.setup = function(opt) + opt = opt or {} + m.config = vim.tbl_deep_extend("force", get_default_config(), opt) + + init_colors() + math.randomseed(uv.now()) + + local group = api.nvim_create_augroup("InterestingWordsGroup", { clear = true }) + api.nvim_create_autocmd({ "WinEnter" }, { + callback = function() + recolorAllWords() + local windows = api.nvim_list_wins() + for _, i in ipairs(windows) do + hide_search_count(api.nvim_win_get_buf(fn.win_getid(i))) + end + end, + group = group, + }) + + if m.config.navigation then + vim.keymap.set("n", "n", function() + m.NavigateToWord(true) + end, { noremap = true, silent = true, desc = "InterestingWord Navigation Forward" }) + vim.keymap.set("n", "N", m.NavigateToWord, + { noremap = true, silent = true, desc = "InterestingWord Navigation Backword" }) + end + + if m.config.search_key then + vim.keymap.set("n", m.config.search_key, function() + m.InterestingWord("n", true) + end, { noremap = true, silent = true, desc = "InterestingWord Toggle Search" }) + vim.keymap.set("x", m.config.search_key, function() + m.InterestingWord("v", true) + end, { noremap = true, silent = true, desc = "InterestingWord Toggle Search" }) + vim.keymap.set("n", m.config.cancel_search_key, function() + m.UncolorAllWords(true) + end, { noremap = true, silent = true, desc = "InterestingWord Unsearch" }) + end + + if m.config.color_key then + vim.keymap.set("n", m.config.color_key, function() + m.InterestingWord("n", false) + end, { noremap = true, silent = true, desc = "InterestingWord Toggle Color" }) + vim.keymap.set("x", m.config.color_key, function() + m.InterestingWord("v", false) + end, { noremap = true, silent = true, desc = "InterestingWord Toggle Color" }) + vim.keymap.set("n", m.config.cancel_color_key, function() + m.UncolorAllWords() + end, { noremap = true, silent = true, desc = "InterestingWord Uncolor" }) + end + + if m.config.search_count then + m.init_search_count() + end + end + + return m +end)() + +interestingwords.setup({ + select_mode = "loop", -- or "random" + scroll_center = false, + search_key = "<leader>S", + cancel_search_key = "<leader>C", + color_key = "<leader>H", + cancel_color_key = "<leader>C", + colors = { "#ff5f5f", "#5fafff", "#afff5f", "#ffd75f" }, +}) diff --git a/common/nvim/lua/plugins/leetcode.lua b/common/nvim/lua/plugins/leetcode.lua new file mode 100755 index 0000000..50369e1 --- /dev/null +++ b/common/nvim/lua/plugins/leetcode.lua @@ -0,0 +1,68 @@ +---@alias lc.lang +---| "cpp" +---| "java" +---| "python" +---| "python3" +---| "c" +---| "csharp" +---| "javascript" +---| "typescript" +---| "php" +---| "swift" +---| "kotlin" +---| "dart" +---| "golang" +---| "ruby" +---| "scala" +---| "rust" +---| "racket" +---| "erlang" +---| "elixir" + +---@alias lc.sql_lang +---| "pythondata" +---| "mysql" +---| "mssql" +---| "oraclesql" + +---@alias lc.domain +---| "com" +---| "cn" + +---@class lc.UserConfig +local M = { + ---@type lc.domain + domain = 'com', -- For now "com" is the only one supported + + ---@type string + arg = 'leetcode.nvim', + + ---@type lc.lang + lang = 'cpp', + + ---@type lc.sql_lang + sql = 'mysql', + + ---@type string + directory = vim.fn.stdpath('data') .. '/leetcode/', + + ---@type boolean + logging = true, + + console = { + ---@type boolean + open_on_runcode = false, + + size = { + width = '75%', ---@type string | integer + height = '75%', ---@type string | integer + }, + dir = 'row', ---@type "col" | "row" + }, + + description = { + width = '40%', ---@type string | integer + }, +} + +return M diff --git a/common/nvim/lua/plugins/loclist.lua b/common/nvim/lua/plugins/loclist.lua new file mode 100755 index 0000000..9b72a94 --- /dev/null +++ b/common/nvim/lua/plugins/loclist.lua @@ -0,0 +1,18 @@ +local M = {} + +function M.loclist_toggle() + for _, info in ipairs(vim.fn.getwininfo()) do + if info.loclist == 1 then + vim.cmd('lclose') + return + end + end + + if next(vim.fn.getloclist(0)) == nil then + print('loc list empty') + return + end + vim.cmd('lopen') +end + +return M diff --git a/common/nvim/lua/plugins/lsp.lua b/common/nvim/lua/plugins/lsp.lua new file mode 100755 index 0000000..5ed1152 --- /dev/null +++ b/common/nvim/lua/plugins/lsp.lua @@ -0,0 +1,674 @@ +local M = {} + +-- Safe require helper +local function safe_require(name) + local ok, mod = pcall(require, name) + return ok and mod or nil +end + +-- Autocmd groups for managing event listeners +local augroup_format = vim.api.nvim_create_augroup("LspFormattingOnSave", { clear = true }) +local augroup_diag_float = vim.api.nvim_create_augroup("ShowLineDiagnostics", { clear = true }) +local augroup_diag_load = vim.api.nvim_create_augroup("OpenDiagnosticsOnLoad", { clear = true }) +local augroup_highlight = vim.api.nvim_create_augroup("LspDocumentHighlight", { clear = true }) + +-- Border for floating windows +local border = { + { "┌", "FloatBorder" }, { "─", "FloatBorder" }, { "┐", "FloatBorder" }, + { "│", "FloatBorder" }, { "┘", "FloatBorder" }, { "─", "FloatBorder" }, + { "└", "FloatBorder" }, { "│", "FloatBorder" } +} + +-- Initialize LSP modules +local function init_modules() + -- Silently try to load each module + M.lspconfig = safe_require("lspconfig") + M.mason = safe_require("mason") + M.mason_lspconfig = safe_require("mason-lspconfig") + M.mason_tool_installer = safe_require("mason-tool-installer") + M.null_ls = safe_require("null-ls") + + if M.null_ls then + M.builtins = M.null_ls.builtins + end + + return true +end + +-- Check Neovim version compatibility and feature availability +local function has_feature(feature) + if feature == "diagnostic_api" then + return vim.fn.has("nvim-0.6") == 1 + elseif feature == "native_lsp_config" then + -- Check for both vim.lsp.enable AND vim.lsp.config + return vim.fn.has("nvim-0.11") == 1 and vim.lsp.enable ~= nil + elseif feature == "lsp_get_client_by_id" then + return vim.fn.has("nvim-0.10") == 1 + elseif feature == "cmp_nvim_lsp" then + return pcall(require, "cmp_nvim_lsp") + elseif feature == "virtual_text_disabled_by_default" then + return vim.fn.has("nvim-0.11") == 1 + elseif feature == "deprecated_lsp_handlers" then + -- vim.lsp.handlers.hover and signature_help deprecated in 0.12, removed in 0.13 + return vim.fn.has("nvim-0.12") == 0 + elseif feature == "new_lsp_config_api" then + -- New LSP config API available from 0.12+ + return vim.fn.has("nvim-0.12") == 1 and vim.lsp.config ~= nil + end + return false +end + +-- Backwards compatible capabilities setup +local function setup_capabilities() + local capabilities + + if has_feature("cmp_nvim_lsp") then + capabilities = require('cmp_nvim_lsp').default_capabilities() + elseif vim.lsp.protocol and vim.lsp.protocol.make_client_capabilities then + capabilities = vim.lsp.protocol.make_client_capabilities() + else + capabilities = {} + end + + -- Add snippet support if available + if capabilities.textDocument then + capabilities.textDocument.completion = capabilities.textDocument.completion or {} + capabilities.textDocument.completion.completionItem = + capabilities.textDocument.completion.completionItem or {} + capabilities.textDocument.completion.completionItem.snippetSupport = true + end + + -- Set offset encoding for newer versions (0.11+ supports utf-8 and utf-32) + if vim.fn.has("nvim-0.11") == 1 then + capabilities.offsetEncoding = { "utf-8", "utf-32", "utf-16" } + elseif vim.fn.has("nvim-0.9") == 1 then + capabilities.offsetEncoding = { "utf-8", "utf-16" } + end + + return capabilities +end + +-- Default LSP keymaps (fallback if external not available) +local function setup_fallback_keymaps(bufnr) + -- Only set up minimal fallbacks, prefer external setup + local opts = { buffer = bufnr, silent = true, noremap = true } + vim.keymap.set('n', 'gd', vim.lsp.buf.definition, opts) + vim.keymap.set('n', 'K', vim.lsp.buf.hover, opts) + vim.keymap.set('n', '[d', vim.diagnostic.goto_prev, opts) + vim.keymap.set('n', ']d', vim.diagnostic.goto_next, opts) +end + +-- Create LSP directory and config files for native LSP +local function setup_native_lsp_configs() + local config_path = vim.fn.stdpath("config") + local lsp_dir = config_path .. "/lsp" + + -- Create lsp directory if it doesn't exist + vim.fn.mkdir(lsp_dir, "p") + + -- LSP server configurations for native config + local server_configs = { + lua_ls = { + cmd = { "lua-language-server" }, + filetypes = { "lua" }, + root_markers = { ".luarc.json", ".luarc.jsonc", ".luacheckrc", ".stylua.toml", "stylua.toml", "selene.toml", "selene.yml" }, + settings = { + Lua = { + diagnostics = { + globals = { "vim", "use", "_G", "packer_plugins", "P" }, + disable = { + "undefined-global", + "lowercase-global", + "unused-local", + "unused-vararg", + "trailing-space" + }, + }, + workspace = { + library = { + vim.env.VIMRUNTIME, + "${3rd}/luv/library", + "${3rd}/busted/library", + }, + checkThirdParty = false, + }, + telemetry = { + enable = false, + }, + }, + }, + }, + + pyright = { + cmd = { "pyright-langserver", "--stdio" }, + filetypes = { "python" }, + root_markers = { "pyproject.toml", "setup.py", "setup.cfg", "requirements.txt", "Pipfile", "pyrightconfig.json" }, + settings = { + python = { + formatting = { + provider = "none" + } + } + } + }, + + ts_ls = { + cmd = { "typescript-language-server", "--stdio" }, + filetypes = { "javascript", "javascriptreact", "javascript.jsx", "typescript", "typescriptreact", "typescript.tsx" }, + root_markers = { "tsconfig.json", "jsconfig.json", "package.json" }, + init_options = { + disableAutomaticTypeAcquisition = true + }, + }, + + rust_analyzer = { + cmd = { "rust-analyzer" }, + filetypes = { "rust" }, + root_markers = { "Cargo.toml", "rust-project.json" }, + }, + + clangd = { + cmd = { "clangd", "--background-index", "--clang-tidy", "--header-insertion=iwyu" }, + filetypes = { "c", "cpp", "objc", "objcpp", "cuda", "proto" }, + root_markers = { ".clangd", ".clang-tidy", ".clang-format", "compile_commands.json", "compile_flags.txt", "configure.ac" }, + }, + + gopls = { + cmd = { "gopls" }, + filetypes = { "go", "gomod", "gowork", "gotmpl" }, + root_markers = { "go.work", "go.mod" }, + settings = { + gopls = { + gofumpt = true, + codelenses = { + gc_details = false, + generate = true, + regenerate_cgo = true, + run_govulncheck = true, + test = true, + tidy = true, + upgrade_dependency = true, + vendor = true, + }, + hints = { + assignVariableTypes = true, + compositeLiteralFields = true, + compositeLiteralTypes = true, + constantValues = true, + functionTypeParameters = true, + parameterNames = true, + rangeVariableTypes = true, + }, + analyses = { + fieldalignment = true, + nilness = true, + unusedparams = true, + unusedwrite = true, + useany = true, + }, + usePlaceholders = true, + completeUnimported = true, + staticcheck = true, + directoryFilters = { "-.git", "-.vscode", "-.idea", "-.vscode-test", "-node_modules" }, + semanticTokens = true, + }, + }, + }, + + -- Add more basic configs + bashls = { + cmd = { "bash-language-server", "start" }, + filetypes = { "sh", "bash" }, + }, + + --html = { + -- cmd = { "vscode-html-language-server", "--stdio" }, + -- filetypes = { "html" }, + --}, + + --cssls = { + -- cmd = { "vscode-css-language-server", "--stdio" }, + -- filetypes = { "css", "scss", "less" }, + --}, + + --jsonls = { + -- cmd = { "vscode-json-language-server", "--stdio" }, + -- filetypes = { "json", "jsonc" }, + --}, + + yamlls = { + cmd = { "yaml-language-server", "--stdio" }, + filetypes = { "yaml", "yml" }, + }, + } + + -- Write config files to lsp directory + for server_name, config in pairs(server_configs) do + local file_path = lsp_dir .. "/" .. server_name .. ".lua" + local file_content = "return " .. vim.inspect(config) + + -- Only write if file doesn't exist to avoid overwriting user customizations + if vim.fn.filereadable(file_path) == 0 then + local file = io.open(file_path, "w") + if file then + file:write(file_content) + file:close() + vim.notify("Created LSP config: " .. file_path, vim.log.levels.DEBUG) + end + end + end + + return vim.tbl_keys(server_configs) +end + +-- Set up LSP on_attach function +local function create_on_attach() + return function(client, bufnr) + -- Your existing keymap setup function from keys.lua + if _G.setup_lsp_keymaps then + _G.setup_lsp_keymaps(bufnr) + else + setup_fallback_keymaps(bufnr) + end + + -- Disable LSP formatting in favor of null-ls (if null-ls is available) + if M.null_ls then + client.server_capabilities.documentFormattingProvider = false + client.server_capabilities.documentRangeFormattingProvider = false + end + + -- Disable specific LSP capabilities to avoid conflicts + if client.name == "ruff" then + -- Disable ruff hover in favor of Pyright + client.server_capabilities.hoverProvider = false + elseif client.name == "ts_ls" then + -- Disable ts_ls formatting in favor of prettier via null-ls + client.server_capabilities.documentFormattingProvider = false + client.server_capabilities.documentRangeFormattingProvider = false + elseif client.name == "pyright" and M.null_ls then + -- Disable pyright formatting in favor of black/isort via null-ls + client.server_capabilities.documentFormattingProvider = false + client.server_capabilities.documentRangeFormattingProvider = false + end + + -- Set log level (backwards compatible) + if vim.lsp.set_log_level then + vim.lsp.set_log_level("warn") + end + + -- Document highlight on cursor hold + if client.server_capabilities and client.server_capabilities.documentHighlightProvider then + vim.api.nvim_create_autocmd("CursorHold", { + group = augroup_highlight, + buffer = bufnr, + callback = function() + if vim.lsp.buf.document_highlight then + vim.lsp.buf.document_highlight() + end + end, + }) + vim.api.nvim_create_autocmd("CursorMoved", { + group = augroup_highlight, + buffer = bufnr, + callback = function() + if vim.lsp.buf.clear_references then + vim.lsp.buf.clear_references() + end + end, + }) + end + end +end + +-- Set up basic LSP configuration +function M.setup() + -- Initialize all required modules + init_modules() + + -- Enable virtual_text diagnostics by default for 0.11+ (since it's disabled by default) + if has_feature("virtual_text_disabled_by_default") then + vim.diagnostic.config({ virtual_text = true }) + end + + -- Set up Mason if available (useful for tool management) + if M.mason then + M.mason.setup({ + ui = { + border = 'rounded', + icons = { + package_installed = '✓', + package_pending = '➜', + package_uninstalled = '✗' + } + } + }) + end + + -- Set up mason-tool-installer if available + if M.mason_tool_installer then + M.mason_tool_installer.setup({ + ensure_installed = { + -- Language servers + "lua-language-server", "pyright", "typescript-language-server", "rust-analyzer", + "clangd", "bash-language-server", "yaml-language-server", + -- Formatters + "stylua", "clang-format", "prettier", "shfmt", "black", "isort", "goimports", + "sql-formatter", "shellharden", + -- Linters/Diagnostics + "eslint_d", "selene", "flake8", "dotenv-linter", "phpcs", + -- Utilities + "jq" + }, + auto_update = false, + run_on_start = true, + start_delay = 3000, + }) + end + + -- Set up null-ls if available + if M.null_ls and M.builtins then + local sources = { + M.builtins.diagnostics.selene.with({ + condition = function(utils) + return utils.root_has_file({"selene.toml"}) + end, + }), + M.builtins.diagnostics.dotenv_linter, + M.builtins.diagnostics.tidy, + M.builtins.diagnostics.phpcs.with({ + condition = function(utils) + return utils.root_has_file({"phpcs.xml", "phpcs.xml.dist", ".phpcs.xml", ".phpcs.xml.dist"}) + end, + }), + + -- Formatters (prioritized over LSP formatting) + M.builtins.formatting.stylua.with({ + extra_args = { "--quote-style", "AutoPreferSingle", "--indent-width", "2", "--column-width", "160" }, + condition = function(utils) + return utils.root_has_file({"stylua.toml", ".stylua.toml"}) + end, + }), + M.builtins.formatting.prettier.with({ + extra_args = { "--single-quote", "--tab-width", "4", "--print-width", "100" }, + filetypes = { "javascript", "javascriptreact", "typescript", "typescriptreact", "vue", "css", "scss", "less", "html", "json", "jsonc", "yaml", "markdown", "graphql", "handlebars" }, + prefer_local = "node_modules/.bin", + }), + M.builtins.formatting.black.with({ + extra_args = { "--fast" }, + prefer_local = ".venv/bin", + }), + M.builtins.formatting.isort.with({ + extra_args = { "--profile", "black" }, + prefer_local = ".venv/bin", + }), + M.builtins.formatting.goimports, + M.builtins.formatting.clang_format.with({ + extra_args = { "--style", "{BasedOnStyle: Google, IndentWidth: 4}" } + }), + M.builtins.formatting.shfmt.with({ + extra_args = { "-i", "2", "-ci" } + }), + M.builtins.formatting.shellharden, + M.builtins.formatting.sql_formatter, + M.builtins.formatting.dart_format, + + -- Code actions + M.builtins.code_actions.gitsigns, + M.builtins.code_actions.gitrebase, + } + + M.null_ls.setup({ + sources = sources, + update_in_insert = false, + on_attach = function(client, bufnr) + -- Disable LSP formatting in favor of null-ls + client.server_capabilities.documentFormattingProvider = false + client.server_capabilities.documentRangeFormattingProvider = false + + local function lsp_supports_method(client, method) + if client.supports_method then + return client:supports_method(method) + elseif client.server_capabilities then + local capability_map = { + ["textDocument/formatting"] = "documentFormattingProvider", + ["textDocument/rangeFormatting"] = "documentRangeFormattingProvider", + ["textDocument/hover"] = "hoverProvider", + ["textDocument/signatureHelp"] = "signatureHelpProvider", + ["textDocument/documentHighlight"] = "documentHighlightProvider", + } + local cap = capability_map[method] + return cap and client.server_capabilities[cap] + end + return false + end + + if lsp_supports_method(client, "textDocument/formatting") then + vim.api.nvim_create_autocmd("BufWritePre", { + group = augroup_format, + buffer = bufnr, + callback = function() + if vim.fn.has("nvim-0.8") == 1 then + vim.lsp.buf.format({ + async = false, + bufnr = bufnr, + filter = function(formatting_client) + return formatting_client.name == "null-ls" + end, + }) + else + vim.lsp.buf.formatting_sync() + end + end, + }) + end + end, + }) + end + + -- Set up LSP capabilities + local capabilities = setup_capabilities() + local on_attach = create_on_attach() + + -- Set up LSP handlers with version compatibility (avoid deprecated APIs) + if has_feature("deprecated_lsp_handlers") then + -- Use old handler setup for versions before 0.12 + if vim.lsp.handlers then + vim.lsp.handlers['textDocument/hover'] = vim.lsp.with( + vim.lsp.handlers.hover, { border = 'rounded' } + ) + + vim.lsp.handlers['textDocument/signatureHelp'] = vim.lsp.with( + vim.lsp.handlers.signature_help, { border = 'rounded' } + ) + end + else + -- Use new handler setup for 0.12+ (when old handlers are deprecated/removed) + if vim.lsp.handlers then + vim.lsp.handlers['textDocument/hover'] = vim.lsp.with( + vim.lsp.handlers['textDocument/hover'], { border = 'rounded' } + ) + + vim.lsp.handlers['textDocument/signatureHelp'] = vim.lsp.with( + vim.lsp.handlers['textDocument/signatureHelp'], { border = 'rounded' } + ) + end + end + + -- Choose configuration method based on Neovim version and available features + if has_feature("native_lsp_config") then + -- Set up native LSP configuration + local servers = setup_native_lsp_configs() + + -- Set default on_attach and capabilities for all LSP servers + vim.lsp.config('*', { + on_attach = on_attach, + capabilities = capabilities, + }) + + -- Enable the LSP servers + vim.lsp.enable(servers) + + elseif M.mason_lspconfig and M.lspconfig then + -- Set up mason-lspconfig if available + if M.mason_lspconfig then + M.mason_lspconfig.setup({ + ensure_installed = { + "lua_ls", "pyright", "ts_ls", "rust_analyzer", "clangd", "gopls", + "bashls", "html", "cssls", "jsonls", "yamlls" + }, + automatic_installation = true, + }) + end + + -- Use traditional lspconfig with mason + local enabled_servers = {} + + local server_configs = { + lua_ls = { + settings = { + Lua = { + diagnostics = { + globals = { "vim", "use", "_G", "packer_plugins", "P" }, + }, + workspace = { + library = { + vim.env.VIMRUNTIME, + "${3rd}/luv/library", + "${3rd}/busted/library", + }, + checkThirdParty = false, + }, + telemetry = { enable = false }, + }, + }, + }, + pyright = { + settings = { + python = { + formatting = { provider = "none" } + } + } + }, + ts_ls = { + init_options = { + disableAutomaticTypeAcquisition = true + }, + }, + clangd = { + cmd = { "clangd", "--background-index", "--clang-tidy", "--header-insertion=iwyu" }, + }, + gopls = { + settings = { + gopls = { + gofumpt = true, + usePlaceholders = true, + completeUnimported = true, + staticcheck = true, + }, + }, + }, + } + + M.mason_lspconfig.setup_handlers({ + function(server_name) + if not enabled_servers[server_name] then + local config = server_configs[server_name] or {} + config.on_attach = on_attach + config.capabilities = capabilities + M.lspconfig[server_name].setup(config) + enabled_servers[server_name] = true + end + end, + }) + + elseif M.lspconfig then + -- Fallback: Set up servers manually if mason-lspconfig is not available + local servers = { 'lua_ls', 'pyright', 'ts_ls', 'rust_analyzer', 'clangd', 'gopls', 'bashls', 'html', 'cssls', 'jsonls', 'yamlls' } + local enabled_servers = {} + + for _, server in ipairs(servers) do + if not enabled_servers[server] and M.lspconfig[server] then + local config = { + on_attach = on_attach, + capabilities = capabilities, + } + M.lspconfig[server].setup(config) + enabled_servers[server] = true + end + end + end + + return true +end + +-- Global toggle for diagnostics (backwards compatible) +vim.g.diagnostics_visible = true +function _G.toggle_diagnostics() + if has_feature("diagnostic_api") then + if vim.g.diagnostics_visible then + vim.g.diagnostics_visible = false + vim.diagnostic.disable() + else + vim.g.diagnostics_visible = true + vim.diagnostic.enable() + end + else + -- Fallback for older versions + if vim.g.diagnostics_visible then + vim.g.diagnostics_visible = false + vim.lsp.handlers["textDocument/publishDiagnostics"] = function() end + else + vim.g.diagnostics_visible = true + vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with( + vim.lsp.diagnostic.on_publish_diagnostics, {} + ) + end + end +end + +-- Create Mason command if Mason is available +if M.mason then + vim.api.nvim_create_user_command("Mason", function() + require("mason.ui").open() + end, {}) +end + +-- Automatically show diagnostics in a float window for the current line +if has_feature("diagnostic_api") then + vim.api.nvim_create_autocmd("CursorHold", { + group = augroup_diag_float, + pattern = "*", + callback = function() + local opts = { + focusable = false, + close_events = { "BufLeave", "CursorMoved", "InsertEnter", "FocusLost" }, + border = border, + source = "always", + prefix = " ", + scope = "cursor", + } + vim.diagnostic.open_float(nil, opts) + end, + }) + + -- Autocmd to open the diagnostic window when a file with errors is opened + vim.api.nvim_create_autocmd({ "LspAttach", "BufReadPost" }, { + group = augroup_diag_load, + callback = function() + local has_errors = #vim.diagnostic.get(0, { severity = vim.diagnostic.severity.ERROR }) > 0 + if has_errors then + vim.diagnostic.setqflist({ + open = true, + title = "Diagnostics", + }) + end + end, + }) +end + +-- Create Toggle Diagnostic command +vim.api.nvim_create_user_command("ToggleDiagnostics", _G.toggle_diagnostics, { + desc = "Toggle global diagnostics visibility" +}) + +return M diff --git a/common/nvim/lua/plugins/lualine.lua b/common/nvim/lua/plugins/lualine.lua new file mode 100755 index 0000000..9c1cc43 --- /dev/null +++ b/common/nvim/lua/plugins/lualine.lua @@ -0,0 +1,22 @@ +-- lualine.nvim plugin config (modular, robust) +local ok, lualine = pcall(require, 'lualine') +if not ok then return end +local nvim_version = vim.version() +if nvim_version.major == 0 and nvim_version.minor < 5 then return end +lualine.setup({ + options = { + theme = 'auto', + icons_enabled = true, + section_separators = '', + component_separators = '', + disabled_filetypes = {}, + }, + sections = { + lualine_a = {'mode'}, + lualine_b = {'branch', 'diff', 'diagnostics'}, + lualine_c = {'filename'}, + lualine_x = {'encoding', 'fileformat', 'filetype'}, + lualine_y = {'progress'}, + lualine_z = {'location'}, + }, +})
\ No newline at end of file diff --git a/common/nvim/lua/plugins/luasnip.lua b/common/nvim/lua/plugins/luasnip.lua new file mode 100755 index 0000000..75f4c28 --- /dev/null +++ b/common/nvim/lua/plugins/luasnip.lua @@ -0,0 +1,13 @@ +-- LuaSnip plugin config (modular, robust) +local ok, luasnip = pcall(require, 'luasnip') +if not ok then return end +local nvim_version = vim.version() +if nvim_version.major == 0 and nvim_version.minor < 5 then return end +-- Load friendly-snippets if available +pcall(function() + require('luasnip.loaders.from_vscode').lazy_load() +end) +luasnip.config.set_config({ + history = true, + updateevents = "TextChanged,TextChangedI", +})
\ No newline at end of file diff --git a/common/nvim/lua/plugins/messages.lua b/common/nvim/lua/plugins/messages.lua new file mode 100755 index 0000000..8e46c09 --- /dev/null +++ b/common/nvim/lua/plugins/messages.lua @@ -0,0 +1,85 @@ +local M = { + 'Why do programmers prefer dark mode? Because light attracts bugs!', + 'Why did the AI break up with its computer? It found someone with better algorithms!', + "Why do Python programmers prefer snakes? Because they can't stand Java!", + 'Why did the developer go to the beach? To catch some rays and debug JavaScript!', + "Why was the HTML document lonely? It didn't have any <body> to share its content with!", + "Why did the CSS file break up with the HTML file? It couldn't stand the layout!", + 'Why do programmers always mix up Christmas and Halloween? Because Oct 31 == Dec 25!', + 'Why did the computer take up gardening? It wanted to improve its root system!', + 'Why do programmers prefer dark chocolate? It has better byte-size!', + "Why did the developer get mad at their computer? It couldn't understand their emotional code!", + 'Why was the JavaScript developer so good at relationships? They knew how to handle callbacks!', + 'Why did the coder go broke? They lost all their cache!', + 'Why did the SQL query go to therapy? It had too many inner joins!', + 'Why did the programmer plant a light bulb? They wanted to grow a power plant!', + 'Why did the computer keep its drink on the windowsill? It wanted a byte!', + "Why don't programmers like nature? It has too many bugs!", + 'Why did the developer go broke? They spent all their money on keyboard shortcuts!', + 'Why did the computer cross the road? To get to the other website!', + 'Why was the code cold? It left its Windows open!', + 'Why did the coder go to therapy? They had too many issues!', + 'Why was the function sad? It returned null!', + "Why did the programmer quit their job? They didn't get arrays!", + 'Why was the loop so fast? It was in a hurry!', + 'Why was the computer cold? It left its Windows open!', + "Why did the developer stay calm during the crisis? Because they knew how to 'handle' exceptions!", + "Why did the JavaScript developer always smile? Because they had 'callbacks' for everything!", + "Why did the programmer break up with their keyboard? It had too many 'commitment' issues!", + "Why don't Neovim users ever get lost in their text files? Because they always 'find' their way!", + "Why don't Neovim users need a GPS? Because they're experts at 'mapping' their routes!", + 'Why did the Neovim user become a musician? Because they can play the keyboard like a pro!', + "Why don't Neovim users ever lose track of time? Because they have a 'status line' to keep them informed!", + "Why did the Neovim user open a detective agency? Because they have an 'eye' for spotting code errors!", + 'Why did the developer bring a ladder to the coding competition? To take their code to the next level!', + "When your code is running slowly: 'It's not a bug; it's a feature that takes its time.'", + "Why did the programmer go to therapy? Because their code had too many 'issues'!", + "Why was the JavaScript developer sad? Because they didn't 'console' their feelings!", + "Why did the developer get locked out of their own codebase? They forgot the 'key'!", + 'Welcome to Neovim, where plugins multiply faster than rabbits!', + "How many programmers does it take to change a lightbulb? None, that's a hardware problem!", + "When you're debugging and can't find the issue: 'I swear, it was working yesterday!'", + "Why don't programmers trust stairs? Because they're always up to 'something'!", + "When you fix a bug without even trying: 'I guess I'm just that good.'", + 'Why was the computer cold? It left its Windows open!', + "Why do Java developers wear glasses? Because they don't C#!", + "Why did the programmer quit their job? They didn't get arrays!", + "When you write a one-liner that solves a complex problem: 'I am a genius, yes, I am.'", + "When you refactor your code and it breaks everything: 'I've made a huge mistake.'", + "When you accidentally close your editor with unsaved changes: 'Goodbye, cruel world.'", + "When you discover a bug on a Friday afternoon: 'Looks like we're working late again.'", + "When you realize your code from last year: 'Who wrote this junk? Oh, wait...'", + "When you write a comment and six months later can't understand it: 'I speak my own language.'", + "When you join a new project with zero documentation: 'Here be dragons.'", + "When you add a 'TODO' comment and hope someone else will deal with it: 'Not my problem.'", + "Remember, coding is not just about writing code; it's about solving problems.", + 'Stay curious and never stop learning. Technology is always evolving.', + "When debugging, don't guess; use systematic troubleshooting techniques.", + "Keep your code DRY (Don't Repeat Yourself) to make it more maintainable.", + 'Use meaningful variable and function names. Your code should read like a story.', + 'Always test your code thoroughly before deploying it. Automated tests are your friends.', + 'Spend time designing your code before jumping into implementation. Good architecture pays off.', + 'Learn to break down complex problems into smaller, manageable tasks.', + "Code with the future in mind. Write code that's easy to understand and maintain.", + 'Version control is your safety net. Use Git or other VCS systems religiously.', + 'Document your code and processes. It will save you and your team countless hours.', + "Don't optimize prematurely. Measure first, then optimize where it matters.", + "Read other people's code. It's a great way to learn different coding styles and techniques.", + 'Stay organized with your project structure. Consistency makes collaboration smoother.', + 'Take regular breaks to prevent burnout. Your productivity will thank you.', + 'Use comments sparingly but effectively. Explain why, not just what.', + 'Consider pair programming or code reviews to catch issues early and learn from others.', + 'Know when to ask for help. Programming is a team effort.', + "Programming is not just about the code; it's about the problem-solving mindset.", + 'Keep your development environment clean and well-maintained for consistent productivity.', + 'Learn from your mistakes and failures; they are valuable lessons in programming.', + 'When faced with a bug, isolate and reproduce it before attempting to fix it.', + "Why did the developer stay calm during the crisis? Because they knew how to 'handle' exceptions.", + "Why was the JavaScript developer always smiling? Because they had 'callbacks' for everything!", + "Why did the programmer break up with their keyboard? It had too many 'commitment' issues!", + "Margaret Hamilton coined the term 'software engineer.'", + 'Why did the function go to therapy? It had too many issues!', + "Why don't programmers like nature? It has too many bugs!", +} + +return M diff --git a/common/nvim/lua/plugins/modify-blend.lua b/common/nvim/lua/plugins/modify-blend.lua new file mode 100755 index 0000000..1b2c6d5 --- /dev/null +++ b/common/nvim/lua/plugins/modify-blend.lua @@ -0,0 +1,43 @@ +local ui = vim.api.nvim_list_uis()[1] + +local bufnr = vim.api.nvim_create_buf(true, true) +local win = vim.api.nvim_open_win(bufnr, true, { + relative = "editor", + --relative = "cursor", + width = ui.width, + height = ui.height, + anchor = "NE", + row = 10, + col = 10, + style = "minimal", + zindex = 50, +}) + +vim.api.nvim_win_set_option(win, "winblend", 1) + +local blend_start = 15 +local offset = 1 + +CANCEL = false +local timer = vim.loop.new_timer() +timer:start( + 0, + 50, + vim.schedule_wrap(function() + blend_start = blend_start + offset + + if blend_start > 90 then + offset = -1 + elseif blend_start < 10 then + offset = 1 + end + + if CANCEL or not vim.api.nvim_win_is_valid(win) then + timer:close() + timer:stop() + return + end + + vim.cmd([[highlight NormalFloat blend=]] .. tostring(blend_start)) + end) +) diff --git a/common/nvim/lua/plugins/navic.lua b/common/nvim/lua/plugins/navic.lua new file mode 100755 index 0000000..a574d5c --- /dev/null +++ b/common/nvim/lua/plugins/navic.lua @@ -0,0 +1,51 @@ +local M = {} + +function M.setup() + local ok, navic = pcall(require, "nvim-navic") + if not ok or not navic then + return false + end + + navic.setup({ + icons = { + File = " ", + Module = " ", + Namespace = " ", + Package = " ", + Class = " ", + Method = " ", + Property = " ", + Field = " ", + Constructor = " ", + Enum = "練", + Interface = "練", + Function = " ", + Variable = " ", + Constant = " ", + String = " ", + Number = " ", + Boolean = "◩ ", + Array = " ", + Object = " ", + Key = " ", + Null = "ﳠ ", + EnumMember = " ", + Struct = " ", + Event = " ", + Operator = " ", + TypeParameter = " " + }, + highlight = false, + separator = " > ", + depth_limit = 0, + depth_limit_indicator = "..", + safe_output = true, + lsp = { + auto_attach = true + } + }) + + return true +end + +return M diff --git a/common/nvim/lua/plugins/neodev.lua b/common/nvim/lua/plugins/neodev.lua new file mode 100755 index 0000000..07843e1 --- /dev/null +++ b/common/nvim/lua/plugins/neodev.lua @@ -0,0 +1,45 @@ +local M = {} + +--- Setup and configure neodev +-- This function initializes neodev with configurations for better Lua development experience +-- @return boolean True if setup was successful, false otherwise +function M.setup() + local ok, neodev = pcall(require, 'neodev') + if not ok then + return false + end + + neodev.setup({ + --library = { plugins = { "nvim-dap-ui" }, types = true }, + --library = { plugins = { "neotest" }, types = true }, + library = { + enabled = true, -- when not enabled, neodev will not change any settings to the LSP server + -- these settings will be used for your Neovim config directory + runtime = true, -- runtime path + types = true, -- full signature, docs and completion of vim.api, vim.treesitter, vim.lsp and others + --plugins = { "neotest" }, + --{ "nvim-dap-ui" }, + --plugins = true, -- installed opt or start plugins in packpath + -- you can also specify the list of plugins to make available as a workspace library + -- plugins = { "nvim-treesitter", "plenary.nvim", "telescope.nvim" }, + plugins = { "nvim-treesitter", "plenary.nvim", "telescope.nvim", "neotest", "nvim-dap-ui" }, + }, + setup_jsonls = true, -- configures jsonls to provide completion for project specific .luarc.json files + -- for your Neovim config directory, the config.library settings will be used as is + -- for plugin directories (root_dirs having a /lua directory), config.library.plugins will be disabled + -- for any other directory, config.library.enabled will be set to false + override = function(root_dir, options) + end, + -- With lspconfig, Neodev will automatically setup your lua-language-server + -- If you disable this, then you have to set {before_init=require("neodev.lsp").before_init} + -- in your lsp start options + lspconfig = true, + -- much faster, but needs a recent built of lua-language-server + -- needs lua-language-server >= 3.6.0 + pathStrict = true, + }) + + return true +end + +return M diff --git a/common/nvim/lua/plugins/neoscroll.lua b/common/nvim/lua/plugins/neoscroll.lua new file mode 100755 index 0000000..f2ecb04 --- /dev/null +++ b/common/nvim/lua/plugins/neoscroll.lua @@ -0,0 +1,22 @@ +local M = {} + +function M.setup() + local ok, neoscroll = pcall(require, 'neoscroll') + if not ok then + return false + end + + -- Basic configuration + neoscroll.setup({ + mappings = {'<C-u>', '<C-d>', '<C-b>', '<C-f>', '<C-y>', '<C-e>', 'zt', 'zz', 'zb'}, + hide_cursor = true, + stop_eof = true, + respect_scrolloff = false, + cursor_scrolls_alone = true, + easing_function = 'quadratic', + }) + + return true +end + +return M diff --git a/common/nvim/lua/plugins/neotest.lua b/common/nvim/lua/plugins/neotest.lua new file mode 100755 index 0000000..1034d33 --- /dev/null +++ b/common/nvim/lua/plugins/neotest.lua @@ -0,0 +1,38 @@ +local M = {} + +function M.setup() + local ok, neotest = pcall(require, "neotest") + if not ok or not neotest then + return false + end + + -- Safely require adapters + local python_ok, python_adapter = pcall(require, "neotest-python") + local plenary_ok, plenary_adapter = pcall(require, "neotest-plenary") + local vim_test_ok, vim_test_adapter = pcall(require, "neotest-vim-test") + + local adapters = {} + if python_ok and python_adapter then + table.insert(adapters, python_adapter({ + dap = { justMyCode = false }, + })) + end + + if plenary_ok and plenary_adapter then + table.insert(adapters, plenary_adapter) + end + + if vim_test_ok and vim_test_adapter then + table.insert(adapters, vim_test_adapter({ + ignore_file_types = { "python", "vim", "lua" }, + })) + end + + neotest.setup({ + adapters = adapters, + }) + + return true +end + +return M diff --git a/common/nvim/lua/plugins/notify.lua b/common/nvim/lua/plugins/notify.lua new file mode 100755 index 0000000..62a8f47 --- /dev/null +++ b/common/nvim/lua/plugins/notify.lua @@ -0,0 +1,36 @@ +local M = {} + +function M.setup() + local ok, notify = pcall(require, 'notify') + if not ok or not notify then + return false + end + + notify.setup({ + background_colour = '#000000', + icons = { + ERROR = '', + WARN = '', + INFO = '', + DEBUG = '', + TRACE = '✎', + } + }) + + -- Set highlight groups safely + local function set_hl(group, link) + vim.cmd(('hi default link %s %s'):format(group, link)) + end + + set_hl('NotifyERRORBody', 'Normal') + set_hl('NotifyWARNBody', 'Normal') + set_hl('NotifyINFOBody', 'Normal') + set_hl('NotifyDEBUGBody', 'Normal') + set_hl('NotifyTRACEBody', 'Normal') + set_hl('NotifyLogTime', 'Comment') + set_hl('NotifyLogTitle', 'Special') + + return true +end + +return M diff --git a/common/nvim/lua/plugins/nvim-tree.lua b/common/nvim/lua/plugins/nvim-tree.lua new file mode 100755 index 0000000..a212eab --- /dev/null +++ b/common/nvim/lua/plugins/nvim-tree.lua @@ -0,0 +1,479 @@ +local M = {} + +-- Safe require helper +local function safe_require(name) + local ok, mod = pcall(require, name) + return ok and mod or nil +end + + +local ok, api = pcall(require, 'nvim-tree.api') +if not ok then return end +local function on_attach(bufnr) + local function opts(desc) + return { desc = "nvim-tree: " .. desc, buffer = bufnr, noremap = true, silent = true, nowait = true } + end + + local mappings = { + ["<C-]>"] = { api.tree.change_root_to_node, "CD" }, + ["<C-e>"] = { api.node.open.replace_tree_buffer, "Open: In Place" }, + ["<C-k>"] = { api.node.show_info_popup, "Info" }, + ["<C-r>"] = { api.fs.rename_sub, "Rename: Omit Filename" }, + ["<C-t>"] = { api.node.open.tab, "Open: New Tab" }, + ["<C-v>"] = { api.node.open.vertical, "Open: Vertical Split" }, + ["<C-x>"] = { api.node.open.horizontal, "Open: Horizontal Split" }, + ["<BS>"] = { api.node.navigate.parent_close, "Close Directory" }, + -- ["<CR>"] = { api.node.open.edit, "Open" }, + ["<Tab>"] = { api.node.open.preview, "Open Preview" }, + [">"] = { api.node.navigate.sibling.next, "Next Sibling" }, + ["<"] = { api.node.navigate.sibling.prev, "Previous Sibling" }, + ["."] = { api.node.run.cmd, "Run Command" }, + ["-"] = { api.tree.change_root_to_parent, "Up" }, + ["a"] = { api.fs.create, "Create" }, + ["bmv"] = { api.marks.bulk.move, "Move Bookmarked" }, + ["B"] = { api.tree.toggle_no_buffer_filter, "Toggle No Buffer" }, + ["c"] = { api.fs.copy.node, "Copy" }, + -- ["C"] = { api.tree.toggle_git_clean_filter, "Toggle Git Clean" }, + ["[c"] = { api.node.navigate.git.prev, "Prev Git" }, + ["]c"] = { api.node.navigate.git.next, "Next Git" }, + ["d"] = { api.fs.remove, "Delete" }, + ["D"] = { api.fs.trash, "Trash" }, + ["E"] = { api.tree.expand_all, "Expand All" }, + ["e"] = { api.fs.rename_basename, "Rename: Basename" }, + ["]e"] = { api.node.navigate.diagnostics.next, "Next Diagnostic" }, + ["[e"] = { api.node.navigate.diagnostics.prev, "Prev Diagnostic" }, + ["F"] = { api.live_filter.clear, "Clean Filter" }, + ["f"] = { api.live_filter.start, "Filter" }, + ["g?"] = { api.tree.toggle_help, "Help" }, + ["gy"] = { api.fs.copy.absolute_path, "Copy Absolute Path" }, + ["H"] = { api.tree.toggle_hidden_filter, "Toggle Dotfiles" }, + ["I"] = { api.tree.toggle_gitignore_filter, "Toggle Git Ignore" }, + ["J"] = { api.node.navigate.sibling.last, "Last Sibling" }, + ["K"] = { api.node.navigate.sibling.first, "First Sibling" }, + ["m"] = { api.marks.toggle, "Toggle Bookmark" }, + -- ["o"] = { api.node.open.edit, "Open" }, + ["O"] = { api.node.open.no_window_picker, "Open: No Window Picker" }, + ["p"] = { api.fs.paste, "Paste" }, + ["P"] = { api.node.navigate.parent, "Parent Directory" }, + ["q"] = { api.tree.close, "Close" }, + ["r"] = { api.fs.rename, "Rename" }, + ["R"] = { api.tree.reload, "Refresh" }, + ["s"] = { api.node.run.system, "Run System" }, + ["S"] = { api.tree.search_node, "Search" }, + ["U"] = { api.tree.toggle_custom_filter, "Toggle Hidden" }, + ["W"] = { api.tree.collapse_all, "Collapse" }, + ["x"] = { api.fs.cut, "Cut" }, + ["y"] = { api.fs.copy.filename, "Copy Name" }, + ["Y"] = { api.fs.copy.relative_path, "Copy Relative Path" }, + ["<2-LeftMouse>"] = { api.node.open.edit, "Open" }, + ["<2-RightMouse>"] = { api.tree.change_root_to_node, "CD" }, + + -- Mappings migrated from view.mappings.list + ["l"] = { api.node.open.edit, "Open" }, + ["<CR>"] = { api.node.open.edit, "Open" }, + ["o"] = { api.node.open.edit, "Open" }, + ["h"] = { api.node.navigate.parent_close, "Close Directory" }, + ["v"] = { api.node.open.vertical, "Open: Vertical Split" }, + ["C"] = { api.tree.change_root_to_node, "CD" }, + } + for keys, mapping in pairs(mappings) do + vim.keymap.set("n", keys, mapping[1], opts(mapping[2])) + end +end + +---- Icons configuration for nvim-tree +--local icons = { +-- webdev_colors = true, +-- git_placement = "before", +-- modified_placement = "after", +-- padding = " ", +-- symlink_arrow = " ➛ ", +-- show = { +-- file = true, +-- folder = true, +-- folder_arrow = true, +-- git = true, +-- modified = true, +-- }, +-- glyphs = { +-- default = "", +-- symlink = "", +-- bookmark = "", +-- modified = "●", +-- folder = { +-- arrow_closed = "", +-- arrow_open = "", +-- default = "", +-- open = "", +-- empty = "", +-- empty_open = "", +-- symlink = "", +-- symlink_open = "", +-- }, +-- git = { +-- unstaged = "✗", +-- staged = "✓", +-- unmerged = "", +-- renamed = "➜", +-- untracked = "★", +-- deleted = "", +-- ignored = "◌", +-- }, +-- }, +--} + +local icons = { + webdev_colors = true, + git_placement = "signcolumn", + modified_placement = "after", + padding = " ", + show = { + file = true, + folder = true, + folder_arrow = true, + git = true, + modified = true, + }, + + glyphs = { + default = "", + symlink = "", + folder = { + arrow_open = "", + arrow_closed = "", + default = " ", + open = " ", + empty = " ", + empty_open = " ", + symlink = "", + symlink_open = "", + }, + + git = { + deleted = "", + unmerged = "", + untracked = "", + unstaged = "", + staged = "", + renamed = "➜", + ignored = "◌", + }, + }, + web_devicons = { + folder = { + enable = true, + color = true, + }, + }, +} + +local float = { + enable = false, + open_win_config = function() + local screen_w = vim.o.columns + local screen_h = vim.o.lines - vim.o.cmdheight + local window_w = screen_w * WIDTH_RATIO + local window_h = screen_h * HEIGHT_RATIO + local window_w_int = math.floor(window_w) + local window_h_int = math.floor(window_h) + local center_x = (screen_w - window_w) / 2 + local center_y = ((vim.o.lines - window_h) / 2) - vim.o.cmdheight + return { + border = "rounded", + relative = "editor", + row = center_y, + col = center_x, + width = window_w_int, + height = window_h_int, + } + end, +} + +local renderer = { + group_empty = true, -- default: true. Compact folders that only contain a single folder into one node in the file tree. + highlight_git = false, + full_name = false, + highlight_opened_files = "icon", -- "none" (default), "icon", "name" or "all" + highlight_modified = "icon", -- "none", "name" or "all". Nice and subtle, override the open icon + root_folder_label = ":~:s?$?/..?", + indent_width = 2, + indent_markers = { + enable = true, + inline_arrows = true, + icons = { + corner = "└", + edge = "│", + item = "│", + bottom = "─", + none = " ", + }, + }, + icons = icons, +} + +local system_open = { cmd = "zathura" } + +local HEIGHT_RATIO = 0.8 +local WIDTH_RATIO = 0.15 +local view = { + cursorline = true, + float = float, + --signcolumn = 'no', + --width = function() + -- return math.floor(vim.opt.columns:get() * WIDTH_RATIO) + --end, + width = { max = 38, min = 38 }, + side = "left", +} + +-- Open nvim-tree when opening a directory +local function open_nvim_tree(data) + -- buffer is a directory + local directory = vim.fn.isdirectory(data.file) == 1 + + if not directory then + return + end + + -- change to the directory + vim.cmd.cd(data.file) + + -- open the tree + require("nvim-tree.api").tree.open() +end + + +-- Setup function +function M.setup() + -- Check if nvim-tree is installed + --local nvim_tree = safe_require('nvim-tree') + --if not nvim_tree then + -- return false + --end + + local nvim_tree = safe_require('nvim-tree') + if type(nvim_tree) ~= "table" or not nvim_tree.setup then + --vim.notify("[nvim-tree] Plugin did not load correctly", vim.log.levels.ERROR) + return false + end + + -- Setup nvim-tree + nvim_tree.setup({ + sync_root_with_cwd = true, + respect_buf_cwd = true, + disable_netrw = true, + hijack_netrw = true, + open_on_tab = false, + hijack_cursor = false, + update_cwd = true, + hijack_directories = { + enable = true, + auto_open = true, + }, + diagnostics = { + enable = true, + icons = { + error = "✘", + warning = "", + hint = "◉", + info = "", + }, + }, + filesystem_watchers = { + enable = true, + debounce_delay = 50, + ignore_dirs = { "node_modules", ".config/nvm" }, + }, + update_focused_file = { + enable = true, + update_cwd = true, + --update_root = true, + ignore_list = {}, + }, + --root_dirs = {}, + --system_open = { + -- --cmd = nil, + -- --args = {}, + --}, + system_open = system_open, + filters = { + dotfiles = false, + custom = {}, + }, + --git = { + -- enable = true, + -- ignore = true, + -- timeout = 500, + --}, + git = { ignore = false }, + view = view, + renderer = renderer, + --renderer = { + -- indent_markers = { + -- enable = false, + -- icons = { + -- corner = "└ ", + -- edge = "│ ", + -- none = " ", + -- }, + -- }, + -- icons = icons, + --}, + on_attach = on_attach, + notify = { + threshold = vim.log.levels.ERROR, + }, + log = { + enable = true, + truncate = true, + types = { + diagnostics = true, + git = true, + profile = true, + watcher = true, + }, + }, + trash = { + cmd = "gio trash", + require_confirm = true, + }, + modified = { + enable = true, + show_on_dirs = true, + show_on_open_dirs = true, + }, + actions = { + use_system_clipboard = true, + change_dir = { + enable = true, + global = false, + restrict_above_cwd = false, + }, + remove_file = { + close_window = true, + }, + open_file = { + quit_on_open = false, + resize_window = true, + window_picker = { + enable = true, + chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", + exclude = { + filetype = { "notify", "packer", "qf", "diff", "fugitive", "fugitiveblame" }, + buftype = { "nofile", "terminal", "help" }, + }, + }, + }, + }, + }) + + + local api = require("nvim-tree.api") + --local event = api.events.Event + --api.events.subscribe(event.TreeOpen, function(_) + -- vim.cmd([[setlocal statuscolumn=\ ]]) + -- vim.cmd([[setlocal cursorlineopt=number]]) + -- vim.cmd([[setlocal fillchars+=vert:🮇]]) + -- vim.cmd([[setlocal fillchars+=horizup:🮇]]) + -- vim.cmd([[setlocal fillchars+=vertright:🮇]]) + --end) + + local function open_nvim_tree(data) + vim.cmd.cd(data.file:match("(.+)/[^/]*$")) + local directory = vim.fn.isdirectory(data.file) == 1 + if not directory then + return + end + require("nvim-tree.api").tree.open() + end + + -- Auto open nvim-tree when opening a directory + vim.api.nvim_create_autocmd({ "VimEnter" }, { pattern = { "*" }, callback = open_nvim_tree }) + + -- Change Root To Global Current Working Directory + local function change_root_to_global_cwd() + local api = require("nvim-tree.api") + local global_cwd = vim.fn.getcwd(-1, -1) + api.tree.change_root(global_cwd) + end + + local function copy_file_to(node) + local file_src = node["absolute_path"] + -- The args of input are {prompt}, {default}, {completion} + -- Read in the new file path using the existing file's path as the baseline. + local file_out = vim.fn.input("COPY TO: ", file_src, "file") + -- Create any parent dirs as required + local dir = vim.fn.fnamemodify(file_out, ":h") + vim.fn.system({ "mkdir", "-p", dir }) + -- Copy the file + vim.fn.system({ "cp", "-R", file_src, file_out }) + end + + local function edit_and_close(node) + api.node.open.edit(node, {}) + api.tree.close() + end + + --vim.api.nvim_create_augroup('NvimTreeRefresh', {}) + --vim.api.nvim_create_autocmd('BufEnter', { + -- pattern = 'NvimTree_1', + -- command = 'NvimTreeRefresh', + -- group = 'NvimTreeRefresh', + --}) + + vim.api.nvim_create_autocmd({ "CursorHold" }, { + pattern = "NvimTree*", + callback = function() + local def = vim.api.nvim_get_hl_by_name("Cursor", true) + vim.api.nvim_set_hl( + 0, + "Cursor", + vim.tbl_extend("force", def, { + blend = 100, + }) + ) + vim.opt.guicursor = "n-v-c-sm:block,i-ci-ve:ver25,r-cr-o:hor20,a:Cursor/lCursor" + end, + }) + + vim.api.nvim_create_autocmd({ "BufLeave", "WinClosed", "WinLeave" }, { + pattern = "NvimTree*", + callback = function() + local def = vim.api.nvim_get_hl_by_name("Cursor", true) + vim.api.nvim_set_hl( + 0, + "Cursor", + vim.tbl_extend("force", def, { + blend = 0, + }) + ) + vim.opt.guicursor = "n-v-c-sm:block,i-ci-ve:ver25,r-cr-o:hor20" + end, + }) + + vim.api.nvim_command("highlight NvimTreeNormal guibg=NONE ctermbg=NONE") + vim.api.nvim_command("highlight NvimTreeNormalNC guibg=NONE ctermbg=NONE guifg=NONE") + vim.api.nvim_command("highlight NvimTreeNormalFloat guibg=NONE ctermbg=NONE") + vim.api.nvim_command("highlight NvimTreeEndOfBuffer guibg=NONE ctermbg=NONE") --(NonText) + vim.api.nvim_command("highlight NvimTreeCursorLine guibg=#50fa7b guifg=#000000") + vim.api.nvim_command("highlight NvimTreeSymlinkFolderName guifg=#f8f8f2 guibg=NONE ctermbg=NONE") + vim.api.nvim_command("highlight NvimTreeFolderName guifg=#f8f8f2 guibg=NONE ctermbg=NONE") + vim.api.nvim_command("highlight NvimTreeRootFolder guifg=#f8f8f2 guibg=NONE ctermbg=NONE") + vim.api.nvim_command("highlight NvimTreeEmptyFolderName guifg=#f8f8f2 guibg=NONE ctermbg=NONE") --(Directory) + vim.api.nvim_command("highlight NvimTreeOpenedFolderName guifg=#f8f8f2 guibg=NONE ctermbg=NONE") --(Directory) + vim.api.nvim_command("highlight NvimTreeOpenedFile guifg=#50fa7b guibg=NONE ctermbg=NONE") + vim.api.nvim_command("highlight NvimTreeExecFile guifg=#ff882a guibg=none gui=NONE") + + return true +end + +---- Set highlights +--vim.cmd([[highlight NvimTreeNormal guibg=NONE ctermbg=NONE]]) +--vim.cmd([[highlight NvimTreeNormalNC guibg=NONE ctermbg=NONE guifg=NONE]]) +--vim.cmd([[highlight NvimTreeNormalFloat guibg=NONE ctermbg=NONE]]) +--vim.cmd([[highlight NvimTreeEndOfBuffer guibg=NONE ctermbg=NONE]]) +--vim.cmd([[highlight NvimTreeCursorLine guibg=#50fa7b guifg=#000000]]) + +-- Highlight Groups + +return M diff --git a/common/nvim/lua/plugins/overseer.lua b/common/nvim/lua/plugins/overseer.lua new file mode 100755 index 0000000..593d094 --- /dev/null +++ b/common/nvim/lua/plugins/overseer.lua @@ -0,0 +1,14 @@ +local M = {} + +function M.setup() + local ok, overseer = pcall(require, 'overseer') + if not ok or not overseer then + return false + end + + overseer.setup({}) + + return true +end + +return M diff --git a/common/nvim/lua/plugins/plenary.lua b/common/nvim/lua/plugins/plenary.lua new file mode 100755 index 0000000..f572244 --- /dev/null +++ b/common/nvim/lua/plugins/plenary.lua @@ -0,0 +1,3 @@ +-- plenary.nvim plugin config (modular, robust) +local ok, _ = pcall(require, 'plenary') +-- No config needed
\ No newline at end of file diff --git a/common/nvim/lua/plugins/prettier.lua b/common/nvim/lua/plugins/prettier.lua new file mode 100755 index 0000000..ca57ea9 --- /dev/null +++ b/common/nvim/lua/plugins/prettier.lua @@ -0,0 +1,8 @@ +local M = {} + +function M.setup() + -- No-op if prettier is not installed + return true +end + +return M diff --git a/common/nvim/lua/plugins/quickfix.lua b/common/nvim/lua/plugins/quickfix.lua new file mode 100755 index 0000000..4a76da0 --- /dev/null +++ b/common/nvim/lua/plugins/quickfix.lua @@ -0,0 +1,15 @@ +local M = {} + +M.close = function() + vim.cmd.cclose() +end + +M.open = function() + if vim.tbl_count(vim.fn.getqflist()) == 0 then + vim.notify('Nothing in quickfix list; not opening.', vim.log.levels.WARN) + else + vim.cmd.copen() + end +end + +return M diff --git a/common/nvim/lua/plugins/snippets.lua b/common/nvim/lua/plugins/snippets.lua new file mode 100755 index 0000000..989ad8a --- /dev/null +++ b/common/nvim/lua/plugins/snippets.lua @@ -0,0 +1,33 @@ +local M = {} + +function M.setup() + local ok, ls = pcall(require, "luasnip") + if not ok or not ls then + return false + end + + -- Safely load snippets + pcall(function() require("luasnip.loaders.from_lua").load({ paths = "~/.config/nvim/snippets/" }) end) + pcall(function() require("luasnip.loaders.from_vscode").lazy_load() end) + pcall(function() require("luasnip.loaders.from_snipmate").lazy_load() end) + + ls.config.set_config { + history = true, + updateevents = "TextChanged,TextChangedI", + enable_autosnippets = true, + region_check_events = "InsertEnter", + delete_check_events = "TextChanged", + store_selection_keys = "<Tab>", + ext_opts = { + [require("luasnip.util.types").choiceNode] = { + active = { + virt_text = { { "«", "GruvboxOrange" } }, + }, + }, + }, + } + + return true +end + +return M diff --git a/common/nvim/lua/plugins/sniprun.lua b/common/nvim/lua/plugins/sniprun.lua new file mode 100755 index 0000000..418e8cc --- /dev/null +++ b/common/nvim/lua/plugins/sniprun.lua @@ -0,0 +1,57 @@ +local status_ok, sniprun = pcall(require, 'sniprun') +if not status_ok then + return +end + +sniprun.setup({ + -- selected_interpreters = {}, --# use those instead of the default for the current filetype + -- repl_enable = { "Python3_original" }, --# enable REPL-like behavior for the given interpreters + -- repl_disable = {}, --# disable REPL-like behavior for the given interpreters + + -- interpreter_options = { --# intepreter-specific options, see docs / :SnipInfo <name> + -- GFM_original = { + -- use_on_filetypes = { "markdown.pandoc" }, --# the 'use_on_filetypes' configuration key is + -- --# available for every interpreter + -- }, + -- }, + + --# you can combo different display modes as desired + display = { + -- "Classic", --# display results in the command-line area + --'VirtualTextOk', --# display ok results as virtual text (multiline is shortened) + -- "VirtualTextErr", --# display error results as virtual text + -- "TempFloatingWindow", --# display results in a floating window + -- "LongTempFloatingWindow", --# same as above, but only long results. To use with VirtualText__ + 'Terminal', --# display results in a vertical split + -- "TerminalWithCode", --# display results and code history in a vertical split + -- "NvimNotify", --# display with the nvim-notify plugin + -- "Api" --# return output to a programming interface + }, + + display_options = { + terminal_width = 45, --# change the terminal display option width + notification_timeout = 5, --# timeout for nvim_notify output + }, + + --# You can use the same keys to customize whether a sniprun producing + --# no output should display nothing or '(no output)' + show_no_output = { + 'Classic', + 'TempFloatingWindow', --# implies LongTempFloatingWindow, which has no effect on its own + }, + + --# customize highlight groups (setting this overrides colorscheme) + -- snipruncolors = { + -- SniprunVirtualTextOk = { bg = "NONE", fg = "#66eeff", ctermbg = "Black", cterfg = "Cyan" }, + -- SniprunFloatingWinOk = { fg = "NONE", ctermfg = "Cyan" }, + -- SniprunVirtualTextErr = { bg = "#881515", fg = "#000000", ctermbg = "DarkRed", cterfg = "Black" }, + -- SniprunFloatingWinErr = { fg = "#881515", ctermfg = "DarkRed" }, + -- }, + + --# miscellaneous compatibility/adjustement settings + inline_messages = 0, --# inline_message (0/1) is a one-line way to display messages + --# to workaround sniprun not being able to display anything + + borders = 'single', --# display borders around floating windows + --# possible values are 'none', 'single', 'double', or 'shadow' +}) diff --git a/common/nvim/lua/plugins/statuscol.lua b/common/nvim/lua/plugins/statuscol.lua new file mode 100755 index 0000000..c538790 --- /dev/null +++ b/common/nvim/lua/plugins/statuscol.lua @@ -0,0 +1,37 @@ +local M = {} + +function M.setup() + local ok, statuscol = pcall(require, "statuscol") + if not ok or not statuscol then + return false + end + + local builtin_ok, builtin = pcall(require, "statuscol.builtin") + if not builtin_ok or not builtin then + return false + end + + statuscol.setup({ + segments = { + { text = { builtin.lnumfunc }, click = "v:lua.ScLa" }, + { text = { "%s" }, click = "v:lua.ScSa" }, + { text = { builtin.foldfunc }, click = "v:lua.ScFa" }, + }, + ft_ignore = { + "NvimTree", + "packer", + "NeogitStatus", + "toggleterm", + "dapui_scopes", + "dapui_breakpoints", + "dapui_stacks", + "dapui_watches", + "dapui_console", + "dapui_repl", + }, + }) + + return true +end + +return M diff --git a/common/nvim/lua/plugins/surround.lua b/common/nvim/lua/plugins/surround.lua new file mode 100755 index 0000000..71023c7 --- /dev/null +++ b/common/nvim/lua/plugins/surround.lua @@ -0,0 +1,35 @@ +local M = {} + +function M.setup() + local ok, surround = pcall(require, 'nvim-surround') + if not ok or not surround then + return false + end + + surround.setup({ + keymaps = { + insert = false, + insert_line = false, + normal = false, + normal_cur = false, + normal_line = false, + normal_cur_line = false, + visual = "<S-s>", + visual_line = false, + delete = false, + change = false, + }, + aliases = { + ["a"] = false, + ["b"] = false, + ["B"] = false, + ["r"] = false, + ["q"] = false, + ["s"] = false, + }, + }) + + return true +end + +return M diff --git a/common/nvim/lua/plugins/telescope.lua b/common/nvim/lua/plugins/telescope.lua new file mode 100755 index 0000000..5aca8ac --- /dev/null +++ b/common/nvim/lua/plugins/telescope.lua @@ -0,0 +1,740 @@ +local M = {} + +-- Safely require a module +-- @param name string The module name to require +-- @return table|nil The loaded module or nil if failed +local function safe_require(name) + local ok, mod = pcall(require, name) + return ok and mod or nil +end + +--- Setup and configure Telescope +-- This function initializes Telescope with default configurations and extensions +-- @return boolean True if setup was successful, false otherwise +function M.setup() + -- Check if Telescope is installed + local telescope = safe_require("telescope") + if not telescope then + return false + end + -- Require Telescope and fail early if missing + local telescope = safe_require("telescope") + if not telescope then + return false + end + + local actions = safe_require("telescope.actions") + local actions_set = safe_require("telescope.actions.set") + local actions_state = safe_require("telescope.actions.state") + local finders = safe_require("telescope.finders") + local pickers = safe_require("telescope.pickers") + local config_mod = safe_require("telescope.config") + local utils = safe_require("telescope.utils") + local previewers = require("telescope.previewers") + + local config = config_mod and config_mod.values or {} + + -- 🛡 Safe previewer to avoid nil path error + local safe_previewer = function() + return require("telescope.previewers").new_buffer_previewer({ + define_preview = function(self, entry) + if not entry or type(entry) ~= "table" then return end + + local path = entry.path or entry.filename or entry.value + if type(path) ~= "string" or path == "" then return end + + -- Avoid expanding things like " Recent Books" which aren't valid files + if path:match("^%s") then return end + + -- Resolve tilde if present + path = path:gsub("^~", vim.env.HOME) + + if vim.fn.filereadable(path) ~= 1 and vim.fn.isdirectory(path) ~= 1 then + return + end + + -- Protect against nil path being passed further + if not self.state or not self.state.bufnr or not self.state.bufname then return end + + local preview_utils = require("telescope.previewers.utils") + preview_utils.buffer_previewer_maker(path, self.state.bufnr, { + bufname = self.state.bufname, + callback = function(bufnr, success) + if not success then + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { "Failed to preview file." }) + end + end, + }) + end, + }) + end + + local function get_extension_actions(ext) + local ok, telescope_ext = pcall(require, "telescope._extensions." .. ext) + if not ok then return {} end + return telescope_ext.actions or {} + end + + telescope.setup({ + defaults = { + vimgrep_arguments = { + "rg", + "--color=never", + "--no-heading", + "--with-filename", + "--line-number", + "--column", + "--smart-case", + "--hidden", + "--fixed-strings", + "--trim", + }, + previewer = safe_previewer(), + prompt_prefix = " ", + selection_caret = " ", + entry_prefix = " ", + path_display = { "tail" }, + file_ignore_patterns = { + "packer_compiled.lua", + "~/.config/zsh/plugins", + "zcompdump", + "%.DS_Store", + "%.git/", + "%.spl", + "%[No Name%]", + "/$", + "node_modules", + "%.png", + "%.zip", + "%.pxd", + "^.local/", + "^.cache/", + "^downloads/", + "^music/", + }, + mappings = { + i = { + ["<C-n>"] = actions.cycle_history_next, + ["<C-p>"] = actions.cycle_history_prev, + ["<C-j>"] = actions.move_selection_next, + ["<C-k>"] = actions.move_selection_previous, + ["<Esc>"] = actions.close, + ["<?>"] = actions.which_key, + ["<Down>"] = actions.move_selection_next, + ["<Up>"] = actions.move_selection_previous, + ["<CR>"] = actions.select_default, + ["<C-x>"] = actions.select_horizontal, + ["<C-y>"] = actions.select_vertical, + ["<C-t>"] = actions.select_tab, + ["<C-c>"] = actions.delete_buffer, + ["<C-u>"] = actions.preview_scrolling_up, + ["<C-d>"] = actions.preview_scrolling_down, + ["<PageUp>"] = actions.results_scrolling_up, + ["<PageDown>"] = actions.results_scrolling_down, + ["<Tab>"] = actions.toggle_selection + actions.move_selection_worse, + ["<S-Tab>"] = actions.toggle_selection + actions.move_selection_better, + ["<C-q>"] = actions.send_to_qflist + actions.open_qflist, + ["<M-q>"] = actions.send_selected_to_qflist + actions.open_qflist, + ["<C-l>"] = actions.complete_tag, + ["<C-_>"] = actions.which_key, + }, + n = { + ["<esc>"] = actions.close, + ["<q>"] = actions.close, + ["<CR>"] = actions.select_default, + ["<C-x>"] = actions.select_horizontal, + ["<C-y>"] = actions.select_vertical, + ["<C-t>"] = actions.select_tab, + ["<C-c>"] = actions.delete_buffer, + ["<Tab>"] = actions.toggle_selection + actions.move_selection_worse, + ["<S-Tab>"] = actions.toggle_selection + actions.move_selection_better, + ["<C-q>"] = actions.send_to_qflist + actions.open_qflist, + ["<M-q>"] = actions.send_selected_to_qflist + actions.open_qflist, + ["j"] = actions.move_selection_next, + ["k"] = actions.move_selection_previous, + ["H"] = actions.move_to_top, + ["M"] = actions.move_to_middle, + ["L"] = actions.move_to_bottom, + ["<Down>"] = actions.move_selection_next, + ["<Up>"] = actions.move_selection_previous, + ["gg"] = actions.move_to_top, + ["G"] = actions.move_to_bottom, + ["<C-u>"] = actions.preview_scrolling_up, + ["<C-d>"] = actions.preview_scrolling_down, + ["<PageUp>"] = actions.results_scrolling_up, + ["<PageDown>"] = actions.results_scrolling_down, + ["cd"] = function(prompt_bufnr) + local selection = actions_state.get_selected_entry() + local dir = vim.fn.fnamemodify(selection.path, ":p:h") + actions.close(prompt_bufnr) + vim.cmd("silent lcd " .. dir) + end, + ["?"] = actions.which_key, + }, + }, + }, + preview = { + filesize_limit = 3, + timeout = 250, + }, + selection_strategy = "reset", + sorting_strategy = "ascending", + scroll_strategy = "limit", + color_devicons = true, + layout_strategy = "horizontal", + layout_config = { + horizontal = { + height = 0.95, + preview_cutoff = 70, + width = 0.92, + preview_width = { 0.55, max = 50 }, + }, + bottom_pane = { + height = 12, + preview_cutoff = 70, + prompt_position = "bottom", + }, + }, + find_files = { + cwd = vim.fn.getcwd(), + prompt_prefix = " ", + follow = true, + }, + extensions = { + file_browser = { + theme = "dropdown", + hijack_netrw = false, + mappings = { + i = { + ["<C-w>"] = function() vim.cmd("normal vbd") end, + ["<C-h>"] = function() + local fb_actions = get_extension_actions("file_browser") + if fb_actions.goto_parent_dir then + fb_actions.goto_parent_dir() + end + end, + }, + n = { + ["N"] = function() + local fb_actions = get_extension_actions("file_browser") + if fb_actions.create then + fb_actions.create() + end + end, + ["<C-h>"] = function() + local fb_actions = get_extension_actions("file_browser") + if fb_actions.goto_parent_dir then + fb_actions.goto_parent_dir() + end + end, + }, + }, + }, + }, + }) + + -- Load extensions + for _, ext in ipairs({ + "fzf", "ui-select", "file_browser", "changed_files", + "media_files", "notify", "dap", "session-lens", "recent_files" + }) do + pcall(telescope.load_extension, ext) + end + + -- Define the custom command findhere/startup + vim.cmd('command! Findhere lua require("plugins.telescope").findhere()') + + return true +end + +-- Find config files +local function _sys_path(repo_path) + local home = os.getenv("HOME") or vim.fn.expand("~") + + -- Case 1: Files in the OS-specific home folder (e.g., linux/home/.bashrc) + if repo_path:find("/home/", 1, true) then + local file = repo_path:match(".*/home/(.*)") + return home .. "/" .. file + -- Case 2: Files in the common folder (e.g., common/README.md) + elseif repo_path:find("common/", 1, true) then + local file = repo_path:match("common/(.*)") + return home .. "/" .. file + -- Case 3: Root-level files (e.g., profile/profile_script or README.md) + elseif repo_path:find("profile/", 1, true) or repo_path:find("README.md", 1, true) then + return home .. "/" .. repo_path + -- Case 4: System-level files (e.g., linux/etc/issue) + elseif repo_path:find("/etc/", 1, true) then + local file = repo_path:match(".*/etc/(.*)") + return "/etc/" .. file + -- Return nil for paths that don't match any known pattern + else + return nil + end +end + +function M.find_configs() + local telescope_builtin = require("telescope.builtin") + local tracked_files = {} + local home = os.getenv("HOME") or "~" + local original_dir = vim.fn.getcwd() + vim.fn.chdir(home) + + vim.api.nvim_create_autocmd("VimLeave", { + callback = function() + vim.fn.chdir(original_dir) + end, + }) + + -- Check if the bare repository exists + if vim.fn.isdirectory(home .. "/.cfg") == 1 then + -- Repository exists, use git to find tracked files + local handle = io.popen("git --git-dir=" .. home .. "/.cfg --work-tree=" .. home .. " ls-tree --name-only -r HEAD") + local cfg_files = "" + if handle then + cfg_files = handle:read("*a") or "" + handle:close() + end + + -- Process the list of files + for file in string.gmatch(cfg_files, "[^\n]+") do + file = vim.trim(file) + if file ~= "" then + local fullpath = _sys_path(file) + if fullpath and (vim.fn.filereadable(fullpath) == 1 or vim.fn.isdirectory(fullpath) == 1) then + table.insert(tracked_files, fullpath) + end + end + end + end + + -- If no files were found (either no repo or no tracked files), use fallback paths + if #tracked_files == 0 then + local fallback_dirs = { + home .. "/.config/nvim", + home .. "/.config/zsh", + home .. "/.config/tmux", + home .. "/.bashrc", + home .. "/.zshrc", + home .. "/.tmux.conf", + } + for _, path in ipairs(fallback_dirs) do + if vim.fn.filereadable(path) == 1 or vim.fn.isdirectory(path) == 1 then + table.insert(tracked_files, path) + end + end + end + + if #tracked_files == 0 then + vim.notify("[find_configs] No configuration files found to search.", vim.log.levels.WARN) + return + end + + -- Launch Telescope + telescope_builtin.find_files({ + hidden = true, + no_ignore = false, + prompt_title = " Find Configs", + results_title = "Config Files", + path_display = { "smart" }, + search_dirs = tracked_files, + layout_strategy = "horizontal", + layout_config = { preview_width = 0.65, width = 0.75 }, + previewer = true, + }) +end + +function M.find_scripts() + require("telescope.builtin").find_files({ + hidden = true, + no_ignore = true, + prompt_title = " Find Scripts", + path_display = { "smart" }, + search_dirs = { + "~/.scripts", + }, + layout_strategy = "horizontal", + layout_config = { preview_width = 0.65, width = 0.75 }, + }) +end + +function M.find_projects() + local search_dir = "~/projects" + local actions = safe_require("telescope.actions") + local actions_set = safe_require("telescope.actions.set") + local actions_state = safe_require("telescope.actions.state") + local finders = safe_require("telescope.finders") + local pickers = safe_require("telescope.pickers") + local config_mod = safe_require("telescope.config") + local config = config_mod and config_mod.values or {} + + pickers + .new({}, { + prompt_title = "Find Projects", + finder = finders.new_oneshot_job({ + "find", + vim.fn.expand(search_dir), + "-type", + "d", + "-maxdepth", + "1", + }), + previewer = require("telescope.previewers").vim_buffer_cat.new({}), + sorter = config.generic_sorter({}), + attach_mappings = function(prompt_bufnr, map) + actions_set.select:replace(function() + local entry = actions_state.get_selected_entry() + if entry ~= nil then + local dir = entry.value + actions.close(prompt_bufnr, false) + vim.fn.chdir(dir) + vim.cmd("e .") + vim.cmd("echon ''") + print("cwd: " .. vim.fn.getcwd()) + end + end) + return true + end, + }) + :find() +end + +function M.grep_notes() + local opts = {} + opts.hidden = false + opts.search_dirs = { + "~/documents/main/", + } + opts.prompt_prefix = " " + opts.prompt_title = " Grep Notes" + opts.path_display = { "smart" } + require("telescope.builtin").live_grep(opts) +end + +function M.find_notes() + require("telescope.builtin").find_files({ + hidden = true, + no_ignore = false, + prompt_title = " Find Notes", + path_display = { "smart" }, + search_dirs = { + "~/documents/main", + }, + layout_strategy = "horizontal", + layout_config = { preview_width = 0.65, width = 0.75 }, + }) +end + +function M.find_private() + require("telescope.builtin").find_files({ + hidden = true, + no_ignore = false, + prompt_title = " Find Notes", + path_display = { "smart" }, + search_dirs = { + "~/notes/private", + "~/notes", + }, + layout_strategy = "horizontal", + layout_config = { preview_width = 0.65, width = 0.75 }, + }) +end + +function M.find_books() + local search_dir = "~/documents/books" + local actions = safe_require("telescope.actions") + local actions_set = safe_require("telescope.actions.set") + local actions_state = safe_require("telescope.actions.state") + local finders = safe_require("telescope.finders") + local pickers = safe_require("telescope.pickers") + local config_mod = safe_require("telescope.config") + local config = config_mod and config_mod.values or {} + + vim.fn.jobstart("$HOME/.scripts/track-books.sh") + local recent_books_directory = vim.fn.stdpath("config") .. "/tmp/" + local recent_books_file = recent_books_directory .. "recent_books.txt" + + -- Check if recent_books.txt exists, create it if not + if vim.fn.filereadable(recent_books_file) == 0 then + vim.fn.mkdir(recent_books_directory, "p") -- Ensure the directory exists + vim.fn.writefile({}, recent_books_file) -- Create an empty file + end + + local search_cmd = "find " .. vim.fn.expand(search_dir) .. " -type d -o -type f -maxdepth 1" + + local recent_books = vim.fn.readfile(recent_books_file) + local search_results = vim.fn.systemlist(search_cmd) + + local results = {} + + -- Section for Recent Books + table.insert(results, " Recent Books") + for _, recent_book_path in ipairs(recent_books) do + local formatted_path = vim.fn.fnameescape(recent_book_path) + table.insert(results, formatted_path) + end + + -- Section for All Books + table.insert(results, " All Books") + local directories = {} + local files = {} + + for _, search_result in ipairs(search_results) do + if vim.fn.isdirectory(search_result) == 1 then + table.insert(directories, search_result) + else + table.insert(files, search_result) + end + end + + table.sort(directories) + table.sort(files) + + for _, dir in ipairs(directories) do + table.insert(results, dir) + end + + for _, file in ipairs(files) do + table.insert(results, file) + end + + local picker = pickers.new({}, { + prompt_title = "Find Books", + finder = finders.new_table({ + results = results, + }), + file_ignore_patterns = { + "%.git", + }, + previewer = require("telescope.previewers").vim_buffer_cat.new({}), + sorter = config.generic_sorter({}), + attach_mappings = function(prompt_bufnr, map) + actions_set.select:replace(function() + local entry = actions_state.get_selected_entry() + if entry ~= nil then + local path = entry.value + + actions.close(prompt_bufnr, false) + + -- Check if it's under "Recent Books" + if path == " Recent Books" or path == " All Books" then + vim.notify("Cannot select 'All Books'/'Recent Books', please select a book or directory.", + vim.log.levels.WARN, { title = "Find Books" }) + else + -- Determine whether it's a directory or a file + local is_directory = vim.fn.isdirectory(path) + if is_directory then + -- It's a directory, navigate to it in the current buffer + vim.cmd("e " .. path) + else + -- It's a file, open it + vim.cmd("e " .. path) + end + end + end + end) + return true + end, + }) + + picker:find() +end + +function M.grep_current_dir() + local buffer_dir = require("telescope.utils").buffer_dir() + local opts = { + prompt_title = "Live Grep in " .. buffer_dir, + cwd = buffer_dir, + } + require("telescope.builtin").live_grep(opts) +end + +-- Helper functions that depend on telescope availability +local function get_dropdown_theme() + return require("telescope.themes").get_dropdown({ + hidden = true, + no_ignore = true, + previewer = false, + prompt_title = "", + preview_title = "", + results_title = "", + layout_config = { + prompt_position = "top", + }, + }) +end + +-- Set current folder as prompt title +local function with_title(opts, extra) + extra = extra or {} + local path = opts.cwd or opts.path or extra.cwd or extra.path or nil + local title = "" + local buf_path = vim.fn.expand("%:p:h") + local cwd = vim.fn.getcwd() + if path ~= nil and buf_path ~= cwd then + title = require("plenary.path"):new(buf_path):make_relative(cwd) + else + title = vim.fn.fnamemodify(cwd, ":t") + end + + return vim.tbl_extend("force", opts, { + prompt_title = title, + }, extra or {}) +end + +-- Find here +function M.findhere() + -- Open file browser if argument is a folder + local arg = vim.api.nvim_eval("argv(0)") + if arg and (vim.fn.isdirectory(arg) ~= 0 or arg == "") then + vim.defer_fn(function() + require("telescope.builtin").find_files(with_title(get_dropdown_theme())) + end, 10) + end +end + +-- Find dirs +function M.find_dirs() + local root_dir = vim.fn.input("Enter the root directory: ") + + -- Check if root_dir is empty + if root_dir == "" then + print("No directory entered. Aborting.") + return + end + + local entries = {} + + -- Use vim.fn.expand() to get an absolute path + local root_path = vim.fn.expand(root_dir) + + local subentries = vim.fn.readdir(root_path) + if subentries then + for _, subentry in ipairs(subentries) do + local absolute_path = root_path .. "/" .. subentry + table.insert(entries, subentry) + end + end + + local actions = safe_require("telescope.actions") + local actions_set = safe_require("telescope.actions.set") + local actions_state = safe_require("telescope.actions.state") + local finders = safe_require("telescope.finders") + local pickers = safe_require("telescope.pickers") + local config_mod = safe_require("telescope.config") + local config = config_mod and config_mod.values or {} + + pickers + .new({}, { + prompt_title = "Change Directory or Open File", + finder = finders.new_table({ + results = entries, + }), + previewer = config.file_previewer({}), + sorter = config.generic_sorter({}), + attach_mappings = function(prompt_bufnr, map) + actions_set.select:replace(function() + local entry = actions_state.get_selected_entry() + if entry ~= nil then + local selected_entry = entry.value + actions.close(prompt_bufnr, false) + local selected_path = root_path .. "/" .. selected_entry + if vim.fn.isdirectory(selected_path) == 1 then + vim.fn.chdir(selected_path) + vim.cmd("e .") + print("cwd: " .. vim.fn.getcwd()) + else + vim.cmd("e " .. selected_path) + end + end + end) + return true + end, + }) + :find() +end + +-- Safe telescope function wrapper for keymaps +local function safe_telescope_call(module_path, func_name, fallback_msg) + return function() + local ok, module = pcall(require, module_path) + if ok and module[func_name] then + module[func_name]() + else + vim.notify(fallback_msg or ("Telescope plugin not available for " .. func_name), vim.log.levels.WARN) + end + end +end + +local function safe_telescope_builtin(func_name, fallback_msg) + return function(opts) + local ok, telescope_builtin = pcall(require, "telescope.builtin") + if not ok then + vim.notify(fallback_msg or ("Telescope builtin module (telescope.builtin) not found!"), vim.log.levels.ERROR) + vim.notify("Error details: " .. tostring(telescope_builtin), vim.log.levels.DEBUG) -- telescope_builtin will contain the error message here + return + end + + if not telescope_builtin[func_name] then + vim.notify(fallback_msg or ("Telescope builtin function '" .. func_name .. "' not found!"), vim.log.levels.ERROR) + vim.notify("Available builtin functions: " .. vim.inspect(vim.tbl_keys(telescope_builtin)), vim.log.levels.DEBUG) + return + end + + -- If both are ok, proceed + telescope_builtin[func_name](opts or {}) + end +end + +-- Safe builtin telescope functions +local function safe_telescope_builtin(func_name, fallback_msg) + return function(opts) + local ok, telescope_builtin = pcall(require, "telescope.builtin") + if ok and telescope_builtin[func_name] then + telescope_builtin[func_name](opts or {}) + else + vim.notify(fallback_msg or ("Telescope builtin not available: " .. func_name), vim.log.levels.WARN) + end + end +end + +-- Safe extension calls with better checking +local function safe_telescope_extension(ext_name, func_name, fallback_msg) + return function(opts) + local telescope_mod = package.loaded.telescope or require("telescope") + if not telescope_mod then + return + end + + -- Check if extension is loaded + if not telescope_mod.extensions or not telescope_mod.extensions[ext_name] then + vim.notify(fallback_msg or ("Telescope extension '" .. ext_name .. "' not available (plugin may not be installed)"), vim.log.levels.WARN) + return + end + + local ext_func = telescope_mod.extensions[ext_name][func_name] + if not ext_func then + vim.notify(fallback_msg or ("Function '" .. func_name .. "' not found in extension '" .. ext_name .. "'"), vim.log.levels.WARN) + return + end + + ext_func(opts or {}) + end +end + +-- Fallback-safe `find_files` +M.safe_find_files = function() + local builtin = safe_require("telescope.builtin") + if builtin and builtin.find_files then + builtin.find_files() + else + local file = vim.fn.input("Open file: ", "", "file") + if file ~= "" then vim.cmd("edit " .. file) end + end +end + +-- Export safe wrapper functions for external use +M.safe_telescope_call = safe_telescope_call +M.safe_telescope_builtin = safe_telescope_builtin +M.safe_telescope_extension = safe_telescope_extension + +return M diff --git a/common/nvim/lua/plugins/toggleterm.lua b/common/nvim/lua/plugins/toggleterm.lua new file mode 100755 index 0000000..6b7aad5 --- /dev/null +++ b/common/nvim/lua/plugins/toggleterm.lua @@ -0,0 +1,294 @@ +local M = {} + +--- Setup and configure toggleterm.nvim +-- This function initializes and configures the toggleterm plugin for terminal management +-- @return boolean True if setup was successful, false otherwise +function M.setup() + local ok, toggleterm = pcall(require, 'toggleterm') + if not ok or not toggleterm then + return false + end + + toggleterm.setup({ + --open_mapping = [[<leader>tt]], + autochdir = true, + hide_numbers = true, + shade_filetypes = {}, + shade_terminals = false, + --shading_factor = 1, + start_in_insert = true, + insert_mappings = true, + terminal_mappings = true, + persist_size = true, + direction = 'float', + --direction = "vertical", + --direction = "horizontal", + close_on_exit = true, + shell = vim.o.shell, + highlights = { + -- highlights which map to a highlight group name and a table of it's values + -- NOTE: this is only a subset of values, any group placed here will be set for the terminal window split + --Normal = { + -- background = "#000000", + --}, + --Normal = { guibg = 'Black', guifg = 'White' }, + --FloatBorder = { guibg = 'Black', guifg = 'DarkGray' }, + --NormalFloat = { guibg = 'Black' }, + float_opts = { + --winblend = 3, + }, + }, + size = function(term) + if term.direction == 'horizontal' then + return 7 + elseif term.direction == 'vertical' then + return math.floor(vim.o.columns * 0.4) + end + end, + float_opts = { + width = 70, + height = 15, + border = 'curved', + highlights = { + border = 'Normal', + --background = 'Normal', + }, + --winblend = 0, + }, + }) + + -- Set up keymaps for toggleterm + local Terminal = require('toggleterm.terminal').Terminal + + -- Custom terminal commands + local lazygit + if not Terminal then return end + local term = Terminal:new({ + cmd = 'lazygit', + dir = 'git_dir', + direction = 'float', + float_opts = { + border = 'curved', + }, + on_open = function(term) + vim.cmd('startinsert!') + vim.api.nvim_buf_set_keymap(term.bufnr, 'n', 'q', '<cmd>close<CR>', {noremap = true, silent = true}) + end, + }) + if term then + lazygit = term + end + + -- Toggle functions + local function _lazygit_toggle() + if not Terminal then return end + if not lazygit then + init_lazygit() + end + if lazygit then + pcall(lazygit.toggle, lazygit) + end + end + + -- Set up keymaps + vim.keymap.set('n', '<leader>tt', '<cmd>ToggleTerm<CR>', {noremap = true, silent = true, desc = 'Toggle Terminal'}) + vim.keymap.set('n', '<leader>tf', '<cmd>ToggleTerm direction=float<CR>', {noremap = true, silent = true, desc = 'Toggle Float Terminal'}) + vim.keymap.set('n', '<leader>th', '<cmd>ToggleTerm size=10 direction=horizontal<CR>', {noremap = true, silent = true, desc = 'Toggle Horizontal Terminal'}) + vim.keymap.set('n', '<leader>tv', '<cmd>ToggleTerm size=80 direction=vertical<CR>', {noremap = true, silent = true, desc = 'Toggle Vertical Terminal'}) + vim.keymap.set('n', '<leader>tl', _lazygit_toggle, {noremap = true, silent = true, desc = 'Toggle Lazygit'}) + + -- Terminal mode mappings + vim.keymap.set('t', '<esc>', [[<C-\><C-n>]], {noremap = true, silent = true}) + vim.keymap.set('t', 'jk', [[<C-\><C-n>]], {noremap = true, silent = true}) + vim.keymap.set('t', '<C-h>', [[<Cmd>wincmd h<CR>]], {noremap = true, silent = true}) + vim.keymap.set('t', '<C-j>', [[<Cmd>wincmd j<CR>]], {noremap = true, silent = true}) + vim.keymap.set('t', '<C-k>', [[<Cmd>wincmd k<CR>]], {noremap = true, silent = true}) + vim.keymap.set('t', '<C-l>', [[<Cmd>wincmd l<CR>]], {noremap = true, silent = true}) + + return true +end + +-- Terminal utility functions +local mods = {} + +-- Simple empty check function if mods.empty is not available +function mods.empty(v) + return v == nil or v == '' +end +local float_handler = function(term) + if not mods.empty(vim.fn.mapcheck('jk', 't')) then + vim.keymap.del('t', 'jk', { buffer = term.bufnr }) + vim.keymap.del('t', '<esc>', { buffer = term.bufnr }) + end +end + +function _G.set_terminal_keymaps() + local opts = { noremap = true } + --local opts = {buffer = 0} + --vim.api.nvim_buf_set_keymap(0, "i", ";to", "[[<Esc>]]<cmd>Toggleterm", opts) + vim.api.nvim_buf_set_keymap(0, 't', '<C-c>', [[<Esc>]], opts) + vim.api.nvim_buf_set_keymap(0, 't', '<esc>', [[<C-\><C-n>]], opts) + vim.api.nvim_buf_set_keymap(0, 't', 'jk', [[<C-\><C-n>]], opts) + vim.api.nvim_buf_set_keymap(0, 't', '<C-h>', [[<C-\><C-n><C-W>h]], opts) + vim.api.nvim_buf_set_keymap(0, 't', '<C-j>', [[<C-\><C-n><C-W>j]], opts) + vim.api.nvim_buf_set_keymap(0, 't', '<C-k>', [[<C-\><C-n><C-W>k]], opts) + vim.api.nvim_buf_set_keymap(0, 't', '<C-l>', [[<C-\><C-n><C-W>l]], opts) +end + +-- if you only want these mappings for toggle term use term://*toggleterm#* instead +vim.cmd('autocmd! TermOpen term://* lua set_terminal_keymaps()') +local Terminal +local horizontal_term, vertical_term + +-- Safely require toggleterm.terminal +local toggleterm_ok, toggleterm = pcall(require, 'toggleterm.terminal') +if toggleterm_ok and toggleterm and toggleterm.Terminal then + Terminal = toggleterm.Terminal + -- Initialize terminals only if Terminal is available + if Terminal then + local ok1, hterm = pcall(Terminal.new, Terminal, { hidden = true, direction = 'horizontal' }) + local ok2, vterm = pcall(Terminal.new, Terminal, { hidden = true, direction = 'vertical' }) + if ok1 then horizontal_term = hterm end + if ok2 then vertical_term = vterm end + end +end + +function Horizontal_term_toggle() + if horizontal_term then + pcall(horizontal_term.toggle, horizontal_term, 8, 'horizontal') + end +end + +function Vertical_term_toggle() + if vertical_term then + pcall(vertical_term.toggle, vertical_term, math.floor(vim.o.columns * 0.5), 'vertical') + end +end + +-- Initialize lazygit terminal instance +local lazygit = nil +local Cur_cwd = vim.fn.getcwd() + +-- Function to initialize lazygit terminal +local function init_lazygit() + if not Terminal then return nil end + if not lazygit then + local ok, term = pcall(function() + return Terminal:new({ + cmd = 'lazygit', + count = 5, + id = 1000, + dir = 'git_dir', + direction = 'float', + on_open = float_handler, + hidden = true, + float_opts = { + border = { '╒', '═', '╕', '│', '╛', '═', '╘', '│' }, + width = 150, + height = 40, + }, + }) + end) + if ok and term then + lazygit = term + end + end + return lazygit +end + +-- Initialize lazygit on first use +function Lazygit_toggle() + -- Initialize lazygit if not already done + if not init_lazygit() then return end + + -- cwd is the root of project. if cwd is changed, change the git. + local cwd = vim.fn.getcwd() + if cwd ~= Cur_cwd then + Cur_cwd = cwd + if lazygit then + lazygit:close() + end + lazygit = Terminal:new({ + cmd = "zsh --login -c 'lazygit'", + dir = 'git_dir', + direction = 'float', + hidden = true, + on_open = float_handler, + float_opts = { + border = { '╒', '═', '╕', '│', '╛', '═', '╘', '│' }, + width = 150, + height = 40, + }, + }) + end + if lazygit then + lazygit:toggle() + else + vim.notify("Failed to initialize lazygit terminal", vim.log.levels.ERROR) + end +end + +local node = nil +local ncdu = nil + +-- Initialize node terminal if Terminal is available +if Terminal then + local ok1, nterm = pcall(function() return Terminal:new({ cmd = 'node', hidden = true }) end) + local ok2, ncduterm = pcall(function() return Terminal:new({ cmd = 'ncdu', hidden = true }) end) + if ok1 then node = nterm end + if ok2 then ncdu = ncduterm end +end + +function _NODE_TOGGLE() + if not node then return end + pcall(node.toggle, node) +end + +function _NCDU_TOGGLE() + if not ncdu then return end + pcall(ncdu.toggle, ncdu) +end + +local htop = nil + +function _HTOP_TOGGLE() + if not Terminal then return end + if not htop then + local ok, term = pcall(function() return Terminal:new({ cmd = 'htop', hidden = true }) end) + if ok then htop = term end + end + if htop then + pcall(htop.toggle, htop) + end +end + +local python = nil + +function _PYTHON_TOGGLE() + if not Terminal then return end + if not python then + local ok, term = pcall(function() return Terminal:new({ cmd = 'python', hidden = true }) end) + if ok then python = term end + end + if python then + pcall(python.toggle, python) + end +end + +function Gh_dash() + Terminal:new({ + cmd = 'gh dash', + hidden = true, + direction = 'float', + on_open = float_handler, + float_opts = { + height = function() + return math.floor(vim.o.lines * 0.8) + end, + width = function() + return math.floor(vim.o.columns * 0.95) + end, + }, + }) + Gh_dash:toggle() +end diff --git a/common/nvim/lua/plugins/treesitter.lua b/common/nvim/lua/plugins/treesitter.lua new file mode 100755 index 0000000..9df99b8 --- /dev/null +++ b/common/nvim/lua/plugins/treesitter.lua @@ -0,0 +1,54 @@ +local M = {} + +function M.setup() + local ok, treesitter = pcall(require, "nvim-treesitter.configs") + if not ok or not treesitter then + return false + end + + -- Add custom parser directory to runtime path + vim.opt.runtimepath:append("$HOME/.local/share/treesitter") + + -- Configure treesitter + treesitter.setup({ + -- Install parsers in custom directory + parser_install_dir = "$HOME/.local/share/treesitter", + + -- Enable syntax highlighting + highlight = { + enable = true, + -- Disable additional regex-based highlighting to improve performance + additional_vim_regex_highlighting = false, + }, + + -- Enable indentation + indent = { + enable = true, + }, + + -- Additional modules to enable + incremental_selection = { + enable = true, + keymaps = { + init_selection = "gnn", + node_incremental = "grn", + scope_incremental = "grc", + node_decremental = "grm", + }, + }, + + -- Ensure parsers are installed automatically + ensure_installed = { + "bash", "c", "cpp", "css", "dockerfile", "go", "html", + "javascript", "json", "lua", "markdown", "python", "rust", + "toml", "typescript", "vim", "yaml" + }, + + -- Auto-install parsers + auto_install = true, + }) + + return true +end + +return M diff --git a/common/nvim/lua/plugins/trouble.lua b/common/nvim/lua/plugins/trouble.lua new file mode 100755 index 0000000..4a07e3b --- /dev/null +++ b/common/nvim/lua/plugins/trouble.lua @@ -0,0 +1,73 @@ +local M = {} + +--- Setup and configure trouble.nvim +-- This function initializes and configures the trouble plugin for diagnostics and references +-- @return boolean True if setup was successful, false otherwise +function M.setup() + local ok, trouble = pcall(require, 'trouble') + if not ok then + return false + end + + trouble.setup({ + position = "bottom", -- bottom, top, left, right + height = 10, + width = 50, + icons = { + indent = { + fold = { + open = "", + closed = "", + }, + }, + kinds = { + -- you can use LSP kind symbols or devicons here + -- remove if you want default + }, + }, + modes = { + diagnostics = { + groups = { "filename", "kind" }, + }, + symbols = { + format = "{kind_icon} {symbol.name} {symbol.kind} [{symbol.scope}]", + }, + }, + action_keys = { + close = "q", + cancel = "<esc>", + refresh = "r", + jump = { "<cr>", "<tab>" }, + open_split = { "<c-x>" }, + open_vsplit = { "<c-v>" }, + open_tab = { "<c-t>" }, + jump_close = { "o" }, + toggle_preview = "P", + hover = "K", + preview = "p", + close_folds = { "zM", "zm" }, + open_folds = { "zR", "zr" }, + toggle_fold = { "zA", "za" }, + previous = "k", + next = "j", + }, + indent_lines = true, + auto_open = false, + auto_close = false, + auto_preview = true, + auto_fold = false, + auto_jump = { "lsp_definitions" }, + signs = { + error = "", + warning = "▲", + info = "", + hint = "⚑", + other = "•", + }, + use_diagnostic_signs = true, + }) + + return true +end + +return M diff --git a/common/nvim/lua/plugins/vimtex.lua b/common/nvim/lua/plugins/vimtex.lua new file mode 100755 index 0000000..732e6ed --- /dev/null +++ b/common/nvim/lua/plugins/vimtex.lua @@ -0,0 +1,45 @@ +--ft = { "latex", "tex" }, +--if vim.loop.os_uname().sysname == "Linux" then +-- vim.g.vimtex_view_method = "zathura" +--end +--vim.g["vimtex_view_method"] = "zathura" -- main variant with xdotool (requires X11; not compatible with wayland) +--vim.g.vimtex_compiler_method = "pdflatex" +-- compilation configuration +vim.g["vimtex_compiler_method"] = "latexmk" +--vim.g["vimtex_compiler_method"] = "xelatex" +--vim.g["vimtex_compiler_method"] = "lualatex" +vim.g["vimtex_compiler_latexmk"] = { + callback = 1, + continuous = 1, + executable = "latexmk", + options = { + "-shell-escape", + "-verbose", + "-file-line-error", + "-synctex=1", + "-interaction=nonstopmode", + }, +} +vim.g["vimtex_view_enabled"] = 1 +vim.g["vimtex_view_zathura_check_libsynctex"] = 0 +--vim.g["vimtex_view_method"] = "zathura" -- main variant with xdotool (requires X11; not compatible with wayland) +if vim.loop.os_uname().sysname == "Linux" then + vim.g.vimtex_view_method = "zathura" +end +--vim.g.vimtex_view_method = "sioyek" +--vim.g["vimtex_view_method"] = "zathura_simple" -- for variant without xdotool to avoid errors in wayland +vim.g["vimtex_quickfix_mode"] = 0 -- suppress error reporting on save and build +vim.g["vimtex_mappings_enabled"] = 0 -- Ignore mappings +vim.g["vimtex_indent_enabled"] = 0 -- Auto Indent +vim.g["tex_flavor"] = "latex" -- how to read tex files +vim.g["tex_indent_items"] = 0 -- turn off enumerate indent +vim.g["tex_indent_brace"] = 0 -- turn off brace indent +--vim.g.vimtex_view_forward_search_on_start = 0 +--vim.g["vimtex_context_pdf_viewer"] = "zathura" -- external PDF viewer run from vimtex menu command +--vim.g["latex_view_general_viewer"] = "zathura" +vim.g["vimtex_log_ignore"] = { -- Error suppression: + "Underfull", + "Overfull", + "specifier changed to", + "Token not allowed in a PDF string", +} diff --git a/common/nvim/lua/plugins/web-devicons.lua b/common/nvim/lua/plugins/web-devicons.lua new file mode 100755 index 0000000..a565a31 --- /dev/null +++ b/common/nvim/lua/plugins/web-devicons.lua @@ -0,0 +1,125 @@ +local M = {} + +-- Cache the nerd fonts check with better error handling +local function get_nerd_fonts_available() + if vim.g.nerd_fonts_available ~= nil then + return vim.g.nerd_fonts_available + end + + local has_nerd_fonts = false + local ok, result = pcall(function() + if vim.fn.has('unix') == 1 and vim.fn.executable('fc-list') == 1 then + local handle = io.popen('fc-list | grep -i nerd 2>/dev/null') + if handle then + local result = handle:read('*a') + handle:close() + return result ~= "" + end + end + return false + end) + + has_nerd_fonts = ok and result or false + vim.g.nerd_fonts_available = has_nerd_fonts + return has_nerd_fonts +end + +-- Helper function to get icon with fallback and validation +local function get_icon(nerd_icon, fallback, color, cterm_color, name) + local has_nerd = get_nerd_fonts_available() + + -- Validate colors + if not color or color == '' then + color = '#6d8086' -- Default gray color + end + if not cterm_color or cterm_color == '' then + cterm_color = '102' -- Default gray for terminal + end + + -- Pick icon + local icon = has_nerd and nerd_icon or fallback + if not icon or icon == '' then + icon = has_nerd and '' or '[F]' + end + + return { + icon = icon, + color = color, + cterm_color = cterm_color, + name = name or 'File', + } +end + +function M.setup() + local ok, devicons = pcall(require, 'nvim-web-devicons') + if not ok or not devicons then + return false + end + + devicons.setup({ + color_icons = true, + override = { + -- Languages + js = get_icon('', '[JS]', '#f5c06f', '179', 'Js'), + jsx = get_icon('', '[JSX]', '#689fb6', '67', 'Jsx'), + ts = get_icon('', '[TS]', '#4377c1', '67', 'Ts'), + tsx = get_icon('', '[TSX]', '#4377c1', '67', 'Tsx'), + lua = get_icon('', '[LUA]', '#51a0cf', '74', 'Lua'), + py = get_icon('', '[PY]', '#3572A5', '67', 'Python'), + rb = get_icon('', '[RB]', '#701516', '124', 'Ruby'), + go = get_icon('', '[GO]', '#519aba', '74', 'Go'), + rs = get_icon('', '[RS]', '#dea584', '173', 'Rust'), + + -- Images + png = get_icon('', '[PNG]', '#d4843e', '173', 'Png'), + jpg = get_icon('', '[JPG]', '#16a085', '36', 'Jpg'), + jpeg = get_icon('', '[JPG]', '#16a085', '36', 'Jpeg'), + webp = get_icon('', '[WEBP]', '#3498db', '32', 'Webp'), + svg = get_icon('', '[SVG]', '#3affdb', '80', 'Svg'), + + -- Archives + zip = get_icon('', '[ZIP]', '#e6b422', '178', 'Zip'), + rar = get_icon('', '[RAR]', '#e6b422', '178', 'Rar'), + ['7z'] = get_icon('', '[7Z]', '#e6b422', '178', '7z'), + tar = get_icon('', '[TAR]', '#e6b422', '178', 'Tar'), + gz = get_icon('', '[GZ]', '#e6b422', '178', 'GZip'), + bz2 = get_icon('', '[BZ2]', '#e6b422', '178', 'BZip2'), + + -- Docs + md = get_icon('', '[MD]', '#519aba', '67', 'Markdown'), + txt = get_icon('', '[TXT]', '#6d8086', '102', 'Text'), + pdf = get_icon('', '[PDF]', '#e74c3c', '160', 'PDF'), + doc = get_icon('', '[DOC]', '#2c6ecb', '27', 'Word'), + docx = get_icon('', '[DOC]', '#2c6ecb', '27', 'Word'), + xls = get_icon('', '[XLS]', '#1d6f42', '29', 'Excel'), + xlsx = get_icon('', '[XLS]', '#1d6f42', '29', 'Excel'), + + -- Config + json = get_icon('', '[JSON]', '#f5c06f', '179', 'Json'), + yaml = get_icon('', '[YAML]', '#6d8086', '102', 'Yaml'), + toml = get_icon('', '[TOML]', '#6d8086', '102', 'Toml'), + conf = get_icon('', '[CFG]', '#6d8086', '102', 'Config'), + ini = get_icon('', '[INI]', '#6d8086', '102', 'Ini'), + + -- Shell + sh = get_icon('', '[SH]', '#4d5a5e', '59', 'Shell'), + zsh = get_icon('', '[ZSH]', '#89e051', '113', 'Zsh'), + bash = get_icon('', '[BASH]', '#89e051', '113', 'Bash'), + + -- Git + ['.gitignore'] = get_icon('', '[GIT]', '#e24329', '166', 'GitIgnore'), + ['.gitattributes'] = get_icon('', '[GIT]', '#e24329', '166', 'GitAttributes'), + ['.gitconfig'] = get_icon('', '[GIT]', '#e24329', '166', 'GitConfig'), + }, + default = { + icon = get_nerd_fonts_available() and '' or '[F]', + name = 'File', + color = '#6d8086', + cterm_color = '102', + }, + }) + + return true +end + +return M diff --git a/common/nvim/lua/plugins/which-key.lua b/common/nvim/lua/plugins/which-key.lua new file mode 100755 index 0000000..10015aa --- /dev/null +++ b/common/nvim/lua/plugins/which-key.lua @@ -0,0 +1,53 @@ +local M = {} + +function M.setup() + local ok, wk = pcall(require, 'which-key') + if not ok then + return false + end + + -- Basic configuration + wk.setup({ + plugins = { + marks = true, + registers = true, + spelling = { enabled = true, suggestions = 20 }, + presets = { + operators = true, + motions = true, + text_objects = true, + windows = true, + nav = true, + z = true, + g = true, + }, + }, + --window = { + -- border = "none", + -- position = "bottom", + -- margin = { 1, 0, 1, 0 }, + -- padding = { 2, 2, 2, 2 }, + -- winblend = 0 + --}, + --layout = { + -- height = { min = 4, max = 25 }, + -- width = { min = 20, max = 50 }, + -- spacing = 3, + -- align = "left" + --}, + --ignore_missing = false, + --hidden = { "<silent>", "<cmd>", "<Cmd>", "<CR>", "call", "lua", "^:", "^ " }, + --show_help = true, + --triggers = "<leader>", + --triggers_blacklist = { + -- i = { "j", "k" }, + -- v = { "j", "k" }, + --} + }) + + + + return true +end + +return M diff --git a/common/nvim/lua/plugins/zen-mode.lua b/common/nvim/lua/plugins/zen-mode.lua new file mode 100755 index 0000000..7e52854 --- /dev/null +++ b/common/nvim/lua/plugins/zen-mode.lua @@ -0,0 +1,7 @@ +local status, zenMode = pcall(require, "zen-mode") +if (not status) then return end + +zenMode.setup { +} + +vim.keymap.set('n', '<C-w>o', '<cmd>ZenMode<cr>', { silent = true }) |
