diff --git a/README.md b/README.md index 6627059..2e61e35 100644 --- a/README.md +++ b/README.md @@ -214,10 +214,16 @@ require("grapple").setup({ scope = "git", ---User-defined scopes or overrides - ---For more information, please see the Scope API section + ---For more information about scopes, please see the Scope API section ---@type grapple.scope_definition[] scopes = {}, + ---Default scopes provided by Grapple + ---For more information about default scopes, please see the Scopes section + ---Disable by setting scope to "false". For example, { lsp = false } + ---@type table + default_scopes = { ... } + ---Show icons next to tags or scopes in Grapple windows ---Requires "nvim-tree/nvim-web-devicons" ---@type boolean @@ -630,6 +636,16 @@ require("grapple").use_scope("projects") +#### `Grapple.delete_scope` + +Delete a default or user-defined scope. + +**API**: `require("grapple").delete_scope(scope)` + +**`returns`**: `string?` error + +**`scope`**: `string` scope name + #### `Grapple.use_scope` Change the currently selected scope. @@ -715,6 +731,14 @@ require("grapple").setup({ end } }) + +-- Disable a default scope +-- Note: be careful to disable default scopes that are used as fallbacks +require("grapple").setup({ + default_scopes = { + lsp = false + } +}) ``` diff --git a/lua/grapple.lua b/lua/grapple.lua index 059bb1c..194d986 100644 --- a/lua/grapple.lua +++ b/lua/grapple.lua @@ -3,8 +3,16 @@ local Grapple = {} ---@param opts? grapple.settings function Grapple.setup(opts) local app = require("grapple.app").get() - app:update(opts) - app:load_current_scope() + + local err = app:update(opts) + if err then + return vim.notify(err, vim.log.levels.ERROR) + end + + local err = app:load_current_scope() + if err then + return vim.notify(err, vim.log.levels.ERROR) + end end ---@class grapple.options @@ -379,10 +387,20 @@ end ---Create a user-defined scope ---@param definition grapple.scope_definition +---@return string? error function Grapple.define_scope(definition) local App = require("grapple.app") local app = App.get() - app:define_scope(definition) + return app:define_scope(definition) +end + +---Delete a user-defined or default scope +---@param scope string +---@return string? error +function Grapple.delete_scope(scope) + local App = require("grapple.app") + local app = App.get() + return app:delete_scope(scope) end ---Change the currently selected scope @@ -625,6 +643,7 @@ function Grapple.initialize() -- API methods which are not actionable local excluded_subcmds = { "define_scope", + "delete_scope", "exists", "find", "initialize", diff --git a/lua/grapple/app.lua b/lua/grapple/app.lua index 880630d..a1b8869 100644 --- a/lua/grapple/app.lua +++ b/lua/grapple/app.lua @@ -45,23 +45,26 @@ function App:new() end ---@param opts? grapple.settings +---@return string? error function App:update(opts) self.settings:update(opts) - -- Define default scopes, if not already defined - for _, definition in ipairs(self.settings.default_scopes) do - self:define_scope(vim.tbl_extend("force", definition, { force = false })) - end - - -- Define user scopes, force recreation - for _, definition in ipairs(self.settings.scopes) do - self:define_scope(vim.tbl_extend("force", definition, { force = true })) + for _, definition in ipairs(self.settings:scopes()) do + if definition.delete then + self:delete_scope(definition.name) + else + local err = self:define_scope(vim.tbl_extend("force", definition, { force = true })) + if err then + return err + end + end end end ---@param definition grapple.scope_definition +---@return string? error function App:define_scope(definition) - self.scope_manager:define(definition.name, definition.resolver, { + return self.scope_manager:define(definition.name, definition.resolver, { force = definition.force, desc = definition.desc, fallback = definition.fallback, @@ -69,6 +72,12 @@ function App:define_scope(definition) }) end +---@param scope_name string +---@return string? error +function App:delete_scope(scope_name) + return self.scope_manager:delete(scope_name) +end + ---@return string? error function App:load_current_scope() local scope, err = self:current_scope() diff --git a/lua/grapple/scope_manager.lua b/lua/grapple/scope_manager.lua index e265caf..8701b8c 100644 --- a/lua/grapple/scope_manager.lua +++ b/lua/grapple/scope_manager.lua @@ -89,7 +89,7 @@ function ScopeManager:define(name, resolver, opts) if opts.fallback then fallback, err = self:get(opts.fallback) if not fallback then - return err + return string.format("could not create scope: %s, error: %s", name, err) end end @@ -108,4 +108,15 @@ function ScopeManager:define(name, resolver, opts) return nil end +---@param name string +---@return string? error +function ScopeManager:delete(name) + if not self:exists(name) then + return + end + + self.cache:close(name) + self.scopes[name] = nil +end + return ScopeManager diff --git a/lua/grapple/settings.lua b/lua/grapple/settings.lua index f912213..2292ab3 100644 --- a/lua/grapple/settings.lua +++ b/lua/grapple/settings.lua @@ -40,8 +40,8 @@ local DEFAULT_SETTINGS = { style = "relative", ---A string of characters used for quick selecting in Grapple windows - ---An empty string or nil will disable quick select - ---@type string | nil + ---An empty string will disable quick select + ---@type string quick_select = "123456789", ---@class grapple.scope_definition @@ -53,16 +53,16 @@ local DEFAULT_SETTINGS = { ---@field resolver grapple.scope_resolver ---Default scopes provided by Grapple - ---@type grapple.scope_definition[] + ---@type table default_scopes = { - { + global = { name = "global", desc = "Global scope", resolver = function() return "global", vim.loop.cwd() end, }, - { + static = { name = "static", desc = "Initial working directory", cache = true, @@ -70,7 +70,7 @@ local DEFAULT_SETTINGS = { return vim.loop.cwd(), vim.loop.cwd() end, }, - { + cwd = { name = "cwd", desc = "Current working directory", cache = { event = "DirChanged" }, @@ -78,7 +78,7 @@ local DEFAULT_SETTINGS = { return vim.loop.cwd(), vim.loop.cwd() end, }, - { + git = { name = "git", desc = "Git root directory", fallback = "cwd", @@ -98,10 +98,10 @@ local DEFAULT_SETTINGS = { return root, root end, }, - { + git_branch = { name = "git_branch", desc = "Git root directory and branch", - fallback = "git", + fallback = "cwd", cache = { event = { "BufEnter", "FocusGained" }, debounce = 1000, -- ms @@ -126,7 +126,7 @@ local DEFAULT_SETTINGS = { return id, path end, }, - { + lsp = { name = "lsp", desc = "LSP root directory", fallback = "git", @@ -417,6 +417,38 @@ function Settings:quick_select() return vim.tbl_filter(Util.not_empty, vim.split(self.inner.quick_select, "")) end +---Override scopes to combine both the default scopes and user-defined scopes +---@return grapple.scope_definition[] +function Settings:scopes() + -- HACK: Define the order so that fallbacks are defined first + local default_order = { + "global", + "cwd", + "git", + "git_branch", + "lsp", + } + + local scopes = {} + + for _, name in ipairs(default_order) do + local definition = self.inner.default_scopes[name] + if definition == false then + table.insert(scopes, { name = name, delete = true }) + elseif type(definition) == "table" then + table.insert(scopes, self.inner.default_scopes[name]) + else + error(string.format("invalid default scope: %s", vim.inspect(definition))) + end + end + + for _, definition in ipairs(self.inner.scopes) do + table.insert(scopes, definition) + end + + return scopes +end + -- Update settings in-place ---@param opts? grapple.settings function Settings:update(opts) diff --git a/tests/grapple/app_spec.lua b/tests/grapple/app_spec.lua new file mode 100644 index 0000000..820526d --- /dev/null +++ b/tests/grapple/app_spec.lua @@ -0,0 +1,14 @@ +local App = require("grapple.app") +local Util = require("grapple.util") + +describe("App", function() + describe(".update", function() + it("defines the default scopes", function() + local app = App:new() + app:update() + local names = vim.tbl_keys(app.scope_manager.scopes) + table.sort(names, Util.as_lower) + assert.same({ "cwd", "git", "git_branch", "global", "lsp" }, names) + end) + end) +end) diff --git a/tests/grapple/settings_spec.lua b/tests/grapple/settings_spec.lua new file mode 100644 index 0000000..eed2706 --- /dev/null +++ b/tests/grapple/settings_spec.lua @@ -0,0 +1,64 @@ +local Settings = require("grapple.settings") + +describe("Settings", function() + describe("Defaults", function() + it("has the correct style defaults", function() + local settings = Settings:new() + assert.same(true, settings.icons) + assert.same(true, settings.status) + assert.same("end", settings.name_pos) + assert.same("relative", settings.style) + end) + + it("has the correct scope default", function() + local settings = Settings:new() + assert.same("git", settings.scope) + end) + end) + + describe(".quick_select", function() + it("has the correct quick_select default", function() + local settings = Settings:new() + assert.same({ "1", "2", "3", "4", "5", "6", "7", "8", "9" }, settings:quick_select()) + end) + + it("can be set to nothing", function() + local settings = Settings:new() + settings:update({ quick_select = "" }) + assert.same({}, settings:quick_select()) + end) + end) + + describe(".scopes", function() + it("has the correct scopes default", function() + local settings = Settings:new() + -- stylua: ignore + local names = vim.tbl_map(function(def) return def.name end, settings:scopes()) + assert.same({ "global", "cwd", "git", "git_branch", "lsp" }, names) + end) + + it("merges default and user-defined scopes", function() + local settings = Settings:new() + settings:update({ scopes = { { name = "test" } } }) + -- stylua: ignore + local names = vim.tbl_map(function(def) return def.name end, settings:scopes()) + assert.same({ "global", "cwd", "git", "git_branch", "lsp", "test" }, names) + end) + + it("overrides default scope definitions", function() + local settings = Settings:new() + settings:update({ default_scopes = { global = { name = "bob" } } }) + -- stylua: ignore + local names = vim.tbl_map(function(def) return def.name end, settings:scopes()) + assert.same({ "bob", "cwd", "git", "git_branch", "lsp" }, names) + end) + + it("marks default scopes to be deleted", function() + local settings = Settings:new() + settings:update({ default_scopes = { cwd = false } }) + -- stylua: ignore + local deleted = vim.tbl_filter(function(def) return def.delete end, settings:scopes()) + assert.same({ { name = "cwd", delete = true } }, deleted) + end) + end) +end)