Compare commits

..

31 Commits

Author SHA1 Message Date
70853bd01e disabled packages were still getting force loaded 2025-07-10 21:20:53 -04:00
9d4322572c should've tested the changes, nvim_create_usercommand doesn't like...
unkown options
2025-07-04 05:27:36 -04:00
3b33a604d8 commands get rerun by default now 2025-07-04 05:25:06 -04:00
5aff147731 add better type definitions, and fix a bug related to...
lua indexing my beloved
2025-07-04 05:04:23 -04:00
edf32fbf06 fix bug: remove the callback from the on_load list 2025-07-03 18:02:44 -04:00
adec93b7f4 fix formatting log lines causing errors 2025-07-03 17:02:33 -04:00
1cd5f63f8e actually error when we fail to setup lazy loading for a package 2025-07-03 17:00:46 -04:00
8e46eddecd Allow users to lazy load on another package 2025-07-03 16:31:04 -04:00
542298c1fe registering a filetype lazy load condition should happen on self not the...
lazy_loader
2025-07-02 21:51:34 -04:00
cfc3f08d53 remove a testcase for specs 2025-07-02 21:13:30 -04:00
1c2f49fcfa unpin dep internally 2025-07-02 21:13:08 -04:00
f6209048f1 Revert "use the correct_spec function to handle dependencies and requirements"
This reverts commit 318a86d786.
2025-07-02 21:07:04 -04:00
5bd30d9397 don't error when the ui is closed 2025-07-02 20:36:14 -04:00
1538046b6f make finding modules synchronous to avoid some bugs when calling...
internal neovim api functions
2025-07-02 20:34:42 -04:00
5deffca36e improve lazy loading on commands, dep will now load on completion 2025-07-02 17:54:43 -04:00
318a86d786 use the correct_spec function to handle dependencies and requirements 2025-07-02 15:00:37 -04:00
5541a656e2 add tests for the spec checker 2025-07-02 15:00:00 -04:00
59a963a5c5 update the LICENSE 2025-07-02 04:23:14 -04:00
cd43b3b853 add git pre-commit hooks 2025-07-02 04:15:58 -04:00
585f04c745 add testing...
this is very limited right now, but I will expand as I see fit
2025-07-02 03:53:31 -04:00
a13d616da5 add documentation 2025-07-02 03:11:20 -04:00
26ef7289f6 update readme 2025-07-01 23:34:32 -04:00
d406ee18f2 allow never syncing 2025-07-01 23:34:00 -04:00
e0f39fe0db allow disabling modules 2025-07-01 23:33:33 -04:00
e8276e3137 ensure that the setup step doesn't get run multiple times when opening...
the ui
2025-07-01 21:57:08 -04:00
44ec0633ce add a ui (wip) 2025-07-01 21:44:43 -04:00
fa3457e463 formatting 2025-07-01 21:38:41 -04:00
f4d1c4cf25 reorganize the main file a little bit 2025-07-01 21:38:10 -04:00
2346a0baa5 remove lazied field from package as it was unused 2025-07-01 15:55:37 -04:00
8b91fc9c2f bench doesn't need to be a class 2025-06-29 18:12:47 -04:00
b60e6a3934 allow lazy loading colorschemes 2025-06-29 18:12:30 -04:00
22 changed files with 1429 additions and 203 deletions

11
.githooks/pre-commit Executable file
View File

@ -0,0 +1,11 @@
#!/bin/sh
echo "Running tests before commit..."
# run tests
make test || {
echo "Tests failed. Commit aborted."
exit 1
}
echo "Tests passed. Proceeding with commit."

1
.gitignore vendored
View File

@ -1 +1,2 @@
.DS_Store .DS_Store
doc/tags

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2023 squibid Copyright (c) 2023-2025 squibid
Copyright (c) 2021-2023 chiya.dev Copyright (c) 2021-2023 chiya.dev
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy

4
Makefile Normal file
View File

@ -0,0 +1,4 @@
test:
nvim --headless -c "PlenaryBustedDirectory tests/ {minimal_init = './tests/minit.lua'}"
.PHONY: test

View File

@ -1,7 +1,5 @@
# dep # dep
> This readme is a work in progress.
A versatile, declarative and correct [neovim][2] package manager in [Lua][3]. A versatile, declarative and correct [neovim][2] package manager in [Lua][3].
Originally written for personal use by [luaneko][4]. Adapted by [squibid][5] for Originally written for personal use by [luaneko][4]. Adapted by [squibid][5] for
general use. general use.
@ -11,13 +9,14 @@ What does that mean?
1. `versatile` - packages can be declared in any Lua file in any order of your 1. `versatile` - packages can be declared in any Lua file in any order of your
liking. liking.
2. `declarative` - packages are declared using simple Lua tables. 2. `declarative` - packages are declared using simple Lua tables.
3. `correct` - packages are always loaded in a correct and consistent order. 3. `correct` - packages are always loaded in a correct and consistent order
(barring any lazy loading).
In addition to the above dep has been built to be completely in control of you, In addition to the above dep has been built to be completely in control of you,
the user. With the help of lazy loading you can choose when your plugin loads the user. With the help of lazy loading you can choose when your plugin loads
down to the finest detail (examples may be found below). down to the finest detail (examples may be found below).
See also squibid's [neovim-configs][10] for an example of how dep can be used in See also squibid's [neovim-config][10] for an example of how dep can be used in
practice. practice.
## Requirements ## Requirements
@ -35,7 +34,7 @@ practice.
local path = vim.fn.stdpath("data") .. "/site/pack/deps/opt/dep" local path = vim.fn.stdpath("data") .. "/site/pack/deps/opt/dep"
if vim.fn.empty(vim.fn.glob(path)) > 0 then if vim.fn.empty(vim.fn.glob(path)) > 0 then
vim.fn.system({ "git", "clone", "--depth=1", "https://git.squi.bid/dep", path }) vim.fn.system({ "git", "clone", "--depth=1", "https://git.squi.bid/squibid/dep", path })
end end
vim.cmd("packadd dep") vim.cmd("packadd dep")
@ -57,6 +56,7 @@ require "dep" {
- `:DepClean` - cleans removed packages. - `:DepClean` - cleans removed packages.
- `:DepReload` - reloads all packages. - `:DepReload` - reloads all packages.
- `:DepLog` - opens the log file. - `:DepLog` - opens the log file.
- `:DepUi` - opens the ui.
## Package specification ## Package specification
@ -83,8 +83,9 @@ A package must be declared in the following format.
os.execute(...) os.execute(...)
end, end,
-- [function] Code used to determine when the package should be loaded. -- [function|true] Code used to determine when the package should be loaded.
lazy = function(load) lazy = function(load)
load:cmd("LoadPackage")
end, end,
-- [string] Overrides the short name of the package. -- [string] Overrides the short name of the package.
@ -93,7 +94,7 @@ A package must be declared in the following format.
-- [string] Overrides the URL of the git repository to clone. -- [string] Overrides the URL of the git repository to clone.
-- Defaults to "https://github.com/{full_name}.git". -- Defaults to "https://github.com/{full_name}.git".
url = "https://git.chiya.dev/user/package.git", url = "https://git.squi.bid/user/package.git",
-- [string] Overrides the source in which the package is gotten -- [string] Overrides the source in which the package is gotten
-- from. This is not set by default. -- from. This is not set by default.
@ -351,7 +352,7 @@ require "dep" {
``` ```
If you're in the need of a deeper understanding of how the utils work go check If you're in the need of a deeper understanding of how the utils work go check
out `lua/lazy/utils.lua` for the source code. out `lua/lazy/loader/init.lua` for the source code.
## Separating code into modules ## Separating code into modules
@ -378,6 +379,13 @@ return {
Package specifications from other modules can be loaded using the `modules` option. Package specifications from other modules can be loaded using the `modules` option.
```lua ```lua
require "dep" {
modules = {
prefix = "packages"
}
}
-- the above is equivalent to
require "dep" { require "dep" {
modules = { modules = {
prefix = "packages.", prefix = "packages.",
@ -386,7 +394,7 @@ require "dep" {
} }
} }
-- the above is equivalent to -- or
require "dep" { require "dep" {
modules = { modules = {
"packages.search", "packages.search",
@ -474,6 +482,14 @@ require "dep" {
- Lazy loading nvim-cmp doesn't work as the external sources don't get reconized - Lazy loading nvim-cmp doesn't work as the external sources don't get reconized
by nvim-cmp when it's loaded. by nvim-cmp when it's loaded.
## Contributing
When contributing you may choose to run tests before commiting changes, if that
is so you may choose to run the following:
```sh
git config core.hooksPath .githooks
```
## License ## License
dep is licensed under the [MIT License](LICENSE). dep is licensed under the [MIT License](LICENSE).

670
doc/dep.txt Normal file
View File

@ -0,0 +1,670 @@
*dep.txt* Declarative Package Manager 02-Jul-2025
==============================================================================
Table of Contents *dep-table-of-contents*
1. Introduction |dep|
2. Setup |dep-setup|
3. Specs |dep-spec|
- Package Spec |dep-package-spec|
- Module Spec |dep-module-spec|
4. Lazy Loading |dep-lazy-loading|
- Lazy Loading API |dep-lazy-loading-api|
- Lazy Loading API Shorthands |dep-lazy-loading-api-shorthands|
5. Commands |dep-commands|
6. Examples |dep-examples|
- Declaring Dependencies |dep-examples-declaring-dependencies|
- Modules |dep-examples-modules|
- Lazy Loading |dep-examples-lazy-loading|
7. Credits & License |dep-credits|
==============================================================================
1. Introduction *dep*
A versatile, declarative and correct neovim package manager in Lua. Originally
written for personal use by luaneko. Adapted by squibid for general use.
What does that mean?
1. `versatile` - packages can be declared in any Lua file in any order of your
liking.
2. `declarative` - packages are declared using simple Lua tables.
3. `correct` - packages are always loaded in a correct and consistent order
(barring any lazy loading).
In addition to the above dep has been built to be completely in control of you,
the user. With the help of lazy loading you can choose when your plugin loads
down to the finest detail (examples may be found below).
==============================================================================
2. Setup *dep-setup*
Put the following anywhere before any actual use of dep.
>lua
local path = vim.fn.stdpath("data") .. "/site/pack/deps/opt/dep"
if vim.fn.empty(vim.fn.glob(path)) > 0 then
vim.fn.system({
"git",
"clone",
"--depth=1",
"https://git.squi.bid/squibid/dep",
path,
})
end
vim.cmd("packadd dep")
<
==============================================================================
3. Specs *dep-spec*
dep uses a variation of specifications to ensure everything works smoothly.
This includes a basic spec used when setting up dep:
>lua
require("dep") {
-- [string] Specifies when dep should automatically synchronize.
-- "never": disable this behavior
-- "new": only install newly declared packages (default)
-- "always": synchronize all packages on startup
sync = "new",
-- [array] Specifies the modules to load package specifications from.
-- Defaults to an empty table.
-- Items can be either an array of package specifications,
-- or a string that indicates the name of the module from which the array
-- of package specifications is loaded.
--
-- "."'s are added between the prefix and module names as required. In
-- addition if there is only a prefix and no modules supplied then dep
-- automatically loads all lua files in the directory.
modules = {
-- [string] Prefix string to prepend to all module names. This is
-- optional.
prefix = "",
-- [string] module names
...
},
-- [table|string] package specification(s)
...
}
<
PACKAGE SPEC *dep-package-spec*
>lua
{
-- [string] Specifies the full name of the package.
-- This is the only required field; all other fields are optional.
"user/package",
-- [function] Code to run before the package is loaded into neovim.
setup = function()
vim.g.package_config = ...
end,
-- [function] Code to run after the package is loaded into neovim.
load = function()
require "package".setup(...)
end,
-- [function] Code to run after the package is installed or updated.
config = function()
os.execute(...)
end,
-- [function|true] Code used to determine when the package should be
-- loaded.
lazy = function(load)
load:cmd("LoadPackage")
end,
-- [string] Overrides the short name of the package.
-- Defaults to a substring of the full name after '/'.
as = "custom_package",
-- [string] Overrides the URL of the git repository to clone.
-- Defaults to "https://github.com/{full_name}.git".
url = "https://git.squi.bid/user/package.git",
-- [string] Overrides the source in which the package is gotten
-- from. This is not set by default.
path = "~/my-local-package/",
-- [string] Overrides the name of the branch to clone.
-- Defaults to whatever the remote configured as their HEAD, which is
-- usually "master".
branch = "develop",
-- [string] Overrides the commit ref to target
-- Defaults to the latest commit on the current branch
commit = "e76cb03",
-- [boolean] Prevents the package from being loaded.
disable = true,
-- [boolean] Prevents the package from being updated.
pin = true,
-- [string|array] Specifies requirements that must be loaded before the
-- package. If given a string, it is wrapped into an array.
reqs = {...},
-- [string|array] Specifies dependents that must be loaded after the
-- package. If given a string, it is wrapped into an array.
deps = {...}
}
<
MODULE SPEC *dep-module-spec*
>lua
{
-- [string] Specifies a name for the module
name = "a name",
-- [string] Specifies a description of the module
desc = "a description of the module",
-- [boolean] Prevents all packages in the module from being loaded.
disable = false,
-- [table|string] package specification(s)
...
}
<
More information on the package specifications may be found in
|dep-package-spec|.
==============================================================================
4. Lazy Loading *dep-lazy-loading*
Lazy loading is important for making sure neovim can load nice and fast unlike
a certain bloated IDE. It has a seperate section in this documentation to
ensure that you can use it to it's full extent.
If you refer to the |dep-package-spec| you'll notice the `lazy` flag which may
be used to conditionally load a package. When it is set to a function you
choose when it runs and more information on that may be found in
|dep-lazy-loading-api|. In addition to setting it to a function you may set it
to `true` in which case dep takes care of loading it for you.
When setting a colorscheme dep checks to make sure that the plugin is loaded,
therefore it's recommended that you make use of the `lazy` flags ability to be
set to `true` by setting any colorscheme that you have installed, but do not
use as your main one to lazy.
LAZY LOADING API *dep-lazy-loading-api*
Within the |dep-package-spec| the lazy flag when set to a function takes one
argument `load` which is a class containing loading functions. For the
following examples assume that `load` is set to the class which may be found
within `lua/dep/lazy/loader/init.lua`.
------------------------------------------------------------------------------
LOAD:CMD *dep-lazy-loading-api-cmd*
`load:cmd` is a function which allows you to specify a command for the package
to load on. It takes the similar arguments to |nvim_create_user_command()|
with a key difference in what the command runs. The following is an example of
what arguments the function takes:
>lua
load:cmd("Command", {})
<
Notice the missing 'command' argument which is found in
|nvim_create_user_command|, this is replaced by a callback function. The above
is equivalent to the following:
>lua
load:cmd("Command", {
callback = function()
load:cleanup()
if (rerun) then
vim.cmd("Command")
end
end
})
<
If you wish the second argument may be completely ommitted. Note the inclusion
of a `rerun` field, this is a parameter which may be passed into the options table
to re-run the binding after loading the package. You may choose to disable the
built-in logic by passing false.
------------------------------------------------------------------------------
LOAD:AUTO *dep-lazy-loading-api-auto*
`load:auto` is a function which allows you to specify an auto command for the
package to load on. It takes the same arguments as |nvim_create_autocmd()|.
The following is an example of using it:
>lua
load:auto("InsertEnter", {})
<
Just like with |nvim_create_autocmd()| you may choose to pass in a 'callback'
by default the above is equivalent to the following:
>lua
load:auto("InsertEnter", {
callback = function()
load:cleanup()
end
})
<
As with `load:cmd` the second argument may be ommitted.
------------------------------------------------------------------------------
LOAD:FT *dep-lazy-loading-api-ft*
`load:ft` is a function which allows you to specify a filetype for the package
to load on. It takes one argument: 'filetype' like so:
>lua
load:ft("lua")
<
Which is equivalent to the following:
>lua
load:auto("FileType", {
pattern = "lua",
callback = function()
load:cleanup()
end
})
<
Note that this is just an expansion of `load:auto` for your convenience.
------------------------------------------------------------------------------
LOAD:KEYMAP *dep-lazy-loading-api-keymap*
`load:keymap` is a function which allows you to specify a keymap for the
package to load on. It takes the similar arguments to |vim.keymap.set()| with a
key difference in what the command runs. The following is an example of what
arguments the function takes:
>lua
load:keymap("n", "<leader>p", {})
<
Notice the missing 'rhs' argument which is found in |vim.keymap.set|, this is
replaced by a callback function. The above is equivalent to the following:
>lua
load:keymap("n", "<leader>p", {
callback = function()
-- register keymap unload
load:cleanup()
-- call the keymap after the user has mapped it
if type(rerun) == "function" then
rerun()
elseif rerun then
local keys = vim.api.nvim_replace_termcodes(bind, true, false, true)
vim.api.nvim_input(keys)
end
end
})
<
Note the inclusion of a `rerun` field, this is a parameter which may be passed
into the options table to re-run the binding after loading the package. You
may choose to include your own logic by passing a function to the `rerun`
field or disable the built-in logic by passing false.
------------------------------------------------------------------------------
LOAD:PLUGIN *dep-lazy-loading-api-plugin*
`load:plugin` is a function which allows you to specify another plugin for the
package to load after. It takes two arguments: `plugin` which is the name of
the plugin you want to follow like: 'user/package'. The second argument is
`opts` which is a table with one option: `callback` which is a function. The
following is an example:
>lua
load:plugin("user/package", {})
<
Which is the same as:
>lua
load:plugin("user/package", {
callback = function()
self:cleanup()
end
})
<
When 'user/package' is already loaded the `callback` is called immediately.
LAZY LOADING API SHORTHANDS *dep-lazy-loading-api-shorthands*
On occasion you may wish to only define one condition for the package to load.
When that is the case you may choose to use the built-in shorthands. By
loading them:
>lua
require("dep.lazy.loader.short")
<
The shorthands are very similar to those found in |dep-lazy-loading-api| with
a key exception: instead of running the functions within the body of a
function set as the lazy field to a package specification this is the lazy
field and may be use like so:
>lua
{ "user/package",
lazy = require("dep.lazy.loader.short").cmd("Command")
}
<
And you may of course put the shorthands in a variable to make this actually
shorter:
>lua
local short = require("dep.lazy.loader.short")
{ "user/package",
lazy = short.cmd("Command")
}
<
==============================================================================
5. Commands *dep-commands*
------------------------------------------------------------------------------
SYNC ALL PLUGINS *:DepSync*
- installs new packages, updates packages to the latest versions,
cleans removed packages and reloads packages as necessary.
------------------------------------------------------------------------------
CLEAN REMOVED PLUGINS *:DepClean*
- cleans removed packages.
------------------------------------------------------------------------------
RELOAD ALL PLUGINS *:DepReload*
- reloads all packages.
------------------------------------------------------------------------------
OPEN THE UI *:DepUi*
- opens the ui.
------------------------------------------------------------------------------
OPEN THE LOG *:DepLog*
- opens the log file.
==============================================================================
6. Examples *dep-examples*
When a string is given where a package specification table is expected, it is
assumed to be the package's full name.
>lua
require("dep") {
-- these two are equivalent
"user/package",
{ "user/package" },
}
<
A package can be declared multiple times. Multiple declarations of the same
package are combined into one. This is useful when declaring dependencies,
which is explored later.
>lua
require("dep") {
{ "user/package",
reqs = "user/dependency",
disabled = true,
config = function()
print("my config hook")
end
},
{ "user/package",
requires = "user/another_dependency",
deps = "user/dependent",
disabled = false,
config = function()
os.execute("make")
end
}
}
-- the above is equivalent to
require("dep") {
{ "user/package",
reqs = { "user/dependency", "user/another_dependency" },
deps = "user/dependent",
disabled = true,
config = function()
print("my config hook")
os.execute("make")
end
}
}
<
DECLARING DEPENDENCIES *dep-examples-declaring-dependencies*
The dependencies and dependents declared in a package specification are
themselves package specifications. If a dependency or dependent is declared
multiple times, they are combined into one just like normal package
specifications.
>lua
require("dep") {
{ "user/package",
reqs = {
{ "user/dependency1",
reqs = "user/dependency2"
}
}
}
}
-- the above is equivalent to
require("dep") {
{ "user/dependency2",
deps = {
{ "user/dependency1",
deps = "user/package"
}
}
}
}
-- which is equivalent to
require("dep") {
{ "user/dependency1",
reqs = "user/dependency2",
deps = "user/package"
}
}
-- which is equivalent to
require("dep") {
{ "user/dependency1",
reqs = "user/dependency2"
},
{ "user/package",
reqs = "user/dependency1"
}
}
-- which is equivalent to
require("dep") {
{ "user/dependency2",
deps = "user/dependency1"
},
{ "user/dependency1",
deps = "user/package"
}
}
-- all of the above are guaranteed to load in the following order:
-- dependency2, dependency1, package
<
If dep detects a circular dependency cycle, it reports the problematic packages
instead of hanging or crashing.
>lua
-- this throws an error saying package1 depends on package2 which depends on
-- package1
require("dep") {
{ "user/package1",
reqs = "user/package2"
},
{ "user/package2",
reqs = "user/package1"
}
}
<
A dependency can be marked as disabled, which disables all dependents
automatically.
>lua
require("dep") {
{ "user/dependency",
disabled = true
},
{ "user/package1",
disabled = true, -- implied
reqs = "user/dependency"
},
{ "user/package2",
disabled = true, -- implied
reqs = "user/dependency"
}
}
<
If a dependency fails to load for some reason, all of its dependents are
guaranteed to not load.
>lua
require("dep") {
{ "user/problematic",
load = function()
error("bad hook")
end
},
{ "user/dependent",
requires = "user/problematic",
load = function()
print "unreachable"
end
}
}
<
MODULES *dep-examples-modules*
Suppose you split your `init.lua` into two files `packages/search.lua` and
`packages/vcs.lua`, which declare the packages telescope.nvim and vim-fugitive
respectively.
>lua
-- ~/.config/nvim/lua/packages/search.lua:
return {
{ "nvim-telescope/telescope.nvim",
reqs = "nvim-lua/plenary.nvim"
}
}
<
>lua
-- ~/.config/nvim/lua/packages/vcs.lua:
return { "tpope/vim-fugitive" }
<
Package specifications from other modules can be loaded using the `modules`
option.
>lua
require("dep") {
modules = {
prefix = "packages"
}
}
-- the above is equivalent to
require("dep") {
modules = {
prefix = "packages.",
"search",
"vcs"
}
}
-- or
require("dep") {
modules = {
"packages.search",
"packages.vcs"
}
}
-- which is equivalent to
local packages = {}
for _, package in ipairs(require "packages.search") do
table.insert(packages, package)
end
for _, package in ipairs(require "packages.vcs") do
table.insert(packages, package)
end
require("dep")(packages)
-- which is ultimately equivalent to
require("dep") {
{ "nvim-telescope/telescope.nvim",
reqs = "nvim-lua/plenary.nvim"
},
"tpope/vim-fugitive"
}
-- all of the above are guaranteed to load plenary.nvim before
-- telescope.nvim. order of telescope.nvim and vim-fugitive is consistent but
-- unspecified.
<
Entire modules can be marked as disabled, which disables all top-level packages
declared in that module.
>lua
return {
disable = true,
{ "user/package",
disabled = true, -- implied by module
reqs = {
{ "user/dependency",
-- disabled = true -- not implied
}
},
deps = {
{ "user/dependent",
disabled = true -- implied by dependency
}
}
}
}
<
LAZY LOADING *dep-examples-lazy-loading*
Lazy loading is a very complicated topic, and therefore this part of the
documentation assumes you have experience with regular package managment.
Let's go over loading order, and how the lazy loader determines what needs to
be loaded.
Let's say we have the following spec:
>lua
{ "user/package",
lazy = true,
deps = "user/dependent"
}
<
This is the same as the following:
>lua
{ "user/package",
lazy = true
},
{ "user/dependent",
reqs = "user/package",
lazy = require("dep.lazy.loader.short").plugin("user/package")
}
<
What you're seeing is implicit lazy loading. By default dep will lazy load
dependents who are explicitly defined in the spec. Now if we we're to modify
'user/dependent' like so:
>lua
{ "user/package",
lazy = true
},
{ "user/dependent",
reqs = "user/package",
lazy = function(load)
load:plugin("user/package")
load:cmd("LoadDependent")
end
}
<
If we were to call the command `:LoadDependent` it would first load
'user/package', and then load 'user/dependent'.
==============================================================================
7. Credits & License *dep-credits*
dep is licensed under the MIT License. Check the LICENSE file for more info.
vim:tw=78:ts=8:noet:ft=help:norl:

View File

@ -1,10 +1,11 @@
local logger = require('dep.log') local logger = require("dep.log")
local git = require('dep.git') local git = require("dep.git")
local fs = require('dep.fs') local fs = require("dep.fs")
local packager = require('dep.package') local packager = require("dep.package")
local h = require('dep.helpers')
local modules = require("dep.modules") local modules = require("dep.modules")
local bench = require("dep.bench") local bench = require("dep.bench")
local lazy = require("dep.lazy")
local ui = require("dep.ui")
-- all functions for convenience -- all functions for convenience
local M = {} local M = {}
@ -16,7 +17,7 @@ local M = {}
--- sync a tree of plugins --- sync a tree of plugins
---@param tree package[] tree of plugins ---@param tree package[] tree of plugins
---@param cb function? callback ---@param cb function? callback
function M.synctree(tree, cb) local function synctree(tree, cb)
local progress = 0 local progress = 0
local has_errors = false local has_errors = false
@ -56,12 +57,19 @@ function M.synctree(tree, cb)
end end
end end
-- basically the main function of our program --- check if a package should be synced
---@param opts speclist ---@param opts table options
return function(opts) ---@param package package package table spec
M.config_path = debug.getinfo(2, "S").source:sub(2) ---@return boolean sync
logger.pipe = logger:setup() local function shouldsync(opts, package)
bench:setup() if opts.sync == "never" then
return false
elseif opts.sync == "new" or opts.sync == nil then
return not package.exists
else
return opts.sync == "always"
end
end
--- make comparison for table.sort --- make comparison for table.sort
---@param a package package spec a ---@param a package package spec a
@ -74,6 +82,17 @@ return function(opts)
return a.id < b.id return a.id < b.id
end end
-- basically the main function of our program
---@param opts speclist
return function(opts)
M.config_path = debug.getinfo(2, "S").source:sub(2)
logger.pipe = logger:setup()
bench.setup()
lazy.setup()
-- generate doc tags
vim.cmd.helptags(vim.fn.stdpath('data')..'/site/pack/deps/opt/dep/doc')
local initialized, err = pcall(function() local initialized, err = pcall(function()
packager.set_base_dir(opts.base_dir or vim.fn.stdpath("data").."/site/pack/deps/opt/") packager.set_base_dir(opts.base_dir or vim.fn.stdpath("data").."/site/pack/deps/opt/")
bench.mark("load", function() bench.mark("load", function()
@ -81,8 +100,7 @@ return function(opts)
local root = packager:new({ local root = packager:new({
"squibid/dep", "squibid/dep",
url = "https://git.squi.bid/squibid/dep.git", url = "https://git.squi.bid/squibid/dep.git",
branch = "lazy", branch = "lazy"
pin = true
}) })
if not root then if not root then
logger:log("error", "couldn't register root package") logger:log("error", "couldn't register root package")
@ -113,27 +131,16 @@ return function(opts)
package:reload() package:reload()
end end
--- check if a package should be synced
---@param package table package table spec
---@return boolean sync
local function shouldsync(package)
if opts.sync == "new" or opts.sync == nil then
return not package.exists
else
return opts.sync == "always"
end
end
-- get all package that need syncing -- get all package that need syncing
local targets = {} local targets = {}
for _, package in pairs(packager.get_packages()) do for _, package in pairs(packager.get_packages()) do
if shouldsync(package) then if shouldsync(opts, package) then
table.insert(targets, package) table.insert(targets, package)
end end
end end
-- install all targets -- install all targets
M.synctree(targets) synctree(targets)
end) end)
if not initialized then if not initialized then
@ -141,27 +148,8 @@ return function(opts)
end end
-- add some user commands -- add some user commands
vim.api.nvim_create_user_command("DepLog", function()
vim.cmd('vsp '..logger.path)
vim.opt_local.readonly = true
-- make the log auto update while it's open
local w = h.uv.new_fs_event()
local function watch_file(fname)
local fullpath = vim.api.nvim_call_function(
'fnamemodify', { fname, ':p' })
w:start(fullpath, {}, vim.schedule_wrap(function(...)
vim.cmd('checktime')
w:stop()
watch_file(fname)
end))
end
watch_file(logger.path)
end, {})
vim.api.nvim_create_user_command("DepSync", function() vim.api.nvim_create_user_command("DepSync", function()
M.synctree(packager.get_packages()) synctree(packager.get_packages())
end, {}) end, {})
vim.api.nvim_create_user_command("DepReload", function() vim.api.nvim_create_user_command("DepReload", function()
@ -175,5 +163,15 @@ return function(opts)
fs:clean(packager) fs:clean(packager)
end, {}) end, {})
vim.api.nvim_create_user_command("DepUi", function()
ui.open(packager)
ui.set_page("P")
end, {})
vim.api.nvim_create_user_command("DepLog", function()
ui.open(packager)
ui.set_page("L")
end, {})
logger:cleanup() logger:cleanup()
end end

View File

@ -7,11 +7,8 @@
local bench = {} local bench = {}
local b local b
function bench:setup() function bench.setup()
local o = {} local o = {}
self = {}
self.__index = self
setmetatable(o, self)
o.perf = {} o.perf = {}
o.inited = true o.inited = true

37
lua/dep/lazy/init.lua Normal file
View File

@ -0,0 +1,37 @@
local h = require("dep.helpers")
local packager = require("dep.package")
---@class lazy
local lazy = {}
-- since this is already a ridiculous "optimization" we should really be caching
-- the results of this for when the user keeps on loading the colorscheme that
-- they've lazy loaded, that way we speed up the lazy loading process
local function colorscheme()
-- if a colorscheme doesn't exist attempt load it prior to it being set
vim.api.nvim_create_autocmd("ColorschemePre", {
pattern = vim.fn.getcompletion("", "color"),
callback = function(e)
for _, p in pairs(packager.get_packages()) do
if not p.loaded then
for _, ext in ipairs({ ".lua", ".vim" }) do
local path = p.dir.."/colors/"..e.match..ext
if h.uv.fs_stat(path) then
p:ensureadded(true)
-- break out of here, we've loaded the colorscheme
return
end
end
end
end
end
})
end
--- setup all lazy handlers
function lazy.setup()
-- start the colorscheme watcher
colorscheme()
end
return lazy

View File

@ -1,46 +1,67 @@
local logger = require('dep.log') local logger = require('dep.log')
local packager = require('dep.package')
---@class lazy ---@class lazy_loader
---@field load function the function to load the plugin ---@field load function the function to load the plugin
---@field command_ids table the commands that have been registered ---@field command_ids string[] the commands that have been registered
---@field auto_ids table the auto commands that have been registered ---@field auto_ids number[] the auto commands that have been registered
---@field keybind_ids table the keybinds that have been registered ---@field keybind_ids table[] the keybinds that have been registered
local lazy = {} ---@field plugin_ids table[] the plugins that have been registered
local lazy_loader = {}
--- create a new instance of lazy --- create a new instance of lazy
---@return lazy ---@return lazy
function lazy:new() function lazy_loader:new()
local o = {} local o = {}
setmetatable(o, self) setmetatable(o, self)
o.command_ids = {} o.command_ids = {}
o.auto_ids = {} o.auto_ids = {}
o.keybind_ids = {} o.keybind_ids = {}
o.plugin_ids = {}
self.__index = self self.__index = self
return o return o
end end
--- set the loading function --- set the loading callback
---@param load function the loading function ---@param load function the loader function
function lazy:set_load(load) function lazy_loader:set_load(load)
self.load = load self.load = load
end end
--- get the configured load function
---@return function load function
function lazy:get_load()
return self.load
end
--- create a usercommand which will trigger the plugin to load --- create a usercommand which will trigger the plugin to load
---@param name string the name of the command ---@param name string the name of the command
---@param opts vim.api.keyset.user_command? options ---@param opts vim.api.keyset.user_command? options
function lazy:cmd(name, opts) function lazy_loader:cmd(name, opts)
opts = opts or {} opts = opts or {}
vim.api.nvim_create_user_command(name, opts['callback'] or function()
-- move the rerun arg to a seperate variable because keymap.set doesn't like
-- options it doesn't know of
local rerun = opts["rerun"] or true
opts['rerun'] = nil
-- load the plugin on completion
if not opts["complete"] then
opts["complete"] = function(_, line, _)
self:cleanup() self:cleanup()
-- return all completions for the current input, we need this to ensure
-- that the new completions are loaded from the actual plugin, not our
-- definiton of the command
return vim.fn.getcompletion(line, "cmdline")
end
opts["nargs"] = "*"
end
vim.api.nvim_create_user_command(name, opts['callback'] or function(_)
self:cleanup()
-- attempt to rerun the command
if not rerun then
pcall(vim.cmd, name)
end
end, opts) end, opts)
table.insert(self.command_ids, name) table.insert(self.command_ids, name)
@ -49,7 +70,7 @@ end
--- create an auto command which will trigger the plugin to load --- create an auto command which will trigger the plugin to load
---@param event string the event to trigger on ---@param event string the event to trigger on
---@param opts vim.api.keyset.create_autocmd? options ---@param opts vim.api.keyset.create_autocmd? options
function lazy:auto(event, opts) function lazy_loader:auto(event, opts)
opts = opts or {} opts = opts or {}
opts['callback'] = opts['callback'] or function() opts['callback'] = opts['callback'] or function()
@ -62,17 +83,20 @@ end
--- create an auto command which will trigger on filetype --- create an auto command which will trigger on filetype
---@param filetype string filetype to register the auto on ---@param filetype string filetype to register the auto on
function lazy:ft(filetype) function lazy_loader:ft(filetype)
lazy:auto("FileType", { self:auto("FileType", {
pattern = filetype pattern = filetype
}) })
end end
---@class lazy.Opts: vim.keymap.set.Opts
---@field rerun boolean|function weather to rerun and what to do
--- create a keybind which will trigger the plugin to load --- create a keybind which will trigger the plugin to load
---@param mode string the mode to trigger in ---@param mode string the mode to trigger in
---@param bind string the binding to use ---@param bind string the binding to use
---@param opts vim.keymap.set.Opts? options ---@param opts lazy.Opts? options
function lazy:keymap(mode, bind, opts) function lazy_loader:keymap(mode, bind, opts)
opts = opts or {} opts = opts or {}
-- move the rerun arg to a seperate variable because keymap.set doesn't like -- move the rerun arg to a seperate variable because keymap.set doesn't like
@ -85,7 +109,9 @@ function lazy:keymap(mode, bind, opts)
self:cleanup() self:cleanup()
-- call the keymap after the user has mapped it -- call the keymap after the user has mapped it
if rerun then if type(rerun) == "function" then
rerun()
elseif rerun then
local keys = vim.api.nvim_replace_termcodes(bind, true, false, true) local keys = vim.api.nvim_replace_termcodes(bind, true, false, true)
vim.api.nvim_input(keys) vim.api.nvim_input(keys)
end end
@ -94,31 +120,55 @@ function lazy:keymap(mode, bind, opts)
table.insert(self.keybind_ids, { ['mode'] = mode, ['bind'] = bind }) table.insert(self.keybind_ids, { ['mode'] = mode, ['bind'] = bind })
end end
--- load a plugin when another plugin loads
---@param plugin string plugin name
---@param opts table? options
function lazy_loader:plugin(plugin, opts)
opts = opts or {}
opts["callback"] = opts["callback"] or function()
self:cleanup()
end
if packager.get_packages()[plugin].loaded then
opts["callback"]()
else
local on_load = packager.get_packages()[plugin].on_load
local on_load_idx = #on_load + 1
on_load[on_load_idx] = opts["callback"]
table.insert(self.plugin_ids, { plugin, on_load_idx })
end
end
--- cleanup all the callbacks, and load the plugin --- cleanup all the callbacks, and load the plugin
function lazy:cleanup() function lazy_loader:cleanup()
-- cleanup user commands -- cleanup user commands
for _, command_id in pairs(self.command_ids) do for _, command_id in ipairs(self.command_ids) do
local ok, err = pcall(vim.api.nvim_del_user_command, command_id) local ok, err = pcall(vim.api.nvim_del_user_command, command_id)
if not ok then if not ok then
logger:log("lazy", err or "failed to delete user command") logger:log("lazy", err or "failed to delete user command")
end end
end end
-- cleanup auto commands -- cleanup auto commands
for _, auto_id in pairs(self.auto_ids) do for _, auto_id in ipairs(self.auto_ids) do
local ok, err = pcall(vim.api.nvim_del_autocmd, auto_id) local ok, err = pcall(vim.api.nvim_del_autocmd, auto_id)
if not ok then if not ok then
logger:log("lazy", err or "failed to delete auto command") logger:log("lazy", err or "failed to delete auto command")
end end
end end
-- cleanup keymaps -- cleanup keymaps
for _, keybind_id in pairs(self.keybind_ids) do for _, keybind_id in ipairs(self.keybind_ids) do
local ok, err = pcall(vim.keymap.del, keybind_id.mode, keybind_id.bind, {}) local ok, err = pcall(vim.keymap.del, keybind_id.mode, keybind_id.bind, {})
if not ok then if not ok then
logger:log("lazy", err or "failed to delete keymap") logger:log("lazy", err or "failed to delete keymap")
end end
end end
-- cleanup plugins
for _, plugin_id in ipairs(self.plugin_ids) do
table.remove(packager.get_packages()[plugin_id[1]].on_load, plugin_id[2])
end
-- load the plugin -- load the plugin
self:load() self:load()
end end
return lazy return lazy_loader

View File

@ -0,0 +1,72 @@
-- This file contains shorthands which rely on the loader core functions. They
-- are intended to ease lazy loading condition definitions to use them you may
-- do the following:
--
-- ```lua
-- _G.dep_short = require("dep.lazy.loader.short")
-- ```
--
-- Which will allow you to reference it anywhere in your config like so:
--
-- ```lua
-- require("dep") {
-- { "some/plugin",
-- lazy = dep_short.cmd("TheCommand")
-- }
-- }
-- ```
--
-- Happy vimming o/
local short = {}
--- create a single command
---@param name string the name of the command
---@param opts vim.api.keyset.user_command? options
---@return function callback
function short.cmd(name, opts)
return function(load)
load:cmd(name, opts)
end
end
--- create a single auto command
---@param event string the event to trigger on
---@param opts vim.api.keyset.create_autocmd? options
---@return function callback
function short.auto(event, opts)
return function(load)
load:auto(event, opts)
end
end
--- create a single auto command which will trigger on filetype
---@param filetype string filetype to register the auto on
---@return function callback
function short.ft(filetype)
return function(load)
load:ft(filetype)
end
end
--- create a single keybind
---@param mode string the mode to trigger in
---@param bind string the binding to use
---@param opts lazy.Opts? options
---@return function callback
function short.keymap(mode, bind, opts)
return function(load)
load:keymap(mode, bind, opts)
end
end
--- create a single plugin load event for when another plugin loads
---@param plugin string plugin name
---@param opts table? options
---@return function callback
function short.plugin(plugin, opts)
return function(load)
load:plugin(plugin, opts)
end
end
return short

View File

@ -1,43 +0,0 @@
--- shorthands for when you only need to define one callback
local short = {}
--- create a usercommand which will trigger the plugin to load
---@param name string the name of the command
---@param opts vim.api.keyset.user_command? options
---@return function callback lazy setup
function short:cmd(name, opts)
return function(load)
load:cmd(name, opts)
end
end
--- create an auto command which will trigger the plugin to load
---@param event string the event to trigger on
---@param opts vim.api.keyset.create_autocmd? options
---@return function callback lazy setup
function short:auto(event, opts)
return function(load)
load:auto(event, opts)
end
end
--- create an auto command which will trigger on filetype
---@param filetype string filetype to register the auto on
function short:ft(filetype)
return function(load)
load:ft(filetype)
end
end
--- create a keybind which will trigger the plugin to load
---@param mode string the mode to trigger in
---@param bind string the binding to use
---@param opts vim.keymap.set.Opts? options
---@return function callback lazy setup
function short:keymap(mode, bind, opts)
return function(load)
load:keymap(mode, bind, opts)
end
end
return short

View File

@ -79,8 +79,8 @@ function logger:log(level, message, ...)
-- write to the pipe if it's open -- write to the pipe if it's open
if logger.pipe then if logger.pipe then
logger.pipe:write(string.format("[%s] %s:%s: %s\n", os.date("%T"), logger.pipe:write(string.format("[%s] %s:%s:(%s) %s\n", os.date("%T"),
source.short_src:gsub('.*%/', ''), source.currentline, message)) source.short_src:gsub('.*%/', ''), source.currentline, level, message))
end end
end) end)
end end

View File

@ -30,10 +30,7 @@ function modules:setup(speclist, overrides, config_path)
"lua", (speclist.modules.prefix:gsub("%.", "/")) "lua", (speclist.modules.prefix:gsub("%.", "/"))
) )
h.uv.fs_scandir(path, function(err, handle) local handle = h.uv.fs_scandir(path)
if err then
logger:log("error", "failed to load modules; reason: %s", err)
else
while handle do while handle do
local name = h.uv.fs_scandir_next(handle) local name = h.uv.fs_scandir_next(handle)
if name then if name then
@ -46,13 +43,9 @@ function modules:setup(speclist, overrides, config_path)
-- when attempting to load it -- when attempting to load it
name = name:sub(0, #name - 4) name = name:sub(0, #name - 4)
-- attempt to load the module -- put the module into the list of modules
local mod = module.new(nil, name, speclist.modules.prefix, overrides) table.insert(speclist.modules, name)
if not mod then
goto continue
end
table.insert(o.modules, mod)
::continue:: ::continue::
elseif name == nil then elseif name == nil then
-- no more entries -- no more entries
@ -60,12 +53,11 @@ function modules:setup(speclist, overrides, config_path)
else else
-- if there's a single error bail out -- if there's a single error bail out
logger:log("error", "failed to run clean uv.fs_scandir_next failed") logger:log("error", "failed to run clean uv.fs_scandir_next failed")
return break
end end
end end
end end
end)
else
-- loop through all modules and initialize them -- loop through all modules and initialize them
for _, modpath in ipairs(speclist.modules) do for _, modpath in ipairs(speclist.modules) do
local mod = module.new(nil, modpath, speclist.modules.prefix, overrides) local mod = module.new(nil, modpath, speclist.modules.prefix, overrides)
@ -76,7 +68,6 @@ function modules:setup(speclist, overrides, config_path)
table.insert(o.modules, mod) table.insert(o.modules, mod)
::continue:: ::continue::
end end
end
return self return self
end end

View File

@ -5,6 +5,7 @@ local packager = require("dep.package")
---@class module ---@class module
---@field name string name of the module ---@field name string name of the module
---@field desc string description of the module ---@field desc string description of the module
---@field disable boolean weather to disable all the packages inside the module
---@field path string path to the module ---@field path string path to the module
---@field mod table the module ---@field mod table the module
---@field packages package[] all packages registed from the module ---@field packages package[] all packages registed from the module
@ -18,6 +19,8 @@ local module = {}
---@return module|false module false on failure to load module ---@return module|false module false on failure to load module
---@nodiscard ---@nodiscard
function module:new(modpath, prefix, overrides) function module:new(modpath, prefix, overrides)
overrides = overrides or {}
local ok, err local ok, err
local o = {} local o = {}
self = {} self = {}
@ -45,6 +48,11 @@ function module:new(modpath, prefix, overrides)
o.name = o.mod.name or o.name o.name = o.mod.name or o.name
o.desc = o.mod.desc or o.desc o.desc = o.mod.desc or o.desc
-- ensure the overrides are properly set
overrides = vim.tbl_extend("force", overrides, {
disable = o.mod.disable or overrides.disable
})
-- allow a module to be a spec -- allow a module to be a spec
if spec_man.check(o.mod, true) ~= false then if spec_man.check(o.mod, true) ~= false then
o.mod = { o.mod } o.mod = { o.mod }

View File

@ -9,13 +9,12 @@ local bench = require("dep.bench")
---@field lazy boolean if the package is lazy loaded in any way ---@field lazy boolean if the package is lazy loaded in any way
---@field added boolean if the package has been added in vim ---@field added boolean if the package has been added in vim
---@field configured boolean if the package has been configured ---@field configured boolean if the package has been configured
---@field lazied boolean if the packages lazy loading has been set
---@field loaded boolean if a package has been loaded ---@field loaded boolean if a package has been loaded
---@field subtree_loaded boolean is the subtree has been loaded ---@field subtree_loaded boolean is the subtree has been loaded
---@field on_config function[] table of functions to run on config ---@field on_config function[] table of functions to run on config
---@field on_setup function[] table of function to run on setup ---@field on_setup function[] table of function to run on setup
---@field on_load function[] table of functions to run on load ---@field on_load function[] table of functions to run on load
---@field lazy_load function[] table of functions to run which will tell the package when to load ---@field lazy_load table table of functions and booleans to run which will tell the package when to load
---@field requirements package[] this package's requirements ---@field requirements package[] this package's requirements
---@field dependents package[] packages that require this package ---@field dependents package[] packages that require this package
---@field perf table performance metrics for the package ---@field perf table performance metrics for the package
@ -89,7 +88,6 @@ function package:new(spec, overrides)
o.exists = false o.exists = false
o.lazy = false o.lazy = false
o.added = false o.added = false
o.lazied = false
o.configured = false o.configured = false
o.loaded = false o.loaded = false
o.subtree_loaded = false o.subtree_loaded = false
@ -167,18 +165,10 @@ function package:new(spec, overrides)
-- if the child package is lazy loaded make sure the child package -- if the child package is lazy loaded make sure the child package
-- is only loaded when the parent package has finished loading -- is only loaded when the parent package has finished loading
if o.lazy then if o.lazy then
table.insert(o.on_load, function()
local ok = o:loadtree(true)
if not ok then
logger:log("lazy",
"failed to run loadtree for %s, some packages may not be loaded",
o.id)
end
end)
-- tell the dep that it's gonna be lazy -- tell the dep that it's gonna be lazy
pkg.lazy = true pkg.lazy = true
table.insert(pkg.lazy_load, function(_) end) table.insert(pkg.lazy_load,
require("dep.lazy.loader.short").plugin(id))
end end
end end
end end
@ -254,6 +244,10 @@ function package:ensureadded(force)
--- load a package --- load a package
---@param pkg package ---@param pkg package
local function loadpkg(pkg) local function loadpkg(pkg)
if not self.enabled then
return false
end
-- make sure to load the dependencies first -- make sure to load the dependencies first
for _, p in pairs(pkg.requirements) do for _, p in pairs(pkg.requirements) do
if not p.loaded then if not p.loaded then
@ -296,10 +290,9 @@ function package:ensureadded(force)
elseif not self.added and self.lazy then elseif not self.added and self.lazy then
logger:log("lazy", "registering %d lazy hooks for %s", #self.lazy_load, logger:log("lazy", "registering %d lazy hooks for %s", #self.lazy_load,
self.id) self.id)
self.lazied = true
for _, load_cond in pairs(self.lazy_load) do for _, load_cond in pairs(self.lazy_load) do
-- configure the lazy loader for the user -- configure the lazy loader for the user
local l = require('dep.lazy.utils'):new() local l = require('dep.lazy.loader'):new()
if l == true then if l == true then
logger:log("lazy", "failed to get lazy utils") logger:log("lazy", "failed to get lazy utils")
return false return false
@ -315,8 +308,14 @@ function package:ensureadded(force)
loadpkg(self) loadpkg(self)
end) end)
-- run it -- run it if it's not just a stopper to keep the plugin lazy
load_cond(l) if load_cond ~= true then
local ok, err = pcall(load_cond, l)
if not ok then
logger:log("error", "failed to register lazy load conditions for '%s': %s",
self.name, err)
end
end
end end
return self return self
end end

View File

@ -15,7 +15,7 @@ local logger = require("dep.log")
---@field setup function? code to run before the package is loaded ---@field setup function? code to run before the package is loaded
---@field load function? code to run after the package is loaded ---@field load function? code to run after the package is loaded
---@field config function? code to run after the package is installed/updated ---@field config function? code to run after the package is installed/updated
---@field lazy function? code to run which determines when the package is loaded ---@field lazy function|true? code to run which determines when the package is loaded
---@field as string? overrides the short name of the package which is usually set ---@field as string? overrides the short name of the package which is usually set
--- to a substring of all the chars after "/" in spec[1] --- to a substring of all the chars after "/" in spec[1]
---@field url string? the url to the git repository hosting the package ---@field url string? the url to the git repository hosting the package
@ -114,8 +114,9 @@ function spec:check(silent)
end end
if self.lazy ~= nil then -- spec.lazy if self.lazy ~= nil then -- spec.lazy
if type(self.lazy) ~= "function" then if type(self.lazy) ~= "function" and self.lazy ~= true then
logger:log("spec", "spec.lazy must be a function in %s", self[1]) logger:log("spec", "spec.lazy must be a function or boolean in %s",
self[1])
return false return false
end end
end end

59
lua/dep/ui/format.lua Normal file
View File

@ -0,0 +1,59 @@
local logger = require("dep.log")
local format = {}
--- format a boolean to a chunk with highlights
---@param b boolean
---@return chunk chunk
function format.bool(b)
return { vim.inspect(b), b and "DiffAdd" or "DiffDelete" }
end
--- format a number to a chunk with highlights
---@param n number
---@return chunk chunk
function format.number(n)
return { vim.inspect(n), "Number" }
end
--- format a log line with highlights
---@param log_line string log line
---@return chunk[] chunks
function format.log_line(log_line)
-- make sure we don't do operations on nil values
if not log_line or log_line == "" then
return {}
end
-- error on any nil values, this should prevent us from parsing an incorrectly
-- formatted log line
local log_time, colon, log_path, log_path_ln, level, rest
local ok = pcall(function()
log_time = string.sub(log_line, string.find(log_line, "%[") + 1,
string.find(log_line, "%]") - 1)
colon = string.find(log_line, ":", 11)
log_path = string.sub(log_line, string.find(log_line, "%]") + 2,
colon - 1)
log_path_ln = string.sub(log_line, colon + 1,
string.find(log_line, ":", colon + 1) - 1)
level = string.sub(log_line, string.find(log_line, "%(") + 1,
string.find(log_line, "%)") - 1)
rest = string.sub(log_line, string.find(log_line, "%)") + 2)
end)
if not ok then
return {}
end
return {
{ "[", "" },
{ log_time, "Boolean" },
{ "] ", "" },
{ log_path, "String" },
{ ":", "" },
{ log_path_ln, "Number" },
{ ": ", "" },
{ rest, logger.stage_colors[level] or "" }
}
end
return format

207
lua/dep/ui/init.lua Normal file
View File

@ -0,0 +1,207 @@
local h = require("dep.helpers")
local page = require("dep.ui.page")
local logger = require("dep.log")
local format = require("dep.ui.format")
---@class ui
---@field bufnr number
---@field winnr number
---@field header_bufnr number
---@field header_winnr number
---@field pages page[]
local ui = {}
-- the active page being displayed
local active_page
-- all the pages
local pages = {}
-- the header ext mark
local header_ext_id
local function page_packages(packager)
local p = page:new("Packages", "P")
for _, pkg in pairs(packager.get_packages()) do
p:new_line({
{ pkg.id, "@conditional" },
{ " loaded: ", "" },
format.bool(pkg.loaded),
{ " lazy: ", "" },
format.bool(pkg.lazy)
})
end
return p
end
local function page_log()
local p = page:new("Log", "L")
local f = io.open(logger.path, "r")
if not f then
return
end
-- put the cursor at the bottom of the page after drawing
p.post_draw = function()
if ui.winnr then
vim.api.nvim_win_set_cursor(ui.winnr, { #p.content, 0 })
end
end
-- read in the contents of the file, and keep watching for updates
local function update_contents()
repeat
local line = f:read("*l")
-- if the line isn't empty we shouldn't draw it
if line then
p:new_line(format.log_line(line))
end
until not line
end
update_contents()
local fullpath = vim.api.nvim_call_function(
"fnamemodify", { logger.path, ":p" })
h.uv.new_fs_event():start(fullpath, {}, vim.schedule_wrap(function()
update_contents()
-- if the log is currently being displayed then make sure to draw it when
-- it updates
if active_page == p then
p:draw(ui.bufnr)
end
end))
return p
end
--- set the current page
---@param p string|page page to set
function ui.set_page(p)
if type(p) == "string" then
for _, v in ipairs(pages) do
if p == v.kb then
v:draw(ui.bufnr)
active_page = v
break
end
end
elseif type(p) == "table" then
p:draw(ui.bufnr)
active_page = p
end
-- color the header text
local txt = vim.api.nvim_buf_get_text(ui.header_bufnr, 0, 0, -1, -1, {})[1]
local start_range = (string.find(txt, active_page.name)) - 2
local end_range = #active_page.name + start_range + 2
if header_ext_id then
vim.api.nvim_buf_del_extmark(ui.header_bufnr, active_page.hlns, header_ext_id)
end
header_ext_id = vim.api.nvim_buf_set_extmark(ui.header_bufnr,
active_page.hlns, 0, start_range, {
hl_mode = "replace",
hl_group = "CurSearch",
end_col = end_range,
})
end
local setup
--- setup all the pages
---@param packager package the packager
local function setup_pages(packager)
if setup then
return
end
local header_text = ""
table.insert(pages, page_packages(packager))
table.insert(pages, page_log())
for _, v in ipairs(pages) do
header_text = header_text.." "..v.name.." "
vim.keymap.set("n", v.kb, function()
ui.set_page(v)
end, { buffer = ui.bufnr })
end
-- set the header text
vim.api.nvim_buf_set_lines(ui.header_bufnr, 0, -1, false, { header_text })
-- add keymaps
vim.keymap.set("n", "q", function()
vim.api.nvim_win_close(ui.winnr, false)
ui.winnr = nil
end, { buffer = ui.bufnr })
setup = true
end
--- setup the ui
---@param packager package
function ui.open(packager)
if not ui.bufnr then
ui.bufnr = vim.api.nvim_create_buf(false, true)
end
if not ui.header_bufnr then
ui.header_bufnr = vim.api.nvim_create_buf(false, true)
end
local header_height = 1
local width = math.floor(vim.o.columns * 0.8)
local height = math.floor(vim.o.lines * 0.8) - header_height
if not ui.winnr then
ui.winnr = vim.api.nvim_open_win(ui.bufnr, true, {
relative = "editor",
row = (vim.o.lines - height) / 2,
col = (vim.o.columns - width) / 2,
width = width,
height = height,
border = "solid",
zindex = 998,
})
end
if not ui.header_winnr then
ui.header_winnr = vim.api.nvim_open_win(ui.header_bufnr, false, {
relative = "editor",
row = ((vim.o.lines - height) / 2) - (header_height * 2),
col = (vim.o.columns - width) / 2,
width = width,
height = header_height,
border = "solid",
zindex = 999,
focusable = false
})
end
vim.api.nvim_win_set_buf(ui.winnr, ui.bufnr)
vim.api.nvim_win_set_buf(ui.header_winnr, ui.header_bufnr)
-- make sure the header closes when the body does and vice versa
local function cb()
vim.api.nvim_win_close(ui.header_winnr, false)
ui.header_winnr = nil
vim.api.nvim_win_close(ui.winnr, false)
ui.winnr = nil
end
vim.api.nvim_create_autocmd("WinClosed", {
pattern = ui.winnr.."",
callback = cb
})
vim.api.nvim_create_autocmd("WinClosed", {
pattern = ui.header_winnr.."",
callback = cb
})
setup_pages(packager)
end
return ui

84
lua/dep/ui/page.lua Normal file
View File

@ -0,0 +1,84 @@
---@class chunk: table
---@field [1] string text to be displayed
---@field [2] string neovim highlight group to use
---@class page
---@field name string name of the ui page
---@field kb string keybind of the page
---@field content chunk[]|chunk[][] all the chunks
---@field hlns number highlight namespace
---@field pre_draw function things to do prior to drawing to the buffer
---@field post_draw function things to do post drawing to the buffer
local page = {}
--- create a new page
---@param name string the name of the page
---@param kb string keybind to change to the page
---@return page page
function page:new(name, kb)
local o = {}
self.__index = self
setmetatable(o, self)
o.hlns = vim.api.nvim_create_namespace("DepUi")
o.name = name
o.kb = kb
o.content = {}
return o
end
--- add a new line to the page
---@param line chunk|chunk[] new line
function page:new_line(line)
table.insert(self.content, line)
end
--- draw the page to the given buffer
---@param bufnr number buffer number
function page:draw(bufnr)
-- try to run pre_draw steps
if self.pre_draw then
self.pre_draw()
end
-- ready all information for rendering
for i, chunk in ipairs(self.content) do
local linenr = i - 1
local text = ""
local hls = {}
if type(chunk[1]) == "table" then
local j = 0
for _, ch in ipairs(chunk) do
text = text..ch[1]
table.insert(hls, { ch[2], j, j + #ch[1] })
j = j + #ch[1]
end
elseif type(chunk[1]) == "string" then
text = chunk[1]
table.insert(hls, { chunk[2], 0, #text })
end
-- draw the text to the buffer
vim.api.nvim_buf_set_lines(bufnr, linenr, -1, false, { text })
-- highlight the buffer
for _, hl in ipairs(hls) do
vim.api.nvim_buf_set_extmark(bufnr, self.hlns, linenr, hl[2], {
hl_mode = "replace",
hl_group = hl[1],
end_col = hl[3],
end_row = linenr
})
end
end
-- try to run post_draw steps
if self.post_draw then
self.post_draw()
end
end
return page

63
tests/dep_spec.lua Normal file
View File

@ -0,0 +1,63 @@
---@diagnostic disable: undefined-global, undefined-field
local dep_ui_format = require("dep.ui.format")
local dep_spec_man = require("dep.spec")
describe("ui log formatting", function()
it("returns the proper chunks to print a formatted line", function()
assert.same(
{
{ "[", "" },
{ "11:22:33", "Boolean" },
{ "] ", "" },
{ "file.lua", "String" },
{ ":", "" },
{ "1", "Number" },
{ ": ", "" },
{ "some fancy message", "" }
},
dep_ui_format.log_line("[11:22:33] file.lua:1:(vim) some fancy message")
)
-- malformed log line
assert.same({},
dep_ui_format.log_line("11:22:33] file.lua:1:(vim) some fancy message"))
-- test nil values
assert.same({}, dep_ui_format.log_line(""))
assert.same({}, dep_ui_format.log_line(nil))
end)
end)
describe("package specification", function()
it("gets the package's name", function()
assert.equal(dep_spec_man.get_name({ "user/package" }), "package")
assert.equal(dep_spec_man.get_name({ "user/package.git" }), "package.git")
end)
it("ensurses specs are in the proper format", function()
local correct = { "user/package" }
assert.same(dep_spec_man.correct_spec("user/package"), correct)
assert.same(dep_spec_man.correct_spec({ "user/package" }), correct)
assert.same(dep_spec_man.correct_spec({ { "user/package" } }), correct)
end)
it("checks a spec for correctness", function()
assert.same(
dep_spec_man.check({ "user/package" }, true),
{ "user/package" }
)
assert.same(
dep_spec_man.check({
"user/package",
deps = "user/dependency"
}, true),
{
"user/package",
deps = {
"user/dependency"
}
}
)
end)
end)

1
tests/minit.lua Normal file
View File

@ -0,0 +1 @@
vim.opt.rtp:prepend(".")