diff options
| author | srdusr <trevorgray@srdusr.com> | 2025-09-24 04:19:28 +0200 |
|---|---|---|
| committer | srdusr <trevorgray@srdusr.com> | 2025-09-24 04:19:28 +0200 |
| commit | e95332b121672abaf9fd02692d81869b1e46c02d (patch) | |
| tree | 9dc0def690d7941b18783733045ba091112e9452 /common/config/nvim/lua | |
| parent | 0f6cee92221dc517bd756083e260dd9373851b82 (diff) | |
| parent | 7ed2303648bf83bb081d9bd863660ebf2344ce47 (diff) | |
| download | dotfiles-e95332b121672abaf9fd02692d81869b1e46c02d.tar.gz dotfiles-e95332b121672abaf9fd02692d81869b1e46c02d.zip | |
Merge commit '7ed2303648bf83bb081d9bd863660ebf2344ce47'
Diffstat (limited to 'common/config/nvim/lua')
59 files changed, 7805 insertions, 4469 deletions
diff --git a/common/config/nvim/lua/plugins/auto-session.lua b/common/config/nvim/lua/plugins/auto-session.lua new file mode 100755 index 0000000..d982e08 --- /dev/null +++ b/common/config/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/config/nvim/lua/plugins/autopairs.lua b/common/config/nvim/lua/plugins/autopairs.lua index 90b62b1..22dcf27 100644..100755 --- a/common/config/nvim/lua/plugins/autopairs.lua +++ b/common/config/nvim/lua/plugins/autopairs.lua @@ -1,9 +1,16 @@ -local status_ok, autopairs = pcall(require, "nvim-autopairs") -if not status_ok then - return -end +local M = {} -autopairs.setup({ +--- 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" }, @@ -85,3 +92,8 @@ cmp.event:on( -- return --end --cmp.event:on("confirm_done", cmp_autopairs.on_confirm_done { map_char = { tex = "" } }) + + return true +end + +return M diff --git a/common/config/nvim/lua/plugins/cmp-gh-source.lua b/common/config/nvim/lua/plugins/cmp-gh-source.lua index 4990c35..4990c35 100644..100755 --- a/common/config/nvim/lua/plugins/cmp-gh-source.lua +++ b/common/config/nvim/lua/plugins/cmp-gh-source.lua diff --git a/common/config/nvim/lua/plugins/cmp.lua b/common/config/nvim/lua/plugins/cmp.lua index fe212fc..7de04ad 100644..100755 --- a/common/config/nvim/lua/plugins/cmp.lua +++ b/common/config/nvim/lua/plugins/cmp.lua @@ -1,354 +1,67 @@ --- Setup nvim-cmp. -vim.opt.completeopt = "menu,menuone,noselect" ---vim.g.completeopt = "menu,menuone,noselect,noinsert" -local cmp_status_ok, cmp = pcall(require, "cmp") -if not cmp_status_ok then - return -end ---local WIDE_HEIGHT = 40 - -local opts = { - -- whether to highlight the currently hovered symbol - -- disable if your cpu usage is higher than you want it - -- or you just hate the highlight - -- default: true - highlight_hovered_item = true, - show_guides = true, -} -require("symbols-outline").setup(opts) - ---local snippets_paths = function() --- local plugins = { "friendly-snippets" } --- local paths = {} --- local path --- local root_path = vim.env.HOME .. "/.vim/plugged/" --- for _, plug in ipairs(plugins) do --- path = root_path .. plug --- if vim.fn.isdirectory(path) ~= 0 then --- table.insert(paths, path) --- end --- end --- return paths ---end --- ---require("luasnip.loaders.from_vscode").lazy_load({ --- paths = snippets_paths(), --- include = nil, -- Load all languages --- exclude = {}, ---}) +local M = {} ---require("luasnip.loaders.from_vscode").lazy_load() -local lspkind = require("lspkind") -local kind_icons = { - Text = "", - Method = "m", --"", - Function = "", - Constructor = "", --"⚙️", - Field = "", - Variable = "", - Class = "ﴯ", - Interface = "", - Module = "", - Property = "", - Unit = "", - Value = "", - Enum = "", - Keyword = "", - Snippet = "", - Color = "", - File = "", - Reference = "", - Folder = "", - EnumMember = "", - Constant = "", - Struct = "", - Event = "", - Operator = "", - TypeParameter = "", -} -cmp.setup({ +--- 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) - -- require("luasnip").lsp_expand(args.body) - --end, expand = function(args) - local luasnip = require("luasnip") - if not luasnip then - return - end - luasnip.lsp_expand(args.body) + if luasnip_ok then luasnip.lsp_expand(args.body) end end, }, mapping = cmp.mapping.preset.insert({ - -- ["<CR>"] = cmp.mapping.confirm({ - -- behavior = cmp.ConfirmBehavior.Replace, - -- select = true, - -- }), - --["<C-k>"] = cmp.mapping(cmp.mapping.select_prev_item(), { 'i', 'c' }), - --["<C-j>"] = cmp.mapping(cmp.mapping.select_next_item(), { 'i', 'c' }), - ["<C-y>"] = cmp.mapping.confirm({ select = true }), - --["<C-e>"] = cmp.mapping.close(), - --['<C-e>'] = cmp.mapping({ - -- i = cmp.mapping.abort(), - -- c = cmp.mapping.close(), - --}), - ["<C-e>"] = cmp.mapping({ - i = function() - if cmp.visible() then - cmp.abort() - require("user.mods").toggle_completion() - require("notify")("completion off") - else - cmp.complete() - require("user.mods").toggle_completion() - require("notify")("completion on") - end - end, - }), - --["<CR>"] = cmp.mapping({ - -- i = function(fallback) - -- if cmp.visible() then - -- cmp.confirm({ behavior = cmp.ConfirmBehavior.Replace, select = false }) - -- require("user.mods").toggle_completion() - -- else - -- fallback() - -- end - -- end, - --}), - - -- ["<C-e>"] = cmp.mapping({ - -- i = function() - -- if cmp.visible() then - -- require("notify")("visible") - -- cmp.abort() - -- else - -- require("notify")("not visible") - -- cmp.complete() - -- end - -- end, - -- c = function() - -- if cmp.visible() then - -- require("notify")("visible") - -- cmp.close() - -- else - -- require("notify")("not visible") - -- cmp.complete() - -- end - -- end, - -- }), - --['<CR>'] = cmp.config.disable, - ["<C-u>"] = cmp.mapping.scroll_docs(-4), - ["<C-d>"] = cmp.mapping.scroll_docs(4), - ["<C-Space>"] = cmp.mapping.complete(), - --['<C-o>'] = function(fallback) - -- if cmp.visible() then - -- cmp.mapping.confirm({ select = true })(fallback) - -- else - -- cmp.mapping.complete()(fallback) - -- end - --end + ['<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_lua" }, - { name = "luasnip" }, - --{ name = 'luasnip', option = { use_show_condition = false } }, - { name = "gh_issues" }, - { name = "nvim_lsp", max_item_count = 6 }, - { name = "nvim_lua" }, - --{ name = "luasnip" }, - --{ name = "luasnip", keyword_length = 4 }, - --{ name = "buffer", keyword_length = 3 }, - { name = "path" }, - { name = "buffer", max_item_count = 6 }, - --{ name = "buffer", option = { get_bufnrs = function() - -- return vim.api.nvim_list_bufs() - --end - --}}, - { name = "cmp_git" }, - { name = "spell" }, - { name = "zsh" }, - { name = "treesitter" }, - { name = "calc" }, - { name = "nvim_lsp_signature_help" }, - --{ name = "cmdline" }, - --{ name = 'treesitter' }, - --{ name = "cmdline", keyword_pattern = [=[[^[:blank:]\!]*]=] }, --exclamation mark hangs a bit without this - --{name = 'luasnip', keyword_length = 2}, + { name = 'nvim_lsp' }, + { name = 'luasnip' }, + { name = 'buffer' }, }), - formatting = { - --formatting = { - --local icons = kind_icons - --format = function(entry, vim_item) - ----vim_item.kind = string.format("%s", kind_icons[vim_item.kind]) - ----vim_item.kind = lspkind.presets.default[vim_item.kind] - --vim_item.kind = string.format('%s %s', kind_icons[vim_item.kind], vim_item.kind) -- This concatonates the icons with the name of the item kind - ----vim_item.kind = string.format("%s %s", icons[vim_item.kind], vim_item.kind) - --vim_item.menu = ({ - ----nvim_lsp = "LSP", - ----luasnip = "snip", - ----buffer = "buf", - ----path = "path", - ----cmdline = "cmd", - --buffer = "[buf]", - --nvim_lsp = "[LSP]", - --nvim_lua = "[api]", - --path = "[path]", - --luasnip = "[snip]", - --cmdline = "[cmd]", - --gh_issues = "[issues]", - --})[entry.source.name] - --return vim_item - --end, - format = lspkind.cmp_format({ - with_text = true, - menu = { - nvim_lsp = "[LSP]", - luasnip = "[snip]", - buffer = "[buf]", - nvim_lua = "[api]", - path = "[path]", - gh_issues = "[issues]", - spell = "[spell]", - zsh = "[zsh]", - treesitter = "[treesitter]", - calc = "[calc]", - nvim_lsp_signature_help = "[signature]", - cmdline = "[cmd]", - }, - }), - --}, - - -- - -- - --fields = { "abbr", "kind", "menu" }, - -- format = lspkind.cmp_format({ - -- mode = 'symbol_text', -- show only symbol annotations - -- maxwidth = 50, -- prevent the popup from showing more than provided characters (e.g 50 will not show more than 50 characters) - -- }) - --format = require('lspkind').cmp_format { - -- with_text = true, - -- menu = { - -- luasnip = "Snip", - -- buffer = "Buf", - -- nvim_lsp = "LSP", - -- path = "Path", - -- cmdline = "Cmd", - -- cmp_git = "Git", - -- }, - --}, - }, - --format = function(entry, vim_item) - -- -- Kind icons - -- --vim_item.kind = string.format("%s", kind_icons[vim_item.kind]) - -- vim_item.kind = lspkind.presets.default[vim_item.kind] - -- -- vim_item.kind = string.format('%s %s', kind_icons[vim_item.kind], vim_item.kind) -- This concatonates the icons with the name of the item kind - -- vim_item.menu = ({ - -- nvim_lsp = "LSP", - -- luasnip = "Snip", - -- buffer = "Buf", - -- path = "Path", - -- cmdline = "Cmd", - -- })[entry.source.name] - -- return vim_item - --end, - confirm_opts = { - behavior = cmp.ConfirmBehavior.Replace, - select = false, - }, - - event = {}, - - experimental = { - ghost_text = true, -- this feature conflicts with copilot.vim's preview. - hl_group = "Nontext", - --native_menu = false, - }, - - view = { - entries = { name = "custom", selection_order = "top_down" }, - }, - - window = { - --completion = cmp.config.window.bordered(), - completion = { - border = { "", "", "", " ", "", "", "", " " }, - --border = { "╭", "─", "╮", "│", "╯", "─", "╰", "│" }, - --border = { '', '', '', '', '', '', '', '' }, - --border = "CmpBorder", - winhighlight = "Normal:Pmenu,FloatBorder:Pmenu,CursorLine:PmenuSel,Search:None", - --winhighlight = "Normal:CmpPmenu,CursorLine:PmenuSel,Search:None", - }, - --documentation = cmp.config.window.bordered(), - documentation = { - --max_height = math.floor(WIDE_HEIGHT * (WIDE_HEIGHT / vim.o.lines)), - --max_width = math.floor((WIDE_HEIGHT * 2) * (vim.o.columns / (WIDE_HEIGHT * 2 * 16 / 9))), - border = { "", "", "", " ", "", "", "", " " }, - --border = { "╭", "─", "╮", "│", "╯", "─", "╰", "│" }, - winhighlight = "FloatBorder:NormalFloat", - }, - }, -}) - -cmp.setup.cmdline({ "/", "?" }, { - mapping = cmp.mapping.preset.cmdline(), - sources = { - { name = "buffer" }, - }, }) -cmp.setup.cmdline(":", { - mapping = { - ["<C-p>"] = cmp.mapping(cmp.mapping.select_prev_item(), { "i", "c" }), - ["<C-n>"] = cmp.mapping(cmp.mapping.select_next_item(), { "i", "c" }), - ["<C-y>"] = cmp.mapping(cmp.mapping.confirm({ select = true }), { "i", "c" }), - ["<C-e>"] = cmp.mapping(cmp.mapping.close(), { "i", "c" }), - ["<C-u>"] = cmp.mapping(cmp.mapping.scroll_docs(-4), { "i", "c" }), - ["<C-d>"] = cmp.mapping(cmp.mapping.scroll_docs(4), { "i", "c" }), - ["<C-Space>"] = cmp.mapping(cmp.mapping.complete(), { "i", "c" }), - --["<C-k>"] = cmp.mapping.select_prev_item(), - --["<C-j>"] = cmp.mapping.select_next_item(), - --['<C-y>'] = cmp.mapping.confirm({ select = true }), - --["<C-e>"] = cmp.mapping.close(), - ----['<CR>'] = cmp.config.disable, - --["<C-u>"] = cmp.mapping.scroll_docs(-4), - --["<C-d>"] = cmp.mapping.scroll_docs(4), - --["<C-Space>"] = cmp.mapping.complete(), - }, - - sources = cmp.config.sources({ - { name = "path" }, - }, { - --{ name = "cmdline" }, - { name = "cmdline", keyword_pattern = [=[[^[:blank:]\!]*]=], keyword_length = 3 }, - }), -}) +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 { - "micangl/cmp-vimtex", - config = function() - require("cmp_vimtex").setup({ - additional_information = { - info_in_menu = true, - info_in_window = true, - info_max_length = 60, - match_against_info = true, - symbols_in_menu = true, - }, - bibtex_parser = { - enabled = true, - }, - search = { - browser = "xdg-open", - default = "google_scholar", - search_engines = { - google_scholar = { - name = "Google Scholar", - get_url = require("cmp_vimtex").url_default_format("https://scholar.google.com/scholar?hl=en&q=%s"), - }, - -- Other search engines. - }, - }, - }) - end, -} +return M diff --git a/common/config/nvim/lua/plugins/colorizer.lua b/common/config/nvim/lua/plugins/colorizer.lua new file mode 100755 index 0000000..6019bc5 --- /dev/null +++ b/common/config/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/config/nvim/lua/plugins/colorscheme.lua b/common/config/nvim/lua/plugins/colorscheme.lua index be78ac8..7fbabc1 100644..100755 --- a/common/config/nvim/lua/plugins/colorscheme.lua +++ b/common/config/nvim/lua/plugins/colorscheme.lua @@ -1,344 +1,24 @@ ---local function MyHighlights() --- vim.cmd('highlight Visual cterm=NONE ctermbg=76 ctermfg=16 gui=NONE guibg=#5fd700 guifg=#000000') --- vim.cmd('highlight StatusLine cterm=NONE ctermbg=231 ctermfg=160 gui=NONE guibg=#ffffff guifg=#d70000') --- vim.cmd('highlight Normal cterm=NONE ctermbg=17 gui=NONE guibg=#00005f') --- vim.cmd('highlight NonText cterm=NONE ctermbg=17 gui=NONE guibg=#00005f') ---end --- ---local function setupMyColors() --- vim.cmd('augroup MyColors') --- vim.cmd('autocmd!') --- vim.cmd('autocmd ColorScheme * call v:lua.MyHighlights()') --- vim.cmd('augroup END') ---end --- ---setupMyColors() +local M = {} ---vim.api.nvim_command("highlight FoldColumn guibg=none") +-- List of preferred colorschemes in order of preference +local preferred_colorschemes = { + 'tokyonight', + 'desert', + 'default' +} ---vim.api.nvim_command("highlight SignColumn guifg=none guibg=none cterm=NONE ctermfg=none ctermbg=NONE gui=NONE") ---vim.api.nvim_command("highlight ColorColumn guifg=none guibg=none cterm=NONE ctermfg=none ctermbg=NONE gui=NONE") ---vim.api.nvim_command("highlight TabLineSel guibg=none guifg=none gui=bold") ---vim.api.nvim_command("highlight TabLineNC guibg=none gui=bold") ---vim.api.nvim_command("highlight StatusLine guibg=#333842 gui=bold") ---vim.api.nvim_command("highlight StatusLineNC guibg=none ctermfg=Cyan guifg=#80a0ff gui=bold") +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 --- Custom colorscheme ---vim.cmd([[ --- let g:terminal_color_0 = '#2e3436' --- let g:terminal_color_1 = '#cc0000' --- let g:terminal_color_2 = '#4e9a06' --- let g:terminal_color_3 = '#c4a000' --- let g:terminal_color_4 = '#3465a4' --- let g:terminal_color_5 = '#75507b' --- let g:terminal_color_6 = '#0b939b' --- let g:terminal_color_7 = '#d3d7cf' --- let g:terminal_color_8 = '#555753' --- let g:terminal_color_9 = '#ef2929' --- let g:terminal_color_10 = '#8ae234' --- let g:terminal_color_11 = '#fce94f' --- let g:terminal_color_12 = '#729fcf' --- let g:terminal_color_13 = '#ad7fa8' --- let g:terminal_color_14 = '#00f5e9' --- let g:terminal_color_15 = '#eeeeec' --- --- set background=dark --- execute "silent! colorscheme base16-eighties" --- highlight Comment guifg=#585858 --- highlight Normal guifg=#999999 --- "highlight TabLine guifg=#333333 guibg=#777777 --- "highlight TabLineSel guifg=#FA7F7F --- highlight MatchParen gui=bold guibg=black guifg=limegreen ---]]) ---local colors = { --- background = '#1c1c1c', --- foreground = '#cacaca', --- accent = '#fe5186', --- blue = '#61afef', --- cyan = '#3e8fb0', --- dark_green = '#5faf5f', --- dark_grey = '#222222', --- dark_orange = '#ff5d62', --- dark_red = '#bf1131', --- dark_yellow = '#ffaf00', --- green = '#98c379', --- grey = '#2a2a2a', --- light_grey = '#727169', --- light_orange = '#ffa066', --- light_pink = '#e46876', --- light_yellow = '#e6db74', --- magenta = '#c678dd', --- orange = '#ff8700', --- purple = '#af87d7', --- red = '#c4384b', --- violet = '#a9a1e1', --- white = '#e3e3e3', --- yellow = '#d7af5f', ---} ----- Base highlights to override themes to my preferences ---local base = { --- CursorLine = { bg = 'none', fg = 'none' }, --- -- Avoid changing the foreground color from the main theme --- MatchParen = { fg = colors.accent, bg = 'none', bold = true }, --- Visual = { bg = colors.grey, fg = 'none' }, --- ActionPreviewBorder = { link = 'XMenuBorder' }, --- ActionPreviewNormal = { link = 'XMenu' }, --- CallHierarchyBorder = { link = 'XMenuBorder' }, --- CallHierarchyNormal = { link = 'XMenu' }, --- CodeActionBorder = { link = 'XMenuBorder' }, --- CodeActionNormal = { link = 'XMenu' }, --- DefinitionBorder = { link = 'XMenuBorder' }, --- DefinitionNormal = { link = 'XMenu' }, --- EndOfBuffer = { fg = colors.background }, --- FinderBorder = { link = 'XMenuBorder' }, --- FinderNormal = { link = 'XMenu' }, --- FloatBorder = { link = 'XMenuBorder' }, --- FloatNormal = { link = 'XMenu' }, --- HoverBorder = { link = 'XMenuBorder' }, --- HoverNormal = { link = 'XMenu' }, --- NormalFloat = { link = 'XMenu' }, --- OutlinePreviewBorder = { link = 'XMenuBorder' }, --- OutlinePreviewNormal = { link = 'XMenu' }, --- Pmenu = { link = 'XMenu' }, --- RenameBorder = { link = 'XMenuBorder' }, --- RenameNormal = { link = 'XMenu' }, --- TerminalBorder = { link = 'XMenuBorder' }, --- TerminalNormal = { link = 'XMenu' }, --- Todo = { fg = colors.orange, bold = true }, --- SagaNormal = { link = 'XMenu' }, --- SagaBorder = { link = 'XMenuBorder' }, --- StatusLine = { bg = colors.background, fg = colors.foreground }, --- TitleString = { link = 'XMenu' }, --- TitleIcon = { link = 'XMenu' }, --- ActionPreviewTitle = { link = 'XMenu' }, --- -- Mason --- MasonHeader = { link = 'XMenu' }, --- MasonNormal = { link = 'XMenu' }, --- NvimTreeCursorColumn = { bg = 'none', fg = 'none' }, --- --NvimTreeCursorLine = { bg = colors.dark_grey, fg = colors.accent }, --- -- Telescope --- TelescopeBorder = { link = 'XMenuBorder' }, --- TelescopePromptBorder = { link = 'XMenuBorder' }, --- TelescopePromptNormal = { link = 'XMenu', fg = colors.foreground }, --- TelescopePromptPrefix = { link = 'XMenu', fg = colors.foreground }, --- TelescopeNormal = { bg = colors.background }, --- TelescopePreviewBorder = { link = 'XMenuBorder' }, --- TelescopePreviewNormal = { link = 'XMenu', fg = colors.foreground }, --- TelescopePreviewTitle = { fg = colors.background, bg = colors.light_orange }, --- TelescopePreviewLine = { fg = colors.background, bg = colors.orange }, --- TelescopePromptTitle = { fg = colors.background, bg = colors.light_orange }, --- TelescopeResultsTitle = { fg = colors.background, bg = colors.light_orange }, --- TelescopeResultsBorder = { link = 'XMenuBorder' }, --- TelescopeResultsNormal = { link = 'XMenu' }, ---} --- ---local theme = { --- -- Transparent background --- Conceal = { bg = 'none', fg = colors.foreground }, --- FoldCoumn = { bg = 'none' }, --- LineNr = { bg = 'none', fg = colors.grey }, --- MsgArea = { bg = 'none', fg = colors.foreground }, --- NonText = { bg = 'none', fg = colors.light_grey }, --- Normal = { bg = 'none', fg = colors.foreground }, --- NormalNC = { bg = 'none', fg = colors.foreground }, --- SignColumn = { bg = 'none' }, --- WinSeparator = { bg = 'none', fg = colors.grey }, --- -- Language Syntax --- Boolean = { fg = colors.grey }, --- Character = { fg = colors.yellow }, --- Comment = { fg = colors.light_grey }, --- Conditional = { fg = colors.blue }, --- Constant = { fg = colors.grey }, --- Debug = { fg = colors.green }, --- Define = { fg = colors.green }, --- Delimiter = { fg = colors.grey }, --- Error = { fg = colors.red }, --- Exception = { fg = colors.dark_orange }, --- Float = { fg = colors.light_pink }, --- Function = { fg = colors.foreground }, --- Identifier = { fg = colors.foreground }, --- Ignore = { fg = colors.light_grey }, --- Include = { fg = colors.dark_orange }, --- Keyword = { fg = colors.blue }, --- Label = { fg = colors.grey }, --- Macro = { fg = colors.dark_orange }, --- Number = { fg = colors.light_pink }, --- Operator = { fg = colors.purple }, --- PreCondit = { fg = colors.dark_orange }, --- PreProc = { fg = colors.dark_orange }, --- Repeat = { fg = colors.blue }, --- Special = { fg = colors.dark_orange }, --- SpecialChar = { fg = colors.dark_orange }, --- SpecialComment = { fg = colors.orange }, --- Statement = { fg = colors.blue }, --- StorageClass = { fg = colors.light_orange }, --- String = { fg = colors.yellow }, --- Structure = { fg = colors.light_orange }, --- Tag = { fg = colors.orange }, --- Todo = { fg = colors.orange, bold = true }, --- Type = { fg = colors.light_orange }, --- Typedef = { fg = colors.foreground }, --- Underlined = { fg = colors.foreground }, --- -- Rest --- ColorColumn = { bg = colors.dark_grey }, --- Cursor = { bg = colors.accent }, --- CursorColumn = { bg = 'none', fg = colors.accent }, --- CursorLine = { bg = 'none', fg = 'none' }, --- CursorLineNr = { bg = 'none', fg = colors.light_pink }, --- Directory = { fg = colors.cyan, bold = true }, --- ErrorMsg = { bg = 'none', fg = colors.red }, --- IncSearch = { bg = colors.accent, fg = colors.foreground }, --- MatchParen = { fg = colors.accent, bg = 'none', bold = true }, --- Search = { bg = colors.accent }, --- SpellBad = { bg = 'none', fg = 'none', undercurl = true, ctermbg = 'none', ctermfg = 'none' }, --- SpellLocal = { bg = 'none', fg = 'none', undercurl = true, ctermbg = 'none', ctermfg = 'none' }, --- SpellRare = { bg = 'none', fg = 'none', undercurl = true, ctermbg = 'none', ctermfg = 'none' }, --- StatusLine = { fg = colors.foreground, bg = 'none' }, --- StatusLineNC = { fg = 'none', bg = 'none' }, --- Title = { fg = colors.blue, bg = '', bold = true }, --- VertSplit = { bg = 'none', fg = colors.grey }, --- Visual = { bg = colors.grey, fg = 'none' }, --- -- Diagnostic --- DiagnosticBorder = { link = 'XMenuBorder' }, --- DiagnosticNormal = { link = 'XMenu' }, --- DiagnosticError = { fg = colors.red }, --- DiagnosticFloatingError = { link = 'XMenu' }, --- DiagnosticFloatingHint = { link = 'XMenu' }, --- DiagnosticFloatingInfo = { link = 'XMenu' }, --- DiagnosticFloatingWarn = { link = 'XMenu' }, --- DiagnosticSignHint = { fg = colors.grey, bold = true, bg = 'none' }, --- DiagnosticUnderlineError = { undercurl = true }, --- DiagnosticVirtualTextHint = { bg = 'none' }, --- DiagnosticWarn = { fg = colors.yellow }, --- -- nvim cmp --- CmpItemAbbr = { fg = colors.foreground }, --- CmpItemAbbrMatch = { fg = colors.dark_orange }, --- CmpItemAbbrMatchFuzzy = { fg = colors.dark_orange }, --- CmpBorder = { link = 'FloatBorder' }, --- CmpDocBorder = { link = 'FloatBorder' }, --- CmpItemMenuDefault = { link = 'NormalFloat' }, --- CmpMenu = { link = 'NormalFloat' }, --- -- cmp item kinds --- CmpItemKindConstant = { fg = colors.light_pink }, --- CmpItemKindFunction = { fg = colors.blue }, --- CmpItemKindIdentifier = { fg = colors.purple }, --- CmpItemKindField = { fg = colors.dark_orange }, --- CmpItemKindVariable = { fg = colors.purple }, --- CmpItemKindSnippet = { fg = colors.light_pink }, --- CmpItemKindText = { fg = colors.foreground }, --- CmpItemKindStructure = { fg = colors.blue }, --- CmpItemKindType = { fg = colors.orange }, --- CmpItemKindKeyword = { fg = colors.blue }, --- CmpItemKindMethod = { fg = colors.purple }, --- CmpItemKindConstructor = { fg = colors.blue }, --- CmpItemKindFolder = { fg = colors.blue }, --- CmpItemKindModule = { fg = colors.dark_orange }, --- CmpItemKindProperty = { fg = colors.dark_orange }, --- CmpItemKindEnum = { fg = colors.blue }, --- CmpItemKindUnit = { fg = colors.green }, --- CmpItemKindClass = { fg = colors.blue }, --- CmpItemKindFile = { fg = colors.blue }, --- CmpItemKindInterface = { fg = colors.blue }, --- CmpItemKindColor = { fg = colors.yellow }, --- CmpItemKindReference = { fg = colors.blue }, --- CmpItemKindEnumMember = { fg = colors.green }, --- CmpItemKindStruct = { fg = colors.blue }, --- -- CmpItemKindValue = { fg = "" }, --- -- CmpItemKindEvent = { fg = "" }, --- CmpItemKindOperator = { fg = colors.purple }, --- CmpItemKindTypeParameter = { fg = colors.light_pink }, --- CmpItemKindCopilot = { fg = colors.green }, --- -- LSPSaga --- OutlinePreviewBorder = { link = 'NormalFloat' }, --- OutlinePreviewNormal = { link = 'FloatBorder' }, --- -- Diffview --- DiffViewSignColumn = { bg = 'none' }, --- -- Treesitter --- -- ['@string'] = { fg = colors.yellow, bg = '' }, --- ['@boolean'] = { fg = colors.dark_yellow }, --- ['@character'] = { fg = colors.foreground }, --- ['@character.special'] = { fg = colors.dark_orange }, --- ['@comment'] = { link = 'Comment' }, --- ['@conditional'] = { fg = colors.blue }, --- ['@constant'] = { fg = colors.foreground }, --- ['@constant.builtin'] = { fg = colors.dark_yellow }, --- ['@constant.macro'] = { fg = colors.dark_orange }, --- ['@constructor'] = { fg = colors.blue }, --- ['@debug'] = { link = 'Debug' }, --- ['@define'] = { link = "Define" }, --- ['@exception'] = { fg = colors.dark_orange }, --- ['@field'] = { link = 'Identifier' }, --- ['@field.yaml'] = { fg = colors.light_orange }, --- ['@float'] = { fg = colors.light_pink }, --- ['@function'] = { link = 'Function' }, --- ['@function.builtin'] = { fg = colors.dark_green }, --- ['@function.macro'] = { fg = colors.dark_orange }, --- ['@include'] = { fg = colors.dark_orange }, --- ['@keyword'] = { link = 'Keyword' }, --- ['@keyword.operator'] = { fg = colors.purple }, --- ['@keyword.return'] = { fg = colors.dark_orange }, --- ['@label'] = { fg = colors.blue }, --- ['@macro'] = { fg = colors.dark_orange }, --- ['@method'] = { fg = colors.foreground }, --- ['@namespace'] = { fg = colors.foreground }, --- ['@number'] = { link = 'Number' }, --- ['@operator'] = { link = 'Operator' }, --- ['@parameter'] = { fg = colors.foreground }, --- ['@preproc'] = { link = 'PreProc' }, --- ['@property'] = { fg = colors.foreground }, --- ['@punctuation.Special'] = { fg = colors.purple }, --- ['@punctuation.bracket'] = { fg = colors.purple }, --- ['@punctuation.delimiter'] = { fg = colors.purple }, --- ['@repeat'] = { link = 'Repeat' }, --- ['@storageclass'] = { link = 'StorageClass' }, --- ['@string.escape'] = { fg = colors.dark_orange }, --- ['@string.regex'] = { fg = colors.green }, --- ['@string.special'] = { fg = colors.dark_orange }, --- ['@structure'] = { link = 'Structure' }, --- ['@tag'] = { link = 'Tag' }, --- ['@tag.attribute'] = { fg = colors.foreground }, --- ['@tag.delimiter'] = { fg = colors.purple }, --- ['@text.danger'] = { fg = colors.dark_orange }, --- ['@text.literal'] = { link = 'String' }, --- ['@text.reference'] = { fg = colors.green }, --- ['@text.strong'] = { bold = true }, --- ['@text.title'] = { link = 'Title' }, --- ['@text.todo'] = { link = 'Todo' }, --- ['@text.underline'] = { link = 'Underlined' }, --- ['@text.uri'] = { fg = colors.blue }, --- ['@text.warning'] = { link = 'Todo' }, --- ['@type'] = { link = 'Type' }, --- ['@type.definition'] = { link = 'Typedef' }, --- ['@variable'] = { fg = colors.foreground }, --- ['@variable.builtin'] = { fg = colors.light_orange }, ---} --- ---local M = {} --- ---local function _load(config) --- for group, hl in pairs(config) do --- if not vim.tbl_isempty(hl) then --- vim.api.nvim_set_hl(0, group, hl) --- end --- end ---end --- --- ---function M.setup() --- vim.api.nvim_set_hl(0, 'XMenu', { bg = colors.dark_grey, default = true }) --- vim.api.nvim_set_hl(0, 'XMenuBorder', { bg = colors.dark_grey, fg = colors.dark_grey, default = true }) --- vim.g.terminal_color_0 = colors.dark_grey ---end --- -----function M.load(full) ----- _load(base) ----- ----- if not full then ----- _load_diagnostics() ----- return ----- end ----- ----- _load(theme) -----end --- ---return M +return M diff --git a/common/config/nvim/lua/plugins/comment.lua b/common/config/nvim/lua/plugins/comment.lua new file mode 100755 index 0000000..392b279 --- /dev/null +++ b/common/config/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/config/nvim/lua/plugins/dap.lua b/common/config/nvim/lua/plugins/dap.lua index 98ae3fd..7de032c 100644..100755 --- a/common/config/nvim/lua/plugins/dap.lua +++ b/common/config/nvim/lua/plugins/dap.lua @@ -1,4 +1,10 @@ -local dap = require("dap") +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" @@ -155,11 +161,12 @@ local dapui = require("dapui") --}) -- Load dapui configuration only if it hasn't been loaded before -if not vim.g.loaded_dapui then - require("dapui").setup({ - mappings = { - expand = "<CR>", - open = "o", + 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", @@ -240,14 +247,19 @@ 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, -}) + 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/config/nvim/lua/plugins/dashboard.lua b/common/config/nvim/lua/plugins/dashboard.lua index f02242c..43a3461 100644..100755 --- a/common/config/nvim/lua/plugins/dashboard.lua +++ b/common/config/nvim/lua/plugins/dashboard.lua @@ -1,11 +1,31 @@ -local db = require('dashboard') -local messages = require('plugins.messages') +local M = {} -function GetRandomMessage() - -- Get a random index from the messages array - local randomIndex = math.random(1, #messages) - return messages[randomIndex] -end +--- 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() @@ -16,10 +36,11 @@ end -- end, --}) -db.setup({ - theme = 'hyper', + -- Configure dashboard + db.setup({ + theme = "hyper", config = { - mru = { limit = 10, label = '' }, + mru = { limit = 20, label = "" }, project = { limit = 10 }, header = { [[ ███╗ ██╗ ███████╗ ██████╗ ██╗ ██╗ ██╗ ███╗ ███╗]], @@ -31,46 +52,47 @@ db.setup({ }, disable_move = false, shortcut = { - { desc = ' Plugins', group = 'Number', action = 'PackerStatus', key = 'p' }, - --{ desc = " Plugins", group = "@property", action = "PackerStatus", key = "p" }, + { desc = " Plugins", group = "Number", action = "PackerStatus", key = "p" }, + { + desc = " Files", + group = "Number", + action = "Telescope find_files", + key = "f", + }, { - desc = ' Files', - group = 'Number', - --group = "Label", - action = 'Telescope find_files', - key = 'f', + desc = " TODO", + group = "Number", + action = ":edit ~/documents/main/inbox/tasks/TODO.md", + key = "t", }, { - desc = ' Text', - group = 'Number', - --group = "Label", - action = 'enew', - key = 't', + desc = " New", + group = "Number", + action = "enew", + key = "e", }, { - desc = ' Grep', - group = 'Number', - --group = "Label", - action = 'Telescope live_grep', - key = 'g', + desc = " Grep", + group = "Number", + action = "Telescope live_grep", + key = "g", }, { - desc = ' Scheme', - group = 'Number', - --group = "Label", - action = 'Telescope colorscheme', - key = 's', + desc = " Scheme", + group = "Number", + action = "Telescope colorscheme", + key = "s", }, { - desc = ' Config', - group = 'Number', - --group = "Label", - action = ':edit ~/.config/nvim/init.lua', - key = 'c', + desc = " Config", + group = "Number", + action = ":edit ~/.config/nvim/init.lua", + key = "c", }, }, footer = function() - return { '', GetRandomMessage() } + return { "", "" } + --return { "", GetRandomMessage() } end, }, hide = { @@ -80,7 +102,16 @@ db.setup({ }, }) ---highlights +-- 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 @@ -88,3 +119,8 @@ db.setup({ --DashboardMruTitle DashboardMruIcon DashboardFiles DashboardShotCutIcon ---- Doome theme --DashboardDesc DashboardKey DashboardIcon DashboardShotCut + + return true +end + +return M diff --git a/common/config/nvim/lua/plugins/fidget.lua b/common/config/nvim/lua/plugins/fidget.lua index d401c5f..d401c5f 100644..100755 --- a/common/config/nvim/lua/plugins/fidget.lua +++ b/common/config/nvim/lua/plugins/fidget.lua diff --git a/common/config/nvim/lua/plugins/friendly-snippets.lua b/common/config/nvim/lua/plugins/friendly-snippets.lua new file mode 100755 index 0000000..2a7695e --- /dev/null +++ b/common/config/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/config/nvim/lua/plugins/fugitive.lua b/common/config/nvim/lua/plugins/fugitive.lua new file mode 100755 index 0000000..22620e3 --- /dev/null +++ b/common/config/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/config/nvim/lua/plugins/fzf.lua b/common/config/nvim/lua/plugins/fzf.lua index 5675e9f..9e62c48 100644..100755 --- a/common/config/nvim/lua/plugins/fzf.lua +++ b/common/config/nvim/lua/plugins/fzf.lua @@ -1,60 +1,43 @@ ---vim.cmd([[ --- let g:fzf_history_dir = '~/.local/share/fzf-history' --- map <leader>z :FZF<CR> --- map <leader>a :Files<CR> --- map <leader>l :Lines<CR> --- map <leader>L :BLines<CR> --- map <leader>B :Buffers<CR> --- map <leader>h :History:<CR> --- nnoremap <leader>g :Rg<CR> --- "nnoremap <leader>t :Tags<CR> --- nnoremap <leader>m :Marks<CR> --- " This is the default extra key bindings --- let g:fzf_action = { --- \ 'ctrl-t': 'tab split', --- \ 'ctrl-x': 'split', --- \ 'ctrl-y': 'vsplit' } --- let g:fzf_tags_command = 'ctags -R' --- " Border color --- let g:fzf_layout = {'up':'~90%', 'window': { 'width': 0.8, 'height': 0.8,'yoffset':0.5,'xoffset': 0.5, 'highlight': 'Todo', 'border': 'sharp' } } --- let $FZF_DEFAULT_OPTS = '--layout=reverse --info=inline' --- let $FZF_DEFAULT_COMMAND="rg --files --hidden" --- " Customize fzf colors to match your color scheme --- let g:fzf_colors = --- \ { 'fg': ['fg', 'Normal'], --- \ 'bg': ['bg', 'Normal'], --- \ 'hl': ['fg', 'Comment'], --- \ 'fg+': ['fg', 'CursorLine', 'CursorColumn', 'Normal'], --- \ 'bg+': ['bg', 'CursorLine', 'CursorColumn'], --- \ 'hl+': ['fg', 'Statement'], --- \ 'info': ['fg', 'PreProc'], --- \ 'border': ['fg', 'Ignore'], --- \ 'prompt': ['fg', 'Conditional'], --- \ 'pointer': ['fg', 'Exception'], --- \ 'marker': ['fg', 'Keyword'], --- \ 'spinner': ['fg', 'Label'], --- \ 'header': ['fg', 'Comment'] } --- " Get Files --- command! -bang -nargs=? -complete=dir Files --- \ call fzf#vim#files(<q-args>, fzf#vim#with_preview({'options': ['--layout=reverse', '--info=inline']}), <bang>0) --- " Get text in files with Rg --- command! -bang -nargs=* Rg --- \ call fzf#vim#grep( --- \ 'rg --column --line-number --no-heading --color=always --smart-case '.shellescape(<q-args>), 1, --- \ fzf#vim#with_preview(), <bang>0) --- " Ripgrep advanced --- function! RipgrepFzf(query, fullscreen) --- let command_fmt = 'rg --column --line-number --no-heading --color=always --smart-case %s || true' --- let initial_command = printf(command_fmt, shellescape(a:query)) --- let reload_command = printf(command_fmt, '{q}') --- let spec = {'options': ['--phony', '--query', a:query, '--bind', 'change:reload:'.reload_command]} --- call fzf#vim#grep(initial_command, 1, fzf#vim#with_preview(spec), a:fullscreen) --- endfunction --- command! -nargs=* -bang RG call RipgrepFzf(<q-args>, <bang>0) --- " Git grep --- command! -bang -nargs=* GGrep --- \ call fzf#vim#grep( --- \ 'git grep --line-number '.shellescape(<q-args>), 0, --- \ fzf#vim#with_preview({'dir': systemlist('git rev-parse --show-toplevel')[0]}), <bang>0) --- command! -bang FM call fzf#run(fzf#wrap({'source': 'cat ~/.fzf-marks | sed "s/.*: \(.*\)$/\1/" | sed "s#~#${HOME}#"', 'sink': 'lcd'}, <bang>0)) ---]]) +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/config/nvim/lua/plugins/git.lua b/common/config/nvim/lua/plugins/git.lua new file mode 100755 index 0000000..24a0871 --- /dev/null +++ b/common/config/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/config/nvim/lua/plugins/gitsigns.lua b/common/config/nvim/lua/plugins/gitsigns.lua index 8fbdae1..7bbe637 100644..100755 --- a/common/config/nvim/lua/plugins/gitsigns.lua +++ b/common/config/nvim/lua/plugins/gitsigns.lua @@ -1,4 +1,15 @@ -require("gitsigns").setup({ +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", @@ -45,3 +56,30 @@ 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/config/nvim/lua/plugins/goto-preview.lua b/common/config/nvim/lua/plugins/goto-preview.lua index d4d2c67..eb54a8c 100644..100755 --- a/common/config/nvim/lua/plugins/goto-preview.lua +++ b/common/config/nvim/lua/plugins/goto-preview.lua @@ -1,4 +1,12 @@ -require('goto-preview').setup { +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 @@ -14,5 +22,10 @@ require('goto-preview').setup { 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 -} + bufhidden = "wipe", -- the bufhidden option to set on the floating window. See :h bufhidden + } + + return true +end + +return M diff --git a/common/config/nvim/lua/plugins/hardtime.lua b/common/config/nvim/lua/plugins/hardtime.lua index cb03468..b440334 100644..100755 --- a/common/config/nvim/lua/plugins/hardtime.lua +++ b/common/config/nvim/lua/plugins/hardtime.lua @@ -1,26 +1,26 @@ -local hardtime = require('hardtime') +-- 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' }, + restriction_mode = "hint", + disabled_filetypes = { "qf", "netrw", "NvimTree", "NvimTree_1", "lazy", "mason", "oil", "dashboard" }, disable_mouse = false, disabled_keys = { - ['<Up>'] = {}, - ['<Down>'] = {}, - ['<Left>'] = {}, - ['<Right>'] = {}, + ["<Up>"] = {}, + ["<Down>"] = {}, + ["<Left>"] = {}, + ["<Right>"] = {}, }, }) --- Function to toggle the hardtime state and echo a message -local hardtime_enabled = true - function ToggleHardtime() hardtime.toggle() hardtime_enabled = not hardtime_enabled - local message = hardtime_enabled and 'hardtime on' or 'hardtime off' + local message = hardtime_enabled and "hardtime on" or "hardtime off" vim.cmd('echo "' .. message .. '"') end diff --git a/common/config/nvim/lua/plugins/harpoon.lua b/common/config/nvim/lua/plugins/harpoon.lua index 784ee0b..8e842b3 100644..100755 --- a/common/config/nvim/lua/plugins/harpoon.lua +++ b/common/config/nvim/lua/plugins/harpoon.lua @@ -1,4 +1,12 @@ -require("harpoon").setup({ +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, }, @@ -12,18 +20,26 @@ require("harpoon").setup({ -- --{ "<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" }, - --}, -}) -vim.api.nvim_set_keymap("n", "<leader>ma", ":lua require('harpoon.mark').add_file()<CR>", {}) -vim.api.nvim_set_keymap("n", "<leader>mt", ":lua require('harpoon.mark').toggle_file()<CR>", {}) -vim.api.nvim_set_keymap("n", "<leader>mq", ":lua require('harpoon.ui').toggle_quick_menu()<CR>", {}) -vim.api.nvim_set_keymap("n", "<leader>mh", ":lua require('harpoon.ui').nav_file(1)<CR>", {}) -vim.api.nvim_set_keymap("n", "<leader>mj", ":lua require('harpoon.ui').nav_file(2)<CR>", {}) -vim.api.nvim_set_keymap("n", "<leader>mk", ":lua require('harpoon.ui').nav_file(3)<CR>", {}) -vim.api.nvim_set_keymap("n", "<leader>ml", ":lua require('harpoon.ui').nav_file(4)<CR>", {}) + }) + + -- 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 ---local mark = require("harpoon.mark") ---local ui = require("harpoon.ui") +return M -- --vim.keymap.set("n", "<leader>a", mark.add_file) --vim.keymap.set("n", "<C-e>", ui.toggle_quick_menu) diff --git a/common/config/nvim/lua/plugins/heirline.lua b/common/config/nvim/lua/plugins/heirline.lua index c41aff3..a4c2fc3 100644..100755 --- a/common/config/nvim/lua/plugins/heirline.lua +++ b/common/config/nvim/lua/plugins/heirline.lua @@ -1,1290 +1,1497 @@ -local conditions = require('heirline.conditions') -local utils = require('heirline.utils') - -local colors = { - --bg = "#23232e", - bg = nil, - 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 = 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', - }, -} - -require('heirline').load_colors(colors) - -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) - --if not self.once then - -- vim.cmd("au ModeChanged *:*o redrawstatus") - --end - --self.once = true - 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', +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, }, - }, - 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', - pattern = '*:*', - callback = vim.schedule_wrap(function() - vim.cmd('redrawstatus') - end), - }, -} - --- LSP -local LSPActive = { - condition = conditions.lsp_attached, - update = { 'LspAttach', 'LspDetach' }, - provider = function() - local buf_clients = vim.lsp.buf_get_clients() - local buf_client_names = {} - - -- add client - for _, client in pairs(buf_clients) do - if client.name ~= 'null-ls' then - table.insert(buf_client_names, client.name) + 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 '⚙️ ' .. table.concat(buf_client_names, '') - end, - hl = { fg = colors.lightgray, bold = false }, -} -local Navic = { - condition = function() - return require('nvim-navic').is_available() - end, - static = { - -- create a type highlight map - 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', + + -- 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"), }, - -- bit operation dark magic, see below... - enc = function(line, col, winnr) - return bit.bor(bit.lshift(line, 16), bit.lshift(col, 6), winnr) + ---- 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, - -- line: 16 bit (65535); col: 10 bit (1023); winnr: 6 bit (63) - 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 + 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, - }, - init = function(self) - local data = require('nvim-navic').get_data() or {} - local children = {} - -- create a child for each level - for i, d in ipairs(data) do - -- encode line and column numbers into a single integer - 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], - }, - { - -- escape `%`s (elixir) and buggy default separators - provider = d.name:gsub('%%', '%%%%'):gsub('%s*->%s*', ''), - -- highlight icon only or location name as well - -- hl = self.type_hl[d.type], - - on_click = { - -- pass the encoded position through minwid - minwid = pos, - callback = function(_, minwid) - -- decode - local line, col, winnr = self.dec(minwid) - vim.api.nvim_win_set_cursor(vim.fn.win_getid(winnr), { line, col }) - end, - name = 'heirline_navic', - }, - }, - } - -- add a separator only if needed - if #data > 1 and i < #data then - table.insert(child, { - provider = ' > ', - hl = { fg = 'bright_fg' }, - }) + 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 - table.insert(children, child) - end - -- instantiate the new child, overwriting the previous one - self.child = self:new(children, 1) - end, - -- evaluate the children containing navic components - provider = function(self) - return self.child:eval() - end, - hl = { fg = 'gray' }, - update = 'CursorMoved', -} - --- Diagnostics -local Diagnostics = { - condition = conditions.has_diagnostics, - static = { - error_icon = vim.fn.sign_getdefined('DiagnosticSignError')[1].text, - warn_icon = vim.fn.sign_getdefined('DiagnosticSignWarn')[1].text, - info_icon = vim.fn.sign_getdefined('DiagnosticSignInfo')[1].text, - hint_icon = vim.fn.sign_getdefined('DiagnosticSignHint')[1].text, - }, - 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) - -- 0 is just another output, we can decide to print it or not! - return self.errors > 0 and (self.error_icon .. self.errors .. ' ') + + return result or "" end, - hl = { fg = colors.diag.error, bg = colors.bg }, - }, - { - provider = function(self) - return self.warnings > 0 and (self.warn_icon .. self.warnings .. ' ') + hl = { fg = "lightgray", bold = false }, + } + + local Navic = { + condition = function() + local ok, navic = pcall(require, "nvim-navic") + return ok and navic.is_available() end, - hl = { fg = colors.diag.warn, bg = colors.bg }, - }, - { - provider = function(self) - return self.info > 0 and (self.info_icon .. self.info .. ' ') + 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, - hl = { fg = colors.diag.info, bg = colors.bg }, - }, - { provider = function(self) - return self.hints > 0 and (self.hint_icon .. self.hints) + return self.child:eval() end, - hl = { fg = colors.diag.hint, bg = colors.bg }, - }, - on_click = { - callback = function() - require('trouble').toggle({ mode = 'document_diagnostics' }) - -- or - -- vim.diagnostic.setqflist() + 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, - name = 'heirline_diagnostics', - }, -} - --- Git --- For the ones who're not (too) afraid of changes! Uses gitsigns. -local Git = { - condition = conditions.is_git_repo, - init = function(self) - self.status_dict = vim.b.gitsigns_status_dict - self.has_changes = self.status_dict.added ~= 0 or self.status_dict.removed ~= 0 or self.status_dict.changed ~= 0 - end, - --{ - -- -- git branch name - -- provider = function(self) - -- --return ' ' .. self.status_dict.head - -- return ' ' .. self.status_dict.head - -- end, - -- --hl = { bold = true }, - -- hl = { fg = colors.git.active, bold = true, bg = colors.bg }, - --}, - -- You could handle delimiters, icons and counts similar to Diagnostics - { - -- git branch icon - provider = function() - return ' ' + 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, - hl = { fg = colors.git.active, bg = colors.bg }, - }, + { + 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", + }, + } - { - -- git branch name - provider = function(self) - return self.status_dict.head + -- FileNameBlock: FileIcon, FileName and friends + local FileNameBlock = { + init = function(self) + self.filename = vim.api.nvim_buf_get_name(0) end, - hl = { fg = colors.white, bg = colors.bg }, - }, + 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 - { - condition = function(self) - return self.has_changes + 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 = "(" - provider = '', - }, - { provider = function(self) - local count = self.status_dict.added or 0 - --return count > 0 and ("+" .. count) - return count > 0 and (' ' .. count) + return self.icon end, - --hl = { fg = "git_add" }, - hl = { fg = colors.git.add, bg = colors.bg }, - }, - { - provider = function(self) - local count = self.status_dict.removed or 0 - --return count > 0 and ("-" .. count) - return count > 0 and (' ' .. count) + hl = function(self) + return { fg = self.icon_color, bold = true } end, - --hl = { fg = "git_del" }, - hl = { fg = colors.git.del, bg = colors.bg }, - }, - { + } + + local FileName = { provider = function(self) - local count = self.status_dict.changed or 0 - --return count > 0 and ("~" .. count) - return count > 0 and (' ' .. count) + 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 = "git_change" }, - hl = { fg = colors.git.change, bg = colors.bg }, - }, - --{ - -- condition = function(self) - -- return self.has_changes - -- end, - -- provider = ")", - --}, - on_click = { - callback = function() - -- If you want to use Fugitive: - -- vim.cmd("G") - - -- If you prefer Lazygit - -- use vim.defer_fn() if the callback requires - -- opening of a floating window - -- (this also applies to telescope) - vim.defer_fn(function() - vim.cmd('Lazygit') - end, 100) + 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, - name = 'heirline_git', - }, -} - --- Debugger --- Display informations from nvim-dap! --- Note that we add spaces separately, so that only the icon characters will be clickable ---local DAPMessages = { --- condition = function() --- local session = require("dap").session() --- return session ~= nil --- end, --- provider = function() --- return " " .. require("dap").status() .. " " --- end, --- hl = "Debug", --- { --- provider = "", --- on_click = { --- callback = function() --- require("dap").step_into() --- end, --- name = "heirline_dap_step_into", --- }, --- }, --- { provider = " " }, --- { --- provider = "", --- on_click = { --- callback = function() --- require("dap").step_out() --- end, --- name = "heirline_dap_step_out", --- }, --- }, --- { provider = " " }, --- { --- provider = " ", --- on_click = { --- callback = function() --- require("dap").step_over() --- end, --- name = "heirline_dap_step_over", --- }, --- }, --- { provider = " " }, --- { --- provider = "ﰇ", --- on_click = { --- callback = function() --- require("dap").run_last() --- end, --- name = "heirline_dap_run_last", --- }, --- }, --- { provider = " " }, --- { --- provider = "", --- on_click = { --- callback = function() --- require("dap").terminate() --- require("dapui").close({}) --- end, --- name = "heirline_dap_close", --- }, --- }, --- { provider = " " }, --- -- icons: ﰇ ---} + } --- Tests --- This requires the great ultest. ---local UltTest = { --- condition = function() --- return vim .api.nvim_call_function("ultest#is_test_file", {}) ~= 0 --- end, --- static = { --- passed_icon = vim.fn.sign_getdefined("test_pass")[1].text, --- failed_icon = vim.fn.sign_getdefined("test_fail")[1].text, --- passed_hl = { fg = utils.get_highlight("UltestPass").fg }, --- failed_hl = { fg = utils.get_highlight("UltestFail").fg }, --- }, --- init = function(self) --- self.status = vim.api.nvim_call_function("ultest#status", {}) --- end, --- --- -- again, if you'd like icons and numbers to be colored differently, --- -- just split the component in two --- { --- provider = function(self) --- return self.passed_icon .. self.status.passed .. " " --- end, --- hl = function(self) --- return self.passed_hl --- end, --- }, --- { --- provider = function(self) --- return self.failed_icon .. self.status.failed .. " " --- end, --- hl = function(self) --- return self.failed_hl --- end, --- }, --- { --- provider = function(self) --- return "of " .. self.status.tests - 1 --- end, --- }, ---} + -- FileType, FileEncoding and FileFormat + local FileType = { + provider = function() + return vim.bo.filetype + end, + hl = { fg = colors.white, bold = false, bg = colors.bg }, + } --- FileNameBlock: FileIcon, FileName and friends -local FileNameBlock = { - -- let's first set up some attributes needed by this component and it's children - init = function(self) - self.filename = vim.api.nvim_buf_get_name(0) - end, - --hl = { fg = utils.get_highlight("Statusline").fg, bold = true, bg = colors.bg }, - hl = { bg = colors.bg }, -} - --- FileIcon, FileName, FileFlags and FileNameModifier -local FileIcon = { - init = function(self) - local filename = self.filename - local extension = vim.fn.fnamemodify(filename, ':e') - self.icon, self.icon_color = require('nvim-web-devicons').get_icon_color(filename, extension, { default = true }) - end, - provider = function(self) - return self.icon and (self.icon .. ' ') - end, - hl = function(self) - return { fg = self.icon_color, bg = colors.bg } - end, -} - -local FileName = { - provider = function(self) - -- first, trim the pattern relative to the current directory. For other - -- options, see :h filename-modifers - local filename = vim.fn.fnamemodify(self.filename, ':.') - if filename == '' then - return 'No Name' - end - -- now, if the filename would occupy more than 1/4th of the available - -- space, we trim the file path to its initials - -- See Flexible Components section below for dynamic truncation - if not conditions.width_percent_below(#filename, 0.25) then - filename = vim.fn.pathshorten(filename) - end - return filename - end, - --hl = { fg = utils.get_highlight("Statusline").fg, bold = false, bg = colors.bg }, - hl = { fg = colors.white, bold = false, bg = colors.bg }, -} - -local FileFlags = { - { + local FileEncoding = { + Space, provider = function() - if vim.bo.modified then - return ' [+]' -- ±[+] - end + 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 = colors.green, bg = colors.bg }, - }, - { + hl = { fg = utils.get_highlight("Statusline").fg, bold = true, bg = colors.bg }, + } + + local FileSize = { provider = function() - if not vim.bo.modifiable or vim.bo.readonly then - return ' ' + 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 = colors.orange }, - 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 -- :h 'enc' - return enc:lower() - end, - --hl = { fg = utils.get_highlight("Statusline").fg, bold = true, bg = colors.bg }, - hl = { bg = colors.bg, bold = false }, -} - -local FileFormat = { - provider = function() - local fmt = vim.bo.fileformat - --return fmt ~= "unix" and fmt:upper() - return fmt ~= 'unix' and fmt:lower() - end, - hl = { fg = utils.get_highlight('Statusline').fg, bold = true, bg = colors.bg }, -} - --- FileSize and FileLastModified -local FileSize = { - provider = function() - -- stackoverflow, compute human readable file size - local suffix = { 'b', 'k', 'M', 'G', 'T', 'P', 'E' } - local fsize = vim.fn.getfsize(vim.api.nvim_buf_get_name(0)) - 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 = { - -- did you know? Vim is full of functions! - provider = function() - local ftime = vim.fn.getftime(vim.api.nvim_buf_get_name(0)) - return (ftime > 0) and os.date('%c', ftime) - end, - hl = { fg = utils.get_highlight('Statusline').fg, bold = true, bg = colors.bg }, -} - --- Spell --- Add indicator when spell is set! -local Spell = { - condition = function() - return vim.wo.spell - end, - provider = ' 暈', - 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) - if ok and search.total then - self.search = search - end - end, - provider = function(self) - local search = self.search - return string.format('[%d/%d]', search.current, math.min(search.total, search.maxcount)) - end, -} - -local MacroRec = { - condition = function() - return vim.fn.reg_recording() ~= '' and vim.o.cmdheight == 0 - end, - provider = ' ', - hl = { fg = 'orange', bold = true }, - utils.surround({ '[', ']' }, nil, { + hl = { fg = utils.get_highlight("Statusline").fg, bold = true, bg = colors.bg }, + } + + local FileLastModified = { provider = function() - return vim.fn.reg_recording() + 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 = 'green', bold = true }, - }), - update = { - 'RecordingEnter', - 'RecordingLeave', - }, -} - -local ShowCmd = { - condition = function() - return vim.o.cmdheight == 0 - end, - provider = ':%3.5(%S%)', -} - -local cursor_location = { - { provider = '%1(%4l:%-3(%c%)%) %*', hl = { fg = colors.black, bold = true } }, -} - -local Ruler = { cursor_location } - ---utils.make_flexible_component( --- 3, --- { Ruler, hl = { fg = utils.get_highlight("statusline").bg, force = true } }, --- { provider = "%<" } ---), ---local cursor_location = { --- { provider = "%7(%l:%c%) ", hl = { bold = true } }, --- { --- provider = " ", --- hl = function(self) --- local color = self:mode_color() --- return { fg = color, bold = true } --- end, --- }, ---} + hl = { fg = utils.get_highlight("Statusline").fg, bold = true, bg = colors.bg }, + } -local WordCount = { - condition = function() - return conditions.buffer_matches({ - filetype = { - 'markdown', - 'txt', - 'vimwiki', - }, - }) - end, - Space, - { + local Spell = { + condition = function() + return vim.wo.spell + end, provider = function() - return 'W:' .. vim.fn.wordcount().words + return " " .. Icons.Indicator.spell .. " " end, - }, -} - --- Working Directory -local WorkDir = { - init = function(self) - self.icon = (vim.fn.haslocaldir(0) == 1 and 'l' or 'g') .. ' ' .. ' ' - local cwd = vim.fn.getcwd(0) - self.cwd = vim.fn.fnamemodify(cwd, ':~') - end, - hl = { fg = 'colors.blue', bold = true }, - flexible = 1, - { - -- evaluates to the full-lenth path - provider = function(self) - local trail = self.cwd:sub(-1) == '/' and '' or '/' - return self.icon .. self.cwd .. trail .. ' ' + 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, - }, - { - -- evaluates to the shortened path provider = function(self) - local cwd = vim.fn.pathshorten(self.cwd) - local trail = self.cwd:sub(-1) == '/' and '' or '/' - return self.icon .. cwd .. trail .. ' ' + 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, - }, - { - -- evaluates to "", hiding the component - provider = '', - }, -} - --- Snippets Indicator --- This requires ultisnips ---local Snippets = { --- -- check that we are in insert or select mode --- condition = function() --- return vim.tbl_contains({'s', 'i'}, vim.fn.mode()) --- end, --- provider = function() --- local forward = (vim.fn["UltiSnips#CanJumpForwards"]() == 1) and "" or "" --- local backward = (vim.fn["UltiSnips#CanJumpBackwards"]() == 1) and " " or "" --- return backward .. forward --- end, --- hl = { fg = "red", bold = true }, ---} + update = { "CursorMoved", "CursorMovedI", "SearchWrapped" }, + } --- let's add the children to our FileNameBlock component -FileNameBlock = utils.insert( - FileNameBlock, - FileIcon, - utils.insert(FileNameModifier, FileName), -- a new table where FileName is a child of FileNameModifier - unpack(FileFlags), -- A small optimisation, since their parent does nothing - { provider = '%<' } -- this means that the statusline is cut here when there's not enough space -) - -local FileInfoBlock = { - -- let's first set up some attributes needed by this component and it's children - init = function(self) - self.filename = vim.api.nvim_buf_get_name(0) - end, -} - -FileInfoBlock = utils.insert( - FileInfoBlock, - Space, - FileIcon, - FileType, - { provider = '%<' } -- this means that the statusline is cut here when there's not enough space -) - -LeftSpace = utils.surround({ '', '' }, function(self) - return self:mode_color() -end, { LeftSpace, hl = { fg = utils.get_highlight('statusline').bg, force = true } }) - -RightSpace = utils.surround({ '', '' }, 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 } }) - -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 } }, - --{ Navic, hl = { bg = colors.nobg, force = true } }, - --{ DAPMessages, hl = { bg = colors.nobg, force = true } }, - { Align, hl = { bg = colors.nobg, force = true } }, -} - -local right = { - --{ Space, hl = { bg = colors.nobg, force = true } }, - { 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 } - -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 } }, - --{ DAPMessages, 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, - --{ FileNameBlock, hl = { bg = colors.nobg, force = true } }, - --{ Align, hl = { bg = colors.nobg, force = true } }, - specialsections, -} - -local SpecialStatusline = { - condition = function() - return conditions.buffer_matches({ - buftype = { 'nofile', 'prompt', 'help', 'quickfix' }, - filetype = { '^git.*', 'fugitive', 'dashboard' }, - }) - end, - specialsections, -} - ---local InactiveStatusline = SpecialStatusline - -local TerminalStatusline = { - condition = function() - return conditions.buffer_matches({ buftype = { 'terminal' } }) - end, - specialsections, -} - -local StatusLine = { - static = { - --mode_colors = { - -- n = colors.blue, - -- i = colors.green, - -- v = colors.purple, - -- V = colors.purple, - -- ["\22"] = colors.purple, - -- c = colors.orange, - -- s = colors.purple, - -- S = colors.purple, - -- ["\19"] = colors.purple, - -- R = colors.red, - -- r = colors.red, - -- ["!"] = colors.orange, - -- t = colors.orange, - --}, - 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) - local mode = conditions.is_active() and vim.fn.mode() or 'n' - return self.mode_colors[mode] + local MacroRec = { + condition = function() + return vim.fn.reg_recording() ~= "" and vim.o.cmdheight == 0 end, - hl = function(self) - local color = self:mode_color() -- here! - return { bg = color } + provider = function() + return Icons.Indicator.recording .. " " end, - }, - fallthrough = false, - SpecialStatusline, - TerminalStatusline, - InactiveStatusline, - DefaultStatusline, -} - --- ---- WinBar --- -local WinbarFileNameBlock = { - -- let's first set up some attributes needed by this component and it's children - init = function(self) - self.filename = vim.api.nvim_buf_get_name(0) - end, - hl = { bg = colors.bg }, -} - -local WinbarFileName = { - provider = function(self) - -- first, trim the pattern relative to the current directory. For other - -- options, see :h filename-modifers - local filename = vim.fn.fnamemodify(self.filename, ':.') - if filename == '' then - return 'No Name' - end - -- now, if the filename would occupy more than 1/4th of the available - -- space, we trim the file path to its initials - -- See Flexible Components section below for dynamic truncation - if not conditions.width_percent_below(#filename, 0.25) then - filename = vim.fn.pathshorten(filename) - end - return filename - end, - --hl = { fg = utils.get_highlight("Statusline").fg, bold = false, bg = colors.bg }, - hl = { fg = colors.gray, bold = false, bg = colors.bg }, -} - -WinbarFileNameBlock = utils.insert( - WinbarFileNameBlock, - FileIcon, - utils.insert(WinbarFileName), -- a new table where FileName is a child of FileNameModifier - unpack(FileFlags), -- A small optimisation, since their parent does nothing - { provider = '%<' } -- this means that the statusline is cut here when there's not enough space -) - -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, -}) - -On_click = { - -- get the window id of the window in which the component was evaluated - minwid = function() - return vim.api.nvim_get_current_win() - end, - callback = function(_, minwid) - -- winid is the window id of the window the component was clicked from - local winid = minwid - -- do something with the window id, e.g.: - local buf = vim.api.nvim_win_get_buf(winid) - -- ... - end, -} - -local CloseButton = { - condition = function(self) - return not vim.bo.modified - end, - -- a small performance improvement: - -- re register the component callback only on layout/buffer changes. - update = { 'WinNew', 'WinClosed', 'BufEnter' }, - { provider = ' ' }, - { - provider = '', - 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) + hl = { fg = "orange", bold = true }, + utils.surround({ "[", "]" }, nil, { + provider = function() + return vim.fn.reg_recording() end, - name = 'heirline_winbar_close_button', + hl = { fg = "green", bold = true }, + }), + update = { + "RecordingEnter", + "RecordingLeave", + callback = vim.schedule_wrap(function() + vim.cmd("redrawstatus") + end), }, - }, -} + } -local Center = { - fallthrough = false, - { - -- Hide the winbar for special buffers + 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({ - buftype = { 'terminal', 'nofile', 'prompt', 'help', 'quickfix' }, - filetype = { 'dap-ui', 'NvimTree', '^git.*', 'fugitive', 'dashboard' }, + filetype = { + "markdown", + "txt", + "vimwiki", + }, }) end, - init = function() - vim.opt_local.winbar = nil + 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, - }, - { - -- A special winbar for terminals - condition = function() - return conditions.buffer_matches({ buftype = { 'terminal' } }) + 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, - FileType, + } + + FileInfoBlock = utils.insert( + FileInfoBlock, Space, - --TerminalName, - }, - { - -- An inactive winbar for regular files + 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 not conditions.is_active() + return conditions.buffer_matches({ + buftype = { "nofile", "prompt", "help", "quickfix" }, + filetype = { "^git.*", "fugitive", "dashboard" }, + }) end, - --utils.surround({ "", "" }, colors.nobg, { FileIcon, { WinbarFileName, hl = { fg = colors.gray } }, FileFlags } ), - utils.surround({ '', '' }, colors.nobg, { WinbarFileNameBlock }), - }, - -- A winbar for regular files - utils.surround({ '', '' }, colors.nobg, { FileNameBlock }), -} - ---local WinBar = { Align, Center, Align } -local WinBar = { Space, Center } - --- TabLine ---local TablineBufnr = { --- provider = function(self) --- return tostring(self.bufnr) .. "." --- end, --- hl = { fg = colors.white, bold = false }, ----- hl = "Comment", ---} + 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 }, + } --- we redefine the filename component, as we probably only want the tail and not the relative path -local TablineFileName = { - provider = function(self) - -- self.filename will be defined later, just keep looking at the example! - local filename = self.filename - filename = filename == '' and 'No Name' or vim.fn.fnamemodify(filename, ':t') - return filename - end, - hl = function(self) - return { fg = colors.white, bold = self.is_active or self.is_visible, italic = true } - end, -} - -local TablineFileFlags = { - { + local WinbarFileName = { provider = function(self) - if vim.bo[self.bufnr].modified then - return ' [+] ' + 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.green }, - }, - { - provider = function(self) - if not vim.bo[self.bufnr].modifiable or vim.bo[self.bufnr].readonly then - return ' ' + 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, - hl = { fg = 'orange' }, - }, -} - -local TablineFileIcon = { - init = function(self) - local filename = self.filename - local extension = vim.fn.fnamemodify(filename, ':e') - self.icon, self.icon_color = require('nvim-web-devicons').get_icon_color(filename, extension, { default = true }) - end, - provider = function(self) - return self.icon and (' ' .. self.icon .. ' ') - end, - hl = function(self) - return { fg = self.icon_color } - end, -} - --- Here the filename block finally comes together -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' - -- why not? - --elseif not vim.api.nvim_buf_is_loaded(self.bufnr) then - --return { fg = "gray", bg = colors.bg } - else - return 'TabLineFill' - end - end, - on_click = { - callback = function(_, minwid, _, button) - if button == 'm' then -- close on mouse middle click - vim.api.nvim_buf_delete(minwid, { force = false }) + }) + + 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 - vim.api.nvim_win_set_buf(0, minwid) + 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, - minwid = function(self) - return self.bufnr + 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, - name = 'heirline_tabline_buffer_callback', - }, - --TablineBufnr, - TablineFileIcon, - TablineFileName, - TablineFileFlags, -} - --- a nice "x" button to close the buffer -local TablineCloseButton = { - condition = function(self) - return not vim.api.nvim_buf_get_option(self.bufnr, 'modified') - end, - { provider = ' ' }, - { - provider = ' ', - --hl = { fg = "red", bg = colors.bg }, - hl = { fg = colors.red }, on_click = { - callback = function(_, minwid) - vim.api.nvim_buf_delete(minwid, { force = false }) + 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_close_buffer_callback', + name = "heirline_tabline_buffer_callback", }, - }, -} - --- The final touch! -local TablineBufferBlock = utils.surround({ '', '' }, function(self) - --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 = ' ', hl = { fg = colors.white } }, -- left truncation, optional (defaults to "<") - { provider = ' ', hl = { fg = colors.white } } -- right trunctation, also optional (defaults to ...... yep, ">") --- by the way, open a lot of buffers and try clicking them ;) -) --- TabList -local Tabpage = { - provider = function(self) - return '%' .. self.tabnr .. 'T ' .. self.tabnr .. ' %T' - end, - hl = function(self) - if not self.is_active then - return 'TabLineFill' + 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 'TabLineSel' + return utils.get_highlight("TabLineFill").bg end - end, -} - -local TabpageClose = { - provider = '%999X %X', - --hl = "TabLine", - hl = { fg = colors.red, bg = colors.bg }, -} - -local TabPages = { - -- only show this component if there's 2 or more tabpages - condition = function() - return #vim.api.nvim_list_tabpages() >= 2 - end, - { - provider = '%=', - }, - utils.make_tablist(Tabpage), - TabpageClose, -} - --- TabLineOffset -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, { 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 - 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 }, - - --hl = function(self) - -- if vim.api.nvim_get_current_win() == self.winid then - -- return 'TablineSel' - -- else - -- return 'TablineFill' - -- end - --end, -} - -local TabLine = { - TabLineOffset, - BufferLine, - TabPages, -} - -require('heirline').setup({ - statusline = StatusLine, - winbar = WinBar, - tabline = TabLine, - --statuscolumn = StatusColumn -}) - --- Yep, with heirline we're driving manual! -vim.o.showtabline = 2 -vim.cmd([[au FileType * if index(['wipe', 'delete', 'unload'], &bufhidden) >= 0 | set nobuflisted | endif]]) - -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 + vim.api.nvim_win_set_buf(0, bufs[index]) + end -local function goto_buf(index) - local bufs = get_bufs() - if index > #bufs then - index = #bufs + local function add_key(key, index) + vim.keymap.set("n", "<A-" .. key .. ">", function() + goto_buf(index) + end, { noremap = true, silent = true }) end - vim.api.nvim_win_set_buf(0, bufs[index]) -end -local function addKey(key, index) - vim.keymap.set('', '<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, + } + }) -for i = 1, 9 do - addKey(i, i) end -addKey('0', 10) + +return M diff --git a/common/config/nvim/lua/plugins/indent-blankline.lua b/common/config/nvim/lua/plugins/indent-blankline.lua index 25a2da0..cbbcf27 100644..100755 --- a/common/config/nvim/lua/plugins/indent-blankline.lua +++ b/common/config/nvim/lua/plugins/indent-blankline.lua @@ -1,24 +1,73 @@ ---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) +local M = {} -require("ibl").setup({ indent = { highlight = highlight } }) +--- 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/config/nvim/lua/plugins/interestingwords.lua b/common/config/nvim/lua/plugins/interestingwords.lua new file mode 100755 index 0000000..655ed42 --- /dev/null +++ b/common/config/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/config/nvim/lua/plugins/leetcode.lua b/common/config/nvim/lua/plugins/leetcode.lua index 50369e1..50369e1 100644..100755 --- a/common/config/nvim/lua/plugins/leetcode.lua +++ b/common/config/nvim/lua/plugins/leetcode.lua diff --git a/common/config/nvim/lua/plugins/loclist.lua b/common/config/nvim/lua/plugins/loclist.lua index 9b72a94..9b72a94 100644..100755 --- a/common/config/nvim/lua/plugins/loclist.lua +++ b/common/config/nvim/lua/plugins/loclist.lua diff --git a/common/config/nvim/lua/plugins/lsp.lua b/common/config/nvim/lua/plugins/lsp.lua index 286e0d2..5ed1152 100644..100755 --- a/common/config/nvim/lua/plugins/lsp.lua +++ b/common/config/nvim/lua/plugins/lsp.lua @@ -1,448 +1,674 @@ -local lspconfig = require("lspconfig") -local mason_lspconfig = require("mason-lspconfig") -local null_ls = require("null-ls") --- local lsp_lines = require('lsp_lines') -require("mason").setup() -require("mason-null-ls").setup({ handlers = {}, ensure_installed = nil, automatic_installation = true, - automatic_setup = true }) - -local keymap = vim.keymap -local cmd = vim.cmd - -local border = { { "┌", "FloatBorder" }, { "─", "FloatBorder" }, { "┐", "FloatBorder" }, { "│", "FloatBorder" }, - { "┘", "FloatBorder" }, { "─", "FloatBorder" }, { "└", "FloatBorder" }, { "│", "FloatBorder" } } - --- Set up LSP servers if not done before -if not vim.g.lsp_setup_done then - -- Clear existing LSP clients - for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do - local clients = require("user.mods").get_lsp_clients(bufnr) - - for _, client in ipairs(clients) do - client.stop() - end +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 - local signs = { Error = " ", Warn = "▲", Info = "", Hint = "⚑" } - -- - for type, icon in pairs(signs) do - local hl = "DiagnosticSign" .. type - vim.fn.sign_define(hl, { text = icon, texthl = hl, numhl = hl }) + 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 - -- lsp_lines.setup() - - -- vim.keymap.set("n", "g?", function() - -- local lines_enabled = not vim.diagnostic.config().virtual_lines - -- vim.diagnostic.config( - -- { - -- virtual_lines = lines_enabled, - -- virtual_text = not lines_enabled - -- } - -- ) - -- end, { noremap = true, silent = true }) - - vim.diagnostic.config({ - underline = false, - signs = true, - virtual_text = true, -- virtual_lines = { only_current_line = true }, - virtual_lines = false, - float = { - show_header = true, - source = "if_many", -- border = 'rounded', - border = border, - focusable = true, - }, - update_in_insert = false, -- default to false - severity_sort = true, -- default to false - }) +-- Backwards compatible capabilities setup +local function setup_capabilities() + local capabilities - vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, - { underline = false, virtual_text = false, signs = true, update_in_insert = false }) - - 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" }) - - -- Use an on_attach function to only map the following keys after the language server attaches to the current buffer - local on_attach = function(client, bufnr) - -- Enable completion triggered by <c-x><c-o> - vim.api.nvim_buf_set_option(bufnr, "omnifunc", "v:lua.vim.lsp.omnifunc") - local map = function(mode, l, r, opts) - opts = opts or {} - opts.silent = true - opts.noremap = true - opts.buffer = bufnr - keymap.set(mode, l, r, opts) - end - -- Mappings - map("n", "K", "<Cmd>lua vim.lsp.buf.hover()<CR>") - -- map("n", "gd", "<Cmd>lua vim.lsp.buf.definition()<CR>") - map("n", "gd", "<cmd>lua require('goto-preview').goto_preview_definition()<CR>") - -- map("n", "gi", "<Cmd>lua vim.lsp.buf.implementation()<CR>") - map("n", "gi", "<cmd>lua require('goto-preview').goto_preview_implementation()<CR>") - -- map("n", "gr", "<Cmd>lua vim.lsp.buf.references()<CR>") - map("n", "gr", "<cmd>lua require('goto-preview').goto_preview_references()<CR>") - map("n", "gD", "<Cmd>lua vim.lsp.buf.declaration()<CR>") -- most lsp servers don't implement textDocument/Declaration, so gD is useless for now. - map("n", "<leader>k", "<Cmd>lua vim.lsp.buf.signature_help()<CR>") - -- map("n", "gt", "<Cmd>lua vim.lsp.buf.type_definition()<CR>") - map("n", "gt", "<cmd>lua require('goto-preview').goto_preview_type_definition()<CR>") - map("n", "gn", "<Cmd>lua vim.lsp.buf.rename()<CR>") - map("n", "ga", "<Cmd>lua vim.lsp.buf.code_action()<CR>") - map("n", "gf", "<Cmd>lua vim.lsp.buf.format()<CR>") - map("n", "go", "<Cmd>lua vim.diagnostic.open_float()<CR>") - map("n", "<leader>go", - ":call utils#ToggleDiagnosticsOpenFloat()<CR> | :echom ('Toggle Diagnostics Float open/close...')<CR> | :sl! | echo ('')<CR>") - map("n", "gq", "<Cmd>lua vim.diagnostic.setloclist()<CR>") - map("n", "[d", "<Cmd>lua vim.diagnostic.goto_prev()<CR>") - map("n", "]d", "<Cmd>lua vim.diagnostic.goto_next()<CR>") - map("n", "gs", "<Cmd>lua vim.lsp.buf.document_symbol()<CR>") - map("n", "gw", "<Cmd>lua vim.lsp.buf.workspace_symbol()<CR>") - map("n", "<leader>wa", "<Cmd>lua vim.lsp.buf.add_workspace_folder()<CR>") - map("n", "<leader>wr", "<Cmd>lua vim.lsp.buf.remove_workspace_folder()<CR>") - map("n", "<leader>wl", function() - print(vim.inspect(vim.lsp.buf.list_workspace_folders())) - end) - - -- TODO: Use the nicer new API for autocommands - cmd("augroup lsp_aucmds") - if client.server_capabilities.documentHighlightProvider then - cmd("au CursorHold <buffer> lua vim.lsp.buf.document_highlight()") - cmd("au CursorMoved <buffer> lua vim.lsp.buf.clear_references()") - end - cmd("augroup END") + 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 - -- Toggle diagnostics visibility - vim.g.diagnostics_visible = true - function _G.toggle_diagnostics() - if vim.g.diagnostics_visible then - vim.g.diagnostics_visible = false - vim.diagnostic.disable() - else - vim.g.diagnostics_visible = true - vim.diagnostic.enable() - 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 - -- Open float for diagnostics automatically - vim.cmd([[ - augroup OpenFloat - " autocmd CursorHold,CursorHoldI * lua vim.diagnostic.open_float(nil, {focusable = false,}) - autocmd CursorHold * lua vim.diagnostic.open_float(nil, {focusable = false,}) - - augroup END - ]]) - - -- Suppress error messages from lang servers - vim.lsp.set_log_level("debug") - local capabilities = vim.lsp.protocol.make_client_capabilities() - capabilities = require("cmp_nvim_lsp").default_capabilities() - capabilities.textDocument.completion.completionItem.snippetSupport = true - capabilities.offsetEncoding = { "utf-8", "utf-16" } - - local function prefer_null_ls_fmt(client) - client.server_capabilities.documentHighlightProvider = true - client.server_capabilities.documentFormattingProvider = true - on_attach(client) + -- 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 - --local cmp_nvim_lsp = require('cmp_nvim_lsp') - local servers = { - asm_lsp = {}, - bashls = {}, - clangd = { - on_attach = on_attach, - capabilites = capabilities, - cmd = { "clangd", "--offset-encoding=utf-16", "--cross-file-rename", "--header-insertion=never", - "--suggest-missing-includes" }, - init_options = { - clangdFileStatus = true, - }, - root_files = { - ".clangd", - ".clang-tidy", - ".clang-format", - "compile_commands.json", - "compile_flags.txt", - "configure.ac", -- AutoTools + 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, + }, + }, }, }, - cssls = { filetypes = { "css", "scss", "less", "sass" }, - root_dir = lspconfig.util.root_pattern("package.json", ".git") }, -- ghcide = {}, - html = {}, - jsonls = { prefer_null_ls = true, cmd = { "--stdio" } }, - intelephense = {}, - julials = { - on_new_config = function(new_config, _) - local julia = vim.fn.expand("~/.julia/environments/nvim-lspconfig/bin/julia") - if lspconfig.util.path.is_file(julia) then - new_config.cmd[1] = julia - end - end, - settings = { julia = { format = { indent = 2 } } }, - }, - pyright = { settings = { python = { formatting = { provider = "yapf" }, linting = { pytypeEnabled = true } } } }, - rust_analyzer = { + + pyright = { + cmd = { "pyright-langserver", "--stdio" }, + filetypes = { "python" }, + root_markers = { "pyproject.toml", "setup.py", "setup.cfg", "requirements.txt", "Pipfile", "pyrightconfig.json" }, settings = { - ["rust-analyzer"] = { cargo = { allFeatures = true }, checkOnSave = { command = "clippy", - extraArgs = { "--no-deps" } } }, - }, + python = { + formatting = { + provider = "none" + } + } + } }, - dartls = { - cmd = { "dart", "language-server", "--protocol=lsp" }, - filetypes = { "dart" }, + + 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 = { - closingLabels = true, - flutterOutline = true, - onlyAnalyzeProjectsWithOpenFiles = true, - outline = true, - suggestFromUnimportedLibraries = true, - }, -- root_dir = root_pattern("pubspec.yaml"), - settings = { dart = { completeFunctionCalls = true, showTodos = true } }, - on_attach = function(client, bufnr) end, + disableAutomaticTypeAcquisition = true + }, }, - lua_ls = { - on_attach = on_attach, - capabilities = capabilities, - debounce_text_changes = 500, + + 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 = { - Lua = { - runtime = { version = "LuaJIT", path = vim.split(package.path, ";") }, - diagnostics = { enable = true, globals = { "vim" } }, - workspace = { maxPreload = 2000, preloadFileSize = 50000, checkThirdParty = false }, + 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, }, }, }, - sqlls = {}, - tsserver = { - capabilities = require("cmp_nvim_lsp").default_capabilities(vim.lsp.protocol.make_client_capabilities()), - on_attach = function(client) - client.server_capabilities.document_formatting = false - client.server_capabilities.document_range_formatting = false - end, - filetypes = { "javascript", "javascriptreact", "javascript.jsx", "typescript", "typescriptreact", "typescript.tsx" }, + + -- Add more basic configs + bashls = { + cmd = { "bash-language-server", "start" }, + filetypes = { "sh", "bash" }, }, - vimls = {}, - yamlls = {}, - } - mason_lspconfig.setup({ - ensure_installed = servers, -- will be installed by mason - automatic_installation = true, - }) + --html = { + -- cmd = { "vscode-html-language-server", "--stdio" }, + -- filetypes = { "html" }, + --}, - -- Your other configurations ... - -- require("lspconfig").dartls.setup({ capabilities = capabilities }) - -- local installed_lsp = mason_lspconfig.ensure_installed - -- local mason_lspconfig = require("mason-lspconfig").ensure_installed - - -- require("lspconfig").setup({ - -- function() - -- for _, lsp in ipairs(installed_lsp) do - -- if - -- lsp ~= "sqls" - -- --and lsp ~= "sumneko_lua" - -- --and lsp ~= "stylelint_lsp" - -- --and lsp ~= "rust_analyzer" - -- --and lsp ~= "sourcekit" - -- and lsp ~= "dartls" - -- then - -- lspconfig[lsp].setup({ - -- on_attach = on_attach, - -- capabilities = capabilities, - -- }) - -- end - -- end - -- end, - -- }) - - for server, config in pairs(servers) do - if config.prefer_null_ls then - if config.on_attach then - local old_on_attach = config.on_attach - config.on_attach = function(client, bufnr) - old_on_attach(client, bufnr) - prefer_null_ls_fmt(client) - end - else - config.on_attach = prefer_null_ls_fmt + --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 - elseif not config.on_attach then - config.on_attach = on_attach 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 - lspconfig[server].setup(config) +-- 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 - -- null_ls setup - local builtins = null_ls.builtins - local augroup = vim.api.nvim_create_augroup("LspFormatting", {}) - - -- local eslint_opts = { - -- -- condition = function(utils) - -- -- return utils.root_has_file ".eslintrc.js" or utils.root_has_file ".eslintrc" or utils.root_has_file ".eslintrc.json" - -- -- end, - -- -- diagnostics_format = "#{m} [#{c}]", - -- prefer_local = true, - -- } - - -- null_ls.setup({ - local sources = { - -- Diagnostics - builtins.diagnostics.chktex, - -- null_ls.builtins.code_actions.eslint_d, - -- null_ls.builtins.diagnostics.eslint_d, - -- null_ls.builtins.formatting.eslint_d, - -- null_ls.builtins.diagnostics.cppcheck, - -- null_ls.builtins.diagnostics.proselint, - -- null_ls.builtins.diagnostics.pylint, - -- builtins.diagnostics.selene, - builtins.diagnostics.dotenv_linter, - builtins.diagnostics.shellcheck.with({ -- shell script diagnostics - diagnostic_config = { -- see :help vim.diagnostic.config() - underline = true, - virtual_text = false, - signs = true, - update_in_insert = false, - severity_sort = true, + -- 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" }, - diagnostics_format = "[#{c}] #{m} (#{s})", -- this will run every time the source runs, - -- so you should prefer caching results if possible - }), - builtins.diagnostics.zsh.with({ filetypes = "zsh", "sh" }), - builtins.diagnostics.todo_comments, - builtins.diagnostics.teal, - -- null_ls.builtins.diagnostics.vale, - builtins.diagnostics.vint, - builtins.diagnostics.tidy, - builtins.diagnostics.php, - builtins.diagnostics.phpcs, - builtins.diagnostics.flake8, - builtins.diagnostics.eslint_d.with({ - condition = function(utils) - return utils.root_has_file(".eslintrc.json") + 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, - }), - builtins.formatting.eslint_d, - -- null_ls.builtins.diagnostics.write_good.with { filetypes = { 'markdown', 'tex' } }, - - -- Formatting - builtins.formatting.shfmt.with({ filetypes = { "bash", "zsh", "sh" }, extra_args = { "-i", "2", "-ci" } }), - builtins.formatting.shellharden, - builtins.formatting.trim_whitespace.with({ filetypes = { "tmux", "teal", "zsh" } }), -- builtins.formatting.beautysh, - builtins.formatting.beautysh.with({ filetypes = "zsh" }), - builtins.formatting.clang_format.with({ - filetypes = { "c", "cpp", "cs", "java", "cuda", "proto" }, - extra_args = { - "--style", - "{BasedOnStyle: Google, IndentWidth: 4, BreakBeforeBinaryOperators: NonAssignment, AllowShortFunctionsOnASingleLine: None}", - }, - }), - --builtins.formatting.rustfmt, - builtins.formatting.sql_formatter, - -- null_ls.builtins.formatting.cmake_format, - builtins.formatting.isort, - builtins.formatting.htmlbeautifier, -- null_ls.builtins.formatting.prettier, - builtins.formatting.prettierd, - builtins.formatting.prettier.with({ - filetypes = { "javascript", "javascriptreact", "typescript", "typescriptreact", "json", "yaml", "markdown", "html", - "css", "scss", "less", "graphql", "vue", "svelte" }, - extra_args = { "--single-quote", "--tab-width 4", "--print-width 200" }, - }), - -- builtins.formatting.stylua, - -- builtins.formatting.lua_format, - builtins.formatting.stylua.with({ - filetypes = { "lua" }, - command = "stylua", - args = { "--quote_style", "AutoPreferSingle", "--indent-width", "2", "--column-width", "160", "--indent-type", - "Spaces", "-" }, - }), - -- builtins.formatting.dart_format, - builtins.formatting.dart_format.with({ filetypes = { "dart" } }), - builtins.formatting.trim_whitespace, - builtins.formatting.yapf, - -- null_ls.builtins.formatting.black - - -- Code Actions - builtins.code_actions.shellcheck, -- shell script code actions - -- builtins.code_actions.eslint_d.with(eslint_opts), - -- null_ls.builtins.code_actions.refactoring.with { filetypes = { 'javascript', 'typescript', 'lua', 'python', 'c', 'cpp' } }, - builtins.code_actions.gitsigns, - builtins.code_actions.gitrebase, -- Hover - builtins.hover.dictionary, - builtins.hover.printenv, - } - -- }) - -- Linters/Formatters ensure installed - -- for _, pkg_name in ipairs({ - -- "dart-debug-Adaptor", - -- "stylua", - -- "prettier", - -- "prettierd", - -- }) do - - -- Import the builtins table from the null-ls module and store it in the null_ls_sources variable - null_ls.setup({ - sources = sources, - update_in_insert = true, - on_attach = function(client, bufnr) - if client.supports_method("textDocument/formatting") then - vim.api.nvim_clear_autocmds({ group = augroup, buffer = bufnr }) - vim.api.nvim_create_autocmd("BufWritePre", { - group = augroup, - buffer = bufnr, - callback = function() - vim.lsp.buf.format() - end, - }) - end - end, - }) + }) + end - -- Install all the null-ls sources using Mason - local registry = require("mason-registry") - for _, source_name in ipairs(sources) do - local ok, pkg = pcall(registry.get_package, source_name) - if ok then - if not pkg:is_installed() then - pkg:install() - 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 - -- Loop through the null_ls_sources table and install the packages - -- Install all sources for null-ls - -- local null_ls_sources = require("null-ls").builtins - - -- for _, source_name in ipairs(null_ls_sources) do - -- local ok, pkg = pcall(mason.get_package, source_name) - -- if ok then - -- if not pkg:is_installed() then - -- pkg:install() - -- end - -- end - -- end - vim.api.nvim_create_user_command("NullLsToggle", function() - -- you can also create commands to disable or enable sources - require("null-ls").toggle({}) - end, {}) - local null_ls_stop = function() - local null_ls_client - local clients = require("user.mods").get_lsp_clients(bufnr) + -- 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() - for _, client in ipairs(clients) do - if client.name == "null-ls" then - null_ls_client = client - end + -- 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 - if not null_ls_client then - return + + -- 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 - null_ls_client.stop() + 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 - vim.api.nvim_create_user_command("NullLsStop", null_ls_stop, {}) +-- 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 - vim.g.lsp_setup_done = true +-- 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/config/nvim/lua/plugins/lualine.lua b/common/config/nvim/lua/plugins/lualine.lua new file mode 100755 index 0000000..9c1cc43 --- /dev/null +++ b/common/config/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/config/nvim/lua/plugins/luasnip.lua b/common/config/nvim/lua/plugins/luasnip.lua new file mode 100755 index 0000000..75f4c28 --- /dev/null +++ b/common/config/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/config/nvim/lua/plugins/messages.lua b/common/config/nvim/lua/plugins/messages.lua index 8e46c09..8e46c09 100644..100755 --- a/common/config/nvim/lua/plugins/messages.lua +++ b/common/config/nvim/lua/plugins/messages.lua diff --git a/common/config/nvim/lua/plugins/modify-blend.lua b/common/config/nvim/lua/plugins/modify-blend.lua index 1b2c6d5..1b2c6d5 100644..100755 --- a/common/config/nvim/lua/plugins/modify-blend.lua +++ b/common/config/nvim/lua/plugins/modify-blend.lua diff --git a/common/config/nvim/lua/plugins/navic.lua b/common/config/nvim/lua/plugins/navic.lua index a95485d..a574d5c 100644..100755 --- a/common/config/nvim/lua/plugins/navic.lua +++ b/common/config/nvim/lua/plugins/navic.lua @@ -1,50 +1,51 @@ -local navic = require("nvim-navic") ---local on_attach = function(client, bufnr) --- if client.server_capabilities.documentSymbolProvider then --- navic.attach(client, bufnr) --- end ---end +local M = {} ---require("lspconfig").clangd.setup { --- on_attach = on_attach ---} +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 = " ", - }, - lsp = { - auto_attach = true, - --preference = nil, - }, - highlight = false, - separator = " > ", - depth_limit = 0, - depth_limit_indicator = "..", - safe_output = true -} + 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/config/nvim/lua/plugins/neodev.lua b/common/config/nvim/lua/plugins/neodev.lua index f820422..07843e1 100644..100755 --- a/common/config/nvim/lua/plugins/neodev.lua +++ b/common/config/nvim/lua/plugins/neodev.lua @@ -1,4 +1,15 @@ -require("neodev").setup({ +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 = { @@ -23,7 +34,12 @@ require("neodev").setup({ -- 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, -}) + -- 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/config/nvim/lua/plugins/neoscroll.lua b/common/config/nvim/lua/plugins/neoscroll.lua index d122584..f2ecb04 100644..100755 --- a/common/config/nvim/lua/plugins/neoscroll.lua +++ b/common/config/nvim/lua/plugins/neoscroll.lua @@ -1,21 +1,22 @@ -require("neoscroll").setup({ - easing_function = "quadratic", -}) +local M = {} -local t = {} --- Syntax: t[keys] = {function, {function arguments}} --- Use the "sine" easing function -t["<C-u>"] = { "scroll", { "-vim.wo.scroll", "true", "20", [['cubic']] } } -t["<C-d>"] = { "scroll", { "vim.wo.scroll", "true", "20", [['cubic']] } } --- Use the "circular" easing function -t["<C-b>"] = { "scroll", { "-vim.api.nvim_win_get_height(0)", "true", "50", [['cubic']] } } -t["<C-f>"] = { "scroll", { "vim.api.nvim_win_get_height(0)", "true", "50", [['cubic']] } } --- Pass "nil" to disable the easing animation (constant scrolling speed) -t["<C-y>"] = { "scroll", { "-0.10", "false", "100", nil } } -t["<C-e>"] = { "scroll", { "0.10", "false", "100", nil } } --- When no easing function is provided the default easing function (in this case "quadratic") will be used -t["zt"] = { "zt", { "10" } } -t["zz"] = { "zz", { "10" } } -t["zb"] = { "zb", { "10" } } +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 -require("neoscroll.config").set_mappings(t) +return M diff --git a/common/config/nvim/lua/plugins/neotest.lua b/common/config/nvim/lua/plugins/neotest.lua index aa73899..1034d33 100644..100755 --- a/common/config/nvim/lua/plugins/neotest.lua +++ b/common/config/nvim/lua/plugins/neotest.lua @@ -1,11 +1,38 @@ -require("neotest").setup({ - adapters = { - require("neotest-python")({ +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 }, - }), - require("neotest-plenary"), - require("neotest-vim-test")({ + })) + 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/config/nvim/lua/plugins/notify.lua b/common/config/nvim/lua/plugins/notify.lua index dcb496a..62a8f47 100644..100755 --- a/common/config/nvim/lua/plugins/notify.lua +++ b/common/config/nvim/lua/plugins/notify.lua @@ -1,18 +1,36 @@ -require('notify').setup({ - background_colour = '#000000', - icons = { - ERROR = '', - WARN = '', - INFO = '', - DEBUG = '', - TRACE = '✎', - }, -}) +local M = {} -vim.api.nvim_command('hi default link NotifyERRORBody Normal') -vim.api.nvim_command('hi default link NotifyWARNBody Normal') -vim.api.nvim_command('hi default link NotifyINFOBody Normal') -vim.api.nvim_command('hi default link NotifyDEBUGBody Normal') -vim.api.nvim_command('hi default link NotifyTRACEBody Normal') -vim.api.nvim_command('hi default link NotifyLogTime Comment') -vim.api.nvim_command('hi default link NotifyLogTitle Special') +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/config/nvim/lua/plugins/nvim-tree.lua b/common/config/nvim/lua/plugins/nvim-tree.lua index e817c04..a212eab 100644..100755 --- a/common/config/nvim/lua/plugins/nvim-tree.lua +++ b/common/config/nvim/lua/plugins/nvim-tree.lua @@ -1,13 +1,126 @@ ------------------------------------------------------------ --- Neovim File Tree Configuration ------------------------------------------------------------ +local M = {} ---- To see mappings `g?` on nvim-tree ---- To see default mappings `:nvim-tree-default-mappings` +-- 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" }, --- Nvim-Tree.lua advises to do this at the start. -vim.g.loaded_netrw = 1 -vim.g.loaded_netrwPlugin = 1 + -- 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, @@ -39,7 +152,7 @@ local icons = { git = { deleted = "", unmerged = "", - untracked = "", + untracked = "", unstaged = "", staged = "", renamed = "➜", @@ -54,6 +167,28 @@ local icons = { }, } +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, @@ -75,33 +210,11 @@ local renderer = { }, icons = icons, } + local system_open = { cmd = "zathura" } local HEIGHT_RATIO = 0.8 local WIDTH_RATIO = 0.15 - -local float = { - enable = false, - open_win_config = function() - local screen_w = vim.opt.columns:get() - local screen_h = vim.opt.lines:get() - vim.opt.cmdheight:get() - 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.opt.lines:get() - window_h) / 2) - vim.opt.cmdheight:get() - return { - border = "rounded", - relative = "editor", - row = center_y, - col = center_x, - width = window_w_int, - height = window_h_int, - } - end, -} - local view = { cursorline = true, float = float, @@ -113,311 +226,254 @@ local view = { side = "left", } -local api = require("nvim-tree.api") -local function on_attach(bufnr) - local function opts(desc) - return { desc = "nvim-tree: " .. desc, buffer = bufnr, noremap = true, silent = true, nowait = true } +-- 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 - 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" }, + -- change to the directory + vim.cmd.cd(data.file) - -- 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 + -- open the tree + require("nvim-tree.api").tree.open() end ---api.events.subscribe(api.events.Event.FileCreated, function(file) --- vim.cmd('edit' .. file.fname) ---end) - -require("nvim-tree").setup({ - --auto_reload_on_write = true, - --create_in_closed_folder = false, - --hijack_cursor = true, - --disable_netrw = true, - --hijack_netrw = true, - --hijack_unnamed_buffer_when_opening = false, - --ignore_buffer_on_setup = false, - update_focused_file = { - enable = true, + + +-- 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, - update_root = true, - ignore_list = {}, - }, - root_dirs = {}, - --prefer_startup_root = true, - --hijack_directories = { - -- enable = false, - --}, - --respect_buf_cwd = false, - sync_root_with_cwd = true, - --reload_on_bufenter = false, - filesystem_watchers = { - enable = true, - debounce_delay = 50, - ignore_dirs = { "node_modules", ".config/nvm" }, - }, - view = view, - system_open = system_open, - renderer = renderer, - on_attach = on_attach, - notify = { - threshold = vim.log.levels.ERROR, - }, - log = { - enable = true, - truncate = true, - types = { - diagnostics = true, - git = true, - profile = true, - watcher = true, + hijack_directories = { + enable = true, + auto_open = true, }, - }, - git = { ignore = false }, - diagnostics = { - enable = true, - show_on_dirs = true, - icons = { - hint = "⚑", - info = "", - warning = "▲", - error = "", + diagnostics = { + enable = true, + icons = { + error = "✘", + warning = "", + hint = "◉", + info = "", + }, }, - }, - trash = { - cmd = "gio trash", - require_confirm = true, - }, - modified = { - enable = true, - show_on_dirs = true, - show_on_open_dirs = true, - }, - --filters = { - -- dotfiles = false, - -- git_clean = false, - -- no_buffer = false, - -- custom = {}, - -- exclude = {}, - --}, - actions = { - use_system_clipboard = true, - change_dir = { + filesystem_watchers = { + enable = true, + debounce_delay = 50, + ignore_dirs = { "node_modules", ".config/nvm" }, + }, + update_focused_file = { enable = true, - global = false, - restrict_above_cwd = false, + update_cwd = true, + --update_root = true, + ignore_list = {}, + }, + --root_dirs = {}, + --system_open = { + -- --cmd = nil, + -- --args = {}, + --}, + system_open = system_open, + filters = { + dotfiles = false, + custom = {}, }, - remove_file = { - close_window = true, + --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, + }, }, - open_file = { - quit_on_open = true, - --eject = true, - resize_window = false, - window_picker = { + 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, - chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", - exclude = { - filetype = { "notify", "packer", "qf", "diff", "fugitive", "fugitiveblame" }, - buftype = { "nofile", "terminal", "help" }, + 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 -vim.api.nvim_create_autocmd({ "VimEnter" }, { 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 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 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 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 -local function edit_and_close(node) - api.node.open.edit(node, {}) - api.tree.close() + --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 ---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:append("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, -}) +---- 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 -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 NvimTreeSymlink ") ---vim.api.nvim_command("highlight NvimTreeSymlinkFolderName ") --(Directory) ---vim.api.nvim_command("highlight NvimTreeFolderName ") --(Directory) ---vim.api.nvim_command("highlight NvimTreeRootFolder ") ---vim.api.nvim_command("highlight NvimTreeFolderIcon ") ---vim.api.nvim_command("highlight NvimTreeOpenedFolderIcon ") --(NvimTreeFolderIcon) ---vim.api.nvim_command("highlight NvimTreeClosedFolderIcon ") --(NvimTreeFolderIcon) ---vim.api.nvim_command("highlight NvimTreeFileIcon ") ---vim.api.nvim_command("highlight NvimTreeEmptyFolderName ") --(Directory) ---vim.api.nvim_command("highlight NvimTreeOpenedFolderName ") --(Directory) ---vim.api.nvim_command("highlight NvimTreeExecFile ") -vim.api.nvim_command("highlight NvimTreeExecFile guifg=#ff882a guibg=none gui=NONE") ---vim.api.nvim_command("highlight NvimTreeOpenedFile ") ---vim.api.nvim_command("highlight NvimTreeModifiedFile ") ---vim.api.nvim_command("highlight NvimTreeSpecialFile ") ---vim.api.nvim_command("highlight NvimTreeImageFile ") ---vim.api.nvim_command("highlight NvimTreeIndentMarker ") ---vim.api.nvim_command("highlight NvimTreeLspDiagnosticsError ") --(DiagnosticError) ---vim.api.nvim_command("highlight NvimTreeLspDiagnosticsWarning ") --(DiagnosticWarn) ---vim.api.nvim_command("highlight NvimTreeLspDiagnosticsInformation ") --(DiagnosticInfo) ---vim.api.nvim_command("highlight NvimTreeLspDiagnosticsHint ") --(DiagnosticHint) ---vim.api.nvim_command("highlight NvimTreeGitDirty ") ---vim.api.nvim_command("highlight NvimTreeGitStaged ") ---vim.api.nvim_command("highlight NvimTreeGitMerge ") ---vim.api.nvim_command("highlight NvimTreeGitRenamed ") ---vim.api.nvim_command("highlight NvimTreeGitNew ") ---vim.api.nvim_command("highlight NvimTreeGitDeleted ") ---vim.api.nvim_command("highlight NvimTreeGitIgnored ") --(Comment) ---vim.api.nvim_command("highlight NvimTreeNormal ") ---vim.api.nvim_command("highlight NvimTreeEndOfBuffer ") --(NonText) ---vim.api.nvim_command("highlight NvimTreeCursorColumn ") --(CursorColumn) ---vim.api.nvim_command("highlight NvimTreeFileDirty ") --(NvimTreeGitDirty) ---vim.api.nvim_command("highlight NvimTreeFileStaged ") --(NvimTreeGitStaged) ---vim.api.nvim_command("highlight NvimTreeFileMerge ") --(NvimTreeGitMerge) ---vim.api.nvim_command("highlight NvimTreeFileRenamed ") --(NvimTreeGitRenamed) ---vim.api.nvim_command("highlight NvimTreeFileNew ") --(NvimTreeGitNew) ---vim.api.nvim_command("highlight NvimTreeFileDeleted ") --(NvimTreeGitDeleted) ---vim.api.nvim_command("highlight NvimTreeFileIgnored ") --(NvimTreeGitIgnored) ---vim.api.nvim_command("highlight NvimTreeLiveFilterPrefix ") ---vim.api.nvim_command("highlight NvimTreeLiveFilterValue ") ---vim.api.nvim_command("highlight NvimTreeBookmark ") + +return M diff --git a/common/config/nvim/lua/plugins/overseer.lua b/common/config/nvim/lua/plugins/overseer.lua index 6319d36..593d094 100644..100755 --- a/common/config/nvim/lua/plugins/overseer.lua +++ b/common/config/nvim/lua/plugins/overseer.lua @@ -1 +1,14 @@ -require('overseer').setup() +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/config/nvim/lua/plugins/plenary.lua b/common/config/nvim/lua/plugins/plenary.lua new file mode 100755 index 0000000..f572244 --- /dev/null +++ b/common/config/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/config/nvim/lua/plugins/prettier.lua b/common/config/nvim/lua/plugins/prettier.lua new file mode 100755 index 0000000..ca57ea9 --- /dev/null +++ b/common/config/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/config/nvim/lua/plugins/quickfix.lua b/common/config/nvim/lua/plugins/quickfix.lua index 4a76da0..4a76da0 100644..100755 --- a/common/config/nvim/lua/plugins/quickfix.lua +++ b/common/config/nvim/lua/plugins/quickfix.lua diff --git a/common/config/nvim/lua/plugins/session.lua b/common/config/nvim/lua/plugins/session.lua deleted file mode 100644 index 0c84625..0000000 --- a/common/config/nvim/lua/plugins/session.lua +++ /dev/null @@ -1,5 +0,0 @@ -require('auto-session').setup({ - log_level = 'error', - auto_session_suppress_dirs = { '~/', '~/projects', '~/downloads', '/' }, -}) -require('session-lens').setup({}) diff --git a/common/config/nvim/lua/plugins/snippets.lua b/common/config/nvim/lua/plugins/snippets.lua index 7df6b2f..989ad8a 100644..100755 --- a/common/config/nvim/lua/plugins/snippets.lua +++ b/common/config/nvim/lua/plugins/snippets.lua @@ -1,68 +1,33 @@ -local ls = require "luasnip" -- +local M = {} -require("luasnip.loaders.from_lua").load({ paths = "~/.config/nvim/snippets/" }) ---local options = { -ls.config.set_config { - history = true, - updateevents = "TextChanged,TextChangedI", - -- Autosnippets: - enable_autosnippets = true, -- - region_check_events = "InsertEnter", - delete_check_events = "TextChanged", - update_events = "TextChanged,TextChangedI", - store_selection_keys = "<Tab>", - ext_opts = { -- - [require("luasnip.util.types").choiceNode] = { - active = { - virt_text = { { "«", "GruvboxOrange" } }, - }, - }, - }, -} - ---local keymap = vim.keymap ---local keymap = vim.api.nvim_set_keymap -local keymap = vim.keymap.set ---keymap('i', '<c-f>', 'luasnip#expand_or_jumpable() ? "<Plug>luasnip-expand-or-jump" : "<Tab>"', {expr = true, silent = true}) -keymap({ "i", "s"}, "<c-f>", function() - if ls.expand_or_jumpable() then - ls.expand() - end -end) - -keymap({ "i", "s"}, "<c-j>", function() - if ls.jumpable(1) then - ls.jump(1) +function M.setup() + local ok, ls = pcall(require, "luasnip") + if not ok or not ls then + return false end -end) -keymap({ "i", "s"}, "<c-k>", function() - if ls.jumpable(-1) then - ls.jump(-1) - end -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) -keymap({ "i", "s"}, "<c-c>", function() - if ls.choice_active() then - ls.change_choice(1) - end -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" } }, + }, + }, + }, + } -keymap({ "i", "s"}, "<a-c>", function() - if ls.choice_active() then - ls.change_choice(-1) - end -end) + return true +end --- Character class Matching --- %a letters (A-Z, a-z) --- %c control characters (\n, \t, \r, ...) --- %d digits (0-9) --- %l lower-case letter (a-z) --- %p punctuation characters (!, ?, &, ...) --- %s space characters --- %u upper-case letters --- %w alphanumeric characters (A-Z, a-z, 0-9) --- %x hexadecimal digits (\3, \4, ...) --- %z the character with representation 0 --- . Matches any character +return M diff --git a/common/config/nvim/lua/plugins/sniprun.lua b/common/config/nvim/lua/plugins/sniprun.lua index 418e8cc..418e8cc 100644..100755 --- a/common/config/nvim/lua/plugins/sniprun.lua +++ b/common/config/nvim/lua/plugins/sniprun.lua diff --git a/common/config/nvim/lua/plugins/statuscol.lua b/common/config/nvim/lua/plugins/statuscol.lua index 24a2308..c538790 100644..100755 --- a/common/config/nvim/lua/plugins/statuscol.lua +++ b/common/config/nvim/lua/plugins/statuscol.lua @@ -1,13 +1,17 @@ -local status, statuscol = pcall(require, "statuscol") +local M = {} -if not status then - vim.notify("statuscol not found") - return -end +function M.setup() + local ok, statuscol = pcall(require, "statuscol") + if not ok or not statuscol then + return false + end -local builtin = require("statuscol.builtin") + local builtin_ok, builtin = pcall(require, "statuscol.builtin") + if not builtin_ok or not builtin then + return false + end -statuscol.setup({ + statuscol.setup({ segments = { { text = { builtin.lnumfunc }, click = "v:lua.ScLa" }, { text = { "%s" }, click = "v:lua.ScSa" }, @@ -25,4 +29,9 @@ statuscol.setup({ "dapui_console", "dapui_repl", }, -}) + }) + + return true +end + +return M diff --git a/common/config/nvim/lua/plugins/surround.lua b/common/config/nvim/lua/plugins/surround.lua index 04def1b..71023c7 100644..100755 --- a/common/config/nvim/lua/plugins/surround.lua +++ b/common/config/nvim/lua/plugins/surround.lua @@ -1,4 +1,12 @@ -require("nvim-surround").setup({ +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, @@ -18,5 +26,10 @@ require("nvim-surround").setup({ ["r"] = false, ["q"] = false, ["s"] = false, - }, -}) + }, + }) + + return true +end + +return M diff --git a/common/config/nvim/lua/plugins/telescope.lua b/common/config/nvim/lua/plugins/telescope.lua index 506be8b..5aca8ac 100644..100755 --- a/common/config/nvim/lua/plugins/telescope.lua +++ b/common/config/nvim/lua/plugins/telescope.lua @@ -1,372 +1,384 @@ local M = {} --- Shorten function names -local actions = require('telescope.actions') -local fb_actions = require('telescope').extensions.file_browser.actions ---local builtin = require("telescope.builtin") ---local utils = require("telescope.utils") ---local layout_actions = require("telescope.actions.layout") ---local themes = require('telescope.themes') -local actions_set = require('telescope.actions.set') -local actions_state = require('telescope.actions.state') -local finders = require('telescope.finders') -local pickers = require('telescope.pickers') -local config = require('telescope.config').values - -require('telescope').setup({ - defaults = { - vimgrep_arguments = { - 'rg', - '--color=never', - '--no-heading', - '--with-filename', - '--line-number', - '--column', - '--smart-case', - '--hidden', - '--fixed-strings', - '--trim', +-- 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, + }, + }, }, - prompt_prefix = ' ', - selection_caret = ' ', - entry_prefix = ' ', - path_display = { 'tail' }, - --path_display = { "truncate" }, - --path_display = { "smart" }, - file_ignore_patterns = { - 'packer_compiled.lua', - '~/.config/zsh/plugins', - 'zcompdump', - '%.DS_Store', - '%.git/', - '%.spl', - --"%.log", - '%[No Name%]', -- new files / sometimes folders (netrw) - '/$', -- ignore folders (netrw) - 'node_modules', - '%.png', - '%.zip', - '%.pxd', - --"^.vim/", - '^.local/', - '^.cache/', - '^downloads/', - '^music/', - --"^node_modules/", - --"^undodir/", + preview = { + filesize_limit = 3, + timeout = 250, }, - 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, - - --["<C-c>"] = actions.close, - ['<Esc>'] = actions.close, -- close w/ one esc - --["<Esc>"] = "close", -- close w/ one esc - ['<?>'] = actions.which_key, -- keys from pressing <C-/> - - ['<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, -- keys from pressing <C-/> - --["<C-o>"] = function(prompt_bufnr) - -- local selection = require("telescope.actions.state").get_selected_entry() - -- local dir = vim.fn.fnamemodify(selection.path, ":p:h") - -- require("telescope.actions").close(prompt_bufnr) - -- -- Depending on what you want put `cd`, `lcd`, `tcd` - -- vim.cmd(string.format("silent lcd %s", dir)) - --end, + 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 }, }, - n = { - --["cd"] = function(prompt_bufnr) - -- local selection = require("telescope.actions.state").get_selected_entry() - -- local dir = vim.fn.fnamemodify(selection.path, ":p:h") - -- require("telescope.actions").close(prompt_bufnr) - -- -- Depending on what you want put `cd`, `lcd`, `tcd` - -- vim.cmd(string.format("silent lcd %s", dir)) - --end, - ['<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 = require('telescope.actions.state').get_selected_entry() - local dir = vim.fn.fnamemodify(selection.path, ':p:h') - require('telescope.actions').close(prompt_bufnr) - -- Depending on what you want put `cd`, `lcd`, `tcd` - vim.cmd(string.format('silent lcd %s', dir)) - end, - ['?'] = actions.which_key, - --["<C-o>"] = function(prompt_bufnr) - -- local selection = require("telescope.actions.state").get_selected_entry() - -- local dir = vim.fn.fnamemodify(selection.path, ":p:h") - -- require("telescope.actions").close(prompt_bufnr) - -- -- Depending on what you want put `cd`, `lcd`, `tcd` - -- vim.cmd(string.format("silent lcd %s", dir)) - --end, + bottom_pane = { + height = 12, + preview_cutoff = 70, + prompt_position = "bottom", }, }, - }, - 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, }, - }, - find_files = { - --cwd = '%:p:h', - cwd = vim.fn.getcwd(), - prompt_prefix = ' ', - --hidden = true, - --no_ignore = false, - follow = true, - }, - --pickers = { - -- live_grep = { - -- disable_coordinates = true, - -- layout_config = { - -- horizontal = { - -- preview_width = 0.55, - -- }, - -- }, - -- }, - --}, - --pickers = { - -- live_grep = { - -- mappings = { - -- i = { - -- ["<C-f>"] = ts_select_dir_for_grep, - -- }, - -- n = { - -- ["<C-f>"] = ts_select_dir_for_grep, - -- }, - -- }, - -- }, - --}, - --pickers = { - --lsp_references = { - -- prompt_prefix='⬅️', - -- show_line=false, - -- trim_text=true, - -- include_declaration=false, - -- initial_mode = "normal", - --}, - --lsp_definitions = { - -- prompt_prefix='➡️', - -- show_line=false, - -- trim_text=true, - -- initial_mode = "normal", - --}, - --lsp_document_symbols = { - -- prompt_prefix='* ', - -- show_line = false, - --}, - --treesitter = { - -- prompt_prefix=' ', - -- show_line = false, - --}, - --keymaps = { prompt_prefix='? ' }, - --oldfiles = { prompt_prefix=' ' }, - --highlights = { prompt_prefix=' ' }, - --git_files = { - -- prompt_prefix=' ', - -- show_untracked = true, - -- path_display = { "tail" }, - --}, - --buffers = { - -- prompt_prefix=' ', - -- ignore_current_buffer = true, - -- initial_mode = "normal", - -- sort_mru = true, - --}, - --live_grep = { - -- cwd='%:p:h', - -- disable_coordinates=true, - -- prompt_title='Search in Folder', - -- prompt_prefix=' ', - --}, - --spell_suggest = { - -- initial_mode = "normal", - -- prompt_prefix = "暈", - -- theme = "cursor", - -- layout_config = { cursor = { width = 0.3 } } - --}, - --colorscheme = { - -- enable_preview = true, - -- prompt_prefix = '', - -- results_title = '', - -- layout_strategy = "bottom_pane", - --}, - --}, - - extensions = { - file_browser = { - theme = 'dropdown', - -- disables netrw and use telescope-file-browser in its place - hijack_netrw = false, - mappings = { - -- your custom insert mode mappings - ['i'] = { - ['<C-w>'] = function() - vim.cmd('normal vbd') - end, - ['<C-h>'] = fb_actions.goto_parent_dir, - }, - ['n'] = { - -- your custom normal mode mappings - ['N'] = fb_actions.create, - ['<C-h>'] = fb_actions.goto_parent_dir, - --["/"] = function() - -- vim.cmd("startinsert") - --end, + 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: --- have to be loaded after telescope setup/config -require('telescope').load_extension('fzf') -require('telescope').load_extension('ui-select') -require('telescope').load_extension('file_browser') -require('telescope').load_extension('changed_files') -require('telescope').load_extension('media_files') -require('telescope').load_extension('notify') -require('telescope').load_extension('dap') -require('telescope').load_extension('session-lens') -require('telescope').load_extension('flutter') -require('telescope').load_extension('recent_files') ---require('telescope').load_extension('projects') - ---M.curbuf = function(opts) --- opts = opts --- or themes.get_dropdown({ --- previewer = false, --- shorten_path = false, --- border = true, --- }) --- require("telescope.builtin").current_buffer_fuzzy_find(opts) ---end + }) -function M.find_configs() - -- Track dotfiles (bare git repository) - -- Inside shell config file: - -- alias config='git --git-dir=$HOME/.cfg --work-tree=$HOME' - -- cfg_files=$(config ls-tree --name-only -r HEAD) - -- export CFG_FILES="$cfg_files" - local tracked_files = {} - - for file in string.gmatch(os.getenv('CFG_FILES'), '[^\n]+') do - table.insert(tracked_files, os.getenv('HOME') .. '/' .. file) + -- 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 - local history = os.getenv('HOME') .. '/.config/zsh/.zhistory' - table.insert(tracked_files, history) + -- Define the custom command findhere/startup + vim.cmd('command! Findhere lua require("plugins.telescope").findhere()') - require('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 }, - }) + 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({ + require("telescope.builtin").find_files({ hidden = true, no_ignore = true, - prompt_title = ' Find Scripts', - path_display = { 'smart' }, + prompt_title = " Find Scripts", + path_display = { "smart" }, search_dirs = { - '~/.scripts', + "~/.scripts", }, - layout_strategy = 'horizontal', + layout_strategy = "horizontal", layout_config = { preview_width = 0.65, width = 0.75 }, }) end function M.find_projects() - local search_dir = '~/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', + prompt_title = "Find Projects", finder = finders.new_oneshot_job({ - 'find', + "find", vim.fn.expand(search_dir), - '-type', - 'd', - '-maxdepth', - '1', + "-type", + "d", + "-maxdepth", + "1", }), - previewer = require('telescope.previewers').vim_buffer_cat.new({}), + previewer = require("telescope.previewers").vim_buffer_cat.new({}), sorter = config.generic_sorter({}), attach_mappings = function(prompt_bufnr, map) actions_set.select:replace(function() @@ -375,9 +387,9 @@ function M.find_projects() local dir = entry.value actions.close(prompt_bufnr, false) vim.fn.chdir(dir) - vim.cmd('e .') + vim.cmd("e .") vim.cmd("echon ''") - print('cwd: ' .. vim.fn.getcwd()) + print("cwd: " .. vim.fn.getcwd()) end end) return true @@ -390,50 +402,64 @@ function M.grep_notes() local opts = {} opts.hidden = false opts.search_dirs = { - '~/documents/notes/', + "~/documents/main/", } - opts.prompt_prefix = ' ' - opts.prompt_title = ' Grep Notes' - opts.path_display = { 'smart' } - require('telescope.builtin').live_grep(opts) + 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({ + require("telescope.builtin").find_files({ hidden = true, no_ignore = false, - prompt_title = ' Find Notes', - path_display = { 'smart' }, + prompt_title = " Find Notes", + path_display = { "smart" }, search_dirs = { - '~/documents/notes/private/', - '~/documents/notes', + "~/documents/main", }, - layout_strategy = 'horizontal', + layout_strategy = "horizontal", layout_config = { preview_width = 0.65, width = 0.75 }, }) end function M.find_private() - require('telescope.builtin').find_files({ + require("telescope.builtin").find_files({ hidden = true, no_ignore = false, - prompt_title = ' Find Notes', - path_display = { 'smart' }, + prompt_title = " Find Notes", + path_display = { "smart" }, search_dirs = { - '~/notes/private', - '~/notes', + "~/notes/private", + "~/notes", }, - layout_strategy = 'horizontal', + layout_strategy = "horizontal", layout_config = { preview_width = 0.65, width = 0.75 }, }) end function M.find_books() - local search_dir = '~/documents/books' - 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' - local search_cmd = 'find ' .. vim.fn.expand(search_dir) .. ' -type d -o -type f -maxdepth 1' + 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) @@ -441,14 +467,14 @@ function M.find_books() local results = {} -- Section for Recent Books - table.insert(results, ' 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') + table.insert(results, " All Books") local directories = {} local files = {} @@ -472,14 +498,14 @@ function M.find_books() end local picker = pickers.new({}, { - prompt_title = 'Find Books', + prompt_title = "Find Books", finder = finders.new_table({ results = results, }), file_ignore_patterns = { - '%.git', + "%.git", }, - previewer = require('telescope.previewers').vim_buffer_cat.new({}), + previewer = require("telescope.previewers").vim_buffer_cat.new({}), sorter = config.generic_sorter({}), attach_mappings = function(prompt_bufnr, map) actions_set.select:replace(function() @@ -490,17 +516,18 @@ function M.find_books() 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' }) + 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) + vim.cmd("e " .. path) else -- It's a file, open it - vim.cmd('e ' .. path) + vim.cmd("e " .. path) end end end @@ -513,48 +540,43 @@ function M.find_books() end function M.grep_current_dir() - local buffer_dir = require('telescope.utils').buffer_dir() + local buffer_dir = require("telescope.utils").buffer_dir() local opts = { - prompt_title = 'Live Grep in ' .. buffer_dir, + prompt_title = "Live Grep in " .. buffer_dir, cwd = buffer_dir, } - require('telescope.builtin').live_grep(opts) + require("telescope.builtin").live_grep(opts) end --------------------------------------------------------------------------------- - -local dropdown = require('telescope.themes').get_dropdown({ - hidden = true, - no_ignore = true, - previewer = false, - prompt_title = '', - preview_title = '', - results_title = '', - layout_config = { - --anchor = "S", - prompt_position = 'top', - }, -}) - --- File browser always relative to buffer ---local opts_file_browser = vim.tbl_extend('force', dropdown, { --- path_display = { '%:p:h' }, ---}) +-- 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 with_title = function(opts, extra) +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 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) + title = require("plenary.path"):new(buf_path):make_relative(cwd) else - title = vim.fn.fnamemodify(cwd, ':t') + title = vim.fn.fnamemodify(cwd, ":t") end - return vim.tbl_extend('force', opts, { + return vim.tbl_extend("force", opts, { prompt_title = title, }, extra or {}) end @@ -562,31 +584,21 @@ 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 + 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(dropdown)) - -- require'telescope.builtin'.find_files(require('telescope.themes').get_dropdown({ - -- hidden = true, - -- results_title = '', - -- layout_config = { prompt_position = 'top' }, - -- })) + require("telescope.builtin").find_files(with_title(get_dropdown_theme())) end, 10) end end --- Define the custom command findhere/startup -vim.cmd('command! Findhere lua require("plugins.telescope").findhere()') ---vim.cmd('command! Startup lua require("plugins.telescope").findhere()') ---vim.api.nvim_command('autocmd VimEnter * lua require("plugins/telescope").findhere()') - -- Find dirs function M.find_dirs() - local root_dir = vim.fn.input('Enter the root directory: ') + 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.') + if root_dir == "" then + print("No directory entered. Aborting.") return end @@ -598,14 +610,22 @@ function M.find_dirs() local subentries = vim.fn.readdir(root_path) if subentries then for _, subentry in ipairs(subentries) do - local absolute_path = root_path .. '/' .. subentry + 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', + prompt_title = "Change Directory or Open File", finder = finders.new_table({ results = entries, }), @@ -617,13 +637,13 @@ function M.find_dirs() if entry ~= nil then local selected_entry = entry.value actions.close(prompt_bufnr, false) - local selected_path = root_path .. '/' .. selected_entry + 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()) + vim.cmd("e .") + print("cwd: " .. vim.fn.getcwd()) else - vim.cmd('e ' .. selected_path) + vim.cmd("e " .. selected_path) end end end) @@ -633,4 +653,88 @@ function M.find_dirs() :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/config/nvim/lua/plugins/toggleterm.lua b/common/config/nvim/lua/plugins/toggleterm.lua index e67bdec..6b7aad5 100644..100755 --- a/common/config/nvim/lua/plugins/toggleterm.lua +++ b/common/config/nvim/lua/plugins/toggleterm.lua @@ -1,8 +1,15 @@ -local status_ok, toggleterm = pcall(require, 'toggleterm') -if not status_ok then - return -end -toggleterm.setup({ +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, @@ -47,9 +54,67 @@ toggleterm.setup({ --background = 'Normal', }, --winblend = 0, - }, -}) -local mods = require('user.mods') + }, + }) + + -- 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 }) @@ -72,54 +137,77 @@ 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 = require('toggleterm.terminal').Terminal +local Terminal +local horizontal_term, vertical_term -local horizontal_term = Terminal:new({ hidden = true, direction = 'horizontal' }) -local vertical_term = Terminal:new({ hidden = true, direction = 'vertical' }) +-- 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() - horizontal_term:toggle(8, 'horizontal') + if horizontal_term then + pcall(horizontal_term.toggle, horizontal_term, 8, 'horizontal') + end end function Vertical_term_toggle() - horizontal_term:toggle(math.floor(vim.o.columns * 0.5), 'vertical') + if vertical_term then + pcall(vertical_term.toggle, vertical_term, math.floor(vim.o.columns * 0.5), 'vertical') + end end -local lazygit = 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, - }, +-- Initialize lazygit terminal instance +local lazygit = nil +local Cur_cwd = vim.fn.getcwd() - ---- Function to run on opening the terminal - --on_open = function(term) - -- vim.api.nvim_buf_set_keymap(term.bufnr, 'n', 'q', '<cmd>close<CR>', - -- {noremap = true, silent = true}) - -- vim.api.nvim_buf_set_keymap(term.bufnr, 'n', '<esc>', '<cmd>close<CR>', - -- {noremap = true, silent = true}) - -- vim.api.nvim_buf_set_keymap(term.bufnr, 'n', '<C-\\>', '<cmd>close<CR>', - -- {noremap = true, silent = true}) - --end, - ---- Function to run on closing the terminal - --on_close = function(term) - -- vim.cmd("startinsert!") - --end -}) +-- 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 - lazygit:close() + if lazygit then + lazygit:close() + end lazygit = Terminal:new({ cmd = "zsh --login -c 'lazygit'", dir = 'git_dir', @@ -133,31 +221,58 @@ function Lazygit_toggle() }, }) end - lazygit:toggle() + if lazygit then + lazygit:toggle() + else + vim.notify("Failed to initialize lazygit terminal", vim.log.levels.ERROR) + end end -local node = Terminal:new({ cmd = 'node', hidden = true }) +local node = nil +local ncdu = nil -function _NODE_TOGGLE() - node:toggle() +-- 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 -local ncdu = Terminal:new({ cmd = 'ncdu', hidden = true }) +function _NODE_TOGGLE() + if not node then return end + pcall(node.toggle, node) +end function _NCDU_TOGGLE() - ncdu:toggle() + if not ncdu then return end + pcall(ncdu.toggle, ncdu) end -local htop = Terminal:new({ cmd = 'htop', hidden = true }) +local htop = nil function _HTOP_TOGGLE() - 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 = Terminal:new({ cmd = 'python', hidden = true }) +local python = nil function _PYTHON_TOGGLE() - 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() diff --git a/common/config/nvim/lua/plugins/treesitter.lua b/common/config/nvim/lua/plugins/treesitter.lua index 7f481d3..9df99b8 100644..100755 --- a/common/config/nvim/lua/plugins/treesitter.lua +++ b/common/config/nvim/lua/plugins/treesitter.lua @@ -1,31 +1,54 @@ -require'nvim-treesitter.configs'.setup { - -- A list of parser names, or "all" (the four listed parsers should always be installed) +local M = {} - ensure_installed = { - "c", - "bash", - "lua", - "rust", - }, - --ensure_installed = "all", -- one of "all" or a list of languages - --ignore_install = { "" }, -- List of parsers to ignore installing - sync_install = false, - auto_install = true, - highlight = { - enable = false, - disable = {}, - }, - indent = { - enable = true, - disable = {}, - --disable = { "python", "css" } - }, - autotag = { - enable = true, - }, -} ---vim.opt.foldmethod = "expr" ---vim.opt.foldexpr = "nvim_treesitter#foldexpr()" +function M.setup() + local ok, treesitter = pcall(require, "nvim-treesitter.configs") + if not ok or not treesitter then + return false + end ---local parser_config = require "nvim-treesitter.parsers".get_parser_configs() ---parser_config.tsx.filetype_to_parsername = { "javascript", "typescript.tsx" } + -- 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/config/nvim/lua/plugins/trouble.lua b/common/config/nvim/lua/plugins/trouble.lua index 7d74730..4a07e3b 100644..100755 --- a/common/config/nvim/lua/plugins/trouble.lua +++ b/common/config/nvim/lua/plugins/trouble.lua @@ -1,47 +1,73 @@ -require('trouble').setup({ - position = 'bottom', -- position of the list can be: bottom, top, left, right - height = 10, -- height of the trouble list when position is top or bottom - width = 50, -- width of the list when position is left or right - icons = true, -- use devicons for filenames - mode = 'document_diagnostics', -- "workspace_diagnostics", "document_diagnostics", "quickfix", "lsp_references", "loclist" - fold_open = '', -- icon used for open folds - fold_closed = '', -- icon used for closed folds - group = true, -- group results by file - padding = true, -- add an extra new line on top of the list - action_keys = { -- key mappings for actions in the trouble list - -- map to {} to remove a mapping, for example: - -- close = {}, - close = 'q', -- close the list - cancel = '<esc>', -- cancel the preview and get back to your last window / buffer / cursor - refresh = 'r', -- manually refresh - jump = { '<cr>', '<tab>' }, -- jump to the diagnostic or open / close folds - open_split = { '<c-x>' }, -- open buffer in new split - open_vsplit = { '<c-v>' }, -- open buffer in new vsplit - open_tab = { '<c-t>' }, -- open buffer in new tab - jump_close = { 'o' }, -- jump to the diagnostic and close the list - toggle_mode = 'm', -- toggle between "workspace" and "document" diagnostics mode - toggle_preview = 'P', -- toggle auto_preview - hover = 'K', -- opens a small popup with the full multiline message - preview = 'p', -- preview the diagnostic location - close_folds = { 'zM', 'zm' }, -- close all folds - open_folds = { 'zR', 'zr' }, -- open all folds - toggle_fold = { 'zA', 'za' }, -- toggle fold of current file - previous = 'k', -- previous item - next = 'j', -- next item +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 + }, }, - indent_lines = true, -- add an indent guide below the fold icons - auto_open = false, -- automatically open the list when you have diagnostics - auto_close = false, -- automatically close the list when you have no diagnostics - auto_preview = true, -- automatically preview the location of the diagnostic. <esc> to close preview and go back to last window - auto_fold = false, -- automatically fold a file trouble list at creation - auto_jump = { 'lsp_definitions' }, -- for the given modes, automatically jump if there is only a single result + 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 = { - -- icons / text used for a diagnostic - error = '', - warning = '▲', - information = '', - hint = '⚑', - other = '', + error = "", + warning = "▲", + info = "", + hint = "⚑", + other = "•", }, - use_diagnostic_signs = true, -- enabling this will use the signs defined in your lsp client -}) + use_diagnostic_signs = true, + }) + + return true +end + +return M diff --git a/common/config/nvim/lua/plugins/vimtex.lua b/common/config/nvim/lua/plugins/vimtex.lua index 732e6ed..732e6ed 100644..100755 --- a/common/config/nvim/lua/plugins/vimtex.lua +++ b/common/config/nvim/lua/plugins/vimtex.lua diff --git a/common/config/nvim/lua/plugins/web-devicons.lua b/common/config/nvim/lua/plugins/web-devicons.lua index 06f2d1c..a565a31 100644..100755 --- a/common/config/nvim/lua/plugins/web-devicons.lua +++ b/common/config/nvim/lua/plugins/web-devicons.lua @@ -1,22 +1,125 @@ -local devicons = require('nvim-web-devicons') - --- Set devicons overrides early. -devicons.setup({ - override = { - js = { icon = '', color = '#f5c06f', name = 'Js' }, - jsx = { icon = '', color = '#689fb6', name = 'Jsx' }, - ts = { icon = '', color = '#4377c1', name = 'Ts' }, - tsx = { icon = '', color = '#4377c1', name = 'Tsx' }, - png = { icon = '', color = '#d4843e', name = 'Png' }, - webp = { icon = '', color = '#3498db', name = 'Webp' }, - jpg = { icon = '', color = '#16a085', name = 'Jpg' }, - svg = { icon = '', color = '#3affdb', name = 'Svg' }, - zsh = { - icon = '', - color = '#428850', - cterm_color = '65', - name = 'Zsh', +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', }, - }, - color_icons = true, -}) + }) + + return true +end + +return M diff --git a/common/config/nvim/lua/plugins/which-key.lua b/common/config/nvim/lua/plugins/which-key.lua index 10c0c41..10015aa 100644..100755 --- a/common/config/nvim/lua/plugins/which-key.lua +++ b/common/config/nvim/lua/plugins/which-key.lua @@ -1,60 +1,53 @@ -require("which-key").setup { - plugins = { - marks = true, -- shows a list of your marks on ' and ` - registers = true, -- shows your registers on " in NORMAL or <C-r> in INSERT mode - spelling = { - enabled = true, -- enabling this will show WhichKey when pressing z= to select spelling suggestions - suggestions = 9, -- how many suggestions should be shown in the list? - }, - -- the presets plugin, adds help for a bunch of default keybindings in Neovim - -- No actual key bindings are created - presets = { - operators = true, -- adds help for operators like d, y, ... and registers them for motion / text object completion - motions = true, -- adds help for motions - text_objects = true, -- help for text objects triggered after entering an operator - windows = true, -- default bindings on <c-w> - nav = true, -- misc bindings to work with windows - z = true, -- bindings for folds, spelling and others prefixed with z - g = true, -- bindings for prefixed with g +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, + }, }, - }, - -- add operators that will trigger motion and text object completion - -- to enable all native operators, set the preset / operators plugin above - operators = { gc = "Comments" }, - key_labels = { - -- override the label used to display some keys. It doesn't effect WK in any other way. - -- For example: - -- ["<space>"] = "SPC", - -- ["<cr>"] = "RET", - -- ["<tab>"] = "TAB", - }, - icons = { - breadcrumb = "»", -- symbol used in the command line area that shows your active key combo - separator = "➜", -- symbol used between a key and it's label - group = "+", -- symbol prepended to a group - }, - window = { - border = "none", -- none, single, double, shadow - position = "bottom", -- bottom, top - margin = { 0, 0, 0, 0 }, -- extra window margin [top, right, bottom, left] - padding = { 1, 0, 1, 0 }, -- extra window padding [top, right, bottom, left] - }, - layout = { - height = { min = 1, max = 25 }, -- min and max height of the columns - width = { min = 20, max = 50 }, -- min and max width of the columns - spacing = 1, -- spacing between columns - align = "center", -- align columns left, center or right - }, - ignore_missing = false, -- enable this to hide mappings for which you didn't specify a label - hidden = { "<silent>", "<cmd>", "<Cmd>", "<CR>", "call", "lua", "^:", "^ " }, -- hide mapping boilerplate - show_help = true, -- show help message on the command line when the popup is visible - triggers = "auto", -- automatically setup triggers - -- triggers = {"<leader>"} -- or specify a list manually + --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 - triggers_blacklist = { - -- list of mode / prefixes that should never be hooked by WhichKey - -- this is mostly relevant for key maps that start with a native binding - -- most people should not need to change this - n = { "o", "O" }, - }, -} +return M diff --git a/common/config/nvim/lua/plugins/zen-mode.lua b/common/config/nvim/lua/plugins/zen-mode.lua index 7e52854..7e52854 100644..100755 --- a/common/config/nvim/lua/plugins/zen-mode.lua +++ b/common/config/nvim/lua/plugins/zen-mode.lua diff --git a/common/config/nvim/lua/setup/compat.lua b/common/config/nvim/lua/setup/compat.lua new file mode 100755 index 0000000..ef90444 --- /dev/null +++ b/common/config/nvim/lua/setup/compat.lua @@ -0,0 +1,104 @@ +-- setup/compat.lua +-- Automatically patches deprecated APIs based on Neovim version + +-- Version check helper +local function has_version(major, minor, patch) + local v = vim.version() + patch = patch or 0 + return v.major > major + or (v.major == major and v.minor > minor) + or (v.major == major and v.minor == minor and v.patch >= patch) +end + +-- === GLOBAL PATCHES === -- + +-- Neovim 0.10+: vim.islist replaces deprecated vim.tbl_islist +if has_version(0, 10) then + if vim.tbl_islist == nil then + vim.tbl_islist = vim.islist + end +end + +-- Neovim 0.12+: vim.tbl_flatten removed → shim using vim.iter +if has_version(0, 12) then + vim.tbl_flatten = function(t) + return vim.iter(t):flatten():totable() + end +end + +-- === DEPRECATION SHIMS (0.13 / 1.0) === -- + +-- client.is_stopped → client:is_stopped() +if has_version(0, 13) then + local mt = getmetatable(vim.lsp._client or {}) + if mt and mt.__index and mt.__index.is_stopped then + mt.__index.is_stopped = function(client, ...) + return client:is_stopped(...) + end + end +end + +-- client.request → client:request() +if has_version(0, 13) then + local mt = getmetatable(vim.lsp._client or {}) + if mt and mt.__index and mt.__index.request then + mt.__index.request = function(client, ...) + return client:request(...) + end + end +end + +-- vim.validate{tbl} → vim.validate(tbl) +if has_version(1, 0) then + if type(vim.validate) == "function" then + local old_validate = vim.validate + vim.validate = function(arg) + -- Handle both forms for backward compatibility + if type(arg) == "table" then + return old_validate(arg) + else + return old_validate{ arg } + end + end + end +end + +-- Deprecated: vim.lsp.get_active_clients (moved in 0.11+) +if has_version(0, 11) then + if vim.lsp.get_active_clients == nil then + vim.lsp.get_active_clients = function(...) + return vim.lsp.get_clients(...) + end + end +end + +-- Deprecated: vim.diagnostic.setqflist / setloclist (moved in 0.11+) +if has_version(0, 11) then + if vim.diagnostic.setqflist == nil then + vim.diagnostic.setqflist = function(diags, opts) + return vim.diagnostic.toqflist(diags, opts) + end + end + if vim.diagnostic.setloclist == nil then + vim.diagnostic.setloclist = function(diags, opts) + return vim.diagnostic.toloclist(diags, opts) + end + end +end + +-- Deprecated: vim.lsp.buf.formatting/formatting_sync (removed in 0.8+) +if has_version(0, 8) then + if vim.lsp.buf.formatting == nil then + vim.lsp.buf.formatting = function(opts) + return vim.lsp.buf.format(opts) + end + end + if vim.lsp.buf.formatting_sync == nil then + vim.lsp.buf.formatting_sync = function(opts, timeout_ms) + return vim.lsp.buf.format(vim.tbl_extend("force", opts or {}, { timeout_ms = timeout_ms })) + end + end +end + +-- Return something to satisfy require() +return true diff --git a/common/config/nvim/lua/setup/manager.lua b/common/config/nvim/lua/setup/manager.lua new file mode 100755 index 0000000..9cf1d14 --- /dev/null +++ b/common/config/nvim/lua/setup/manager.lua @@ -0,0 +1,811 @@ +-- manager.lua + +local M = {} + +-- State tracking +local state = { + manager_invoked = nil, + initialized = false, + bootstrap_completed = {}, +} + +-- Path constants +local PATHS = { + lazy = vim.fn.stdpath("data") .. "/lazy/lazy.nvim", + packer = vim.fn.stdpath("data") .. "/site/pack/packer/start/packer.nvim", + packer_dir = vim.fn.stdpath("data") .. "/site/pack/packer/start", + builtin_dir = vim.fn.stdpath("data") .. "/nvim/site/pack/core/opt", +} + +-- Utility functions +local function safe_require(module) + local ok, result = pcall(require, module) + return ok and result or nil +end + +local function notify(msg, level) + vim.notify("[Manager] " .. msg, level or vim.log.levels.INFO) +end + +local function execute_git_command(cmd, _) + -- Use vim.fn.system instead of os.execute for better cross-platform support and error handling + local result = vim.fn.system(cmd) + return vim.v.shell_error == 0 +end + +local function get_nvim_version() + local version = vim.version() + if version then + return version.major, version.minor, version.patch + end + + -- Fallback for older versions + local version_str = vim.fn.execute("version"):match("NVIM v(%d+%.%d+%.%d+)") + if version_str then + local major, minor, patch = version_str:match("(%d+)%.(%d+)%.(%d+)") + return tonumber(major), tonumber(minor), tonumber(patch) + end + return 0, 0, 0 +end + +local function has_builtin_manager() + local major, minor = get_nvim_version() + return major > 0 or (major == 0 and minor >= 12) +end + +-- CRITICAL FIX: This function is essential to prevent runtime conflicts. +-- It removes the specified manager's directory from the runtimepath. +local function cleanup_manager(manager_name) + if manager_name == "packer" then + -- Reset packer state and remove from rtp + local packer = safe_require("packer") + if packer then + pcall(packer.reset) + end + -- Remove the entire packer directory from rtp + local packer_rtp = vim.fn.glob(PATHS.packer_dir) + if packer_rtp then + local rtp_items = vim.split(vim.o.rtp, ",") + local new_rtp_items = {} + for _, item in ipairs(rtp_items) do + if item ~= packer_rtp then + table.insert(new_rtp_items, item) + end + end + vim.o.rtp = table.concat(new_rtp_items, ",") + end + elseif manager_name == "lazy" then + -- Lazy.nvim clears its state on each run, but we can remove it from rtp for good measure + local lazy_rtp = vim.fn.glob(PATHS.lazy) + if lazy_rtp then + local rtp_items = vim.split(vim.o.rtp, ",") + local new_rtp_items = {} + for _, item in ipairs(rtp_items) do + if item ~= lazy_rtp then + table.insert(new_rtp_items, item) + end + end + vim.o.rtp = table.concat(new_rtp_items, ",") + end + elseif manager_name == "builtin" then + -- Built-in manager is handled by vim.opt.packpath and doesn't need manual cleanup from rtp + -- unless we want to disable its packages, which isn't the goal here. + end +end + +-- IMPROVED: Use vim.g for persistence instead of file system +local function save_manager_choice(manager_name) + vim.g.nvim_manager_choice = manager_name + -- Also save to data directory as a simple text file for true persistence across sessions + local data_dir = vim.fn.stdpath("data") + local choice_file = data_dir .. "/.manager_choice" + local file = io.open(choice_file, "w") + if file then + file:write(manager_name) + file:close() + end +end + +local function load_manager_choice() + -- First check vim.g (current session) + if vim.g.nvim_manager_choice then + return vim.g.nvim_manager_choice + end + + -- Then check persistent file + local data_dir = vim.fn.stdpath("data") + local choice_file = data_dir .. "/.manager_choice" + local file = io.open(choice_file, "r") + if file then + local choice = file:read("*a"):gsub("%s+", "") -- trim whitespace + file:close() + if choice and choice ~= "" then + vim.g.nvim_manager_choice = choice -- cache in session + return choice + end + end + + return nil +end + +--- Packer Manager Implementation +-- +-- Handles cloning, setup, and configuration of Packer.nvim. +local Packer = {} + +function Packer.bootstrap() + if state.bootstrap_completed.packer then + return true + end + + local fn = vim.fn + if fn.isdirectory(PATHS.packer_dir) == 0 then + fn.mkdir(PATHS.packer_dir, "p") + end + + if fn.empty(fn.glob(PATHS.packer)) > 0 then + local is_windows = vim.loop.os_uname().version:match("Windows") + local git_cmd + + if is_windows then + git_cmd = string.format( + 'git clone --depth=1 https://github.com/wbthomason/packer.nvim "%s" >nul 2>&1', + PATHS.packer + ) + else + git_cmd = string.format( + 'env -i PATH="%s" HOME="%s" git clone --depth=1 --quiet https://github.com/wbthomason/packer.nvim %q >/dev/null 2>&1', + os.getenv("PATH") or "/usr/bin:/bin", + os.getenv("HOME") or "/tmp", + PATHS.packer + ) + end + + if not execute_git_command(git_cmd, "Failed to clone packer.nvim") then + return false + end + end + + state.bootstrap_completed.packer = true + return true +end + +function Packer.setup() + if not Packer.bootstrap() then + return false + end + + -- Ensure packer.nvim is in the runtime path + vim.cmd("packadd packer.nvim") + + local packer = safe_require("packer") + if not packer then + notify("Failed to load packer.nvim", vim.log.levels.ERROR) + return false + end + + -- Reset any existing configuration from a previous run + pcall(packer.reset) + + packer.init({ + auto_reload_compiled = true, + display = { + open_fn = function() + return require("packer.util").float({ border = "rounded" }) + end, + }, + luarocks = { + python_cmd = 'python3' + }, + }) + + local plugins = safe_require("setup.plugins") + if not plugins then + notify("Failed to load plugins configuration", vim.log.levels.ERROR) + return false + end + + packer.startup(function(use) + use "wbthomason/packer.nvim" + for _, plugin in ipairs(plugins) do + -- CHECK FOR EXCLUDE HERE - Packer support for exclude option + if plugin.exclude and vim.tbl_contains(plugin.exclude, "packer") then + --notify("Excluding plugin for packer: " .. (plugin.name or plugin.as or plugin[1] or "unknown"), vim.log.levels.INFO) + goto continue + end + + -- Packer doesn't have a lazy option, so we ensure all plugins are loaded eagerly + -- by clearing any lazy-loading keys from the plugins table. + local packer_plugin = vim.deepcopy(plugin) + packer_plugin.event = nil + packer_plugin.keys = nil + packer_plugin.cmd = nil + packer_plugin.ft = nil + packer_plugin.lazy = nil + packer_plugin.exclude = nil -- Remove exclude from the actual plugin spec + use(packer_plugin) + ::continue:: + end + end) + + return true +end + +function Packer.is_available() + return vim.fn.isdirectory(PATHS.packer) == 1 +end + +--- Lazy.nvim Manager Implementation +-- +local Lazy = {} + +function Lazy.bootstrap() + if state.bootstrap_completed.lazy then + return true + end + + -- Check if lazy.nvim is already cloned + if not vim.loop.fs_stat(PATHS.lazy) then + local is_windows = vim.loop.os_uname().version:match("Windows") + local git_cmd + + if is_windows then + git_cmd = string.format( + 'git clone --filter=blob:none --branch=stable https://github.com/folke/lazy.nvim.git "%s" >nul 2>&1', + PATHS.lazy + ) + else + git_cmd = string.format( + 'env -i PATH="%s" HOME="%s" git clone --filter=blob:none --branch=stable --quiet https://github.com/folke/lazy.nvim.git %q >/dev/null 2>&1', + os.getenv("PATH") or "/usr/bin:/bin", + os.getenv("HOME") or "/tmp", + PATHS.lazy + ) + end + + if not execute_git_command(git_cmd, "Failed to clone lazy.nvim") then + return false + end + end + + state.bootstrap_completed.lazy = true + return true +end + +function Lazy.setup() + if not Lazy.bootstrap() then + return false + end + + -- Ensure lazy.nvim is in the runtime path before requiring it + vim.opt.rtp:prepend(PATHS.lazy) + + local lazy = safe_require("lazy") + if not lazy then + notify("Failed to load lazy.nvim", vim.log.levels.ERROR) + return false + end + + -- FIX: Correctly require plugins and set up lazy.nvim + local plugins = safe_require("setup.plugins") + if not plugins then + notify("Failed to load plugins configuration", vim.log.levels.ERROR) + return false + end + + -- Filter out excluded plugins for Lazy + local filtered_plugins = {} + for _, plugin in ipairs(plugins) do + -- CHECK FOR EXCLUDE HERE - Lazy support for exclude option + if plugin.exclude and vim.tbl_contains(plugin.exclude, "lazy") then + --notify("Excluding plugin for lazy: " .. (plugin.name or plugin[1] or "unknown"), vim.log.levels.INFO) + else + local lazy_plugin = vim.deepcopy(plugin) + lazy_plugin.exclude = nil -- Remove exclude from the actual plugin spec + table.insert(filtered_plugins, lazy_plugin) + end + end + + -- Setup Lazy.nvim with the correct options + lazy.setup(filtered_plugins, { + { + import = "plugins", + }, + defaults = { lazy = false }, -- Set plugins to be lazy-loaded by default + install = { missing = true }, -- CRITICAL FIX: This ensures missing plugins are installed + ui = { + border = "rounded", + }, + performance = { + rtp = { + disabled_plugins = { + "gzip", "matchit", "matchparen", "netrwPlugin", + "tarPlugin", "tohtml", "tutor", "zipPlugin", + }, + }, + }, + }) + + return true +end + +function Lazy.is_available() + return vim.loop.fs_stat(PATHS.lazy) ~= nil +end + +--- Built-in manager implementation (Neovim 0.12+) +-- +local Builtin = {} + +function Builtin.bootstrap() + if not has_builtin_manager() then + --notify("Built-in package manager not available in this Neovim version", vim.log.levels.WARN) + return false + end + + state.bootstrap_completed.builtin = true + return true +end + +function Builtin.setup() + if not has_builtin_manager() then + --notify("Built-in package manager not available in this Neovim version", vim.log.levels.WARN) + return false + end + + local plugins = safe_require("setup.plugins") + if not plugins then + notify("Failed to load plugins configuration", vim.log.levels.ERROR) + return false + end + + -- Convert plugins to builtin manager format + local builtin_specs = {} + for _, plugin in ipairs(plugins) do + -- CHECK FOR EXCLUDE HERE + if plugin.exclude and vim.tbl_contains(plugin.exclude, "builtin") then + --notify("Excluding plugin for builtin: " .. (plugin.name or plugin[1] or "unknown"), vim.log.levels.INFO) + goto continue + end + local spec = {} + + if type(plugin) == "string" then + -- Handle string format like "user/repo" + if plugin:match("^[%w%-_%.]+/[%w%-_%.]+$") then + -- It's a GitHub shorthand + spec.src = "https://github.com/" .. plugin + spec.name = plugin:match("/([%w%-_%.]+)$") -- Extract repo name + else + -- It's already a full URL + spec.src = plugin + end + elseif type(plugin) == "table" then + -- Handle table format + if plugin[1] and type(plugin[1]) == "string" then + -- Format like {"user/repo", ...} + if plugin[1]:match("^[%w%-_%.]+/[%w%-_%.]+$") then + spec.src = "https://github.com/" .. plugin[1] + spec.name = plugin[1]:match("/([%w%-_%.]+)$") + else + spec.src = plugin[1] + end + + -- Copy other properties + for k, v in pairs(plugin) do + if type(k) == "string" then + spec[k] = v + end + end + elseif plugin.src then + spec.src = plugin.src + for k, v in pairs(plugin) do + if k ~= "src" then + spec[k] = v + end + end + elseif plugin.url then + spec.src = plugin.url + for k, v in pairs(plugin) do + if k ~= "url" then + spec[k] = v + end + end + else + notify("Invalid plugin specification for built-in manager: " .. vim.inspect(plugin), vim.log.levels.WARN) + goto continue + end + + -- Handle name override + if plugin.name then + spec.name = plugin.name + elseif plugin.as then + spec.name = plugin.as + elseif not spec.name and spec.src then + -- Extract name from URL if not specified + spec.name = spec.src:match("/([%w%-_%.]+)%.git$") or spec.src:match("/([%w%-_%.]+)$") or spec.src + end + + -- Handle version + if plugin.version then + spec.version = plugin.version + end + + -- Remove keys that builtin manager doesn't understand + spec.lazy = nil + spec.event = nil + spec.keys = nil + spec.cmd = nil + spec.ft = nil + spec.dependencies = nil + spec.config = nil + spec.build = nil + spec.run = nil + spec.priority = nil + spec.as = nil + spec.url = nil + spec.exclude = nil + spec[1] = nil -- Remove positional argument + end + + if spec.src then + table.insert(builtin_specs, spec) + end + ::continue:: + end + + -- Debug: Show what we're about to install + --notify(string.format("Installing %d plugins with built-in manager", #builtin_specs), vim.log.levels.INFO) + + -- CRITICAL FIX: Call vim.pack.add with the specs directly, not wrapped in array + if #builtin_specs > 0 then + local ok, err = pcall(vim.pack.add, builtin_specs) + if not ok then + notify("Failed to add plugins: " .. tostring(err), vim.log.levels.ERROR) + return false + end + + --notify("Plugins added successfully. Use :Pack to install/update them.", vim.log.levels.INFO) + else + notify("No valid plugins found for built-in manager", vim.log.levels.WARN) + end + + -- Create user commands for convenience - FIXED COMMAND NAMES + vim.api.nvim_create_user_command("Package", function(opts) + local subcommand = opts.fargs[1] or "update" + local names = vim.list_slice(opts.fargs, 2) + + if subcommand == "add" then + -- For add, we need to re-run setup to add new plugins + --notify("Re-running builtin manager setup to add new plugins...") + Builtin.setup() + elseif subcommand == "update" then + if #names == 0 then + names = nil -- Update all plugins + end + vim.pack.update(names) + elseif subcommand == "status" then + local plugins = vim.pack.get() + print(string.format("Built-in manager: %d plugins managed", #plugins)) + for _, plugin in ipairs(plugins) do + local status = plugin.active and "active" or "inactive" + print(string.format(" %s (%s): %s", plugin.spec.name, status, plugin.path)) + end + else + -- Default behavior - treat as update + if subcommand then + table.insert(names, 1, subcommand) + end + if #names == 0 then + names = nil + end + vim.pack.update(names) + end + end, { + nargs = "*", + complete = function(arglead, cmdline, cursorpos) + local args = vim.split(cmdline, "%s+") + if #args <= 2 then + -- Complete subcommands + local subcommands = { "add", "update", "status" } + local matches = {} + for _, cmd in ipairs(subcommands) do + if cmd:find("^" .. arglead) then + table.insert(matches, cmd) + end + end + return matches + else + -- Complete plugin names + local plugins = vim.pack.get() + local names = {} + for _, plugin in ipairs(plugins) do + if plugin.spec.name:find("^" .. arglead) then + table.insert(names, plugin.spec.name) + end + end + return names + end + end, + desc = "Manage plugins with built-in manager. Usage: :Pack [add|update|status] [plugin_names...]" + }) + + ---- Keep the old command for backwards compatibility + --vim.api.nvim_create_user_command("PackageStatus", function() + -- vim.cmd("Pack status") + --end, { + -- nargs = 0, + -- desc = "Show status of plugins managed by built-in manager (deprecated, use :Pack status)" + --}) + + return true +end + +function Builtin.is_available() + return has_builtin_manager() +end + +--- Manager registry +-- +local MANAGERS = { + packer = Packer, + lazy = Lazy, + builtin = Builtin, +} + +--- Core management functions +-- +local function activate_manager(manager_name) + local manager = MANAGERS[manager_name] + if not manager then + notify("Unknown manager: " .. manager_name, vim.log.levels.ERROR) + return false + end + + -- Cleanup the old manager before activating the new one to prevent runtime conflicts. + if state.manager_invoked and state.manager_invoked ~= manager_name then + cleanup_manager(state.manager_invoked) + end + + if not manager.bootstrap() then + return false + end + + local ok = manager.setup() + if ok then + state.manager_invoked = manager_name + -- CRITICAL FIX: Persist the manager choice after successful setup + save_manager_choice(manager_name) + end + return ok +end + +--- Auto-detection and command setup +-- +local function setup_auto_detection() + -- Autocmd to activate Packer when Packer commands are used + vim.api.nvim_create_autocmd("CmdUndefined", { + pattern = "Packer*", + callback = function(event) + if state.manager_invoked ~= "packer" then + local ok = activate_manager("packer") + if ok then + -- Re-execute the original command after setup + vim.cmd(event.match) + end + end + end, + desc = "Auto-activate Packer when Packer commands are used" + }) + + -- Autocmd to activate Lazy when Lazy commands are used + vim.api.nvim_create_autocmd("CmdUndefined", { + pattern = "Lazy*", + callback = function(event) + if state.manager_invoked ~= "lazy" then + local ok = activate_manager("lazy") + if ok then + -- CRITICAL FIX: Use vim.schedule to defer the command execution + -- This ensures Lazy's setup is complete before running the command. + vim.schedule(function() + pcall(vim.cmd, event.match) + end) + end + end + end, + desc = "Auto-activate Lazy and re-execute command" + }) + + vim.api.nvim_create_autocmd("CmdUndefined", { + pattern = "Package*", + callback = function(event) + if state.manager_invoked ~= "builtin" and has_builtin_manager() then + local ok = activate_manager("builtin") + if ok then + vim.cmd(event.match) + end + end + end, + desc = "Auto-activate built-in manager when Pack commands are used" + }) +end + +--- Public API +-- +function M.setup() + if state.initialized then + return + end + + -- Initial bootstrap attempt for all managers to see what's available + for name, manager in pairs(MANAGERS) do + -- CRITICAL FIX: Always bootstrap, but don't set up yet + pcall(manager.bootstrap) + end + + -- CRITICAL FIX: Check for a previously saved choice + local persistent_choice = load_manager_choice() + if persistent_choice and MANAGERS[persistent_choice] then + -- If a choice exists, immediately activate that manager for this session + activate_manager(persistent_choice) + else + -- If no choice exists, set up the autocmds to wait for a command + setup_auto_detection() + end + + state.initialized = true +end + +function M.use_manager(manager_name) + if not state.initialized then + M.setup() + end + + local available = M.available_managers() + if not vim.tbl_contains(available, manager_name) then + notify(string.format("Manager '%s' is not available. Available: %s", + manager_name, table.concat(available, ", ")), vim.log.levels.WARN) + return false + end + + return activate_manager(manager_name) +end + +function M.available_managers() + local managers = {} + for name, manager in pairs(MANAGERS) do + if manager.is_available() then + table.insert(managers, name) + end + end + return managers +end + +function M.current_manager() + return state.manager_invoked +end + +function M.status() + local info = { + initialized = state.initialized, + current_manager = state.manager_invoked, + available_managers = M.available_managers(), + bootstrap_completed = state.bootstrap_completed, + } + + print("=== Neovim Plugin Manager Status ===") + print(string.format("Initialized: %s", tostring(info.initialized))) + print(string.format("Current Manager: %s", info.current_manager or "None")) + print(string.format("Available Managers: %s", table.concat(info.available_managers, ", "))) + + -- FIX: Properly format the Neovim version + local major, minor, patch = get_nvim_version() + print(string.format("Neovim Version: %d.%d.%d", major, minor, patch)) + print(string.format("Built-in Support: %s", tostring(has_builtin_manager()))) + + return info +end + +-- FIX: Added M.get_nvim_version function to the public API +function M.get_nvim_version() + local major, minor, patch = get_nvim_version() + return { major = major, minor = minor, patch = patch } +end + +function M.reset_nvim() + vim.ui.input({ + prompt = "Are you sure you want to reset Neovim? This will delete all data, state, cache, and plugins. (y/N): " + }, function(input) + if input and input:lower() == "y" then + local fn = vim.fn + local is_windows = vim.loop.os_uname().version:match("Windows") + + local paths_to_remove = { + fn.stdpath("data"), + fn.stdpath("state"), + fn.stdpath("cache"), + fn.stdpath("config") .. "/plugin", + } + + local cmd = "" + if is_windows then + local paths_quoted = {} + for _, path in ipairs(paths_to_remove) do + table.insert(paths_quoted, string.format('"%s"', path)) + end + cmd = "powershell -Command \"Remove-Item " .. + table.concat(paths_quoted, ", ") .. " -Recurse -Force -ErrorAction SilentlyContinue\"" + else + local paths_quoted = {} + for _, path in ipairs(paths_to_remove) do + table.insert(paths_quoted, vim.fn.shellescape(path)) + end + cmd = "rm -rf " .. table.concat(paths_quoted, " ") + end + + notify("Resetting Neovim... Please restart after this operation.") + + vim.defer_fn(function() + local result = os.execute(cmd) + if result ~= 0 then + notify("Reset command may have failed. You might need to delete directories manually.", vim.log.levels.WARN) + else + notify("Reset completed successfully. Please restart Neovim.") + end + end, 100) + else + notify("Reset cancelled.") + end + end) +end + +-- Clear manager choice function +function M.clear_choice() + vim.g.nvim_manager_choice = nil + local data_dir = vim.fn.stdpath("data") + local choice_file = data_dir .. "/.manager_choice" + os.remove(choice_file) + notify("Manager choice cleared. Next command will determine the manager.") +end + +vim.api.nvim_create_user_command("Reset", function() + M.reset_nvim() +end, { + nargs = 0, + desc = "Reset Neovim's data, state, cache, and plugin directories" +}) + +local function manager_command(opts) + local subcommand = opts.fargs[1] + + if subcommand == "status" then + M.status() + elseif subcommand == "packer" or subcommand == "Packer" then + M.use_manager("packer") + elseif subcommand == "lazy" or subcommand == "Lazy" then + M.use_manager("lazy") + elseif subcommand == "builtin" or subcommand == "built-in" or subcommand == "Builtin" or subcommand == "Built-in" then + M.use_manager("builtin") + elseif subcommand == "clear" then + M.clear_choice() + else + print("Unknown subcommand. Try 'status', 'packer', 'lazy', 'builtin' or 'clear'.") + end +end + +vim.api.nvim_create_user_command("Manager", manager_command, { + nargs = "+", + complete = function(arglead) + local subcommands = { "status", "packer", "Packer", "lazy", "Lazy", "builtin", "built-in", "Builtin", "Built-in", + "clear" } + local result = {} + for _, subcommand in ipairs(subcommands) do + if subcommand:find("^" .. arglead, 1) then + table.insert(result, subcommand) + end + end + return result + end, + desc = "Manage plugins. Subcommands: status, packer, lazy, builtin, clear" +}) + +return M diff --git a/common/config/nvim/lua/setup/plugins.lua b/common/config/nvim/lua/setup/plugins.lua new file mode 100755 index 0000000..0fb0886 --- /dev/null +++ b/common/config/nvim/lua/setup/plugins.lua @@ -0,0 +1,609 @@ +-- plugins.lua + +-- Helper to compare current Neovim version +local function version_at_least(minor, major) + local v = vim.version() + major = major or 0 + return v.major > major or (v.major == major and v.minor >= minor) +end + +local function version_below(minor, major) + local v = vim.version() + major = major or 0 + return v.major < major or (v.major == major and v.minor < minor) +end + +-- Normalize version input: number -> {0, number}, table -> itself +local function parse_version(ver) + if type(ver) == "number" then return 0, ver end + if type(ver) == "table" then return ver[1] or 0, ver[2] or 0 end + return 0, 0 +end + +-- Determine if plugin should be loaded based on version +local function should_load_plugin(min_version, max_version) + local min_major, min_minor = parse_version(min_version) + local max_major, max_minor = parse_version(max_version) + + local ok_min = not min_version or version_at_least(min_minor, min_major) + local ok_max = not max_version or version_below(max_minor, max_major) + + return ok_min and ok_max +end + +-- Helper to check if a table contains a specific value +local function contains(table, val) + for _, v in ipairs(table) do + if v == val then + return true + end + end + return false +end + +-- The master list of plugins with all potential options. +-- Keys like 'lazy', 'event', 'keys', 'dependencies' are for Lazy.nvim. +-- Keys like 'config', 'run', 'build' are for all managers. +local universal_plugins = { + -- Core + { "nvim-lua/plenary.nvim", lazy = true }, + { "lewis6991/impatient.nvim" }, + + { + "nvim-treesitter/nvim-treesitter", + min_version = 9, + event = "BufReadPre", + }, + { "nvim-treesitter/nvim-treesitter-textobjects", dependencies = { "nvim-treesitter/nvim-treesitter" } }, + { "nvim-treesitter/playground", cmd = "TSPlaygroundToggle" }, + + -- LSP + { "nvimtools/none-ls.nvim", event = "BufReadPre" }, + { "neovim/nvim-lspconfig", min_version = { 0, 9 }, event = "BufReadPre" }, + { + "mason-org/mason.nvim", + min_version = 10, + cmd = "Mason", + event = "BufReadPre", + }, + { "mason-org/mason-lspconfig.nvim", dependencies = { "mason-org/mason.nvim" } }, + { + "whoissethdaniel/mason-tool-installer.nvim", + dependencies = { "mason-org/mason.nvim" }, + event = "BufReadPre", + }, + { + "https://git.sr.ht/~whynothugo/lsp_lines.nvim", + name = 'lsp_lines.nvim', + config = function() + require("lsp_lines").setup() + vim.diagnostic.config({ + virtual_text = false, + }) + end, + event = "LspAttach", + }, + { "rmagatti/goto-preview", event = "LspAttach" }, + + -- Linters/Formatters + { "mhartington/formatter.nvim", event = "BufReadPre" }, + { + "jay-babu/mason-null-ls.nvim", + event = "BufReadPre", + }, + + -- Completion + { "hrsh7th/nvim-cmp", event = "InsertEnter", exclude = { "builtin" } }, + { "hrsh7th/cmp-nvim-lsp", dependencies = { "hrsh7th/nvim-cmp" }, exclude = { "builtin" } }, + { "hrsh7th/cmp-buffer", dependencies = { "hrsh7th/nvim-cmp" }, exclude = { "builtin" } }, + { "hrsh7th/cmp-path", dependencies = { "hrsh7th/nvim-cmp" }, exclude = { "builtin" } }, + { "hrsh7th/cmp-cmdline", dependencies = { "hrsh7th/nvim-cmp" }, exclude = { "builtin" } }, + { "petertriho/cmp-git", dependencies = { "hrsh7th/nvim-cmp" }, exclude = { "builtin" } }, + { "tamago324/cmp-zsh", dependencies = { "hrsh7th/nvim-cmp" }, exclude = { "builtin" } }, + { "f3fora/cmp-spell", dependencies = { "hrsh7th/nvim-cmp" }, exclude = { "builtin" } }, + { "hrsh7th/cmp-calc", dependencies = { "hrsh7th/nvim-cmp" }, exclude = { "builtin" } }, + { "saadparwaiz1/cmp_luasnip", dependencies = { "hrsh7th/nvim-cmp" }, exclude = { "builtin" } }, + { "hrsh7th/cmp-nvim-lsp-signature-help", dependencies = { "hrsh7th/nvim-cmp" }, exclude = { "builtin" } }, + { "rcarriga/cmp-dap", dependencies = { "hrsh7th/nvim-cmp" }, exclude = { "builtin" } }, + { "micangl/cmp-vimtex", dependencies = { "hrsh7th/nvim-cmp" }, exclude = { "builtin" } }, + + -- Snippets + { "L3MON4D3/LuaSnip", event = "InsertEnter" }, + { "rafamadriz/friendly-snippets", dependencies = { "L3MON4D3/LuaSnip" } }, + + -- Git + { "tpope/vim-fugitive", cmd = { "G", "Git", "Gdiffsplit" }, event = "VeryLazy" }, + { "kdheepak/lazygit.nvim", cmd = "LazyGit", keys = "<leader>gg" }, + { "lewis6991/gitsigns.nvim", min_version = 11, dependencies = { "nvim-lua/plenary.nvim" }, event = "BufReadPre" }, + + -- UI/UX & Enhancements + { "rcarriga/nvim-notify", lazy = false }, + { + "nvim-tree/nvim-tree.lua", + --cmd = { "NvimTreeToggle", "NvimTreeFocus", "NvimTreeFindFile" }, + --keys = { "<C-n>", "<leader>e" }, + lazy = false, + dependencies = { "nvim-tree/nvim-web-devicons"}, + config = function() + require("plugins.nvim-tree").setup() + end, + }, + { "ThePrimeagen/harpoon", keys = { "<leader>h" } }, + { "airblade/vim-rooter", event = "BufEnter" }, + { "ibhagwan/fzf-lua", cmd = "FzfLua" }, + + --- **Telescope** --- + { + "nvim-telescope/telescope.nvim", + dependencies = { "nvim-lua/plenary.nvim" }, + config = function() + require("plugins.telescope").setup() + end, + }, + { + "nvim-telescope/telescope-fzf-native.nvim", + build = "make", + cond = function() + return vim.fn.executable("make") == 1 + end, + dependencies = { "nvim-telescope/telescope.nvim" }, + }, + { "nvim-telescope/telescope-live-grep-args.nvim", dependencies = { "nvim-telescope/telescope.nvim" } }, + { "nvim-telescope/telescope-ui-select.nvim", dependencies = { "nvim-telescope/telescope.nvim" } }, + { "nvim-telescope/telescope-project.nvim", dependencies = { "nvim-telescope/telescope.nvim" } }, + { "nvim-telescope/telescope-media-files.nvim", dependencies = { "nvim-telescope/telescope.nvim" } }, + { "nvim-telescope/telescope-file-browser.nvim", dependencies = { "nvim-telescope/telescope.nvim" } }, + { "nvim-telescope/telescope-symbols.nvim", dependencies = { "nvim-telescope/telescope.nvim" } }, + { "nvim-telescope/telescope-dap.nvim", dependencies = { "nvim-telescope/telescope.nvim" } }, + { "axkirillov/telescope-changed-files", dependencies = { "nvim-telescope/telescope.nvim" } }, + { "smartpde/telescope-recent-files", dependencies = { "nvim-telescope/telescope.nvim" } }, + --- End Telescope --- + + -- Neovim UX + { "folke/neodev.nvim", ft = "lua" }, + { + "numToStr/Navigator.nvim", + lazy = false, + config = function() + require("Navigator").setup() + end, + }, + { "tpope/vim-eunuch", cmd = { "Rename", "Delete", "Mkdir" } }, + { "tpope/vim-unimpaired", lazy = true, event = "VeryLazy" }, + { "kylechui/nvim-surround", event = "VeryLazy" }, + { + "mbbill/undotree", + cmd = "UndotreeToggle", + keys = "<leader>u", + event = "BufReadPre" + }, + { + "myusuf3/numbers.vim", + event = "BufReadPost", + config = function() + vim.cmd("let g:numbers_exclude = ['dashboard']") + end, + }, + { "windwp/nvim-autopairs", event = "InsertEnter" }, + { "numToStr/Comment.nvim", keys = { "gc", "gb" }, event = "VeryLazy" }, + { "akinsho/toggleterm.nvim", cmd = { "ToggleTerm", "TermExec" } }, + { "tweekmonster/startuptime.vim", cmd = "StartupTime" }, + { "qpkorr/vim-bufkill", cmd = { "BD", "BUN" } }, + { + "ggandor/leap.nvim", + keys = { "s", "S" }, + event = "VeryLazy", + config = function() + require("leap").add_default_mappings() + end, + }, + { + "ggandor/flit.nvim", + keys = { "f", "F", "t", "T" }, + event = "VeryLazy", + config = function() + require("flit").setup() + end, + }, + { + "folke/which-key.nvim", + min_version = { 0, 10 }, + event = "VeryLazy", + --keys = "<leader>", + config = function() + require("which-key").setup() + end, + }, + { "folke/zen-mode.nvim", cmd = "ZenMode" }, + { "romainl/vim-cool", event = "VeryLazy" }, + { "antoinemadec/FixCursorHold.nvim", lazy = true }, + { "folke/trouble.nvim", cmd = { "Trouble", "TroubleToggle" } }, + + -- Colorschemes & Visuals (load immediately) + { "nyoom-engineering/oxocarbon.nvim", lazy = false, priority = 1000 }, + { "bluz71/vim-nightfly-guicolors", lazy = false, priority = 1000 }, + { "ayu-theme/ayu-vim", lazy = false, priority = 1000 }, + { "joshdick/onedark.vim", lazy = false, priority = 1000 }, + { "NTBBloodbath/doom-one.nvim", lazy = false, priority = 1000 }, + { "nyngwang/nvimgelion", lazy = false, priority = 1000 }, + { "projekt0n/github-nvim-theme", lazy = false, priority = 1000 }, + { "folke/tokyonight.nvim", lazy = false, priority = 1000 }, + { "ribru17/bamboo.nvim", lazy = false, priority = 1000 }, + + -- UI Enhancements + --{ "kyazdani42/nvim-web-devicons", lazy = true }, + { "nvim-tree/nvim-web-devicons", lazy = true }, + { "onsails/lspkind-nvim", dependencies = { "hrsh7th/nvim-cmp" }, exclude = { "builtin" } }, + { "kevinhwang91/nvim-ufo", dependencies = { "kevinhwang91/promise-async" }, event = "BufReadPre" }, + { + "luukvbaal/statuscol.nvim", + event = "WinNew", + config = function() + local builtin = require("statuscol.builtin") + require("statuscol").setup({ + relculright = true, + segments = { + { text = { builtin.foldfunc }, click = "v:lua.ScFa" }, + { text = { "%s" }, click = "v:lua.ScSa" }, + { text = { builtin.lnumfunc, " " }, click = "v:lua.ScLa" }, + }, + }) + end, + }, + { "lukas-reineke/indent-blankline.nvim", event = "BufReadPre" }, + { + "glepnir/dashboard-nvim", + dependencies = { "nvim-tree/nvim-web-devicons" }, + cmd = "Dashboard", + }, + { "karb94/neoscroll.nvim", event = "BufReadPre" }, + { "MunifTanjim/prettier.nvim", event = "BufWritePre" }, + { + "norcalli/nvim-colorizer.lua", + cmd = { "ColorizerToggle", "ColorizerAttachToBuffer" }, + config = function() + require("colorizer").setup({ + user_default_options = { + RGB = true, + RRGGBB = true, + names = false, + RRGGBBAA = false, + css = false, + css_fn = true, + mode = "foreground", + }, + }) + end, + }, + { "MunifTanjim/nui.nvim" }, + { "metakirby5/codi.vim", cmd = "Codi" }, + { + "kosayoda/nvim-lightbulb", + dependencies = { "antoinemadec/FixCursorHold.nvim" }, + event = "LspAttach", + }, + { "SmiteshP/nvim-navic", event = "LspAttach" }, + { + "rebelot/heirline.nvim", + event = "VeryLazy", + dependencies = { + "nvim-tree/nvim-web-devicons", -- For file icons + "lewis6991/gitsigns.nvim", -- For git status + }, + config = function() + -- Ensure gitsigns is loaded before Heirline + if package.loaded["gitsigns"] == nil then + require("gitsigns").setup() + end + local ok, heirline = pcall(require, "plugins.heirline") + if ok and heirline then + heirline.setup() + else + vim.notify("Failed to load Heirline configuration", vim.log.levels.ERROR) + end + end, + init = function() + -- Set up the statusline to use Heirline once it's loaded + vim.opt.statusline = "%{%v:lua.require'heirline'.eval_statusline()%}" + vim.opt.winbar = "%{%v:lua.require'heirline'.eval_winbar()%}" + vim.opt.tabline = "%{%v:lua.require'heirline'.eval_tabline()%}" + end, + }, + { + "samodostal/image.nvim", + config = function() + require("image").setup({}) + end, + ft = { "markdown" }, + }, + + -- Language Specific + { "simrat39/rust-tools.nvim", ft = "rust" }, + { + "saecki/crates.nvim", + dependencies = { "nvim-lua/plenary.nvim" }, + config = function() + require("crates").setup() + end, + ft = "rust", + }, + { + "akinsho/flutter-tools.nvim", + dependencies = { + "nvim-lua/plenary.nvim", + "stevearc/dressing.nvim", + }, + config = function() + require("flutter-tools").setup({ + debugger = { + enabled = true, + run_via_dap = true, + }, + }) + end, + ft = "dart", + }, + { + "iamcco/markdown-preview.nvim", + build = "cd app && npm install", + ft = "markdown", + config = function() + vim.g.mkdp_filetypes = { "markdown" } + vim.cmd("let g:mkdp_auto_close = 0") + end, + cmd = "MarkdownPreview", + }, + { + "ellisonleao/glow.nvim", + config = function() + local glow_path = vim.fn.executable("~/.local/bin/glow") == 1 and "~/.local/bin/glow" or "/usr/bin/glow" + require("glow").setup({ + style = "dark", + glow_path = glow_path, + }) + end, + ft = "markdown", + }, + + -- Debugging + { "mfussenegger/nvim-dap", event = "VeryLazy" }, + { "rcarriga/nvim-dap-ui", dependencies = { "mfussenegger/nvim-dap" }, cmd = "DapUI" }, + { "theHamsta/nvim-dap-virtual-text", dependencies = { "mfussenegger/nvim-dap" } }, + { "gabrielpoca/replacer.nvim", cmd = "Replacer" }, + { "jayp0521/mason-nvim-dap.nvim", dependencies = { "mason-org/mason.nvim" } }, + + -- Misc + { "rmagatti/auto-session", event = "VimEnter" }, + { "tpope/vim-sleuth", lazy = true }, + { "michaelb/sniprun", build = "bash ./install.sh", cmd = "SnipRun" }, + { "stevearc/overseer.nvim", cmd = "Overseer" }, + { + "nvim-neotest/neotest", + dependencies = { + "nvim-neotest/neotest-python", + "nvim-neotest/neotest-plenary", + "nvim-neotest/neotest-vim-test", + "nvim-neotest/nvim-nio", + }, + cmd = "Neotest", + }, + { "kawre/leetcode.nvim", cmd = "Leetcode" }, + { "m4xshen/hardtime.nvim", lazy = true }, + + -- LaTeX + { "lervag/vimtex", ft = "tex" }, +} + +-- Helper function to detect current manager +local function detect_current_manager() + -- Check if we're currently using lazy (by checking if lazy module exists) + if package.loaded["lazy"] or package.loaded["lazy.core.util"] then + return "lazy" + end + + -- Check if we're currently using packer + if package.loaded["packer"] then + return "packer" + end + + -- Check for builtin manager + if vim.plugins and vim.plugins.spec then + return "builtin" + end + + return "unknown" +end + +local function format_for_lazy(plugin) + -- Lazy.nvim's format is the closest to our universal format, so we can + -- largely just copy the table, with some specific adjustments. + local new_plugin = vim.deepcopy(plugin) + + -- Lazy.nvim uses `dependencies` key for dependencies, not `requires` + if new_plugin.requires then + new_plugin.dependencies = new_plugin.requires + new_plugin.requires = nil + end + + -- Change 'as' to 'name' for lazy + if new_plugin.as then + new_plugin.name = new_plugin.as + new_plugin.as = nil + end + + -- Change 'run' to 'build' for lazy + if new_plugin.run then + new_plugin.build = new_plugin.run + new_plugin.run = nil + end + + return new_plugin +end + +local function format_for_packer(plugin) + -- For Packer, we need to remove lazy-loading keys to force eager loading + local new_plugin = vim.deepcopy(plugin) + + -- Convert dependencies back to requires for packer + if new_plugin.dependencies then + new_plugin.requires = new_plugin.dependencies + new_plugin.dependencies = nil + end + + -- Convert name back to as for packer + if new_plugin.name then + new_plugin.as = new_plugin.name + new_plugin.name = nil + end + + -- Convert build back to run for packer + if new_plugin.build then + new_plugin.run = new_plugin.build + new_plugin.build = nil + end + + -- Remove lazy-loading keys to force eager loading in packer + new_plugin.event = nil + new_plugin.keys = nil + new_plugin.cmd = nil + new_plugin.ft = nil + new_plugin.lazy = nil + + return new_plugin +end + +local function format_for_builtin(plugin) + -- This function is now simplified, as the main loop handles flattening + local new_plugin = vim.deepcopy(plugin) + + -- Convert GitHub shorthand to full URL if needed + if type(new_plugin) == "string" then + if new_plugin:match("^[%w%-_%.]+/[%w%-_%.]+$") then + return { + src = "https://github.com/" .. new_plugin, + name = new_plugin:match("/([%w%-_%.]+)$") + } + else + return { src = new_plugin } + end + end + + -- Handle table format + if new_plugin[1] and type(new_plugin[1]) == "string" then + local repo = new_plugin[1] + if repo:match("^[%w%-_%.]+/[%w%-_%.]+$") then + new_plugin.src = "https://github.com/" .. repo + new_plugin.name = new_plugin.name or new_plugin.as or repo:match("/([%w%-_%.]+)$") + else + new_plugin.src = repo + end + new_plugin[1] = nil -- Remove positional argument + end + + -- Convert url to src if present + if new_plugin.url then + new_plugin.src = new_plugin.url + new_plugin.url = nil + end + + -- Convert 'as' to 'name' + if new_plugin.as then + new_plugin.name = new_plugin.as + new_plugin.as = nil + end + + -- Only keep the keys that vim.pack uses: src, name, and version + new_plugin.dependencies = nil + new_plugin.config = nil + new_plugin.build = nil + new_plugin.run = nil + new_plugin.cond = nil + new_plugin.min_version = nil + new_plugin.max_version = nil + new_plugin.lazy = nil + new_plugin.priority = nil + new_plugin.event = nil + new_plugin.keys = nil + new_plugin.cmd = nil + new_plugin.ft = nil + new_plugin.requires = nil + + return new_plugin +end + +-- Detect which manager is currently active and format plugins accordingly +local current_manager = detect_current_manager() +local plugins_to_process = {} +local processed_plugins = {} -- Use a set to avoid duplicates + +-- Flatten the plugin list for the builtin manager +if current_manager == "builtin" then + local function get_plugin_name(plugin) + if type(plugin) == "string" then + return plugin:match("/([%w%-_%.]+)$") or plugin + elseif type(plugin) == "table" then + -- Get name from 'name', 'as', or from the src/url + return plugin.name or plugin.as or (type(plugin[1]) == "string" and plugin[1]:match("/([%w%-_%.]+)$")) or + plugin.url:match("/([%w%-_%.]+)$") + end + end + + local function add_to_process(plugin) + local name = get_plugin_name(plugin) + if name and not processed_plugins[name] then + table.insert(plugins_to_process, plugin) + processed_plugins[name] = true + end + end + + for _, plugin in ipairs(universal_plugins) do + add_to_process(plugin) + if plugin.dependencies then + for _, dep in ipairs(plugin.dependencies) do + add_to_process(dep) + end + end + if plugin.requires then + for _, req in ipairs(plugin.requires) do + add_to_process(req) + end + end + end +else + plugins_to_process = universal_plugins +end + +local finalized_plugins = {} + +for _, plugin in ipairs(plugins_to_process) do + local cond_ok = true + + -- Check for the new 'exclude' option first + if plugin.exclude and contains(plugin.exclude, current_manager) then + cond_ok = false + end + + if cond_ok and (plugin.min_version or plugin.max_version) then + cond_ok = should_load_plugin(plugin.min_version, plugin.max_version) + end + if cond_ok and plugin.cond then + cond_ok = plugin.cond() + end + + if cond_ok then + local new_plugin + if current_manager == "lazy" then + new_plugin = format_for_lazy(plugin) + elseif current_manager == "packer" then + new_plugin = format_for_packer(plugin) + elseif current_manager == "builtin" then + new_plugin = format_for_builtin(plugin) + else + -- Default to lazy format if manager is unknown + new_plugin = format_for_lazy(plugin) + end + table.insert(finalized_plugins, new_plugin) + end +end + +return finalized_plugins diff --git a/common/config/nvim/lua/user/keys.lua b/common/config/nvim/lua/user/keys.lua index 87d852b..63b64fa 100644..100755 --- a/common/config/nvim/lua/user/keys.lua +++ b/common/config/nvim/lua/user/keys.lua @@ -1,19 +1,134 @@ -local keymap = vim.keymap +-- ============================================================================ +-- Key Mappings +-- ============================================================================ + local map = function(mode, l, r, opts) - opts = opts or {} - opts.silent = true - opts.noremap = true - keymap.set(mode, l, r, opts) + if r == nil then + vim.notify("Attempted to map key '" .. l .. "' but RHS is nil", vim.log.levels.WARN) + return + end + opts = vim.tbl_extend('force', { + silent = true, + noremap = true + }, opts or {}) + vim.keymap.set(mode, l, r, opts) end -local term_opts = { noremap = true, silent = false } -local mods = require("user.mods") -local bufnr = vim.api.nvim_get_current_buf() --- Semi-colon as leader key +-- Leader key vim.g.mapleader = ";" +vim.g.maplocalleader = "\\" + +-- Tmux/Vim navigation +local function smart_move(direction, tmux_cmd) + local curwin = vim.api.nvim_get_current_win() + vim.cmd('wincmd ' .. direction) + if curwin == vim.api.nvim_get_current_win() then + vim.fn.system('tmux select-pane ' .. tmux_cmd) + end +end + +-- Window Navigation +map('n', '<C-h>', function() smart_move('h', '-L') end) +map('n', '<C-j>', function() smart_move('j', '-D') end) +map('n', '<C-k>', function() smart_move('k', '-U') end) +map('n', '<C-l>', function() smart_move('l', '-R') end) + +-- Buffer Navigation +map('n', '<leader>bn', '<cmd>bnext<CR>') +map('n', '<leader>bp', '<cmd>bprevious<CR>') +--map('n', '<leader>bd', '<cmd>bdelete<CR>') +map('n', '<leader>ba', '<cmd>%bdelete<CR>') + + + +-- Get list of loaded buffers in order +local function get_buffers() + local bufs = {} + for _, buf in ipairs(vim.api.nvim_list_bufs()) do + if vim.api.nvim_buf_is_loaded(buf) then + table.insert(bufs, buf) + end + end + return bufs +end + +-- Swap two buffers by index in the buffer list +local function swap_buffers(idx1, idx2) + local bufs = get_buffers() + local buf1 = bufs[idx1] + local buf2 = bufs[idx2] + if not buf1 or not buf2 then return end + local name1 = vim.api.nvim_buf_get_name(buf1) + local name2 = vim.api.nvim_buf_get_name(buf2) + vim.cmd("b " .. buf1) + vim.cmd("file " .. name2) + vim.cmd("b " .. buf2) + vim.cmd("file " .. name1) +end + +-- Move current buffer left +vim.keymap.set("n", "<leader>bh", function() + local bufs = get_buffers() + local curr = vim.api.nvim_get_current_buf() + local idx + for i, b in ipairs(bufs) do if b == curr then idx = i break end end + if idx and idx > 1 then + swap_buffers(idx, idx-1) + end +end, { noremap = true, silent = true }) + +-- Move current buffer right +vim.keymap.set("n", "<leader>bl", function() + local bufs = get_buffers() + local curr = vim.api.nvim_get_current_buf() + local idx + for i, b in ipairs(bufs) do if b == curr then idx = i break end end + if idx and idx < #bufs then + swap_buffers(idx, idx+1) + end +end, { noremap = true, silent = true }) +-- Save and Quit +map('n', '<leader>w', '<cmd>w<CR>') +map('n', '<leader>q', '<cmd>q<CR>') +map('n', '<leader>wq', '<cmd>wq<CR>') +map('n', '<leader>Q', '<cmd>qa!<CR>') + +-- Resize Windows +map('n', '<M-Up>', '<cmd>resize -2<CR>') +map('n', '<M-Down>', '<cmd>resize +2<CR>') +map('n', '<M-Left>', '<cmd>vertical resize -2<CR>') +map('n', '<M-Right>', '<cmd>vertical resize +2<CR>') + +-- Quickfix and Location List +map('n', ']q', '<cmd>cnext<CR>zz') +map('n', '[q', '<cmd>cprev<CR>zz') +map('n', ']l', '<cmd>lnext<CR>zz') +map('n', '[l', '<cmd>lprev<CR>zz') + +-- Terminal Mode +map('t', '<Esc>', '<C-\\><C-n>') +map('t', '<C-h>', '<C-\\><C-n><C-w>h') +map('t', '<C-j>', '<C-\\><C-n><C-w>j') +map('t', '<C-k>', '<C-\\><C-n><C-w>k') +map('t', '<C-l>', '<C-\\><C-n><C-w>l') + +-- Insert mode escape +map('i', 'jk', '<ESC>') + +-- Tmux/(n)vim navigation +local function smart_move(direction, tmux_cmd) + local curwin = vim.api.nvim_get_current_win() + vim.cmd('wincmd ' .. direction) + if curwin == vim.api.nvim_get_current_win() then + vim.fn.system('tmux select-pane ' .. tmux_cmd) + end +end + +map('n', '<C-h>', function() smart_move('h', '-L') end, {silent = true}) +map('n', '<C-j>', function() smart_move('j', '-D') end, {silent = true}) +map('n', '<C-k>', function() smart_move('k', '-U') end, {silent = true}) +map('n', '<C-l>', function() smart_move('l', '-R') end, {silent = true}) --- "jk" and "kj" to exit insert-mode -map("i", "jk", "<esc>") -- Jump to next match on line using `.` instead of `;` NOTE: commented out in favour of "ggandor/flit.nvim" --map("n", ".", ";") @@ -23,29 +138,84 @@ map("i", "jk", "<esc>") -- Reload nvim config map("n", "<leader><CR>", - "<cmd>luafile ~/.config/nvim/init.lua<CR> | :echom ('Nvim config loading...') | :sl! | echo ('')<CR>") +"<cmd>luafile ~/.config/nvim/init.lua<CR> | :echom ('Nvim config loading...') | :sl! | echo ('')<CR>") + +vim.keymap.set("t", "<Esc>", "<C-\\><C-n>", { desc = "Exit terminal mode" }) +vim.keymap.set("t", "<C-b>", "<C-\\><C-n>", { desc = "Exit terminal mode" }) --------------- Extended Operations --------------- -- Conditional 'q' to quit on floating/quickfix/help windows otherwise still use it for macros -- TODO: Have a list of if available on system/packages, example "Zen Mode" to not work on it (quit Zen Mode) map("n", "q", function() - local config = vim.api.nvim_win_get_config(0) - if config.relative ~= "" then -- is_floating_window? - return ":silent! close!<CR>" - elseif vim.o.buftype == "quickfix" then - return ":quit<CR>" - elseif vim.o.buftype == "help" then - return ":close<CR>" - else - return "q" - end + local config = vim.api.nvim_win_get_config(0) + if config.relative ~= "" then -- is_floating_window? + return ":silent! close!<CR>" + elseif vim.o.buftype == "quickfix" then + return ":quit<CR>" + elseif vim.o.buftype == "help" then + return ":close<CR>" + else + return "q" + end end, { expr = true, replace_keycodes = true }) +-- Minimalist Tab Completion +map("i", "<Tab>", function() + local col = vim.fn.col('.') - 1 + local line = vim.fn.getline('.') + local prev_char = line:sub(col, col) + if vim.fn.pumvisible() == 1 or prev_char:match("%w") then + return vim.api.nvim_replace_termcodes("<C-n>", true, true, true) + else + return vim.api.nvim_replace_termcodes("<Tab>", true, true, true) + end +end, { expr = true }) + +-- Shift-Tab for reverse completion +map("i", "<S-Tab>", function() + if vim.fn.pumvisible() == 1 then + return vim.api.nvim_replace_termcodes("<C-p>", true, true, true) + else + return vim.api.nvim_replace_termcodes("<S-Tab>", true, true, true) + end +end, { expr = true }) + + +-- Toggle completion +map("n", "<Leader>tc", ':lua require("user.mods").toggle_completion()<CR>') + +-- Minimalist Auto Completion +map("i", "<CR>", function() + -- Exit this keymap if nvim-cmp is present + local cmp_is_present, _ = pcall(require, "cmp") + if cmp_is_present and require("cmp").visible() then + return vim.api.nvim_replace_termcodes("<C-y>", true, true, true) + elseif cmp_is_present then + return vim.api.nvim_replace_termcodes("<CR>", true, true, true) + end + + -- when cmp is NOT present + if vim.fn.pumvisible() == 1 then + return vim.api.nvim_replace_termcodes("<C-y>", true, true, true) + else + return vim.api.nvim_replace_termcodes("<CR>", true, true, true) + end +end, { expr = true }) + +-- Closing compaction in insert mode +map("i", "[", "[]<Left>") +map("i", "(", "()<Left>") +map("i", "{", "{}<Left>") +map("i", "/*", "/**/<Left><Left>") + -- Edit new file -map("n", "<leader>;", [[:e <C-R>=expand("%:h")..'/'<CR>]], { noremap = true, silent = true, desc = "New file" }) +map("n", "<leader>e", [[:e <C-R>=expand("%:h")..'/'<CR>]], { noremap = true, silent = true, desc = "New file" }) -- Write as sudo -map("c", "W", "exe 'w !sudo tee >/dev/null %:p:S' | setl nomod", { silent = true, desc = "Write as Sudo" }) +map("c", "W!", "exe 'w !sudo tee >/dev/null %:p:S' | setl nomod", { silent = true, desc = "Write as Sudo" }) + +-- Don't format on save +map("c", "F!", ":noautocmd w<CR>") -- Combine buffers list with buffer name map("n", "<Leader>b", ":buffers<CR>:buffer<Space>") @@ -60,20 +230,25 @@ map("n", "<leader>d", ":bd<cr>") map("n", "<TAB>", ":bnext<CR>") map("n", "<S-TAB>", ":bprevious<CR>") +-- Close all buffers and reopen last one +map("n", "<leader>D", ":update | %bdelete | edit # | normal `<CR>") + -- Delete file of current buffer map("n", "<leader>rm", "<CMD>call delete(expand('%')) | bdelete!<CR>") -- List marks -map("n", "<Leader>m", ":marks<CR>") +map("n", "<Leader>M", ":marks<CR>") -- Messages -map("n", "<Leader>M", ":messages<CR>") +map("n", "<Leader>m", ":messages<CR>") --- Clear messages or just refresh/redraw the screen -map("n", "<leader>i", "<cmd>lua require('notify').dismiss()<CR>") - --- Unsets the 'last search pattern' register by hitting return ---map("n", "<CR>", "!silent :noh<CR><CR>") +map("n", "<leader>i", function() + local ok, notify = pcall(require, "notify") + if ok then + notify.dismiss() + end +end) -- Toggle set number map("n", "<leader>$", ":NumbersToggle<CR>") @@ -89,20 +264,36 @@ map("t", "<C-l>", "<C-\\><C-N><C-l>") -- Split window map("n", "<leader>-", ":split<CR>") map("n", "<leader>\\", ":vsplit<CR>") -map("n", "<leader>c", "<C-w>c") + +-- Close window +--map("n", "<leader>c", "<C-w>c") +map({ "n", "t", "c" }, "<leader>c", function() + local winid = vim.api.nvim_get_current_win() + local config = vim.api.nvim_win_get_config(winid) + + if config.relative ~= "" then + -- This is a floating window + vim.cmd("CloseFloatingWindows") + else + -- Not a float/close window + vim.cmd("close") + end +end, { desc = "Close current float or all floating windows" }) -- Resize Panes ---map('n', '<Leader>+', ':resize +5<CR>') ---map('n', '<Leader>-', ':resize -5<CR>') map("n", "<Leader><", ":vertical resize +5<CR>") map("n", "<Leader>>", ":vertical resize -5<CR>") map("n", "<Leader>=", "<C-w>=") +-- Mapping for left and right arrow keys in command-line mode +vim.api.nvim_set_keymap("c", "<A-h>", "<Left>", { noremap = true, silent = false }) -- Left Arrow +vim.api.nvim_set_keymap("c", "<A-l>", "<Right>", { noremap = true, silent = false }) -- Right Arrow + -- Map Alt+(h/j/k/l) in insert(include terminal/command) mode to move directional -map({ "i", "t", "c" }, "<A-h>", "<left>") -map({ "i", "t", "c" }, "<A-j>", "<down>") -map({ "i", "t", "c" }, "<A-k>", "<up>") -map({ "i", "t", "c" }, "<A-l>", "<right>") +map({ "i", "t" }, "<A-h>", "<Left>") +map({ "i", "t" }, "<A-j>", "<Down>") +map({ "i", "t" }, "<A-k>", "<Up>") +map({ "i", "t" }, "<A-l>", "<Right>") -- Create tab, edit and move between them map("n", "<C-T>n", ":tabnew<CR>") @@ -110,10 +301,6 @@ map("n", "<C-T>e", ":tabedit") map("n", "<leader>[", ":tabprev<CR>") map("n", "<leader>]", ":tabnext<CR>") --- "Zoom" a split window into a tab and/or close it ---map("n", "<Leader>,", ":tabnew %<CR>") ---map("n", "<Leader>.", ":tabclose<CR>") - -- Vim TABs map("n", "<leader>1", "1gt<CR>") map("n", "<leader>2", "2gt<CR>") @@ -134,10 +321,26 @@ map("n", "<leader>0", "10gt<CR>") --map("n", ">", ">>", term_opts) --map("x", "<", "<gv", term_opts) --map("x", ">", ">gv", term_opts) -map("v", "<", "<gv") -map("v", ">", ">gv") -map("n", "<", "<S-v><<esc>") -map("n", ">", "<S-v>><esc>") +--map("v", "<", "<gv") +--map("v", ">", ">gv") +--map("n", "<", "<S-v><<esc>change mode to normal") +--map("n", ">", "<S-v>><esc>change mode to normal") + +-- Visual mode: Indent and reselect the visual area, like default behavior but explicit +map("v", "<", "<gv", { desc = "Indent left and reselect" }) +map("v", ">", ">gv", { desc = "Indent right and reselect" }) + +-- Normal mode: Indent current line and enter Visual Line mode to repeat easily +map("n", "<", "v<<", { desc = "Indent left and select" }) +map("n", ">", "v>>", { desc = "Indent right and select" }) + +---- Visual mode: Indent and reselect the visual area, like default behavior but explicit +--map("v", "<", "<", { desc = "Indent left" }) +--map("v", ">", ">", { desc = "Indent right" }) +-- +---- Normal mode: Indent current line and enter Visual Line mode to repeat easily +--map("n", "<", "v<<", { desc = "Indent left and select" }) +--map("n", ">", "v>>", { desc = "Indent right and select" }) -- Set alt+(j/k) to switch lines of texts or simply move them map("n", "<A-k>", ':let save_a=@a<Cr><Up>"add"ap<Up>:let @a=save_a<Cr>') @@ -163,33 +366,31 @@ map("n", "<leader><C-l>", "<Cmd>!clear<CR>") -- Change file to an executable map("n", "<Leader>x", - ":lua require('user.mods').Toggle_executable()<CR> | :echom ('Toggle executable')<CR> | :sl! | echo ('')<CR>") +":lua require('user.mods').Toggle_executable()<CR> | :echom ('Toggle executable')<CR> | :sl! | echo ('')<CR>") -- map("n", "<leader>x", ":!chmod +x %<CR>") +vim.keymap.set("n", "<leader>cm", function() + vim.cmd("redir @+") + vim.cmd("silent messages") + vim.cmd("redir END") + vim.notify("Copied :messages to clipboard") +end, { desc = "Copy :messages to clipboard" }) + -- Paste without replace clipboard map("v", "p", '"_dP') --- Swap two pieces of text, use x to cut in visual mode, then use Ctrl-x in --- visual mode to select text to swap with ---map("v", "<C-X>", "<Esc>`.``gvP``P") +map("n", "]p", 'm`o<Esc>"+p``', opts) + +map("n", "[p", 'm`O<Esc>"+p``', opts) + +-- Bind Ctrl-V to paste in insert/normal/command mode +map("i", "<C-v>", "<C-G>u<C-R><C-P>+", opts) +map("n", "<C-v>", '"+p', { noremap = true, silent = true }) +vim.api.nvim_set_keymap("c", "<C-v>", "<C-R>=getreg('+')<CR><BS>", { noremap = true, silent = false }) -- Change Working Directory to current project map("n", "<leader>cd", ":cd %:p:h<CR>:pwd<CR>") --- Open the current file in the default program (on Mac this should just be just `open`) -map("n", "<leader>o", ":!xdg-open %<cr><cr>") - --- URL handling -if vim.fn.has("mac") == 1 then - map("", "gx", '<Cmd>call jobstart(["open", expand("<cfile>")], {"detach": v:true})<CR>', {}) -elseif vim.fn.has("unix") == 1 then - map("", "gx", '<Cmd>call jobstart(["xdg-open", expand("<cfile>")], {"detach": v:true})<CR>', {}) -elseif vim.fn.has("wsl") == 1 then - map("", "gx", '<Cmd>call jobstart(["wslview", expand("<cfile>")], {"detach": v:true})<CR>', {}) -else - map[""].gx = { '<Cmd>lua print("Error: gx is not supported on this OS!")<CR>' } -end - -- Search and replace map("v", "<leader>sr", 'y:%s/<C-r><C-r>"//g<Left><Left>c') @@ -197,13 +398,6 @@ map("v", "<leader>sr", 'y:%s/<C-r><C-r>"//g<Left><Left>c') map("n", "<leader>s", ":%s//g<Left><Left>") map("v", "<leader>s", ":s//g<Left><Left>") --- Toggle completion -map("n", "<Leader>tc", ':lua require("user.mods").toggle_completion()<CR>') - --- Disable default completion. -map("i", "<C-n>", "<Nop>") -map("i", "<C-p>", "<Nop>") - -- Set line wrap map("n", "<M-z>", function() local wrap_status = vim.api.nvim_exec("set wrap ?", true) @@ -223,12 +417,14 @@ map("n", "<Space>", "&foldlevel ? 'zM' : 'zR'", { expr = true }) -- Use space to toggle fold --map("n", "<Space>", "za") --- Make a copy of current file ---vim.cmd([[ --- map <leader>s :up \| saveas! %:p:r-<C-R>=strftime("%y.%m.%d-%H:%M")<CR>-bak.<C-R>=expand("%:e")<CR> \| 3sleep \| e #<CR> ---]]) map("n", "<leader>.b", ":!cp % %.backup<CR>") +-- Go to next window +map("n", "<leader>wn", "<C-w>w", { desc = "Next window" }) + +-- Go to previous window +map("n", "<leader>wp", "<C-w>W", { desc = "Previous window" }) + -- Toggle transparency map("n", "<leader>tb", ":call utils#Toggle_transparent_background()<CR>") @@ -240,7 +436,136 @@ map("n", "<C-w>z", "<C-w>|<C-w>_") map("n", "<leader>sl", ":call utils#ToggleHiddenAll()<CR>") -- Open last closed buffer -map("n", "<C-t>", ":call OpenLastClosed()<CR>") +map("n", "<C-t>", ":call utils#OpenLastClosed()<CR>") + + +-- Automatically set LSP keymaps when LSP attaches to a buffer +--vim.api.nvim_create_autocmd("LspAttach", { +-- callback = function(args) +-- local bufnr = args.buf +-- local opts = { buffer = bufnr } +-- map("n", "K", vim.lsp.buf.hover) +-- map("n", "gd", "<cmd>lua require('goto-preview').goto_preview_definition()<CR>") +-- map("n", "gi", "<cmd>lua require('goto-preview').goto_preview_implementation()<CR>") +-- map("n", "gr", "<cmd>lua require('goto-preview').goto_preview_references()<CR>") +-- map("n", "gD", vim.lsp.buf.declaration) +-- map("n", "<leader>k", vim.lsp.buf.signature_help) +-- map("n", "gt", "<cmd>lua require('goto-preview').goto_preview_type_definition()<CR>") +-- map("n", "gn", vim.lsp.buf.rename) +-- map("n", "ga", vim.lsp.buf.code_action) +-- map("n", "gf", function() vim.lsp.buf.format({ async = true }) end) +-- map("n", "go", vim.diagnostic.open_float) +-- map("n", "<leader>go", ":call utils#ToggleDiagnosticsOpenFloat()<CR> | :echom ('Toggle Diagnostics Float open/close...')<CR> | :sl! | echo ('')<CR>") +-- map("n", "gq", vim.diagnostic.setloclist) +-- map("n", "[d", vim.diagnostic.goto_prev) +-- map("n", "]d", vim.diagnostic.goto_next) +-- map("n", "gs", vim.lsp.buf.document_symbol) +-- map("n", "gw", vim.lsp.buf.workspace_symbol) +-- map("n", "<leader>wa", vim.lsp.buf.add_workspace_folder) +-- map("n", "<leader>wr", vim.lsp.buf.remove_workspace_folder) +-- map("n", "<leader>wl", function() +-- print(vim.inspect(vim.lsp.buf.list_workspace_folders())) +-- end) +-- end, +--}) + +---- LSP Global Keymaps (available in all buffers) +--map("n", "[d", vim.diagnostic.goto_prev, { desc = "LSP: Previous Diagnostic" }) +--map("n", "]d", vim.diagnostic.goto_next, { desc = "LSP: Next Diagnostic" }) +--map("n", "go", vim.diagnostic.open_float, { desc = "LSP: Open Diagnostic Float" }) +-- +---- LSP Buffer-local keymaps function (to be called from LSP on_attach) +--_G.setup_lsp_keymaps = function(bufnr) +-- local bmap = function(mode, l, r, opts) +-- opts = opts or {} +-- opts.silent = true +-- opts.noremap = true +-- opts.buffer = bufnr +-- vim.keymap.set(mode, l, r, opts) +-- end +-- +-- bmap("n", "K", vim.lsp.buf.hover, { desc = "LSP: Hover Documentation" }) +-- bmap("n", "gd", vim.lsp.buf.definition, { desc = "LSP: Go to Definition" }) +-- bmap("n", "gD", vim.lsp.buf.declaration, { desc = "LSP: Go to Declaration" }) +-- bmap("n", "gi", vim.lsp.buf.implementation, { desc = "LSP: Go to Implementation" }) +-- bmap("n", "gt", vim.lsp.buf.type_definition, { desc = "LSP: Go to Type Definition" }) +-- bmap("n", "gr", vim.lsp.buf.references, { desc = "LSP: Go to References" }) +-- bmap("n", "gn", vim.lsp.buf.rename, { desc = "LSP: Rename" }) +-- bmap("n", "ga", vim.lsp.buf.code_action, { desc = "LSP: Code Action" }) +-- bmap("n", "<leader>k", vim.lsp.buf.signature_help, { desc = "LSP: Signature Help" }) +-- bmap("n", "gs", vim.lsp.buf.document_symbol, { desc = "LSP: Document Symbols" }) +--end + +-- LSP Global Keymaps (available in all buffers) +map("n", "[d", vim.diagnostic.goto_prev, { desc = "LSP: Previous Diagnostic" }) +map("n", "]d", vim.diagnostic.goto_next, { desc = "LSP: Next Diagnostic" }) +map("n", "go", vim.diagnostic.open_float, { desc = "LSP: Open Diagnostic Float" }) +map("n", "<leader>go", ":call utils#ToggleDiagnosticsOpenFloat()<CR> | :echom ('Toggle Diagnostics Float open/close...')<CR> | :sl! | echo ('')<CR>") + +-- LSP Buffer-local keymaps function (to be called from LSP on_attach) +_G.setup_lsp_keymaps = function(bufnr) + local bmap = function(mode, l, r, opts) + opts = opts or {} + opts.silent = true + opts.noremap = true + opts.buffer = bufnr + vim.keymap.set(mode, l, r, opts) + end + + -- Your preferred keybindings + bmap("n", "K", function() + vim.lsp.buf.hover { border = "single", max_height = 25, max_width = 120 } + end, { desc = "LSP: Hover Documentation" }) + + bmap("n", "gd", function() + vim.lsp.buf.definition { + on_list = function(options) + -- Custom logic to avoid showing multiple definitions for Lua patterns like: + -- `local M.my_fn_name = function() ... end` + local unique_defs = {} + local def_loc_hash = {} + + for _, def_location in pairs(options.items) do + local hash_key = def_location.filename .. def_location.lnum + if not def_loc_hash[hash_key] then + def_loc_hash[hash_key] = true + table.insert(unique_defs, def_location) + end + end + + options.items = unique_defs + vim.fn.setloclist(0, {}, " ", options) + + -- Open location list if multiple definitions, otherwise jump directly + if #options.items > 1 then + vim.cmd.lopen() + else + vim.cmd([[silent! lfirst]]) + end + end, + } + end, { desc = "LSP: Go to Definition" }) + + bmap("n", "<C-]>", vim.lsp.buf.definition, { desc = "LSP: Go to Definition (Alt)" }) + bmap("n", "gD", vim.lsp.buf.declaration, { desc = "LSP: Go to Declaration" }) + bmap("n", "gi", vim.lsp.buf.implementation, { desc = "LSP: Go to Implementation" }) + bmap("n", "gt", vim.lsp.buf.type_definition, { desc = "LSP: Go to Type Definition" }) + bmap("n", "gr", vim.lsp.buf.references, { desc = "LSP: Go to References" }) + bmap("n", "gn", vim.lsp.buf.rename, { desc = "LSP: Rename" }) + bmap("n", "<leader>rn", vim.lsp.buf.rename, { desc = "LSP: Rename (Alt)" }) + bmap("n", "ga", vim.lsp.buf.code_action, { desc = "LSP: Code Action" }) + bmap("n", "<leader>ca", vim.lsp.buf.code_action, { desc = "LSP: Code Action (Alt)" }) + bmap("n", "<leader>k", vim.lsp.buf.signature_help, { desc = "LSP: Signature Help" }) + bmap("n", "<C-k>", vim.lsp.buf.signature_help, { desc = "LSP: Signature Help (Alt)" }) + bmap("n", "gs", vim.lsp.buf.document_symbol, { desc = "LSP: Document Symbols" }) + + -- Workspace folder management + bmap("n", "<leader>wa", vim.lsp.buf.add_workspace_folder, { desc = "LSP: Add Workspace Folder" }) + bmap("n", "<leader>wr", vim.lsp.buf.remove_workspace_folder, { desc = "LSP: Remove Workspace Folder" }) + bmap("n", "<leader>wl", function() + vim.print(vim.lsp.buf.list_workspace_folders()) + end, { desc = "LSP: List Workspace Folders" }) +end ---------------- Plugin Operations ---------------- -- Packer @@ -250,12 +575,6 @@ map("n", "<leader>Ps", "<cmd>PackerSync<cr>") map("n", "<leader>PS", "<cmd>PackerStatus<cr>") map("n", "<leader>Pu", "<cmd>PackerUpdate<cr>") --- Tmux navigation (aserowy/tmux.nvim) -map("n", "<C-h>", "<CMD>NavigatorLeft<CR>") -map("n", "<C-l>", "<CMD>NavigatorRight<CR>") -map("n", "<C-k>", "<CMD>NavigatorUp<CR>") -map("n", "<C-j>", "<CMD>NavigatorDown<CR>") - -- ToggleTerm map({ "n", "t" }, "<leader>tt", "<cmd>ToggleTerm<CR>") map({ "n", "t" }, "<leader>th", "<cmd>lua Horizontal_term_toggle()<CR>") @@ -270,9 +589,9 @@ map("n", "<leader>tg", "<cmd>lua Gh_dash()<CR>") map("n", "<leader>gs", vim.cmd.Git) map("n", "<leader>ga", ":Git add %:p<CR><CR>") --map("n", "<leader>gs", ":Gstatus<CR>") -map("n", "<leader>gc", ":Gcommit -v -q<CR>") +--map("n", "<leader>gc", ":Gcommit -v -q<CR>") map("n", "<leader>gt", ":Gcommit -v -q %:p<CR>") ---map("n", "<leader>gd", ":Gdiff<CR>") +map("n", "<leader>gd", ":Gdiff<CR>") map("n", "<leader>ge", ":Gedit<CR>") --map("n", "<leader>gr", ":Gread<Cj>") map("n", "<leader>gw", ":Gwrite<CR><CR>") @@ -287,42 +606,128 @@ map("n", "<leader>gm", ":Gmove<Space>") --map("n", "<leader>gpl", ":Dispatch! git pull<CR>") -- Telescope -map("n", "<leader>ff", ":cd %:p:h<CR>:pwd<CR><cmd>lua require('telescope.builtin').find_files()<cr>") -- find files with hidden option +-- Safe load of your custom Telescope module +-- This initial pcall for "plugins.telescope" is fine because it just checks if YOUR module is there. +-- The actual checks for Telescope's core modules happen *inside* your wrapper functions when called. +local telescope_ok, telescope_module = pcall(require, "plugins.telescope") + +if telescope_ok and telescope_module then + + -- Direct function calls from your plugins.telescope module + -- M.safe_find_files handles its own internal `builtin` check + map("n", "<leader>ff", telescope_module.safe_find_files, { desc = "Find files" }) + + -- For `find all files`, use your `safe_telescope_builtin` wrapper + -- You need to wrap it in a function to pass the options correctly. + map("n", "<leader>f.", function() + telescope_module.safe_telescope_builtin("find_files")({ hidden = true, no_ignore = true }) + end, { desc = "Find all files" }) + + + --- + --- Built-in Telescope functions + --- Note: safe_telescope_builtin returns a function, so you map directly to it. + --- + map("n", "<leader>fg", function() telescope_module.safe_telescope_builtin("live_grep")() end, { desc = "Live grep" }) + map("n", "<leader>fb", function() telescope_module.safe_telescope_builtin("buffers")() end, { desc = "Find buffers" }) + map("n", "<leader>fh", function() telescope_module.safe_telescope_builtin("help_tags")() end, { desc = "Help tags" }) + map("n", "<leader>fc", function() telescope_module.safe_telescope_builtin("commands")() end, { desc = "Commands" }) + map("n", "<leader>fd", function() telescope_module.safe_telescope_builtin("diagnostics")() end, { desc = "Diagnostics" }) + map("n", "<leader>fk", function() telescope_module.safe_telescope_builtin("keymaps")() end, { desc = "Keymaps" }) + map("n", "<leader>fr", function() telescope_module.safe_telescope_builtin("registers")() end, { desc = "Registers" }) + map("n", "<leader>ffc", function() telescope_module.safe_telescope_builtin("current_buffer_fuzzy_find")() end, { desc = "Current buffer fuzzy find" }) + -- Corrected the previous `fp` mapping that pointed to `pickers` + map("n", "<leader>fp", function() telescope_module.safe_telescope_builtin("oldfiles")() end, { desc = "Recently opened files" }) + + + --- + --- Telescope Extension functions + --- Note: safe_telescope_extension returns a function, so you map directly to it. + --- + map("n", "<leader>cf", function() telescope_module.safe_telescope_extension("changed_files", "changed_files")() end, { desc = "Changed files" }) + map("n", "<leader>fm", function() telescope_module.safe_telescope_extension("media_files", "media_files")() end, { desc = "Media files" }) + map("n", "<leader>fi", function() telescope_module.safe_telescope_extension("notify", "notify")() end, { desc = "Notifications" }) + map("n", "<Leader>fs", function() telescope_module.safe_telescope_extension("session-lens", "search_session")() end, { desc = "Search sessions" }) + map("n", "<Leader>frf", function() telescope_module.safe_telescope_extension("recent_files", "pick")() end, { desc = "Recent files" }) + map("n", "<Leader>f/", function() telescope_module.safe_telescope_extension("file_browser", "file_browser")() end, { desc = "File browser" }) + + + --- + --- Custom functions defined in plugins.telescope.lua + --- Note: safe_telescope_call returns a function, so you map directly to it. + --- (These were already correct as safe_telescope_call returns a callable function) + --- + map("n", "<leader>ffd", telescope_module.safe_telescope_call("plugins.telescope", "find_dirs"), { desc = "Find directories" }) + map("n", "<leader>ff.", telescope_module.safe_telescope_call("plugins.telescope", "find_configs"), { desc = "Find configs" }) + map("n", "<leader>ffs", telescope_module.safe_telescope_call("plugins.telescope", "find_scripts"), { desc = "Find scripts" }) + map("n", "<leader>ffw", telescope_module.safe_telescope_call("plugins.telescope", "find_projects"), { desc = "Find projects" }) + map("n", "<leader>ffB", telescope_module.safe_telescope_call("plugins.telescope", "find_books"), { desc = "Find books" }) + map("n", "<leader>ffn", telescope_module.safe_telescope_call("plugins.telescope", "find_notes"), { desc = "Find notes" }) + map("n", "<leader>fgn", telescope_module.safe_telescope_call("plugins.telescope", "grep_notes"), { desc = "Grep notes" }) + map("n", "<leader>fpp", telescope_module.safe_telescope_call("plugins.telescope", "find_private"), { desc = "Find private notes" }) + map("n", "<leader>fgc", telescope_module.safe_telescope_call("plugins.telescope", "grep_current_dir"), { desc = "Grep current directory" }) + +end +---- Fallback keymaps when telescope is not available +--map("n", "<leader>ff", function() +-- local file = vim.fn.input("Open file: ", "", "file") +-- if file ~= "" then +-- vim.cmd("edit " .. file) +-- end +--end, { desc = "Find files (fallback)" }) + +---- You can add other basic fallbacks here +--map("n", "<leader>fg", function() +-- vim.notify("Live grep requires telescope plugin", vim.log.levels.WARN) +--end, { desc = "Live grep (unavailable)" }) +----end + + map("n", "<leader>fF", ":cd %:p:h<CR>:pwd<CR><cmd>lua require('user.mods').findFilesInCwd()<CR>", - { noremap = true, silent = true, desc = "Find files in cwd" }) -map("n", "<leader>f.", function() - require("telescope.builtin").find_files({ hidden = true, no_ignore = true }) -end) -- find all files -map("n", "<leader>fg", "<cmd>lua require('telescope.builtin').live_grep()<cr>") -map("n", "<leader>fb", "<cmd>lua require('telescope.builtin').buffers()<cr>") -map("n", "<leader>fh", "<cmd>lua require('telescope.builtin').help_tags()<cr>") -map("n", "<leader>fc", "<cmd>lua require('telescope.builtin').commands()<cr>") -map("n", "<leader>cf", "<cmd>Telescope changed_files<cr>") -map("n", "<leader>fp", "<cmd>Telescope pickers<cr>") -map("n", "<leader>fd", "<cmd>lua require('telescope.builtin').diagnostics()<cr>") -map("n", "<leader>fk", "<cmd>lua require('telescope.builtin').keymaps()<cr>") -map("n", "<leader>fr", "<cmd>lua require('telescope.builtin').registers({})<CR>") -- registers picker -map("n", "<leader>fm", "<cmd>lua require('telescope').extensions.media_files.media_files({})<cr>") -- find media files -map("n", "<leader>fi", "<cmd>lua require('telescope').extensions.notify.notify({})<cr>") -- find notifications -map("n", "<Leader>fs", '<cmd>lua require("session-lens").search_session()<CR>') -map("n", "<leader>ffd", [[<Cmd>lua require'plugins.telescope'.find_dirs()<CR>]]) -- find dies -map("n", "<leader>ff.", [[<Cmd>lua require'plugins.telescope'.find_configs()<CR>]]) -- find configs -map("n", "<leader>ffs", [[<Cmd>lua require'plugins.telescope'.find_scripts()<CR>]]) -- find scripts -map("n", "<leader>ffw", [[<Cmd>lua require'plugins.telescope'.find_projects()<CR>]]) -- find projects -map("n", "<leader>ffb", [[<Cmd>lua require'plugins.telescope'.find_books()<CR>]]) -- find books -map("n", "<leader>ffn", [[<Cmd>lua require'plugins.telescope'.find_notes()<CR>]]) -- find notes -map("n", "<leader>fgn", [[<Cmd>lua require'plugins.telescope'.grep_notes()<CR>]]) -- search notes -map("n", "<Leader>frf", "<cmd>lua require('telescope').extensions.recent_files.pick()<CR>") -map("n", "<leader>ffc", "<cmd>lua require('telescope.builtin').current_buffer_fuzzy_find()<cr>") -map("n", "<Leader>f/", "<cmd>lua require('telescope').extensions.file_browser.file_browser()<CR>") ---map("n", "<leader>f/", "<cmd>lua require('plugins.telescope').curbuf()<cr>") -- find files with hidden option --- Map a shortcut to open the picker. +{ noremap = true, silent = true, desc = "Find files in cwd" }) -- FZF -map("n", "<leader>fz", "<cmd>lua require('fzf-lua').files()<CR>") +map("n", "<leader>fz", function() + local ok, fzf_lua = pcall(require, "fzf-lua") + if ok then + fzf_lua.files() -- no config, just open + else + local handle = io.popen("find . -type f | fzf") + if handle then + local result = handle:read("*a") + handle:close() + result = result:gsub("\n", "") + if result ~= "" then + vim.cmd("edit " .. vim.fn.fnameescape(result)) + end + else + vim.notify("fzf not found or failed to run", vim.log.levels.ERROR) + end + end +end, { desc = "FZF file picker (fzf-lua or fallback)" }) + +map("n", "gA", ":FzfLua lsp_code_actions<CR>") -- Nvim-tree -map("n", "<leader>e", "<cmd>Rooter<CR>:NvimTreeToggle<CR>", {}) +local function safe_nvim_tree_toggle() + local ok_tree, tree_api = pcall(require, "nvim-tree.api") + if ok_tree then + pcall(vim.cmd, "Rooter") -- silently run Rooter if available + tree_api.tree.toggle() + else + -- Fallback to netrw + local cur_buf = vim.api.nvim_get_current_buf() + local ft = vim.api.nvim_get_option_value("filetype", { buf = cur_buf }) + + if ft == "netrw" then + vim.cmd("close") + else + vim.cmd("Lexplore") + end + end +end + +map("n", "<leader>f", safe_nvim_tree_toggle, { desc = "Toggle file explorer" }) -- Undotree map("n", "<leader>u", vim.cmd.UndotreeToggle) @@ -338,29 +743,77 @@ map("n", "<leader>ww", "<cmd>lua require('user.mods').Toggle_autopairs()<CR>") map("n", "<leader>zm", "<CMD>ZenMode<CR> | :echom ('Zen Mode')<CR> | :sl! | echo ('')<CR>") -- Vim-rooter -map("n", "<leader>ro", "<CMD>Rooter<CR> | :echom ('cd to root/project directory')<CR> | :sl! | echo ('')<CR>", term_opts) +local function safe_project_root() + if vim.fn.exists(":Rooter") == 2 then + vim.cmd("Rooter") + else + vim.cmd("cd %:p:h") + end +end +vim.keymap.set("n", "<leader>ro", safe_project_root, { desc = "Project root" }) -- Trouble (UI to show diagnostics) -map("n", "<leader>t", ":cd %:p:h<CR>:pwd<CR><CMD>TroubleToggle<CR>") -map("n", "<leader>tw", ":cd %:p:h<CR>:pwd<CR><CMD>TroubleToggle workspace_diagnostics<CR>") -map("n", "<leader>td", ":cd %:p:h<CR>:pwd<CR><CMD>TroubleToggle document_diagnostics<CR>") -map("n", "<leader>tq", ":cd %:p:h<CR>:pwd<CR><CMD>TroubleToggle quickfix<CR>") -map("n", "<leader>tl", ":cd %:p:h<CR>:pwd<CR><CMD>TroubleToggle loclist<CR>") -map("n", "gR", "<CMD>TroubleToggle lsp_references<CR>") +local function safe_trouble_toggle(view, opts) + local ok, _ = pcall(require, "trouble") + if ok then + local cmd = "Trouble" + if view then + cmd = cmd .. " " .. view .. " toggle" + if opts then + cmd = cmd .. " " .. opts + end + else + cmd = cmd .. " diagnostics toggle" + end + vim.cmd(cmd) + else + vim.cmd("copen") + end +end + +-- Replace 'map' with 'vim.keymap.set' if not already a global alias +vim.keymap.set("n", "<leader>t", function() + safe_trouble_toggle() +end, { desc = "Diagnostics (Workspace)" }) + +vim.keymap.set("n", "<leader>tw", function() + vim.cmd("cd %:p:h | pwd") + safe_trouble_toggle("diagnostics") +end, { desc = "Diagnostics (Workspace)" }) + +vim.keymap.set("n", "<leader>td", function() + vim.cmd("cd %:p:h | pwd") + safe_trouble_toggle("diagnostics", "filter.buf=0") +end, { desc = "Diagnostics (Buffer)" }) + +vim.keymap.set("n", "<leader>tq", function() + vim.cmd("cd %:p:h | pwd") + safe_trouble_toggle("qflist") +end, { desc = "Quickfix List" }) + +vim.keymap.set("n", "<leader>tl", function() + vim.cmd("cd %:p:h | pwd") + safe_trouble_toggle("loclist") +end, { desc = "Location List" }) + +vim.keymap.set("n", "gR", function() + safe_trouble_toggle("lsp") +end, { desc = "LSP References/Definitions" }) -- Null-ls -map("n", "<leader>ls", "<CMD>NullLsToggle<CR>") +map("n", "<leader>ls", ':lua require("null-ls").toggle({})<CR>') + -- Replacer map("n", "<Leader>qr", ':lua require("replacer").run()<CR>') -- Quickfix map("n", "<leader>q", function() - if vim.fn.getqflist({ winid = 0 }).winid ~= 0 then - require("plugins.quickfix").close() - else - require("plugins.quickfix").open() - end + if vim.fn.getqflist({ winid = 0 }).winid ~= 0 then + require("plugins.quickfix").close() + else + require("plugins.quickfix").open() + end end, { desc = "Toggle quickfix window" }) -- Move to the next and previous item in the quickfixlist @@ -375,17 +828,17 @@ local dap_ok, dap = pcall(require, "dap") local dap_ui_ok, ui = pcall(require, "dapui") if not (dap_ok and dap_ui_ok) then - require("notify")("nvim-dap or dap-ui not installed!", "warning") - return + --require("notify")("nvim-dap or dap-ui not installed!", "warning") + return end vim.fn.sign_define("DapBreakpoint", { text = "🐞" }) -- Start debugging session map("n", "<leader>ds", function() - dap.continue() - ui.toggle({}) - vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<C-w>=", false, true, true), "n", false) -- Spaces buffers evenly + dap.continue() + ui.toggle({}) + vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<C-w>=", false, true, true), "n", false) -- Spaces buffers evenly end) -- Set breakpoints, get variable values, step into/out of functions, etc. @@ -394,31 +847,34 @@ map("n", "<leader>dC", dap.continue) -- map("n", "<leader>dt", dap.terminate) map("n", "<leader>dt", ui.toggle) map("n", "<leader>dd", function() - dap.disconnect({ terminateDebuggee = true }) + dap.disconnect({ terminateDebuggee = true }) end) map("n", "<leader>dn", dap.step_over) map("n", "<leader>di", dap.step_into) map("n", "<leader>do", dap.step_out) map("n", "<leader>db", dap.toggle_breakpoint) map("n", "<leader>dB", function() - dap.clear_breakpoints() - require("notify")("Breakpoints cleared", "warn") + dap.clear_breakpoints() + require("notify")("Breakpoints cleared", "warn") +end) +map("n", "<leader>dl", function() + local ok, dap_widgets = pcall(require, "dap.ui.widgets") + if ok then dap_widgets.hover() end end) -map("n", "<leader>dl", require("dap.ui.widgets").hover) map("n", "<leader>de", function() - require("dapui").float_element() + require("dapui").float_element() end, { desc = "Open Element" }) map("n", "<leader>dq", function() - require("dapui").close() - require("dap").repl.close() - local session = require("dap").session() - if session then - require("dap").terminate() - end - require("nvim-dap-virtual-text").refresh() + require("dapui").close() + require("dap").repl.close() + local session = require("dap").session() + if session then + require("dap").terminate() + end + require("nvim-dap-virtual-text").refresh() end, { desc = "Terminate Debug" }) map("n", "<leader>dc", function() - require("telescope").extensions.dap.commands() + require("telescope").extensions.dap.commands() end, { desc = "DAP-Telescope: Commands" }) --vim.keymap.set("n", "<leader>B", ":lua require'dap'.set_breakpoint(vim.fn.input('Breakpoint condition: '))<CR>") --vim.keymap.set("v", "<leader>B", ":lua require'dap'.set_breakpoint(vim.fn.input('Breakpoint condition: '))<CR>") @@ -459,10 +915,10 @@ map("n", "<leader>rr", '<CMD>lua require("user.mods").toggleCodeRunner()<CR>') -- Run executable file map("n", "<leader>rx", - ":lua require('user.mods').RunCurrentFile()<CR>:echom 'Running executable file...'<CR>:sl!<CR>:echo ''<CR>") +":lua require('user.mods').RunCurrentFile()<CR>:echom 'Running executable file...'<CR>:sl!<CR>:echo ''<CR>") --- Close all floating windows -map({ "n", "t", "c" }, "<leader>w", "<CMD>CloseFloatingWindows<CR>") +-- Set Files to current location as dir +map({ "n" }, "<leader>cf", "<CMD>e %:h<CR>") -- Vimtex map("n", "<Leader>lc", ":VimtexCompile<cr>") diff --git a/common/config/nvim/lua/user/mods.lua b/common/config/nvim/lua/user/mods.lua index c20c687..b4e1579 100644..100755 --- a/common/config/nvim/lua/user/mods.lua +++ b/common/config/nvim/lua/user/mods.lua @@ -1,15 +1,84 @@ +-- ============================================================================ +-- Modules/Utility functions +-- ============================================================================ + local M = {} ---- Shorten Function Names +-- Shorten Function Names local fn = vim.fn +local api = vim.api + +--- Check if an executable exists +---@param name string The name of the executable to check +---@return boolean function M.executable(name) - if fn.executable(name) > 0 then - return true + return fn.executable(name) > 0 +end + +--- Check if a feature is available in Neovim +---@param feat string The feature to check (e.g., 'nvim-0.7') +---@return boolean +function M.has(feat) + return fn.has(feat) == 1 +end + +--- Setup command aliases +---@param from string The alias +---@param to string The command to alias to +function M.setup_command_alias(from, to) + local cmd = string.format('cnoreabbrev <expr> %s (getcmdtype() == ":" && getcmdline() == "%s") ? "%s" : "%s"', + from, from, to, from) + api.nvim_command(cmd) +end + +--- Preserve cursor position while formatting +---@param cmd string The command to run +function M.preserve_cursor(cmd) + local cursor = api.nvim_win_get_cursor(0) + vim.cmd(cmd) + api.nvim_win_set_cursor(0, cursor) +end + +--- Toggle quickfix window +function M.toggle_quickfix() + local qf_exists = false + for _, win in pairs(fn.getwininfo()) do + if win.quickfix == 1 then + qf_exists = true + break + end end + if qf_exists then + vim.cmd('cclose') + else + vim.cmd('copen') + end +end - return false +--- Toggle location list +function M.toggle_location() + local loc_exists = false + for _, win in pairs(fn.getwininfo()) do + if win.loclist == 1 then + loc_exists = true + break + end + end + if loc_exists then + vim.cmd('lclose') + else + vim.cmd('lopen') + end end +-- Setup command aliases +M.setup_command_alias('W', 'w') +M.setup_command_alias('Wq', 'wq') +M.setup_command_alias('WQ', 'wq') +M.setup_command_alias('Q', 'q') +M.setup_command_alias('Qa', 'qa') +M.setup_command_alias('QA', 'qa') + -------------------------------------------------- --- Check whether a feature exists in Nvim @@ -28,49 +97,76 @@ end -- Format on save local format_augroup = vim.api.nvim_create_augroup("LspFormatting", {}) -require("null-ls").setup({ - -- you can reuse a shared lspconfig on_attach callback here - on_attach = function(client, bufnr) - if client.supports_method("textDocument/formatting") then - vim.api.nvim_clear_autocmds({ group = format_augroup, buffer = bufnr }) - vim.api.nvim_create_autocmd("BufWritePre", { - group = format_augroup, - buffer = bufnr, - callback = function() - -- on 0.8, you should use vim.lsp.buf.format({ bufnr = bufnr }) instead - --vim.lsp.buf.formatting_seq_sync() - vim.lsp.buf.format({ bufnr = bufnr }) - end, - }) - end - end, -}) + +local ok, null_ls = pcall(require, "null-ls") +if ok then + null_ls.setup({ + on_attach = function(client, bufnr) + if client.supports_method("textDocument/formatting") then + vim.api.nvim_clear_autocmds({ group = format_augroup, buffer = bufnr }) + vim.api.nvim_create_autocmd("BufWritePre", { + group = format_augroup, + buffer = bufnr, + callback = function() + if vim.lsp.buf.format then + vim.lsp.buf.format({ bufnr = bufnr }) + else + vim.lsp.buf.formatting_seq_sync() + end + end, + }) + end + end, + }) +end vim.cmd([[autocmd BufWritePre <buffer> lua vim.lsp.buf.format()]]) ---vim.cmd [[autocmd BufWritePre * lua vim.lsp.buf.format()]] + -------------------------------------------------- ---Determine if a value of any type is empty ---@param item any ---@return boolean? + +--- Checks if an item is considered "empty". +-- +-- An item is considered empty if: +-- - It is nil. +-- - It is an empty string. +-- - It is an empty table. +-- - It is a number equal to 0 (you might want to adjust this based on your definition of "empty" for numbers). +-- +-- @param item any The item to check. +-- @return boolean True if the item is empty, false otherwise. function M.empty(item) - if not item then + -- Case 1: item is nil + if item == nil then return true end + local item_type = type(item) + + -- Case 2: empty string if item_type == "string" then return item == "" end - if item_type == "number" then - return item <= 0 - end + if item_type == "table" then return vim.tbl_isempty(item) end - return item ~= nil + if item_type == "number" then + return item == 0 -- Changed from item <= 0 for a stricter "empty" definition for numbers + end + + if item_type == "boolean" then + return not item -- Returns true if item is false, false if item is true + end + + return false end + -------------------------------------------------- --- Create a dir if it does not exist @@ -533,22 +629,34 @@ function M.DeleteCurrentBuffer() -- Delay before closing the nvim-tree window end -vim.cmd([[autocmd FileType NvimTree lua require("user.mods").DeleteCurrentBuffer()]]) -- On :bd nvim-tree should behave as if it wasn't opened +-- Only run DeleteCurrentBuffer if NvimTree is loaded +vim.api.nvim_create_autocmd("FileType", { + pattern = "NvimTree", + callback = function() + local ok, mods = pcall(require, "user.mods") + if ok and type(mods.DeleteCurrentBuffer) == "function" then + mods.DeleteCurrentBuffer() + end + end, +}) + +-- Handle NvimTree window closure safely vim.api.nvim_create_autocmd("BufEnter", { nested = true, callback = function() - -- Only 1 window with nvim-tree left: we probably closed a file buffer - if #vim.api.nvim_list_wins() == 1 and require("nvim-tree.utils").is_nvim_tree_buf() then - local api = require("nvim-tree.api") - -- Required to let the close event complete. An error is thrown without this. + local ok_utils, utils = pcall(require, "nvim-tree.utils") + if not ok_utils then return end + + if #vim.api.nvim_list_wins() == 1 and utils.is_nvim_tree_buf() then + local ok_api, api = pcall(require, "nvim-tree.api") + if not ok_api then return end + vim.defer_fn(function() - -- close nvim-tree: will go to the last buffer used before closing - api.tree.toggle({ find_file = true, focus = true }) - -- re-open nivm-tree - api.tree.toggle({ find_file = true, focus = true }) - -- nvim-tree is still the active window. Go to the previous window. + -- Safely toggle tree off and on + pcall(api.tree.toggle, { find_file = true, focus = true }) + pcall(api.tree.toggle, { find_file = true, focus = true }) vim.cmd("wincmd p") end, 0) end @@ -741,105 +849,105 @@ end -------------------------------------------------- --- Intercept file open -local augroup = vim.api.nvim_create_augroup("user-autocmds", { clear = true }) -local intercept_file_open = true -vim.api.nvim_create_user_command("InterceptToggle", function() - intercept_file_open = not intercept_file_open - local intercept_state = "`Enabled`" - if not intercept_file_open then - intercept_state = "`Disabled`" - end - vim.notify("Intercept file open set to " .. intercept_state, vim.log.levels.INFO, { - title = "Intercept File Open", - ---@param win integer The window handle - on_open = function(win) - vim.api.nvim_buf_set_option(vim.api.nvim_win_get_buf(win), "filetype", "markdown") - end, - }) -end, { desc = "Toggles intercepting BufNew to open files in custom programs" }) - --- NOTE: Add "BufReadPre" to the autocmd events to also intercept files given on the command line, e.g. --- `nvim myfile.txt` -vim.api.nvim_create_autocmd({ "BufNew" }, { - group = augroup, - callback = function(args) - ---@type string - local path = args.match - ---@type integer - local bufnr = args.buf - - ---@type string? The file extension if detected - local extension = vim.fn.fnamemodify(path, ":e") - ---@type string? The filename if detected - local filename = vim.fn.fnamemodify(path, ":t") - - ---Open a given file path in a given program and remove the buffer for the file. - ---@param buf integer The buffer handle for the opening buffer - ---@param fpath string The file path given to the program - ---@param fname string The file name used in notifications - ---@param prog string The program to execute against the file path - local function open_in_prog(buf, fpath, fname, prog) - vim.notify(string.format("Opening `%s` in `%s`", fname, prog), vim.log.levels.INFO, { - title = "Open File in External Program", - ---@param win integer The window handle - on_open = function(win) - vim.api.nvim_buf_set_option(vim.api.nvim_win_get_buf(win), "filetype", "markdown") - end, - }) - local mods = require("user.mods") - local nvim_ver = mods.get_nvim_version() - - local version_major, version_minor = string.match(nvim_ver, "(%d+)%.(%d+)") - version_major = tonumber(version_major) - version_minor = tonumber(version_minor) - - if version_major > 0 or (version_major == 0 and version_minor >= 10) then - vim.system({ prog, fpath }, { detach = true }) - else - vim.fn.jobstart({ prog, fpath }, { detach = true }) - end - vim.api.nvim_buf_delete(buf, { force = true }) - end - - local extension_callbacks = { - ["pdf"] = function(buf, fpath, fname) - open_in_prog(buf, fpath, fname, "zathura") - end, - ["epub"] = function(buf, fpath, fname) - open_in_prog(buf, fpath, fname, "zathura") - end, - ["mobi"] = "pdf", - ["png"] = function(buf, fpath, fname) - open_in_prog(buf, fpath, fname, "vimiv") - end, - ["jpg"] = "png", - ["mp4"] = function(buf, fpath, fname) - open_in_prog(buf, fpath, fname, "vlc") - end, - ["gif"] = "mp4", - } - - ---Get the extension callback for a given extension. Will do a recursive lookup if an extension callback is actually - ---of type string to get the correct extension - ---@param ext string A file extension. Example: `png`. - ---@return fun(bufnr: integer, path: string, filename: string?) extension_callback The extension callback to invoke, expects a buffer handle, file path, and filename. - local function extension_lookup(ext) - local callback = extension_callbacks[ext] - if type(callback) == "string" then - callback = extension_lookup(callback) - end - return callback - end - - if extension ~= nil and not extension:match("^%s*$") and intercept_file_open then - local callback = extension_lookup(extension) - if type(callback) == "function" then - callback(bufnr, path, filename) - end - end - end, -}) +---- Intercept file open +--local augroup = vim.api.nvim_create_augroup("user-autocmds", { clear = true }) +--local intercept_file_open = true +--vim.api.nvim_create_user_command("InterceptToggle", function() +-- intercept_file_open = not intercept_file_open +-- local intercept_state = "`Enabled`" +-- if not intercept_file_open then +-- intercept_state = "`Disabled`" +-- end +-- vim.notify("Intercept file open set to " .. intercept_state, vim.log.levels.INFO, { +-- title = "Intercept File Open", +-- ---@param win integer The window handle +-- on_open = function(win) +-- vim.api.nvim_buf_set_option(vim.api.nvim_win_get_buf(win), "filetype", "markdown") +-- end, +-- }) +--end, { desc = "Toggles intercepting BufNew to open files in custom programs" }) + +---- NOTE: Add "BufReadPre" to the autocmd events to also intercept files given on the command line, e.g. +---- `nvim myfile.txt` +--vim.api.nvim_create_autocmd({ "BufNew" }, { +-- group = augroup, +-- callback = function(args) +-- ---@type string +-- local path = args.match +-- ---@type integer +-- local bufnr = args.buf +-- +-- ---@type string? The file extension if detected +-- local extension = vim.fn.fnamemodify(path, ":e") +-- ---@type string? The filename if detected +-- local filename = vim.fn.fnamemodify(path, ":t") +-- +-- ---Open a given file path in a given program and remove the buffer for the file. +-- ---@param buf integer The buffer handle for the opening buffer +-- ---@param fpath string The file path given to the program +-- ---@param fname string The file name used in notifications +-- ---@param prog string The program to execute against the file path +-- local function open_in_prog(buf, fpath, fname, prog) +-- vim.notify(string.format("Opening `%s` in `%s`", fname, prog), vim.log.levels.INFO, { +-- title = "Open File in External Program", +-- ---@param win integer The window handle +-- on_open = function(win) +-- vim.api.nvim_buf_set_option(vim.api.nvim_win_get_buf(win), "filetype", "markdown") +-- end, +-- }) +-- local mods = require("user.mods") +-- local nvim_ver = mods.get_nvim_version() +-- +-- local version_major, version_minor = string.match(nvim_ver, "(%d+)%.(%d+)") +-- version_major = tonumber(version_major) +-- version_minor = tonumber(version_minor) +-- +-- if version_major > 0 or (version_major == 0 and version_minor >= 10) then +-- vim.system({ prog, fpath }, { detach = true }) +-- else +-- vim.fn.jobstart({ prog, fpath }, { detach = true }) +-- end +-- vim.api.nvim_buf_delete(buf, { force = true }) +-- end +-- +-- local extension_callbacks = { +-- ["pdf"] = function(buf, fpath, fname) +-- open_in_prog(buf, fpath, fname, "zathura") +-- end, +-- ["epub"] = function(buf, fpath, fname) +-- open_in_prog(buf, fpath, fname, "zathura") +-- end, +-- ["mobi"] = "pdf", +-- ["png"] = function(buf, fpath, fname) +-- open_in_prog(buf, fpath, fname, "vimiv") +-- end, +-- ["jpg"] = "png", +-- ["mp4"] = function(buf, fpath, fname) +-- open_in_prog(buf, fpath, fname, "vlc") +-- end, +-- ["gif"] = "mp4", +-- } +-- +-- ---Get the extension callback for a given extension. Will do a recursive lookup if an extension callback is actually +-- ---of type string to get the correct extension +-- ---@param ext string A file extension. Example: `png`. +-- ---@return fun(bufnr: integer, path: string, filename: string?) extension_callback The extension callback to invoke, expects a buffer handle, file path, and filename. +-- local function extension_lookup(ext) +-- local callback = extension_callbacks[ext] +-- if type(callback) == "string" then +-- callback = extension_lookup(callback) +-- end +-- return callback +-- end +-- +-- if extension ~= nil and not extension:match("^%s*$") and intercept_file_open then +-- local callback = extension_lookup(extension) +-- if type(callback) == "function" then +-- callback(bufnr, path, filename) +-- end +-- end +-- end, +--}) -------------------------------------------------- @@ -1061,5 +1169,259 @@ end, { bang = true, nargs = 0 }) -------------------------------------------------- + +-- Platform detection +local uname = vim.loop.os_uname().sysname +local has = vim.fn.has + +local is_mac = has("mac") == 1 +local is_linux = uname == "Linux" +local is_windows = has("win32") == 1 or uname:find("Windows") +local is_wsl = has("wsl") == 1 or (uname:find("Linux") and (os.getenv("WSL_DISTRO_NAME") ~= nil)) +local is_termux = has("termux") == 1 or (os.getenv("PREFIX") and os.getenv("PREFIX"):find("com.termux")) +local os_name = (is_mac and "mac") or (is_linux and "linux") or (is_windows and "windows") or (is_wsl and "wsl") or (is_termux and "termux") or uname:lower() + +-- Check if a command exists +local function command_exists(cmd) + local handle = io.popen(cmd .. " --version 2>/dev/null") + if handle then + local result = handle:read("*a") + handle:close() + return result ~= "" + end + return false +end + +-- Detect clipboard tool on Linux +local function detect_clipboard_tool() + if command_exists("xclip") then return "xclip" end + if command_exists("xsel") then return "xsel" end + if command_exists("wl-copy") and command_exists("wl-paste") then return "wl-clipboard" end + return nil +end + +-- OSC52 clipboard copy fallback +local function osc52_copy(text) + local encoded = vim.fn.system("base64 | tr -d '\n'", text) + io.write(string.format("\027]52;c;%s\007", encoded)) +end + +---- Set clipboard +--function set_clipboard(text) +-- if not text or text == "" then return end +-- +-- if is_mac then +-- local handle = io.popen("pbcopy", "w") +-- if handle then +-- handle:write(text) +-- handle:close() +-- end +-- elseif is_linux then +-- local tool = detect_clipboard_tool() +-- if tool == "xclip" then +-- local handle = io.popen("xclip -selection clipboard", "w") +-- if handle then handle:write(text) handle:close() end +-- elseif tool == "xsel" then +-- local handle = io.popen("xsel --clipboard --input", "w") +-- if handle then handle:write(text) handle:close() end +-- elseif tool == "wl-clipboard" then +-- local handle = io.popen("wl-copy", "w") +-- if handle then handle:write(text) handle:close() end +-- else +-- osc52_copy(text) +-- vim.notify("Using OSC52 for clipboard (install xclip, xsel, or wl-clipboard for better support)", vim.log.levels.INFO) +-- end +-- elseif is_wsl or is_windows then +-- local handle = io.popen("clip", "w") +-- if handle then handle:write(text) handle:close() end +-- elseif is_termux then +-- local handle = io.popen("termux-clipboard-set", "w") +-- if handle then handle:write(text) handle:close() end +-- else +-- vim.notify("No clipboard support for OS: " .. os_name, vim.log.levels.WARN) +-- end +--end +-- +---- Clipboard sync autocmd setup +--local function setup_clipboard_sync() +-- local ok, Job = pcall(require, "plenary.job") +-- if not ok then +-- -- plenary not available, skip +-- return +-- end +-- +-- vim.api.nvim_create_augroup("clipboard_sync", { clear = true }) +-- vim.api.nvim_create_autocmd("TextYankPost", { +-- group = "clipboard_sync", +-- desc = "Sync yanked text to system clipboard", +-- pattern = "*", +-- callback = function() +-- local text = vim.fn.getreg("\"") +-- if text ~= nil and text ~= "" then +-- set_clipboard(text) +-- end +-- end, +-- }) +--end +--setup_clipboard_sync() +-- +---- Terminal clear function (optional) +--function clear_terminal() +-- vim.opt.scrollback = 1 +-- vim.api.nvim_feedkeys("i", "n", false) +-- vim.api.nvim_feedkeys("clear\r", "n", false) +-- vim.api.nvim_feedkeys("\x1b", "n", false) +-- vim.api.nvim_feedkeys("i", "n", false) +-- vim.defer_fn(function() +-- vim.opt.scrollback = 10000 +-- end, 100) +--end +-- +---- Get clipboard content (optional) +--function GetClipboard() +-- local handle +-- +-- if is_mac then +-- handle = io.popen("pbpaste", "r") +-- elseif is_linux then +-- local tool = detect_clipboard_tool() +-- if tool == "xclip" then +-- handle = io.popen("xclip -selection clipboard -o", "r") +-- elseif tool == "xsel" then +-- handle = io.popen("xsel --clipboard --output", "r") +-- elseif tool == "wl-clipboard" then +-- handle = io.popen("wl-paste", "r") +-- end +-- elseif is_wsl or is_windows then +-- handle = io.popen("powershell.exe Get-Clipboard", "r") +-- elseif is_termux then +-- handle = io.popen("termux-clipboard-get", "r") +-- end +-- +-- if handle then +-- local result = handle:read("*a") +-- handle:close() +-- return result or "" +-- end +-- +-- return "" +--end + +-------------------------------------------------- + +-- Cross-platform file/URL opener +function M.open_file_or_url(path) + local commands = { + mac = string.format('open "%s"', path), + linux = string.format('xdg-open "%s" &', path), + wsl = string.format('wslview "%s" &', path), + windows = string.format('start "" "%s"', path), + termux = string.format('am start -a android.intent.action.VIEW -d "%s"', path), + } + + local cmd = commands[M.os_name] + if cmd then + os.execute(cmd) + else + vim.notify("No supported file opener for this OS: " .. tostring(M.os_name), vim.log.levels.WARN) + end +end + +-------------------------------------------------- + +-- Automcmd to close netrw buffer when file is opened +vim.api.nvim_create_autocmd("FileType", { + pattern = "netrw", + callback = function() + vim.api.nvim_create_autocmd("BufEnter", { + once = true, + callback = function() + if vim.bo.filetype ~= "netrw" then + for _, buf in ipairs(vim.api.nvim_list_bufs()) do + if vim.bo[buf].filetype == "netrw" then + vim.api.nvim_buf_delete(buf, { force = true }) + end + end + end + end, + }) + end, +}) + +-------------------------------------------------- + +-- Autocomplete +vim.api.nvim_create_autocmd("InsertCharPre", { + callback = function() + -- Exit the autocmd if nvim-cmp is present + local cmp_is_present, _ = pcall(require, "cmp") + if cmp_is_present then + return + end + + -- Skip unwanted buffer types (Telescope, NvimTree, etc.) + local ft = vim.bo.filetype + local bt = vim.bo.buftype + local ignore_ft = { + "TelescopePrompt", + "prompt", + "nofile", + "terminal", + "help", + "quickfix", + "lazy", + "neo-tree", + "NvimTree", + "starter", + "packer", + } + + if bt ~= "" or vim.tbl_contains(ignore_ft, ft) then + return + end + + local col = vim.fn.col(".") + local line = vim.fn.getline(".") + local function safe_sub(i) + return line:sub(i, i) + end + + local prev3 = safe_sub(col - 3) + local prev2 = safe_sub(col - 2) + local prev1 = safe_sub(col - 1) + local curr = vim.v.char + + if curr:match("%w") and prev3:match("%W") and prev2:match("%w") and prev1:match("%w") then + vim.api.nvim_feedkeys( + vim.api.nvim_replace_termcodes("<C-n>", true, true, true), + "n", + true + ) + end + end, +}) +-------------------------------------------------- + +M.has_treesitter = function ( bufnr ) + if not bufnr then + bufnr = vim.api.nvim_get_current_buf() + end + + local highlighter = require( "vim.treesitter.highlighter" ) + + if highlighter.active[ bufnr ] then + return true + else + return false + end +end + +M.parse_treesitter = function ( bufnr, range ) + local parser = vim.treesitter.get_parser( bufnr ) + + -- XXX https://neovim.io/doc/user/treesitter.html#LanguageTree%3Aparse() + parser:parse( range ) +end + -- ... return M diff --git a/common/config/nvim/lua/user/opts.lua b/common/config/nvim/lua/user/opts.lua index cc8debc..bac80c3 100644..100755 --- a/common/config/nvim/lua/user/opts.lua +++ b/common/config/nvim/lua/user/opts.lua @@ -1,40 +1,137 @@ ---[[ opts.lua ]] - --- Environment ---vim.opt.shell = "zsh" -- -vim.o.updatetime = 250 -vim.o.shell = '/bin/zsh' -vim.scriptencoding = 'utf-8' -- -vim.opt.encoding = 'utf-8' -- -vim.opt.fileencoding = 'utf-8' -- -vim.g.python3_host_prog = '/usr/bin/python3' -- +-- ============================================================================ +-- Options +-- ============================================================================ + +local uname = vim.loop.os_uname() +local system = uname.sysname +local shell = nil + +if system == "Windows_NT" then + -- Windows options + if vim.fn.executable("pwsh") == 1 then + shell = "pwsh" + elseif vim.fn.executable("powershell") == 1 then + shell = "powershell" + elseif vim.fn.executable("bash") == 1 then + shell = "bash" + end +else + -- Unix-like systems: use the user's default shell + local env_shell = os.getenv("SHELL") + if env_shell and vim.fn.executable(env_shell) == 1 then + shell = env_shell + else + -- fallback logic + if vim.fn.executable("zsh") == 1 then + shell = vim.fn.exepath("zsh") + elseif vim.fn.executable("bash") == 1 then + shell = vim.fn.exepath("bash") + end + end +end + +-- Finally set the shell if we found one +if shell then + vim.o.shell = shell +end + +-- Core Settings +vim.opt.encoding = 'utf-8' +vim.opt.fileencoding = 'utf-8' +vim.scriptencoding = 'utf-8' +vim.opt.termguicolors = true +vim.opt.mouse = 'a' +vim.opt.clipboard = 'unnamedplus' +vim.opt.hidden = true +vim.opt.updatetime = 300 +vim.opt.timeoutlen = 500 +vim.opt.ttimeoutlen = 10 + +-- Display +vim.opt.number = true +vim.opt.relativenumber = true +vim.opt.cursorline = true +vim.opt.signcolumn = 'yes' +vim.opt.showcmd = true +vim.opt.showmode = true +vim.opt.showmatch = true +vim.opt.laststatus = 2 +vim.opt.cmdheight = 1 +vim.opt.scrolloff = 5 +vim.opt.sidescrolloff = 5 +vim.opt.display = 'lastline' + +-- Indentation +vim.opt.autoindent = true +vim.opt.smartindent = true +vim.opt.expandtab = true +vim.opt.tabstop = 2 +vim.opt.shiftwidth = 2 +vim.opt.softtabstop = 2 +vim.opt.shiftround = true + +-- Search +vim.opt.hlsearch = true +vim.opt.incsearch = true +vim.opt.ignorecase = true +vim.opt.smartcase = true +vim.opt.inccommand = 'split' + +-- Window Management +vim.opt.splitright = true +vim.opt.splitbelow = true +vim.opt.winminwidth = 1 +vim.opt.winwidth = 5 + +-- File Handling +vim.opt.autoread = true +--vim.opt.autowrite = true +vim.opt.backup = true +vim.opt.backupdir = vim.fn.stdpath('cache') .. '/backup//' +vim.opt.directory = vim.fn.stdpath('cache') .. '/swap//' +vim.opt.undofile = true +vim.opt.undodir = vim.fn.stdpath('cache') .. '/undo//' +vim.opt.swapfile = false + +-- Wildmenu +vim.opt.wildmenu = true +vim.opt.wildmode = 'longest:full,full' +vim.opt.wildignorecase = true +vim.opt.wildignore = '*.o,*.obj,.git,*.rbc,*.pyc,__pycache__' + + +vim.scriptencoding = "utf-8" -- +vim.opt.encoding = "utf-8" -- +vim.opt.fileencoding = "utf-8" -- +vim.g.python3_host_prog = "/usr/bin/python3" -- vim.g.loaded_python3_provider = 1 -- vim.g.sh_noisk = 1 -- iskeyword word boundaries when editing a 'sh' file vim.o.autochdir = true --vim.o.writeany= true -- Clipboard -vim.opt.clipboard:append({ 'unnamedplus' }) -- Install xclip or this will slowdown startup +vim.opt.clipboard:append({ "unnamedplus" }) -- Install xclip or this will slowdown startup -- Behaviour -vim.opt.backspace = { 'start', 'eol', 'indent' } -- Make backspace work as you would expect. +vim.opt.backspace = { "start", "eol", "indent" } -- Make backspace work as you would expect. vim.opt.hidden = true -- Switch between buffers without having to save first. +vim.opt.conceallevel = 2 vim.opt.splitbelow = true -- make split put the new buffer below the current buffer vim.opt.splitright = true -- make vsplit put the new buffer on the right of the current buffer vim.opt.scrolloff = 8 -- vim.opt.sidescrolloff = 8 -- how many lines to scroll when using the scrollbar vim.opt.autoread = true -- reload files if changed externally -vim.opt.display = 'lastline' -- Show as much as possible of the last line. -vim.opt.inccommand = 'split' -- +vim.opt.display = "lastline" -- Show as much as possible of the last line. +vim.opt.inccommand = "split" -- vim.opt.ttyfast = true -- Faster redrawing. vim.opt.lazyredraw = false -- Only redraw when necessary -vim.opt.keywordprg = ':help' -- :help options +vim.opt.keywordprg = ":help" -- :help options vim.opt.ruler = true -- vim.opt.errorbells = false -- vim.opt.list = true -- Show non-printable characters. vim.opt.showmatch = true -- vim.opt.matchtime = 3 -- -vim.opt.showbreak = '↪ ' -- +vim.opt.showbreak = "↪ " -- vim.opt.linebreak = true -- vim.opt.exrc = true -- --vim.opt.autochdir = true -- or use this to use <:e> to create a file in current directory @@ -59,10 +156,28 @@ vim.opt.shiftround = true -- >> indents to next multiple of 'shiftwidth'. vim.opt.smartindent = true -- smart indent -- Column/statusline/Cl -vim.opt.number = true -- + +-- Enable number and relativenumber by default +vim.opt.number = true +vim.opt.relativenumber = true + +-- Entering insert mode: disable relativenumber +vim.api.nvim_create_autocmd("InsertEnter", { + callback = function() + vim.opt.relativenumber = false + end, +}) + +-- Leaving insert mode: enable relativenumber +vim.api.nvim_create_autocmd("InsertLeave", { + callback = function() + vim.opt.relativenumber = true + end, +}) + vim.opt.title = true -- --vim.opt.colorcolumn = "+1" -- -vim.opt.signcolumn = 'yes:1' -- always show the sign column +vim.opt.signcolumn = "yes:1" -- always show the sign column --vim.opt.signcolumn = "yes:" .. vim.o.numberwidth --vim.opt.signcolumn = "number" --vim.opt.signcolumn = "no" -- @@ -90,51 +205,52 @@ vim.opt.report = 0 -- Always report changed lines. --vim.opt.stl = " " -- Backup/undo/swap -local prefix = vim.env.XDG_CONFIG_HOME or vim.fn.expand('~/.config') +local prefix = vim.env.XDG_CONFIG_HOME or vim.fn.expand("~/.config") --vim.opt.undodir = os.getenv("HOME") .. "/.vim/undodir" -vim.opt.undodir = { prefix .. '/nvim/tmp/.undo//' } -vim.opt.directory = { prefix .. '/nvim/tmp/.swp//' } -vim.opt.backupdir = { prefix .. '/nvim/tmp/.backup//' } +--vim.opt.undodir = { prefix .. "/nvim/tmp/.undo//" } +vim.opt.undodir = os.getenv("HOME") .. "/.vim/undodir" +vim.opt.directory = { prefix .. "/nvim/tmp/.swp//" } +vim.opt.backupdir = { prefix .. "/nvim/tmp/.backup//" } vim.opt.undofile = true -- vim.opt.swapfile = true -- vim.opt.backup = true -- --vim.opt.backupcopy = -- Add timestamp as extension for backup files -vim.api.nvim_create_autocmd('BufWritePre', { - group = vim.api.nvim_create_augroup('timestamp_backupext', { clear = true }), - desc = 'Add timestamp to backup extension', - pattern = '*', +vim.api.nvim_create_autocmd("BufWritePre", { + group = vim.api.nvim_create_augroup("timestamp_backupext", { clear = true }), + desc = "Add timestamp to backup extension", + pattern = "*", callback = function() - vim.opt.backupext = '-' .. vim.fn.strftime('%Y%m%d%H%M') + vim.opt.backupext = "-" .. vim.fn.strftime("%Y%m%d%H%M") end, }) -- Format --vim.opt.textwidth = 80 -- -vim.opt.isfname:append('@-@') +vim.opt.isfname:append("@-@") vim.cmd([[let &t_Cs = "\e[4:3m"]]) -- Undercurl vim.cmd([[let &t_Ce = "\e[4:0m"]]) -- -vim.opt.path:append({ '**' }) -- Finding files - Search down into subfolder -vim.cmd('set whichwrap+=<,>,[,],h,l') -- +vim.opt.path:append({ "**" }) -- Finding files - Search down into subfolder +vim.cmd("set whichwrap+=<,>,[,],h,l") -- vim.cmd([[set iskeyword+=-]]) -- --vim.cmd([[set formatoptions-=cro]]) -- TODO: this doesn't seem to work vim.opt.formatoptions = vim.opt.formatoptions - - 't' -- wrap with text width - + 'c' -- wrap comments - + 'r' -- insert comment after enter - - 'o' -- insert comment after o/O - - 'q' -- allow formatting of comments with gq - - 'a' -- format paragraphs - + 'n' -- recognized numbered lists - - '2' -- use indent of second line for paragraph - + 'l' -- long lines are not broken - + 'j' -- remove comment when joining lines + - "t" -- wrap with text width + + "c" -- wrap comments + + "r" -- insert comment after enter + - "o" -- insert comment after o/O + - "q" -- allow formatting of comments with gq + - "a" -- format paragraphs + + "n" -- recognized numbered lists + - "2" -- use indent of second line for paragraph + + "l" -- long lines are not broken + + "j" -- remove comment when joining lines vim.opt.wrapscan = true -- " Searches wrap around end-of-file. --vim.wo.number = true -- --vim.opt.wrap = false -- No Wrap lines --vim.opt.foldmethod = 'manual' -- --vim.opt.foldmethod = "expr" -- -vim.opt.foldmethod = 'manual' +vim.opt.foldmethod = "manual" vim.opt.foldlevel = 3 vim.opt.confirm = false --vim.opt.shortmess:append("sI") @@ -142,42 +258,42 @@ vim.opt.confirm = false --vim.opt.shortmess = "sI" --vim.o.shortmess = vim.o.shortmess:gsub('s', '') vim.opt.shortmess = table.concat({ -- Use abbreviations and short messages in command menu line. - 'f', -- Use "(3 of 5)" instead of "(file 3 of 5)". - 'i', -- Use "[noeol]" instead of "[Incomplete last line]". - 'l', -- Use "999L, 888C" instead of "999 lines, 888 characters". - 'm', -- Use "[+]" instead of "[Modified]". - 'n', -- Use "[New]" instead of "[New File]". - 'r', -- Use "[RO]" instead of "[readonly]". - 'w', -- Use "[w]", "[a]" instead of "written", "appended". - 'x', -- Use "[dos]", "[unix]", "[mac]" instead of "[dos format]", "[unix format]", "[mac format]". - 'o', -- Overwrite message for writing a file with subsequent message. - 'O', -- Message for reading a file overwrites any previous message. - 's', -- Disable "search hit BOTTOM, continuing at TOP" such messages. - 't', -- Truncate file message at the start if it is too long. - 'T', -- Truncate other messages in the middle if they are too long. - 'I', -- Don't give the :intro message when starting. - 'c', -- Don't give ins-completion-menu messages. - 'F', -- Don't give the file info when editing a file. + "f", -- Use "(3 of 5)" instead of "(file 3 of 5)". + "i", -- Use "[noeol]" instead of "[Incomplete last line]". + "l", -- Use "999L, 888C" instead of "999 lines, 888 characters". + "m", -- Use "[+]" instead of "[Modified]". + "n", -- Use "[New]" instead of "[New File]". + "r", -- Use "[RO]" instead of "[readonly]". + "w", -- Use "[w]", "[a]" instead of "written", "appended". + "x", -- Use "[dos]", "[unix]", "[mac]" instead of "[dos format]", "[unix format]", "[mac format]". + "o", -- Overwrite message for writing a file with subsequent message. + "O", -- Message for reading a file overwrites any previous message. + "s", -- Disable "search hit BOTTOM, continuing at TOP" such messages. + "t", -- Truncate file message at the start if it is too long. + "T", -- Truncate other messages in the middle if they are too long. + "I", -- Don't give the :intro message when starting. + "c", -- Don't give ins-completion-menu messages. + "F", -- Don't give the file info when editing a file. }) vim.opt.fillchars = { - horiz = '─', - horizup = '┴', - horizdown = '┬', - vert = '│', - vertleft = '┤', - vertright = '├', - verthoriz = '┼', - foldopen = '', - foldsep = '│', - foldclose = '', - fold = '─', - eob = ' ', + horiz = "─", + horizup = "┴", + horizdown = "┬", + vert = "│", + vertleft = "┤", + vertright = "├", + verthoriz = "┼", + foldopen = "", + foldsep = "│", + foldclose = "", + fold = "─", + eob = " ", --diff = "┃", - diff = '░', - msgsep = '━', + diff = "░", + msgsep = "━", --msgsep = "‾", } -vim.opt.listchars = { tab = '▸ ', trail = '·' } -- +vim.opt.listchars = { tab = "▸ ", trail = "·" } -- --vim.opt.fillchars:append({ eob = " " }) -- remove the ~ from end of buffer vim.opt.modeline = true -- vim.opt.modelines = 3 -- modelines (comments that set vim options on a per-file basis) @@ -194,40 +310,45 @@ vim.opt.smartcase = true -- smart case vim.opt.synmaxcol = 200 -- Only highlight the first 200 columns. --vim.opt.winblend = 30 --vim.opt.winblend = 5 -vim.opt.wildoptions = 'pum' -- +vim.opt.wildoptions = "pum" -- --vim.opt.pumblend = 5 -- vim.opt.pumblend = 12 -- --vim.opt.pumblend=15 vim.opt.pumheight = 10 -- pop up menu height -- Better Completion -vim.opt.complete = { '.', 'w', 'b', 'u', 't' } -- +vim.opt.complete = { ".", "w", "b", "u", "t" } -- --vim.opt.completeopt = { "longest,menuone,preview" } -- -vim.opt.completeopt = { 'menu', 'menuone', 'noselect' } +vim.opt.completeopt = { "menu", "menuone", "noselect" } --vim.opt.completeopt = { "menuone", "noselect" } -- mostly just for cmp --vim.opt.completeopt = { "menu", "menuone", "noselect" } -- +-- Spellcheck +vim.opt.spelllang = { "en_gb", "en_us" } -- Set a list of preferred dictionaries +vim.opt.spell = true +--vim.opt.spellfile = "~/.config/nvim/spell/en.utf-8.add" -- Specify a personal dictionary file + -- Wildmenu completion -- vim.opt.wildmenu = true -- -vim.opt.wildmode = { 'list:longest' } -- -vim.opt.wildignore:append({ '.hg', '.git', '.svn' }) -- Version control -vim.opt.wildignore:append({ '*.aux', '*.out', '*.toc' }) -- LaTeX intermediate files -vim.opt.wildignore:append({ '*.jpg', '*.bmp', '*.gif', '*.png', '*.jpeg' }) -- binary images -vim.opt.wildignore:append({ '*.o', '*.obj', '*.exe', '*.dll', '*.manifest' }) -- compiled object files -vim.opt.wildignore:append({ '*.spl' }) -- compiled spelling word lists -vim.opt.wildignore:append({ '*.sw?' }) -- Vim swap files -vim.opt.wildignore:append({ '*.DS_Store' }) -- OSX bullshit -vim.opt.wildignore:append({ '*.luac' }) -- Lua byte code -vim.opt.wildignore:append({ 'migrations' }) -- Django migrations -vim.opt.wildignore:append({ '*.pyc' }) -- Python byte code -vim.opt.wildignore:append({ '*.orig' }) -- Merge resolution files -vim.opt.wildignore:append({ '*/node_modules/*' }) -- +vim.opt.wildmode = { "list:longest" } -- +vim.opt.wildignore:append({ ".hg", ".git", ".svn" }) -- Version control +vim.opt.wildignore:append({ "*.aux", "*.out", "*.toc" }) -- LaTeX intermediate files +vim.opt.wildignore:append({ "*.jpg", "*.bmp", "*.gif", "*.png", "*.jpeg" }) -- binary images +vim.opt.wildignore:append({ "*.o", "*.obj", "*.exe", "*.dll", "*.manifest" }) -- compiled object files +vim.opt.wildignore:append({ "*.spl" }) -- compiled spelling word lists +vim.opt.wildignore:append({ "*.sw?" }) -- Vim swap files +vim.opt.wildignore:append({ "*.DS_Store" }) -- OSX bullshit +vim.opt.wildignore:append({ "*.luac" }) -- Lua byte code +vim.opt.wildignore:append({ "migrations" }) -- Django migrations +vim.opt.wildignore:append({ "*.pyc" }) -- Python byte code +vim.opt.wildignore:append({ "*.orig" }) -- Merge resolution files +vim.opt.wildignore:append({ "*/node_modules/*" }) -- -- Shada vim.opt.shada = "!,'1000,f1,<1000,s100,:1000,/1000,h" -- Sessions -vim.opt.sessionoptions = 'blank,buffers,curdir,folds,help,tabpages,winsize,winpos,terminal' +vim.opt.sessionoptions = "blank,buffers,curdir,folds,help,tabpages,winsize,winpos,terminal" --vim.opt.sessionoptions = "curdir,folds,help,options,tabpages,winsize,winpos,terminal,globals" -- --vim.opt.sessionoptions = "buffers,curdir,folds,help,tabpages,winsize,winpos,terminal" --vim.opt.sessionoptions:remove({ "blank", "buffers", "globals" }) @@ -241,13 +362,8 @@ vim.g.netrw_winsize = 25 --vim.cmd([[ -- "filetype plugin indent on --]]) -vim.cmd('filetype plugin on') -vim.cmd('filetype indent off') - --- Let clipboard register be + -vim.cmd([[ - let g:clipbrdDefaultReg = '+' -]]) +vim.cmd("filetype plugin on") +vim.cmd("filetype indent off") --vim.cmd([[ -- "autocmd BufEnter * :syntax sync fromstart @@ -256,6 +372,7 @@ vim.cmd([[ -- "autocmd FileType lua set comments=s1:---,m:--,ex:-- --]]) + -- Fast macros without lazyredraw vim.cmd([[ set re=0 @@ -280,7 +397,7 @@ vim.cmd([[ " Only show cursorline in the augroup END ]]) vim.opt.cursorline = true -- -vim.opt.guicursor = 'i:ver100,r:hor100' -- +vim.opt.guicursor = "i:ver100,r:hor100" -- -- Trailing whitespace vim.cmd([[ " Only show in insert mode @@ -319,33 +436,3 @@ vim.cmd([[ let &scrollback=s:scroll_value endfunction ]]) - --- Yank to clipboard in Termux -if vim.fn.has('termux') == 1 then - local Job = require('plenary.job') - vim.api.nvim_create_autocmd('TextYankPost', { - pattern = '*', - callback = function() - Job:new({ - command = 'termux-clipboard-set', - writer = vim.fn.getreg('@'), - }):start() - end, - }) -end - -function GetTermuxClipboard() - if vim.fn.has('termux') == 1 then - local sysclip = vim.fn.system('termux-clipboard-get') - if sysclip ~= '@' then - vim.fn.setreg('@', sysclip) - end - end - return '' -end - -if vim.fn.has('termux') == 1 then - vim.cmd('autocmd TextYankPost * call GetTermuxClipboard()') - vim.cmd('noremap <expr> p Paste("p")') - vim.cmd('noremap <expr> P Paste("P")') -end diff --git a/common/config/nvim/lua/user/pack.lua b/common/config/nvim/lua/user/pack.lua deleted file mode 100644 index 7ed86db..0000000 --- a/common/config/nvim/lua/user/pack.lua +++ /dev/null @@ -1,403 +0,0 @@ -local fn = vim.fn - --------------------------------------------------- - --- Automatically install packer -local install_path = fn.stdpath("data") .. "/site/pack/packer/start/packer.nvim" -if fn.empty(fn.glob(install_path)) > 0 then - PACKER_BOOTSTRAP = fn.system({ - "git", - "clone", - "--depth", - "1", - "https://github.com/wbthomason/packer.nvim", - install_path, - }) - print("Installing packer, please close and reopen Neovim...") - vim.cmd([[packadd packer.nvim]]) -end - --------------------------------------------------- - --- Autocommand that reloads neovim whenever you save this file -vim.cmd([[ - augroup packer_user_config - autocmd! - autocmd BufWritePost pack.lua source <afile> | PackerSync - augroup end -]]) - --------------------------------------------------- - --- Use a protected call so don't error out on first use -local status_ok, packer = pcall(require, "packer") -if not status_ok then - return -end - --------------------------------------------------- - --- Have packer use a popup window and set a maximum number of jobs -packer.init({ - auto_reload_compiled = true, - --max_jobs = 90, - display = { - open_fn = function() - return require("packer.util").float({ border = "rounded" }) - end, - }, -}) - --------------------------------------------------- - --- Install plugins here -return packer.startup(function(use) - -- Defaults - use("wbthomason/packer.nvim") -- Have packer manage itself (package manager) - use("nvim-lua/plenary.nvim") -- Useful lua functions used by lots of plugins - use("lewis6991/impatient.nvim") -- Faster loading/startup times - - -- Tree-sitter - use({ "nvim-treesitter/nvim-treesitter", run = ":TSUpdate" }) -- For language parsing, examples: highlighting, folding, jumping, refactoring... - use("nvim-treesitter/nvim-treesitter-refactor") -- Refactor module for nvim-treesitter - - -- lsp - use("williamboman/mason.nvim") -- Package manager to install and manage LSP servers, DAP servers, linters and formatters - use("neovim/nvim-lspconfig") -- Collection of LSP configs - use("williamboman/mason-lspconfig.nvim") -- Bridges mason.nvim with nvim-lspconfig to help use them together - use({ - "https://git.sr.ht/~whynothugo/lsp_lines.nvim", - config = function() - require("lsp_lines").setup() - end, - }) - use("rmagatti/goto-preview") - - -- Debugger - use("mfussenegger/nvim-dap") -- Debug Adapter Protocol client implementation for Neovim - use("rcarriga/nvim-dap-ui") -- UI for nvim-dap - --use { "rcarriga/nvim-dap-ui", requires = {"mfussenegger/nvim-dap"} } - use("theHamsta/nvim-dap-virtual-text") - use("gabrielpoca/replacer.nvim") - use("jayp0521/mason-nvim-dap.nvim") - --use({ - -- "jayp0521/mason-nvim-dap.nvim", - -- config = function() - -- require("mason-nvim-dap").setup({ - -- automatic_installation = true, - -- ensure_installed = { "python", "cppdbg", "codelldb" }, - -- }) - -- end, - --}) - - -- Linters/Formatters - use("mhartington/formatter.nvim") - use("jay-babu/mason-null-ls.nvim") - --use({"jayp0521/mason-null-ls.nvim", - -- config = function() - -- require('mason-null-ls.nvim').setup({ - -- automatic_setup = true, - -- }) - -- end - --}) - use({ - "jose-elias-alvarez/null-ls.nvim", -- Provides LSP: linters, formatters, diagnostics, code actions and etc... - requires = { "jay-babu/mason-null-ls.nvim" }, - }) - - -- Completion - use("hrsh7th/nvim-cmp") -- Completion engine plugin - use("hrsh7th/cmp-nvim-lsp") -- Completion source for nvim-lsp - use("hrsh7th/cmp-buffer") -- Completion source for content of current buffer - use("hrsh7th/cmp-path") -- Completion source for paths - use("hrsh7th/cmp-cmdline") -- Completion source for command-line - use("petertriho/cmp-git") -- Completion source for git - use("tamago324/cmp-zsh") -- Completion source for zsh - use("f3fora/cmp-spell") -- Completion source for spell-checking - use("hrsh7th/cmp-calc") -- Completion source for math calculation - use("saadparwaiz1/cmp_luasnip") -- Completion source for snippets, specifically for luasnip - use("hrsh7th/cmp-nvim-lsp-signature-help") -- Completion source for displaying function signatures with the current parameter emphasized - use("rcarriga/cmp-dap") - - -- Snippets - use("L3MON4D3/LuaSnip") -- Snippet engine - use("rafamadriz/friendly-snippets") -- Collection of snippets to use - - -- Git - use("tpope/vim-fugitive") -- - --use("dinhhuy258/git.nvim") -- For git blame & browse - use("kdheepak/lazygit.nvim") -- Terminal UI for git commands - use("lewis6991/gitsigns.nvim") -- Git decorations - - -- File explorer/fuzzy finder - use("kyazdani42/nvim-tree.lua") -- File explorer - use("ibhagwan/fzf-lua") -- Fuzzy finder - use("ThePrimeagen/harpoon") - --use("nvim-telescope/telescope.nvim") -- Fuzzy finder with lots of features/extendabilities - use({ - "nvim-telescope/telescope.nvim", - branch = "0.1.x", - --config = function() - -- require('plugins.telescope').setup() - --end, - requires = { - "nvim-lua/plenary.nvim", - "nvim-telescope/telescope-live-grep-args.nvim", - "nvim-telescope/telescope-file-browser.nvim", - { "nvim-telescope/telescope-fzf-native.nvim", run = "make" }, - }, - }) - use({ "nvim-telescope/telescope-fzf-native.nvim", run = "make" }) -- Support fzf syntax/algorithm - use("nvim-telescope/telescope-ui-select.nvim") -- - use("nvim-telescope/telescope-project.nvim") -- - use("nvim-telescope/telescope-media-files.nvim") -- - use("nvim-telescope/telescope-file-browser.nvim") -- - use({ "nvim-telescope/telescope-symbols.nvim", after = "telescope.nvim" }) -- Search emoji(s) and other symbols - use("nvim-telescope/telescope-dap.nvim") - use("axkirillov/telescope-changed-files") -- - use("smartpde/telescope-recent-files") - use("rmagatti/auto-session") - use("rmagatti/session-lens") - - -- UX - use("folke/neodev.nvim") - use({ - "numToStr/Navigator.nvim", -- Navigate between Tmux and Nvim - config = function() - require("Navigator").setup() - end, - }) - use({ "tpope/vim-eunuch", cmd = { "Rename", "Delete", "Mkdir" } }) -- Handy unix commands inside Vim (Rename, Move etc.) - --use("tpope/vim-obsession") -- - use("tpope/vim-unimpaired") -- - --use("tpope/vim-surround") -- - use({ - "kylechui/nvim-surround", - tag = "*", -- Use for stability; omit to use `main` branch for the latest features - }) - --use("vimpostor/vim-tpipeline") -- - --use("nathom/filetype.nvim") -- - use("mbbill/undotree") - use({ - "myusuf3/numbers.vim", -- - vim.cmd("let g:numbers_exclude = ['dashboard']"), - }) - use("windwp/nvim-autopairs") -- - use("numToStr/Comment.nvim") -- - use("akinsho/toggleterm.nvim") -- - use("tweekmonster/startuptime.vim") -- - use("qpkorr/vim-bufkill") - use({ - "ggandor/leap.nvim", -- - config = function() - require("leap").add_default_mappings() - --require("leap").set_default_keymaps() - --vim.keymap.set('n', '-', '<Plug>(leap-forward)', {}) - --vim.keymap.set('n', '_', '<Plug>(leap-backward)', {}) - end, - }) - use({ - "ggandor/flit.nvim", -- - config = function() - require("flit").setup() - end, - }) - use("folke/which-key.nvim") -- - use("folke/zen-mode.nvim") -- - use("romainl/vim-cool") -- - use("antoinemadec/FixCursorHold.nvim") -- - use({ - "folke/trouble.nvim", - requires = "nvim-tree/nvim-web-devicons", - }) - use({ - "airblade/vim-rooter", -- - --vim.cmd("let g:rooter_change_directory_for_non_project_files = ''"), - --vim.cmd("let g:rooter_change_directory_for_non_project_files = 'current'") - }) - use({ "michaelb/sniprun", run = "bash ./install.sh" }) - use({ "stevearc/overseer.nvim" }) - --use("vim-test/vim-test") -- - --use({ - -- "rcarriga/vim-ultest", -- - -- requires = { "vim-test/vim-test" }, - -- run = ":UpdateRemotePlugins", - -- config = function() - -- require("plugins.ultest") - -- end, - --}) - --use({"rcarriga/neotest", - -- config = function() - -- require("neotest").setup() - --end, - --}) - use({ - "nvim-neotest/neotest", - requires = { - { - "nvim-neotest/neotest-python", - "nvim-neotest/neotest-plenary", - "nvim-neotest/neotest-vim-test", - }, - }, - }) - use("kawre/leetcode.nvim") - use("m4xshen/hardtime.nvim") - use({ - "luckasRanarison/nvim-devdocs", - config = function() - require("nvim-devdocs").setup() - end, - }) - - -- Colorschemes - use("bluz71/vim-nightfly-guicolors") - use("ayu-theme/ayu-vim") - use("joshdick/onedark.vim") - use("NTBBloodbath/doom-one.nvim") - use("nyngwang/nvimgelion") - use("projekt0n/github-nvim-theme") - use("folke/tokyonight.nvim") - use("ribru17/bamboo.nvim") - - -- UI - use("kyazdani42/nvim-web-devicons") -- - use("onsails/lspkind-nvim") -- - use({ "kevinhwang91/nvim-ufo", requires = "kevinhwang91/promise-async" }) -- Fold code - use("lukas-reineke/indent-blankline.nvim") - use({ - "luukvbaal/statuscol.nvim", - config = function() - local builtin = require("statuscol.builtin") - require("statuscol").setup({ - relculright = true, - segments = { - { text = { builtin.foldfunc }, click = "v:lua.ScFa" }, - { text = { "%s" }, click = "v:lua.ScSa" }, - { text = { builtin.lnumfunc, " " }, click = "v:lua.ScLa" }, - }, - }) - end, - }) - use({ - "glepnir/dashboard-nvim", - --event = 'VimEnter', - requires = { "nvim-tree/nvim-web-devicons" }, - }) - use("rcarriga/nvim-notify") -- Notification plugin - use("karb94/neoscroll.nvim") -- Faster/smooth scrolling - --use("MunifTanjim/prettier.nvim") -- Prettier plugin for Neovim's built-in LSP client - use({ - "norcalli/nvim-colorizer.lua", -- colorize hexa and rgb strings - cmd = { "ColorizerToggle", "ColorizerAttachToBuffer" }, - config = function() - require("colorizer").setup({ - --'*'; - user_default_options = { - RGB = true, - RRGGBB = true, - names = false, - RRGGBBAA = false, - css = false, - css_fn = true, - mode = "foreground", - }, - }) - end, - }) - use("MunifTanjim/nui.nvim") - use({ - "j-hui/fidget.nvim", - tag = "legacy", - }) -- UI to show nvim-lsp progress - use("metakirby5/codi.vim") - use({ - "simrat39/symbols-outline.nvim", -- - config = function() - require("symbols-outline").setup({ - auto_close = true, - }) - end, - }) - use({ - "kosayoda/nvim-lightbulb", -- - requires = "antoinemadec/FixCursorHold.nvim", - }) - use({ - "SmiteshP/nvim-navic", -- Statusline/Winbar component that uses LSP to show current code context - requires = "neovim/nvim-lspconfig", - }) - use({ - "rebelot/heirline.nvim", -- Statusline that is highly configurable - --requires = 'kyazdani42/nvim-web-devicons', - --event = 'VimEnter', - }) - use({ - "samodostal/image.nvim", - config = function() - require("image").setup({}) - end, - }) - -- Language specific tools - use("simrat39/rust-tools.nvim") -- Rust tooling ecosystem - use({ - "saecki/crates.nvim", -- - requires = { "nvim-lua/plenary.nvim" }, - config = function() - require("crates").setup() - end, - }) - use({ - "akinsho/flutter-tools.nvim", - requires = { - "nvim-lua/plenary.nvim", - "stevearc/dressing.nvim", -- optional for vim.ui.select - }, - config = function() - require("flutter-tools").setup({ - debugger = { - enabled = true, - run_via_dap = true, - }, - }) - end, - }) - use({ - "iamcco/markdown-preview.nvim", -- Markdown Preview - run = function() - vim.fn["mkdp#util#install"]() - end, - vim.cmd("let g:mkdp_auto_close = 0"), - }) - use({ - "ellisonleao/glow.nvim", -- Markdown Preview - config = function() - local glow_path - - -- Check if glow exists in ~/.local/bin - if vim.fn.executable("~/.local/bin/glow") == 1 then - glow_path = "~/.local/bin/glow" - else - -- Fallback to /usr/bin/glow - glow_path = "/usr/bin/glow" - end - - require("glow").setup({ - style = "dark", - glow_path = glow_path, - }) - end, - }) - use({ - "lervag/vimtex", - }) - use("micangl/cmp-vimtex") - - -------------------------------------------------- - - -- Automatically set up your configuration after cloning packer.nvim - -- Put this at the end after all plugins - if PACKER_BOOTSTRAP then - require("packer").sync() - end -end) diff --git a/common/config/nvim/lua/user/view.lua b/common/config/nvim/lua/user/view.lua index 837fce4..f243194 100644..100755 --- a/common/config/nvim/lua/user/view.lua +++ b/common/config/nvim/lua/user/view.lua @@ -1,69 +1,180 @@ --- Colorscheme - --- Colors -vim.opt.termguicolors = true - --- Available colorschemes: --- [[ nightfly ayu onedark doom-one nvimgelion github_dark tokyonight bamboo ]] - -require('tokyonight').setup({ - style = 'night', - transparent = true, - transparent_sidebar = true, - dim_inactive = false, - styles = { - sidebars = 'transparent', - floats = 'transparent', - }, -}) - --- Define default color scheme -local default_colorscheme = 'tokyonight-night' -local fallback_colorscheme = 'desert' - --- Attempt to set the default color scheme -local status_ok, _ = pcall(vim.cmd, 'colorscheme ' .. default_colorscheme) - --- If the default color scheme is not found, use the fallback color scheme -if not status_ok then - vim.cmd('colorscheme ' .. fallback_colorscheme) +-- ============================================================================ +-- View/UI +-- ============================================================================ + +local M = {} + +-- List of available themes (for reference or user selection UI) +M.available_themes = { + "nightfly", "ayu", "onedark", "doom-one", "nvimgelion", "github_dark", "tokyonight", "bamboo", "oxocarbon" +} + +-- Configuration +local default_colorscheme = "tokyonight" +local fallback_colorscheme = "default" + +-- Diagnostic icons +local Signs = { + Error = "✘", + Warn = "", + Hint = "◉", + Info = "", +} + +-- Setup Function +function M.setup() + -- Truecolor & syntax + vim.opt.termguicolors = true + vim.cmd("syntax on") + + -- Colorscheme setup with fallback + local ok = pcall(vim.cmd, "colorscheme " .. default_colorscheme) + if not ok then + vim.cmd("colorscheme " .. fallback_colorscheme) + end + + -- Optional: Tokyonight configuration + pcall(function() + require("tokyonight").setup({ + style = "night", + transparent = true, + transparent_sidebar = true, + dim_inactive = false, + styles = { + sidebars = "transparent", + floats = "transparent", + }, + }) + end) + + -- Highlight groups + local highlights = { + -- Core UI + { group = "Normal", options = { bg = "none" } }, + { group = "NormalNC", options = { bg = "none" } }, + { group = "NormalFloat", options = { bg = "none" } }, + { group = "Float", options = { bg = "none" } }, + { group = "FloatBorder", options = { bg = "none", fg = "#7f8493" } }, + { group = "StatusLine", options = { bg = "none" } }, + { group = "TabLine", options = { bg = "#333842", bold = true } }, + { group = "TabLineSel", options = { bg = "#333842", bold = true } }, + { group = "TabLineFill", options = { bg = "none", bold = true } }, + { group = "WinBar", options = { bg = "none", bold = true } }, + { group = "WinBarNC", options = { bg = "none" } }, + { group = "WinSeparator", options = { bg = "none", fg = "#444b62", bold = true } }, + { group = "EndOfBuffer", options = { bg = "none", fg = "#7f8493" } }, + { group = "NonText", options = { bg = "none", fg = "#555b71" } }, + { group = "LineNr", options = { bg = "none", fg = "#555b71" } }, + { group = "SignColumn", options = { bg = "none" } }, + { group = "FoldColumn", options = { bg = "none" } }, + { group = "CursorLine", options = { bg = "#3a3f52" } }, + { group = "CursorLineNr", options = { bg = "#3a3f52", fg = "#cdd6f4" } }, + { group = "CursorLineSign", options = { bg = "none" } }, + { group = "Title", options = { bg = "none", bold = true } }, + { group = "Comment", options = { bg = "none", fg = "#6b7089" } }, + { group = "MsgSeparator", options = { bg = "none" } }, + { group = "WarningMsg", options = { bg = "none", fg = "#e6c384" } }, + { group = "MoreMsg", options = { bg = "none", fg = "#7f8493" } }, + + -- Pop-up / menu + { group = "Pmenu", options = { bg = "none" } }, + { group = "PmenuSel", options = { fg = "black", bg = "white" } }, + { group = "PmenuThumb", options = { bg = "none" } }, + { group = "PmenuSbar", options = { bg = "none" } }, + { group = "PmenuExtra", options = { bg = "none" } }, + { group = "PmenuExtraSel", options = { bg = "none" } }, + { group = "WildMenu", options = { link = "PmenuSel" } }, + + -- Telescope + { group = "TelescopeNormal", options = { bg = "none" } }, + { group = "TelescopePromptNormal", options = { bg = "none" } }, + { group = "TelescopeResultsNormal", options = { bg = "none" } }, + { group = "TelescopePreviewNormal", options = { bg = "none" } }, + { group = "TelescopeBorder", options = { bg = "none", fg = "#7f8493" } }, + { group = "TelescopeMatching", options = { fg = "#cba6f7", bold = true } }, + + -- Blending + { group = "Winblend", options = { bg = "none" } }, + { group = "Pumblend", options = { bg = "none" } }, + + ---- NvimTree + --{ group = "NvimTreeNormal", options = { bg = "none", fg = "NONE" } }, + --{ group = "NvimTreeNormalNC", options = { bg = "none", fg = "NONE" } }, + --{ group = "NvimTreeNormalFloat", options = { bg = "none" } }, + --{ group = "NvimTreeEndOfBuffer", options = { bg = "none" } }, + --{ group = "NvimTreeCursorLine", options = { bg = "#50fa7b", fg = "#000000" } }, + --{ group = "NvimTreeSymlinkFolderName", options = { fg = "#f8f8f2", bg = "none" } }, + --{ group = "NvimTreeFolderName", options = { fg = "#f8f8f2", bg = "none" } }, + --{ group = "NvimTreeRootFolder", options = { fg = "#f8f8f2", bg = "none" } }, + --{ group = "NvimTreeEmptyFolderName", options = { fg = "#f8f8f2", bg = "none" } }, + --{ group = "NvimTreeOpenedFolderName", options = { fg = "#f8f8f2", bg = "none" } }, + --{ group = "NvimTreeOpenedFile", options = { fg = "#50fa7b", bg = "none" } }, + --{ group = "NvimTreeExecFile", options = { fg = "#ff882a", bg = "none" } }, + } + + for _, hl in ipairs(highlights) do + vim.api.nvim_set_hl(0, hl.group, hl.options) + end + + -- Reapply highlights on ColorScheme change + vim.api.nvim_create_autocmd("ColorScheme", { + group = vim.api.nvim_create_augroup("CustomHighlights", { clear = true }), + pattern = "*", + callback = function() + for _, hl in ipairs(highlights) do + vim.api.nvim_set_hl(0, hl.group, hl.options) + end + end, + }) + + -- Optional window separator styling + vim.cmd([[ + augroup CustomWinSeparator + autocmd! + autocmd WinEnter * setlocal winhl=WinSeparator:WinSeparatorA + autocmd WinLeave * setlocal winhl=WinSeparator:WinSeparator + augroup END + ]]) + + -- Diagnostics configuration + local border = "rounded" + vim.diagnostic.config({ + signs = { + text = { + [vim.diagnostic.severity.ERROR] = Signs.Error, + [vim.diagnostic.severity.WARN] = Signs.Warn, + [vim.diagnostic.severity.HINT] = Signs.Hint, + [vim.diagnostic.severity.INFO] = Signs.Info, + }, + }, + underline = true, + virtual_text = false, + virtual_lines = false, + float = { + show_header = true, + source = "always", + border = border, + focusable = true, + }, + update_in_insert = false, + severity_sort = true, + }) + + -- Fallback statusline if heirline is missing + local heirline_ok, _ = pcall(require, "heirline") + if not heirline_ok then + local statusline_path = vim.fn.stdpath("config") .. "/autoload/statusline.vim" + if vim.fn.filereadable(statusline_path) == 1 then + vim.cmd.source(statusline_path) + vim.api.nvim_create_autocmd("VimEnter", { + callback = function() + vim.cmd("call autoload#statusline#ActivateStatusline()") + end, + }) + else + vim.notify("Fallback statusline script not found:\n" .. statusline_path, vim.log.levels.ERROR) + end + end end -vim.api.nvim_command('syntax on') -vim.api.nvim_command('highlight Normal guibg=NONE ctermbg=NONE') -vim.api.nvim_command('highlight NormalNC guibg=NONE') -vim.api.nvim_command('highlight NormalFloat guibg=NONE ctermbg=NONE') -vim.api.nvim_command('highlight Float guibg=NONE ctermbg=NONE') -vim.api.nvim_command('highlight NonText guibg=NONE ctermbg=NONE') -vim.api.nvim_command('highlight SignColumn guibg=NONE') -vim.api.nvim_command('highlight FoldColumn guibg=NONE') -vim.api.nvim_command('highlight CursorLineSign guibg=NONE ctermbg=NONE') -vim.api.nvim_command('highlight Title guibg=NONE gui=bold') -vim.api.nvim_command('highlight TabLine guibg=#333842 gui=bold') -vim.api.nvim_command('highlight TabLineSel guibg=#333842 gui=bold') -vim.api.nvim_command('highlight TabLineFill guibg=NONE gui=bold') -vim.api.nvim_command('highlight WinBar guibg=NONE ctermbg=NONE gui=bold') -vim.api.nvim_command('highlight WinBarNC guibg=NONE ctermbg=NONE') -vim.api.nvim_command('highlight LineNr guibg=NONE ctermbg=NONE') -vim.api.nvim_command('highlight WinSeparator guibg=NONE gui=bold ctermbg=NONE') -vim.api.nvim_command('highlight MsgSeparator guibg=NONE ctermbg=NONE') -vim.api.nvim_command('highlight EndOfBuffer guibg=NONE guifg=Normal') -vim.api.nvim_command('highlight Comment guibg=NONE ctermbg=NONE') -vim.api.nvim_command('highlight Winblend guibg=NONE ctermbg=NONE') -vim.api.nvim_command('highlight NormalFloat guibg=NONE ctermbg=NONE') -vim.api.nvim_command('highlight Pumblend guibg=NONE ctermbg=NONE') -vim.api.nvim_command('highlight WildMenu guibg=NONE ctermbg=NONE') -vim.api.nvim_command('highlight WarningMsg guibg=NONE ctermbg=NONE') -vim.api.nvim_command('highlight Pmenu guibg=NONE ctermbg=NONE') -vim.api.nvim_command('highlight PmenuSel guibg=NONE ctermbg=NONE') -vim.api.nvim_command('highlight PmenuThumb guibg=NONE ctermbg=NONE') -vim.api.nvim_command('highlight PmenuSbar guibg=NONE ctermbg=NONE') -vim.api.nvim_command('highlight PmenuExtra guibg=NONE ctermbg=NONE') -vim.api.nvim_command('highlight PmenuExtraSel guibg=NONE ctermbg=NONE') -vim.api.nvim_command('highlight MoreMsg guibg=NONE ctermbg=NONE') - --- Set different window separator colorscheme -vim.cmd([[ -au WinEnter * setl winhl=WinSeparator:WinSeparatorA -au WinLeave * setl winhl=WinSeparator:WinSeparator -]]) +return M |
