diff --git a/lua/dora/config/icon.lua b/lua/dora/config/icon.lua new file mode 100644 index 00000000..a6fed4b8 --- /dev/null +++ b/lua/dora/config/icon.lua @@ -0,0 +1,83 @@ +---@class dora.config.icon +local M = {} + +M.icons = { + ActiveLSP = "", + ActiveTS = "", + ArrowLeft = "", + ArrowRight = "", + Bookmarks = "", + BufferClose = "󰅖", + DapBreakpoint = "", + DapBreakpointCondition = "", + DapBreakpointRejected = "", + DapLogPoint = ".>", + DapStopped = "󰁕", + Debugger = "", + DefaultFile = "", + Diagnostic = "󰒡", + DiagnosticError = "", + DiagnosticHint = "󰌵", + DiagnosticInfo = "󰋼", + DiagnosticWarn = "", + Ellipsis = "…", + FileNew = "", + FileModified = "", + FileReadOnly = "", + FoldClosed = "", + FoldOpened = "", + FoldSeparator = " ", + FolderClosed = "", + FolderEmpty = "", + FolderOpen = "", + Git = "󰊢", + GitAdd = "", + GitBranch = "", + GitChange = "", + GitConflict = "", + GitDelete = "", + GitIgnored = "◌", + GitRenamed = "➜", + GitSign = "▎", + GitStaged = "✓", + GitUnstaged = "✗", + GitUntracked = "★", + LSPLoaded = "", + LSPLoading1 = "", + LSPLoading2 = "󰀚", + LSPLoading3 = "", + MacroRecording = "", + Package = "󰏖", + Paste = "󰅌", + Refresh = "", + Search = "", + Selected = "❯", + Session = "󱂬", + Sort = "󰒺", + Spellcheck = "󰓆", + Tab = "󰓩", + TabClose = "󰅙", + Terminal = "", + Window = "", + WordFile = "󰈭", + VimLogo = "", + Bookmark = "", +} + +---@param kind string +---@param padding? number +---@return string +function M.predefined_icon(kind, padding) + local icon = M.icons[kind] + if icon == nil then + return "" + end + return icon .. string.rep(" ", padding or 0) +end + +---@param opts table +function M.setup(opts) + M.icons = vim.tbl_extend("force", M.icons, opts) +end + +return M diff --git a/lua/dora/config/init.lua b/lua/dora/config/init.lua new file mode 100644 index 00000000..dc48a022 --- /dev/null +++ b/lua/dora/config/init.lua @@ -0,0 +1,38 @@ +---@class dora.config +local M = { + ---@type dora.config.lsp + lsp = require("dora.config.lsp"), + ---@type dora.config.nix + nix = require("dora.config.nix"), + ---@type dora.config.icon + icon = require("dora.config.icon"), + ---@type dora.config.package + package = require("dora.config.package"), + ---@type dora.config.ui + ui = require("dora.config.ui"), + ---@type dora.config.vim + vim = require("dora.config.vim"), + ---@type dora.config.integration + integration = require("dora.config.integration"), +} + +---@class dora.config.SetupOptions +---@field lsp? dora.config.lsp.SetupOptions +---@field nix? dora.config.nix.SetupOption +---@field icons? table +---@field ui? dora.config.ui.SetupOptions +---@field packages? string[] +---@field vim? dora.config.vim.SetupOption + +---@param opts dora.config.SetupOptions +function M.setup(opts) + M.lsp.setup(opts.lsp or {}) + M.nix.setup(opts.nix or {}) + M.icon.setup(opts.icons or {}) + M.ui.setup(opts.ui or {}) + M.vim.setup(opts.vim or {}) + + M.package.setup(opts.packages or {}) +end + +return M diff --git a/lua/dora/config/integration.lua b/lua/dora/config/integration.lua new file mode 100644 index 00000000..34648517 --- /dev/null +++ b/lua/dora/config/integration.lua @@ -0,0 +1,24 @@ +---@class dora.config.integration +local M = {} + +---@class dora.config.integration.SetupOptions +---@field enable_yazi? boolean +---@field enable_lazygit? boolean + +---@type dora.config.integration.SetupOptions +local default_options = {} + +M._ = default_options + +---@param opts dora.config.integration.SetupOptions +function M.setup(opts) + M._ = vim.tbl_deep_extend("force", default_options, opts or {}) +end + +---@param name string +---@return boolean +function M.enabled(name) + return not not M._["enable_" .. name] +end + +return M diff --git a/lua/dora/config/lsp.lua b/lua/dora/config/lsp.lua new file mode 100644 index 00000000..8b4046c3 --- /dev/null +++ b/lua/dora/config/lsp.lua @@ -0,0 +1,297 @@ +---@class dora.config.lsp +local M = {} + +---@type dora.config.lsp.Config +M.config = {} + +---@class dora.config.lsp.SetupOptions +---@field config? dora.config.lsp.Config +---@field methods? dora.config.lsp.methods + +---@param opts dora.config.lsp.SetupOptions +function M.setup(opts) + M.config = vim.tbl_deep_extend("force", M.config, opts.config or {}) --[[@as dora.config.lsp.Config]] + M.methods = vim.tbl_extend("force", M.methods, opts.methods or {}) --[[@as dora.config.lsp.methods]] +end + +---@alias dora.config.lsp.config.Backend "native" | "telescope" | "glance" | "lspsaga" + +---@class dora.config.lsp.config.BackendOptions +---@field ["*"] dora.config.lsp.config.Backend|nil +---@field definitions? dora.config.lsp.config.Backend +---@field type_definitions? dora.config.lsp.config.Backend +---@field implementations? dora.config.lsp.config.Backend +---@field references? dora.config.lsp.config.Backend +---@field code_action? dora.config.lsp.config.Backend + +---@class dora.config.lsp.Config +---@field backend? dora.config.lsp.config.BackendOptions +---@field server_opts? table +---@field capabilities? table +---@field setups? table + +---@type dora.config.lsp.Config +local Config = { + backend = { + ["*"] = "native", + definitions = "glance", + type_definitions = "glance", + implementations = "glance", + references = "glance", + code_action = "lspsaga", + }, + server_opts = {}, + setups = {}, +} +M.config = Config + +---@class dora.config.lsp.methods +local Methods = {} +M.methods = Methods + +function Methods.declaration() + if vim.g.vscode then + require("vscode-neovim").call("editor.action.revealDeclaration") + else + vim.lsp.buf.declaration() + end +end + +---@param method string +---@return dora.config.lsp.config.Backend +local function get_backend(method) + ---@type dora.lib + local lib = require("dora.lib") + return vim.F.if_nil( + lib.tbl.optional_field(M.config, "backend", method), + lib.tbl.optional_field(M.config, "backend", "*"), + "native" + ) +end + +function Methods.definitions() + local backend = get_backend("definitions") + if vim.g.vscode then + require("vscode-neovim").call("editor.action.revealDefinition") + elseif backend == "native" then + vim.lsp.buf.definition() + elseif backend == "telescope" then + require("telescope.builtin").lsp_definitions() + elseif backend == "glance" then + require("glance").open("definitions") + else + vim.notify( + "Unknown backend for definitions: " .. backend, + vim.log.levels.ERROR + ) + end +end + +function Methods.type_definitions() + local backend = get_backend("type_definitions") + if vim.g.vscode then + require("vscode-neovim").call("editor.action.goToTypeDefinition") + elseif backend == "native" then + vim.lsp.buf.type_definition() + elseif backend == "telescope" then + require("telescope.builtin").lsp_type_definitions() + elseif backend == "glance" then + require("glance").open("type_definitions") + else + vim.notify( + "Unknown backend for type definitions: " .. backend, + vim.log.levels.ERROR + ) + end +end + +function Methods.implementations() + local backend = get_backend("implementations") + if vim.g.vscode then + require("vscode-neovim").call("editor.action.goToImplementation") + elseif backend == "native" then + vim.lsp.buf.implementation() + elseif backend == "telescope" then + require("telescope.builtin").lsp_implementations() + elseif backend == "glance" then + require("glance").open("implementations") + else + vim.notify( + "Unknown backend for implementations: " .. backend, + vim.log.levels.ERROR + ) + end +end + +function Methods.references() + local backend = get_backend("references") + if vim.g.vscode then + require("vscode-neovim").call("references-view.findReferences") + elseif backend == "native" then + vim.lsp.buf.references() + elseif backend == "telescope" then + require("telescope.builtin").lsp_references() + elseif backend == "glance" then + require("glance").open("references") + else + vim.notify( + "Unknown backend for references: " .. backend, + vim.log.levels.ERROR + ) + end +end + +function Methods.code_action() + if vim.g.vscode then + require("vscode-neovim").call("editor.action.quickFix") + else + vim.cmd("Lspsaga code_action") + end +end + +function Methods.next_diagnostic() + if vim.g.vscode then + require("vscode-neovim").call("editor.action.marker.nextInFiles") + else + vim.diagnostic.goto_next { wrap = false } + end +end + +function Methods.prev_diagnostic() + if vim.g.vscode then + require("vscode-neovim").call("editor.action.marker.prevInFiles") + else + vim.diagnostic.goto_prev { wrap = false } + end +end + +local lsp_hover_group = + vim.api.nvim_create_augroup("dora_lsp_hover", { clear = true }) + +function Methods.show_hover() + if vim.g.vscode then + require("vscode-neovim").call("editor.action.showHover") + else + vim.o.eventignore = "CursorHold" + vim.api.nvim_exec_autocmds("User", { + pattern = "DoraShowHover", + }) + vim.lsp.buf.hover() + vim.api.nvim_create_autocmd({ "CursorMoved" }, { + group = lsp_hover_group, + buffer = 0, + command = 'set eventignore=""', + once = true, + }) + end +end + +local function close_preview_window(winnr, bufnrs) + vim.schedule(function() + -- exit if we are in one of ignored buffers + if bufnrs and vim.list_contains(bufnrs, vim.api.nvim_get_current_buf()) then + return + end + + local augroup = "preview_window_" .. winnr + pcall(vim.api.nvim_del_augroup_by_name, augroup) + pcall(vim.api.nvim_win_close, winnr, true) + end) +end + +local function close_preview_autocmd(events, winnr, bufnrs) + local augroup = vim.api.nvim_create_augroup("preview_window_" .. winnr, { + clear = true, + }) + + -- close the preview window when entered a buffer that is not + -- the floating window buffer or the buffer that spawned it + vim.api.nvim_create_autocmd("BufEnter", { + group = augroup, + callback = function() + close_preview_window(winnr, bufnrs) + end, + }) + + if #events > 0 then + local simple_events = {} + local events_with_pattern = {} + + for _, event in ipairs(events) do + -- split event with space + local parts = vim.split(event, " ", { + trimempty = false, + }) + if #parts == 1 then + table.insert(simple_events, event) + else + local pattern = table.concat({ unpack(parts, 2) }, " ") + table.insert(events_with_pattern, { + event = parts[1], + pattern = pattern, + }) + end + end + + vim.api.nvim_create_autocmd(simple_events, { + group = augroup, + buffer = bufnrs[2], + callback = function() + close_preview_window(winnr) + end, + }) + for _, event in ipairs(events_with_pattern) do + vim.api.nvim_create_autocmd(event.event, { + group = augroup, + buffer = bufnrs[2], + callback = function(ev) + if ev.match == event.pattern then + close_preview_window(winnr) + end + end, + }) + end + end +end + +function Methods.open_diagnostic() + local opts = { + focusable = false, + border = "solid", + source = "if_many", + prefix = " ", + focus = false, + scope = "cursor", + } + + local bufnr, win = vim.diagnostic.open_float(opts) + + if bufnr == nil then + return + end + + vim.api.nvim_set_option_value( + "winhl", + "FloatBorder:NormalFloat,Normal:NormalFloat", + { win = win } + ) + + close_preview_autocmd({ + "CursorMoved", + "InsertEnter", + "User DoraShowHover", + "BufLeave", + "FocusLost", + }, win, { bufnr, vim.api.nvim_get_current_buf() }) +end + +function Methods.rename(new_name, options) + options = options or {} + local filter = function(client) + return not vim.tbl_contains({ "null-ls", "copilot" }, client.name) + end + options.filter = options.filter or filter + vim.lsp.buf.rename(new_name, options) +end + +return M diff --git a/lua/dora/config/nix.lua b/lua/dora/config/nix.lua new file mode 100644 index 00000000..f8508fb5 --- /dev/null +++ b/lua/dora/config/nix.lua @@ -0,0 +1,37 @@ +---@class dora.config.nix +local M = {} + +---@type table +M.nixpkgs = {} -- : + +---@type table +M.bin = {} -- : + +---@param name string +---@return string? +function M.resolve_pkg(name) + return M.nixpkgs[name] +end + +---@param name string +---@return string +function M.resolve_bin(name) + local path = M.bin[name] + if path == nil then + return name + else + return path + end +end + +---@class dora.config.nix.SetupOption +---@field pkgs? table +---@field bin? table + +---@param opts dora.config.nix.SetupOption +function M.setup(opts) + M.nixpkgs = opts.pkgs or {} + M.bin = opts.pkgs or {} +end + +return M diff --git a/lua/dora/config/package.lua b/lua/dora/config/package.lua new file mode 100644 index 00000000..45785407 --- /dev/null +++ b/lua/dora/config/package.lua @@ -0,0 +1,102 @@ +---@class dora.config.package +local M = {} + +---@type table +M._ = {} + +local function load_package(name) + ---@type dora.core.package + local Package = require("dora.core.package") + + if M._[name] then + return + end + + ---@type dora.core.package.PackageOption + local module = require(name) + if module == nil then + vim.notify("Failed to load package module: " .. name, vim.log.levels.WARN) + return + end + + local pkg = Package.new_package(module) + M._[name] = pkg + + for _, dep in ipairs(pkg:deps()) do + load_package(dep) + end +end + +---@param pkgs string[] +function M.setup(pkgs) + for _, dep in ipairs(pkgs) do + load_package(dep) + end +end + +---@return dora.core.package.Package[] +function M.sorted_package() + ---@type table + local outgoing_edges = {} + ---@type table + local incoming_counts = {} + + local num_of_packages = 0 + + for _, pkg in pairs(M._) do + for _, dep in ipairs(pkg:deps()) do + if outgoing_edges[dep] == nil then + outgoing_edges[dep] = {} + end + table.insert(outgoing_edges[dep], pkg:name()) + incoming_counts[pkg:name()] = (incoming_counts[pkg:name()] or 0) + 1 + end + num_of_packages = num_of_packages + 1 + end + for name, _ in pairs(M._) do + if incoming_counts[name] == nil then + incoming_counts[name] = 0 + end + end + + local queue = {} + for name, count in pairs(incoming_counts) do + if count == 0 then + table.insert(queue, name) + end + end + + if #queue == 0 then + vim.print(outgoing_edges) + error("No packages as first") + end + + ---@type string[] + local sorted = {} + + while #queue > 0 do + local name = table.remove(queue, 1) + table.insert(sorted, name) + + if outgoing_edges[name] then + for _, outgoing in ipairs(outgoing_edges[name]) do + incoming_counts[outgoing] = incoming_counts[outgoing] - 1 + if incoming_counts[outgoing] == 0 then + table.insert(queue, outgoing) + end + end + end + end + + if #sorted ~= num_of_packages then + error("Not all packages are sorted") + end + + local res = {} + for _, name in ipairs(sorted) do + res[#res + 1] = M._[name] + end + return res +end + +return M diff --git a/lua/dora/config/ui.lua b/lua/dora/config/ui.lua new file mode 100644 index 00000000..a1aab90e --- /dev/null +++ b/lua/dora/config/ui.lua @@ -0,0 +1,38 @@ +---@class dora.config.ui +local M = {} + +---@alias dora.config.ui.DashboardHeader string[] +M.dashboard_header = { + [[ $$$$F**+ .oW$$$eu]], + [[ ..ueeeWeeo.. e$$$$$$$$$]], + [[ .eW$$$$$$$$$$$$$$$b- d$$$$$$$$$$W]], + [[ ,,,,,,,uee$$$$$$$$$$$$$$$$$$$$$ H$$$$$$$$$$$~]], + [[ :eoC$$$$$$$$$$$C""?$$$$$$$$$$$$$$$ T$$$$$$$$$$"]], + [[ $$$*$$$$$$$$$$$$$e "$$$$$$$$$$$$$$i$$$$$$$$F"]], + [[ ?f"!?$$$$$$$$$$$$$$ud$$$$$$$$$$$$$$$$$$$$*Co]], + [[ $ o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$]], + [[ !!!!m.*eeeW$$$$$$$$$$$f?$$$$$$$$$$$$$$$$$$$$$$$$$$$$$U]], + [[ !!!!!! !$$$$$$$$$$$$$$ T$$$$$$$$$$$$$$$$$$$$$$$$$$$$$]], + [[ *!!*.o$$$$$$$$$$$$$$$e,d$$$$$$$$$$$$$$$$$$$$$$$$$$$$$:]], + [[ "eee$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$C]], + [[b ?$$$$$$$$$$$$$$**$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$!]], + [[Tb "$$$$$$$$$$$$$$*uL"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$']], + [[ $$o."?$$$$$$$$F" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$]], + [[ $$$$en ``` .e$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$']], + [[ $$$B* =*"?.e$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$F]], + [[ $$$W"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$"]], + [[ "$$$o#$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$"]], + [[ ?$$$W$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$"]], +} + +---@class dora.config.ui.SetupOptions +---@field dashboard_header? dora.config.ui.DashboardHeader + +---@param opts dora.config.ui.SetupOptions +function M.setup(opts) + if opts.dashboard_header then + M.dashboard_header = opts.dashboard_header + end +end + +return M diff --git a/lua/dora/config/vim.lua b/lua/dora/config/vim.lua new file mode 100644 index 00000000..aca3575f --- /dev/null +++ b/lua/dora/config/vim.lua @@ -0,0 +1,123 @@ +---@class dora.config.vim +local M = {} + +---@class dora.config.vim.SetupOption +---@field relative_number_blacklist? string[] +---@field cursorline_blacklist? string[] +---@field quick_close_filetypes? string[] + +---@type dora.config.vim.SetupOption +local SetupOption = { + ---@type string[] + relative_number_blacklist = { + "startify", + "NvimTree", + "packer", + "alpha", + "nuipopup", + "toggleterm", + "noice", + "crates.nvim", + "lazy", + "Trouble", + "rightclickpopup", + "TelescopePrompt", + "Glance", + "DressingInput", + "lspinfo", + "nofile", + "mason", + "Outline", + "aerial", + "flutterToolsOutline", + "neo-tree", + "neo-tree-popup", + "", + }, + ---@type string[] + cursorline_blacklist = { + "alpha", + "noice", + "mason", + }, + ---@type string[] + quick_close_filetypes = { + "qf", + "help", + "man", + "notify", + "nofile", + "lspinfo", + "terminal", + "prompt", + "toggleterm", + "copilot", + "startuptime", + "tsplayground", + "PlenaryTestPopup", + "aerial", + "Outline", + "ClangdAST", + }, + ---@type string[] + uncountable_filetypes = { + "quickfix", + "defx", + "CHADTree", + "NvimTree", + "noice", + "fidget", + "scrollview", + "notify", + "Trouble", + "sagacodeaction", + "rightclickpopup", + "DiffviewFiles", + "neo-tree", + "neo-tree-popup", + "NvimSeparator", + }, +} +M.config = SetupOption + +---@param opts dora.config.vim.SetupOption +function M.setup(opts) + M.config = vim.tbl_deep_extend("force", M.config, opts) +end + +function M.is_uncountable(win_id) + local buf_id = vim.api.nvim_win_get_buf(win_id) + local ft = vim.api.nvim_get_option_value("ft", { + buf = buf_id, + }) + local bt = vim.api.nvim_get_option_value("buftype", { + buf = buf_id, + }) + + local lookup_table = + vim.tbl_add_reverse_lookup(vim.deepcopy(M.config.uncountable_filetypes)) + + return (lookup_table[ft] ~= nil and lookup_table[ft]) + or (lookup_table[bt] ~= nil and lookup_table[bt]) +end + +function M.check_last_window() + local n = vim.api.nvim_call_function("winnr", { "$" }) + local total = n + for i = 1, n do + local win_id = vim.api.nvim_call_function("win_getid", { i }) + if M.is_uncountable(win_id) then + total = total - 1 + end + end + + if total == 0 then + if vim.api.nvim_call_function("tabpagenr", { "$" }) == 1 then + vim.api.nvim_command("quitall!") + else + vim.api.nvim_command("tabclose") + end + end +end + +return M diff --git a/lua/dora/core/action.lua b/lua/dora/core/action.lua new file mode 100644 index 00000000..95a14708 --- /dev/null +++ b/lua/dora/core/action.lua @@ -0,0 +1,106 @@ +---@class dora.core.action +local M = {} + +---@alias dora.core.action.Condition fun(buf:dora.lib.vim.BufferWrapper):boolean + +---@class dora.core.action.Action: dora.core.action.ActionOption +local Action = {} + +---@class dora.core.action.KeySpec: LazyKeysBase +---@field mode? string|string[] + +---@class dora.core.action.ActionOption +---@field id string Unique id +---@field title string +---@field callback string|function +---@field icon? string +---@field category? string +---@field description? string Shown description if action hovered +---@field keys? string|(string|dora.core.action.KeySpec)[] +---@field plugin? string +---@field condition? dora.core.action.Condition + +---@class dora.core.action._MakeActionOptionsArgs +---@field from? string +---@field category? string +---@field condition? dora.core.action.Condition +---@field actions? dora.core.action.ActionOption[] + +---@param opts dora.core.action._MakeActionOptionsArgs +---@return dora.core.action.ActionOption[] +function M.make_options(opts) + local common_opts = { + from = opts.from, + category = opts.category, + condition = opts.condition, + } + local res = {} + for _, action in ipairs(opts.actions) do + res[#res + 1] = vim.tbl_extend("keep", action, common_opts) + end + return res +end + +function Action:execute() + if type(self.callback) == "string" then + vim.cmd(self.callback) + else + self.callback() + end +end + +---@return LazyKeysSpec[] +function Action:into_lazy_keys() + if not self.keys then + return {} + end + local callback = function() + self:execute() + end + if type(self.keys) == "string" then + return { + { + self.keys, + callback, + }, + } + end + local keys = self.keys + if not vim.tbl_isarray(keys) then + keys = { keys } + end + + local res = {} + for _, key in ipairs(keys) do + if type(key) == "string" then + res[#res + 1] = { + key, + callback, + } + else + local k = vim.deepcopy(key) + k[2] = callback + res[#res + 1] = k + end + end + + return res +end + +---@param opts dora.core.action.ActionOption +---@return dora.core.action.Action +function M.new_action(opts) + return setmetatable(opts, { __index = Action }) --[[@as dora.core.action.Action]] +end + +---Returns this action can be used in the given buffer +---@param buffer dora.lib.vim.BufferWrapper +---@return boolean +function Action:enabled(buffer) + if self.condition then + return self.condition(buffer) + end + return true +end + +return M diff --git a/lua/dora/core/init.lua b/lua/dora/core/init.lua new file mode 100644 index 00000000..899a7c41 --- /dev/null +++ b/lua/dora/core/init.lua @@ -0,0 +1,7 @@ +---@class dora.core +local M = {} + +---@type dora.core.action +M.action = require("dora.core.action") + +return M diff --git a/lua/dora/core/package.lua b/lua/dora/core/package.lua new file mode 100644 index 00000000..443f6f87 --- /dev/null +++ b/lua/dora/core/package.lua @@ -0,0 +1,41 @@ +---@class dora.core.package +local M = {} + +---@class dora.core.package.PackageOption +---@field name string +---@field deps? string[] +---@field setup? fun():any +---@field plugins? dora.core.plugin.PluginOption[] + +---@class dora.core.package.Package +---@field private _ dora.core.package.PackageOption +local Package = {} + +function Package:name() + return self._.name +end + +---@return string[] +function Package:deps() + return self._.deps or {} +end + +---@return dora.core.plugin.PluginOption[] +function Package:plugins() + return self._.plugins or {} +end + +function Package:setup() + if self._.setup then + self._.setup() + end +end + +---@param opts dora.core.package.PackageOption +---@return dora.core.package.Package +function M.new_package(opts) + local p = { _ = opts } + return setmetatable(p, { __index = Package }) +end + +return M diff --git a/lua/dora/core/plugin.lua b/lua/dora/core/plugin.lua new file mode 100644 index 00000000..7afafcbc --- /dev/null +++ b/lua/dora/core/plugin.lua @@ -0,0 +1,153 @@ +---@type dora.lib +local lib = require("dora.lib") +---@type dora.core.action +local action_module = require("dora.core.action") + +---@class dora.core.plugin +local M = {} + +---@class dora.core.plugin.ExtraPluginOptions +---@field nixpkg? string Whether the plugin will be loaded from nix +---@field gui? "all"|string[] Can be used in which gui environment +---@field actions? dora.core.action.ActionOption[]|fun():dora.core.action.ActionOption[] +---@field keys? LazyKeysSpec[] +---@field after? string|string[] The plugin spec must be after these plugins' specs in 'lazy.nvim' + +---@class dora.core.plugin.PluginOption: dora.core.plugin.ExtraPluginOptions,LazyPluginSpec + +---@class dora.core.plugin.Plugin +---@field options dora.core.plugin.PluginOption +---@field actions dora.core.action.Action[] +local Plugin = {} + +---@param option dora.core.plugin.PluginOption +---@return dora.core.plugin.Plugin +function M.new_plugin(option) + local actions = {} + if option.actions ~= nil then + ---@type dora.core.action.ActionOption[] + local values + if type(option.actions) == "function" then + values = option.actions() + else + values = option.actions --[[@as dora.core.action.ActionOption[] ]] + end + for _, action in ipairs(values) do + actions[#actions + 1] = action_module.new_action(action) + end + end + return setmetatable({ + options = option, + actions = actions, + }, { __index = Plugin }) +end + +---@return string +function Plugin:name() + if self.options.name ~= nil then + return self.options.name + end + return self.options[1] +end + +---@return string[] +function Plugin:alias() + local res = { + self.options[1], + } + if self.options.name ~= nil then + res[#res + 1] = self.options.name + end + -- $USER/$REPO, REPO can be used + local repo = (self.options[1]):match(".*/(.*)") + if repo ~= nil then + res[#res + 1] = repo + end + return res +end + +---Converts a plugin into a lazy plugin +---@return LazyPluginSpec? opts options for `lazy.nvim`, nil to skip this +function Plugin:into_lazy_spec() + local gui_cond = function() + local current_gui = lib.vim.current_gui() + if current_gui == nil then + return true + end + local gui = self.options.gui + if gui == nil then + return false + end + if type(gui) == "string" then + if current_gui == gui or gui == "all" then + return true + end + elseif vim.list_contains(gui, current_gui) then + return true + end + return false + end + + -- only export the fields that are in the base class + ---@type LazyPluginSpec + local lazy = + lib.tbl.filter_out_keys(self.options, { "nixpkg", "gui", "actions" }) + + if lazy.cond == nil then + lazy.cond = gui_cond + elseif type(lazy.cond) == "function" then + local old_cond = lazy.cond --[[@as fun():boolean ]] + lazy.cond = function() + return (not not old_cond()) and gui_cond() + end + elseif type(lazy.cond) == "boolean" then + if lazy.cond == true then + lazy.cond = gui_cond + end + end + + local enabled = vim.F.if_nil(lazy.enabled, true) + if type(enabled) == "function" then + enabled = not not enabled() + end + if type(lazy.cond) == "function" then + enabled = enabled and lazy.cond() + else + enabled = enabled and not not lazy.cond + end + + ---@type dora.config + local config = require("dora.config") + + -- if `nixpkg` is set, load this plugin from nix + if self.options.nixpkg ~= nil then + local nixpkg = self.options.nixpkg + local path = config.nix.resolve_pkg(nixpkg --[[ @as string ]]) + if path ~= nil then + lazy.dir = path + lazy.build = nil -- skip build if it's from nix + end + end + + if self.actions ~= nil and enabled then + local all_keys + if self.options.keys == nil then + all_keys = {} + elseif vim.tbl_isarray(self.options.keys) then + all_keys = vim.deepcopy(self.options.keys) + else + all_keys = { vim.deepcopy(self.options.keys) } + end + for _, action in ipairs(self.actions) do + local keys = action:into_lazy_keys() + for _, key in ipairs(keys) do + all_keys[#all_keys + 1] = key + end + end + lazy.keys = all_keys + end + + return lazy +end + +return M diff --git a/lua/dora/core/registry.lua b/lua/dora/core/registry.lua new file mode 100644 index 00000000..6873f640 --- /dev/null +++ b/lua/dora/core/registry.lua @@ -0,0 +1,40 @@ +---@class dora.core.registry +local M = {} + +---@type table +M.plugins = {} +---@type table +M.actions = {} +---@type table +M.alias_to_fullname = {} + +---@param plugin dora.core.plugin.Plugin +function M.register_plugin(plugin) + if M.plugins[plugin:name()] == nil then + M.plugins[plugin:name()] = {} + end + table.insert(M.plugins[plugin:name()], plugin) + for _, alias in ipairs(plugin:alias()) do + M.alias_to_fullname[alias] = plugin:name() + end + for _, action in ipairs(plugin.actions) do + if M.actions[action.id] ~= nil then + error("Action " .. action.id .. " already exists") + end + M.actions[action.id] = action + end +end + +---@param name string +---@return dora.core.plugin.Plugin[] +function M.get_plugin(name) + return M.plugins[name] +end + +---@param name string +---@return boolean +function M.has(name) + return M.alias_to_fullname[name] ~= nil +end + +return M diff --git a/lua/dora/init.lua b/lua/dora/init.lua new file mode 100644 index 00000000..e133f7a9 --- /dev/null +++ b/lua/dora/init.lua @@ -0,0 +1,79 @@ +local function bootstrap_lazy_nvim() + local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" + if not vim.uv.fs_stat(lazypath) then + vim.fn.system { + "git", + "clone", + "--filter=blob:none", + "https://github.com/folke/lazy.nvim.git", + "--branch=stable", -- latest stable release + lazypath, + } + end + vim.opt.rtp:prepend(lazypath) +end + +---@class dora +local M = {} + +---@param opts? dora.config.SetupOptions +function M.setup(opts) + ---@type dora.config + local config = require("dora.config") + + ---@type dora.core.registry + local registry = require("dora.core.registry") + ---@type dora.core.plugin + local plugin = require("dora.core.plugin") + + -- set mapleader at very beginning of profile + vim.api.nvim_set_var("mapleader", " ") + + config.setup(opts or {}) + + local specs = {} + local packages = config.package.sorted_package() + for _, pkg in ipairs(packages) do + for _, plug_opts in ipairs(pkg:plugins()) do + local plug = plugin.new_plugin(plug_opts) + registry.register_plugin(plug) + specs[#specs + 1] = plug:into_lazy_spec() + end + end + + bootstrap_lazy_nvim() + + require("lazy").setup { + spec = specs, + change_detection = { enabled = false }, + install = { + missing = true, + }, + performance = { + cache = { enabled = true }, + install = { colorscheme = { "tokyonight", "habamax" } }, + rtp = { + paths = { + "~/Projects/dora.nvim", + }, + disabled_plugins = { + "gzip", + "matchit", + "matchparen", + "netrwPlugin", + "tarPlugin", + "tohtml", + "tutor", + "zipPlugin", + "spellfile", + }, + }, + }, + } + + for _, pkg in ipairs(packages) do + pkg:setup() + end +end + +return M diff --git a/lua/dora/lib/fs.lua b/lua/dora/lib/fs.lua new file mode 100644 index 00000000..bbfab5bb --- /dev/null +++ b/lua/dora/lib/fs.lua @@ -0,0 +1,36 @@ +---@class dora.lib.fs +local M = {} + +---Returns the path of the current file's git repo. +---@return string? +function M.git_repo_path() + local Path = require("plenary.path") + local current_file = vim.api.nvim_buf_get_name(0) + local current_dir + + if current_file == "" then + current_dir = Path.new(vim.fn.getcwd()) + else + local current = Path.new(current_file) + current_dir = current:parent() + end + + local p = vim.system({ + "git", + "rev-parse", + "--show-toplevel", + }, { + cwd = tostring(current_dir), + }) + + local res = p:wait() + if res.code ~= 0 then + return nil + end + + local stdout = res.stdout + + return stdout:match("^()%s*$") and "" or stdout:match("^%s*(.*%S)") +end + +return M diff --git a/lua/dora/lib/func.lua b/lua/dora/lib/func.lua new file mode 100644 index 00000000..e1c036f9 --- /dev/null +++ b/lua/dora/lib/func.lua @@ -0,0 +1,122 @@ +---@class dora.lib.func +local M = {} + +---@class dora.lib.CacheManager +---@field private entries table +local CacheManager = {} + +CacheManager.new = function() + local self = setmetatable({}, { __index = CacheManager }) + self.entries = {} + return self +end + +---Get cache value +---@param key string|string[] +---@return any|nil +function CacheManager:get(key) + key = self:key(key) + if self.entries[key] ~= nil then + return self.entries[key] + end + return nil +end + +---Set cache value explicitly +---@param key string|string[] +---@vararg any +function CacheManager:set(key, value) + key = self:key(key) + self.entries[key] = value +end + +---Ensure value by callback +---@generic T +---@param key string|string[] +---@param callback fun(): T +---@return T +function CacheManager:ensure(key, callback) + local value = self:get(key) + if value == nil then + local v = callback() + self:set(key, v) + return v + end + return value +end + +---Clear all cache entries +function CacheManager:clear() + self.entries = {} +end + +---Create key +---@param key string|string[] +---@return string +function CacheManager:key(key) + if type(key) == "table" then + local res = "" + for _, v in ipairs(key) do + if type(v) == "string" then + res = res .. v + else + res = res .. tostring(v) + end + end + return res + end + return key +end + +---@return dora.lib.CacheManager +function M.new_cache_manager() + return CacheManager.new() +end + +---@generic T +---@param fun fun():T +---@return T +function M.call_once(fun) + local res + local called = false + return function() + if not called then + res = fun() + called = true + end + return res + end +end + +---@generic T +---@param name string +---@param callback fun(module): T +---@return T? +function M.require_then(name, callback) + local has_module, module = pcall(require, name) + if has_module then + return callback(module) + end +end + +---@param callback string|fun(): any +---@param feedkeys? boolean +---@return fun(): any +function M.normalize_callback(callback, feedkeys) + if type(callback) == "string" then + if feedkeys == true then + return function() + local key = vim.api.nvim_replace_termcodes(callback .. "", true, false, true) + vim.api.nvim_feedkeys(key, "t", false) + end + else + return function() + vim.api.nvim_command(callback) + end + end + else + return callback + end +end + +return M diff --git a/lua/dora/lib/init.lua b/lua/dora/lib/init.lua new file mode 100644 index 00000000..4663ed54 --- /dev/null +++ b/lua/dora/lib/init.lua @@ -0,0 +1,19 @@ +---@class dora.lib +local M = {} + +---@type dora.lib.func +M.func = require("dora.lib.func") + +---@type dora.lib.vim +M.vim = require("dora.lib.vim") + +---@type dora.lib.tbl +M.tbl = require("dora.lib.tbl") + +---@type dora.lib.fs +M.fs = require("dora.lib.fs") + +---@type dora.lib.lazy +M.lazy = require("dora.lib.lazy") + +return M diff --git a/lua/dora/lib/lazy.lua b/lua/dora/lib/lazy.lua new file mode 100644 index 00000000..302445a8 --- /dev/null +++ b/lua/dora/lib/lazy.lua @@ -0,0 +1,19 @@ +---@class dora.lib.lazy +local M = {} + +---@param plugin string +function M.has(plugin) + return require("lazy.core.config").spec.plugins[plugin] ~= nil +end + +---@param name string +function M.opts(name) + local plugin = require("lazy.core.config").plugins[name] + if not plugin then + return {} + end + local Plugin = require("lazy.core.plugin") + return Plugin.values(plugin, "opts", false) +end + +return M diff --git a/lua/dora/lib/tbl.lua b/lua/dora/lib/tbl.lua new file mode 100644 index 00000000..85592bb0 --- /dev/null +++ b/lua/dora/lib/tbl.lua @@ -0,0 +1,60 @@ +---@class dora.lib.tbl +local M = {} + +---@param t table +---@param ... string +function M.optional_field(t, ...) + local keys = { ... } + local now = t + for _, key in ipairs(keys) do + if now[key] == nil then + return nil + end + now = now[key] + end + return now +end + +---Reverse given list-like table in place. +---NOTE: this mutates the given table. +---@generic T +---@param lst T[] +function M.list_reverse(lst) + for i = 1, math.floor(#lst / 2) do + local j = #lst - i + 1 + lst[i], lst[j] = lst[j], lst[i] + end +end + +---@param t table +---@return table +function M.filter_out_keys(t, keys) + local res = {} + for k, v in pairs(t) do + if not vim.tbl_contains(keys, k) then + res[k] = v + end + end + return res +end + +---@param tbl any +---@return any[] +function M.flatten_array(tbl) + if type(tbl) ~= "table" then + return { tbl } + end + + if vim.tbl_isarray(tbl) then + local res = {} + for _, value in ipairs(tbl) do + local inner_value = M.flatten_array(value) + res = vim.list_extend(res, inner_value) + end + return res + else + return { tbl } + end +end + +return M diff --git a/lua/dora/lib/vim.lua b/lua/dora/lib/vim.lua new file mode 100644 index 00000000..6d7adf65 --- /dev/null +++ b/lua/dora/lib/vim.lua @@ -0,0 +1,96 @@ +---@class dora.lib.vim +local M = {} + +---@class dora.lib.vim.BufferWrapper Buffer relative properties +---@field bufnr number +---@field filetype string +---@field full_file_name string +---@field lsp_servers lsp.Client[] +---@field ts_highlighter vim.treesitter.highlighter[] +local Buffer = {} + +---@return string +function Buffer:as_cache_key() + local arr = { + self.filetype, + self.full_file_name, + #self.lsp_servers, + #self.ts_highlighter, + } + for _, v in ipairs(self.lsp_servers) do + arr[#arr + 1] = v.name + end + return vim.json.encode(arr) +end + +---Returns the number of language servers attached to the buffer except +---`null-ls`, `copilot` and `none-ls`. +---@return number +function Buffer:lang_server_count() + local count = 0 + for _, v in ipairs(self.lsp_servers) do + if v.name ~= "null-ls" and v.name ~= "copilot" and v.name ~= "none-ls" then + count = count + 1 + end + end + return count +end + +---@param bufnr number +---@return dora.lib.vim.BufferWrapper +function M.wrap_buffer(bufnr) + return setmetatable({ + bufnr = bufnr, + filetype = vim.api.nvim_get_option_value("filetype", { buf = bufnr }), + full_file_name = vim.api.nvim_buf_get_name(bufnr), + lsp_servers = vim.lsp.get_clients { + bufnr = bufnr, + }, + ts_highlighter = { vim.treesitter.highlighter.active[bufnr] }, + }, { + __index = Buffer, + }) +end + +---@return string? +function M.current_gui() + if vim.g.vscode then + return "vscode" + elseif vim.g.neovide then + return "neovide" + else + return nil + end +end + +---@param cmd string +---@return fun():any +function M.input_then_exec(cmd) + return function() + vim.ui.input({ + prompt = "Arguments, " .. cmd, + }, function(input) + if input then + if input.length > 0 then + vim.api.nvim_command(cmd .. input) + else + vim.api.nvim_command(cmd) + end + end + end) + end +end + +---@param callback fun(client?: lsp.Client, buffer: number) +function M.on_lsp_attach(callback) + vim.api.nvim_create_autocmd("LspAttach", { + callback = function(args) + local buffer = args.buf ---@type number + ---@type lsp.Client? + local client = vim.lsp.get_client_by_id(args.data.client_id) + callback(client, buffer) + end, + }) +end + +return M diff --git a/lua/dora/packages/_builtin/init.lua b/lua/dora/packages/_builtin/init.lua new file mode 100644 index 00000000..5a58ce14 --- /dev/null +++ b/lua/dora/packages/_builtin/init.lua @@ -0,0 +1,152 @@ +---@type dora.lib +local lib = require("dora.lib") + +---@type dora.core.package.PackageOption +return { + name = "dora.packages._builtin", + plugins = lib.tbl.flatten_array { + require("dora.packages._builtin.plugins._"), + { "folke/lazy.nvim", lazy = true }, + { + "catppuccin/nvim", + name = "catppuccin", + lazy = true, + build = ":CatppuccinCompile", + opts = { + flavour = "frappe", + term_colors = true, + transparent_background = false, + compile_path = vim.fn.stdpath("cache") .. "/catppuccin", + dim_inactive = { enabled = false }, + styles = { + comments = { "italic" }, + conditionals = { "italic" }, + properties = {}, + functions = {}, + keywords = {}, + operators = {}, + loops = {}, + booleans = {}, + numbers = {}, + types = {}, + strings = {}, + variables = {}, + }, + custom_highlights = function(C) + return { + ["@lsp.typemod.variable.mutable.rust"] = { style = { "underline" } }, + ["@lsp.typemod.selfKeyword.mutable.rust"] = { + style = { "underline" }, + }, + ["@variable.builtin"] = { fg = C.maroon, style = { "italic" } }, + NormalFloat = { fg = C.text, bg = C.none }, + NoicePopup = { fg = C.text, bg = C.none }, + NormalNC = { fg = C.text, bg = C.none }, + NoiceGuiDocPopupBorder = { fg = C.base, bg = C.none }, + } + end, + integrations = { + alpha = true, + aerial = true, + barbecue = { + dim_dirname = true, + bold_basename = true, + }, + treesitter = true, + headlines = true, + flash = true, + gitsigns = true, + notify = true, + native_lsp = { + enabled = true, + virtual_text = { + errors = { "italic" }, + hints = { "italic" }, + warnings = { "italic" }, + information = { "italic" }, + }, + underlines = { + errors = { "undercurl" }, + hints = { "undercurl" }, + warnings = { "undercurl" }, + information = { "undercurl" }, + }, + }, + semantic_tokens = true, + lsp_trouble = true, + markdown = true, + noice = true, + telescope = { + enabled = true, + style = "nvchad", + }, + illuminate = true, + nvimtree = false, + mason = true, + neotree = true, + mini = true, + which_key = true, + hop = true, + cmp = true, + lsp_saga = true, + octo = true, + navic = { enabled = true }, + window_picker = true, + }, + }, + actions = { + { + id = "catppuccin.compile", + callback = "CatppuccinCompile", + description = "Recompile Catppuccin", + from = "catppuccin.nvim", + category = "Catppuccin", + }, + }, + }, + { + "dstein64/vim-startuptime", + cmd = "StartupTime", + config = function() + vim.g.startuptime_tries = 10 + end, + actions = { + { + id = "vim-startuptime.show", + title = "Show Vim's startup time", + callback = "StartupTime", + }, + }, + }, + { + "willothy/flatten.nvim", + lazy = false, + priority = 1001, + opts = { + window = { + open = "alternate", + }, + }, + }, + { + "MunifTanjim/nui.nvim", + lazy = true, + }, + { + "nvim-lua/popup.nvim", + lazy = true, + }, + { + "nvim-lua/plenary.nvim", + lazy = true, + }, + { + "nvim-tree/nvim-web-devicons", + lazy = true, + }, + }, + setup = function() + require("dora.packages._builtin.setup.options")() + require("dora.packages._builtin.setup.keymaps")() + end, +} diff --git a/lua/dora/packages/_builtin/plugins/_.lua b/lua/dora/packages/_builtin/plugins/_.lua new file mode 100644 index 00000000..33bea240 --- /dev/null +++ b/lua/dora/packages/_builtin/plugins/_.lua @@ -0,0 +1,132 @@ +local _yazi +local _lazygit + +---@type dora.core.plugin.PluginOption[] +return { + { + "tpope/vim-repeat", + event = "VeryLazy", + }, + { + "osyo-manga/vim-jplus", + event = "BufReadPost", + keys = { { "J", "(jplus)", mode = { "n", "v" }, noremap = false } }, + gui = "all", + }, + { + "akinsho/toggleterm.nvim", + version = "*", + init = function() -- code to run before plugin loaded + vim.g.toggleterm_terminal_mapping = "" + end, + config = function(_, opts) + require("toggleterm").setup(opts) + end, + opts = { + open_mapping = "", + hide_numbers = true, + direction = "float", + start_in_insert = true, + shell = vim.o.shell, + close_on_exit = true, + float_opts = { border = "rounded" }, + }, + actions = function() + ---@type dora.core.action + local action = require("dora.core.action") + + ---@type dora.config + local config = require("dora.config") + + ---@type dora.core.action.ActionOption[] + local actions = { + { + id = "toggleterm.toggle", + title = "Toggle terminal", + callback = "ToggleTerm", + keys = { "", desc = "toggle-terminal" }, + }, + } + + if config.integration.enabled("yazi") then + actions[#actions + 1] = { + id = "toggleterm.yazi", + title = "Open Yazi", + callback = function() + local executable = config.nix.resolve_bin("yazi") + if vim.fn.executable(executable) ~= 1 then + vim.notify( + "Command [yazi] not found!", + vim.log.levels.ERROR, + { title = "toggleterm.nvim" } + ) + return + end + local root = vim.fn.getcwd() + if not _yazi then + _yazi = require("toggleterm.terminal").Terminal:new { + cmd = "yazi", + dir = root, + direction = "float", + close_on_exit = true, + start_in_insert = true, + hidden = true, + } + else + _yazi:change_dir(root) + end + _yazi:toggle() + end, + keys = { "ff", desc = "toggle-yazi" }, + } + end + + if config.integration.enabled("lazygit") then + actions[#actions + 1] = { + id = "toggleterm.lazygit", + title = "Open lazygit", + callback = function() + local executable = config.nix.resolve_bin("lazygit") + if vim.fn.executable(executable) ~= 1 then + vim.notify( + "Command [lazygit] not found!", + vim.log.levels.ERROR, + { title = "toggleterm.nvim" } + ) + return + end + local repo_path = require("dora.lib").fs.git_repo_path() + if repo_path == nil then + vim.notify( + "Not in a git repo!", + vim.log.levels.ERROR, + { title = "toggleterm.nvim" } + ) + return + end + if not _lazygit then + _lazygit = require("toggleterm.terminal").Terminal:new { + cmd = "lazygit", + dir = repo_path, + direction = "float", + close_on_exit = true, + start_in_insert = true, + hidden = true, + } + else + _lazygit:change_dir(repo_path) + end + _lazygit:toggle() + end, + keys = { "g", desc = "toggle-lazygit" }, + } + end + + return action.make_options { + from = "toggleterm.nvim", + category = "ToggleTerm", + actions = actions, + } + end, + }, +} diff --git a/lua/dora/packages/_builtin/setup/autocmds.lua b/lua/dora/packages/_builtin/setup/autocmds.lua new file mode 100644 index 00000000..2aae9aac --- /dev/null +++ b/lua/dora/packages/_builtin/setup/autocmds.lua @@ -0,0 +1,147 @@ +local function mkdir() + local dir = vim.fn.expand(":p:h") + if vim.fn.isdirectory(dir) == 0 then + vim.fn.mkdir(dir, "p") + end +end + +return function() + ---@type dora.config + local config = require("dora.config") + + vim.api.nvim_create_autocmd("TermEnter", { + pattern = "*", + callback = function() + local winnr = vim.api.nvim_get_current_win() + vim.api.nvim_set_option_value("nu", false, { + scope = "local", + win = winnr, + }) + vim.api.nvim_set_option_value("rnu", false, { + scope = "local", + win = winnr, + }) + end, + }) + + vim.api.nvim_create_autocmd({ "BufEnter", "FocusGained", "WinEnter" }, { + pattern = "*", + callback = function(event) + local lookup_table = vim.tbl_add_reverse_lookup( + vim.deepcopy(config.vim.config.cursorline_blacklist or {}) + ) + + local ft = vim.api.nvim_get_option_value("ft", { buf = event.buf }) + local bt = vim.api.nvim_get_option_value("bt", { buf = event.buf }) + if lookup_table[ft] == nil and bt ~= "nofile" then + local winnr = vim.api.nvim_get_current_win() + vim.api.nvim_set_option_value("nu", true, { + win = winnr, + }) + vim.api.nvim_set_option_value("rnu", true, { + win = winnr, + }) + vim.api.nvim_set_option_value("signcolumn", "yes", { + win = winnr, + }) + end + end, + }) + + vim.api.nvim_create_autocmd({ "BufLeave", "FocusLost", "WinLeave" }, { + pattern = "*", + callback = function(event) + local lookup_table = vim.tbl_add_reverse_lookup( + vim.deepcopy(config.vim.config.cursorline_blacklist or {}) + ) + + local ft = vim.api.nvim_get_option_value("ft", { buf = event.buf }) + local bt = vim.api.nvim_get_option_value("bt", { buf = event.buf }) + if lookup_table[ft] == nil and bt ~= "nofile" then + local winnr = vim.api.nvim_get_current_win() + vim.api.nvim_set_option_value("nu", true, { + win = winnr, + }) + vim.api.nvim_set_option_value("rnu", false, { + win = winnr, + }) + end + end, + }) + + -- quick close buffers with + vim.api.nvim_create_autocmd("FileType", { + pattern = config.vim.config.quick_close_filetypes, + callback = function(event) + vim.bo[event.buf].buflisted = false + vim.api.nvim_buf_set_keymap( + event.buf, + "n", + "q", + "close", + { silent = true } + ) + end, + }) + + vim.api.nvim_create_autocmd({ "InsertLeave", "WinEnter" }, { + pattern = "*", + callback = function(event) + local ft = vim.api.nvim_get_option_value("ft", { buf = event.buf }) + local lookup_table = vim.tbl_add_reverse_lookup( + vim.deepcopy(config.vim.config.cursorline_blacklist or {}) + ) + if lookup_table[ft] == nil then + local winnr = vim.api.nvim_get_current_win() + vim.api.nvim_set_option_value("cursorline", true, { + win = winnr, + }) + end + end, + }) + + vim.api.nvim_create_autocmd({ "InsertEnter", "WinLeave" }, { + pattern = "*", + callback = function() + local winnr = vim.api.nvim_get_current_win() + vim.api.nvim_set_option_value("cursorline", false, { + win = winnr, + }) + end, + }) + + vim.api.nvim_create_autocmd({ "VimResized" }, { + callback = function() + vim.cmd("wincmd =") + vim.cmd("tabdo wincmd =") + end, + }) + + -- move quickfix windows to botright automatically + vim.api.nvim_create_autocmd( + "FileType", + { pattern = "qf", command = "wincmd J" } + ) + + -- highlight on yank + vim.api.nvim_create_autocmd("TextYankPost", { + callback = function() + vim.highlight.on_yank() + end, + }) + + vim.api.nvim_create_autocmd({ "BufEnter", "WinClosed", "WinEnter" }, { + pattern = "*", + callback = function() + -- close when all windows are uncountable + config.vim.check_last_window() + end, + }) + + vim.api.nvim_create_autocmd("BufWritePre", { + pattern = "*", + callback = function() + mkdir() + end, + }) +end diff --git a/lua/dora/packages/_builtin/setup/keymaps.lua b/lua/dora/packages/_builtin/setup/keymaps.lua new file mode 100644 index 00000000..d2dc68ec --- /dev/null +++ b/lua/dora/packages/_builtin/setup/keymaps.lua @@ -0,0 +1,100 @@ +return function() + ---@type dora.config + local config = require("dora.config") + + -- Disable arrows + vim.api.nvim_set_keymap("", "", "", {}) + vim.api.nvim_set_keymap("", "", "", {}) + vim.api.nvim_set_keymap("", "", "", {}) + vim.api.nvim_set_keymap("", "", "", {}) + + vim.keymap.set("n", "wv", "wincmd v", { + desc = "split-window-vertical", + }) + + vim.keymap.set("n", "w-", "wincmd s", { + desc = "split-window-horizontal", + }) + + vim.keymap.set("n", "w=", "wincmd =", { + desc = "balance-window", + }) + + vim.keymap.set("n", "wr", "wincmd r", { + desc = "rotate-window-rightwards", + }) + + vim.keymap.set("n", "wx", "wincmd x", { + desc = "exchange-window-with-next", + }) + + local function goto_countable_window(i) + local function skip_uncountable_windows(cnt) + local rest = cnt + + for _, win_id in pairs(vim.api.nvim_tabpage_list_wins(0)) do + if + not config.vim.is_uncountable(win_id) + and vim.api.nvim_win_is_valid(win_id) + then + rest = rest - 1 + if rest == 0 then + return win_id + end + end + end + + return 0 + end + + vim.schedule(function() + local win_id = skip_uncountable_windows(i) + if win_id > 0 then + vim.api.nvim_set_current_win(win_id) + end + end) + end + + for i = 1, 9 do + vim.keymap.set("n", "" .. i, function() + goto_countable_window(i) + end, { desc = "goto-win-" .. i }) + end + + vim.keymap.set("n", "fs", "update", { desc = "update" }) + + vim.keymap.set("n", "", "nohl", { desc = "nohl" }) + + vim.keymap.set("n", "q", "q", { desc = "quit" }) + vim.keymap.set( + "n", + "Q", + "confirm qall", + { desc = "quit-all" } + ) + + -- NMAP("", function() + -- require("telescope").extensions.command_palette.command_palette { + -- layout_strategy = "center", + -- sorting_strategy = "ascending", + -- layout_config = { + -- anchor = "N", + -- width = 0.5, + -- prompt_position = "top", + -- height = 0.5, + -- }, + -- border = true, + -- results_title = false, + -- -- borderchars = { " ", " ", " ", " ", " ", " ", " ", " " }, + -- borderchars = { + -- prompt = { "─", "│", " ", "│", "╭", "╮", "│", "│" }, + -- results = { "─", "│", "─", "│", "├", "┤", "╯", "╰" }, + -- preview = { "─", "│", "─", "│", "╭", "╮", "╯", "╰" }, + -- }, + -- } + -- end, "open-command-palette") + + -- NMAP(";;", function() + -- require("htts").onRightClick() + -- end) +end diff --git a/lua/dora/packages/_builtin/setup/options.lua b/lua/dora/packages/_builtin/setup/options.lua new file mode 100644 index 00000000..9d9ffc2a --- /dev/null +++ b/lua/dora/packages/_builtin/setup/options.lua @@ -0,0 +1,81 @@ +return function() + vim.opt.title = false + vim.opt.ttyfast = true + + vim.opt.termguicolors = true + + vim.o.updatetime = 100 + vim.o.redrawtime = 1500 + vim.o.timeout = true + vim.o.ttimeout = true + vim.o.timeoutlen = 500 + vim.o.ttimeoutlen = 10 + + vim.o.foldcolumn = "1" + vim.o.foldlevel = 99 + vim.o.foldlevelstart = 99 + vim.o.foldenable = true + + vim.o.sessionoptions = + "blank,buffers,curdir,folds,help,tabpages,winsize,winpos,terminal,localoptions" + + -- no bells + vim.cmd([[set noerrorbells novisualbell t_vb=]]) + + -- numbers + vim.opt.number = true + vim.opt.relativenumber = true + vim.opt.signcolumn = "yes" + vim.opt.cursorline = true + + -- cursor settings + vim.opt.guicursor = + "n-v-c-sm:block,i-ci-ve:ver25,r-cr-o:hor20,n-v-c:blinkon500-blinkoff500,a:Cursor/lCursor" + vim.opt.termsync = true + + -- mouse mode + vim.opt.mouse = "a" + vim.opt.mousemoveevent = true + + -- statusline + vim.opt.laststatus = 3 + + -- edit settings + vim.opt.expandtab = true + vim.opt.smartindent = true + vim.opt.autoindent = true + + -- default tab width: 2 + vim.opt.tabstop = 2 + vim.opt.shiftwidth = 2 + + vim.opt.showmode = false + vim.opt.cmdheight = 1 + + vim.opt.exrc = true + + vim.opt.wrap = true + + vim.opt.fillchars = "eob: " + + vim.opt.scrolloff = 5 + + vim.opt.timeoutlen = 300 + + vim.opt.hidden = true + + -- jump options + vim.opt.jumpoptions = "stack" + + if vim.fn.has("nvim-0.10") == 1 then + vim.opt.smoothscroll = true + end + + vim.opt.virtualedit = "block" + + -- allow misspellings + vim.cmd.cnoreabbrev("qw", "wq") + vim.cmd.cnoreabbrev("W", "w") + vim.cmd.cnoreabbrev("Wq", "wq") + vim.cmd.cnoreabbrev("WQ", "wq") +end diff --git a/lua/dora/packages/coding/init.lua b/lua/dora/packages/coding/init.lua new file mode 100644 index 00000000..2bd51159 --- /dev/null +++ b/lua/dora/packages/coding/init.lua @@ -0,0 +1,18 @@ +---@type dora.lib +local lib = require("dora.lib") + +---@type dora.core.package.PackageOption +return { + name = "dora.packages.coding", + plugins = lib.tbl.flatten_array { + require("dora.packages.coding.plugins.nvim-cmp"), + require("dora.packages.coding.plugins.luasnip"), + require("dora.packages.coding.plugins.conform"), + require("dora.packages.coding.plugins.ultimate-autopair"), + require("dora.packages.coding.plugins.nvim-surround"), + require("dora.packages.coding.plugins.nvim-ts-context-commentstring"), + require("dora.packages.coding.plugins.comment"), + require("dora.packages.coding.plugins.neogen"), + require("dora.packages.coding.plugins.nvim-lint"), + }, +} diff --git a/lua/dora/packages/coding/plugins/comment.lua b/lua/dora/packages/coding/plugins/comment.lua new file mode 100644 index 00000000..65176bd0 --- /dev/null +++ b/lua/dora/packages/coding/plugins/comment.lua @@ -0,0 +1,21 @@ +---@type dora.core.plugin.PluginOption +return { + "numToStr/Comment.nvim", + gui = "all", + dependencies = { "JoosepAlviste/nvim-ts-context-commentstring" }, + keys = { + { "gcc", desc = "toggle-line-comment" }, + { "gcc", mode = "x", desc = "toggle-line-comment" }, + { "gbc", desc = "toggle-block-comment" }, + { "gbc", mode = "x", desc = "toggle-block-comment" }, + }, + opts = { + toggler = { line = "gcc", block = "gbc" }, + opleader = { line = "gcc", block = "gbc" }, + mappings = { basic = true, extra = false }, + pre_hook = function() + require("ts_context_commentstring.integrations.comment_nvim").create_pre_hook() + end, + }, + config = true, +} diff --git a/lua/dora/packages/coding/plugins/conform.lua b/lua/dora/packages/coding/plugins/conform.lua new file mode 100644 index 00000000..eaf6cd7e --- /dev/null +++ b/lua/dora/packages/coding/plugins/conform.lua @@ -0,0 +1,30 @@ +---@type dora.core.plugin.PluginOption +return { + "stevearc/conform.nvim", + gui = "all", + opts = { + format = { + timeout_ms = 3000, + async = false, + quiet = false, + }, + formatters = {}, + formatters_by_ft = {}, + }, + cmd = { "Conform" }, + event = { "BufReadPost" }, + keys = { + { + "fc", + function() + local conform = require("conform") + conform.format { + async = true, + lsp_fallback = "always", + } + end, + desc = "format-file", + mode = { "n", "v" }, + }, + }, +} diff --git a/lua/dora/packages/coding/plugins/luasnip.lua b/lua/dora/packages/coding/plugins/luasnip.lua new file mode 100644 index 00000000..485a9a6c --- /dev/null +++ b/lua/dora/packages/coding/plugins/luasnip.lua @@ -0,0 +1,91 @@ +---@type dora.core.plugin.PluginOption[] +return { + { + "TwIStOy/luasnip-snippets", + lazy = true, + dependencies = "nvim-treesitter", + gui = "all", + opts = { + user = { + name = "Hawtian Wang", + }, + }, + }, + { + "L3MON4D3/LuaSnip", + gui = "all", + build = "make install_jsregex", + dependencies = { + { + "nvim-cmp", + dependencies = { + "saadparwaiz1/cmp_luasnip", + }, + opts = function(_, opts) + opts.snippet = { + expand = function(args) + local indent_nodes = true + if + vim.api.nvim_get_option_value("filetype", { buf = 0 }) == "dart" + then + indent_nodes = false + end + local ls = require("luasnip") + ls.lsp_expand(args.body, { + indent = indent_nodes, + }) + end, + } + opts.sources[#opts.sources + 1] = { + name = "luasnip", + group_index = 2, + } + end, + }, + "luasnip-snippets", + }, + opts = { + enable_autosnippets = true, + history = false, + updateevents = "TextChanged,TextChangedI", + region_check_events = { + "CursorMoved", + "CursorMovedI", + "CursorHold", + }, + store_select_keys = "", + }, + keys = { + { + "", + function() + local ls = require("luasnip") + if ls.choice_active() then + ls.change_choice(1) + end + end, + mode = { "i", "s", "n", "v" }, + }, + { + "", + function() + local ls = require("luasnip") + if ls.jumpable(-1) then + ls.jump(-1) + end + end, + mode = { "i", "s", "n", "v" }, + }, + { + "", + function() + local ls = require("luasnip") + if ls.expand_or_jumpable() then + ls.expand_or_jump() + end + end, + mode = { "i", "s", "n", "v" }, + }, + }, + }, +} diff --git a/lua/dora/packages/coding/plugins/neogen.lua b/lua/dora/packages/coding/plugins/neogen.lua new file mode 100644 index 00000000..abbe9c88 --- /dev/null +++ b/lua/dora/packages/coding/plugins/neogen.lua @@ -0,0 +1,29 @@ +---@type dora.core.plugin.PluginOption[] +return { + { + "danymat/neogen", + dependencies = { + "nvim-treesitter/nvim-treesitter", + }, + cmd = { "Neogen" }, + gui = "all", + opts = { + input_after_comment = false, + snippet_engine = "luasnip", + }, + actions = { + { + id = "neogen.generate-annotation", + title = "Generate annotation on this", + icon = "📝", + callback = function() + require("neogen").generate() + end, + condition = function(buf) + return #buf.ts_highlighter > 0 + end, + category = "Neogen", + }, + }, + }, +} diff --git a/lua/dora/packages/coding/plugins/nvim-cmp.lua b/lua/dora/packages/coding/plugins/nvim-cmp.lua new file mode 100644 index 00000000..5920001b --- /dev/null +++ b/lua/dora/packages/coding/plugins/nvim-cmp.lua @@ -0,0 +1,94 @@ +---@type dora.core.plugin.PluginOption +return { + "hrsh7th/nvim-cmp", + event = "InsertEnter", + dependencies = { + "hrsh7th/cmp-nvim-lsp", + "hrsh7th/cmp-buffer", + "hrsh7th/cmp-path", + "hrsh7th/cmp-calc", + }, + opts = function() + local cmp = require("cmp") + + return { + matching = { + disallow_fuzzy_matching = false, + disallow_fullfuzzy_matching = true, + disallow_partial_fuzzy_matching = false, + disallow_partial_matching = false, + }, + preselect = cmp.PreselectMode.None, + experimental = { ghost_text = false }, + window = { + completion = { + border = "none", + winhighlight = "Normal:Normal,FloatBorder:FloatBorder", + focusable = false, + col_offset = 0, + }, + documentation = { + border = "single", + winhighlight = "FloatBorder:FloatBorder", + focusable = false, + }, + }, + sources = { + { name = "nvim_lsp", group_index = 1, max_item_count = 100 }, + { name = "async_path", group_index = 4 }, + { + name = "buffer", + group_index = 5, + option = { + get_bufnrs = function() + local buf = vim.api.nvim_get_current_buf() + local byte_size = vim.api.nvim_buf_get_offset( + buf, + vim.api.nvim_buf_line_count(buf) + ) + if byte_size > 1024 * 1024 then -- 1 Megabyte max + return {} + end + return { buf } + end, + }, + max_item_count = 10, + }, + }, + completion = { + completeopt = "menu,menuone,noselect,noinsert,preview", + }, + mapping = { + -- cmp.mapping(i_cr_action, { "i", "c" }), + [""] = cmp.mapping.confirm { select = true }, + [""] = cmp.mapping.select_prev_item {}, + [""] = cmp.mapping.select_next_item {}, + [""] = cmp.mapping.select_prev_item {}, + [""] = cmp.mapping.select_next_item {}, + [""] = cmp.mapping(cmp.mapping.scroll_docs(-4), { "i" }), + [""] = cmp.mapping(cmp.mapping.scroll_docs(4), { "i" }), + }, + enabled = function() + local bt = vim.api.nvim_get_option_value("buftype", { + buf = 0, + }) + return bt ~= "prompt" + end, + sorting = { + priority_weight = 2, + comparators = { + cmp.config.compare.offset, + cmp.config.compare.exact, + cmp.config.compare.score, + -- require("clangd_extensions.cmp_scores"), + cmp.config.compare.locality, + cmp.config.compare.recently_used, + cmp.config.compare.kind, + cmp.config.compare.sort_text, + cmp.config.compare.length, + cmp.config.compare.order, + }, + }, + } + end, +} diff --git a/lua/dora/packages/coding/plugins/nvim-lint.lua b/lua/dora/packages/coding/plugins/nvim-lint.lua new file mode 100644 index 00000000..af93bfed --- /dev/null +++ b/lua/dora/packages/coding/plugins/nvim-lint.lua @@ -0,0 +1,17 @@ +---@type dora.core.plugin.PluginOption +return { + "mfussenegger/nvim-lint", + event = { "BufReadPost", "BufNewFile" }, + opts = { + linters_by_ft = {}, + }, + config = function(_, opts) + require("lint").linters_by_ft = opts.linters_by_ft + + vim.api.nvim_create_autocmd("BufWritePost", { + callback = function() + require("lint").try_lint() + end, + }) + end, +} diff --git a/lua/dora/packages/coding/plugins/nvim-surround.lua b/lua/dora/packages/coding/plugins/nvim-surround.lua new file mode 100644 index 00000000..696cb413 --- /dev/null +++ b/lua/dora/packages/coding/plugins/nvim-surround.lua @@ -0,0 +1,8 @@ +---@type dora.core.plugin.PluginOption +return { + "kylechui/nvim-surround", + gui = "all", + version = "*", + event = "BufReadPost", + opts = {}, +} diff --git a/lua/dora/packages/coding/plugins/nvim-ts-context-commentstring.lua b/lua/dora/packages/coding/plugins/nvim-ts-context-commentstring.lua new file mode 100644 index 00000000..fefc1dcc --- /dev/null +++ b/lua/dora/packages/coding/plugins/nvim-ts-context-commentstring.lua @@ -0,0 +1,21 @@ +---@type dora.core.plugin.PluginOption +return { + "JoosepAlviste/nvim-ts-context-commentstring", + gui = "all", + event = { + "BufReadPost", + "BufNewFile", + }, + dependencies = { + "nvim-treesitter/nvim-treesitter", + }, + opts = { + context_commentstring = { + enable = true, + }, + }, + config = function(_, opts) + vim.g.skip_ts_context_commentstring_module = true + require("ts_context_commentstring").setup(opts) + end, +} diff --git a/lua/dora/packages/coding/plugins/ultimate-autopair.lua b/lua/dora/packages/coding/plugins/ultimate-autopair.lua new file mode 100644 index 00000000..4e3dadd5 --- /dev/null +++ b/lua/dora/packages/coding/plugins/ultimate-autopair.lua @@ -0,0 +1,44 @@ +---@type dora.core.plugin.PluginOption +return { + "altermo/ultimate-autopair.nvim", + gui = "all", + event = { + "InsertEnter", + "CmdlineEnter", + }, + opts = { + close = { + map = "", + cmap = "", + }, + tabout = { + hopout = true, + }, + fastwarp = { + enable = true, + map = "", + rmap = "", + cmap = "", + crmap = "", + enable_normal = true, + enable_reverse = true, + hopout = false, + multiline = true, + nocursormove = true, + no_nothing_if_fail = false, + }, + config_internal_pairs = { + { + "'", + "'", + suround = true, + cond = function(fn) + return not fn.in_lisp() or fn.in_string() + end, + alpha = true, + nft = { "tex", "rust" }, + multiline = false, + }, + }, + }, +} diff --git a/lua/dora/packages/editor/init.lua b/lua/dora/packages/editor/init.lua new file mode 100644 index 00000000..d2f1abb8 --- /dev/null +++ b/lua/dora/packages/editor/init.lua @@ -0,0 +1,16 @@ +---@type dora.lib +local lib = require("dora.lib") + +---@type dora.core.package.PackageOption +return { + name = "dora.packages.editor", + plugins = lib.tbl.flatten_array { + require("dora.packages.editor.plugins.neo-tree"), + require("dora.packages.editor.plugins.telescope"), + require("dora.packages.editor.plugins.vim-illuminate"), + require("dora.packages.editor.plugins.which-key"), + require("dora.packages.editor.plugins.bookmarks"), + require("dora.packages.editor.plugins.dial"), + require("dora.packages.editor.plugins._others"), + }, +} diff --git a/lua/dora/packages/editor/plugins/_others.lua b/lua/dora/packages/editor/plugins/_others.lua new file mode 100644 index 00000000..4652e669 --- /dev/null +++ b/lua/dora/packages/editor/plugins/_others.lua @@ -0,0 +1,325 @@ +---@type dora.core.plugin.PluginOption[] +return { + { + "m4xshen/smartcolumn.nvim", + event = { "BufReadPre", "BufNewFile" }, + opts = { + colorcolumn = "80", + disabled_filetypes = { + "help", + "NvimTree", + "lazy", + "mason", + "help", + "alpha", + "bookmarks_input", + "noice", + }, + }, + }, + { + "kazhala/close-buffers.nvim", + cmd = { + "BDelete", + "BWipeout", + }, + opts = { + filetype_ignore = { + "dashboard", + "NvimTree", + "TelescopePrompt", + "terminal", + "toggleterm", + "packer", + "fzf", + }, + preserve_window_layout = { "this" }, + next_buffer_cmd = function(windows) + require("bufferline").cycle(1) + local bufnr = vim.api.nvim_get_current_buf() + for _, window in ipairs(windows) do + vim.api.nvim_win_set_buf(window, bufnr) + end + end, + }, + actions = function() + ---@type dora.core.action + local action = require("dora.core.action") + + local function redraw_all() + vim.api.nvim_command("redrawstatus!") + vim.api.nvim_command("redraw!") + end + + return action.make_options { + from = "close-buffers", + category = "CloseBuffer", + actions = { + { + id = "close-buffer.delete-all-hidden-buffers", + title = "Delete all hidden buffers", + callback = function() + require("close_buffers").delete { type = "hidden", force = true } + redraw_all() + end, + keys = { "ch", desc = "delete-hidden-buffers" }, + }, + { + id = "close-buffer.delete-all-buffers-without-name", + title = "Delete all buffers without name", + callback = function() + require("close_buffers").delete { type = "nameless" } + redraw_all() + end, + }, + { + id = "close-buffer.delete-current-buffer", + title = "Delete current buffer", + callback = function() + require("close_buffers").delete { type = "this" } + redraw_all() + end, + }, + { + id = "close-buffer.delete-all-buffers-matching-the-regex", + title = "Delete all buffers matching the regex", + callback = function() + vim.ui.input({ prompt = "Regex" }, function(input) + if input ~= nil and #input > 0 then + require("close_buffers").delete { regex = input } + end + end) + redraw_all() + end, + }, + }, + } + end, + }, + { + "folke/trouble.nvim", + dependencies = { "folke/lsp-colors.nvim", "telescope.nvim" }, + cmd = { "Trouble", "TroubleClose", "TroubleToggle", "TroubleRefresh" }, + opts = { use_diagnostic_signs = true }, + actions = function() + ---@type dora.core.action + local action = require("dora.core.action") + + local lsp_actions = action.make_options { + from = "trouble.nvim", + category = "Trouble", + condition = function(buffer) + return #buffer.lsp_servers > 0 + end, + actions = { + { + id = "trouble.open-workspace-diagnostic", + title = "Open workspace diagnostic", + callback = "Trouble workspace_diagnostic", + keys = { "xw", desc = "lsp-workspace-diagnostic" }, + }, + { + id = "trouble.open-document-diagnostic", + title = "Open document diagnostic", + callback = "Trouble document_diagnostic", + keys = { "xd", desc = "lsp-document-diagnostic" }, + }, + }, + } + + local common_actions = action.make_options { + from = "trouble.nvim", + category = "Trouble", + actions = { + { + id = "trouble.toggle-window", + title = "Toggle trouble window", + callback = "TroubleToggle", + keys = { "xx", desc = "trouble-toggle" }, + }, + }, + } + + local actions_in_trouble_window = action.make_options { + from = "trouble.nvim", + category = "Trouble", + condition = function() + return require("trouble").is_open() + end, + actions = { + { + id = "trouble.previous-trouble", + title = "Previous trouble", + callback = function() + require("trouble").previous { skip_groups = true, jump = true } + end, + keys = { "[q", desc = "previous-trouble" }, + }, + { + id = "trouble.next-trouble", + title = "Next trouble", + callback = function() + require("trouble").next { skip_groups = true, jump = true } + end, + keys = { "]q", desc = "next-trouble" }, + }, + }, + } + + return { + unpack(lsp_actions), + unpack(common_actions), + unpack(actions_in_trouble_window), + } + end, + }, + { + "folke/todo-comments.nvim", + event = "BufReadPost", + cmd = { "TodoTrouble", "TodoTelescope" }, + dependencies = { "nvim-lua/plenary.nvim", "telescope.nvim", "trouble.nvim" }, + opts = { + highlight = { keyword = "wide_bg", pattern = "(KEYWORDS)\\([^)]*\\):" }, + search = { pattern = "(KEYWORDS)\\([^)]*\\):" }, + keywords = { + HACK = { alt = { "UNSAFE" } }, + FIX = { alt = { "FIXME", "BUG", "FIXIT", "ISSUE", "FUCKING" } }, + }, + }, + actions = function() + ---@type dora.core.action + local action = require("dora.core.action") + + return action.make_options { + from = "todo-comments.nvim", + category = "TodoComments", + actions = { + { + id = "todo-comments.open-todos-in-trouble", + title = "Open todos in trouble", + callback = "TodoTrouble", + keys = { "xt", desc = "touble-todo" }, + }, + { + id = "todo-comments.open-todos-fix-fixme-in-trouble", + title = "Open todos,fix,fixme in trouble", + callback = "TodoTrouble keywords=TODO,FIX,FIXME", + keys = { "xT", desc = "trouble-TFF" }, + }, + { + id = "todo-comments.list-all-todos-in-telescope", + title = "List all todos in telescope", + callback = "TodoTelescope", + keys = { "lt", desc = "list-todos" }, + }, + { + id = "todo-comments.goto-next-todo", + title = "Goto next todo", + callback = function() + require("todo-comments").jump_next() + end, + keys = { "]t", desc = "jump-next-todo" }, + }, + { + id = "todo-comments.goto-previous-todo", + title = "Goto previous todo", + callback = function() + require("todo-comments").jump_prev() + end, + keys = { "[t", desc = "jump-previous-todo" }, + }, + }, + } + end, + }, + { + "echasnovski/mini.move", + gui = "all", + opts = { + mappings = { + left = "", + right = "", + down = "", + up = "", + line_left = "", + line_right = "", + line_down = "", + line_up = "", + }, + }, + keys = { + { "", mode = { "n", "v" } }, + { "", mode = { "n", "v" } }, + { "", mode = { "n", "v" } }, + { "", mode = { "n", "v" } }, + }, + }, + { + "abecodes/tabout.nvim", + event = "InsertEnter", + dependencies = { "nvim-treesitter/nvim-treesitter" }, + opts = { + tabouts = { + { open = "'", close = "'" }, + { open = '"', close = '"' }, + { open = "`", close = "`" }, + { open = "(", close = ")" }, + { open = "[", close = "]" }, + { open = "{", close = "}" }, + { open = "<", close = ">" }, + }, + }, + keys = { + { + "", + "(TaboutMulti)", + mode = "i", + silent = true, + }, + { + "", + "(TaboutBackMulti)", + mode = "i", + silent = true, + }, + }, + }, + { + "aperezdc/vim-template", + cmd = { "Template", "TemplateHere" }, + init = function() + vim.g.templates_directory = { + vim.fn.stdpath("config") .. "/templates", + } + vim.g.templates_no_autocmd = 0 + vim.g.templates_debug = 0 + vim.g.templates_no_builtin_templates = 1 + end, + gui = "all", + actions = function() + ---@type dora.core.action + local action = require("dora.core.action") + ---@type dora.lib + local lib = require("dora.lib") + + return action.make_options { + from = "vim-template", + category = "Template", + actions = { + { + id = "template.expand-template-into-current-buffer", + title = "Expand template into current buffer", + callback = lib.vim.input_then_exec("Template"), + description = "Expand a mated template fron the beginning of the buffer", + }, + { + id = "template.expand-template-under-cursor", + title = "Expand template under cursor", + callback = lib.vim.input_then_exec("TemplateHere"), + description = "Expand a matched template under the cursor", + }, + }, + } + end, + }, +} diff --git a/lua/dora/packages/editor/plugins/bookmarks.lua b/lua/dora/packages/editor/plugins/bookmarks.lua new file mode 100644 index 00000000..a2f9b6ca --- /dev/null +++ b/lua/dora/packages/editor/plugins/bookmarks.lua @@ -0,0 +1,89 @@ +---@type dora.core.plugin.PluginOption +return { + "crusj/bookmarks.nvim", + dependencies = { + "nvim-tree/nvim-web-devicons", + "nvim-telescope/telescope.nvim", + }, + event = "BufReadPost", + opts = function() + ---@type dora.config + local config = require("dora.config") + + return { + mappings_enabled = false, + sign_icon = config.icon.predefined_icon("Bookmark"), + virt_pattern = { + "*.dart", + "*.cpp", + "*.ts", + "*.lua", + "*.js", + "*.c", + "*.h", + "*.cc", + "*.hh", + "*.hpp", + "*.md", + "*.rs", + "*.toml", + }, + fix_enable = true, + } + end, + config = function(_, opts) + require("bookmarks").setup(opts) + require("telescope").load_extension("bookmarks") + end, + actions = function() + ---@type dora.core.action + local action = require("dora.core.action") + + return action.make_options { + from = "bookmarks.nvim", + category = "Bookmarks", + actions = { + { + id = "bookmarks.list-bookmarks", + title = "List bookmarks", + callback = function() + vim.api.nvim_command("Telescope bookmarks") + end, + keys = { "lm", desc = "list-bookmarks", silent = true }, + }, + { + id = "bookmarks.toggle-bookmarks", + title = "Toggle bookmarks", + callback = function() + require("bookmarks").toggle_bookmarks() + end, + keys = { "bt", desc = "toggle-bookmarks", silent = true }, + }, + { + id = "bookmarks.add-bookmark", + title = "Add bookmark", + callback = function() + require("bookmarks").add_bookmarks() + end, + keys = { "ba", desc = "add-bookmark", silent = true }, + }, + { + id = "bookmarks.delete-at-virt-line", + title = "Delete bookmark at virt text line", + callback = function() + require("bookmarks.list").delete_on_virt() + end, + keys = { "bd", desc = "delete-at-virt-line", silent = true }, + }, + { + id = "bookmarks.show-bookmark-desc", + title = "Show bookmark desc", + callback = function() + require("bookmarks.list").show_desc() + end, + keys = { "bs", desc = "show-bookmark-desc", silent = true }, + }, + }, + } + end, +} diff --git a/lua/dora/packages/editor/plugins/dial.lua b/lua/dora/packages/editor/plugins/dial.lua new file mode 100644 index 00000000..d420cea3 --- /dev/null +++ b/lua/dora/packages/editor/plugins/dial.lua @@ -0,0 +1,114 @@ +---@type dora.core.plugin.PluginOption +return { + "monaqa/dial.nvim", + gui = "all", + event = { "BufReadPost" }, + keys = { + { + "", + desc = "dial inc", + mode = { "n", "v" }, + }, + { + "", + desc = "dial dec", + mode = { "n", "v" }, + }, + }, + opts = function() + local augend = require("dial.augend") + + local function define_custom(...) + return augend.constant.new { + elements = { ... }, + word = true, + cyclic = true, + } + end + + local opts = {} + + opts.commons = { + augend.integer.alias.decimal, + augend.integer.alias.hex, + augend.integer.alias.binary, + augend.date.alias["%Y/%m/%d"], + augend.date.alias["%Y-%m-%d"], + define_custom("true", "false"), + define_custom("yes", "no"), + define_custom("YES", "NO"), + define_custom("||", "&&"), + define_custom("enable", "disable"), + define_custom( + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Sunday" + ), + define_custom( + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December" + ), + } + + opts.ft = { + toml = { augend.semver.alias.semver }, + } + + return opts + end, + config = function(_, opts) + local groups = {} + for ft, ft_opts in pairs(opts.ft) do + groups[ft] = { + unpack(opts.commons), + unpack(ft_opts), + } + end + require("dial.config").augends:register_group(groups) + + vim.keymap.set({ "n", "v" }, "", require("dial.map").inc_normal(), { + desc = "dial inc", + }) + vim.keymap.set({ "n", "v" }, "", require("dial.map").dec_normal(), { + desc = "dial dec", + }) + + for ft, _ in pairs(opts.ft) do + vim.api.nvim_create_autocmd("FileType", { + pattern = ft, + callback = function() + vim.keymap.set( + { "n", "v" }, + "", + require("dial.map").inc_normal(ft), + { + desc = "dial inc", + } + ) + vim.keymap.set( + { "n", "v" }, + "", + require("dial.map").dec_normal(ft), + { + desc = "dial dec", + } + ) + end, + }) + end + end, +} diff --git a/lua/dora/packages/editor/plugins/neo-tree.lua b/lua/dora/packages/editor/plugins/neo-tree.lua new file mode 100644 index 00000000..230425a7 --- /dev/null +++ b/lua/dora/packages/editor/plugins/neo-tree.lua @@ -0,0 +1,202 @@ +---@type dora.core.plugin.PluginOption +return { + "nvim-neo-tree/neo-tree.nvim", + branch = "main", + dependencies = { + "MunifTanjim/nui.nvim", + "nvim-tree/nvim-web-devicons", + "nvim-telescope/telescope.nvim", + }, + cmd = { "Neotree" }, + init = function() + vim.g.neo_tree_remove_legacy_commands = true + end, + keys = { + { + "", + function() + vim.api.nvim_command("Neotree action=focus") + end, + desc = "file-explorer", + }, + { + "ft", + function() + vim.api.nvim_command("Neotree action=focus") + end, + desc = "file-explorer", + }, + }, + opts = function() + ---@type dora.config + local config = require("dora.config") + local predefined_icon = config.icon.predefined_icon + + return { + auto_clean_after_session_restore = true, + close_if_last_window = true, + enable_refresh_on_write = false, + sources = { "filesystem", "buffers", "git_status" }, + source_selector = { + winbar = true, + content_layout = "center", + sources = { + { + source = "filesystem", + display_name = predefined_icon("FolderClosed", 1) .. "File", + }, + { + source = "buffers", + display_name = predefined_icon("DefaultFile", 1) .. "Bufs", + }, + { + source = "git_status", + display_name = predefined_icon("Git", 1) .. "Git", + }, + { + source = "diagnostics", + display_name = predefined_icon("Diagnostic", 1) .. "Diagnostic", + }, + }, + }, + default_component_configs = { + indent = { padding = 0 }, + icon = { + folder_closed = predefined_icon("FolderClosed"), + folder_open = predefined_icon("FolderOpen"), + folder_empty = predefined_icon("FolderEmpty"), + folder_empty_open = predefined_icon("FolderEmpty"), + default = predefined_icon("DefaultFile"), + }, + modified = { symbol = predefined_icon("FileModified") }, + git_status = { + symbols = { + added = predefined_icon("GitAdd"), + deleted = predefined_icon("GitDelete"), + modified = predefined_icon("GitChange"), + renamed = predefined_icon("GitRenamed"), + untracked = predefined_icon("GitUntracked"), + ignored = predefined_icon("GitIgnored"), + unstaged = predefined_icon("GitUnstaged"), + staged = predefined_icon("GitStaged"), + conflict = predefined_icon("GitConflict"), + }, + }, + }, + commands = { + parent_or_close = function(state) + local node = state.tree.get_node() + if + (node.type == "directory" or node.has_children()) + and node.is_expanded() + then + state.commands.toggle_node(state) + else + require("neo-tree.ui.renderer").focus_node( + state, + node.get_parent_id() + ) + end + end, + child_or_open = function(state) + local node = state.tree.get_node() + if node.type == "directory" or node.has_children() then + if not node.is_expanded() then + state.commands.toggle_node(state) + else + require("neo-tree.ui.renderer").focus_node( + state, + node.get_child_ids()[1] + ) + end + else + state.commands.open(state) + end + end, + copy_selector = function(state) + local node = state.tree.get_node() + local filepath = node.get_id() + local filename = node.name + local modify = vim.fn.fnamemodify + local vals = { + BASENAME = modify(filename, ":r"), + EXTENSION = modify(filename, ":e"), + FILENAME = filename, + ["PATH (CWD)"] = modify(filepath, ":."), + ["PATH (HOME)"] = modify(filepath, ":~"), + PATH = filepath, + URI = vim.uri_from_fname(filepath), + } + local options = vim.tbl_filter(function(val) + return vals[val] ~= "" + end, vim.tbl_keys(vals)) + if #options == 0 then + vim.notify("No values to copy", vim.log.levels.WARN) + return + end + table.sort(options) + vim.ui.select(options, { + prompt = "Choose to copy to clipboard:", + format_item = function(item) + return tostring(item) .. ": " .. tostring(vals[item]) .. "}" + end, + }, function(choice) + local result = vals[choice] + if result then + vim.notify("Copied: " .. tostring(result)) + vim.fn.setreg("+", result) + end + end) + end, + find_in_dir = function(state) + local node = state.tree.get_node() + local path = node.get_id() + local cwd + if node.type == "directory" then + cwd = path + else + cwd = vim.fn.fnamemodify(path, ":h") + end + require("telescope.builtin").find_files { cwd = cwd } + end, + }, + window = { + width = 40, + mappings = { + [""] = false, + ["[b"] = "prev_source", + ["]b"] = "next_source", + [""] = "open_with_window_picker", + F = "find_in_dir", + Y = "copy_selector", + h = "parent_or_close", + l = "child_or_open", + o = "open", + }, + fuzzy_finder_mappings = { + [""] = "move_cursor_down", + [""] = "move_cursor_up", + }, + }, + filesystem = { + follow_current_file = { enabled = true }, + hijack_netrw_behavior = "open_current", + use_libuv_file_watcher = false, + filtered_items = { + hide_gitignored = true, + hide_dotfiles = false, + hide_by_name = { "node_modules" }, + never_show = { ".DS_Store", "thumbs.db" }, + }, + }, + event_handlers = { + { + event = "neo_tree_buffer_enter", + handler = function() + vim.opt_local.signcolumn = "auto" + end, + }, + }, + } + end, +} diff --git a/lua/dora/packages/editor/plugins/telescope.lua b/lua/dora/packages/editor/plugins/telescope.lua new file mode 100644 index 00000000..b30d7108 --- /dev/null +++ b/lua/dora/packages/editor/plugins/telescope.lua @@ -0,0 +1,168 @@ +---@type dora.core.plugin.PluginOption[] +return { + { + "nvim-telescope/telescope-fzf-native.nvim", + name = "telescope-fzf-native.nvim", + nixpkg = "telescope-fzf-native-nvim", + lazy = true, + build = "cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release && cmake --build build --config Release && cmake --install build --prefix build", + }, + { + "nvim-telescope/telescope.nvim", + cmd = { "Telescope" }, + lazy = true, + dependencies = { + "nvim-lua/plenary.nvim", + "kkharji/sqlite.lua", + "folke/flash.nvim", + "telescope-fzf-native.nvim", + "TwIStOy/project.nvim", + }, + opts = function() + ---@type dora.lib + local lib = require("dora.lib") + local actions = require("telescope.actions") + + return { + defaults = { + selection_caret = "➤ ", + selection_strategy = "reset", + sorting_strategy = "descending", + layout_strategy = "horizontal", + history = { + path = vim.fn.stdpath("data") + .. "/database/telescope_history.sqlite3", + }, + winblend = lib.vim.current_gui() ~= nil and 20 or 0, + color_devicons = true, + + mappings = { + i = { + [""] = false, + [""] = false, + [""] = actions.preview_scrolling_up, + [""] = actions.preview_scrolling_down, + [""] = actions.move_selection_next, + [""] = actions.move_selection_previous, + [""] = actions.cycle_history_prev, + [""] = actions.cycle_history_next, + [""] = actions.close, + }, + n = { ["q"] = actions.close }, + }, + }, + pickers = { + find_files = { + theme = "ivy", + layout_config = { + height = 0.4, + }, + }, + }, + extensions = { + fzf = { + fuzzy = true, + override_generic_sorter = true, + override_file_sorter = true, + case_mode = "smart_case", + }, + }, + } + end, + config = function(_, opts) + require("telescope").setup(opts) + require("telescope").load_extension("fzf") + + -- require("telescope").load_extension("possession") + -- require("telescope").load_extension("command_palette") + -- require("telescope").load_extension("projects") + -- if require("ht.core.globals").has_obsidian_vault then + -- require("telescope").load_extension("obsidian") + -- end + -- require("telescope").load_extension("my_find_files") + -- + end, + actions = function() + ---@type dora.core.action + local action = require("dora.core.action") + + local lsp_actions = action.make_options { + from = "telescope.nvim", + category = "Telescope", + condition = function(buffer) + return #buffer.lsp_servers > 0 + end, + actions = { + { + id = "telescope.lsp-document-symbols", + title = "Find document symbols", + callback = function() + require("telescope.builtin").lsp_document_symbols {} + end, + keys = { "ls", desc = "document-symbols" }, + }, + { + id = "telescope.lsp-workspace-symbols", + title = "Find workspace symbols", + callback = function() + require("telescope.builtin").lsp_workspace_symbols {} + end, + keys = { "lw", desc = "workspace-symbols" }, + }, + }, + } + + local common_actions = action.make_options { + from = "telescope.nvim", + category = "Telescope", + actions = { + { + id = "telescope.all-buffers", + title = "Find all buffers", + callback = function() + require("telescope.builtin").buffers {} + end, + keys = { "", desc = "all-buffers" }, + }, + { + id = "telescope.find-files", + callback = function() + if vim.b._dora_project_cwd ~= nil then + require("telescope.builtin").find_files { + cwd = vim.b._dora_project_cwd, + no_ignore = vim.b._dora_project_no_ignore, + follow = true, + } + else + require("telescope.builtin").find_files {} + end + end, + title = "Edit project files", + keys = { "e", desc = "edit-project-files" }, + }, + { + id = "telescope.live-grep", + callback = function() + if vim.b._dora_project_cwd ~= nil then + require("telescope.builtin").live_grep { + cwd = vim.b._dora_project_cwd, + no_ignore = vim.b._dora_project_no_ignore, + follow = true, + } + else + require("telescope.builtin").live_grep {} + end + end, + title = "Search for a string in current working directory", + keys = { "lg", desc = "live-grep" }, + }, + }, + } + + return { + unpack(lsp_actions), + unpack(common_actions), + } + end, + }, +} diff --git a/lua/dora/packages/editor/plugins/vim-illuminate.lua b/lua/dora/packages/editor/plugins/vim-illuminate.lua new file mode 100644 index 00000000..163b2055 --- /dev/null +++ b/lua/dora/packages/editor/plugins/vim-illuminate.lua @@ -0,0 +1,105 @@ +---@type dora.core.plugin.PluginOption[] +return { + { + "RRethy/vim-illuminate", + event = { "BufReadPost", "BufNewFile" }, + gui = "all", + opts = { + delay = 200, + filetypes_denylist = { + "nuipopup", + "rightclickpopup", + "NvimTree", + "help", + }, + }, + config = function(_, opts) + require("illuminate").configure(opts) + vim.api.nvim_create_autocmd("FileType", { + callback = function() + local buffer = vim.api.nvim_get_current_buf() + pcall(vim.keymap.del, "n", "]]", { buffer = buffer }) + pcall(vim.keymap.del, "n", "[[", { buffer = buffer }) + end, + }) + end, + keys = { + { + "]]", + function() + require("illuminate").goto_next_reference(false) + end, + desc = "Next Reference", + }, + { + "[[", + function() + require("illuminate").goto_prev_reference(false) + end, + desc = "Prev Reference", + }, + }, + actions = function() + ---@type dora.core.action + local action = require("dora.core.action") + + return action.make_options { + from = "vim-illuminate", + category = "Illuminate", + actions = { + { + id = "illuminate.pause", + callback = "IlluminatePause", + title = "Globally pause vim-illuminate", + }, + { + id = "illuminate.resume", + callback = "IlluminateResume", + title = "Globally resume vim-illuminate", + }, + { + id = "illuminate.toggle", + callback = "IlluminateToggle", + title = "Globally toggle the pause/resume for vim-illuminate", + }, + { + id = "illuminate.pause_buf", + callback = "IlluminatePauseBuf", + title = "Buffer-local pause of vim-illuminate", + }, + { + id = "illuminate.resume_buf", + callback = "IlluminateResumeBuf", + title = "Buffer-local resume of vim-illuminate", + }, + { + id = "illuminate.toggle_buf", + callback = "IlluminateToggleBuf", + title = "Buffer-local toggle of the pause/resume for vim-illuminate", + }, + { + id = "illuminate.freeze", + callback = function() + require("illuminate").freeze_buf() + end, + title = "Freeze the illumination on the buffer, this won't clear the highlights", + }, + { + id = "illuminate.unfreeze", + callback = function() + require("illuminate").unfreeze_buf() + end, + title = "Unfreeze the illumination on the buffer", + }, + { + id = "illuminate.toggle_freeze", + callback = function() + require("illuminate").toggle_freeze_buf() + end, + title = "Toggle the frozen state of the buffer", + }, + }, + } + end, + }, +} diff --git a/lua/dora/packages/editor/plugins/which-key.lua b/lua/dora/packages/editor/plugins/which-key.lua new file mode 100644 index 00000000..84777419 --- /dev/null +++ b/lua/dora/packages/editor/plugins/which-key.lua @@ -0,0 +1,50 @@ +---@type dora.core.plugin.PluginOption[] +return { + { + "folke/which-key.nvim", + event = "VeryLazy", + opts = { + key_labels = { [""] = "SPC", [""] = "RET", [""] = "TAB" }, + layout = { align = "center" }, + ignore_missing = false, + hidden = { + "", + "", + "", + "", + "call", + "lua", + "^:", + "^ ", + }, + show_help = true, + icons = { + breadcrumb = "»", + separator = "󰜴", + group = "+", + }, + }, + config = function(_, opts) + vim.defer_fn(function() + local wk = require("which-key") + wk.setup(opts) + wk.register { + mode = { "n", "v" }, + g = { name = "+goto" }, + ["]"] = { name = "+next" }, + ["["] = { name = "+prev" }, + ["b"] = { name = "+bookmarks" }, + ["f"] = { name = "+file" }, + ["l"] = { name = "+list" }, + ["n"] = { name = "+no" }, + ["p"] = { name = "+preview" }, + ["r"] = { name = "+remote" }, + ["t"] = { name = "+toggle" }, + ["v"] = { name = "+vcs" }, + ["w"] = { name = "+window" }, + ["x"] = { name = "+xray" }, + } + end, 100) + end, + }, +} diff --git a/lua/dora/packages/extra/editor.lua b/lua/dora/packages/extra/editor.lua new file mode 100644 index 00000000..8a8c9d2f --- /dev/null +++ b/lua/dora/packages/extra/editor.lua @@ -0,0 +1,94 @@ +---@type dora.core.package.PackageOption +return { + name = "dora.packages.extra.editor", + deps = { + "dora.packages.editor", + }, + plugins = { + { + "folke/flash.nvim", + event = "BufReadPost", + dependencies = { + { + "telescope.nvim", + opts = function(_, opts) + local function flash(prompt_bufnr) + require("flash").jump { + pattern = "^", + label = { after = { 0, 0 } }, + search = { + mode = "search", + exclude = { + function(win) + return vim.bo[vim.api.nvim_win_get_buf(win)].filetype + ~= "TelescopeResults" + end, + }, + }, + action = function(match) + local picker = + require("telescope.actions.state").get_current_picker( + prompt_bufnr + ) + picker:set_selection(match.pos[1] - 1) + end, + } + end + opts.defaults.mappings.i[""] = flash + opts.defaults.mappings.n["s"] = flash + end, + }, + }, + }, + { + "sindrets/diffview.nvim", + cmd = "DiffviewOpen", + opts = { + view = { + merge_tool = { + layout = "diff3_mixed", + }, + }, + }, + actions = function() + ---@type dora.core.action + local action = require("dora.core.action") + + ---@type dora.core.action.ActionOption[] + local actions = { + { + id = "diffview.open", + title = "Open diffview", + callback = "DiffviewOpen", + }, + { + id = "diffview.close", + title = "Close the current diffview. You can also use :tabclose.", + callback = "DiffviewClose", + }, + { + id = "diffview.toggle", + title = "Toggle the file panel.", + callback = "DiffviewToggleFiles", + }, + { + id = "diffview.focus", + title = "Bring focus to the file panel.", + callback = "DiffviewFocusFiles", + }, + { + id = "diffview.refresh", + title = "Update stats and entries in the file list of the current Diffview.", + callback = "DiffviewRefresh", + }, + } + + return action.make_options { + from = "diffview.nvim", + category = "diffview", + actions = actions, + } + end, + }, + }, +} diff --git a/lua/dora/packages/extra/lang/cmake.lua b/lua/dora/packages/extra/lang/cmake.lua new file mode 100644 index 00000000..e8e114ab --- /dev/null +++ b/lua/dora/packages/extra/lang/cmake.lua @@ -0,0 +1,64 @@ +---@type dora.core.package.PackageOption +return { + name = "dora.packages.extra.lang.cmake", + deps = { + "dora.packages.coding", + "dora.packages.lsp", + "dora.packages.treesitter", + }, + plugins = { + { + "nvim-treesitter", + opts = function(_, opts) + if type(opts.ensure_installed) == "table" then + if vim.list_contains(opts.ensure_installed, "cmake") then + return + end + vim.list_extend(opts.ensure_installed, { "cmake" }) + end + end, + }, + { + "nvim-lspconfig", + opts = { + servers = { + opts = { + cmake = { + initializationOptions = { buildDirectory = "build" }, + }, + }, + }, + }, + }, + { + "conform.nvim", + opts = { + formatters_by_ft = { + cpp = { "gersemi" }, + }, + }, + }, + { + "dial.nvim", + opts = function(_, opts) + local function define_custom(...) + local augend = require("dial.augend") + return augend.constant.new { + elements = { ... }, + word = true, + cyclic = true, + } + end + + if opts.ft.cmake == nil then + opts.ft.cmake = {} + end + + vim.list_extend(opts.ft.cmake, { + define_custom("on", "off"), + define_custom("ON", "OFF"), + }) + end, + }, + }, +} diff --git a/lua/dora/packages/extra/lang/cpp.lua b/lua/dora/packages/extra/lang/cpp.lua new file mode 100644 index 00000000..7f7f38c3 --- /dev/null +++ b/lua/dora/packages/extra/lang/cpp.lua @@ -0,0 +1,238 @@ +---@type dora.core.package.PackageOption +return { + name = "dora.packages.extra.lang.cpp", + deps = { + "dora.packages.coding", + "dora.packages.lsp", + "dora.packages.treesitter", + }, + plugins = { + { + "nvim-treesitter", + opts = function(_, opts) + if type(opts.ensure_installed) == "table" then + vim.list_extend(opts.ensure_installed, { "c", "cpp" }) + end + end, + }, + { + "nvim-lspconfig", + opts = { + servers = { + opts = { + clangd = { + capabilities = { + offsetEncoding = { "utf-16" }, + }, + cmd = { + "clangd", + "--clang-tidy", + "--background-index", + "--background-index-priority=normal", + "--ranking-model=decision_forest", + "--completion-style=detailed", + "--header-insertion=iwyu", + "--limit-references=100", + "--limit-results=100", + "--include-cleaner-stdlib", + "-j=20", + }, + root_dir = function(fname) + return require("lspconfig.util").root_pattern( + "Makefile", + "configure.ac", + "configure.in", + "config.h.in", + "meson.build", + "meson_options.txt", + "build.ninja", + "BLADE_ROOT" + )(fname) or require("lspconfig.util").root_pattern( + "compile_commands.json", + "compile_flags.txt" + )(fname) or require("lspconfig.util").find_git_ancestor( + fname + ) + end, + init_options = { + usePlaceholders = true, + completeUnimported = true, + clangdFileStatus = true, + }, + }, + }, + setup = { + clangd = function() + ---@type dora.lib + local lib = require("dora.lib") + + lib.vim.on_lsp_attach(function(client, bufnr) + if client and client.name == "clangd" then + vim.keymap.set("n", "fa", function() + vim.api.nvim_command("ClangdSwitchSourceHeader") + end, { + desc = "clangd-switch-header", + buffer = bufnr, + }) + end + end) + + require("clangd_extensions").setup { + autoSetHints = false, + hover_with_actions = true, + inlay_hints = { + inline = false, + only_current_line = false, + only_current_line_autocmd = "CursorHold", + show_parameter_hints = false, + show_variable_name = true, + other_hints_prefix = "", + max_len_align = false, + max_len_align_padding = 1, + right_align = false, + right_align_padding = 7, + highlight = "Comment", + }, + ast = { + role_icons = { + type = "🄣", + declaration = "🄓", + expression = "🄔", + statement = ";", + specifier = "🄢", + ["template argument"] = "🆃", + }, + kind_icons = { + Compound = "🄲", + Recovery = "🅁", + TranslationUnit = "🅄", + PackExpansion = "🄿", + TemplateTypeParm = "🅃", + TemplateTemplateParm = "🅃", + TemplateParamObject = "🅃", + }, + }, + memory_usage = { border = "rounded" }, + symbol_info = { border = "rounded" }, + } + end, + }, + }, + }, + }, + { + "p00f/clangd_extensions.nvim", + lazy = true, + after = { + "nvim-cmp", + "nvim-lspconfig", + }, + config = function() end, + }, + { + "nvim-cmp", + opts = function(_, opts) + table.insert( + opts.sorting.comparators, + 1, + require("clangd_extensions.cmp_scores") + ) + end, + }, + { + "TwIStOy/cpp-toolkit.nvim", + dependencies = { + "nvim-telescope/telescope.nvim", + "nvim-lua/plenary.nvim", + }, + gui = "all", + cmd = { "CppGenDef", "CppDebugPrint", "CppToolkit" }, + ft = { "cpp", "c" }, + opts = { + impl_return_type_style = "trailing", + }, + config = function(_, opts) + require("cpp-toolkit").setup(opts) + require("telescope").load_extension("cpptoolkit") + end, + actions = function() + ---@type dora.core.action + local action = require("dora.core.action") + + return action.make_options { + from = "cpp-toolkit.nvim", + category = "CppToolkit", + actions = { + { + id = "cpptoolkit.insert-header", + title = "Insert header", + callback = "Telescope cpptoolkit insert_header", + }, + { + id = "cpptoolkit.gen-def", + title = "Generate function implementation", + callback = "CppGenDef", + }, + { + id = "cpptoolkit.move-value", + title = "Move value", + callback = "CppToolkit shortcut move_value", + }, + { + id = "cpptoolkit.forward-value", + title = "Forward value", + callback = "CppToolkit shortcut forward_value", + }, + }, + } + end, + }, + { + "mfussenegger/nvim-lint", + event = { "BufReadPost", "BufNewFile" }, + opts = function(_, opts) + opts.linters_by_ft.cpp = { "cpplint", "cppcheck" } + end, + }, + { + "conform.nvim", + opts = { + formatters_by_ft = { + cpp = { "clang_format" }, + }, + }, + }, + { + "dial.nvim", + opts = function(_, opts) + local function define_custom(...) + local augend = require("dial.augend") + return augend.constant.new { + elements = { ... }, + word = true, + cyclic = true, + } + end + + if opts.ft.cpp == nil then + opts.ft.cpp = {} + end + + vim.list_extend(opts.ft.cpp, { + define_custom("Debug", "Info", "Warn", "Error", "Fatal"), + define_custom("first", "second"), + define_custom("true_type", "false_type"), + define_custom("uint8_t", "uint16_t", "uint32_t", "uint64_t"), + define_custom("int8_t", "int16_t", "int32_t", "int64_t"), + define_custom("htonl", "ntohl"), + define_custom("htons", "ntohs"), + define_custom("ASSERT_EQ", "ASSERT_NE"), + define_custom("EXPECT_EQ", "EXPECT_NE"), + define_custom("==", "!="), + define_custom("static_cast", "dynamic_cast", "reinterpret_cast"), + define_custom("private", "public", "protected"), + }) + end, + }, + }, +} diff --git a/lua/dora/packages/extra/lang/latex.lua b/lua/dora/packages/extra/lang/latex.lua new file mode 100644 index 00000000..f8da48c0 --- /dev/null +++ b/lua/dora/packages/extra/lang/latex.lua @@ -0,0 +1,69 @@ +---@type dora.core.package.PackageOption +return { + name = "dora.packages.extra.lang.latex", + deps = { + "dora.packages.coding", + "dora.packages.lsp", + "dora.packages.treesitter", + }, + plugins = { + { + "nvim-treesitter", + opts = function(_, opts) + if type(opts.ensure_installed) == "table" then + vim.list_extend(opts.ensure_installed, { "latex" }) + end + end, + }, + { + "kdheepak/cmp-latex-symbols", + ft = "latex", + }, + { + "nvim-cmp", + opts = function(_, opts) + local function in_latex_scope() + local context = require("cmp.config.context") + local ft = vim.api.nvim_get_option_value("filetype", { + buf = 0, + }) + if ft ~= "markdown" and ft ~= "latex" then + return false + end + return context.in_treesitter_capture("text.math") + end + + table.insert(opts.sources, { + name = "latex_symbols", + filetype = { "tex", "latex", "markdown" }, + group_index = 1, + option = { + cache = true, + strategy = 2, -- latex only + }, + entry_filter = function(_, ctx) + if ctx.in_latex_scope == nil then + ctx.in_latex_scope = in_latex_scope() + end + return ctx.in_latex_scope + end, + }) + end, + }, + { + "jbyuki/nabla.nvim", + ft = { "latex", "markdown" }, + keys = { + { + "pf", + function() + require("nabla").popup { + border = "solid", + } + end, + ft = { "latex", "markdown" }, + }, + }, + }, + }, +} diff --git a/lua/dora/packages/extra/lang/lua.lua b/lua/dora/packages/extra/lang/lua.lua new file mode 100644 index 00000000..820eef81 --- /dev/null +++ b/lua/dora/packages/extra/lang/lua.lua @@ -0,0 +1,95 @@ +---@type dora.core.package.PackageOption +return { + name = "dora.packages.extra.lang.lua", + deps = { + "dora.packages.coding", + "dora.packages.lsp", + "dora.packages.treesitter", + }, + plugins = { + { + "nvim-treesitter", + opts = function(_, opts) + if type(opts.ensure_installed) == "table" then + if vim.list_contains(opts.ensure_installed, "lua") then + return + end + vim.list_extend(opts.ensure_installed, { "lua" }) + end + end, + }, + { + "nvim-lspconfig", + opts = function(_, opts) + local libraries = { + "${3rd}/luassert/library", + vim.fn.expand("$VIMRUNTIME/lua"), + vim.fn.expand("$VIMRUNTIME/lua/vim/lsp"), + } + local add_plugins = { + "lazy.nvim", + "plenary.nvim", + "noice.nvim", + "nui.nvim", + "plenary.nvim", + } + for _, plugin in ipairs(add_plugins) do + table.insert( + libraries, + vim.fn.stdpath("data") .. "/lazy/" .. plugin .. "/lua" + ) + end + + opts.servers.opts.lua_ls = { + Lua = { + runtime = { + version = "LuaJIT", + path = vim.split(package.path, ";"), + }, + diagnostics = { + globals = { "vim" }, + disable = {}, + }, + workspace = { library = libraries }, + format = { + enable = true, + defaultConfig = { + indent_style = "space", + continuation_indent_size = "2", + }, + }, + }, + } + end, + }, + { + "conform.nvim", + opts = { + formatters_by_ft = { + lua = { "stylua" }, + }, + }, + }, + { + "dial.nvim", + opts = function(_, opts) + local function define_custom(...) + local augend = require("dial.augend") + return augend.constant.new { + elements = { ... }, + word = true, + cyclic = true, + } + end + + if opts.ft.lua == nil then + opts.ft.lua = {} + end + + vim.list_extend(opts.ft.lua, { + define_custom("==", "~="), + }) + end, + }, + }, +} diff --git a/lua/dora/packages/extra/lang/markdown.lua b/lua/dora/packages/extra/lang/markdown.lua new file mode 100644 index 00000000..205677d3 --- /dev/null +++ b/lua/dora/packages/extra/lang/markdown.lua @@ -0,0 +1,127 @@ +---@type dora.core.package.PackageOption +return { + name = "dora.packages.extra.lang.markdown", + deps = { + "dora.packages.coding", + "dora.packages.lsp", + "dora.packages.treesitter", + }, + plugins = { + { + "jbyuki/nabla.nvim", + ft = { "latex", "markdown" }, + keys = { + { + "pf", + function() + require("nabla").popup { + border = "solid", + } + end, + ft = { "latex", "markdown" }, + }, + }, + }, + { + "iamcco/markdown-preview.nvim", + name = "markdown-preview.nvim", + nixpkg = "markdown-preview-nvim", + ft = { "markdown" }, + build = function() + vim.fn["mkdp#util#install"]() + end, + init = function() + vim.g.mkdp_open_to_the_world = true + vim.g.mkdp_echo_preview_url = true + end, + actions = function() + ---@type dora.core.action + local action = require("dora.core.action") + + return action.make_options { + from = "markdown-preview.nvim", + category = "MdPreview", + condition = function(buf) + return buf.filetype == "markdown" + end, + actions = { + { + id = "markdown-preview.start", + title = "Start md preview", + callback = function() + vim.api.nvim_command("MarkdownPreview") + end, + }, + { + id = "markdown-preview.stop", + title = "Stop md preview", + callback = function() + vim.api.nvim_command("MarkdownPreviewStop") + end, + }, + { + id = "markdown-preview.toggle", + title = "Toggle md preview", + callback = function() + vim.api.nvim_command("MarkdownPreviewToggle") + end, + }, + }, + } + end, + }, + { + "dhruvasagar/vim-table-mode", + ft = { "markdown" }, + cmd = { "TableModeEnable", "TableModeDisable" }, + gui = "all", + actions = function() + ---@type dora.core.action + local action = require("dora.core.action") + + return action.make_options { + from = "vim-table-mode", + category = "TableMode", + condition = function(buf) + return buf.filetype == "markdown" + end, + actions = { + { + id = "table-mode.enable", + title = "Enable table mode", + callback = function() + vim.api.nvim_command("TableModeEnable") + end, + }, + { + id = "table-mode.disable", + title = "Disable table mode", + callback = function() + vim.api.nvim_command("TableModeDisable") + end, + }, + }, + } + end, + }, + { + "lukas-reineke/headlines.nvim", + dependencies = { + "nvim-treesitter/nvim-treesitter", + }, + enabled = function() + return not vim.g.neovide + end, + ft = { "markdown", "vimwiki" }, + opts = {}, + }, + { + "conform.nvim", + opts = { + formatters_by_ft = { + markdown = { "prettier" }, + }, + }, + }, + }, +} diff --git a/lua/dora/packages/extra/lang/python.lua b/lua/dora/packages/extra/lang/python.lua new file mode 100644 index 00000000..e6520ea4 --- /dev/null +++ b/lua/dora/packages/extra/lang/python.lua @@ -0,0 +1,58 @@ +---@type dora.core.package.PackageOption +return { + name = "dora.packages.extra.lang.python", + deps = { + "dora.packages.coding", + "dora.packages.lsp", + "dora.packages.treesitter", + }, + plugins = { + { + "nvim-treesitter", + opts = function(_, opts) + if type(opts.ensure_installed) == "table" then + vim.list_extend(opts.ensure_installed, { "python" }) + end + end, + }, + { + "nvim-lspconfig", + opts = { + servers = { + opts = { + pyright = {}, + }, + }, + }, + }, + { + "conform.nvim", + opts = { + formatters_by_ft = { + cpp = { "black" }, + }, + }, + }, + { + "dial.nvim", + opts = function(_, opts) + local function define_custom(...) + local augend = require("dial.augend") + return augend.constant.new { + elements = { ... }, + word = true, + cyclic = true, + } + end + + if opts.ft.python == nil then + opts.ft.python = {} + end + + vim.list_extend(opts.ft.python, { + define_custom("True", "False"), + }) + end, + }, + }, +} diff --git a/lua/dora/packages/extra/lang/rust.lua b/lua/dora/packages/extra/lang/rust.lua new file mode 100644 index 00000000..eee7a9ee --- /dev/null +++ b/lua/dora/packages/extra/lang/rust.lua @@ -0,0 +1,134 @@ +---@type dora.core.package.PackageOption +return { + name = "dora.packages.extra.lang.rust", + deps = { + "dora.packages.coding", + "dora.packages.lsp", + "dora.packages.treesitter", + }, + plugins = { + { + "saecki/crates.nvim", + dependencies = { "nvim-lua/plenary.nvim", "MunifTanjim/nui.nvim" }, + event = { + { + event = "BufEnter", + pattern = "Cargo.toml", + }, + }, + init = function() + vim.api.nvim_create_autocmd("BufRead", { + group = vim.api.nvim_create_augroup( + "CmpSourceCargo", + { clear = true } + ), + pattern = "Cargo.toml", + callback = function() + local cmp = require("cmp") + cmp.setup.buffer { sources = { { name = "crates" } } } + + vim.keymap.set("n", "K", function() + if require("crates").popup_available() then + require("crates").show_popup() + end + end, { + buffer = true, + }) + end, + }) + end, + opts = { + autoload = true, + popup = { autofocus = true, border = "rounded" }, + src = { + cmp = { + enabled = true, + }, + }, + }, + config = function(_, opts) + require("crates").setup(opts) + require("crates").udpate() + end, + actions = function() + ---@type dora.core.action + local action = require("dora.core.action") + + return action.make_options { + from = "crates.nvim", + category = "Crates", + condition = function(buf) + --- check if buf.full_file_name endwidths "Cargo.toml" + return buf.full_file_name:match("Cargo.toml$") + end, + actions = { + { + id = "crates-nvim.open-homepage", + title = "Open homepage under cursor", + callback = function() + require("crates").open_homepage() + end, + }, + { + id = "crates-nvim.open-documentation", + title = "Open documentation under cursor", + callback = function() + require("crates").open_documentation() + end, + }, + { + id = "crates-nvim.open-repository", + title = "Open repository under cursor", + callback = function() + require("crates").open_repository() + end, + }, + { + id = "crates-nvim.upgrade-crate", + title = "Upgrade crate under cursor", + callback = function() + require("crates").upgrade_crate() + end, + }, + { + id = "crates-nvim.open-crate-popup", + title = "Open crate popup", + callback = function() + require("crates").show_crate_popup() + end, + }, + { + id = "crates-nvim.open-versions-popup", + title = "Open versions popup", + callback = function() + require("crates").show_versions_popup() + end, + }, + { + id = "crates-nvim.open-features-popup", + title = "Open features popup", + callback = function() + require("crates").show_features_popup() + end, + }, + { + id = "crates-nvim.open-dependencies-popup", + title = "Open dependencies popup", + callback = function() + require("crates").show_dependencies_popup() + end, + }, + }, + } + end, + }, + { + "conform.nvim", + opts = { + formatters_by_ft = { + rust = { "rust_format" }, + }, + }, + }, + }, +} diff --git a/lua/dora/packages/extra/lsp.lua b/lua/dora/packages/extra/lsp.lua new file mode 100644 index 00000000..b4842506 --- /dev/null +++ b/lua/dora/packages/extra/lsp.lua @@ -0,0 +1,47 @@ +---@type dora.core.package.PackageOption +return { + name = "dora.packages.extra.lsp", + deps = { + "dora.packages.lsp", + "dora.packages.editor", + }, + plugins = { + { + "stevearc/aerial.nvim", + dependencies = { + "nvim-treesitter", + "nvim-web-devicons", + "telescope.nvim", + }, + cmd = { + "AerialToggle", + "AerialOpen", + "AerialOpenAll", + "AerialClose", + "AerialCloseAll", + "AerialNext", + "AerialPrev", + "AerialGo", + "AerialInfo", + "AerialNavToggle", + "AerialNavOpen", + "AerialNavClose", + }, + opts = { + backends = { "lsp", "markdown", "man" }, + layout = { + default_direction = "right", + placement = "edge", + preserve_equality = true, + }, + attach_mode = "global", + filter_kind = false, + show_guides = true, + }, + config = function(_, opts) + require("aerial").setup(opts) + require("telescope").load_extension("aerial") + end, + }, + }, +} diff --git a/lua/dora/packages/extra/misc/competitive-programming.lua b/lua/dora/packages/extra/misc/competitive-programming.lua new file mode 100644 index 00000000..3cedc4f0 --- /dev/null +++ b/lua/dora/packages/extra/misc/competitive-programming.lua @@ -0,0 +1,74 @@ +---@type dora.core.package.PackageOption +return { + name = "dora.packages.extra.misc.competitive-programming", + plugins = { + { + "p00f/cphelper.nvim", + dependencies = { "nvim-lua/plenary.nvim" }, + cmd = { "CphReceive", "CphTest", "CphRetest", "CphEdit", "CphDelete" }, + keys = { + { + "", + "CphTest", + desc = "Run cp test", + }, + }, + init = function() + local home = os.getenv("HOME") + vim.g["cph#dir"] = home .. "/Projects/competitive-programming" + vim.g["cph#lang"] = "cpp" + vim.g["cph#rust#createjson"] = true + vim.g["cph#cpp#compile_command"] = + "g++ solution.cpp -std=c++20 -o cpp.out" + end, + actions = function() + ---@type dora.core.action + local action = require("dora.core.action") + ---@type dora.lib + local lib = require("dora.lib") + + return action.make_options { + from = "cphelper.nvim", + category = "cphelper", + actions = { + { + id = "cphelper.receive-task", + title = "Receive a parsed task from browser", + callback = "CphReceive", + }, + { + id = "cphelper.run-all-cases", + title = "Test a solutions with all cases", + callback = "CphTest", + }, + { + id = "cphelper.run-specified-case", + title = "Test a solution with a specified case", + callback = lib.vim.input_then_exec("CphTest"), + }, + { + id = "cphelper.retest-all-cases", + title = "Retest a solution with all cases without recompiling", + callback = "CphRetest", + }, + { + id = "cphelper.retest-specified-case", + title = "Retest a solution with a specified case without recompiling", + callback = lib.vim.input_then_exec("CphRetest"), + }, + { + id = "cphelper.edit-task", + title = "Edit/Add a test case", + callback = lib.vim.input_then_exec("CphEdit"), + }, + { + id = "cphelper.delete-task", + title = "Delete a test case", + callback = lib.vim.input_then_exec("CphDelete"), + }, + }, + } + end, + }, + }, +} diff --git a/lua/dora/packages/extra/misc/copilot.lua b/lua/dora/packages/extra/misc/copilot.lua new file mode 100644 index 00000000..7e43bfec --- /dev/null +++ b/lua/dora/packages/extra/misc/copilot.lua @@ -0,0 +1,53 @@ +---@type dora.core.package.PackageOption +return { + name = "dora.packages.extra.misc.copilot", + deps = { + "dora.packages.coding", + }, + plugins = { + { + "zbirenbaum/copilot.lua", + event = "InsertEnter", + dependencies = { "nvim-lua/plenary.nvim" }, + opts = { + suggestion = { + auto_trigger = true, + keymap = { + accept = "", + }, + }, + }, + config = function(_, opts) + vim.defer_fn(function() + require("copilot").setup(opts) + end, 100) + end, + actions = function() + return { + { + id = "copilot.status", + title = "Copilot status", + description = "Show the status of Copilot", + callback = function() + vim.api.nvim_command("Copilot status") + end, + }, + { + id = "copilot.auth", + title = "Copilot auth", + callback = function() + vim.api.nvim_command("Copilot auth") + end, + }, + { + id = "copilot.show-panel", + title = "Copilot panel", + callback = function() + vim.api.nvim_command("Copilot panel") + end, + }, + } + end, + }, + }, +} diff --git a/lua/dora/packages/extra/misc/darwin.lua b/lua/dora/packages/extra/misc/darwin.lua new file mode 100644 index 00000000..06c0a14b --- /dev/null +++ b/lua/dora/packages/extra/misc/darwin.lua @@ -0,0 +1,45 @@ +---@type dora.core.package.PackageOption +return { + name = "dora.packages.extra.misc.darwin", + plugins = { + { + "mrjones2014/dash.nvim", + build = "make install", + cmd = { "Dash", "DashWord" }, + enabled = function() + return vim.loop.os_uname().sysname == "Darwin" + end, + opts = { + dash_app_path = "/Applications/Dash.app", + search_engine = "google", + file_type_keywords = { + dashboard = false, + NvimTree = false, + TelescopePrompt = false, + terminal = false, + packer = false, + fzf = false, + ["neo-tree"] = false, + }, + }, + actions = function() + return { + { + id = "dash.search", + title = "Search Dash", + callback = function() + vim.api.nvim_command("Dash") + end, + }, + { + id = "dash.search-word", + title = "Search Word", + callback = function() + vim.api.nvim_command("DashWord") + end, + }, + } + end, + }, + }, +} diff --git a/lua/dora/packages/extra/misc/tools.lua b/lua/dora/packages/extra/misc/tools.lua new file mode 100644 index 00000000..90821784 --- /dev/null +++ b/lua/dora/packages/extra/misc/tools.lua @@ -0,0 +1,57 @@ +---@type dora.core.package.PackageOption +return { + name = "dora.packages.extra.misc.tools", + plugins = { + { + "skywind3000/asynctasks.vim", + enabled = true, + cmd = { + "AsyncTask", + "AsyncTaskMacro", + "AsyncTaskProfile", + "AsyncTaskEdit", + }, + init = function() + vim.g.asyncrun_open = 10 + vim.g.asyncrun_bell = 0 + vim.g.asyncrun_rootmarks = { + "BLADE_ROOT", + "JK_ROOT", + "WORKSPACE", + ".buckconfig", + "CMakeLists.txt", + } + vim.g.asynctasks_extra_config = + { "~/.dotfiles/dots/tasks/asynctasks.ini" } + end, + dependencies = { + { "skywind3000/asyncrun.vim", cmd = { "AsyncRun", "AsyncStop" } }, + }, + }, + { + "topaxi/gh-actions.nvim", + nixpkg = "gh-actions-nvim", + name = "gh-actions.nvim", + cmd = { "GhActions" }, + dependencies = { "nvim-lua/plenary.nvim", "MunifTanjim/nui.nvim" }, + build = "make", + opts = {}, + }, + { + "pwntester/octo.nvim", + dependencies = { + "nvim-lua/plenary.nvim", + "nvim-telescope/telescope.nvim", + "nvim-tree/nvim-web-devicons", + }, + cmd = { "Octo" }, + opts = { + default_remote = { "origin", "upstream" }, + }, + }, + { + "edkolev/tmuxline.vim", + cmd = { "Tmuxline" }, + }, + }, +} diff --git a/lua/dora/packages/extra/misc/wakatime.lua b/lua/dora/packages/extra/misc/wakatime.lua new file mode 100644 index 00000000..c6c57af9 --- /dev/null +++ b/lua/dora/packages/extra/misc/wakatime.lua @@ -0,0 +1,11 @@ +---@type dora.core.package.PackageOption +return { + name = "dora.packages.extra.misc.wakatime", + plugins = { + { + "wakatime/vim-wakatime", + event = "BufReadPost", + opts = {}, + }, + }, +} diff --git a/lua/dora/packages/extra/obsidian.lua b/lua/dora/packages/extra/obsidian.lua new file mode 100644 index 00000000..fe3d15fb --- /dev/null +++ b/lua/dora/packages/extra/obsidian.lua @@ -0,0 +1,69 @@ +---@type dora.core.package.PackageOption +return { + name = "dora.packages.extra.obsidian", + deps = { + "dora.packages.editor", + "dora.packages.coding", + }, + setup = function() + local au_group = + vim.api.nvim_create_augroup("obsidian_extra_setup", { clear = true }) + + ---@type dora.lib + local lib = require("dora.lib") + + local dir = vim.F.if_nil( + lib.lazy.opts("obsidian.nvim").dir, + vim.fn.expand("~/obsidian-data") + ) + + vim.api.nvim_create_autocmd( + { "BufNewFile", "BufReadPost", "BufWinEnter" }, + { + group = au_group, + pattern = dir .. "/**.md", + callback = function() + vim.wo.conceallevel = 2 + vim.keymap.set("n", "gf", function() + if require("obsidian").util.cursor_on_markdown_link() then + vim.api.nvim_command("ObsidianFollowLink") + end + end, { buffer = true, desc = "obsidian-follow-link" }) + end, + } + ) + end, + plugins = { + { + "epwalsh/obsidian.nvim", + version = "*", + ft = "markdown", + dependencies = { + "plenary.nvim", + "telescope.nvim", + "nvim-treesitter", + }, + cmd = { + "ObsidianBacklinks", + "ObsidianExtractNote", + "ObsidianFollowLink", + "ObsidianLink", + "ObsidianLinkNew", + "ObsidianLinks", + "ObsidianNew", + "ObsidianOpen", + "ObsidianPasteImg", + "ObsidianQuickSwitch", + "ObsidianRename", + "ObsidianSearch", + "ObsidianTags", + "ObsidianTemplate", + "ObsidianToday", + "ObsidianTomorrow", + "ObsidianWorkspace", + "ObsidianYesterday", + }, + opts = {}, + }, + }, +} diff --git a/lua/dora/packages/extra/ui.lua b/lua/dora/packages/extra/ui.lua new file mode 100644 index 00000000..6f4462f6 --- /dev/null +++ b/lua/dora/packages/extra/ui.lua @@ -0,0 +1,47 @@ +---@type dora.core.package.PackageOption +return { + name = "dora.packages.extra.ui", + deps = {}, + plugins = { + { + "folke/drop.nvim", + event = "VimEnter", + cond = function() + return vim.fn.argc() == 0 + end, + opts = { + theme = "snow", + screensaver = false, + }, + }, + { + "NvChad/nvim-colorizer.lua", + ft = { "vim", "lua" }, + cmd = { + "ColorizerAttachToBuffer", + "ColorizerDetachFromBuffer", + "ColorizerReloadAllBuffers", + "ColorizerToggle", + }, + opts = { + filetypes = { "vim", "lua" }, + user_default_options = { + RRGGBB = true, + names = false, + AARRGGBB = true, + mode = "virtualtext", + }, + }, + config = function(_, opts) + require("colorizer").setup(opts) + + local ft = vim.api.nvim_get_option_value("filetype", { + buf = 0, + }) + if ft == "vim" or ft == "lua" then + require("colorizer").attach_to_buffer(0) + end + end, + }, + }, +} diff --git a/lua/dora/packages/lsp/init.lua b/lua/dora/packages/lsp/init.lua new file mode 100644 index 00000000..938d2559 --- /dev/null +++ b/lua/dora/packages/lsp/init.lua @@ -0,0 +1,89 @@ +---@type dora.lib +local lib = require("dora.lib") + +---@type dora.core.package.PackageOption +return { + name = "dora.packages.lsp", + deps = { + "dora.packages.coding", + }, + plugins = lib.tbl.flatten_array { + require("dora.packages.lsp.plugins.nvim-lspconfig"), + require("dora.packages.lsp.plugins.lspsaga"), + require("dora.packages.lsp.plugins.lspkind"), + require("dora.packages.lsp.plugins.glance"), + }, + setup = function() + ---@type dora.config.lsp + local lsp = require("dora.config.lsp") + + if not vim.g.vscode then + -- update lsp._watchfiles + require("dora.packages.lsp.utils.override-fswatch")() + end + + ---@param buffer number + local function create_lsp_autocmds(buffer) + -- display diagnostic win on CursorHold + vim.api.nvim_create_autocmd("CursorHold", { + buffer = buffer, + callback = lsp.methods.open_diagnostic, + }) + end + + ---@param buffer? number + local function setup_lsp_keymaps(buffer) + ---comment normal map + ---@param lhs string + ---@param rhs any + ---@param desc string + local nmap = function(lhs, rhs, desc) + vim.keymap.set("n", lhs, rhs, { desc = desc, buffer = buffer }) + end + + nmap("gD", lsp.methods.declaration, "goto-declaration") + + nmap("gd", lsp.methods.definitions, "goto-definition") + + nmap("gt", lsp.methods.type_definitions, "goto-type-definition") + + if buffer ~= nil then + local current_k_map = vim.fn.mapcheck("K", "n") + -- empty or contains "nvim/runtime" + if current_k_map == "" or current_k_map:find("nvim/runtime") ~= nil then + nmap("K", lsp.methods.show_hover, "show-hover") + end + else + nmap("K", lsp.methods.show_hover, "show-hover") + end + + nmap("gi", lsp.methods.implementations, "goto-impl") + + nmap("gR", lsp.methods.rename, "rename-symbol") + + nmap("ga", lsp.methods.code_action, "code-action") + + nmap("gr", lsp.methods.references, "inspect-references") + + nmap("[c", lsp.methods.prev_diagnostic, "previous-diagnostic") + + nmap("]c", lsp.methods.next_diagnostic, "next-diagnostic") + end + + -- if in vscode environment, create key bindings globally, else only create + -- keybindings on lsp enabled buffers + if vim.g.vscode then + setup_lsp_keymaps() + else + lib.vim.on_lsp_attach(function(_, buffer) + local exists, value = + pcall(vim.api.nvim_buf_get_var, buffer, "_dora_lsp_attached") + if not exists or not value then + create_lsp_autocmds(buffer) + setup_lsp_keymaps(buffer) + vim.api.nvim_buf_set_var(buffer, "_dora_lsp_attached", true) + end + end) + end + end, +} diff --git a/lua/dora/packages/lsp/plugins/glance.lua b/lua/dora/packages/lsp/plugins/glance.lua new file mode 100644 index 00000000..462ebfcc --- /dev/null +++ b/lua/dora/packages/lsp/plugins/glance.lua @@ -0,0 +1,58 @@ +---@type dora.core.plugin.PluginOption +return { + "dnlhc/glance.nvim", + cmd = "Glance", + opts = function() + local actions = require("glance").actions + return { + detached = function(winid) + return vim.api.nvim_win_get_width(winid) < 100 + end, + preview_win_opts = { cursorline = true, number = true, wrap = false }, + border = { disable = true, top_char = "―", bottom_char = "―" }, + theme = { enable = true }, + list = { width = 0.2 }, + mappings = { + list = { + ["j"] = actions.next, + ["k"] = actions.previous, + [""] = false, + [""] = false, + [""] = actions.next_location, + [""] = actions.previous_location, + [""] = actions.preview_scroll_win(5), + [""] = actions.preview_scroll_win(-5), + ["v"] = false, + ["s"] = false, + ["t"] = false, + [""] = actions.jump, + ["o"] = false, + ["l"] = false, + ["q"] = actions.close, + ["Q"] = actions.close, + [""] = actions.close, + }, + preview = { + ["Q"] = actions.close, + [""] = false, + [""] = false, + ["l"] = false, + }, + }, + folds = { fold_closed = "󰅂", fold_open = "󰅀", folded = false }, + indent_lines = { enable = false }, + winbar = { enable = true }, + hooks = { + before_open = function(results, open, jump, method) + if method == "references" or method == "implementations" then + open(results) + elseif #results == 1 then + jump(results[1]) + else + open(results) + end + end, + }, + } + end, +} diff --git a/lua/dora/packages/lsp/plugins/lspkind.lua b/lua/dora/packages/lsp/plugins/lspkind.lua new file mode 100644 index 00000000..97727f04 --- /dev/null +++ b/lua/dora/packages/lsp/plugins/lspkind.lua @@ -0,0 +1,54 @@ +---@type dora.core.plugin.PluginOption +return { + "onsails/lspkind.nvim", + lazy = true, + dependencies = { + { + "nvim-cmp", + opts = function(_, opts) + local lspkind = require("lspkind") + + opts.formatting = { + fields = { "kind", "abbr", "menu" }, + format = function(entry, vim_item) + local kind = lspkind.cmp_format { + mode = "symbol_text", + maxwidth = 50, + ellipsis_char = "...", + before = function(e, item) + item.menu = ({ + buffer = "[Buf]", + nvim_lsp = "[LSP]", + ultisnips = "[Snip]", + luasnip = "[Snip]", + nvim_lua = "[Lua]", + orgmode = "[Org]", + path = "[Path]", + dap = "[DAP]", + emoji = "[Emoji]", + calc = "[CALC]", + latex_symbols = "[LaTeX]", + cmdline_history = "[History]", + cmdline = "[Command]", + copilot = "[Copilot]", + })[e.source.name] or ("[" .. e.source.name .. "]") + if e.source.name == "latex_symbols" then + item.kind = "Math" + end + return item + end, + }(entry, vim_item) + local strings = vim.split(kind.kind, "%s", { trimempty = true }) + kind.kind = strings[1] .. " " + if strings[2] and #strings[2] > 0 then + kind.menu = " (" .. strings[2] .. ")" + else + kind.menu = "" + end + return kind + end, + } + end, + }, + }, +} diff --git a/lua/dora/packages/lsp/plugins/lspsaga.lua b/lua/dora/packages/lsp/plugins/lspsaga.lua new file mode 100644 index 00000000..4bf35e27 --- /dev/null +++ b/lua/dora/packages/lsp/plugins/lspsaga.lua @@ -0,0 +1,22 @@ +---@type dora.core.plugin.PluginOption +return { + "nvimdev/lspsaga.nvim", + event = "LspAttach", + dependencies = { + "nvim-tree/nvim-web-devicons", + "nvim-treesitter/nvim-treesitter", + }, + opts = { + code_action = { + num_shortcut = true, + show_server_name = true, + extend_gitsigns = false, + keys = { quit = { "q", "" }, exec = "" }, + }, + lightbulb = { enable = false }, + symbol_in_winbar = { enable = false }, + ui = { + border = "none", + }, + }, +} diff --git a/lua/dora/packages/lsp/plugins/nvim-lspconfig.lua b/lua/dora/packages/lsp/plugins/nvim-lspconfig.lua new file mode 100644 index 00000000..03e1daa9 --- /dev/null +++ b/lua/dora/packages/lsp/plugins/nvim-lspconfig.lua @@ -0,0 +1,89 @@ +---@type dora.core.plugin.PluginOption +return { + "neovim/nvim-lspconfig", + event = { "BufReadPre", "BufNewFile" }, + opts = function() + ---@type dora.config + local config = require("dora.config") + + return { + diagnostics = { + underline = true, + update_in_insert = false, + virtual_text = { + spacing = 4, + source = "if_many", + prefix = "●", + }, + severity_sort = true, + signs = { + text = { + [vim.diagnostic.severity.ERROR] = config.icon.predefined_icon( + "DiagnosticError", + 1 + ), + [vim.diagnostic.severity.WARN] = config.icon.predefined_icon( + "DiagnosticWarn", + 1 + ), + [vim.diagnostic.severity.INFO] = config.icon.predefined_icon( + "DiagnosticInfo", + 1 + ), + [vim.diagnostic.severity.HINT] = config.icon.predefined_icon( + "DiagnosticHint", + 1 + ), + }, + }, + }, + inlay_hints = { + enabled = false, + }, + capabilities = {}, + servers = { + opts = {}, + setup = {}, + }, + } + end, + config = function(_, opts) + ---@type dora.lib + local lib = require("dora.lib") + + vim.diagnostic.config(vim.deepcopy(opts.diagnostic)) + + local capabilities = vim.tbl_deep_extend( + "force", + {}, + vim.lsp.protocol.make_client_capabilities(), + lib.func.require_then("cmp_nvim_lsp", function(cmp_nvim_lsp) + return cmp_nvim_lsp.default_capabilities() + end) or {}, + opts.capabilities or {} + ) + + ---@param server string + local function setup_server(server) + local server_opts = opts.servers.opts[server] or {} + local server_setup = opts.servers.setup[server] + local common_setup = opts.servers.setup["*"] + + server_opts = vim.tbl_deep_extend("force", { + capabilities = vim.deepcopy(capabilities), + }, server_opts) + + if server_setup ~= nil and server_setup(server, server_opts) then + return + elseif common_setup and common_setup(server, server_opts) then + return + else + require("lspconfig")[server].setup(server_opts) + end + end + + for server, _ in pairs(opts.servers.opts) do + setup_server(server) + end + end, +} diff --git a/lua/dora/packages/lsp/utils/override-fswatch.lua b/lua/dora/packages/lsp/utils/override-fswatch.lua new file mode 100644 index 00000000..48e2ef23 --- /dev/null +++ b/lua/dora/packages/lsp/utils/override-fswatch.lua @@ -0,0 +1,75 @@ +local FSWATCH_EVENTS = { + Created = 1, + Updated = 2, + Removed = 3, + -- Renamed + OwnerModified = 2, + AttributeModified = 2, + MovedFrom = 1, + MovedTo = 3, + -- IsFile + -- IsDir + -- IsSymLink + -- Link + -- Overflow +} + +--- @param data string +--- @param opts table +--- @param callback fun(path: string, event: integer) +local function fswatch_output_handler(data, opts, callback) + if data == nil then + return + end + + local d = vim.split(data, "%s+") + local cpath = d[1] + + for i = 2, #d do + if d[i] == "IsDir" or d[i] == "IsSymLink" or d[i] == "PlatformSpecific" then + return + end + end + + if opts.include_pattern and opts.include_pattern:match(cpath) == nil then + return + end + + if opts.exclude_pattern and opts.exclude_pattern:match(cpath) ~= nil then + return + end + + for i = 2, #d do + local e = FSWATCH_EVENTS[d[i]] + if e then + callback(cpath, e) + end + end +end + +local function fswatch(path, opts, callback) + local obj = vim.system({ + "fswatch", + "--recursive", + "--event-flags", + "--exclude", + "/.git/", + path, + }, { + stdout = function(_, data) + for line in vim.gsplit(data, "\n", { plain = true, trimempty = true }) do + fswatch_output_handler(line, opts, callback) + end + end, + }) + + return function() + obj:kill(2) + end +end + +return function() + if vim.fn.executable("fswatch") == 1 then + require("vim.lsp._watchfiles")._watchfunc = fswatch + end +end diff --git a/lua/dora/packages/treesitter/init.lua b/lua/dora/packages/treesitter/init.lua new file mode 100644 index 00000000..0f2759c9 --- /dev/null +++ b/lua/dora/packages/treesitter/init.lua @@ -0,0 +1,11 @@ +---@type dora.lib +local lib = require("dora.lib") + +---@type dora.core.package.PackageOption +return { + name = "dora.packages.treesitter", + plugins = lib.tbl.flatten_array { + require("dora.packages.treesitter.plugins.nvim-treesitter"), + require("dora.packages.treesitter.plugins._others"), + }, +} diff --git a/lua/dora/packages/treesitter/plugins/_others.lua b/lua/dora/packages/treesitter/plugins/_others.lua new file mode 100644 index 00000000..942a8feb --- /dev/null +++ b/lua/dora/packages/treesitter/plugins/_others.lua @@ -0,0 +1,105 @@ +---@type dora.core.plugin.PluginOption[] +return { + { + "IndianBoy42/tree-sitter-just", + gui = "all", + lazy = true, + config = function() + require("nvim-treesitter.parsers").get_parser_configs().just = { + install_info = { + url = "https://github.com/IndianBoy42/tree-sitter-just", -- local path or git repo + files = { "src/parser.c", "src/scanner.cc" }, + branch = "main", + use_makefile = true, -- this may be necessary on MacOS (try if you see compiler errors) + }, + maintainers = { "@IndianBoy42" }, + } + end, + }, + { + "windwp/nvim-ts-autotag", + gui = "all", + ft = { "markdown" }, + dependencies = { "nvim-treesitter" }, + config = function() + require("nvim-treesitter.configs").setup { + autotag = { + enable = true, + }, + } + end, + }, + { + "nvim-treesitter/nvim-treesitter-textobjects", + gui = "all", + event = { "BufReadPost", "BufNewFile" }, + dependencies = { "nvim-treesitter/nvim-treesitter" }, + opts = { + textobjects = { + select = { + enable = true, + lookahead = true, + keymaps = { + af = "@function.outer", + ["if"] = "@function.inner", + ["i,"] = "@parameter.inner", + ["a,"] = "@parameter.outer", + ["i:"] = "@assignment.rhs", + il = "@lifetime.inner", + ["a;"] = "@statement.outer", + ir = "@super_right.inner", + }, + }, + swap = { + enable = true, + swap_next = { [""] = "@parameter.inner" }, + swap_previous = { [""] = "@parameter.inner" }, + }, + move = { + enable = true, + set_jumps = true, + goto_next_start = { + ["],"] = "@parameter.inner", + ["]l"] = "@lifetime.inner", + ["]f"] = "@function.outer", + ["]r"] = "@super_right.inner", + }, + goto_previous_start = { + ["[,"] = "@parameter.inner", + ["[l"] = "@lifetime.inner", + ["[f"] = "@function.outer", + ["[r"] = "@super_right.inner", + }, + }, + }, + }, + config = function(_, opts) + require("nvim-treesitter.configs").setup(opts) + end, + }, + { + "RRethy/nvim-treesitter-endwise", + gui = "all", + ft = { "lua", "ruby", "vimscript" }, + dependencies = { "nvim-treesitter/nvim-treesitter" }, + opts = { + endwise = { enable = true }, + }, + config = function(_, opts) + require("nvim-treesitter.configs").setup(opts) + end, + }, + { + "Wansmer/treesj", + gui = "all", + dependencies = { + "nvim-treesitter/nvim-treesitter", + }, + cmd = { "TSJToggle", "TSJSplit", "TSJJoin" }, + opts = { + use_default_keymaps = false, + check_syntax_error = false, + max_join_length = 120, + }, + }, +} diff --git a/lua/dora/packages/treesitter/plugins/nvim-treesitter.lua b/lua/dora/packages/treesitter/plugins/nvim-treesitter.lua new file mode 100644 index 00000000..1d024216 --- /dev/null +++ b/lua/dora/packages/treesitter/plugins/nvim-treesitter.lua @@ -0,0 +1,126 @@ +---@type dora.core.plugin.PluginOption +return { + "nvim-treesitter/nvim-treesitter", + gui = "all", + lazy = true, + dependencies = { + "IndianBoy42/tree-sitter-just", + }, + event = { "BufReadPost", "BufNewFile" }, + build = function() + if #vim.api.nvim_list_uis() ~= 0 and not vim.g.vscode then + vim.api.nvim_command("TSUpdate") + end + end, + init = function(plugin) + require("lazy.core.loader").add_to_rtp(plugin) + require("nvim-treesitter.query_predicates") + end, + cmd = { "TSInstall", "TSUpdate", "TSUpdateSync" }, + opts = function() + return { + ensure_installed = { + "astro", + "bash", + "cmake", + "css", + "dart", + "diff", + "dockerfile", + "dot", + "ebnf", + "fish", + "git_config", + "git_rebase", + "gitattributes", + "gitcommit", + "gitignore", + "glimmer", + "go", + "graphql", + "html", + "http", + "ini", + "java", + "javascript", + "json", + "jsonc", + "jsonnet", + "just", + "llvm", + "lua", + "luadoc", + "make", + "markdown", + "markdown_inline", + "matlab", + "ninja", + "nix", + "pascal", + "perl", + "php", + "proto", + "python", + "query", + "regex", + "rust", + "scss", + "sql", + "svelte", + "swift", + "thrift", + "toml", + "tsx", + "twig", + "typescript", + "vim", + "vimdoc", + "vue", + "yaml", + }, + indent = { + enable = false, + }, + highlight = { + enable = not vim.g.vscode, + additional_vim_regex_highlighting = { "markdown" }, + disable = function(lang, bufnr) + if lang == "html" and vim.api.nvim_buf_line_count(bufnr) > 500 then + return true + end + for _, line in ipairs(vim.api.nvim_buf_get_lines(0, 0, 3, false)) do + if #line > 500 then + return true + end + end + return false + end, + }, + incremental_selection = { + enable = true, + keymaps = { + init_selection = "", + node_incremental = "", + scope_incremental = false, + node_decremental = "", + }, + }, + } + end, + keys = { + { "", desc = "init/increment-selection" }, + { "", desc = "decrement-selection" }, + }, + config = function(_, opts) + local parser = require("nvim-treesitter.parsers").get_parser_configs() + -- pin dart parser + parser.dart = { + install_info = { + url = "https://github.com/UserNobody14/tree-sitter-dart", + files = { "src/parser.c", "src/scanner.c" }, + revision = "8aa8ab977647da2d4dcfb8c4726341bee26fbce4", + }, + } + require("nvim-treesitter.configs").setup(opts) + end, +} diff --git a/lua/dora/packages/ui/init.lua b/lua/dora/packages/ui/init.lua new file mode 100644 index 00000000..26a1d2aa --- /dev/null +++ b/lua/dora/packages/ui/init.lua @@ -0,0 +1,14 @@ +---@type dora.lib +local lib = require("dora.lib") + +---@type dora.core.package.PackageOption +return { + name = "dora.packages.ui", + plugins = lib.tbl.flatten_array { + require("dora.packages.ui.plugins._others"), + require("dora.packages.ui.plugins.bufferline"), + require("dora.packages.ui.plugins.lualine"), + require("dora.packages.ui.plugins.noice"), + require("dora.packages.ui.plugins.alpha"), + }, +} diff --git a/lua/dora/packages/ui/plugins/_others.lua b/lua/dora/packages/ui/plugins/_others.lua new file mode 100644 index 00000000..2c3f02ed --- /dev/null +++ b/lua/dora/packages/ui/plugins/_others.lua @@ -0,0 +1,116 @@ +---@type dora.core.plugin.PluginOption[] +return { + { + "rcarriga/nvim-notify", + opts = { + timeout = 3000, + stages = "static", + fps = 60, + max_height = function() + return math.floor( + vim.api.nvim_get_option_value("lines", { scope = "global" }) * 0.75 + ) + end, + max_width = function() + return math.floor( + vim.api.nvim_get_option_value("columns", { scope = "global" }) * 0.75 + ) + end, + on_open = function(win) + vim.api.nvim_win_set_config(win, { zindex = 100 }) + end, + }, + config = function(_, opts) + vim.defer_fn(function() + require("notify").setup(opts) + + ---@type dora.core.registry + local registry = require("dora.core.registry") + + if registry.has("telescope.nvim") then + require("telescope").load_extension("notify") + end + end, 30) + end, + actions = function() + ---@type dora.core.action + local action = require("dora.core.action") + + return action.make_options { + from = "nvim-notify", + category = "Notify", + actions = { + { + id = "notify.list-history", + title = "List notify histories using telescope", + callback = function() + require("telescope").extensions.notify.notify() + end, + keys = { "lh", desc = "list-history" }, + }, + { + id = "notify.dismiss-all", + title = "Dismiss all notifications", + callback = function() + require("notify").dismiss { silent = true, pending = true } + end, + keys = { "nn", desc = "dismiss-all" }, + }, + }, + } + end, + }, + { + "stevearc/dressing.nvim", + lazy = true, + opts = { + input = { + title_pos = "center", + relative = "editor", + insert_only = true, + start_in_insert = true, + }, + }, + init = function() + vim.ui.select = function(...) + require("lazy").load { plugins = { "dressing.nvim" } } + return vim.ui.select(...) + end + vim.ui.input = function(...) + require("lazy").load { plugins = { "dressing.nvim" } } + return vim.ui.input(...) + end + end, + }, + { + "nvim-zh/colorful-winsep.nvim", + event = "WinNew", + opts = { + create_event = function() + local winCount = + require("colorful-winsep.utils").calculate_number_windows() + if winCount == 2 then + local leftWinId = vim.fn.win_getid(vim.fn.winnr("h")) + local filetype = vim.api.nvim_get_option_value( + "filetype", + { buf = vim.api.nvim_win_get_buf(leftWinId) } + ) + if filetype == "NvimTree" or filetype == "neo-tree" then + require("colorful-winsep").NvimSeparatorDel() + end + end + end, + }, + }, + { + "s1n7ax/nvim-window-picker", + opts = { + filter_rules = { + bo = { + filetype = { "NvimTree", "neo-tree", "notify", "NvimSeparator", "" }, + buftype = { "terminal" }, + }, + }, + }, + }, +} diff --git a/lua/dora/packages/ui/plugins/alpha.lua b/lua/dora/packages/ui/plugins/alpha.lua new file mode 100644 index 00000000..8180c754 --- /dev/null +++ b/lua/dora/packages/ui/plugins/alpha.lua @@ -0,0 +1,411 @@ +---@type dora.core.plugin.PluginOption[] +return { + { + "goolord/alpha-nvim", + cond = function() + return vim.fn.argc() == 0 + and vim.o.lines >= 36 + and vim.o.columns >= 80 + and not vim.g.vscode + end, + lazy = false, + dependencies = { + "nvim-tree/nvim-web-devicons", + "nvim-lua/plenary.nvim", + }, + opts = function() + ---@type dora.lib + local lib = require("dora.lib") + ---@type dora.config + local config = require("dora.config") + + local redraw_alpha_autocmd = nil + + local function clear_autocmd() + if redraw_alpha_autocmd then + vim.api.nvim_del_autocmd(redraw_alpha_autocmd) + redraw_alpha_autocmd = nil + end + end + + ---@param sc string + ---@param txt string|fun(): string + ---@param callback string|fun():any + ---@param opts? table + local function make_button(sc, txt, callback, opts) + opts = opts or {} + local on_press = lib.func.normalize_callback( + callback, + vim.F.if_nil(opts.feedkeys, true) + ) + opts = vim.tbl_extend("force", { + position = "center", + shortcut = sc, + cursor = 5, + width = 50, + align_shortcut = "right", + hl_shortcut = "Keyword", + keymap = { + "n", + sc, + "", + { + noremap = true, + silent = true, + nowait = true, + callback = function() + on_press() + if opts.close_on_press then + clear_autocmd() + end + end, + }, + }, + }, opts) + return { + type = "button", + val = txt, + on_press = opts.keymap[4].callback, + opts = opts, + } + end + + ---@param filename string + ---@return string + local function get_extension(filename) + local match = filename:match("^.+(%..+)$") + local ext = "" + if match ~= nil then + ext = match:sub(2) + end + return ext + end + + ---@param filename string + ---@return string, string + local function get_icon_highlight(filename) + local nvim_web_devicons = require("nvim-web-devicons") + local ext = get_extension(filename) + return nvim_web_devicons.get_icon(filename, ext, { default = true }) + end + + ---@param filename string + ---@param sc string short cut + ---@param short_filename? string + local function make_file_button(filename, sc, short_filename) + local nvim_web_devicons = require("nvim-web-devicons") + + short_filename = short_filename or filename + local icon, highlight = get_icon_highlight(filename) + local button_highlights = {} + if type(nvim_web_devicons.highlight) == "boolean" then + if highlight and nvim_web_devicons.highlight then + button_highlights[#button_highlights + 1] = { + highlight, + 0, + 1, + } + end + elseif type(nvim_web_devicons.highlight) == "string" then + button_highlights[#button_highlights + 1] = { + nvim_web_devicons.highlight, + 0, + 1, + } + end + icon = icon .. " " + local fn_start = short_filename:match(".*/") + if fn_start ~= nil then + button_highlights[#button_highlights + 1] = { + "Comment", + #icon - 2, + #fn_start + #icon, + } + end + return make_button(sc, icon .. short_filename, "e " .. filename, { + hl = button_highlights, + feedkeys = false, + }) + end + + ---@param sc string + ---@param project_path string + local function make_project_button(sc, project_path) + local icon_txt = " " + local on_press = function() + vim.api.nvim_command("cd " .. project_path) + -- schedule a redraw to show recent files in project + vim.schedule(function() + require("alpha").redraw() + end) + end + return make_button( + sc, + icon_txt .. vim.fn.fnamemodify(project_path, ":~"), + on_press + ) + end + + local function make_recent_files_buttons() + local path = require("plenary.path") + + local function mru(start, cwd, items_number, opts) + opts = opts + or { + ignore = function(p, ext) + return (string.find(p, "COMMIT_EDITMSG")) + or (vim.tbl_contains({ "gitcommit" }, ext)) + end, + } + + items_number = items_number or 9 + + local oldfiles = {} + for _, v in pairs(vim.v.oldfiles) do + if #oldfiles == items_number then + break + end + local cwd_cond + if not cwd then + cwd_cond = true + else + cwd_cond = vim.startswith(v, cwd) + end + local ignore = (opts.ignore and opts.ignore(v, get_extension(v))) + or false + if (vim.fn.filereadable(v) == 1) and cwd_cond and not ignore then + oldfiles[#oldfiles + 1] = v + end + end + + local target_width = 35 + + local tbl = {} + for i, fn in ipairs(oldfiles) do + local short_fn + if cwd then + short_fn = vim.fn.fnamemodify(fn, ":.") + else + short_fn = vim.fn.fnamemodify(fn, ":~") + end + + if #short_fn > target_width then + short_fn = path.new(short_fn):shorten(1, { -2, -1 }) + if #short_fn > target_width then + short_fn = path.new(short_fn):shorten(1, { -1 }) + end + end + + local special_shortcuts = { "a", "s", "d" } + local shortcut = "" + if i <= #special_shortcuts then + shortcut = special_shortcuts[i] + else + shortcut = tostring(i + start - 1 - #special_shortcuts) + end + + local file_button_el = + make_file_button(fn, " " .. shortcut, short_fn) + tbl[i] = file_button_el + end + return { type = "group", val = tbl, opts = {} } + end + + return { + { + type = "text", + val = "Recent files", + opts = { + hl = "SpecialComment", + shrink_margin = false, + position = "center", + }, + }, + { type = "padding", val = 1 }, + { + type = "group", + val = function() + return { mru(1, vim.fn.getcwd(), 5) } + end, + opts = { shrink_margin = false }, + }, + } + end + + local function make_recent_project_buttons() + local function _build_buttons() + local recent_projects = lib.func.require_then( + "project_nvim", + function(project_nvim) + return project_nvim.get_recent_projects() + end + ) or {} + lib.tbl.list_reverse(recent_projects) + + local buttons = {} + local special_shortcuts = { "a", "s", "d" } + + for i, project_path in ipairs(recent_projects) do + if i > 5 then + break + end + + local shortcut = "" + if i <= #special_shortcuts then + shortcut = special_shortcuts[i] + else + shortcut = tostring(i - #special_shortcuts) + end + buttons[#buttons + 1] = + make_project_button(" " .. shortcut, project_path) + end + + return buttons + end + + return { + { + type = "text", + val = "Recent Projects", + opts = { + hl = "SpecialComment", + shrink_margin = false, + position = "center", + }, + }, + { type = "padding", val = 1 }, + { + type = "group", + val = _build_buttons, + opts = { shrink_margin = false }, + }, + } + end + + local function make_recent_buttons() + local cwd = vim.fn.getcwd() + if cwd == "/" then + return make_recent_project_buttons() + else + return make_recent_files_buttons() + end + end + + local function make_fortune_text() + local stats = require("lazy").stats() + return string.format( + "󱐌 %d plugins loaded in %dms", + stats.count, + stats.startuptime + ) + end + + local function make_header_liens() + local lines = {} + for i, line in ipairs(config.ui.dashboard_header) do + lines[i] = { + type = "text", + val = line, + opts = { + hl = "StartLogo" .. i, + position = "center", + shrink_margin = false, + }, + } + end + lines[#lines + 1] = { + type = "text", + val = "dora.version: " .. require("dora.version").version(), + opts = { + hl = "SpecialComment", + position = "center", + }, + } + return { + type = "group", + val = lines, + opts = { + position = "center", + }, + } + end + + local function make_toolbar_buttons() + local buttons = { + { + type = "text", + val = "Quick Actions", + opts = { hl = "SpecialComment", position = "center" }, + }, + { type = "padding", val = 1 }, + } + + buttons[#buttons + 1] = + make_button("e", "󱪝 New File", ":ene startinsert ") + + if lib.vim.current_gui() ~= nil then + buttons[#buttons + 1] = + make_button("p", " Projects", "PickRecentProject", { + feedkeys = false, + }) + end + + buttons[#buttons + 1] = make_button("q", "󰗼 Quit", function() + vim.cmd("qa") + end) + + return { + type = "group", + val = buttons, + opts = { position = "center" }, + } + end + + return { + layout = { + { type = "padding", val = 1 }, + make_header_liens(), + { type = "padding", val = 1 }, + { + type = "group", + val = make_recent_buttons, + }, + { type = "padding", val = 1 }, + make_toolbar_buttons(), + { type = "padding", val = 1 }, + { + type = "text", + val = make_fortune_text, + opts = { + hl = "AlphaQuote", + position = "center", + }, + }, + }, + opts = { + margin = 5, + redraw_on_resize = true, + setup = function() + vim.api.nvim_create_autocmd("User", { + pattern = "LazyVimStarted", + once = true, + callback = function() + vim.schedule(function() + require("alpha").redraw() + end) + end, + }) + redraw_alpha_autocmd = vim.api.nvim_create_autocmd("DirChanged", { + pattern = "*", + callback = function() + vim.schedule(function() + require("alpha").redraw() + end) + end, + }) + end, + }, + } + end, + }, +} diff --git a/lua/dora/packages/ui/plugins/bufferline.lua b/lua/dora/packages/ui/plugins/bufferline.lua new file mode 100644 index 00000000..6b51c4b8 --- /dev/null +++ b/lua/dora/packages/ui/plugins/bufferline.lua @@ -0,0 +1,89 @@ +---@type dora.core.plugin.PluginOption[] +return { + { + "akinsho/bufferline.nvim", + event = "VeryLazy", + dependencies = { "nvim-tree/nvim-web-devicons", "catppuccin" }, + opts = { + options = { + view = "multiwindow", + sort_by = "insert_after_current", + always_show_bufferline = true, + themable = true, + right_mouse_command = nil, + middle_mouse_command = "bdelete! %d", + indicator = { style = "bold" }, + hover = { enabled = true, delay = 200 }, + separator_style = "thick", + close_command = "BDelete! %d", + numbers = "none", + diagnostics = "", + show_buf_icons = false, + offsets = { + { + filetype = "neo-tree", + text = "File Explorer", + text_align = "center", + highlight = "Directory", + }, + { + filetype = "NvimTree", + text = "File Explorer", + text_align = "center", + highlight = "Directory", + }, + }, + groups = { + options = { toggle_hidden_on_enter = true }, + items = { + { + name = "Tests", + priority = 2, + icon = "", + matcher = function(buf) + local ret0 = string.match(buf.name, "%_test") + local ret1 = string.match(buf.name, "%_spec") + return ret0 ~= nil or ret1 ~= nil + end, + }, + }, + }, + }, + }, + actions = function() + ---@type dora.core.action + local action = require("dora.core.action") + + return action.make_options { + from = "bufferline.nvim", + category = "Bufferline", + actions = { + { + id = "bufferline.cycle-prev", + title = "Previous buffer", + callback = "BufferLineCyclePrev", + keys = { "", desc = "previous-buffer" }, + }, + { + id = "bufferline.cycle-next", + title = "Next buffer", + callback = "BufferLineCycleNext", + keys = { "", desc = "next-buffer" }, + }, + { + id = "bufferline.move-prev", + title = "Move buffer left", + callback = "BufferLineMovePrev", + keys = { "", desc = "move-buffer-left" }, + }, + { + id = "bufferline.move-next", + title = "Move buffer right", + callback = "BufferLineMoveNext", + keys = { ">", desc = "move-buffer-right" }, + }, + }, + } + end, + }, +} diff --git a/lua/dora/packages/ui/plugins/lualine.lua b/lua/dora/packages/ui/plugins/lualine.lua new file mode 100644 index 00000000..3ccde188 --- /dev/null +++ b/lua/dora/packages/ui/plugins/lualine.lua @@ -0,0 +1,98 @@ +---@type dora.core.plugin.PluginOption +return { + "nvim-lualine/lualine.nvim", + event = "VeryLazy", + init = function() + vim.g.lualine_laststatus = vim.o.laststatus + if vim.fn.argc(-1) > 0 then + -- set an empty statusline till lualine loads + vim.o.statusline = " " + else + -- hide the statusline on the starter page + vim.o.laststatus = 0 + end + end, + opts = function() + ---@type dora.config + local config = require("dora.config") + + local function cwd() + local dir = vim.fn.getcwd() + local home = os.getenv("HOME") --[[@as string]] + local match = string.find(dir, home, 1, true) + if match == 1 then + dir = "~" .. string.sub(dir, #home + 1) + end + return config.icon.predefined_icon("FolderOpen", 1) .. dir + end + + local function fileinfo() + local icon = "󰈚 " + local currentFile = vim.fn.expand("%") + local filename + if currentFile == "" then + filename = "Empty " + else + filename = vim.fn.fnamemodify(currentFile, ":.") + local deviconsPresent, devicons = pcall(require, "nvim-web-devicons") + if deviconsPresent then + local ftIcon = devicons.get_icon(filename) + if ftIcon ~= nil then + icon = ftIcon .. " " + end + if vim.fn.expand("%:e") == "md" then + icon = icon .. " " + end + end + end + return icon .. filename + end + + return { + options = { + component_separators = { left = "|", right = "|" }, + section_separators = { left = "", right = "" }, + theme = "auto", + globalstatus = true, + disabled_filetypes = { + statusline = { "dashboard", "alpha", "starter" }, + }, + }, + sections = { + lualine_a = { + { + "mode", + icons_enabled = true, + icon = { + config.icon.predefined_icon("VimLogo", 1), + align = "left", + }, + }, + }, + lualine_b = { cwd }, + lualine_c = { + { fileinfo, separator = "" }, + }, + lualine_x = { + { + "diagnostics", + symbols = { + error = config.icon.predefined_icon("DiagnosticError", 1), + warn = config.icon.predefined_icon("DiagnosticWarn", 1), + info = config.icon.predefined_icon("DiagnosticInfo", 1), + hint = config.icon.predefined_icon("DiagnosticHint", 1), + }, + }, + "branch", + "diff", + }, + lualine_y = { + { "filetype", colored = true, icon_only = false }, + }, + lualine_z = { "progress", "location" }, + }, + tabline = {}, + extensions = { "neo-tree", "lazy" }, + } + end, +} diff --git a/lua/dora/packages/ui/plugins/noice.lua b/lua/dora/packages/ui/plugins/noice.lua new file mode 100644 index 00000000..c501332e --- /dev/null +++ b/lua/dora/packages/ui/plugins/noice.lua @@ -0,0 +1,93 @@ +---@type dora.core.plugin.PluginOption[] +return { + { + "folke/noice.nvim", + event = { "ModeChanged", "BufReadPre", "InsertEnter" }, + dependencies = { "MunifTanjim/nui.nvim", "rcarriga/nvim-notify" }, + opts = { + lsp = { + progress = { enabled = false, throttle = 1000 / 10 }, + override = { + ["vim.lsp.util.convert_input_to_markdown_lines"] = true, + ["vim.lsp.util.stylize_markdown"] = true, + ["cmp.entry.get_documentation"] = true, + }, + signature = { + enabled = false, + auto_open = { + enabled = true, + trigger = true, + luasnip = true, + throttle = 50, + }, + }, + hover = { + enabled = true, + opts = { + border = { style = "none", padding = { 1, 2 } }, + position = { row = 2, col = 2 }, + }, + }, + }, + messages = { enabled = false }, + presets = { + bottom_search = false, + command_palette = true, + long_message_to_split = true, + inc_rename = false, + lsp_doc_border = false, + }, + }, + actions = function() + ---@type dora.core.action + local action = require("dora.core.action") + + return action.make_options { + from = "noice.nvim", + category = "Noice", + actions = { + { + id = "noice.show-message-history", + title = "Shows the message history", + callback = "Noice history", + }, + { + id = "noice.show-last-message", + title = "Shows the last message in a popup", + callback = "Noice last", + }, + { + id = "noice.dismiss-all-messages", + title = "Dismiss all visible messages", + callback = "Noice dismiss", + }, + { + id = "noice.disable", + title = "Disables Noice", + callback = "Noice disable", + }, + { + id = "noice.enable", + title = "Enables Noice", + callback = "Noice enable", + }, + { + id = "noice.show-stats", + title = "Show debugging stats", + callback = "Noice stats", + }, + { + id = "noice.show-message-history-in-telescope", + title = "Opens message history in Telescope", + callback = "Noice telescope", + }, + { + id = "noice.show-errors", + title = "Shows the error messages in a split", + callback = "Noice errors", + }, + }, + } + end, + }, +} diff --git a/lua/dora/version.lua b/lua/dora/version.lua new file mode 100644 index 00000000..8720fcb2 --- /dev/null +++ b/lua/dora/version.lua @@ -0,0 +1,12 @@ +local VERSION_MAJOR = 1 +local VERSION_MINOR = 0 +local VERSION_PATCH = 0 + +---@class dora.version +local M = {} + +function M.version() + return string.format("%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH) +end + +return M diff --git a/lua/ht/plugins/edit/obsidian.lua b/lua/ht/plugins/edit/obsidian.lua index e639bfe1..3611a13f 100644 --- a/lua/ht/plugins/edit/obsidian.lua +++ b/lua/ht/plugins/edit/obsidian.lua @@ -24,15 +24,23 @@ local M = { }, cmd = { "ObsidianBacklinks", - "ObsidianToday", - "ObsidianYesterday", - "ObsidianOpen", - "ObsidianNew", - "ObsidianSearch", - "ObsidianQuickSwitch", + "ObsidianExtractNote", + "ObsidianFollowLink", "ObsidianLink", "ObsidianLinkNew", + "ObsidianLinks", + "ObsidianNew", + "ObsidianOpen", + "ObsidianPasteImg", + "ObsidianQuickSwitch", + "ObsidianRename", + "ObsidianSearch", + "ObsidianTags", "ObsidianTemplate", + "ObsidianToday", + "ObsidianTomorrow", + "ObsidianWorkspace", + "ObsidianYesterday", }, cond = function() return require("ht.core.globals").has_obsidian_vault diff --git a/src/conf/external_tools/lsp_servers/lua_ls.ts b/src/conf/external_tools/lsp_servers/lua_ls.ts index d925c525..57960da2 100644 --- a/src/conf/external_tools/lsp_servers/lua_ls.ts +++ b/src/conf/external_tools/lsp_servers/lua_ls.ts @@ -15,6 +15,7 @@ export const server = new LspServer({ vim.fn.expand("$VIMRUNTIME/lua"), vim.fn.expand("$VIMRUNTIME/lua/vim/lsp"), "${3rd}/luassert/library", + `${vim.fn.stdpath("data")}/lazy/lazy.nvim/lua`, `${vim.fn.stdpath("data")}/lazy/plenary.nvim/lua`, `${vim.fn.stdpath("data")}/lazy/noice.nvim/lua`, `${vim.fn.stdpath("data")}/lazy/nui.nvim/lua`,