Skip to content

Commit

Permalink
feat: configure default scopes (#123)
Browse files Browse the repository at this point in the history
* feat: allow configuring default scopes

* docs: update documentation for default scopes
  • Loading branch information
cbochs authored Mar 16, 2024
1 parent 7f6edfe commit f06f13a
Show file tree
Hide file tree
Showing 7 changed files with 197 additions and 24 deletions.
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, grapple.scope_definition | boolean>
default_scopes = { ... }

---Show icons next to tags or scopes in Grapple windows
---Requires "nvim-tree/nvim-web-devicons"
---@type boolean
Expand Down Expand Up @@ -630,6 +636,16 @@ require("grapple").use_scope("projects")

</details>

#### `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.
Expand Down Expand Up @@ -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
}
})
```

</details>
Expand Down
25 changes: 22 additions & 3 deletions lua/grapple.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -625,6 +643,7 @@ function Grapple.initialize()
-- API methods which are not actionable
local excluded_subcmds = {
"define_scope",
"delete_scope",
"exists",
"find",
"initialize",
Expand Down
27 changes: 18 additions & 9 deletions lua/grapple/app.lua
Original file line number Diff line number Diff line change
Expand Up @@ -45,30 +45,39 @@ 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,
cache = definition.cache,
})
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()
Expand Down
13 changes: 12 additions & 1 deletion lua/grapple/scope_manager.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
52 changes: 42 additions & 10 deletions lua/grapple/settings.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -53,32 +53,32 @@ local DEFAULT_SETTINGS = {
---@field resolver grapple.scope_resolver

---Default scopes provided by Grapple
---@type grapple.scope_definition[]
---@type table<string, grapple.scope_definition | boolean>
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,
resolver = function()
return vim.loop.cwd(), vim.loop.cwd()
end,
},
{
cwd = {
name = "cwd",
desc = "Current working directory",
cache = { event = "DirChanged" },
resolver = function()
return vim.loop.cwd(), vim.loop.cwd()
end,
},
{
git = {
name = "git",
desc = "Git root directory",
fallback = "cwd",
Expand All @@ -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
Expand All @@ -126,7 +126,7 @@ local DEFAULT_SETTINGS = {
return id, path
end,
},
{
lsp = {
name = "lsp",
desc = "LSP root directory",
fallback = "git",
Expand Down Expand Up @@ -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)
Expand Down
14 changes: 14 additions & 0 deletions tests/grapple/app_spec.lua
Original file line number Diff line number Diff line change
@@ -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)
64 changes: 64 additions & 0 deletions tests/grapple/settings_spec.lua
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit f06f13a

Please sign in to comment.