diff --git a/init.lua b/init.lua index ce673961..ee9ea126 100644 --- a/init.lua +++ b/init.lua @@ -407,11 +407,30 @@ require('lazy').setup({ -- You can put your default mappings / updates / etc. in here -- All the info you're looking for is in `:help telescope.setup()` -- - -- defaults = { - -- mappings = { - -- i = { [''] = 'to_fuzzy_refine' }, - -- }, - -- }, + defaults = { + mappings = { + i = { + [''] = 'move_selection_next', + [''] = 'move_selection_previous', + }, + }, + file_ignore_patterns = { '.git/' }, + }, + pickers = { + find_files = { + hidden = true, + }, + live_grep = { + additional_args = function() + return { '--hidden' } + end, + }, + grep_string = { + additional_args = function() + return { '--hidden' } + end, + }, + }, -- pickers = {} extensions = { ['ui-select'] = { @@ -432,14 +451,14 @@ require('lazy').setup({ vim.keymap.set('n', 'fs', builtin.builtin, { desc = '[F]ind [S]elect Telescope' }) vim.keymap.set('n', 'fw', builtin.grep_string, { desc = '[F]ind current [W]ord' }) vim.keymap.set('n', 'fg', builtin.live_grep, { desc = '[F]ind by [G]rep' }) + vim.keymap.set('n', '/', builtin.live_grep, { desc = '[F]ind by [G]rep' }) vim.keymap.set('n', 'fd', builtin.diagnostics, { desc = '[F]ind [D]iagnostics' }) vim.keymap.set('n', 'fr', builtin.resume, { desc = '[F]ind [R]esume' }) vim.keymap.set('n', 'f.', builtin.oldfiles, { desc = '[F]ind Recent Files ("." for repeat)' }) vim.keymap.set('n', '', builtin.buffers, { desc = '[ ] Find existing buffers' }) - -- vim.api.nvim_set_keymap('n', 'gd', 'lua vim.lsp.buf.definition()', { noremap = true, silent = true }) -- Slightly advanced example of overriding default behavior and theme - vim.keymap.set('n', '/', function() + vim.keymap.set('n', 'f/', function() -- You can pass additional configuration to Telescope to change the theme, layout, etc. builtin.current_buffer_fuzzy_find(require('telescope.themes').get_dropdown { winblend = 10, @@ -688,6 +707,7 @@ require('lazy').setup({ -- -- But for many setups, the LSP (`ts_ls`) will work just fine ts_ls = {}, + cssls = {}, -- lua_ls = { @@ -819,7 +839,7 @@ require('lazy').setup({ -- Disable "format_on_save lsp_fallback" for languages that don't -- have a well standardized coding style. You can add additional -- languages here or re-enable it for the disabled ones. - local disable_filetypes = { c = true, cpp = true, vue = true } + local disable_filetypes = { c = true, cpp = true } if disable_filetypes[vim.bo[bufnr].filetype] then return nil else @@ -831,11 +851,17 @@ require('lazy').setup({ end, formatters_by_ft = { lua = { 'stylua' }, - -- Conform can also run multiple formatters sequentially - -- python = { "isort", "black" }, - -- - -- You can use 'stop_after_first' to run the first available formatter from the list - -- javascript = { "prettierd", "prettier", stop_after_first = true }, + javascript = { 'prettierd', 'prettier', stop_after_first = true }, + typescript = { 'prettierd', 'prettier', stop_after_first = true }, + javascriptreact = { 'prettierd', 'prettier', stop_after_first = true }, + typescriptreact = { 'prettierd', 'prettier', stop_after_first = true }, + vue = { 'prettierd', 'prettier', stop_after_first = true }, + css = { 'prettierd', 'prettier', stop_after_first = true }, + scss = { 'prettierd', 'prettier', stop_after_first = true }, + html = { 'prettierd', 'prettier', stop_after_first = true }, + json = { 'prettierd', 'prettier', stop_after_first = true }, + yaml = { 'prettierd', 'prettier', stop_after_first = true }, + markdown = { 'prettierd', 'prettier', stop_after_first = true }, }, }, }, @@ -895,10 +921,11 @@ require('lazy').setup({ -- : Open menu or open docs if already open -- / or /: Select next/previous item -- : Hide menu - -- : Toggle signature help -- -- See :h blink-cmp-config-keymap for defining your own keymap preset = 'super-tab', + [''] = { 'select_next', 'fallback' }, + [''] = { 'select_prev', 'fallback' }, -- For more advanced Luasnip keymaps (e.g. selecting choice nodes, expansion) see: -- https://github.com/L3MON4D3/LuaSnip?tab=readme-ov-file#keymaps @@ -944,28 +971,6 @@ require('lazy').setup({ }, }, - { -- You can easily change to a different colorscheme. - -- Change the name of the colorscheme plugin below, and then - -- change the command in the config to whatever the name of that colorscheme is. - -- - -- If you want to see what colorschemes are already installed, you can use `:Telescope colorscheme`. - 'folke/tokyonight.nvim', - priority = 1000, -- Make sure to load this before all the other start plugins. - config = function() - ---@diagnostic disable-next-line: missing-fields - require('tokyonight').setup { - styles = { - comments = { italic = false }, -- Disable italics in comments - }, - } - - -- Load the colorscheme here. - -- Like many other themes, this one has different styles, and you could load - -- any other, such as 'tokyonight-storm', 'tokyonight-moon', or 'tokyonight-day'. - vim.cmd.colorscheme 'tokyonight-night' - end, - }, - -- Highlight todo, notes, etc in comments { 'folke/todo-comments.nvim', event = 'VimEnter', dependencies = { 'nvim-lua/plenary.nvim' }, opts = { signs = false } }, @@ -1053,7 +1058,9 @@ require('lazy').setup({ -- -- Uncomment the following line and add your plugins to `lua/custom/plugins/*.lua` to get going. { import = 'custom.plugins' }, + { import = 'custom.themes' }, -- + -- For additional information with loading, sourcing and examples see `:help lazy.nvim-🔌-plugin-spec` -- Or use telescope! -- In normal mode type `sh` then write `lazy.nvim-plugin` diff --git a/lazy-lock.json b/lazy-lock.json index 37c77c31..95d26921 100644 --- a/lazy-lock.json +++ b/lazy-lock.json @@ -1,23 +1,36 @@ { + "Comment.nvim": { "branch": "master", "commit": "e30b7f2008e52442154b66f7c519bfd2f1e32acb" }, "LuaSnip": { "branch": "master", "commit": "73813308abc2eaeff2bc0d3f2f79270c491be9d7" }, "blink.cmp": { "branch": "main", "commit": "327fff91fe6af358e990be7be1ec8b78037d2138" }, - "conform.nvim": { "branch": "master", "commit": "9d859cbfbde7a1bd1770e7c97aef30ec5a237a71" }, - "fidget.nvim": { "branch": "main", "commit": "0ba1e16d07627532b6cae915cc992ecac249fb97" }, - "gitsigns.nvim": { "branch": "main", "commit": "1ee5c1fd068c81f9dd06483e639c2aa4587dc197" }, + "catppuccin": { "branch": "main", "commit": "da33755d00e09bff2473978910168ff9ea5dc453" }, + "conform.nvim": { "branch": "master", "commit": "cde4da5c1083d3527776fee69536107d98dae6c9" }, + "curl.nvim": { "branch": "main", "commit": "3ee14fbafc8169fc803e80562ce7ac5b4474bdff" }, + "fidget.nvim": { "branch": "main", "commit": "e32b672d8fd343f9d6a76944fedb8c61d7d8111a" }, + "git-blame.nvim": { "branch": "master", "commit": "9874ec1ec8bc53beb33b7cd82c092b85271a578b" }, + "github-theme": { "branch": "main", "commit": "c106c9472154d6b2c74b74565616b877ae8ed31d" }, + "gitsigns.nvim": { "branch": "main", "commit": "20ad4419564d6e22b189f6738116b38871082332" }, "guess-indent.nvim": { "branch": "main", "commit": "84a4987ff36798c2fc1169cbaff67960aed9776f" }, - "lazy.nvim": { "branch": "main", "commit": "59334064f8604ca073791c25dcc5c9698865406e" }, - "lazydev.nvim": { "branch": "main", "commit": "258d2a5ef4a3e3d6d9ba9da72c9725c53e9afcbd" }, - "mason-lspconfig.nvim": { "branch": "main", "commit": "155eac5d8609a2f110041f8ac3491664cc126354" }, + "harpoon": { "branch": "harpoon2", "commit": "87b1a3506211538f460786c23f98ec63ad9af4e5" }, + "lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" }, + "lazydev.nvim": { "branch": "main", "commit": "5231c62aa83c2f8dc8e7ba957aa77098cda1257d" }, + "lazygit.nvim": { "branch": "main", "commit": "2305deed25bc61b866d5d39189e9105a45cf1cfb" }, + "mason-lspconfig.nvim": { "branch": "main", "commit": "b1d9a914b02ba5660f1e272a03314b31d4576fe2" }, "mason-tool-installer.nvim": { "branch": "main", "commit": "517ef5994ef9d6b738322664d5fdd948f0fdeb46" }, - "mason.nvim": { "branch": "main", "commit": "ad7146aa61dcaeb54fa900144d768f040090bff0" }, - "mini.nvim": { "branch": "main", "commit": "61a5d44a8f31370710e6a998c5a8ee7e61c7e576" }, - "nvim-lspconfig": { "branch": "master", "commit": "e688b486fe9291f151eae7e5c0b5a5c4ef980847" }, + "mason.nvim": { "branch": "main", "commit": "57e5a8addb8c71fb063ee4acda466c7cf6ad2800" }, + "mini.icons": { "branch": "main", "commit": "ff2e4f1d29f659cc2bad0f9256f2f6195c6b2428" }, + "mini.nvim": { "branch": "main", "commit": "dce9bc4e19d02d5c37fe71c16f40f8a5536b0386" }, + "monokai-pro.nvim": { "branch": "master", "commit": "1ac671f6da720cba967d28d25c2f16b8b4e18808" }, + "nightfox.nvim": { "branch": "main", "commit": "ba47d4b4c5ec308718641ba7402c143836f35aa9" }, + "nvim-lspconfig": { "branch": "master", "commit": "95fe3c170753238d3ca4f760e79a991400677abc" }, "nvim-treesitter": { "branch": "master", "commit": "42fc28ba918343ebfd5565147a42a26580579482" }, + "oil.nvim": { "branch": "master", "commit": "7e1cd7703ff2924d7038476dcbc04b950203b902" }, + "onedark.nvim": { "branch": "master", "commit": "6c10964f91321c6a0f09bcc41dd64e7a6602bc4f" }, + "oxocarbon.nvim": { "branch": "main", "commit": "9f85f6090322f39b11ae04a343d4eb9d12a86897" }, "plenary.nvim": { "branch": "master", "commit": "b9fd5226c2f76c951fc8ed5923d85e4de065e509" }, - "telescope-fzf-native.nvim": { "branch": "main", "commit": "1f08ed60cafc8f6168b72b80be2b2ea149813e55" }, + "telescope-fzf-native.nvim": { "branch": "main", "commit": "6fea601bd2b694c6f2ae08a6c6fab14930c60e2c" }, "telescope-ui-select.nvim": { "branch": "master", "commit": "6e51d7da30bd139a6950adf2a47fda6df9fa06d2" }, - "telescope.nvim": { "branch": "master", "commit": "a0bbec21143c7bc5f8bb02e0005fa0b982edc026" }, - "todo-comments.nvim": { "branch": "main", "commit": "304a8d204ee787d2544d8bc23cd38d2f929e7cc5" }, - "tokyonight.nvim": { "branch": "main", "commit": "4d159616aee17796c2c94d2f5f87d2ee1a3f67c7" }, - "which-key.nvim": { "branch": "main", "commit": "904308e6885bbb7b60714c80ab3daf0c071c1492" } + "telescope.nvim": { "branch": "master", "commit": "3a12a853ebf21ec1cce9a92290e3013f8ae75f02" }, + "todo-comments.nvim": { "branch": "main", "commit": "31e3c38ce9b29781e4422fc0322eb0a21f4e8668" }, + "tokyonight.nvim": { "branch": "main", "commit": "5da1b76e64daf4c5d410f06bcb6b9cb640da7dfd" }, + "which-key.nvim": { "branch": "main", "commit": "3aab2147e74890957785941f0c1ad87d0a44c15a" } } diff --git a/lua/arnoldlei/remap.lua b/lua/arnoldlei/remap.lua index eccc412d..cfcac33d 100644 --- a/lua/arnoldlei/remap.lua +++ b/lua/arnoldlei/remap.lua @@ -7,10 +7,10 @@ vim.keymap.set('n', '|', 'vsplit', { desc = 'Split window vertically' } vim.keymap.set('n', '_', 'split', { desc = 'Split window horizontally' }) -- Window navigation -vim.keymap.set('n', '', 'h', { desc = 'Move to left window' }) -vim.keymap.set('n', '', 'j', { desc = 'Move to window below' }) -vim.keymap.set('n', '', 'k', { desc = 'Move to window above' }) -vim.keymap.set('n', '', 'l', { desc = 'Move to right window' }) +-- vim.keymap.set('n', '', 'h', { desc = 'Move to left window' }) +-- vim.keymap.set('n', '', 'j', { desc = 'Move to window below' }) +-- vim.keymap.set('n', '', 'k', { desc = 'Move to window above' }) +-- vim.keymap.set('n', '', 'l', { desc = 'Move to right window' }) vim.keymap.set('v', 'J', ":m '>+1gv=gv") vim.keymap.set('v', 'K', ":m '<-2gv=gv") @@ -50,10 +50,17 @@ vim.keymap.set('n', 'mr', 'CellularAutomaton make_it_rain') vim.keymap.set('n', 'sf', ':source $HOME/.config/nvim/init.lua ', { desc = 'Source Neovim config' }) -vim.keymap.set('n', '', function() - vim.cmd 'so' -end) - -- Jump to beginning/end of method vim.keymap.set('n', '[m', '[m', { desc = 'Jump to beginning of method' }) vim.keymap.set('n', ']m', ']m', { desc = 'Jump to end of method' }) + +-- Diagnostic navigation with float +vim.keymap.set('n', '[d', function() + vim.diagnostic.goto_prev() + vim.diagnostic.open_float() +end, { desc = 'Go to previous diagnostic' }) + +vim.keymap.set('n', ']d', function() + vim.diagnostic.goto_next() + vim.diagnostic.open_float() +end, { desc = 'Go to next diagnostic' }) diff --git a/lua/custom/plugins/TEST.md b/lua/custom/plugins/TEST.md new file mode 100644 index 00000000..478c4bce --- /dev/null +++ b/lua/custom/plugins/TEST.md @@ -0,0 +1 @@ +[a relative link](../../../README.md.md) diff --git a/lua/custom/plugins/harpoon.lua b/lua/custom/plugins/harpoon.lua index a08b2c02..160b490f 100644 --- a/lua/custom/plugins/harpoon.lua +++ b/lua/custom/plugins/harpoon.lua @@ -18,19 +18,19 @@ return { harpoon.ui:toggle_quick_menu(harpoon:list()) end, { desc = 'Harpoon: Toggle quick menu' }) - vim.keymap.set('n', 'h1', function() + vim.keymap.set('n', '', function() harpoon:list():select(1) end, { desc = 'Harpoon: Go to file 1' }) - vim.keymap.set('n', 'h2', function() + vim.keymap.set('n', '', function() harpoon:list():select(2) end, { desc = 'Harpoon: Go to file 2' }) - vim.keymap.set('n', 'h3', function() + vim.keymap.set('n', '', function() harpoon:list():select(3) end, { desc = 'Harpoon: Go to file 3' }) - vim.keymap.set('n', 'h4', function() + vim.keymap.set('n', '', function() harpoon:list():select(4) end, { desc = 'Harpoon: Go to file 4' }) diff --git a/lua/custom/plugins/lazygit.lua b/lua/custom/plugins/lazygit.lua new file mode 100644 index 00000000..86d80911 --- /dev/null +++ b/lua/custom/plugins/lazygit.lua @@ -0,0 +1,22 @@ +return { + 'kdheepak/lazygit.nvim', + lazy = false, + cmd = { + 'LazyGit', + 'LazyGitConfig', + 'LazyGitCurrentFile', + 'LazyGitFilter', + 'LazyGitFilterCurrentFile', + }, + -- optional for floating window border decoration + dependencies = { + 'nvim-telescope/telescope.nvim', + 'nvim-lua/plenary.nvim', + }, + config = function() + require('telescope').load_extension 'lazygit' + end, + keys = { + { 'gh', 'LazyGit', desc = 'LazyGit' }, + }, +} diff --git a/lua/custom/plugins/theme-switcher.lua b/lua/custom/plugins/theme-switcher.lua new file mode 100644 index 00000000..8ccbf0aa --- /dev/null +++ b/lua/custom/plugins/theme-switcher.lua @@ -0,0 +1,18 @@ +return { + dir = vim.fn.stdpath 'config' .. '/lua/theme-switcher', + name = 'theme-switcher', + lazy = false, + priority = 100, + config = function() + require('theme-switcher').setup { + save_on_select = true, + preview_on_navigate = true, + restore_on_startup = true, + } + + -- Keybindings (global, not buffer-specific) + vim.keymap.set('n', 'tt', function() + require('theme-switcher').open() + end, { desc = '[T]heme [T]oggle', noremap = true, silent = true }) + end, +} diff --git a/lua/custom/themes/theme.lua b/lua/custom/themes/theme.lua new file mode 100644 index 00000000..02ccb684 --- /dev/null +++ b/lua/custom/themes/theme.lua @@ -0,0 +1,117 @@ +-- Default theme to load on startup +local DEFAULT_THEME = 'nightfox' + +return { + { + 'projekt0n/github-nvim-theme', + name = 'github-theme', + lazy = false, -- make sure we load this during startup if it is your main colorscheme + priority = 1000, -- make sure to load this before all the other start plugins + config = function() + require('github-theme').setup { + -- ... + } + + vim.cmd 'colorscheme github_dark' + end, + }, + -- Nightfox theme + { + 'EdenEast/nightfox.nvim', + lazy = false, + priority = 1000, + config = function() + require('nightfox').setup { + options = { + compile_path = vim.fn.stdpath 'cache' .. '/nightfox', + compile_file_suffix = '_compiled', + transparent = false, + terminal_colors = true, + dim_inactive = false, + module_default = true, + colorblind = { + enable = false, + simulate_only = false, + severity = { + protan = 0, + deutan = 0, + tritan = 0, + }, + }, + styles = { + comments = 'NONE', + conditionals = 'NONE', + constants = 'NONE', + functions = 'NONE', + keywords = 'NONE', + numbers = 'NONE', + operators = 'NONE', + strings = 'NONE', + types = 'NONE', + variables = 'NONE', + }, + inverse = { + match_paren = false, + visual = false, + search = false, + }, + modules = {}, + }, + palettes = {}, + specs = {}, + groups = {}, + } + end, + }, + + -- Oxocarbon theme + { + 'nyoom-engineering/oxocarbon.nvim', + lazy = false, + priority = 1000, + }, + + -- Tokyo Night theme + { + 'folke/tokyonight.nvim', + lazy = false, + priority = 1000, + config = function() + ---@diagnostic disable-next-line: missing-fields + require('tokyonight').setup { + styles = { + comments = { italic = false }, + }, + } + + -- Set default colorscheme on startup + vim.cmd.colorscheme(DEFAULT_THEME) + end, + }, + { + 'catppuccin/nvim', + name = 'catppuccin', + lazy = false, + priority = 1000, + config = function() + require('catppuccin').setup { + flavour = 'mocha', -- latte, frappe, macchiato, mocha + } + end, + }, + { + 'loctvl842/monokai-pro.nvim', + lazy = false, + priority = 1000, + }, + { + 'navarasu/onedark.nvim', + lazy = false, + priority = 1000, + config = function() + require('onedark').setup { + style = 'dark', -- dark, darker, cool, deep, warm, warmer + } + end, + }, +} diff --git a/lua/theme-switcher/fuzzy.lua b/lua/theme-switcher/fuzzy.lua new file mode 100644 index 00000000..90f06e55 --- /dev/null +++ b/lua/theme-switcher/fuzzy.lua @@ -0,0 +1,101 @@ +local M = {} + +-- Simple fuzzy match algorithm +-- Returns score (higher is better) or nil if no match +function M.fuzzy_match(str, pattern) + if pattern == '' then + return 1000 -- Empty pattern matches everything with high score + end + + str = str:lower() + pattern = pattern:lower() + + local str_idx = 1 + local pattern_idx = 1 + local score = 0 + local consecutive = 0 + local last_match_idx = 0 + + while pattern_idx <= #pattern do + local pattern_char = pattern:sub(pattern_idx, pattern_idx) + local found = false + + -- Search for pattern character in remaining string + while str_idx <= #str do + local str_char = str:sub(str_idx, str_idx) + + if str_char == pattern_char then + found = true + + -- Score bonuses + score = score + 1 + + -- Bonus for consecutive matches + if str_idx == last_match_idx + 1 then + consecutive = consecutive + 1 + score = score + consecutive * 5 + else + consecutive = 0 + end + + -- Bonus for matching at start + if str_idx == 1 then + score = score + 10 + end + + -- Bonus for matching after separator + if str_idx > 1 then + local prev_char = str:sub(str_idx - 1, str_idx - 1) + if prev_char == '-' or prev_char == '_' or prev_char == ' ' then + score = score + 8 + end + end + + last_match_idx = str_idx + str_idx = str_idx + 1 + break + end + + str_idx = str_idx + 1 + end + + if not found then + return nil -- Pattern doesn't match + end + + pattern_idx = pattern_idx + 1 + end + + return score +end + +-- Filter and sort themes by fuzzy match +function M.filter_themes(themes, query) + if query == '' then + return themes + end + + local matches = {} + + for _, theme in ipairs(themes) do + local score = M.fuzzy_match(theme, query) + if score then + table.insert(matches, { theme = theme, score = score }) + end + end + + -- Sort by score (descending) + table.sort(matches, function(a, b) + return a.score > b.score + end) + + -- Extract just the theme names + local filtered = {} + for _, match in ipairs(matches) do + table.insert(filtered, match.theme) + end + + return filtered +end + +return M diff --git a/lua/theme-switcher/init.lua b/lua/theme-switcher/init.lua new file mode 100644 index 00000000..49cbafc8 --- /dev/null +++ b/lua/theme-switcher/init.lua @@ -0,0 +1,216 @@ +local M = {} + +-- Configuration +local config = { + save_on_select = true, + preview_on_navigate = true, + restore_on_startup = true, +} + +-- Store original theme for preview cancellation +local original_theme = nil +local preview_enabled = false +local all_themes = {} +local filtered_themes = {} +local query = '' + +-- Setup function +function M.setup(user_config) + config = vim.tbl_deep_extend('force', config, user_config or {}) + + -- Restore saved theme on startup + if config.restore_on_startup then + local persistence = require 'theme-switcher.persistence' + local themes = require 'theme-switcher.themes' + local saved_theme = persistence.load_theme() + if saved_theme then + themes.apply_theme(saved_theme) + end + end + + -- Create user commands + vim.api.nvim_create_user_command('ThemeSwitcher', function() + M.open() + end, {}) + + vim.api.nvim_create_user_command('ThemeSwitcherSave', function() + local themes = require 'theme-switcher.themes' + local persistence = require 'theme-switcher.persistence' + local current = themes.get_current_theme() + persistence.save_theme(current) + vim.notify('Saved theme: ' .. current, vim.log.levels.INFO) + end, {}) +end + +-- Open theme switcher +function M.open() + local themes = require 'theme-switcher.themes' + local ui = require 'theme-switcher.ui' + + all_themes = themes.get_available_themes() + filtered_themes = all_themes + query = '' + + local current_theme = themes.get_current_theme() + + -- Store original for preview cancellation + original_theme = current_theme + preview_enabled = config.preview_on_navigate + + local buf, win = ui.create_window(filtered_themes, current_theme) + + M.setup_keymaps(buf) +end + +function M.setup_keymaps(buf) + local opts = { buffer = buf, nowait = true, silent = true } + + -- Close and cancel + vim.keymap.set('n', 'q', function() + M.cancel() + end, opts) + + vim.keymap.set('n', '', function() + M.cancel() + end, opts) + + -- Select and apply + vim.keymap.set('n', '', function() + M.select() + end, opts) + + -- Navigation with preview + vim.keymap.set('n', 'j', function() + vim.cmd 'normal! j' + M.preview() + end, opts) + + vim.keymap.set('n', 'k', function() + vim.cmd 'normal! k' + M.preview() + end, opts) + + vim.keymap.set('n', '', function() + vim.cmd 'normal! j' + M.preview() + end, opts) + + vim.keymap.set('n', '', function() + vim.cmd 'normal! k' + M.preview() + end, opts) + + vim.keymap.set('n', '', function() + vim.cmd 'normal! j' + M.preview() + end, opts) + + vim.keymap.set('n', '', function() + vim.cmd 'normal! k' + M.preview() + end, opts) + + -- Text input - any printable character + for i = 32, 126 do + local char = string.char(i) + vim.keymap.set('n', char, function() + M.add_char(char) + end, opts) + end + + -- Backspace + vim.keymap.set('n', '', function() + M.remove_char() + end, opts) + + vim.keymap.set('n', '', function() + M.remove_char() + end, opts) + + -- Clear input + vim.keymap.set('n', '', function() + M.clear_input() + end, opts) + + -- Toggle preview + vim.keymap.set('n', '', function() + preview_enabled = not preview_enabled + vim.notify(preview_enabled and 'Preview enabled' or 'Preview disabled', vim.log.levels.INFO) + end, opts) +end + +function M.add_char(char) + query = query .. char + M.update_filter() +end + +function M.remove_char() + if #query > 0 then + query = query:sub(1, -2) + M.update_filter() + end +end + +function M.clear_input() + query = '' + M.update_filter() +end + +function M.update_filter() + local fuzzy = require 'theme-switcher.fuzzy' + local ui = require 'theme-switcher.ui' + local themes = require 'theme-switcher.themes' + + filtered_themes = fuzzy.filter_themes(all_themes, query) + ui.update_display(filtered_themes, themes.get_current_theme(), query) +end + +-- Preview theme while navigating +function M.preview() + if not preview_enabled then + return + end + + local ui = require 'theme-switcher.ui' + local themes = require 'theme-switcher.themes' + + local selected = ui.get_selected_theme(filtered_themes) + if selected then + themes.apply_theme(selected) + end +end + +-- Select and apply theme +function M.select() + local ui = require 'theme-switcher.ui' + local themes = require 'theme-switcher.themes' + local persistence = require 'theme-switcher.persistence' + + local selected = ui.get_selected_theme(filtered_themes) + + if selected then + themes.apply_theme(selected) + + if config.save_on_select then + persistence.save_theme(selected) + end + + vim.notify('Applied theme: ' .. selected, vim.log.levels.INFO) + end + + ui.close_window() +end + +-- Cancel and restore original theme +function M.cancel() + local ui = require 'theme-switcher.ui' + local themes = require 'theme-switcher.themes' + + if preview_enabled and original_theme then + themes.apply_theme(original_theme) + end + + ui.close_window() +end + +return M diff --git a/lua/theme-switcher/persistence.lua b/lua/theme-switcher/persistence.lua new file mode 100644 index 00000000..11301252 --- /dev/null +++ b/lua/theme-switcher/persistence.lua @@ -0,0 +1,33 @@ +local M = {} + +local config_dir = vim.fn.stdpath 'data' +local theme_file = config_dir .. '/theme_preference.txt' + +-- Save theme preference +function M.save_theme(theme_name) + local file = io.open(theme_file, 'w') + if file then + file:write(theme_name) + file:close() + return true + end + return false +end + +-- Load theme preference +function M.load_theme() + local file = io.open(theme_file, 'r') + if file then + local theme = file:read '*all' + file:close() + return theme:match '^%s*(.-)%s*$' -- trim whitespace + end + return nil +end + +-- Delete preference +function M.clear_theme() + os.remove(theme_file) +end + +return M diff --git a/lua/theme-switcher/themes.lua b/lua/theme-switcher/themes.lua new file mode 100644 index 00000000..e6716141 --- /dev/null +++ b/lua/theme-switcher/themes.lua @@ -0,0 +1,48 @@ +local M = {} + +-- Automatically detect installed colorschemes +function M.get_available_themes() + local themes = {} + + -- Get all available colorschemes from runtime paths + local colorscheme_files = vim.api.nvim_get_runtime_file('colors/*.vim', true) + local lua_colorschemes = vim.api.nvim_get_runtime_file('colors/*.lua', true) + + -- Combine both vim and lua colorschemes + vim.list_extend(colorscheme_files, lua_colorschemes) + + for _, file in ipairs(colorscheme_files) do + local name = vim.fn.fnamemodify(file, ':t:r') + table.insert(themes, name) + end + + -- Remove duplicates and sort + local seen = {} + local unique = {} + for _, theme in ipairs(themes) do + if not seen[theme] then + seen[theme] = true + table.insert(unique, theme) + end + end + + table.sort(unique) + return unique +end + +-- Get current colorscheme +function M.get_current_theme() + return vim.g.colors_name or 'default' +end + +-- Apply a theme +function M.apply_theme(theme_name) + local ok, err = pcall(vim.cmd.colorscheme, theme_name) + if not ok then + vim.notify('Failed to load theme: ' .. theme_name, vim.log.levels.ERROR) + return false + end + return true +end + +return M diff --git a/lua/theme-switcher/ui.lua b/lua/theme-switcher/ui.lua new file mode 100644 index 00000000..84ad96e8 --- /dev/null +++ b/lua/theme-switcher/ui.lua @@ -0,0 +1,124 @@ +local M = {} + +-- Store window and buffer IDs +local win_id = nil +local buf_id = nil +local input_ns = nil + +-- Create floating window with search input +function M.create_window(themes, current_theme) + -- Calculate window size + local width = 60 + local height = math.min(#themes + 4, 20) -- +4 for header and input + + -- Calculate position (center of screen) + local row = math.floor((vim.o.lines - height) / 2) + local col = math.floor((vim.o.columns - width) / 2) + + -- Create buffer + buf_id = vim.api.nvim_create_buf(false, true) -- No file, scratch buffer + + -- Set buffer options + vim.api.nvim_buf_set_option(buf_id, 'bufhidden', 'wipe') + vim.api.nvim_buf_set_option(buf_id, 'filetype', 'theme-switcher') + vim.api.nvim_buf_set_option(buf_id, 'modifiable', false) + + -- Window options + local opts = { + relative = 'editor', + width = width, + height = height, + row = row, + col = col, + style = 'minimal', + border = 'rounded', + title = ' Theme Switcher ', + title_pos = 'center', + } + + -- Create window + win_id = vim.api.nvim_open_win(buf_id, true, opts) + + -- Window-local options + vim.api.nvim_win_set_option(win_id, 'cursorline', true) + vim.api.nvim_win_set_option(win_id, 'number', false) + vim.api.nvim_win_set_option(win_id, 'relativenumber', false) + + input_ns = vim.api.nvim_create_namespace 'theme-switcher-input' + + M.update_display(themes, current_theme, '') + + return buf_id, win_id +end + +-- Update the display with filtered themes +function M.update_display(themes, current_theme, query) + if not buf_id or not vim.api.nvim_buf_is_valid(buf_id) then + return + end + + vim.api.nvim_buf_set_option(buf_id, 'modifiable', true) + + local lines = {} + + -- Input line + table.insert(lines, '> ' .. query) + table.insert(lines, string.rep('─', vim.api.nvim_win_get_width(win_id) - 2)) + + -- Theme list + local current_line = 3 + for i, theme in ipairs(themes) do + local prefix = theme == current_theme and '● ' or ' ' + table.insert(lines, prefix .. theme) + + if theme == current_theme and query == '' then + current_line = i + 2 + end + end + + -- Handle empty results + if #themes == 0 then + table.insert(lines, ' No matches found') + end + + vim.api.nvim_buf_set_lines(buf_id, 0, -1, false, lines) + vim.api.nvim_buf_set_option(buf_id, 'modifiable', false) + + -- Set cursor to first theme (after separator) + if #themes > 0 then + vim.api.nvim_win_set_cursor(win_id, { query == '' and current_line or 3, 0 }) + else + vim.api.nvim_win_set_cursor(win_id, { 3, 0 }) + end +end + +-- Close window +function M.close_window() + if win_id and vim.api.nvim_win_is_valid(win_id) then + vim.api.nvim_win_close(win_id, true) + end + win_id = nil + buf_id = nil + input_ns = nil +end + +-- Get selected theme from cursor position +function M.get_selected_theme(themes) + if not win_id then + return nil + end + + local cursor = vim.api.nvim_win_get_cursor(win_id) + local line_num = cursor[1] + + -- Subtract 2 for input line and separator + local theme_idx = line_num - 2 + + if theme_idx >= 1 and theme_idx <= #themes then + return themes[theme_idx] + end + + return nil +end + +return M