├── .envrc
├── .gitignore
├── LICENSE
├── README.md
├── build.zig
├── config
├── init.lua
├── lua
│ ├── autocmds.lua
│ ├── keymaps.lua
│ ├── options.lua
│ ├── plugins
│ │ ├── autoclose.lua
│ │ ├── catppuccin.lua
│ │ ├── comments.lua
│ │ ├── completions.lua
│ │ ├── conform.lua
│ │ ├── gitsigns.lua
│ │ ├── lsp.lua
│ │ ├── lualine.lua
│ │ ├── markdown.lua
│ │ ├── neotree.lua
│ │ ├── none-ls.lua
│ │ ├── sleuth.lua
│ │ ├── telescope.lua
│ │ ├── tex.lua
│ │ ├── todo.lua
│ │ ├── treesitter.lua
│ │ ├── trouble.lua
│ │ └── undotree.lua
│ └── utils.lua
└── stylua.toml
├── flake.lock
├── flake.nix
├── nix
├── builders
│ ├── default.nix
│ ├── neovimWrapper.nix
│ ├── nixPatchBaseBuilder.nix
│ ├── patcherBuilder.nix
│ ├── providerBuilder.nix
│ ├── rtpBuilder.nix
│ ├── ruby_provider
│ │ ├── Gemfile
│ │ ├── Gemfile.lock
│ │ └── gemset.nix
│ └── zigBuilder.nix
└── utils
│ └── default.nix
├── patchUtils.nix
├── patcher
├── src
│ ├── lib
│ │ ├── LuaIter.zig
│ │ ├── LuaParser.zig
│ │ ├── nixpkgs_parser.zig
│ │ ├── root.zig
│ │ ├── types.zig
│ │ └── utils.zig
│ └── main.zig
└── test
│ ├── luaparser-integration
│ ├── expected
│ │ ├── deep
│ │ │ └── nested
│ │ │ │ └── file
│ │ │ │ └── found_me.lua
│ │ ├── init.lua
│ │ ├── other_ft.toml
│ │ ├── plugin.lua
│ │ ├── raw_plugin.lua
│ │ ├── simple_replacements.lua
│ │ └── symlink.lua
│ ├── input
│ │ ├── deep
│ │ │ └── nested
│ │ │ │ └── file
│ │ │ │ └── found_me.lua
│ │ ├── init.lua
│ │ ├── other_ft.toml
│ │ ├── plugin.lua
│ │ ├── raw_plugin.lua
│ │ ├── simple_replacements.lua
│ │ └── symlink.lua
│ └── root.zig
│ └── root.zig
├── scripts
├── README.md
└── build-commands.zig
├── subPatches.nix
└── templates
├── configV1
└── flake.nix
├── configV2
└── flake.nix
├── default
└── default.nix
/.envrc:
--------------------------------------------------------------------------------
1 | use flake
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Don't commit direnv cache
2 | .direnv
3 |
4 | # Don't commit nix build output
5 | result
6 |
7 | # Don't commit zig caches
8 | **/.zig-cache/**
9 | **/zig-out/**
10 | patcher/test/luaparser-integration/out
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Nico Elbers
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # nixPatch: keep your lazy.nvim config in Lua
2 |
3 |
4 |
5 | - [nixPatch: keep your lazy.nvim config in Lua](#nixPatch-keep-your-lazynvim-config-in-lua)
6 | - [Why](#why)
7 | - [Quick setup](#quick-setup)
8 | - [Installation, the long version](#installation-the-long-version)
9 | - [Setting up your config](#setting-up-your-config)
10 | - [Utilities](#utilities)
11 | - [Loading lazy.nvim](#loading-lazynvim)
12 | - [Dealing with mason and the like](#dealing-with-mason-and-the-like)
13 | - [Setting up the nix part](#setting-up-the-nix-part)
14 | - [Goals](#goals)
15 | - [Limitations](#limitations)
16 | - [Roadmap](#roadmap)
17 | - [How it works](#how-it-works) - [Patching your config](#patching-your-config) - [Zig](#zig)
18 |
19 |
20 | `nixPatch` is a wrapper around Neovim that makes your Lua configuration nix
21 | compatible. It makes the barrier to a working Neovim configuration on nix as
22 | small as possible for existing Neovim users.
23 |
24 | As the creator of [nixCats](https://github.com/BirdeeHub/nixCats-nvim) aptly
25 | said, nix is for downloading and Lua is for configuring. I am taking that idea
26 | further by transforming your configuration to a nix compatible one at build
27 | time. Inside Lua, you have 0 extra dependencies, and as few changes as
28 | possible.
29 |
30 | `nixPatch` provides a flake template that you can put inside your Neovim
31 | configuration. You have to make a couple minor adjustments to your
32 | configuration and specify what dependencies your configuration has and
33 | `nixPatch` will do the rest.
34 |
35 | ## Why
36 |
37 | For fun.
38 |
39 | Besides that, when I originally was looking into nix, my Neovim configuration
40 | was my big blocker. I had spent quite a bit of time on it and I really didn't
41 | want to rewrite it in nix, especially since that'd mean I couldn't use it as a
42 | normal configuration anymore. Then later I found nixCats, which is a great
43 | project, and that helped me to go to nix.
44 |
45 | Somewhere along the line, I had some problems with nixCats (completely my own
46 | fault), and somewhere around midnight I had a fun idea to just parse my
47 | configuration and patch in the plugin directories. A frankly insane 18 hours of
48 | programming later, I had some base concepts that worked, and I decided to go
49 | for it!
50 |
51 | ## Quick setup
52 |
53 | Change loading lazy to:
54 |
55 | ```lua
56 | local set = function(nonNix, nix)
57 | if vim.g.nix == true then
58 | return nix
59 | else
60 | return nonNix
61 | end
62 | end
63 |
64 | -- Bootstrap lazy.nvim
65 | local load_lazy = set(function()
66 | local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
67 | if not (vim.uv or vim.loop).fs_stat(lazypath) then
68 | local lazyrepo = "https://github.com/folke/lazy.nvim.git"
69 | local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath })
70 | if vim.v.shell_error ~= 0 then
71 | vim.api.nvim_echo({
72 | { "Failed to clone lazy.nvim:\n", "ErrorMsg" },
73 | { out, "WarningMsg" },
74 | { "\nPress any key to exit..." },
75 | }, true, {})
76 | vim.fn.getchar()
77 | os.exit(1)
78 | end
79 | end
80 | vim.opt.rtp:prepend(lazypath)
81 | end, function()
82 | -- Prepend the runtime path with the directory of lazy
83 | -- This means we can call `require("lazy")`
84 | vim.opt.rtp:prepend([[lazy.nvim-plugin-path]])
85 | end)
86 |
87 | -- Actually execute the loading function we set above
88 | load_lazy()
89 |
90 | -- Disable resetting the RTP, so that you actually see our new one
91 | require("lazy").setup("plugins", { performance = { rtp = { reset = set(true, false) } } })
92 | ```
93 |
94 | Disable plugins like `Mason` so they don't download things on nix.
95 |
96 | Clone the flake using `nix flake init -t github:NicoElbers/nixPatch-nvim`.
97 |
98 | Set `luaPath` to the directory which contains your `init.lua`.
99 |
100 | Use [nixos search](https://search.nixos.org/packages?channel=unstable&from=-1&size=50&sort=relevance&type=packages&query=vimPlugins) to find all the plugins you're using and import them in `plugins` in the flake.
101 |
102 | Use [nixos search](https://search.nixos.org/packages?channel=unstable&from=-1&size=50&sort=relevance&type=packages) again to find all runtime dependencies (tree-sitter, lsp) and put them in `runtimeDeps`.
103 |
104 | For more detailed information, look at the section below.
105 |
106 | ## Installation, the long version
107 |
108 | ### Setting up your config
109 |
110 | `nixPatch` does its best to work with existing lazy.nvim configurations, but
111 | it's not perfect. The setup you need to however, is minimal. There are 2 main
112 | limitations as of now:
113 |
114 | 1. Plugins that install files, like mason, don't play nice with nix.
115 | 2. lazy.nvim isn't loaded by lazy.nvim, so we need a special way to be able to
116 | load it correctly.
117 |
118 | #### Utilities
119 |
120 | For the other 2 limitations you do need to make some changes to your
121 | configuration, luckily you still don't have to change a thing when you're not
122 | on nix!
123 |
124 | The trick to this is very simple. `nixPatch` does very little magic, but the
125 | one bit of magic it does set the global `vim.g.nix` to `true`. This allows us
126 | to make a very useful utility function:
127 |
128 | ```lua
129 | local set = function(nonNix, nix)
130 | if vim.g.nix == true then
131 | return nix
132 | else
133 | return nonNix
134 | end
135 | end
136 | ```
137 |
138 | Function inspired by [nixCats](https://github.com/BirdeeHub/nixCats-nvim), thank you BirdeeHub!
139 |
140 | We can give a nonNix and a nix value to this function, but what exactly does
141 | that do? It means we can assign values, or functions, or anything really based
142 | on if we're using nix or not. So for example on nix we can disable mason, or we
143 | can have different setup functions on nix and non nix.
144 |
145 | How to better integrate the function
146 |
147 | Of course, it's not very nice to have to copy this function over everywhere,
148 | for that I personally have a `lua/utils.lua` file. This file roughly looks like
149 | this:
150 |
151 | ```lua
152 | -- Set M to an empty table
153 | local M = {}
154 |
155 | -- snip
156 |
157 | -- Add boolean values to this table
158 | M.isNix = vim.g.nix == true
159 | M.isNotNix = vim.g.nix == nil
160 |
161 | -- Add the set function to this table,
162 | -- we can now call it with require("utils").set(a, b)
163 | function M.set(nonNix, nix)
164 | if M.isNix then
165 | return nix
166 | else
167 | return nonNix
168 | end
169 | end
170 |
171 | -- snip
172 |
173 | return M
174 | ```
175 |
176 | That way I can call `require("utils")` anywhere in my config and have access to
177 | `set`! For an example, see [my config](https://github.com/NicoElbers/nvim-config/blob/ed31459b8611da8b91f2979b825e03d8eb553f3f/init.lua#L6-L24).
178 |
179 |
180 |
181 | #### Loading lazy.nvim
182 |
183 | On nonNix [the lazy docs](https://lazy.folke.io/installation) tell you to add this to your config:
184 |
185 | ```lua
186 | -- Bootstrap lazy.nvim
187 | local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
188 | if not (vim.uv or vim.loop).fs_stat(lazypath) then
189 | local lazyrepo = "https://github.com/folke/lazy.nvim.git"
190 | local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath })
191 | if vim.v.shell_error ~= 0 then
192 | vim.api.nvim_echo({
193 | { "Failed to clone lazy.nvim:\n", "ErrorMsg" },
194 | { out, "WarningMsg" },
195 | { "\nPress any key to exit..." },
196 | }, true, {})
197 | vim.fn.getchar()
198 | os.exit(1)
199 | end
200 | end
201 | vim.opt.rtp:prepend(lazypath)
202 | ```
203 |
204 | This is downloading something imperatively, which we want to avoid on nix.
205 | Luckily this is super easy to change with our utility. The problem is what do
206 | we replace it with?
207 |
208 | This is another piece of sort of magic `nixPatch` does. The Lua string
209 | `"lazy.nvim-plugin-path"` is replaced with the appropriate path to the
210 | lazy.nvim plugin. This works because `nixPatch` provides some default patches
211 | if they otherwise wouldn't work out of the box, among these I added the string
212 | `"lazy.nvim-plugin-path"` to be replaced. You can see all default patches in
213 | the
214 | [`subPatches.nix`](https://github.com/NicoElbers/nixPatch-nvim/blob/main/subPatches.nix)
215 | file. You can also add your own, as you'll see a bit later.
216 |
217 | Also note how we have to add `{ performance = { rtp = { reset = set(true, false) } } }` to
218 | our lazygit settings. This is because otherwise lazy will try to force our
219 | runtime path (where your configuration lives) to be `~/.config/{name}`, which
220 | we want to avoid.
221 |
222 | **Beware** if you turn off the `patchSubs` setting, this will no longer work.
223 |
224 | Here is how that looks in practice:
225 |
226 | ```lua
227 | local set = function(nonNix, nix)
228 | if vim.g.nix == true then
229 | return nix
230 | else
231 | return nonNix
232 | end
233 | end
234 |
235 | -- Bootstrap lazy.nvim
236 | local load_lazy = set(function()
237 | local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
238 | if not (vim.uv or vim.loop).fs_stat(lazypath) then
239 | local lazyrepo = "https://github.com/folke/lazy.nvim.git"
240 | local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath })
241 | if vim.v.shell_error ~= 0 then
242 | vim.api.nvim_echo({
243 | { "Failed to clone lazy.nvim:\n", "ErrorMsg" },
244 | { out, "WarningMsg" },
245 | { "\nPress any key to exit..." },
246 | }, true, {})
247 | vim.fn.getchar()
248 | os.exit(1)
249 | end
250 | end
251 | vim.opt.rtp:prepend(lazypath)
252 | end, function()
253 | -- Prepend the runtime path with the directory of lazy
254 | -- This means we can call `require("lazy")`
255 | vim.opt.rtp:prepend([[lazy.nvim-plugin-path]])
256 | end)
257 |
258 | -- Actually execute the loading function we set above
259 | load_lazy()
260 |
261 | -- Disable resetting the RTP, so that you actually see our new one
262 | require("lazy").setup("plugins", { performance = { rtp = { reset = set(true, false) } } })
263 | ```
264 |
265 | #### Dealing with mason and the like
266 |
267 |
268 |
269 | Now that we already have our utility functions this is pretty easy. I'll give
270 | my own examples here in the future, for now look at how nixCats does it. If you
271 | replace `require('nixCatsUtils).lazyadd` with our `set` function, everything
272 | works the same.
273 |
274 | - [Downloading the mason plugins](https://github.com/BirdeeHub/nixCats-nvim/blob/f917800c75ae42bfec3014ea6b79252d6cc23546/nix/templates/kickstart-nvim/init.lua#L487-L509)
275 | - [Using mason for lsp configuration](https://github.com/BirdeeHub/nixCats-nvim/blob/f917800c75ae42bfec3014ea6b79252d6cc23546/nix/templates/kickstart-nvim/init.lua#L702-L748)
276 |
277 | ### Setting up the nix part
278 |
279 | Inside the directory where you have your configuration do `nix flake init -t
280 | github:NicoElbers/nixPatch-nvim`. This creates a `flake.nix`. Inside this flake
281 | you will find the outlines for everything you need.
282 |
283 | The main things you need to look out for are `plugins`, `runtimeDeps` and `luaPath`.
284 |
285 | - luaPath: This is very simply the path to your configuration root, aka the
286 | directory which contains your `init.lua`
287 | - plugins: This is where you tell nix what plugins to download. This will take
288 | a little bit of time, the easiest way I have found to do this is to put
289 | 'vimPlugins' in [nixos search](https://search.nixos.org/packages?channel=unstable&from=-1&size=50&sort=relevance&type=packages&query=vimPlugins)
290 | and search for all your plugins there. In my experience, with a little bit of
291 | fiddling, this worked best. After that, do `nix build`, and try to execute
292 | `./result/bin/nvimp`. Once everything is loaded you can do `:Lazy`, where you
293 | see all your plugins. Every dot that is blue means that the plugin was
294 | downloaded from github aka you need to add that plugin to your list. Once
295 | everything is done all the dots should be orange. Should you have installed
296 | everything but lazy is still downloading something, look at the "Custom
297 | patches" tab below.
298 | - runtimeDeps: This is all the external executables neovim will have access to.
299 | This is things like `tree-sitter` and your lsp's. Same here, look at [nixos search](https://search.nixos.org/packages?channel=unstable) and look for the
300 | executable. In my experience some lsp's have weird names so you might need to
301 | search a little. Feel free to use [my config](https://github.com/NicoElbers/nvim-config/blob/4e686f8fc2a2e0dd980998f4497005849bdb314d/flake.nix#L168-L207)
302 | as a starting point.
303 |
304 | The other options are hopefully explained well enough in the template. If not,
305 | feel free to make an issue and or pr.
306 |
307 | If you want to see what your config looks like, to find errors or just for fun,
308 | `vim.g.configdir` contains the location your patched config is located.
309 |
310 | Custom patches
311 |
312 | nixPatch provides you with the possibility to define custom subsitutions (look
313 | at [how it works](#how-it-works) for more details). These can be used to change
314 | any Lua string into any other Lua string. A specialization of these are plugin
315 | subsitutions. These assume that whatever you're replacing is a url and will
316 | replace a bit of fluff around it to satisfy lazy.nvim.
317 |
318 | In most cases you're gonna want to use a plugin subsitution. You can generate
319 | these very easily using the functions provided by `patchUtils.nix` In the
320 | template they are already imported for you. Have a look at them to see how you
321 | use them. When you have them generated, you need to put them in `customSubs` in
322 | your flake. After this you should be good.
323 |
324 | In the cases that you want to use literal string replacement, a couple of
325 | things to note:
326 |
327 | - You can only replace Lua strings (wrapped in `''`, `""` or `[[]]`), you can't
328 | change arbitrary characters.
329 | - Be careful what strings you're replacing. _Every_ Lua string in your _entire_
330 | config will be checked. Notably, this includes inside comments
331 | - The string you're replacing is matched fully. No substring matching, _every
332 | character has to match exactly_.
333 | - You cannot put code in your configuration. Everything you replace will be
334 | wrapped by `[[]]`, Lua's multiline string. String replacment is only meant to
335 | pass values from nix to your configuration. If you want specific code to run
336 | when you're using nix, use the `set` function discussed above.
337 | - You _can_ escape the multiline string if you really want, I don't validate
338 | your input in any way, but I make 0 guarantees it'll work in the future.
339 |
340 |
341 |
342 | ## Goals
343 |
344 | 1. Make any lazy.nvim configuration nix compatible
345 | 2. Keep Neovim fast
346 | 3. Easy setup for anyone
347 |
348 | These are my goals in order.
349 |
350 | First and foremost I want you to have no limits within your configuration. You
351 | should be able to do whatever you want in non-nix, and do everything within the
352 | limits of nix while using nix. I don't want to enforce anything if I can avoid
353 | it.
354 |
355 | After that I want to keep Neovim fast. Everything should be done at build time,
356 | anything done at runtime will slow down neovim which I will avoid at all costs.
357 | My 29ms startup should stay 29ms under all circumstances.
358 |
359 | Last but not least, setup should be easy. Part of the reason I stared this
360 | project is that I had a hard time making my config nix compatible. You
361 | shouldn't need to know much if anything about nix to get `nixPatch` to work.
362 |
363 | ## Limitations
364 |
365 | - Currently `nixPatch` only works for
366 | [lazy.nvim](https://github.com/folke/lazy.nvim) configurations. I might add
367 | support for plug in the future, however this is not a priority for the
368 | project.
369 | - You might experience issues with aliasing `nixPatch` to `nvim` if you also
370 | have Neovim installed and on your path. Therefore, it is not aliased by
371 | default.
372 | - You might experience issues if you use [page](https://github.com/I60R/page)
373 | as it also provides a binary named `nixPatch`.
374 |
375 | ## Roadmap
376 |
377 | - [x] Make the Lua parser smarter such that one line dependency plugin url's no
378 | longer need to be wrapped
379 | - [ ] Provide a way to iterate over your configuration quickly
380 | - This would mean that the underlying config patcher be exposed as a program
381 | for you to run, updating your settings but not adding new plugins
382 | - [ ] Add support for different package managers
383 | - This should be pretty simple, however I only use lazy.nvim so it's not a
384 | priority for me
385 | - [ ] Provide a script that parses your config and makes the `flake.nix` for
386 | you, with plugins and all
387 | - [ ] Make a nixos module / home manager module
388 | - In my opinion this has friction with the goal of simplicity, as it detaches
389 | the Neovim configuration from the `nixPatch` configuration. Unsure for now
390 |
391 | ## How it works
392 |
393 | ### Patching your config
394 |
395 | `nixPatch` works very differently from other nix Neovim solutions I've seen.
396 | Instead of generating Lua from nix configuration or hijacking the package
397 | manager, `nixPatch` patches your configuration at build time paths. Lazy.nvim
398 | expects either a url or a directory for any given plugin, so with a bit of
399 | clever parsing we can find the urls of your plugins and change them to
400 |
401 | But how does it know what urls to change? Wouldn't that be a lot of manual
402 | labor? Luckily, no. In nixpkgs vim plugin derivations are all put in one large
403 | file in a structured manner. This means that we can parse it quite easily.
404 | Combining this with a list of plugins you provide, we can link a url to a store
405 | path.
406 |
407 | Some plugins, like LuaSnip, don't work, for these exceptions we can make custom
408 | patches. If you look at the `subPatches.nix` file you'll find every custom
409 | patch I provide by default (you can disable these by setting `patchSubs` to
410 | false). Doing that in this repository has the nice advantage that once someone
411 | finds a faulty plugin, they can upstream their custom patch, and make it
412 | available for everyone.
413 |
414 | ### Zig
415 |
416 | I chose to do the patching itself in Zig, not nix. Mainly because I've been
417 | really liking Zig lately, and I'm not confident I could do complex file parsing
418 | in nix. Another advantage of Zig is speed. If I time the patcher on my own
419 | config it takes about 0.1 second, which is pretty good I'd say. Right now, that
420 | speed doesn't make much of a difference, building the executable takes ~10
421 | seconds (although only happens once) and setting up other things for Neovim
422 | takes a few more seconds, but it will make a difference in the future.
423 |
424 | One frustration I've heard is that iterating over your configuration is kind of
425 | annoying in nix. Rebuilding doesn't take ages, but long enough that it's
426 | frustrating. In the future, I plan to provide the patcher executable in some
427 | special "iteration" mode, where you can make changes and patch you config
428 | yourself. Then having that 0.1 second build time will not be that different
429 | from starting up Neovim normally.
430 |
--------------------------------------------------------------------------------
/build.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const Build = std.Build;
3 | const Step = Build.Step;
4 |
5 | pub const NamedModule = struct {
6 | mod: *Build.Module,
7 | name: []const u8,
8 |
9 | pub fn init(b: *Build, name: []const u8, options: Build.Module.CreateOptions) NamedModule {
10 | const mod = b.addModule(name, options);
11 | return NamedModule{
12 | .mod = mod,
13 | .name = name,
14 | };
15 | }
16 | };
17 |
18 | pub fn build(b: *Build) void {
19 | const target = b.standardTargetOptions(.{});
20 | const optimize = b.standardOptimizeOption(.{});
21 |
22 | // Lib module
23 | const lib_mod = NamedModule.init(b, "lib", .{
24 | .root_source_file = b.path("patcher/src/lib/root.zig"),
25 | .target = target,
26 | .optimize = optimize,
27 | });
28 |
29 | // Check step
30 | const check = b.step("check", "Check if project compiles");
31 |
32 | // Create exe
33 | const exe = addExe(b, check, &.{lib_mod}, .{
34 | .name = "config-patcher",
35 | .root_source_file = b.path("patcher/src/main.zig"),
36 | .target = target,
37 | .optimize = optimize,
38 | });
39 |
40 | // Run command
41 | const run_cmd = b.addRunArtifact(exe);
42 | run_cmd.step.dependOn(b.getInstallStep());
43 | if (b.args) |args| {
44 | run_cmd.addArgs(args);
45 | }
46 | const run_step = b.step("run", "Run the app");
47 | run_step.dependOn(&run_cmd.step);
48 |
49 | // Tests
50 | const test_step = b.step("test", "Run unit tests");
51 |
52 | _ = addTest(b, check, &.{lib_mod}, test_step, .{
53 | .root_source_file = b.path("patcher/src/lib/root.zig"),
54 | .target = target,
55 | .optimize = optimize,
56 | });
57 |
58 | _ = addTest(b, check, &.{lib_mod}, test_step, .{
59 | .root_source_file = b.path("patcher/test/root.zig"),
60 | .target = target,
61 | .optimize = optimize,
62 | });
63 | }
64 |
65 | fn addExe(b: *Build, check: *Step, mods: []const NamedModule, options: Build.ExecutableOptions) *Step.Compile {
66 | const exe = b.addExecutable(options);
67 | for (mods) |mod| {
68 | exe.root_module.addImport(mod.name, mod.mod);
69 | }
70 | b.installArtifact(exe);
71 |
72 | const check_exe = b.addExecutable(options);
73 | for (mods) |mod| {
74 | check_exe.root_module.addImport(mod.name, mod.mod);
75 | }
76 | check.dependOn(&check_exe.step);
77 |
78 | return exe;
79 | }
80 |
81 | fn addTest(b: *Build, check: *Step, mods: []const NamedModule, tst_step: *Step, options: Build.TestOptions) *Step.Compile {
82 | const tst = b.addTest(options);
83 | for (mods) |mod| {
84 | tst.root_module.addImport(mod.name, mod.mod);
85 | }
86 | const run_tst = b.addRunArtifact(tst);
87 | run_tst.has_side_effects = true;
88 | tst_step.dependOn(&run_tst.step);
89 |
90 | const check_tst = b.addTest(options);
91 | for (mods) |mod| {
92 | check_tst.root_module.addImport(mod.name, mod.mod);
93 | }
94 | check.dependOn(&check_tst.step);
95 |
96 | return tst;
97 | }
98 |
--------------------------------------------------------------------------------
/config/init.lua:
--------------------------------------------------------------------------------
1 | -- Load the things
2 | require("options")
3 | require("keymaps")
4 | require("autocmds")
5 |
6 | local utils = require("utils")
7 |
8 | local load_lazy = utils.set(function()
9 | local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
10 | if not vim.loop.fs_stat(lazypath) then
11 | vim.fn.system({
12 | "git",
13 | "clone",
14 | "--filter=blob:none",
15 | "https://github.com/folke/lazy.nvim.git",
16 | "--branch=stable", -- latest stable release
17 | lazypath,
18 | })
19 | end
20 | vim.opt.rtp:prepend(lazypath)
21 | end, function()
22 | -- Short URL will be replaced
23 | vim.opt.rtp:prepend([[lazy.nvim-plugin-path]])
24 | end)
25 |
26 | load_lazy()
27 |
28 | require("lazy").setup("plugins", { rocks = { enabled = true } })
29 |
--------------------------------------------------------------------------------
/config/lua/autocmds.lua:
--------------------------------------------------------------------------------
1 | -- vim.api.nvim_create_augroup("File type commands", { clear = false })
2 |
3 | -- vim.api.nvim_create_autocmd("FileType", {
4 | -- pattern = "markdown",
5 | -- desc = "Create useful commands in markdown files",
6 | -- callback = function()
7 | -- vim.api.nvim_buf_create_user_command(0, "MarkdownCompile", function()
8 | -- local filename = vim.fn.expand("%")
9 | -- local filename_pdf = vim.fn.expand("%:r") .. ".pdf"
10 | -- local cwd = vim.fn.expand("%:p:h")
11 |
12 | -- vim.system({ "pandoc", filename, "-o", filename_pdf, "-V", "geometry:margin=1in" }, { cwd = cwd })
13 | -- end, {})
14 |
15 | -- vim.o.wrap = true
16 | -- end,
17 | -- })
18 |
--------------------------------------------------------------------------------
/config/lua/keymaps.lua:
--------------------------------------------------------------------------------
1 | -- Better movement
2 | vim.keymap.set({ "n", "v" }, "j", "v:count == 0 ? 'gj' : 'j'", { expr = true, silent = true })
3 | vim.keymap.set({ "n", "v" }, "k", "v:count == 0 ? 'gk' : 'k'", { expr = true, silent = true })
4 |
5 | vim.keymap.set("v", "J", ":m '>+1gv=gv", { silent = true })
6 | vim.keymap.set("v", "K", ":m '<-2gv=gv", { silent = true })
7 |
8 | vim.keymap.set({ "n", "v" }, "", "zz", { silent = true })
9 | vim.keymap.set({ "n", "v" }, "", "zz", { silent = true })
10 |
11 | vim.keymap.set("n", "n", "nzz")
12 | vim.keymap.set("n", "N", "Nzz")
13 |
14 | vim.keymap.set("n", "'", "`")
15 |
16 | -- Better line inserting
17 | vim.keymap.set("n", "o", 'o"_D')
18 | vim.keymap.set("n", "O", 'O"_D')
19 |
20 | -- Better deleting
21 | vim.keymap.set("n", "d", '0"_D')
22 |
23 | -- Better leaving things
24 | vim.keymap.set("i", "", "")
25 |
26 | -- Quick fix shit
27 |
28 | vim.keymap.set("n", "", "cnext")
29 | vim.keymap.set("n", "", "cprev")
30 |
31 | vim.api.nvim_create_user_command("E", function()
32 | vim.cmd.wa()
33 | vim.cmd.qa()
34 | end, {})
35 |
--------------------------------------------------------------------------------
/config/lua/options.lua:
--------------------------------------------------------------------------------
1 | -- Leader key
2 | vim.g.mapleader = " "
3 |
4 | -- Very useful options
5 | -- clipboard
6 | vim.opt.clipboard = "unnamedplus"
7 |
8 | -- Line numbers
9 | vim.opt.rnu = true
10 | vim.opt.nu = true
11 | vim.opt.scrolloff = 15
12 |
13 | -- Tabs
14 | vim.opt.expandtab = true
15 | vim.opt.tabstop = 4
16 | vim.opt.softtabstop = 4
17 | vim.opt.shiftwidth = 4
18 |
19 | -- Wrap
20 | vim.opt.wrap = false
21 | vim.opt.breakindent = true
22 |
23 | -- Undo files
24 | vim.opt.undofile = true
25 |
26 | -- Searching
27 | vim.opt.hlsearch = false
28 | vim.opt.incsearch = true
29 | vim.opt.ignorecase = true
30 | vim.opt.smartcase = true
31 |
32 | -- Update time
33 | vim.opt.updatetime = 100
34 | vim.opt.timeout = true
35 | vim.opt.timeoutlen = 300
36 |
37 | -- Cool colors
38 | vim.opt.cursorline = true
39 | vim.opt.colorcolumn = { 80, 81 }
40 |
41 | -- Required for formatting I think, can't be fucked to check
42 | -- vim.o.formatexpr = "v:lua.require'conform'.formatexpr()"
43 |
44 | -- Set whitespace characters
45 | vim.opt.listchars:append({
46 | multispace = "·",
47 | lead = "·",
48 | trail = "·",
49 | nbsp = "·",
50 | eol = "↵",
51 | })
52 | vim.opt.list = true
53 |
54 | -- local colorschemeName = "catppuccin-mocha"
55 | -- vim.cmd.colorscheme(colorschemeName)
56 |
--------------------------------------------------------------------------------
/config/lua/plugins/autoclose.lua:
--------------------------------------------------------------------------------
1 | return {
2 | "m4xshen/autoclose.nvim",
3 | event = "InsertEnter",
4 | opts = {
5 | keys = {
6 | ["("] = { escape = false, close = true, pair = "()" },
7 | ["["] = { escape = false, close = true, pair = "[]" },
8 | ["{"] = { escape = false, close = true, pair = "{}" },
9 | ["<"] = { escape = false, close = true, pair = "<>" },
10 |
11 | [">"] = { escape = true, close = false, pair = "<>" },
12 | [")"] = { escape = true, close = false, pair = "()" },
13 | ["]"] = { escape = true, close = false, pair = "[]" },
14 | ["}"] = { escape = true, close = false, pair = "{}" },
15 |
16 | ['"'] = { escape = true, close = true, pair = '""' },
17 | ["'"] = { escape = true, close = false, pair = "''" },
18 | ["`"] = { escape = true, close = true, pair = "``" },
19 | },
20 | options = {
21 | disabled_filetypes = { "text" },
22 | disable_when_touch = false,
23 | touch_regex = "[%w(%[{]",
24 | pair_spaces = false,
25 | auto_indent = true,
26 | disable_command_mode = false,
27 | },
28 | },
29 | }
30 |
--------------------------------------------------------------------------------
/config/lua/plugins/catppuccin.lua:
--------------------------------------------------------------------------------
1 | return {
2 | "catppuccin/nvim",
3 | priority = 1000,
4 | config = function()
5 | -- colorscheme catppuccin " catppuccin-latte, catppuccin-frappe, catppuccin-macchiato, catppuccin-mocha
6 | vim.cmd.colorscheme("catppuccin-mocha")
7 | end,
8 | }
9 |
--------------------------------------------------------------------------------
/config/lua/plugins/comments.lua:
--------------------------------------------------------------------------------
1 | return {
2 | "numToStr/Comment.nvim",
3 | -- event = { "BufReadPost", "BufNewFile", "BufWritePre" },
4 | keys = {
5 | { "gcc" },
6 | { "gbc" },
7 | { "gcO" },
8 | { "gco" },
9 | { "gcA" },
10 | { "gc", mode = "v" },
11 | { "gb", mode = "v" },
12 | },
13 |
14 | opts = {
15 | ignore = "^$",
16 | },
17 | }
18 |
--------------------------------------------------------------------------------
/config/lua/plugins/completions.lua:
--------------------------------------------------------------------------------
1 | return {
2 | {
3 | "hrsh7th/nvim-cmp",
4 | event = "InsertEnter",
5 | dependencies = {
6 | {
7 | "L3MON4D3/LuaSnip",
8 | dependencies = {
9 | {
10 | "rafamadriz/friendly-snippets",
11 | },
12 | },
13 | config = function()
14 | require("luasnip.loaders.from_vscode").lazy_load()
15 | end,
16 | },
17 | "hrsh7th/cmp-nvim-lsp",
18 | "saadparwaiz1/cmp_luasnip",
19 | "L3MON4D3/LuaSnip",
20 | "hrsh7th/cmp-nvim-lua",
21 | "hrsh7th/cmp-nvim-lsp-signature-help",
22 | "hrsh7th/cmp-path",
23 | "hrsh7th/cmp-buffer",
24 | },
25 | config = function()
26 | local cmp = require("cmp")
27 | local luasnip = require("luasnip")
28 |
29 | cmp.setup({
30 | snippet = {
31 | expand = function(args)
32 | luasnip.lsp_expand(args.body)
33 | end,
34 | },
35 | window = {
36 | completion = cmp.config.window.bordered(),
37 | documentation = cmp.config.window.bordered(),
38 | },
39 | mapping = cmp.mapping.preset.insert({
40 | [""] = cmp.mapping.scroll_docs(-4),
41 | [""] = cmp.mapping.scroll_docs(4),
42 | [""] = cmp.mapping.confirm({
43 | behavior = cmp.ConfirmBehavior.Replace,
44 | select = true,
45 | }),
46 | [""] = cmp.mapping(function(fallback)
47 | if cmp.visible() then
48 | cmp.select_next_item()
49 | elseif luasnip.expand_or_locally_jumpable() then
50 | luasnip.expand_or_jump()
51 | else
52 | fallback()
53 | end
54 | end, { "i", "s" }),
55 | [""] = cmp.mapping(function(fallback)
56 | if cmp.visible() then
57 | cmp.select_prev_item()
58 | elseif luasnip.locally_jumpable(-1) then
59 | luasnip.jump(-1)
60 | else
61 | fallback()
62 | end
63 | end, { "i", "s" }),
64 | }),
65 | sources = cmp.config.sources({
66 | { name = "path" },
67 | { name = "nvim_lsp" },
68 | { name = "nvim_lsp_signature_help" },
69 | { name = "luasnip" },
70 | { name = "nvim_lua" },
71 | { name = "buffer" },
72 | { name = "calc" },
73 | }, {}),
74 | })
75 | end,
76 | },
77 | }
78 |
--------------------------------------------------------------------------------
/config/lua/plugins/conform.lua:
--------------------------------------------------------------------------------
1 | return {
2 | "stevearc/conform.nvim",
3 | event = { "BufWritePre" },
4 | cmd = { "ConformInfo" },
5 | opts = {
6 | format_on_save = {
7 | timeout_ms = 500,
8 | lsp_fallback = true,
9 | },
10 | formatters_by_ft = {
11 | lua = { "stylua" },
12 | markdown = { "prettierd" },
13 | rust = { "rustfmt" },
14 | },
15 | formatters = {
16 | prettierd = {},
17 | },
18 | },
19 | }
20 |
--------------------------------------------------------------------------------
/config/lua/plugins/gitsigns.lua:
--------------------------------------------------------------------------------
1 | return {
2 | "lewis6991/gitsigns.nvim",
3 | event = require("utils").LazyFile,
4 | opts = {},
5 | }
6 |
--------------------------------------------------------------------------------
/config/lua/plugins/lsp.lua:
--------------------------------------------------------------------------------
1 | local utils = require("utils")
2 |
3 | return {
4 | {
5 | "neovim/nvim-lspconfig",
6 | dependencies = {
7 | "folke/neodev.nvim",
8 | "j-hui/fidget.nvim",
9 | },
10 | ft = {
11 | "c",
12 | "c++",
13 | "lua",
14 | "markdown",
15 | "nix",
16 | "python",
17 | "html",
18 | "css",
19 | "js",
20 | "ts",
21 | "zig",
22 | },
23 | config = function()
24 | local capabilities = vim.lsp.protocol.make_client_capabilities()
25 | capabilities = require("cmp_nvim_lsp").default_capabilities(capabilities)
26 | capabilities.document_formatting = false
27 |
28 | local lspconfig = require("lspconfig")
29 |
30 | -- c/ c++
31 | lspconfig.clangd.setup({
32 | on_attach = utils.on_attach,
33 | cmd = { "clangd" },
34 | capabilities = capabilities,
35 | })
36 |
37 | -- Lua
38 | lspconfig.lua_ls.setup({
39 | on_attach = utils.on_attach,
40 | cmd = { "lua-language-server" },
41 | capabilities = capabilities,
42 | })
43 |
44 | -- Markdown
45 | lspconfig.marksman.setup({
46 | on_attach = utils.on_attach,
47 | cmd = { "marksman" },
48 | capabilities = capabilities,
49 | })
50 |
51 | -- Nix
52 | lspconfig.nil_ls.setup({
53 | on_attach = utils.on_attach,
54 | cmd = { "nil" },
55 | capabilities = capabilities,
56 | })
57 |
58 | -- Python
59 | lspconfig.pyright.setup({
60 | on_attach = utils.on_attach,
61 | cmd = { "pyright-langserver" },
62 | capabilities = capabilities,
63 | })
64 |
65 | -- Web
66 | lspconfig.tsserver.setup({
67 | on_attach = utils.on_attach,
68 | capabilities = capabilities,
69 | })
70 |
71 | lspconfig.emmet_language_server.setup({
72 | on_attach = utils.on_attach,
73 | cmd = { "emmet-language-server" },
74 | capabilities = capabilities,
75 | })
76 |
77 | lspconfig.tailwindcss.setup({
78 | on_attach = utils.on_attach,
79 | cmd = { "tailwindcss-language-server" },
80 | capabilities = capabilities,
81 | })
82 |
83 | -- local cssls_capabilities = capabilities
84 | -- cssls_capabilities.textDocument.completion.completionItem.snippetSupport = true
85 | -- lspconfig.cssls.setup({
86 | -- on_attach = utils.on_attach,
87 | -- capabilities = cssls_capabilities,
88 | -- })
89 |
90 | -- Zig
91 | lspconfig.zls.setup({
92 | capabilities = capabilities,
93 | cmd = { "zls" },
94 | on_attach = utils.on_attach,
95 | settings = {
96 | warn_style = true,
97 | },
98 | })
99 | end,
100 | },
101 | -- Rust
102 | {
103 | "mrcjkb/rustaceanvim",
104 | version = "^4", -- Recommended
105 | ft = { "rust" },
106 | init = function()
107 | vim.g.rustaceanvim = {
108 | tools = {
109 | enable_clippy = true,
110 | },
111 | server = {
112 | on_attach = utils.on_attach,
113 | default_settings = {
114 | ["rust-analyzer"] = {
115 | cargo = {
116 | allFeatures = true,
117 | features = "all",
118 | loadOutDirsFromCheck = true,
119 | runBuildScripts = true,
120 | },
121 | -- Add clippy lints for Rust
122 | checkOnSave = {
123 | allFeatures = true,
124 | allTargets = true,
125 | command = "clippy",
126 | extraArgs = {
127 | "--",
128 | "--no-deps",
129 | "-Dclippy::pedantic",
130 | "-Dclippy::nursery",
131 | "-Dclippy::unwrap_used",
132 | "-Dclippy::enum_glob_use",
133 | "-Wclippy::complexity",
134 | "-Wclippy::perf",
135 | -- Shitty lints imo
136 | "-Aclippy::module_name_repetitions",
137 | },
138 | },
139 | procMacro = {
140 | enable = true,
141 | ignored = {
142 | ["async-trait"] = { "async_trait" },
143 | ["napi-derive"] = { "napi" },
144 | ["async-recursion"] = { "async_recursion" },
145 | },
146 | },
147 | },
148 | },
149 | },
150 | }
151 | end,
152 | },
153 | }
154 |
--------------------------------------------------------------------------------
/config/lua/plugins/lualine.lua:
--------------------------------------------------------------------------------
1 | return {
2 | "nvim-lualine/lualine.nvim",
3 | event = "VeryLazy",
4 | dependencies = { "nvim-tree/nvim-web-devicons" },
5 | opts = {
6 | options = {
7 | icons_enabled = true,
8 | theme = "dracula",
9 | },
10 | },
11 | }
12 |
--------------------------------------------------------------------------------
/config/lua/plugins/markdown.lua:
--------------------------------------------------------------------------------
1 | local utils = require("utils")
2 |
3 | return {
4 | {
5 | "iamcco/markdown-preview.nvim",
6 | cmd = { "MarkdownPreviewToggle", "MarkdownPreview", "MarkdownPreviewStop" },
7 | ft = { "markdown" },
8 | build = utils.set(function()
9 | vim.fn["mkdp#util#install"]()
10 | end),
11 | },
12 | }
13 |
--------------------------------------------------------------------------------
/config/lua/plugins/neotree.lua:
--------------------------------------------------------------------------------
1 | return {
2 | "nvim-neo-tree/neo-tree.nvim",
3 | branch = "v3.x",
4 | dependencies = {
5 | "nvim-lua/plenary.nvim",
6 | "nvim-tree/nvim-web-devicons", -- not strictly required, but recommended
7 | "MunifTanjim/nui.nvim",
8 | "3rd/image.nvim",
9 | },
10 | opts = {
11 | event_handlers = {
12 | {
13 | event = "file_opened",
14 | handler = function()
15 | require("neo-tree.command").execute({ action = "close" })
16 | end,
17 | },
18 | },
19 | },
20 | -- config = function(_, opts)
21 | -- require("neo-tree").setup(opts)
22 | -- -- vim.keymap.set("n", "", ":Neotree filesystem reveal left")
23 | -- end,
24 | keys = {
25 | { "", ":Neotree filesystem reveal left" },
26 | },
27 | }
28 |
--------------------------------------------------------------------------------
/config/lua/plugins/none-ls.lua:
--------------------------------------------------------------------------------
1 | local utils = require("utils")
2 |
3 | return {
4 | "nvimtools/none-ls.nvim",
5 | dependencies = {
6 | "nvim-lua/plenary.nvim",
7 | },
8 | ft = {
9 | -- checkstyle
10 | "java",
11 |
12 | -- ltrs
13 | "markdown",
14 | "txt",
15 | },
16 | config = function()
17 | local none_ls = require("null-ls")
18 | local bi = none_ls.builtins
19 | none_ls.setup({
20 | defaults = {
21 | update_in_insert = true,
22 | },
23 | sources = {
24 | -- Diagnostics
25 | bi.diagnostics.checkstyle.with({
26 | extra_args = { "-c", "/checkstyle.xml" },
27 | }),
28 |
29 | bi.diagnostics.ltrs.with({
30 | diagnostic_config = {
31 | underline = true,
32 | virtual_text = false,
33 | signs = true,
34 | update_in_insert = true,
35 | },
36 | }),
37 | },
38 | on_attach = utils.on_attach,
39 | })
40 | end,
41 | }
42 |
--------------------------------------------------------------------------------
/config/lua/plugins/sleuth.lua:
--------------------------------------------------------------------------------
1 | return {
2 | "tpope/vim-sleuth",
3 | event = require("utils").LazyFile,
4 | }
5 |
--------------------------------------------------------------------------------
/config/lua/plugins/telescope.lua:
--------------------------------------------------------------------------------
1 | return {
2 | {
3 | "nvim-telescope/telescope.nvim",
4 | tag = "0.1.5",
5 | dependencies = {
6 | "nvim-lua/plenary.nvim",
7 | "debugloop/telescope-undo.nvim",
8 | "nvim-telescope/telescope-ui-select.nvim",
9 | },
10 | cmd = "Telescope",
11 | keys = {
12 | { "f", "Telescope find_files" },
13 | { "ls", "Telescope live_grep" },
14 | { "bs", "Telescope current_buffer_fuzzy_find" },
15 | { "gr", "Telescope lsp_references" },
16 | },
17 | config = function()
18 | vim.defer_fn(function()
19 | local conf = require("telescope")
20 | conf.setup({
21 | extensions = {
22 | ["ui-select"] = {
23 | require("telescope.themes").get_dropdown({}),
24 | },
25 | undo = {},
26 | },
27 | })
28 |
29 | conf.load_extension("ui-select")
30 | conf.load_extension("undo")
31 |
32 | -- local builtin = require("telescope.builtin")
33 | -- vim.keymap.set("n", "pf", builtin.find_files)
34 | -- vim.keymap.set("n", "ps", builtin.live_grep)
35 | -- vim.keymap.set("n", "?", builtin.oldfiles)
36 | -- vim.keymap.set("n", "u", "Telescope undo")
37 |
38 | -- vim.keymap.set("n", "gr", builtin.lsp_references)
39 | end, 0)
40 | end,
41 | },
42 | }
43 |
--------------------------------------------------------------------------------
/config/lua/plugins/tex.lua:
--------------------------------------------------------------------------------
1 | return {
2 | "lervag/vimtex",
3 | ft = { "tex", "plaintex" },
4 | lazy = false, -- lazy-loading will disable inverse search
5 | config = function()
6 | vim.api.nvim_create_autocmd({ "FileType" }, {
7 | group = vim.api.nvim_create_augroup("lazyvim_vimtex_conceal", { clear = true }),
8 | pattern = { "bib", "tex" },
9 | callback = function()
10 | vim.wo.conceallevel = 2
11 | end,
12 | })
13 |
14 | vim.g.vimtex_mappings_disable = { ["n"] = { "K" } } -- disable `K` as it conflicts with LSP hover
15 | vim.g.vimtex_quickfix_method = vim.fn.executable("pplatex") == 1 and "pplatex" or "latexlog"
16 | end,
17 | }
18 |
--------------------------------------------------------------------------------
/config/lua/plugins/todo.lua:
--------------------------------------------------------------------------------
1 | return {
2 | "folke/todo-comments.nvim",
3 | event = { "BufRead", "BufWrite", "BufNewFile" },
4 | cmd = { "TodoTrouble", "TodoTelescope" },
5 | dependencies = { "nvim-lua/plenary.nvim" },
6 | config = true,
7 | -- stylua: ignore
8 | keys = {
9 | { "]t", function() require("todo-comments").jump_next() end, desc = "Next todo comment" },
10 | { "[t", function() require("todo-comments").jump_prev() end, desc = "Previous todo comment" },
11 | { "st", "TodoTelescope keywords=TODO,FIX,FIXME,TEST,TESTS,IDEA", desc = "Keywords I wanna track" },
12 | },
13 | opts = {},
14 | }
15 |
--------------------------------------------------------------------------------
/config/lua/plugins/treesitter.lua:
--------------------------------------------------------------------------------
1 | local utils = require("utils")
2 |
3 | return {
4 | {
5 | "nvim-treesitter/nvim-treesitter",
6 | dependencies = {
7 | {
8 | "nvim-treesitter/nvim-treesitter-textobjects",
9 | lazy = true,
10 | },
11 | },
12 | -- enabled = false,
13 |
14 | event = { "BufReadPost", "BufNewFile", "BufWritePre", "VeryLazy" },
15 | cmd = {
16 | "TSBufDisable",
17 | "TSBufEnable",
18 | "TSBufToggle",
19 | "TSDisable",
20 | "TSEnable",
21 | "TSToggle",
22 | "TSInstall",
23 | "TSInstallInfo",
24 | "TSInstallSync",
25 | "TSModuleInfo",
26 | "TSUninstall",
27 | "TSUpdate",
28 | "TSUpdateSync",
29 | },
30 | keys = {
31 | { "", desc = "Increment Selection" },
32 | { "", desc = "Decrement Selection", mode = "x" },
33 | },
34 |
35 | lazy = vim.fn.argc(-1) == 0,
36 |
37 | init = function(plugin)
38 | -- PERF: add nvim-treesitter queries to the rtp and it's custom query predicates early
39 | -- This is needed because a bunch of plugins no longer `require("nvim-treesitter")`, which
40 | -- no longer trigger the **nvim-treesitter** module to be loaded in time.
41 | -- Luckily, the only things that those plugins need are the custom queries, which we make available
42 | -- during startup.
43 | require("lazy.core.loader").add_to_rtp(plugin)
44 | require("nvim-treesitter.query_predicates")
45 | end,
46 |
47 | build = utils.set(":TSUpdate"),
48 | config = function()
49 | require("nvim-treesitter.configs").setup({
50 | auto_install = utils.set(true, false),
51 | highlight = {
52 | enable = true,
53 | disable = { "latex" },
54 | },
55 | indent = { enable = true },
56 | textobjects = {
57 | select = {
58 | enable = true,
59 | lookahead = true, -- Automatically jump forward to textobj, similar to targets.vim
60 | keymaps = {
61 | -- You can use the capture groups defined in textobjects.scm
62 | ["aa"] = "@parameter.outer",
63 | ["ia"] = "@parameter.inner",
64 | ["af"] = "@function.outer",
65 | ["if"] = "@function.inner",
66 | ["ac"] = "@class.outer",
67 | ["ic"] = "@class.inner",
68 | },
69 | },
70 | move = {
71 | enable = true,
72 | set_jumps = true, -- whether to set jumps in the jumplist
73 | goto_next_start = {
74 | ["]m"] = "@function.outer",
75 | ["]]"] = "@class.outer",
76 | },
77 | goto_next_end = {
78 | ["]M"] = "@function.outer",
79 | ["]["] = "@class.outer",
80 | },
81 | goto_previous_start = {
82 | ["[m"] = "@function.outer",
83 | ["[["] = "@class.outer",
84 | },
85 | goto_previous_end = {
86 | ["[M"] = "@function.outer",
87 | ["[]"] = "@class.outer",
88 | },
89 | },
90 | swap = {
91 | enable = true,
92 | swap_next = {
93 | ["a"] = "@parameter.inner",
94 | },
95 | swap_previous = {
96 | ["A"] = "@parameter.inner",
97 | },
98 | },
99 | },
100 | })
101 | end,
102 | },
103 | }
104 |
--------------------------------------------------------------------------------
/config/lua/plugins/trouble.lua:
--------------------------------------------------------------------------------
1 | return {
2 | "folke/trouble.nvim",
3 | dependencies = { "nvim-tree/nvim-web-devicons" },
4 | opts = {
5 | keys = {
6 | [""] = "jump_close",
7 | [""] = {
8 | ---@param view trouble.View
9 | ---@param ctx trouble.Action.ctx
10 | action = function(view, ctx)
11 | if ctx.item then
12 | view:jump(ctx.item)
13 | view:close()
14 | elseif ctx.node then
15 | view:fold(ctx.node)
16 | end
17 | end,
18 | },
19 | },
20 | },
21 | cmd = "Trouble",
22 | keys = {
23 | {
24 | "gr",
25 | -- "Trouble lsp toggle focus=false win.position=right open_no_results=true",
26 | function()
27 | require("trouble").toggle({
28 | win = {
29 | position = "right",
30 | type = "split",
31 | },
32 | open_no_results = true,
33 | mode = "lsp",
34 | source = "",
35 | })
36 | end,
37 | },
38 | {
39 | "e",
40 | function()
41 | require("trouble").toggle({
42 | mode = "diagnostics",
43 | focus = true,
44 | auto_close = true,
45 | })
46 | end,
47 | },
48 | },
49 | }
50 |
--------------------------------------------------------------------------------
/config/lua/plugins/undotree.lua:
--------------------------------------------------------------------------------
1 | return {
2 | "mbbill/undotree",
3 | -- lazy = false,
4 | opt = {},
5 | keys = {
6 | {
7 | "u",
8 | function()
9 | vim.cmd.UndotreeToggle()
10 | vim.cmd.UndotreeFocus()
11 | end,
12 | },
13 | },
14 | }
15 |
--------------------------------------------------------------------------------
/config/lua/utils.lua:
--------------------------------------------------------------------------------
1 | local update_border = function()
2 | local border = {
3 | { "╭", "FloatBorder" },
4 | { "─", "FloatBorder" },
5 | { "╮", "FloatBorder" },
6 | { "│", "FloatBorder" },
7 | { "╯", "FloatBorder" },
8 | { "─", "FloatBorder" },
9 | { "╰", "FloatBorder" },
10 | { "│", "FloatBorder" },
11 | }
12 | local orig_floating_preview = vim.lsp.util.open_floating_preview
13 |
14 | ---@diagnostic disable-next-line: duplicate-set-field
15 | function vim.lsp.util.open_floating_preview(contents, syntax, opts, ...)
16 | opts = opts or {}
17 | opts.border = opts.border or border
18 | return orig_floating_preview(contents, syntax, opts, ...)
19 | end
20 | end
21 |
22 | local M = {}
23 |
24 | M.LazyFile = { "BufReadPost", "BufNewFile", "BufWritePre" }
25 | M.isNix = vim.g.nix ~= nil
26 | M.isNotNix = vim.g.nix == nil
27 |
28 | function M.set(nonNix, nix)
29 | if M.isNix then
30 | return nix
31 | else
32 | return nonNix
33 | end
34 | end
35 |
36 | function M.is_loaded(name)
37 | local Config = require("lazy.core.config")
38 | return Config.plugins[name] and Config.plugins[name]._.loaded
39 | end
40 |
41 | function M.on_attach(client, bufnr)
42 | update_border()
43 |
44 | local nmap = function(keys, func, desc)
45 | if desc then
46 | desc = "LSP: " .. desc
47 | end
48 |
49 | vim.keymap.set("n", keys, func, { buffer = bufnr, desc = desc })
50 | end
51 |
52 | -- Supports
53 | local supp = function(method)
54 | return client.supports_method(method)
55 | end
56 |
57 | -- Conditional normal map
58 | local cnmap = function(method, keys, func, desc)
59 | if supp(method) then
60 | nmap(keys, func, desc)
61 | end
62 | end
63 |
64 | cnmap("textDocument/hover", "K", vim.lsp.buf.hover, "Hover Docs")
65 | cnmap("textDocument/definition", "gd", vim.lsp.buf.definition, "[G]oto [D]efinition")
66 | cnmap("textDocument/declaration", "gD", vim.lsp.buf.declaration, "[G]oto [D]eclaration")
67 | cnmap("textDocument/implementation", "gi", vim.lsp.buf.implementation, "[G]oto [I]mplementation")
68 | cnmap("textDocument/typeDefinition", "de", vim.lsp.buf.type_definition, "[T]ype [D]efinition")
69 | cnmap("textDocument/rename", "rn", vim.lsp.buf.rename, "[R]e[N]ame")
70 | cnmap("textDocument/codeAction", "ca", vim.lsp.buf.code_action, "[C]ode [A]ction")
71 |
72 | if supp("textDocument/inlayHint") then
73 | vim.lsp.inlay_hint.enable(true)
74 | end
75 | end
76 |
77 | return M
78 |
--------------------------------------------------------------------------------
/config/stylua.toml:
--------------------------------------------------------------------------------
1 | column_width = 120
2 | line_endings = "Unix"
3 | indent_type = "Spaces"
4 | indent_width = 4
5 | quote_style = "AutoPreferDouble"
6 | call_parentheses = "Always"
7 | collapse_simple_statement = "Never"
8 |
9 | [sort_requires]
10 | enabled = false
11 |
--------------------------------------------------------------------------------
/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "flake-compat": {
4 | "flake": false,
5 | "locked": {
6 | "lastModified": 1747046372,
7 | "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
8 | "owner": "edolstra",
9 | "repo": "flake-compat",
10 | "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
11 | "type": "github"
12 | },
13 | "original": {
14 | "owner": "edolstra",
15 | "repo": "flake-compat",
16 | "type": "github"
17 | }
18 | },
19 | "flake-compat_2": {
20 | "flake": false,
21 | "locked": {
22 | "lastModified": 1696426674,
23 | "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
24 | "owner": "edolstra",
25 | "repo": "flake-compat",
26 | "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
27 | "type": "github"
28 | },
29 | "original": {
30 | "owner": "edolstra",
31 | "repo": "flake-compat",
32 | "type": "github"
33 | }
34 | },
35 | "flake-parts": {
36 | "inputs": {
37 | "nixpkgs-lib": [
38 | "neovim-nightly-overlay",
39 | "nixpkgs"
40 | ]
41 | },
42 | "locked": {
43 | "lastModified": 1743550720,
44 | "narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=",
45 | "owner": "hercules-ci",
46 | "repo": "flake-parts",
47 | "rev": "c621e8422220273271f52058f618c94e405bb0f5",
48 | "type": "github"
49 | },
50 | "original": {
51 | "owner": "hercules-ci",
52 | "repo": "flake-parts",
53 | "type": "github"
54 | }
55 | },
56 | "flake-parts_2": {
57 | "inputs": {
58 | "nixpkgs-lib": [
59 | "neovim-nightly-overlay",
60 | "hercules-ci-effects",
61 | "nixpkgs"
62 | ]
63 | },
64 | "locked": {
65 | "lastModified": 1743550720,
66 | "narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=",
67 | "owner": "hercules-ci",
68 | "repo": "flake-parts",
69 | "rev": "c621e8422220273271f52058f618c94e405bb0f5",
70 | "type": "github"
71 | },
72 | "original": {
73 | "id": "flake-parts",
74 | "type": "indirect"
75 | }
76 | },
77 | "git-hooks": {
78 | "inputs": {
79 | "flake-compat": "flake-compat_2",
80 | "gitignore": "gitignore",
81 | "nixpkgs": [
82 | "neovim-nightly-overlay",
83 | "nixpkgs"
84 | ]
85 | },
86 | "locked": {
87 | "lastModified": 1747372754,
88 | "narHash": "sha256-2Y53NGIX2vxfie1rOW0Qb86vjRZ7ngizoo+bnXU9D9k=",
89 | "owner": "cachix",
90 | "repo": "git-hooks.nix",
91 | "rev": "80479b6ec16fefd9c1db3ea13aeb038c60530f46",
92 | "type": "github"
93 | },
94 | "original": {
95 | "owner": "cachix",
96 | "repo": "git-hooks.nix",
97 | "type": "github"
98 | }
99 | },
100 | "gitignore": {
101 | "inputs": {
102 | "nixpkgs": [
103 | "neovim-nightly-overlay",
104 | "git-hooks",
105 | "nixpkgs"
106 | ]
107 | },
108 | "locked": {
109 | "lastModified": 1709087332,
110 | "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
111 | "owner": "hercules-ci",
112 | "repo": "gitignore.nix",
113 | "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
114 | "type": "github"
115 | },
116 | "original": {
117 | "owner": "hercules-ci",
118 | "repo": "gitignore.nix",
119 | "type": "github"
120 | }
121 | },
122 | "hercules-ci-effects": {
123 | "inputs": {
124 | "flake-parts": "flake-parts_2",
125 | "nixpkgs": [
126 | "neovim-nightly-overlay",
127 | "nixpkgs"
128 | ]
129 | },
130 | "locked": {
131 | "lastModified": 1747284884,
132 | "narHash": "sha256-lTSKhRrassMcJ1ZsuUVunyl/F04vvCKY80HB/4rvvm4=",
133 | "owner": "hercules-ci",
134 | "repo": "hercules-ci-effects",
135 | "rev": "7168f6002a6b48a9b6151e1e97e974a0722ecfdc",
136 | "type": "github"
137 | },
138 | "original": {
139 | "owner": "hercules-ci",
140 | "repo": "hercules-ci-effects",
141 | "type": "github"
142 | }
143 | },
144 | "neovim-nightly-overlay": {
145 | "inputs": {
146 | "flake-compat": "flake-compat",
147 | "flake-parts": "flake-parts",
148 | "git-hooks": "git-hooks",
149 | "hercules-ci-effects": "hercules-ci-effects",
150 | "neovim-src": "neovim-src",
151 | "nixpkgs": [
152 | "nixpkgs"
153 | ],
154 | "treefmt-nix": "treefmt-nix"
155 | },
156 | "locked": {
157 | "lastModified": 1747554936,
158 | "narHash": "sha256-LBFEVTt3JISA/HDHznJanvlNvKllNfILr1nfI8KZmVM=",
159 | "owner": "nix-community",
160 | "repo": "neovim-nightly-overlay",
161 | "rev": "5a732bf3edb47767a25c3b05436e4c21f91edf91",
162 | "type": "github"
163 | },
164 | "original": {
165 | "owner": "nix-community",
166 | "repo": "neovim-nightly-overlay",
167 | "type": "github"
168 | }
169 | },
170 | "neovim-src": {
171 | "flake": false,
172 | "locked": {
173 | "lastModified": 1747523215,
174 | "narHash": "sha256-55RIMak4EwDaLdNTkM+4d3LjC90wlkNRaaG8DupK3AM=",
175 | "owner": "neovim",
176 | "repo": "neovim",
177 | "rev": "5661f74ab2a6ef0c497ef2ea49bc58ea89b6ab6b",
178 | "type": "github"
179 | },
180 | "original": {
181 | "owner": "neovim",
182 | "repo": "neovim",
183 | "type": "github"
184 | }
185 | },
186 | "nixpkgs": {
187 | "locked": {
188 | "lastModified": 1747327360,
189 | "narHash": "sha256-LSmTbiq/nqZR9B2t4MRnWG7cb0KVNU70dB7RT4+wYK4=",
190 | "owner": "nixos",
191 | "repo": "nixpkgs",
192 | "rev": "e06158e58f3adee28b139e9c2bcfcc41f8625b46",
193 | "type": "github"
194 | },
195 | "original": {
196 | "owner": "nixos",
197 | "ref": "nixos-unstable",
198 | "repo": "nixpkgs",
199 | "type": "github"
200 | }
201 | },
202 | "root": {
203 | "inputs": {
204 | "neovim-nightly-overlay": "neovim-nightly-overlay",
205 | "nixpkgs": "nixpkgs"
206 | }
207 | },
208 | "treefmt-nix": {
209 | "inputs": {
210 | "nixpkgs": [
211 | "neovim-nightly-overlay",
212 | "nixpkgs"
213 | ]
214 | },
215 | "locked": {
216 | "lastModified": 1747469671,
217 | "narHash": "sha256-bo1ptiFoNqm6m1B2iAhJmWCBmqveLVvxom6xKmtuzjg=",
218 | "owner": "numtide",
219 | "repo": "treefmt-nix",
220 | "rev": "ab0378b61b0d85e73a8ab05d5c6029b5bd58c9fb",
221 | "type": "github"
222 | },
223 | "original": {
224 | "owner": "numtide",
225 | "repo": "treefmt-nix",
226 | "type": "github"
227 | }
228 | }
229 | },
230 | "root": "root",
231 | "version": 7
232 | }
233 |
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | description = "Super thin wrapper around neovim";
3 |
4 | inputs = {
5 | nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
6 |
7 | neovim-nightly-overlay = {
8 | url = "github:nix-community/neovim-nightly-overlay";
9 | inputs.nixpkgs.follows = "nixpkgs";
10 | };
11 |
12 | };
13 |
14 | outputs = { nixpkgs, ... }@inputs:
15 | let
16 | name = "nvimp";
17 |
18 | utils = (import ./nix/utils);
19 | forEachSystem = utils.eachSystem nixpkgs.lib.platforms.all;
20 | builders = (import ./nix/builders);
21 |
22 | inherit (forEachSystem (system:
23 | let
24 | dependencyOverlays = [ ];
25 | in
26 | { inherit dependencyOverlays; })) dependencyOverlays;
27 |
28 | extra_pkg_config = {
29 | allow_unfree = true;
30 | };
31 |
32 |
33 | configuration = { pkgs, ... }:
34 | let
35 | patchUtils = pkgs.callPackage ./patchUtils.nix {};
36 | in
37 | {
38 | # The path to your config
39 | luaPath = ./config;
40 | ##########################
41 |
42 | # Plugins
43 | # Any plugins not under vimPlugins need to have custom substitutions
44 | # plugins = with pkgs.vimPlugins; [ ];
45 | plugins = with pkgs.vimPlugins; [
46 | # lazy
47 | lazy-nvim
48 |
49 | # completions
50 | nvim-cmp
51 | cmp_luasnip
52 | luasnip
53 | friendly-snippets
54 | cmp-path
55 | cmp-buffer
56 | cmp-nvim-lua
57 | cmp-nvim-lsp
58 | cmp-nvim-lsp-signature-help
59 |
60 | # telescope
61 | plenary-nvim
62 | telescope-nvim
63 | telescope-undo-nvim
64 | telescope-ui-select-nvim
65 | telescope-fzf-native-nvim
66 | todo-comments-nvim
67 | trouble-nvim
68 |
69 | # Formatting
70 | conform-nvim
71 |
72 | # lsp
73 | nvim-lspconfig
74 | fidget-nvim
75 | neodev-nvim
76 | rustaceanvim
77 | none-ls-nvim
78 |
79 | nvim-dap # rustaceanvim dep
80 |
81 | # treesitter
82 | nvim-treesitter-textobjects
83 | (nvim-treesitter.withPlugins (
84 | plugins: with plugins; [
85 | asm
86 | bash
87 | bibtex
88 | c
89 | cpp
90 | css
91 | html
92 | http
93 | javascript
94 | lua
95 | make
96 | markdown
97 | markdown_inline
98 | nix
99 | python
100 | rust
101 | toml
102 | typescript
103 | vim
104 | vimdoc
105 | xml
106 | yaml
107 |
108 | comment
109 | diff
110 | git_config
111 | git_rebase
112 | gitcommit
113 | gitignore
114 | gpg
115 | jq
116 | json
117 | json5
118 | llvm
119 | ssh_config
120 | ]
121 | ))
122 |
123 | # ui
124 | lualine-nvim
125 | nvim-web-devicons
126 | gitsigns-nvim
127 | nui-nvim
128 | neo-tree-nvim
129 | undotree
130 |
131 | # Color scheme
132 | onedark-nvim
133 | catppuccin-nvim
134 | tokyonight-nvim
135 |
136 | #misc
137 | vimtex
138 | comment-nvim
139 | vim-sleuth
140 | indent-blankline-nvim
141 | markdown-preview-nvim
142 | image-nvim
143 | autoclose-nvim
144 | ];
145 |
146 |
147 | # Runtime dependencies (think LSPs)
148 | # runtimeDeps = with pkgs; [ ];
149 | runtimeDeps = with pkgs; [
150 | universal-ctags
151 | tree-sitter
152 | ripgrep
153 | fd
154 | gcc
155 | nix-doc
156 | luarocks-nix
157 | lua5_1
158 |
159 | # lsps
160 | lua-language-server
161 | nodePackages_latest.typescript-language-server
162 | emmet-language-server
163 | tailwindcss-language-server
164 | llvmPackages.clang-unwrapped
165 | nil
166 | marksman
167 | pyright
168 |
169 | # Zig sucks bc the LSP is only suported for master
170 | # inputs.zls.packages.${pkgs.system}.zls
171 |
172 | # Rust
173 | rust-analyzer
174 | cargo
175 | rustc
176 |
177 | # Formatters
178 | prettierd
179 | stylua
180 | black
181 | rustfmt
182 | checkstyle
183 | languagetool-rust
184 |
185 | # latex
186 | texliveFull
187 |
188 | # Clipboard
189 | wl-clipboard-rs
190 | ];
191 |
192 | # Evironment variables available at run time
193 | environmentVariables = { };
194 |
195 | # Have a look here https://github.com/NixOS/nixpkgs/blob/master/pkgs/build-support/setup-hooks/make-wrapper.sh
196 | extraWrapperArgs = [ ];
197 |
198 | # Extra python3 packages (must be functions returning lists)
199 | extraPython3Packages = with pkgs; [ ];
200 |
201 | # Extra Lua packages (must be functions returning lists)
202 | extraLuaPackages = with pkgs; [ ];
203 |
204 | # Shared libraries available at run time
205 | sharedLibraries = with pkgs; [ ];
206 |
207 | aliases = [ "vim" "vi" "nvim" ];
208 |
209 | customSubs = with patchUtils; [];
210 | # For example, if you want to add a plugin with the short url
211 | # "cool/plugin" which is in nixpkgs as plugin-nvim you would do:
212 | # ++ (patchUtils.githubUrlSub "cool/plugin" plugin-nvim);
213 | # If you would want to replace the string "replace_me" with "replaced"
214 | # you would have to do:
215 | # ++ (patchUtils.stringSub "replace_me" "replaced")
216 | # For more examples look here: https://github.com/NicoElbers/nixPatch-nvim/blob/main/subPatches.nix
217 |
218 | settings = {
219 | withNodeJs = true;
220 | withRuby = true;
221 | withPerl = true;
222 | withPython3 = true;
223 | extraName = "";
224 | configDirName = "nvim";
225 | aliases = null;
226 | # neovim-unwrapped = null;
227 | neovim-unwrapped = inputs.neovim-nightly-overlay.packages.${pkgs.system}.neovim;
228 | patchSubs = true;
229 | suffix-path = false;
230 | suffix-LD = false;
231 | disablePythonSafePath = false;
232 | };
233 | };
234 | in
235 | forEachSystem (system:
236 | let
237 | inherit (builders) baseBuilder zigBuilder patcherBuilder;
238 | pkgs = nixpkgs.legacyPackages.${system}; # Get a copy of pkgs here for dev shells
239 |
240 | patcher = pkgs.callPackage zigBuilder {};
241 |
242 | configPatcher = (pkgs.callPackage patcherBuilder {}) {
243 | inherit nixpkgs patcher;
244 | };
245 |
246 | configWrapper = baseBuilder configPatcher {
247 | inherit nixpkgs system dependencyOverlays;
248 | };
249 | in {
250 | packages = rec {
251 | default = nixPatch;
252 | nixPatch = configWrapper { inherit configuration extra_pkg_config name; };
253 | };
254 |
255 | inherit configWrapper;
256 | patchUtils = pkgs.callPackage ./patchUtils.nix {};
257 |
258 | # Expose the nightly version from the which matches with the version
259 | # used in the runtime path created by nixPatch. This can help prevent linking
260 | # issues with for example treesitter:
261 | # https://github.com/nvim-treesitter/nvim-treesitter/issues/7275#issuecomment-2433541628
262 | neovim-nightly = inputs.neovim-nightly-overlay.packages.${pkgs.system}.neovim;
263 |
264 | devShells.default = with pkgs; mkShell {
265 | packages = [
266 | zig
267 | zls
268 | ];
269 | };
270 | }) // {
271 | templates = import ./templates;
272 | };
273 | }
274 |
275 |
--------------------------------------------------------------------------------
/nix/builders/default.nix:
--------------------------------------------------------------------------------
1 | {
2 | baseBuilder = import ./nixPatchBaseBuilder.nix;
3 | zigBuilder = import ./zigBuilder.nix;
4 | patcherBuilder = import ./patcherBuilder.nix;
5 | }
6 |
7 |
--------------------------------------------------------------------------------
/nix/builders/neovimWrapper.nix:
--------------------------------------------------------------------------------
1 | {
2 | stdenv
3 | , lib
4 | , makeWrapper
5 | , writeText
6 | , callPackage
7 | , lndir
8 | }:
9 | neovim-unwrapped:
10 | let
11 | lua = neovim-unwrapped.lua;
12 | wrapper = {
13 | luaConfig
14 | , packpathDirs
15 | , manifestRc
16 | , aliases ? null
17 | , extraName ? ""
18 | , wrapRc ? true
19 | , wrapperArgs ? ""
20 | , name ? "nixPatch"
21 | }:
22 | stdenv.mkDerivation (finalAttrs:
23 | let
24 | rtp = (callPackage ./rtpBuilder.nix {}) neovim-unwrapped packpathDirs;
25 |
26 | generatedWrapperArgs = [ "--set" "VIMRUNTIME" "${rtp}/runtime" ];
27 |
28 | finalMakeWrapperArgs =
29 | [ "${neovim-unwrapped}/bin/nvim" "${placeholder "out"}/bin/${name}"]
30 | ++ [ "--set" "NVIM_SYSTEM_RPLUGIN_MANIFEST" "${placeholder "out"}/rplugin.vim" ]
31 | ++ lib.optionals finalAttrs.wrapRc [ "--add-flags" "-u ${luaConfig}/init.lua" ]
32 | ++ generatedWrapperArgs;
33 |
34 | wrapperArgsStr = if lib.isString wrapperArgs then wrapperArgs else lib.escapeShellArgs wrapperArgs;
35 | in {
36 | name = "${name}-${lib.getVersion neovim-unwrapped}${extraName}";
37 |
38 | __structuredAttrs = true;
39 | dontUnpack = true;
40 |
41 | inherit wrapRc generatedWrapperArgs;
42 |
43 | postBuild = lib.optionalString stdenv.isLinux /*bash*/ ''
44 | mkdir -p $out/share/applications
45 | substitute ${neovim-unwrapped}/share/applications/nvim.desktop $out/share/applications/${name}.desktop \
46 | --replace-fail 'Name=Neovim' 'Name=${name}'\
47 | --replace-fail 'TryExec=nvim' 'TryExec=${name}'\
48 | --replace-fail 'Icon=nvim' 'Icon=${neovim-unwrapped}/share/icons/hicolor/128x128/apps/nvim.png'
49 |
50 | sed -i '/^Exec=nvim/c\Exec=${name} "%F"' $out/share/applications/${name}.desktop
51 |
52 | echo "created desktop file"
53 | ''
54 | + lib.optionalString (manifestRc != null) (let
55 | manifestWrapperArgs =
56 | [ "${neovim-unwrapped}/bin/nvim" "${placeholder "out"}/bin/nvim-wrapper" ]
57 | ++ finalAttrs.generatedWrapperArgs;
58 | in /*bash*/ ''
59 | # Copied straight from nixpkgs, not 100% certain what it does
60 | # but no need to remove it
61 | echo "Generating remote plugin manifest"
62 | export NVIM_RPLUGIN_MANIFEST=$out/rplugin.vim
63 | makeWrapper ${lib.escapeShellArgs manifestWrapperArgs} ${wrapperArgsStr}
64 |
65 | # Some plugins assume that the home directory is accessible for
66 | # initializing caches, temporary files, etc. Even if the plugin isn't
67 | # actively used, it may throw an error as soon as Neovim is launched
68 | # (e.g., inside an autoload script), causing manifest generation to
69 | # fail. Therefore, let's create a fake home directory before generating
70 | # the manifest, just to satisfy the needs of these plugins.
71 | #
72 | # See https://github.com/Yggdroot/LeaderF/blob/v1.21/autoload/lfMru.vim#L10
73 | # for an example of this behavior.
74 | export HOME="$(mktemp -d)"
75 |
76 |
77 | # Launch neovim with a vimrc file containing only the generated plugin
78 | # code. Pass various flags to disable temp file generation
79 | # (swap/viminfo) and redirect errors to stderr.
80 | # Only display the log on error since it will contain a few normally
81 | # irrelevant messages.
82 | if ! $out/bin/nvim-wrapper \
83 | -u ${writeText "manifest.vim" manifestRc} \
84 | -i NONE -n \
85 | -V1rplugins.log \
86 | +UpdateRemotePlugins +quit! > outfile 2>&1; then
87 | cat outfile
88 | echo -e "\nGenerating rplugin.vim failed!"
89 | exit 1
90 | fi
91 | rm "${placeholder "out"}/bin/nvim-wrapper"
92 |
93 | # Not sure why this touch is here, but I guess it's a good failsafe
94 | # in case the previous code doesn't work for whatever reason
95 | touch $out/rplugin.vim
96 | '')
97 | + /* bash */ ''
98 |
99 | echo "Looking for lua dependencies..."
100 |
101 | # Ignore failures here, since older nix versions didn't have these
102 | # functions
103 | source ${lua}/nix-support/utils.sh || true
104 | _addToLuaPath "${rtp}" || true
105 |
106 | echo "LUA_PATH: $LUA_PATH"
107 | echo "LUA_CPATH: $LUA_CPATH"
108 |
109 | makeWrapper ${lib.escapeShellArgs finalMakeWrapperArgs} ${wrapperArgsStr} \
110 | --prefix LUA_PATH ';' "$LUA_PATH" \
111 | --prefix LUA_CPATH ';' "$LUA_CPATH"
112 | ''
113 | # Finally, symlink some aliases
114 | + lib.optionalString (aliases != null)
115 | (builtins.concatStringsSep "\n" (builtins.map (alias: /*bash*/ ''
116 | ln -s $out/bin/${name} $out/bin/${alias}
117 | '') aliases));
118 |
119 | preferLocalBuild = true;
120 | nativeBuildInputs = [ makeWrapper lndir ];
121 |
122 | passthru = {
123 | finalPackDir = rtp;
124 |
125 | unwrapped = neovim-unwrapped;
126 | config = luaConfig;
127 | };
128 |
129 | meta = neovim-unwrapped.meta // {
130 | hydraPlatforms = [ ];
131 | priority = (neovim-unwrapped.meta.priority or 0) -1;
132 | };
133 | });
134 | in
135 | lib.makeOverridable wrapper
136 |
--------------------------------------------------------------------------------
/nix/builders/nixPatchBaseBuilder.nix:
--------------------------------------------------------------------------------
1 | patcher:
2 | {
3 | # We require nixpkgs because the wrapper parses it later
4 | nixpkgs
5 | , system
6 | , dependencyOverlays
7 | }:
8 | # TODO: When we name change, probably best to make name a required argument
9 | # and maybe give an option to do name-override
10 | { configuration, specialArgs ? null, extra_pkg_config ? {}, name ? "nv" }:
11 | let
12 | utils = import ../utils;
13 |
14 | # Create packages with your specified overlays
15 | pkgs = import nixpkgs ({
16 | inherit system;
17 | overlays = if builtins.isList dependencyOverlays
18 | then dependencyOverlays
19 | else if builtins.isAttrs dependencyOverlays && builtins.hasAttr system dependencyOverlays
20 | then dependencyOverlays.${system}
21 | else [];
22 | } // { config = extra_pkg_config; });
23 | lib = pkgs.lib;
24 |
25 | # Extract your configuration
26 | rawconfiguration = configuration { inherit pkgs system specialArgs; };
27 |
28 | finalConfiguration = {
29 | # luaPath cannot be merged
30 | plugins = [ ];
31 | aliases = [ ];
32 | runtimeDeps = [ ];
33 | environmentVariables = { };
34 | extraWrapperArgs = [ ];
35 |
36 | python3Packages = [ ];
37 | extraPython3WrapperArgs = [ ];
38 |
39 | luaPackages = [ ];
40 |
41 | sharedLibraries = [ ];
42 |
43 | extraConfig = [ ];
44 | customSubs = [ ];
45 | }
46 | // rawconfiguration;
47 |
48 | finalSettings = {
49 | withNodeJs = false;
50 | withRuby = false;
51 | withPerl = false;
52 | withPython3 = false;
53 | extraName = "";
54 | configDirName = "nvim";
55 | neovim-unwrapped = null;
56 | patchSubs = true;
57 | suffix-path = false;
58 | suffix-LD = false;
59 | }
60 | // rawconfiguration.settings
61 | # TODO: Make wrapRc optional by adding an option to put
62 | # config in xdg.config
63 | #
64 | # Maybe it's a better idea to not have wrapRc at all, and just give an option
65 | # to do the quick patch thing automatically putting your config in /tmp.
66 | # Since nv patches your config, it should _always_ be wrapped.
67 | // { wrapRc = true; };
68 |
69 | inherit (finalConfiguration)
70 | luaPath plugins runtimeDeps extraConfig
71 | environmentVariables python3Packages
72 | extraPython3WrapperArgs customSubs aliases
73 | extraWrapperArgs sharedLibraries luaPackages;
74 |
75 | inherit (finalSettings)
76 | withNodeJs withRuby withPerl withPython3
77 | extraName configDirName neovim-unwrapped
78 | suffix-path patchSubs suffix-LD wrapRc;
79 |
80 | neovim = if neovim-unwrapped == null then pkgs.neovim-unwrapped else neovim-unwrapped;
81 |
82 | # Setup environments
83 | appendPathPos = if suffix-path then "suffix" else "prefix";
84 | appendLinkPos = if suffix-LD then "suffix" else "prefix";
85 |
86 | getEnv = env: lib.flatten
87 | (lib.mapAttrsToList (n: v: [ "--set" "${n}" "${v}" ]) env);
88 |
89 | mappedPlugins = map (p: { plugin = p; optional = true; }) plugins;
90 |
91 | packpathDirs.packages =
92 | let
93 | part = lib.partition (x: x.optional == true) mappedPlugins;
94 | in
95 | {
96 | start = map (x: x.plugin) part.wrong;
97 | opt = map (x: x.plugin) part.right;
98 | };
99 |
100 | getDeps = attrname: map (plugin: plugin.${attrname} or (_: [ ]));
101 |
102 | # Evn implementations from
103 | # https://github.com/NixOS/nixpkgs/blob/748db8ec5cbae3c0bddf63845dc4de51ec6a68d9/pkgs/applications/editors/neovim/utils.nix#L26-L122
104 | extraPython3PackagesCombined = utils.combineFns python3Packages;
105 | pluginPython3Packages = getDeps "python3Dependencies" plugins;
106 | python3Env = pkgs.python3Packages.python.withPackages (ps:
107 | [ ps.pynvim ]
108 | ++ (extraPython3PackagesCombined ps)
109 | ++ (lib.concatMap (f: f ps) pluginPython3Packages));
110 |
111 | rubyEnv = pkgs.bundlerEnv {
112 | name = "neovim-ruby-env";
113 | gemdir = ./ruby_provider;
114 | postBuild = ''
115 | ln -sf ${pkgs.ruby}/bin/* $out/bin
116 | '';
117 | };
118 |
119 | perlEnv = pkgs.perl.withPackages (p: [ p.NeovimExt p.Appcpanminus ]);
120 |
121 | luaEnv = neovim.lua.withPackages (utils.combineFns luaPackages);
122 |
123 | wrapperArgs =
124 | let
125 | binPath = lib.makeBinPath (runtimeDeps
126 | ++ lib.optionals withNodeJs
127 | [ pkgs.nodejs ]
128 | ++ lib.optionals withRuby
129 | [ pkgs.ruby ]
130 | ++ lib.optionals withPython3
131 | [ pkgs.python3 ]);
132 |
133 | in
134 | getEnv environmentVariables
135 | ++
136 | lib.optionals (configDirName != null && configDirName != "nvim")
137 | [ "--set" "NVIM_APPNAME" "${configDirName}" ]
138 | ++ lib.optionals (runtimeDeps != [])
139 | [ "--${appendPathPos}" "PATH" ":" "${binPath}" ]
140 | ++ lib.optionals (sharedLibraries != [])
141 | [ "--${appendLinkPos}" "PATH" ":" "${lib.makeBinPath sharedLibraries}" ]
142 | ++ lib.optionals (luaEnv != null)
143 | [
144 | "--prefix" "LUA_PATH" ";" (neovim.lua.pkgs.luaLib.genLuaPathAbsStr luaEnv)
145 | "--prefix" "LUA_CPATH" ";" (neovim.lua.pkgs.luaLib.genLuaCPathAbsStr luaEnv)
146 | ]
147 | ++
148 | (
149 | # TODO: Do more type safety like this, type safety my beloved
150 | if builtins.isList extraWrapperArgs then extraWrapperArgs
151 | else if builtins.isString then [extraWrapperArgs]
152 | else throw "extraWrapperArgs should be a string or list of strings"
153 | );
154 |
155 | # Get the patched lua config
156 | customSubsPatched =
157 | customSubs
158 | ++ lib.optionals patchSubs ((pkgs.callPackage ./../../subPatches.nix {}) plugins);
159 |
160 | luaConfig = patcher {
161 | inherit luaPath plugins name;
162 | inherit extraConfig;
163 |
164 | customSubs = customSubsPatched;
165 |
166 | inherit withNodeJs;
167 | inherit withRuby rubyEnv;
168 | inherit withPerl perlEnv;
169 | inherit withPython3 python3Env extraPython3WrapperArgs ;
170 | };
171 |
172 | # Copied from
173 | # https://github.com/NixOS/nixpkgs/blob/3178439a4e764da70ca83f47bc144a2a276b2f0b/pkgs/applications/editors/vim/plugins/vim-utils.nix#L227-L277
174 | manifestRc = ''
175 | set nocompatible
176 | '';
177 | in
178 | # Do the now actually put everything together
179 | (pkgs.callPackage ./neovimWrapper.nix {}) neovim {
180 | inherit luaConfig;
181 | inherit wrapRc wrapperArgs;
182 | inherit aliases;
183 | inherit name extraName;
184 | inherit manifestRc;
185 | inherit packpathDirs;
186 | }
187 |
--------------------------------------------------------------------------------
/nix/builders/patcherBuilder.nix:
--------------------------------------------------------------------------------
1 | { lib, stdenvNoCC, callPackage }:
2 | { patcher , nixpkgs }:
3 | {
4 | luaPath
5 | , plugins
6 | , name
7 | , withNodeJs
8 | , withRuby
9 | , rubyEnv ? null
10 | , withPerl
11 | , perlEnv ? null
12 | , withPython3
13 | , python3Env ? null
14 | , extraPython3WrapperArgs ? []
15 | , extraConfig ? []
16 | , customSubs ? []
17 | }:
18 | let
19 | nixpkgsOutPath = nixpkgs.outPath;
20 |
21 | # Put all your plugins in the format nv expects
22 | inputBlob = [(builtins.concatStringsSep ";"
23 | (builtins.map (plugin: "${plugin.pname}|${plugin.version}|${plugin}") plugins))];
24 |
25 | inputBlobEscaped = (if inputBlob == [""]
26 | then "-"
27 | else lib.escapeShellArgs inputBlob);
28 |
29 | # Put all your substitutions in the format nv expects
30 | subToBlobItem = s:
31 | let
32 | type = if s ? type then s.type else "string";
33 | extra = if s ? extra && s.extra != null then s.extra else "-";
34 | in
35 | "${type}|${s.from}|${s.to}|${extra}";
36 |
37 | subBlob = [(builtins.concatStringsSep ";"
38 | (map subToBlobItem customSubs))];
39 |
40 | subBlobEscaped = (if subBlob == [""]
41 | then "-"
42 | else lib.escapeShellArgs subBlob);
43 |
44 | # Link all the provider binaries in one directory
45 | providers = (callPackage ./providerBuilder.nix {}) {
46 | inherit name;
47 | inherit withNodeJs;
48 | inherit withRuby rubyEnv;
49 | inherit withPerl perlEnv;
50 | inherit withPython3 python3Env extraPython3WrapperArgs;
51 | };
52 |
53 | # Generate the lua code to tell neovim about our providers
54 | hostprog_check_table = {
55 | node = withNodeJs;
56 | python = false;
57 | python3 = withPython3;
58 | ruby = withRuby;
59 | perl = withPerl;
60 | };
61 |
62 | genProviderCmd = prog: withProg:
63 | if withProg
64 | then "vim.g.${prog}_host_prog='${providers}/bin/${name}-${prog}'"
65 | else "vim.g.loaded_${prog}_provider=0";
66 |
67 | hostProviderLua = lib.mapAttrsToList genProviderCmd hostprog_check_table;
68 |
69 | finalExtraConfig = builtins.concatStringsSep "\n" (
70 | [ "-- Config generated by ${name}"]
71 | # Advertise providers to your config
72 | ++ hostProviderLua
73 | # Advertise to your config that this is nix
74 | ++ [ "vim.g.nix = true"]
75 | # Make sure nvim looks at the correct config
76 | ++ [ ''
77 | vim.g.configdir = vim.fn.stdpath('config')
78 | vim.opt.packpath:remove(vim.g.configdir)
79 | vim.opt.runtimepath:remove(vim.g.configdir)
80 | vim.opt.runtimepath:remove(vim.g.configdir .. "/after")
81 | vim.g.configdir = [[${placeholder "out"}]]
82 | vim.opt.packpath:prepend(vim.g.configdir)
83 | vim.opt.runtimepath:prepend(vim.g.configdir)
84 | vim.opt.runtimepath:append(vim.g.configdir .. "/after")
85 | '']
86 | ++ [ "-- Extra config provided by user"]
87 | ++ (if (builtins.isList extraConfig) then extraConfig else [extraConfig])
88 | ++ [ "-- Lua config"]
89 | ++ [ "\n" ]);
90 |
91 | finalExtraConfigEscaped = lib.escapeShellArgs [finalExtraConfig];
92 | in
93 | stdenvNoCC.mkDerivation {
94 | name = "nvim-config-patched";
95 | version = "0";
96 | # This should always be the source, because we need to rebuild when this changes.
97 | # No fancy "optimizations" to not copy it to the store
98 | src = luaPath;
99 |
100 | dontConfigure = true;
101 | dontInstall = true;
102 |
103 | # TODO: Set the variables as env variables
104 | buildPhase = /* bash */ ''
105 | ${lib.getExe patcher} \
106 | ${nixpkgsOutPath} \
107 | $(pwd) \
108 | $out \
109 | ${inputBlobEscaped} \
110 | ${subBlobEscaped} \
111 | ${finalExtraConfigEscaped}
112 | '';
113 | }
114 |
--------------------------------------------------------------------------------
/nix/builders/providerBuilder.nix:
--------------------------------------------------------------------------------
1 | { lib, stdenv, makeWrapper, neovim-node-client }:
2 | {
3 | name
4 | , withNodeJs
5 | , withRuby
6 | , rubyEnv ? null
7 | , withPerl
8 | , perlEnv ? null
9 | , withPython3
10 | , python3Env ? null
11 | , extraPython3WrapperArgs ? []
12 | }:
13 | stdenv.mkDerivation {
14 | name = "${name}-providers";
15 |
16 | __structuredAttrs = true;
17 | dontUnpack = true;
18 |
19 | nativeBuildInputs = [ makeWrapper ];
20 |
21 | # Link all the providers into one directory
22 | postBuild = ''
23 | mkdir -p $out/bin
24 | ''
25 | + lib.optionalString withPython3 ''
26 | makeWrapper ${python3Env.interpreter} $out/bin/${name}-python3 --unset PYTHONPATH ${builtins.concatStringsSep " " extraPython3WrapperArgs}
27 |
28 | ''
29 | + lib.optionalString withRuby ''
30 | ln -s ${rubyEnv}/bin/neovim-ruby-host $out/bin/${name}-ruby
31 |
32 | ''
33 | + lib.optionalString withNodeJs ''
34 | ln -s ${neovim-node-client}/bin/neovim-node-host $out/bin/${name}-node
35 |
36 | ''
37 | + lib.optionalString withPerl ''
38 | ln -s ${perlEnv}/bin/perl $out/bin/${name}-perl
39 |
40 | '';
41 | }
42 |
--------------------------------------------------------------------------------
/nix/builders/rtpBuilder.nix:
--------------------------------------------------------------------------------
1 | # Effectively copied and minimized from
2 | # https://github.com/BirdeeHub/nixCats-nvim/blob/77f400d39ad26023f818de365a078d6d532a2c56/nix/builder/vim-pack-dir.nix
3 | {
4 | lib
5 | , stdenv
6 | , buildEnv
7 | , writeText
8 | , runCommand
9 | , python3
10 | , linkFarm
11 | }:
12 | neovim-unwrapped:
13 | let
14 | depFinderClosure = plugin:
15 | [plugin] ++ (lib.unique (builtins.concatLists (map depFinderClosure plugin.dependencies or [])));
16 |
17 | recursiveDepFinder = plugins: lib.concatMap depFinderClosure plugins;
18 |
19 | vimFarm = prefix: name: drvs:
20 | linkFarm name (map (drv: { name = "${prefix}/${lib.getName drv}"; path = drv; }) drvs);
21 |
22 | # apparently this is ACTUALLY the standard. Yeah. Strings XD
23 | # If its stable enough for nixpkgs I guess I can use it here.
24 | grammarMatcher = entry:
25 | (if entry != null && entry.name != null then
26 | (if (builtins.match "^vimplugin-treesitter-grammar-.*" entry.name) != null
27 | then true else false)
28 | else false);
29 |
30 | mkEntryFromDrv = drv: { name = "${lib.getName drv}"; value = drv; };
31 |
32 | # TODO: Go through all this and simplify
33 | # - I know that I can remove the start, opt things, and change it to just a list
34 | # of plugins. I only use one regardless.
35 | # - I might be able to put any of our plugins in the rtp, that way I think a
36 | # lazy warning will go away
37 | packDir = packages:
38 | let
39 | packageLinks = packageName: {start ? [], opt ? []}:
40 | let
41 | depsOfOptionalPlugins = lib.subtractLists opt (recursiveDepFinder opt);
42 | startWithDeps = recursiveDepFinder start;
43 | allPlugins = lib.unique (startWithDeps ++ depsOfOptionalPlugins);
44 |
45 | allPluginsMapped = (map mkEntryFromDrv allPlugins);
46 |
47 | startPlugins = builtins.listToAttrs
48 | (builtins.filter (entry: ! (grammarMatcher entry)) allPluginsMapped);
49 |
50 | allPython3Dependencies = ps:
51 | lib.flatten (builtins.map (plugin: (plugin.python3Dependencies or (_: [])) ps) allPlugins);
52 | python3Env = python3.withPackages allPython3Dependencies;
53 |
54 | ts_grammar_plugin = with builtins; stdenv.mkDerivation (let
55 | treesitter_grammars = (map (entry: entry.value)
56 | (filter (entry: grammarMatcher entry) allPluginsMapped));
57 |
58 | builderLines = map (grmr: /* bash */''
59 | cp --no-dereference ${grmr}/parser/*.so $out/parser
60 | '') treesitter_grammars;
61 |
62 | builderText = /* bash */''
63 | #!/usr/bin/env bash
64 | source $stdenv/setup
65 | mkdir -p $out/parser
66 | '' + (concatStringsSep "\n" builderLines);
67 |
68 | in {
69 | name = "vimplugin-treesitter-grammar-ALL-INCLUDED";
70 | builder = writeText "builder.sh" builderText;
71 | });
72 |
73 | packdirStart = vimFarm "pack/${packageName}/start" "packdir-start"
74 | ( (builtins.attrValues startPlugins) ++ [ts_grammar_plugin]);
75 |
76 | packdirOpt = vimFarm "pack/${packageName}/opt" "packdir-opt" opt;
77 |
78 | # Assemble all python3 dependencies into a single `site-packages` to avoid doing recursive dependency collection
79 | # for each plugin.
80 | # This directory is only for python import search path, and will not slow down the startup time.
81 | # see :help python3-directory for more details
82 | python3link = runCommand "vim-python3-deps" {} ''
83 | mkdir -p $out/pack/${packageName}/start/__python3_dependencies
84 | ln -s ${python3Env}/${python3Env.sitePackages} $out/pack/${packageName}/start/__python3_dependencies/python3
85 | '';
86 | in
87 | [ (neovim-unwrapped + "/share/nvim/runtime") packdirStart packdirOpt ]
88 | ++ lib.optional (allPython3Dependencies python3.pkgs != []) python3link;
89 | in
90 | buildEnv {
91 | # TODO: Use a name variable here
92 | name = "nv-rtp";
93 | paths = (lib.flatten (lib.mapAttrsToList packageLinks packages));
94 | # gather all propagated build inputs from packDir
95 | postBuild = ''
96 | echo "rtp: $out"
97 |
98 | mkdir -p $out/runtime
99 | find $out -mindepth 1 -maxdepth 1 \
100 | ! -name runtime \
101 | ! -name nix-support \
102 | -exec mv {} $out/runtime/ \;
103 |
104 | mkdir $out/nix-support
105 | for i in $(find -L $out -name propagated-build-inputs ); do
106 | cat "$i" >> $out/nix-support/propagated-build-inputs
107 | done
108 | '';
109 | };
110 | in packDir
111 |
--------------------------------------------------------------------------------
/nix/builders/ruby_provider/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gem 'neovim'
4 |
--------------------------------------------------------------------------------
/nix/builders/ruby_provider/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | msgpack (1.7.2)
5 | multi_json (1.15.0)
6 | neovim (0.10.0)
7 | msgpack (~> 1.1)
8 | multi_json (~> 1.0)
9 |
10 | PLATFORMS
11 | ruby
12 |
13 | DEPENDENCIES
14 | neovim
15 |
16 | BUNDLED WITH
17 | 2.3.27
18 |
--------------------------------------------------------------------------------
/nix/builders/ruby_provider/gemset.nix:
--------------------------------------------------------------------------------
1 | # Copied verbatim from https://github.com/Gerg-L/mnw/blob/4ea225024677e7c3a96080af8624fd3aa5dfa1b6/ruby_provider/gemset.nix
2 | {
3 | msgpack = {
4 | groups = [ "default" ];
5 | platforms = [ ];
6 | source = {
7 | remotes = [ "https://rubygems.org" ];
8 | sha256 = "1a5adcb7bwan09mqhj3wi9ib52hmdzmqg7q08pggn3adibyn5asr";
9 | type = "gem";
10 | };
11 | version = "1.7.2";
12 | };
13 | multi_json = {
14 | groups = [ "default" ];
15 | platforms = [ ];
16 | source = {
17 | remotes = [ "https://rubygems.org" ];
18 | sha256 = "sha256-H9BBOLbkqQAX6NG4BMA5AxOZhm/z+6u3girqNnx4YV0=";
19 | type = "gem";
20 | };
21 | version = "1.15.0";
22 | };
23 | neovim = {
24 | dependencies = [
25 | "msgpack"
26 | "multi_json"
27 | ];
28 | groups = [ "default" ];
29 | platforms = [ ];
30 | source = {
31 | remotes = [ "https://rubygems.org" ];
32 | sha256 = "0gl34rriwwmj6p1s6ms0b311wmqaqiyc510svq31283jk0kp0qcd";
33 | type = "gem";
34 | };
35 | version = "0.10.0";
36 | };
37 | }
38 |
--------------------------------------------------------------------------------
/nix/builders/zigBuilder.nix:
--------------------------------------------------------------------------------
1 | { lib, stdenvNoCC, zig_0_14 }:
2 | stdenvNoCC.mkDerivation {
3 | pname = "config-patcher";
4 | version = "0";
5 |
6 | src = lib.sources.sourceByRegex (lib.cleanSource ../../.) [".*patcher.*"];
7 |
8 | nativeBuildInputs = [ zig_0_14 ];
9 |
10 | dontConfigure = true;
11 | dontInstall = true;
12 | doCheck = true;
13 |
14 | buildPhase = ''
15 | mkdir -p .cache
16 |
17 | # Explicitly a debug build, since it cuts total build time by abt 10 seconds
18 | zig build-exe \
19 | -ODebug \
20 | --dep lib \
21 | -Mroot=$(pwd)/patcher/src/main.zig \
22 | -ODebug \
23 | -Mlib=$(pwd)/patcher/src/lib/root.zig \
24 | --cache-dir $(pwd)/.zig-cache \
25 | --global-cache-dir $(pwd)/.cache \
26 | --name config-patcher
27 |
28 | mkdir -p $out/bin
29 | cp config-patcher $out/bin
30 | '';
31 |
32 | checkPhase = ''
33 | zig test \
34 | -ODebug \
35 | --dep lib \
36 | -Mroot=$(pwd)/patcher/test/root.zig \
37 | -ODebug \
38 | -Mlib=$(pwd)/patcher/src/lib/root.zig \
39 | --cache-dir $(pwd)/.zig-cache \
40 | --global-cache-dir $(pwd)/.cache \
41 | --name test
42 |
43 | zig test \
44 | -ODebug \
45 | --dep lib \
46 | -Mroot=$(pwd)/patcher/src/lib/root.zig \
47 | -ODebug \
48 | -Mlib=$(pwd)/patcher/src/lib/root.zig \
49 | --cache-dir $(pwd)/.zig-cache \
50 | --global-cache-dir $(pwd)/.cache \
51 | --name test
52 | '';
53 |
54 | meta.mainProgram = "config-patcher";
55 | }
56 |
--------------------------------------------------------------------------------
/nix/utils/default.nix:
--------------------------------------------------------------------------------
1 | with builtins; rec {
2 | eachSystem = systems: f:
3 | let
4 | # Merge together the outputs for all systems.
5 | op = attrs: system:
6 | let
7 | ret = f system;
8 | op = attrs: key: attrs //
9 | {
10 | ${key} = (attrs.${key} or { })
11 | // { ${system} = ret.${key}; };
12 | }
13 | ;
14 | in
15 | foldl' op attrs (attrNames ret);
16 | in
17 | foldl' op { }
18 | (systems
19 | ++ # add the current system if --impure is used
20 | (if builtins ? currentSystem then
21 | if elem currentSystem systems
22 | then []
23 | else [ currentSystem ]
24 | else []));
25 |
26 | flattenToListWith = flattenFn: attrset: concatMap
27 | (v:
28 | if isAttrs v && !lib.isDerivation v then flattenToListWith v
29 | else if isList v then v
30 | else if v != null then [v]
31 | else []
32 | ) (flattenFn attrset);
33 |
34 | flattenToList = flattenToListWith attrValues;
35 |
36 | flattenMapAttrLeaves = twoArgFn: attrset:
37 | let
38 | mapLeaves = attr: attrValues (mapAttrs (n: v:
39 | if isAttrs v then v
40 | else (twoArgFn n v)
41 | ) attr);
42 | in
43 | flattenToListWith mapLeaves attrset;
44 |
45 |
46 | # Getting python deps
47 | getDeps = attrname: map (plugin: plugin.${attrname} or (_: [ ]));
48 |
49 | combineFns = values: x: (builtins.map (value: value x) values);
50 | }
51 |
--------------------------------------------------------------------------------
/patchUtils.nix:
--------------------------------------------------------------------------------
1 | { lib }:
2 | rec {
3 | # This will create a plugin substitution. To be exact, it will look for either
4 | # `url = "${url}"` or `"${url}"` and change it to
5 | # `dir = [[${plugin}]], name = [[${plugin.pname}]]`
6 | # Any type of lua string will work, `''` `""` or `[[]]`, any amount of whitespace
7 | # beteen 'url', '=' and ${url} will work.
8 | urlSub = url: plugin:
9 | [{
10 | type = "plugin";
11 | from = "${url}";
12 | to = "${plugin}";
13 | extra = "${plugin.pname}";
14 | }];
15 |
16 | # This will create 2 plugin substitutions as per the `urlSub` function.
17 | # One with the url being the shortUrl passed in, and another with
18 | # "https://github.com/${shortUrl}"
19 | githubUrlSub = shortUrl: plugin:
20 | (urlSub shortUrl plugin)
21 | ++ (urlSub "https://github.com/${shortUrl}" plugin);
22 |
23 | # This will substitute the lua string "${from}" to "${to}".
24 | # Any type of lua string will work, `''` `""` or `[[]]`.
25 | stringSub = from: to:
26 | [{
27 | type = "string";
28 | from = from;
29 | to = to;
30 | extra = null;
31 | }];
32 |
33 | # This will substitute the lua string "${from}" to "${to}" if and only if
34 | # the string has a key "${key}".
35 | # In practise this transforms
36 | # `${key} = [[${from}]]`
37 | # to
38 | # `${key} = [[${to}]]`
39 | # Any type of lua string will work, `''` `""` or `[[]]`, any amount of whitespace
40 | # beteen ${key}, '=' and ${from} will work.
41 | keyedStringSub = from: to: key:
42 | [{
43 | type = "string";
44 | from = from;
45 | to = to;
46 | extra = key;
47 | }];
48 |
49 | hasPlugin = plugins: plugin: lib.lists.any (p: p == plugin) plugins;
50 | optPatch = plugins: plugin: sub: lib.optionals (hasPlugin plugins plugin) sub;
51 | }
52 |
--------------------------------------------------------------------------------
/patcher/src/lib/LuaIter.zig:
--------------------------------------------------------------------------------
1 | buf: []const u8,
2 | ptr: usize = 0,
3 | lua_string_cache: ?LuaStringCache = null,
4 |
5 | const LuaStringCache = struct {
6 | ptr_check: usize,
7 |
8 | lua_string_type: LuaStringType,
9 | next_lua_string_start_on: usize,
10 | next_lua_string_end: ?usize,
11 |
12 | pub const LuaStringType = enum {
13 | singleQuote,
14 | doubleQuote,
15 | multiLine,
16 | };
17 |
18 | pub fn isValid(self: LuaStringCache, ptr: usize) bool {
19 | return self.ptr_check == ptr;
20 | }
21 |
22 | pub fn getNextLUaStringType(self: LuaStringCache, ptr: usize) ?LuaStringType {
23 | if (!self.isValid(ptr)) return null;
24 |
25 | return self.lua_string_type;
26 | }
27 |
28 | pub fn getNextLuaStringOpeningOn(self: LuaStringCache, ptr: usize) ?usize {
29 | if (!self.isValid(ptr)) return null;
30 |
31 | return self.next_lua_string_start_on;
32 | }
33 |
34 | pub fn getNextLuaStringEnd(self: LuaStringCache, ptr: usize) ?usize {
35 | if (!self.isValid(ptr)) return null;
36 |
37 | return self.next_lua_string_end;
38 | }
39 | };
40 |
41 | /// A call to next will show the first character
42 | pub fn init(buf: []const u8) LuaIter {
43 | return LuaIter{
44 | .buf = buf,
45 | .ptr = 0,
46 | .lua_string_cache = null,
47 | };
48 | }
49 |
50 | /// A call to back will show the last character
51 | pub fn initBack(buf: []const u8) LuaIter {
52 | return LuaIter{
53 | .buf = buf,
54 | .ptr = buf.len,
55 | .lua_string_cache = null,
56 | };
57 | }
58 |
59 | pub fn isDone(self: LuaIter) bool {
60 | return self.ptr < 0 or self.ptr >= self.buf.len;
61 | }
62 |
63 | pub fn isDoneReverse(self: LuaIter) bool {
64 | return self.ptr <= 0 or self.ptr > self.buf.len;
65 | }
66 |
67 | pub fn peek(self: LuaIter) ?u8 {
68 | if (self.isDone()) return null;
69 | return self.buf[self.ptr];
70 | }
71 |
72 | pub fn next(self: *LuaIter) ?u8 {
73 | defer self.ptr += 1;
74 | return self.peek();
75 | }
76 |
77 | pub fn peekBack(self: LuaIter) ?u8 {
78 | return self.peekBackBy(1);
79 | }
80 |
81 | pub fn peekBackBy(self: LuaIter, by: usize) ?u8 {
82 | if (self.ptr < by) return null;
83 | return self.buf[self.ptr - by];
84 | }
85 |
86 | pub fn back(self: *LuaIter) ?u8 {
87 | defer if (!self.isDoneReverse()) {
88 | self.ptr -= 1;
89 | };
90 | return self.peekBack();
91 | }
92 |
93 | pub fn peekNextLuaString(self: *LuaIter) ?[]const u8 {
94 | const start = self.findLuaStringOpeningAfter() orelse return null;
95 | const stop = self.findLuaStringClosingOn() orelse return null;
96 | return self.buf[start..stop];
97 | }
98 |
99 | /// Returns the slice before the next lua string
100 | /// "String"
101 | /// ^
102 | /// Until this is returned
103 | pub fn peekNextUntilLuaString(self: *LuaIter) ?[]const u8 {
104 | if (self.isDone()) return null;
105 | const string_start = self.findLuaStringOpeningOn() orelse return null;
106 | return self.buf[self.ptr..string_start];
107 | }
108 |
109 | pub fn peekNextStringTableHasKey(self: *LuaIter, key: []const u8) bool {
110 | const string_idx = self.findLuaStringOpeningOn() orelse return false;
111 | var backIter = LuaIter.initBack(self.buf[0..string_idx]);
112 |
113 | // Find the relevant start of table
114 | var brace_counter: u32 = 0;
115 | loop: while (backIter.back()) |char| switch (char) {
116 | '}' => brace_counter += 1,
117 | '{' => {
118 | if (brace_counter == 0)
119 | break :loop
120 | else
121 | brace_counter -= 1;
122 | },
123 | else => {},
124 | } else return false;
125 |
126 | // Find the equals
127 | loop: while (backIter.back()) |char| {
128 | // Ignore whitespace
129 | if (isInlineWhitespace(char)) continue;
130 |
131 | // If we do _not_ find an '=', this is invalid or irrelevant lua (I think)
132 | if (char != '=')
133 | return false;
134 |
135 | break :loop;
136 | } else return false;
137 |
138 | // Ignore whitespace
139 | while (isAnyWhitespace(backIter.back() orelse return false)) {}
140 | // Make sure we include the last character of the key
141 | _ = backIter.next();
142 |
143 | // There isn't enough space to fit the key
144 | if (backIter.ptr < key.len)
145 | return false;
146 |
147 | // If the key had more characters before it (say `xkey`),
148 | // then make sure that we return false
149 | const before_key = backIter.peekBackBy(key.len + 1);
150 | if (before_key != null and !isAnyWhitespace(before_key.?)) return false;
151 |
152 | const maybe_key = backIter.buf[(backIter.ptr - key.len)..backIter.ptr];
153 | return std.mem.eql(u8, key, maybe_key);
154 | }
155 |
156 | /// This function is equal to the following:
157 | /// `{key}\s*=\s*{string}`
158 | /// where:
159 | /// {key} is your input,
160 | /// \s* is any amount of whitespace excluding \n,
161 | /// = is the literal character,
162 | /// {string} is the lua string,
163 | ///
164 | /// It returns a slice from self.ptr to the first character of the key
165 | pub fn peekUntilNextLuaStringKey(self: *LuaIter, key: []const u8) ?[]const u8 {
166 | const before_string = self.peekNextUntilLuaString() orelse return null;
167 |
168 | if (before_string.len <= key.len) return null;
169 |
170 | var iter = initBack(before_string);
171 | while (iter.back()) |char| {
172 | if (isInlineWhitespace(char)) continue;
173 | if (char == '=') break;
174 |
175 | // Non white space character is not '='
176 | return null;
177 | }
178 |
179 | const last_key_char = key[key.len - 1];
180 |
181 | while (iter.back()) |char| {
182 | if (isInlineWhitespace(char)) continue;
183 | if (char != last_key_char) return null;
184 |
185 | // Make sure to include the end char
186 | _ = iter.next();
187 | break;
188 | }
189 |
190 | if (iter.ptr >= key.len and
191 | mem.eql(u8, key, iter.buf[(iter.ptr - key.len)..iter.ptr]))
192 | {
193 | return iter.buf[0..(iter.ptr - key.len)];
194 | } else {
195 | return null;
196 | }
197 | }
198 | pub fn nextUntilBefore(self: *LuaIter, until: []const u8) ?[]const u8 {
199 | const str = self.peekUntilBefore(until) orelse return null;
200 | self.ptr += str.len;
201 | return str;
202 | }
203 |
204 | pub fn peekUntilBefore(self: LuaIter, until: []const u8) ?[]const u8 {
205 | if ((self.ptr + until.len) >= self.buf.len) return null;
206 |
207 | for ((self.ptr)..(self.buf.len - until.len + 1)) |ptr| {
208 | if (!std.mem.eql(u8, self.buf[ptr..(ptr + until.len)], until))
209 | continue;
210 |
211 | return self.buf[self.ptr..ptr];
212 | }
213 | return null;
214 | }
215 |
216 | pub fn nextUntilAfter(self: *LuaIter, until: []const u8) ?[]const u8 {
217 | const str = self.peekUntilAfter(until) orelse return null;
218 | self.ptr += str.len;
219 | return str;
220 | }
221 |
222 | pub fn peekUntilAfter(self: LuaIter, until: []const u8) ?[]const u8 {
223 | if ((self.ptr + until.len) >= self.buf.len) return null;
224 |
225 | for ((self.ptr)..(self.buf.len - until.len + 1)) |ptr| {
226 | if (!std.mem.eql(u8, self.buf[ptr..(ptr + until.len)], until))
227 | continue;
228 |
229 | return self.buf[self.ptr..(ptr + until.len)];
230 | }
231 | return null;
232 | }
233 |
234 | pub fn nextUntilAfterLuaString(self: *LuaIter) ?[]const u8 {
235 | const end_ptr = self.findLuaStringClosingAfter() orelse return null;
236 | defer self.ptr = end_ptr;
237 | return self.buf[self.ptr..end_ptr];
238 | }
239 |
240 | pub fn rest(self: *LuaIter) ?[]const u8 {
241 | if (self.isDone()) return null;
242 | defer self.ptr = self.buf.len;
243 | return self.buf[self.ptr..];
244 | }
245 |
246 | pub fn peekRest(self: *LuaIter) ?[]const u8 {
247 | if (self.isDone()) return null;
248 | return self.buf[self.ptr..];
249 | }
250 |
251 | pub const whitespace_inline = [_]u8{ ' ', '\t' };
252 | fn isInlineWhitespace(char: u8) bool {
253 | inline for (whitespace_inline) |w| {
254 | if (char == w) return true;
255 | }
256 | return false;
257 | }
258 |
259 | pub const whitespace_any = whitespace_inline ++ [_]u8{'\n'};
260 | fn isAnyWhitespace(char: u8) bool {
261 | inline for (whitespace_any) |w| {
262 | if (char == w) return true;
263 | }
264 | return false;
265 | }
266 |
267 | fn findLuaStringOpeningOn(self: *LuaIter) ?usize {
268 | if (self.isDone()) {
269 | return null;
270 | }
271 |
272 | if (self.lua_string_cache) |cache| {
273 | if (cache.getNextLuaStringOpeningOn(self.ptr)) |start_on| {
274 | return start_on;
275 | }
276 | }
277 |
278 | var iter = init(self.buf[self.ptr..]);
279 |
280 | var string_type: ?LuaStringCache.LuaStringType = null;
281 | while (iter.next()) |char| {
282 | if (char == '\'') {
283 | string_type = .singleQuote;
284 | break;
285 | }
286 |
287 | if (char == '"') {
288 | string_type = .doubleQuote;
289 | break;
290 | }
291 |
292 | if (char == '[' and iter.peek() != null and iter.peek() == '[') {
293 | string_type = .multiLine;
294 | break;
295 | }
296 | }
297 |
298 | if (iter.isDone()) {
299 | return null;
300 | } else {
301 | self.lua_string_cache = LuaStringCache{
302 | .ptr_check = self.ptr,
303 | .lua_string_type = string_type.?,
304 | .next_lua_string_start_on = self.ptr + iter.ptr - 1,
305 | .next_lua_string_end = null,
306 | };
307 | return self.ptr + iter.ptr - 1;
308 | }
309 | }
310 | fn findLuaStringOpeningAfter(self: *LuaIter) ?usize {
311 | const ptr = self.findLuaStringOpeningOn() orelse return null;
312 | if (self.buf[ptr] == '[') {
313 | return ptr + 2;
314 | } else {
315 | return ptr + 1;
316 | }
317 | }
318 |
319 | fn findLuaStringClosingOn(self: *LuaIter) ?usize {
320 | if (self.isDone()) {
321 | return null;
322 | }
323 |
324 | if (self.lua_string_cache) |cache| {
325 | if (cache.getNextLuaStringEnd(self.ptr)) |end| {
326 | return end;
327 | }
328 | }
329 |
330 | const start = self.findLuaStringOpeningAfter() orelse return null;
331 | assert(self.lua_string_cache != null); // The cache must exist after finding the opening
332 | var cache = &self.lua_string_cache.?;
333 |
334 | var iter = init(self.buf[start..]);
335 |
336 | while (iter.next()) |char| {
337 | switch (cache.lua_string_type) {
338 | .singleQuote => if (char != '\'') continue,
339 | .doubleQuote => if (char != '\"') continue,
340 | .multiLine => if (char != ']' or iter.peek() != ']') continue,
341 | }
342 |
343 | // Make sure the closing character is not escaped AND
344 | // that the escape is not escaped
345 | if (iter.peekBackBy(2) == '\\' and iter.peekBackBy(3) != '\\') continue;
346 |
347 | break;
348 | }
349 |
350 | if (iter.isDone() and iter.ptr != iter.buf.len) {
351 | return null;
352 | } else {
353 | cache.next_lua_string_end = (start + iter.ptr - 1);
354 | return cache.next_lua_string_end;
355 | }
356 | }
357 |
358 | fn findLuaStringClosingAfter(self: *LuaIter) ?usize {
359 | const ptr = self.findLuaStringClosingOn() orelse return null;
360 | if (self.buf[ptr] == ']') {
361 | return ptr + 2;
362 | } else {
363 | return ptr + 1;
364 | }
365 | }
366 |
367 | // TODO: Make a test folder for this shit
368 | const tst = std.testing;
369 | const expect = tst.expect;
370 | const expectEqual = tst.expectEqual;
371 | const expectEqualStrings = tst.expectEqualStrings;
372 | const alloc = tst.allocator;
373 |
374 | test "next/ peek" {
375 | const in = "abc";
376 | var iter = init(in);
377 | try expectEqual('a', iter.peek());
378 | try expectEqual('a', iter.peek());
379 | try expectEqual('a', iter.next());
380 |
381 | try expectEqual('b', iter.peek());
382 | try expectEqual('b', iter.peek());
383 | try expectEqual('b', iter.next());
384 |
385 | try expectEqual('c', iter.peek());
386 | try expectEqual('c', iter.peek());
387 | try expectEqual('c', iter.next());
388 |
389 | try expectEqual(null, iter.peek());
390 | try expectEqual(null, iter.peek());
391 | try expectEqual(null, iter.next());
392 | try expectEqual(null, iter.next());
393 | }
394 |
395 | test "back/ peekBack" {
396 | const in = "abc";
397 | var iter = initBack(in);
398 | try expectEqual('c', iter.peekBack());
399 | try expectEqual('c', iter.peekBack());
400 | try expectEqual('c', iter.back());
401 |
402 | try expectEqual('b', iter.peekBack());
403 | try expectEqual('b', iter.peekBack());
404 | try expectEqual('b', iter.back());
405 |
406 | try expectEqual('a', iter.peekBack());
407 | try expectEqual('a', iter.peekBack());
408 | try expectEqual('a', iter.back());
409 |
410 | try expectEqual(null, iter.peekBack());
411 | try expectEqual(null, iter.peekBack());
412 | try expectEqual(null, iter.back());
413 | try expectEqual(null, iter.back());
414 | }
415 |
416 | test "nextLuaString double" {
417 | const in =
418 | \\garbage"hello"garbage"garbage"
419 | ;
420 | var iter = init(in);
421 | try expectEqualStrings("hello", iter.peekNextLuaString().?);
422 | try expectEqualStrings("hello", iter.peekNextLuaString().?);
423 | }
424 |
425 | test "nextLuaString single" {
426 | const in =
427 | \\garbage'hello'garbage'garbage'
428 | ;
429 | var iter = init(in);
430 | try expectEqualStrings("hello", iter.peekNextLuaString().?);
431 | try expectEqualStrings("hello", iter.peekNextLuaString().?);
432 | }
433 |
434 | test "nextLuaString multiline" {
435 | const in =
436 | \\garbage[[hello
437 | \\world]]garbage[[garbage]]
438 | ;
439 | var iter = init(in);
440 | try expectEqualStrings("hello\nworld", iter.peekNextLuaString().?);
441 | try expectEqualStrings("hello\nworld", iter.peekNextLuaString().?);
442 | }
443 |
444 | test "nextLuaString empty" {
445 | const in =
446 | \\[[]]
447 | ;
448 | var iter = init(in);
449 | try expectEqualStrings("", iter.peekNextLuaString().?);
450 | try expectEqualStrings("", iter.peekNextLuaString().?);
451 | }
452 |
453 | test "nextLuaString first char" {
454 | const in =
455 | \\[[hello world]]
456 | ;
457 | var iter = init(in);
458 | try expectEqualStrings("hello world", iter.peekNextLuaString().?);
459 | try expectEqualStrings("hello world", iter.peekNextLuaString().?);
460 | }
461 |
462 | test "nextLuaStringHasKey no key" {
463 | const in = "[[hello]]";
464 | var iter = init(in);
465 | try expectEqual(null, iter.peekUntilNextLuaStringKey("some-key"));
466 | }
467 |
468 | test "nextLuaStringHasKey wrong key" {
469 | const in = "some-key = [[hello]]";
470 | var iter = init(in);
471 | try expectEqual(null, iter.peekUntilNextLuaStringKey("other-key"));
472 | }
473 |
474 | test "nextLuaStringHasKey right key" {
475 | const in = "some-key = [[hello]]";
476 | var iter = init(in);
477 | try expectEqualStrings("", iter.peekUntilNextLuaStringKey("some-key").?);
478 | }
479 |
480 | test "nextLuaStringHasKey no whitespace" {
481 | const in = "some-key=[[hello]]";
482 | var iter = init(in);
483 | try expectEqualStrings("", iter.peekUntilNextLuaStringKey("some-key").?);
484 | }
485 |
486 | test "nextLuaStringHasKey lost of whitespace" {
487 | const in = " some-key = [[hello]]";
488 | var iter = init(in);
489 | try expectEqualStrings(" ", iter.peekUntilNextLuaStringKey("some-key").?);
490 | }
491 |
492 | test "nextLuaStringHasKey newline after =" {
493 | const in = "some-key =\n [[hello]]";
494 | var iter = init(in);
495 | try expectEqual(null, iter.peekUntilNextLuaStringKey("some-key"));
496 | }
497 |
498 | test "nextLuaStringHasKey newline before =" {
499 | const in = "some-key\n = [[hello]]";
500 | var iter = init(in);
501 | try expectEqual(null, iter.peekUntilNextLuaStringKey("some-key"));
502 | }
503 |
504 | test "nextLuaStringHasKey key obstructed" {
505 | const in = "some-key, = [[hello]]";
506 | var iter = init(in);
507 | try expectEqual(null, iter.peekUntilNextLuaStringKey("some-key"));
508 | }
509 |
510 | test "nextLuaStringHasKey no string" {
511 | const in = "garbage garbage";
512 | var iter = init(in);
513 | try expectEqual(null, iter.peekUntilNextLuaStringKey("some-key"));
514 | }
515 |
516 | test "skipAfterLuaString" {
517 | const in = "garbage[[hello]]x";
518 | var iter = init(in);
519 | try expect(iter.nextUntilAfterLuaString() != null);
520 | try expectEqual('x', iter.next());
521 | }
522 |
523 | test "string after skip" {
524 | const in = "[[Hello]] 'world'";
525 | var iter = init(in);
526 | try expect(iter.nextUntilAfterLuaString() != null);
527 | try expectEqualStrings("world", iter.peekNextLuaString().?);
528 | }
529 |
530 | test "findLuaStringClosing multiline" {
531 | const in = "[[h]]";
532 | var iter = init(in);
533 | try expectEqual(3, iter.findLuaStringClosingOn().?);
534 | try expectEqual(5, iter.findLuaStringClosingAfter().?);
535 | }
536 |
537 | test "findLuaStringClosing normal" {
538 | const in = "'h'";
539 | var iter = init(in);
540 | try expectEqual(2, iter.findLuaStringClosingOn().?);
541 | try expectEqual(3, iter.findLuaStringClosingAfter().?);
542 | }
543 |
544 | test "findLuaStringOpening multiline" {
545 | const in = "[[h]]";
546 | var iter = init(in);
547 | try expectEqual(0, iter.findLuaStringOpeningOn().?);
548 | try expectEqual(2, iter.findLuaStringOpeningAfter().?);
549 | }
550 |
551 | test "findLuaStringOpening normal" {
552 | const in = "'h'";
553 | var iter = init(in);
554 | try expectEqual(0, iter.findLuaStringOpeningOn().?);
555 | try expectEqual(1, iter.findLuaStringOpeningAfter().?);
556 | }
557 |
558 | test "nextUntilBefore single char" {
559 | const in = "hello|world|";
560 | var iter = init(in);
561 | try expectEqualStrings("hello", iter.nextUntilBefore("|").?);
562 | try expectEqual('|', iter.next().?);
563 | try expectEqualStrings("world", iter.nextUntilBefore("|").?);
564 | try expectEqual('|', iter.next().?);
565 |
566 | try expectEqual(null, iter.nextUntilBefore("|"));
567 | }
568 |
569 | test "nextUntilBefore rest" {
570 | const in = "hello|world";
571 | var iter = init(in);
572 | try expectEqualStrings("hello", iter.nextUntilBefore("|").?);
573 | try expectEqual('|', iter.next().?);
574 | try expectEqual(null, iter.nextUntilBefore("|"));
575 | }
576 |
577 | test "nextUntilBefore multi char" {
578 | const in = "hello|world|||";
579 | var iter = init(in);
580 | try expectEqualStrings("hello|world", iter.nextUntilBefore("|||").?);
581 | try expectEqual(null, iter.nextUntilBefore("|||"));
582 | }
583 |
584 | test "nextUntilBefore not present" {
585 | const in = "hello|world";
586 | var iter = init(in);
587 | try expectEqual(null, iter.nextUntilBefore("||"));
588 | }
589 |
590 | test "nextUntilBefore larger than str" {
591 | const in = "-";
592 | var iter = init(in);
593 | try expectEqual(null, iter.nextUntilBefore("||"));
594 | }
595 |
596 | test "Different string characters, valid string" {
597 | const in = "[[ \" ' ]]";
598 | var iter = init(in);
599 | const expected = " \" ' ";
600 | try expectEqualStrings(expected, iter.peekNextLuaString() orelse return error.failed);
601 | }
602 |
603 | test "Different string characters, no valid string" {
604 | const in = "]] \" ' [[";
605 | var iter = init(in);
606 | const expected = null;
607 | try expectEqual(expected, iter.peekNextLuaString());
608 | }
609 |
610 | test "poorly ended multiline" {
611 | const in = "[[ hello ]";
612 | var iter = init(in);
613 | const expected = null;
614 | try expectEqual(expected, iter.peekNextLuaString());
615 | }
616 |
617 | test "poorly started multiline" {
618 | const in = "[ hello ]]";
619 | var iter = init(in);
620 | const expected = null;
621 | try expectEqual(expected, iter.peekNextLuaString());
622 | }
623 |
624 | test "escaped double quote in string" {
625 | const in = "\" \\\" \""; // convertes to " \" "
626 | var iter = init(in);
627 | const expected = " \\\" ";
628 | try expectEqualStrings(expected, iter.peekNextLuaString() orelse return error.failed);
629 | }
630 |
631 | test "not escaped double quote in string" {
632 | const in = "\" \\\\\" \""; // converts to " \\" "
633 | var iter = init(in);
634 | const expected = " \\\\";
635 | try expectEqualStrings(expected, iter.peekNextLuaString() orelse return error.failed);
636 | }
637 |
638 | const std = @import("std");
639 | const mem = std.mem;
640 | const assert = std.debug.assert;
641 |
642 | const LuaIter = @This();
643 |
--------------------------------------------------------------------------------
/patcher/src/lib/LuaParser.zig:
--------------------------------------------------------------------------------
1 | alloc: Allocator,
2 | in_dir: Dir,
3 | out_dir: Dir,
4 | extra_init_config: []const u8,
5 |
6 | // TODO: Change paths to dirs
7 | pub fn init(alloc: Allocator, in_dir: Dir, out_dir: Dir, extra_init_config: []const u8) !LuaParser {
8 | return LuaParser{
9 | .alloc = alloc,
10 | .in_dir = in_dir,
11 | .out_dir = out_dir,
12 | .extra_init_config = extra_init_config,
13 | };
14 | }
15 |
16 | pub fn deinit(self: *LuaParser) void {
17 | self.in_dir.close();
18 | self.out_dir.close();
19 | }
20 |
21 | /// This function creates the lua config in the specificified location.
22 | ///
23 | /// It extracts specific substitutions from the passed in plugins and then
24 | /// iterates over the input directory recursively. It copies non lua files
25 | /// directly and parses lua files for substitutions before copying the parsed
26 | /// files over.
27 | pub fn createConfig(self: LuaParser, subs: []const Substitution) !void {
28 | // FIXME: Create a loop that asserts subs.from are all unique
29 | // - What if we have a custom sub that overrides a patched sub?
30 | var walker = try self.in_dir.walk(self.alloc);
31 | defer walker.deinit();
32 |
33 | std.log.info("Starting directory walk", .{});
34 | while (try walker.next()) |entry| {
35 | switch (entry.kind) {
36 | .directory, .sym_link => {
37 | // Go on if the dir already exists
38 | self.out_dir.access(entry.path, .{}) catch {
39 | try self.out_dir.makeDir(entry.path);
40 | };
41 | },
42 | .file => {
43 | if (std.mem.eql(u8, ".lua", std.fs.path.extension(entry.basename))) {
44 | std.log.info("parsing '{s}'", .{entry.path});
45 | const in_file = try self.in_dir.openFile(entry.path, .{});
46 | const in_buf = try utils.mmapFile(in_file, .{});
47 | defer utils.unMmapFile(in_buf);
48 |
49 | const out_buf = try parseLuaFile(self.alloc, in_buf, subs);
50 | defer self.alloc.free(out_buf);
51 |
52 | const out_file = try self.out_dir.createFile(entry.path, .{});
53 |
54 | if (std.mem.eql(u8, entry.path, "init.lua")) {
55 | try out_file.writeAll(self.extra_init_config);
56 | try out_file.writeAll("\n");
57 | }
58 |
59 | try out_file.writeAll(out_buf);
60 | } else {
61 | std.log.info("copying '{s}'", .{entry.path});
62 | try self.in_dir.copyFile(
63 | entry.path,
64 | self.out_dir,
65 | entry.path,
66 | .{},
67 | );
68 | }
69 | },
70 | else => std.log.warn("Kind {}", .{entry.kind}),
71 | }
72 | }
73 | }
74 |
75 | /// Memory owned by out
76 | fn subsFromBlob(alloc: Allocator, subs_blob: []const u8, out: *std.ArrayList(Substitution)) !void {
77 | var iter = LuaIter.init(subs_blob);
78 |
79 | if (subs_blob.len < 3) {
80 | return;
81 | }
82 | while (!iter.isDone()) {
83 | const typ = iter.nextUntilBefore("|").?;
84 | _ = iter.next();
85 | const from = iter.nextUntilBefore("|").?;
86 | _ = iter.next();
87 | const to = iter.nextUntilBefore("|").?;
88 | _ = iter.next();
89 | const extra = iter.nextUntilBefore(";") orelse iter.rest() orelse return error.BadSub;
90 | _ = iter.next();
91 |
92 | if (std.mem.eql(u8, typ, "plugin")) {
93 | try out.append(try Substitution.initUrlSub(alloc, from, to, extra));
94 | } else if (std.mem.eql(u8, typ, "string")) {
95 | if (std.mem.eql(u8, extra, "-")) {
96 | try out.append(try Substitution.initStringSub(alloc, from, to, null));
97 | } else {
98 | try out.append(try Substitution.initStringSub(alloc, from, to, extra));
99 | }
100 | } else unreachable;
101 | }
102 | }
103 |
104 | /// Memory owned by caller
105 | fn subsFromPlugins(alloc: Allocator, plugins: []const Plugin, out: *std.ArrayList(Substitution)) !void {
106 | for (plugins) |plugin| {
107 | switch (plugin.tag) {
108 | .UrlNotFound => continue,
109 | .GitUrl => {
110 | try out.append(try Substitution.initUrlSub(
111 | alloc,
112 | plugin.url,
113 | plugin.path,
114 | plugin.pname,
115 | ));
116 | },
117 | .GithubUrl => {
118 | try out.append(try Substitution.initUrlSub(
119 | alloc,
120 | plugin.url,
121 | plugin.path,
122 | plugin.pname,
123 | ));
124 |
125 | var url_splitter = std.mem.splitSequence(u8, plugin.url, "://github.com/");
126 | _ = url_splitter.next().?;
127 | const short_url = url_splitter.rest();
128 |
129 | try out.append(try Substitution.initUrlSub(
130 | alloc,
131 | short_url,
132 | plugin.path,
133 | plugin.pname,
134 | ));
135 | },
136 | }
137 | }
138 | }
139 |
140 | // Good example of url usage:
141 | // https://sourcegraph.com/github.com/Amar1729/dotfiles/-/blob/dot_config/nvim/lua/plugins/treesitter.lua
142 |
143 | /// Returns a _new_ buffer, owned by the caller
144 | fn parseLuaFile(alloc: Allocator, input_buf: []const u8, subs: []const Substitution) ![]const u8 {
145 | var iter = LuaIter.init(input_buf);
146 |
147 | // Tack on 10% file size, should remove any resizes unless you do something
148 | // stupid
149 | var out_arr = try std.ArrayList(u8).initCapacity(
150 | alloc,
151 | @intFromFloat(@as(f32, @floatFromInt(input_buf.len)) * 1.1),
152 | );
153 |
154 | while (!iter.isDone()) {
155 | var chosen_sub: ?Substitution = null;
156 | for (subs) |sub| {
157 | switch (sub.tag) {
158 | .string => |key| {
159 | const next_str = iter.peekNextLuaString() orelse continue;
160 | if (!std.mem.eql(u8, sub.from, next_str)) continue;
161 |
162 | if (key) |k| {
163 | _ = iter.peekUntilNextLuaStringKey(k) orelse continue;
164 | }
165 |
166 | chosen_sub = sub;
167 | },
168 | .url => {
169 | const next_str = iter.peekNextLuaString() orelse continue;
170 | if (!std.mem.eql(u8, sub.from, next_str)) continue;
171 |
172 | chosen_sub = sub;
173 | },
174 | .raw => unreachable,
175 | }
176 | }
177 |
178 | if (chosen_sub) |sub| {
179 | std.log.debug("Sub '{s}' -> '{s}'\n", .{ sub.from, sub.to });
180 |
181 | switch (sub.tag) {
182 | .raw => {
183 | const until_next_instance = iter.peekUntilBefore(sub.from) orelse unreachable;
184 | try out_arr.appendSlice(until_next_instance);
185 | try out_arr.appendSlice(sub.to);
186 |
187 | _ = iter.nextUntilAfter(sub.from) orelse unreachable;
188 | },
189 | .string => |key| {
190 | const until_next_string = blk: {
191 | if (key) |k| {
192 | break :blk iter.peekUntilNextLuaStringKey(k) orelse unreachable;
193 | } else {
194 | break :blk iter.peekNextUntilLuaString() orelse unreachable;
195 | }
196 | };
197 | try out_arr.appendSlice(until_next_string);
198 | if (key) |k| {
199 | try out_arr.appendSlice(k);
200 | try out_arr.appendSlice(" = ");
201 | }
202 | try out_arr.appendSlice("[["); // String opening
203 | try out_arr.appendSlice(sub.to);
204 | try out_arr.appendSlice("]]"); // String closing
205 |
206 | _ = iter.nextUntilAfterLuaString() orelse unreachable;
207 | },
208 | .url => |pname| {
209 | const should_wrap = iter.peekNextStringTableHasKey("dependencies");
210 |
211 | const before_string = iter.peekUntilNextLuaStringKey("url") orelse
212 | iter.peekNextUntilLuaString() orelse unreachable;
213 | try out_arr.appendSlice(before_string);
214 |
215 | if (should_wrap) {
216 | try out_arr.appendSlice("{ ");
217 | }
218 |
219 | try out_arr.appendSlice("dir = "); // Tell lazy this is a dir
220 | try out_arr.appendSlice("[["); // String opening
221 | try out_arr.appendSlice(sub.to);
222 | try out_arr.appendSlice("]]"); // String closing
223 | try out_arr.appendSlice(", name = "); // expose the plugin name
224 | try out_arr.appendSlice("[["); // String opening
225 | try out_arr.appendSlice(pname);
226 | try out_arr.appendSlice("]]"); // String closing
227 |
228 | if (should_wrap) {
229 | try out_arr.appendSlice(" }");
230 | }
231 |
232 | _ = iter.nextUntilAfterLuaString() orelse unreachable;
233 | },
234 | }
235 | } else {
236 | const str = iter.nextUntilAfterLuaString() orelse iter.rest() orelse "";
237 | try out_arr.appendSlice(str);
238 | }
239 | }
240 | std.log.debug("No more subs in this file...", .{});
241 |
242 | return out_arr.toOwnedSlice();
243 | }
244 |
245 | // TODO: Make a test folder for this shit
246 | test "parseLuaFile copy" {
247 | const alloc = std.testing.allocator;
248 | const input_buf = "Hello world";
249 | const expected = "Hello world";
250 |
251 | const subs: []const Substitution = &.{
252 | try Substitution.initStringSub(alloc, "asdf", "fdsa", null),
253 | };
254 | defer {
255 | for (subs) |sub| {
256 | sub.deinit(alloc);
257 | }
258 | }
259 |
260 | const out_buf = try parseLuaFile(alloc, input_buf, subs);
261 | defer alloc.free(out_buf);
262 |
263 | try std.testing.expectEqualStrings(expected, out_buf);
264 | }
265 |
266 | test "parseLuaFile simple sub" {
267 | const alloc = std.testing.allocator;
268 | const input_buf = "'Hello' world";
269 | const expected = "[[hi]] world";
270 |
271 | const subs: []const Substitution = &.{
272 | try Substitution.initStringSub(alloc, "Hello", "hi", null),
273 | };
274 | defer {
275 | for (subs) |sub| {
276 | sub.deinit(alloc);
277 | }
278 | }
279 |
280 | const out_buf = try parseLuaFile(alloc, input_buf, subs);
281 | defer alloc.free(out_buf);
282 |
283 | try std.testing.expectEqualStrings(expected, out_buf);
284 | }
285 |
286 | test "parseLuaFile multiple subs" {
287 | const alloc = std.testing.allocator;
288 | const input_buf = "'Hello' 'world'";
289 | const expected = "[[world]] [[Hello]]";
290 |
291 | const subs: []const Substitution = &.{
292 | try Substitution.initStringSub(alloc, "Hello", "world", null),
293 | try Substitution.initStringSub(alloc, "world", "Hello", null),
294 | };
295 | defer {
296 | for (subs) |sub| {
297 | sub.deinit(alloc);
298 | }
299 | }
300 |
301 | const out_buf = try parseLuaFile(alloc, input_buf, subs);
302 | defer alloc.free(out_buf);
303 |
304 | try std.testing.expectEqualStrings(expected, out_buf);
305 | }
306 |
307 | test "appendFromBlob empty" {
308 | const alloc = std.testing.allocator;
309 | var out_arr = std.ArrayList(Substitution).init(alloc);
310 |
311 | const in = "-";
312 |
313 | try subsFromBlob(alloc, in, &out_arr);
314 |
315 | try std.testing.expectEqual(0, out_arr.items.len);
316 | }
317 |
318 | test "appendFromBlob normal" {
319 | const alloc = std.testing.allocator;
320 | var out_arr = std.ArrayList(Substitution).init(alloc);
321 | defer {
322 | for (out_arr.items) |sub| {
323 | sub.deinit(alloc);
324 | }
325 | out_arr.deinit();
326 | }
327 |
328 | const in = "plugin|from|to|pname;string|from2|to2|key;";
329 |
330 | try subsFromBlob(alloc, in, &out_arr);
331 |
332 | try std.testing.expectEqual(2, out_arr.items.len);
333 |
334 | try std.testing.expectEqualStrings("pname", out_arr.items[0].tag.url);
335 | try std.testing.expectEqualStrings("from", out_arr.items[0].from);
336 | try std.testing.expectEqualStrings("to", out_arr.items[0].to);
337 |
338 | try std.testing.expectEqualStrings("key", out_arr.items[1].tag.string.?);
339 | try std.testing.expectEqualStrings("from2", out_arr.items[1].from);
340 | try std.testing.expectEqualStrings("to2", out_arr.items[1].to);
341 | }
342 |
343 | test "test plugin double quote" {
344 | const alloc = std.testing.allocator;
345 | const input_buf =
346 | \\"short/url"
347 | ;
348 | const expected =
349 | \\dir = [[local/path]], name = [[pname]]
350 | ;
351 |
352 | const subs: []const Substitution = &.{
353 | try Substitution.initUrlSub(
354 | alloc,
355 | "short/url",
356 | "local/path",
357 | "pname",
358 | ),
359 | };
360 | defer {
361 | for (subs) |sub| {
362 | sub.deinit(alloc);
363 | }
364 | }
365 |
366 | const out_buf = try parseLuaFile(alloc, input_buf, subs);
367 | defer alloc.free(out_buf);
368 |
369 | try std.testing.expectEqualStrings(expected, out_buf);
370 | }
371 |
372 | test "test plugin single quote" {
373 | const alloc = std.testing.allocator;
374 | const input_buf =
375 | \\'short/url'
376 | ;
377 | const expected =
378 | \\dir = [[local/path]], name = [[pname]]
379 | ;
380 |
381 | const subs: []const Substitution = &.{
382 | try Substitution.initUrlSub(
383 | alloc,
384 | "short/url",
385 | "local/path",
386 | "pname",
387 | ),
388 | };
389 | defer {
390 | for (subs) |sub| {
391 | sub.deinit(alloc);
392 | }
393 | }
394 |
395 | const out_buf = try parseLuaFile(alloc, input_buf, subs);
396 | defer alloc.free(out_buf);
397 |
398 | try std.testing.expectEqualStrings(expected, out_buf);
399 | }
400 |
401 | test "test plugin multi line" {
402 | const alloc = std.testing.allocator;
403 | const input_buf =
404 | \\[[short/url]]
405 | ;
406 | const expected =
407 | \\dir = [[local/path]], name = [[pname]]
408 | ;
409 |
410 | const subs: []const Substitution = &.{
411 | try Substitution.initUrlSub(
412 | alloc,
413 | "short/url",
414 | "local/path",
415 | "pname",
416 | ),
417 | };
418 | defer {
419 | for (subs) |sub| {
420 | sub.deinit(alloc);
421 | }
422 | }
423 |
424 | const out_buf = try parseLuaFile(alloc, input_buf, subs);
425 | defer alloc.free(out_buf);
426 |
427 | try std.testing.expectEqualStrings(expected, out_buf);
428 | }
429 |
430 | test "test plugin url" {
431 | const alloc = std.testing.allocator;
432 | const input_buf =
433 | \\url =[[short/url]]
434 | ;
435 | const expected =
436 | \\dir = [[local/path]], name = [[pname]]
437 | ;
438 |
439 | const subs: []const Substitution = &.{
440 | try Substitution.initUrlSub(
441 | alloc,
442 | "short/url",
443 | "local/path",
444 | "pname",
445 | ),
446 | };
447 | defer {
448 | for (subs) |sub| {
449 | sub.deinit(alloc);
450 | }
451 | }
452 |
453 | const out_buf = try parseLuaFile(alloc, input_buf, subs);
454 | defer alloc.free(out_buf);
455 |
456 | try std.testing.expectEqualStrings(expected, out_buf);
457 | }
458 |
459 | test "Markdown failing in config" {
460 | const alloc = std.testing.allocator;
461 | const in =
462 | \\local utils = require("utils")
463 | \\
464 | \\return {
465 | \\ {
466 | \\ "iamcco/markdown-preview.nvim",
467 | \\ cmd = { "MarkdownPreviewToggle", "MarkdownPreview", "MarkdownPreviewStop" },
468 | \\ ft = { "markdown" },
469 | \\ build = utils.set(function()
470 | \\ vim.fn["mkdp#util#install"]()
471 | \\ end),
472 | \\ },
473 | \\}
474 | ;
475 |
476 | const expected =
477 | \\local utils = require("utils")
478 | \\
479 | \\return {
480 | \\ {
481 | \\ dir = [[/nix/store/7zf18anjyk8k57knlfpx0gg6ji03scq0-vimplugin-markdown-preview.nvim-2023-10-17]], name = [[markdown-preview]],
482 | \\ cmd = { "MarkdownPreviewToggle", "MarkdownPreview", "MarkdownPreviewStop" },
483 | \\ ft = { "markdown" },
484 | \\ build = utils.set(function()
485 | \\ vim.fn["mkdp#util#install"]()
486 | \\ end),
487 | \\ },
488 | \\}
489 | ;
490 |
491 | const subs: []const Substitution = &.{
492 | try Substitution.initUrlSub(
493 | alloc,
494 | "iamcco/markdown-preview.nvim",
495 | "/nix/store/7zf18anjyk8k57knlfpx0gg6ji03scq0-vimplugin-markdown-preview.nvim-2023-10-17",
496 | "markdown-preview",
497 | ),
498 | };
499 | defer {
500 | for (subs) |sub| {
501 | sub.deinit(alloc);
502 | }
503 | }
504 |
505 | const out_buf = try parseLuaFile(alloc, in, subs);
506 | defer alloc.free(out_buf);
507 |
508 | try std.testing.expectEqualStrings(expected, out_buf);
509 | }
510 |
511 | test "cmp_luasnip failing in config" {
512 | const alloc = std.testing.allocator;
513 | const in =
514 | \\"luasnip"
515 | \\"saadparwaiz1/cmp_luasnip",
516 | \\"L3MON4D3/LuaSnip",
517 | ;
518 |
519 | const expected =
520 | \\"luasnip"
521 | \\dir = [[/nix/store/g60hd3lbr3vj0vzdz1q2rjjvn38l6s09-vimplugin-cmp_luasnip-2023-10-09]], name = [[cmp_luasnip]],
522 | \\dir = [[/nix/store/ma7l3pplq1v4kpw6myg3i4b59dfls8lz-vimplugin-lua5.1-luasnip-2.3.0-1-unstable-2024-06-28]], name = [[luasnip]],
523 | ;
524 |
525 | const subs: []const Substitution = &.{
526 | try Substitution.initUrlSub(
527 | alloc,
528 | "L3MON4D3/LuaSnip",
529 | "/nix/store/ma7l3pplq1v4kpw6myg3i4b59dfls8lz-vimplugin-lua5.1-luasnip-2.3.0-1-unstable-2024-06-28",
530 | "luasnip",
531 | ),
532 |
533 | try Substitution.initUrlSub(
534 | alloc,
535 | "saadparwaiz1/cmp_luasnip",
536 | "/nix/store/g60hd3lbr3vj0vzdz1q2rjjvn38l6s09-vimplugin-cmp_luasnip-2023-10-09",
537 | "cmp_luasnip",
538 | ),
539 | };
540 | defer {
541 | for (subs) |sub| {
542 | sub.deinit(alloc);
543 | }
544 | }
545 |
546 | const out_buf = try parseLuaFile(alloc, in, subs);
547 | defer alloc.free(out_buf);
548 |
549 | try std.testing.expectEqualStrings(expected, out_buf);
550 | }
551 |
552 | test "cmp-nvim-lsp failing in config" {
553 | const alloc = std.testing.allocator;
554 | const in =
555 | \\"luasnip"
556 | \\"hrsh7th/cmp-nvim-lsp",
557 | \\"L3MON4D3/LuaSnip",
558 | ;
559 |
560 | const expected =
561 | \\"luasnip"
562 | \\dir = [[/nix/store/a876kd0xckljpb8j45wwby3fdrqk14aj-vimplugin-cmp-nvim-lsp-2024-05-17]], name = [[cmp-nvim-lsp]],
563 | \\dir = [[/nix/store/ma7l3pplq1v4kpw6myg3i4b59dfls8lz-vimplugin-lua5.1-luasnip-2.3.0-1-unstable-2024-06-28]], name = [[luasnip]],
564 | ;
565 |
566 | const subs: []const Substitution = &.{
567 | try Substitution.initUrlSub(
568 | alloc,
569 | "L3MON4D3/LuaSnip",
570 | "/nix/store/ma7l3pplq1v4kpw6myg3i4b59dfls8lz-vimplugin-lua5.1-luasnip-2.3.0-1-unstable-2024-06-28",
571 | "luasnip",
572 | ),
573 |
574 | try Substitution.initUrlSub(
575 | alloc,
576 | "hrsh7th/cmp-nvim-lsp",
577 | "/nix/store/a876kd0xckljpb8j45wwby3fdrqk14aj-vimplugin-cmp-nvim-lsp-2024-05-17",
578 | "cmp-nvim-lsp",
579 | ),
580 | };
581 | defer for (subs) |sub| sub.deinit(alloc);
582 |
583 | const out_buf = try parseLuaFile(alloc, in, subs);
584 | defer alloc.free(out_buf);
585 |
586 | try std.testing.expectEqualStrings(expected, out_buf);
587 | }
588 |
589 | test "contex-aware-wrapping wrapped dependency" {
590 | const alloc = std.testing.allocator;
591 |
592 | const in =
593 | \\"plugin/url",
594 | \\dependencies = {
595 | \\ { "plugin/url" },
596 | \\}
597 | ;
598 |
599 | const expected =
600 | \\dir = [[plugin/name]], name = [[pname]],
601 | \\dependencies = {
602 | \\ { dir = [[plugin/name]], name = [[pname]] },
603 | \\}
604 | ;
605 |
606 | const subs: []const Substitution = &.{
607 | try Substitution.initUrlSub(
608 | alloc,
609 | "plugin/url",
610 | "plugin/name",
611 | "pname",
612 | ),
613 | };
614 | defer for (subs) |sub| sub.deinit(alloc);
615 |
616 | const out_buf = try parseLuaFile(alloc, in, subs);
617 | defer alloc.free(out_buf);
618 |
619 | try std.testing.expectEqualStrings(expected, out_buf);
620 | }
621 |
622 | test "contex-aware-wrapping unwrapped single dependency" {
623 | const alloc = std.testing.allocator;
624 |
625 | const in =
626 | \\"plugin/url",
627 | \\dependencies = {
628 | \\ "plugin/url",
629 | \\}
630 | ;
631 |
632 | const expected =
633 | \\dir = [[plugin/name]], name = [[pname]],
634 | \\dependencies = {
635 | \\ { dir = [[plugin/name]], name = [[pname]] },
636 | \\}
637 | ;
638 |
639 | const subs: []const Substitution = &.{
640 | try Substitution.initUrlSub(
641 | alloc,
642 | "plugin/url",
643 | "plugin/name",
644 | "pname",
645 | ),
646 | };
647 | defer for (subs) |sub| sub.deinit(alloc);
648 |
649 | const out_buf = try parseLuaFile(alloc, in, subs);
650 | defer alloc.free(out_buf);
651 |
652 | try std.testing.expectEqualStrings(expected, out_buf);
653 | }
654 |
655 | test "contex-aware-wrapping unwrapped multiple dependencies" {
656 | const alloc = std.testing.allocator;
657 |
658 | const in =
659 | \\"plugin/url",
660 | \\dependencies = {
661 | \\ "plugin/url",
662 | \\ "plugin/url",
663 | \\}
664 | ;
665 |
666 | const expected =
667 | \\dir = [[plugin/name]], name = [[pname]],
668 | \\dependencies = {
669 | \\ { dir = [[plugin/name]], name = [[pname]] },
670 | \\ { dir = [[plugin/name]], name = [[pname]] },
671 | \\}
672 | ;
673 |
674 | const subs: []const Substitution = &.{
675 | try Substitution.initUrlSub(
676 | alloc,
677 | "plugin/url",
678 | "plugin/name",
679 | "pname",
680 | ),
681 | };
682 | defer for (subs) |sub| sub.deinit(alloc);
683 |
684 | const out_buf = try parseLuaFile(alloc, in, subs);
685 | defer alloc.free(out_buf);
686 |
687 | try std.testing.expectEqualStrings(expected, out_buf);
688 | }
689 |
690 | test "contex-aware-wrapping mixed multiple dependencies" {
691 | const alloc = std.testing.allocator;
692 |
693 | const in =
694 | \\"plugin/url",
695 | \\dependencies = {
696 | \\ "plugin/url",
697 | \\ { "plugin/url" },
698 | \\ "plugin/url",
699 | \\ { "plugin/url" },
700 | \\ "plugin/url",
701 | \\}
702 | ;
703 |
704 | const expected =
705 | \\dir = [[plugin/name]], name = [[pname]],
706 | \\dependencies = {
707 | \\ { dir = [[plugin/name]], name = [[pname]] },
708 | \\ { dir = [[plugin/name]], name = [[pname]] },
709 | \\ { dir = [[plugin/name]], name = [[pname]] },
710 | \\ { dir = [[plugin/name]], name = [[pname]] },
711 | \\ { dir = [[plugin/name]], name = [[pname]] },
712 | \\}
713 | ;
714 |
715 | const subs: []const Substitution = &.{
716 | try Substitution.initUrlSub(
717 | alloc,
718 | "plugin/url",
719 | "plugin/name",
720 | "pname",
721 | ),
722 | };
723 | defer for (subs) |sub| sub.deinit(alloc);
724 |
725 | const out_buf = try parseLuaFile(alloc, in, subs);
726 | defer alloc.free(out_buf);
727 |
728 | try std.testing.expectEqualStrings(expected, out_buf);
729 | }
730 |
731 | test "contex-aware-wrapping single unwrapped single extensive multiple dependencies" {
732 | const alloc = std.testing.allocator;
733 |
734 | const in =
735 | \\"plugin/url",
736 | \\dependencies = {
737 | \\ "plugin/url",
738 | \\ {
739 | \\ "plugin/url",
740 | \\ opts = {},
741 | \\ },
742 | \\}
743 | ;
744 |
745 | const expected =
746 | \\dir = [[plugin/name]], name = [[pname]],
747 | \\dependencies = {
748 | \\ { dir = [[plugin/name]], name = [[pname]] },
749 | \\ {
750 | \\ dir = [[plugin/name]], name = [[pname]],
751 | \\ opts = {},
752 | \\ },
753 | \\}
754 | ;
755 |
756 | const subs: []const Substitution = &.{
757 | try Substitution.initUrlSub(
758 | alloc,
759 | "plugin/url",
760 | "plugin/name",
761 | "pname",
762 | ),
763 | };
764 | defer for (subs) |sub| sub.deinit(alloc);
765 |
766 | const out_buf = try parseLuaFile(alloc, in, subs);
767 | defer alloc.free(out_buf);
768 |
769 | try std.testing.expectEqualStrings(expected, out_buf);
770 | }
771 |
772 | test "contex-aware-wrapping nested dependencies" {
773 | const alloc = std.testing.allocator;
774 |
775 | const in =
776 | \\"plugin/url",
777 | \\dependencies = {
778 | \\ "plugin/url",
779 | \\ {
780 | \\ "plugin/url",
781 | \\ opts = {},
782 | \\ dependencies = {
783 | \\ "plugin/url",
784 | \\ "plugin/url",
785 | \\ }
786 | \\ },
787 | \\}
788 | ;
789 |
790 | const expected =
791 | \\dir = [[plugin/name]], name = [[pname]],
792 | \\dependencies = {
793 | \\ { dir = [[plugin/name]], name = [[pname]] },
794 | \\ {
795 | \\ dir = [[plugin/name]], name = [[pname]],
796 | \\ opts = {},
797 | \\ dependencies = {
798 | \\ { dir = [[plugin/name]], name = [[pname]] },
799 | \\ { dir = [[plugin/name]], name = [[pname]] },
800 | \\ }
801 | \\ },
802 | \\}
803 | ;
804 |
805 | const subs: []const Substitution = &.{
806 | try Substitution.initUrlSub(
807 | alloc,
808 | "plugin/url",
809 | "plugin/name",
810 | "pname",
811 | ),
812 | };
813 | defer for (subs) |sub| sub.deinit(alloc);
814 |
815 | const out_buf = try parseLuaFile(alloc, in, subs);
816 | defer alloc.free(out_buf);
817 |
818 | try std.testing.expectEqualStrings(expected, out_buf);
819 | }
820 |
821 | const std = @import("std");
822 | const root = @import("root.zig");
823 | const fs = std.fs;
824 | const utils = root.utils;
825 | const assert = std.debug.assert;
826 |
827 | const Allocator = std.mem.Allocator;
828 | const File = fs.File;
829 | const Dir = fs.Dir;
830 | const Plugin = root.Plugin;
831 | const Substitution = root.Substitution;
832 | const LuaIter = root.LuaIter;
833 |
834 | const LuaParser = @This();
835 |
--------------------------------------------------------------------------------
/patcher/src/lib/nixpkgs_parser.zig:
--------------------------------------------------------------------------------
1 | pub fn parseFiles(alloc: Allocator, input_blob: []const u8, nixpkgs_files: []const File) ![]const Plugin {
2 | const user_plugins = try parseBlob(alloc, input_blob);
3 |
4 | for (nixpkgs_files) |file| {
5 | const file_buf = try utils.mmapFile(file, .{});
6 | defer utils.unMmapFile(file_buf);
7 |
8 | try findPluginUrl(
9 | alloc,
10 | file_buf,
11 | user_plugins,
12 | );
13 | }
14 |
15 | for (user_plugins) |plugin| {
16 | if (plugin.tag != .UrlNotFound) continue;
17 |
18 | std.log.warn("Did not find a url for {s}", .{plugin.pname});
19 | }
20 |
21 | // Error if any plugin names are not unique
22 | for (0..user_plugins.len) |needle_idx| {
23 | const needle_plugin = user_plugins[needle_idx];
24 | for (0..user_plugins.len) |haystack_idx| {
25 | if (needle_idx == haystack_idx) continue;
26 | const haystack_plugin = user_plugins[haystack_idx];
27 |
28 | if (std.mem.eql(u8, needle_plugin.pname, haystack_plugin.pname)) {
29 | @branchHint(.unlikely);
30 |
31 | std.log.err(
32 | "Found plugin '{s}' twice, cannot patch unambigiously. exiting",
33 | .{needle_plugin.pname},
34 | );
35 | std.log.err("You may have the same plugin twice in your flake, this is disallowed", .{});
36 | std.process.exit(1);
37 | }
38 | }
39 | }
40 |
41 | return user_plugins;
42 | }
43 |
44 | fn parseBlob(alloc: Allocator, input_blob: []const u8) ![]Plugin {
45 | var plugins = std.ArrayList(Plugin).init(alloc);
46 | errdefer {
47 | for (plugins.items) |plugin| {
48 | plugin.deinit(alloc);
49 | }
50 | plugins.deinit();
51 | }
52 |
53 | // then the blob is empty
54 | if (input_blob.len < 3) {
55 | return plugins.toOwnedSlice();
56 | }
57 |
58 | var blob_spliterator = std.mem.splitSequence(u8, input_blob, ";");
59 | while (blob_spliterator.next()) |plugin_str| {
60 | var plugin_spliterator = std.mem.splitSequence(u8, plugin_str, "|");
61 |
62 | const pname = plugin_spliterator.next().?;
63 | const version = plugin_spliterator.next().?;
64 | const path = plugin_spliterator.next().?;
65 |
66 | // Assert the spliterator is now empty
67 | assert(plugin_spliterator.next() == null);
68 |
69 | try plugins.append(.{
70 | .pname = try alloc.dupe(u8, pname),
71 | .version = try alloc.dupe(u8, version),
72 | .path = try alloc.dupe(u8, path),
73 | .tag = .UrlNotFound,
74 | .url = undefined,
75 | });
76 | }
77 |
78 | std.log.debug("Found {d} plugins", .{plugins.items.len});
79 | return try plugins.toOwnedSlice();
80 | }
81 |
82 | fn findPluginUrl(alloc: Allocator, buf: []const u8, plugins: []Plugin) !void {
83 | const State = union(enum) {
84 | findPname,
85 | verifyVersion: *Plugin,
86 | getUrl: *Plugin,
87 | verifyUrl: *Plugin,
88 | };
89 |
90 | var state: State = .findPname;
91 | defer parseAssert(state == .findPname, "Parsing ended in an invalid state");
92 |
93 | var relevant_urls_found: u32 = 0;
94 |
95 | var line_spliterator = std.mem.splitSequence(u8, buf, "\n");
96 | outer: while (line_spliterator.next()) |line| {
97 | switch (state) {
98 | .findPname => {
99 | var split = splitOnEq(line);
100 | if (!eql("pname", trim(split.first())))
101 | continue :outer;
102 |
103 | const pname = trim(split.next().?);
104 |
105 | inner: for (plugins) |*plugin| {
106 | if (!eql(pname, plugin.pname))
107 | continue :inner;
108 |
109 | // We break because we already found the url, no need for double work
110 | if (plugin.tag != .UrlNotFound) continue :outer;
111 |
112 | state = .{ .verifyVersion = plugin };
113 | continue :outer;
114 | }
115 | },
116 |
117 | .verifyVersion => |plugin| {
118 | var split = splitOnEq(line);
119 | const first = trim(split.first());
120 |
121 | parseAssert(!eql("pname", first), "Skipped to next plugin");
122 | if (!eql("version", first))
123 | continue :outer;
124 |
125 | const version = trim(split.next().?);
126 |
127 | // https://github.com/NixOS/nixpkgs/blob/493f07fef3bdc5c7dc09f642ce12b7777d294a71/pkgs/applications/editors/neovim/build-neovim-plugin.nix#L36
128 | const nvimVersion = try std.fmt.allocPrint(alloc, "-unstable-{s}", .{version});
129 | defer alloc.free(nvimVersion);
130 |
131 | const nvimEql = std.mem.endsWith(u8, plugin.version, nvimVersion);
132 | const vimEql = eql(version, plugin.version);
133 | parseAssert(vimEql or nvimEql, "Version was not a known vim or nvim plugin version");
134 |
135 | state = .{ .getUrl = plugin };
136 | },
137 |
138 | .getUrl => |plugin| {
139 | var split = splitOnEq(line);
140 | const first = trim(split.first());
141 |
142 | parseAssert(!eql("pname", first), "Skipped to next plugin");
143 | if (!eql("src", first))
144 | continue :outer;
145 |
146 | parseAssert(plugin.tag == .UrlNotFound, "Url already found");
147 |
148 | const fetch_method = trim(split.next().?);
149 |
150 | if (eql("fetchFromGitHub", fetch_method)) {
151 | plugin.tag = .GithubUrl;
152 |
153 | var ownerLine = splitOnEq(line_spliterator.next().?);
154 | parseAssert(eql("owner", ownerLine.first()), "Github repo owner not found");
155 | const owner = trim(ownerLine.next().?);
156 |
157 | var repoLine = splitOnEq(line_spliterator.next().?);
158 | parseAssert(eql("repo", repoLine.first()), "Github repo name not found");
159 | const repo = trim(repoLine.next().?);
160 |
161 | plugin.url = try std.fmt.allocPrint(
162 | alloc,
163 | "https://github.com/{s}/{s}",
164 | .{ owner, repo },
165 | );
166 | } else if (eql("fetchgit", fetch_method)) {
167 | plugin.tag = .GitUrl;
168 |
169 | var urlLine = splitOnEq(line_spliterator.next().?);
170 | parseAssert(eql("url", utils.trim(urlLine.first())), "fetchgit URL not found");
171 | const url = trim(urlLine.next().?);
172 |
173 | plugin.url = try alloc.dupe(u8, trim(url));
174 | } else if (eql("fetchzip", fetch_method)) {
175 | var urlLine = splitOnEq(line_spliterator.next().?);
176 | parseAssert(eql("url", utils.trim(urlLine.first())), "fetchzip URL not found");
177 | const url_with_zip = trim(urlLine.next().?);
178 |
179 | if (mem.startsWith(u8, url_with_zip, "https://github.com/")) {
180 | plugin.tag = .GithubUrl;
181 | const last_idx = mem.lastIndexOfScalar(u8, url_with_zip, '/').?;
182 | const second_last_idx = mem.lastIndexOfScalar(u8, url_with_zip[0..last_idx], '/').?;
183 |
184 | plugin.url = try alloc.dupe(u8, trim(url_with_zip[0..second_last_idx]));
185 | } else {
186 | std.log.err(
187 | \\Cannot properly parse url for '{s}'. Please parse the URL manually and add it to your patches
188 | \\ '{s}'
189 | ,
190 | .{ plugin.pname, url_with_zip },
191 | );
192 | state = .findPname;
193 | continue :outer;
194 | }
195 | } else {
196 | std.log.err("Found fetch method '{s}'", .{fetch_method});
197 | unreachable;
198 | }
199 |
200 | state = .{ .verifyUrl = plugin };
201 | },
202 |
203 | .verifyUrl => |plugin| {
204 | var split = splitOnEq(line);
205 | const first = trim(split.first());
206 |
207 | parseAssert(!eql("pname", first), "Skipped to next plugin");
208 | if (!eql("meta.homepage", first) and !eql("meta", first))
209 | continue :outer;
210 |
211 | const url = blk: {
212 | if (eql("meta.homepage", first)) {
213 | break :blk trim(split.next().?);
214 | } else {
215 | // TODO: This assumes that "homepage" will always be the
216 | // next line, make it more robust
217 | var homepage_split = splitOnEq(line_spliterator.next().?);
218 | parseAssert(eql("homepage", homepage_split.first()), "Found neither meta.homepage or homepage");
219 |
220 | break :blk trim(homepage_split.next().?);
221 | }
222 | };
223 |
224 | parseAssert(eql(url, plugin.url), "git URL and homepage URL are not the same");
225 |
226 | relevant_urls_found += 1;
227 | state = .findPname;
228 | },
229 | }
230 | }
231 |
232 | std.log.debug("Found {d} relevant urls", .{relevant_urls_found});
233 | }
234 |
235 | fn parseAssert(ok: bool, assumption: []const u8) void {
236 | if (ok) return;
237 |
238 | std.log.err("Parsing failed: {s}", .{assumption});
239 | std.log.err("Please file a github issue :)", .{});
240 | }
241 |
242 | fn eqlPlugin(a: Plugin, b: Plugin) !void {
243 | try std.testing.expectEqualSlices(u8, a.pname, b.pname);
244 | try std.testing.expectEqualSlices(u8, a.version, b.version);
245 | try std.testing.expectEqualSlices(u8, a.path, b.path);
246 | try std.testing.expectEqual(a.tag, b.tag);
247 |
248 | if (a.tag == .UrlNotFound) return;
249 |
250 | try std.testing.expectEqualSlices(u8, a.url, b.url);
251 | }
252 |
253 | // ---- Tests ----
254 |
255 | test parseBlob {
256 | const alloc = std.testing.allocator;
257 |
258 | const input = "name|version|path;name2|version2|path2;name3|version3|path3";
259 | const output = try parseBlob(alloc, input);
260 | defer {
261 | for (output) |plugin| {
262 | plugin.deinit(alloc);
263 | }
264 | alloc.free(output);
265 | }
266 |
267 | const expected: []const Plugin = &.{
268 | Plugin{
269 | .pname = "name",
270 | .version = "version",
271 | .path = "path",
272 | .tag = .UrlNotFound,
273 | .url = undefined,
274 | },
275 | Plugin{
276 | .pname = "name2",
277 | .version = "version2",
278 | .path = "path2",
279 | .tag = .UrlNotFound,
280 | .url = undefined,
281 | },
282 | Plugin{
283 | .pname = "name3",
284 | .version = "version3",
285 | .path = "path3",
286 | .tag = .UrlNotFound,
287 | .url = undefined,
288 | },
289 | };
290 |
291 | for (0..3) |idx| {
292 | try eqlPlugin(output[idx], expected[idx]);
293 | }
294 | }
295 |
296 | test findPluginUrl {
297 | const alloc = std.testing.allocator;
298 |
299 | const input_buf =
300 | \\# GENERATED by ./pkgs/applications/editors/vim/plugins/update.py. Do not edit!
301 | \\{ lib, buildVimPlugin, buildNeovimPlugin, fetchFromGitHub, fetchgit }:
302 | \\
303 | \\final: prev:
304 | \\{
305 | \\ // Parse this one normally
306 | \\ BetterLua-vim = buildVimPlugin {
307 | \\ pname = "BetterLua.vim";
308 | \\ version = "2020-08-14";
309 | \\ src = fetchFromGitHub {
310 | \\ owner = "euclidianAce";
311 | \\ repo = "BetterLua.vim";
312 | \\ rev = "d2d6c115575d09258a794a6f20ac60233eee59d5";
313 | \\ sha256 = "1rvlx21kw8865dg6q97hx9i2s1n8mn1nyhn0m7dkx625pghsx3js";
314 | \\ };
315 | \\ meta.homepage = "https://github.com/euclidianAce/BetterLua.vim/";
316 | \\ };
317 | \\
318 | \\ // Ignore this one
319 | \\ BufOnly-vim = buildVimPlugin {
320 | \\ pname = "BufOnly.vim";
321 | \\ version = "2010-10-18";
322 | \\ src = fetchFromGitHub {
323 | \\ owner = "vim-scripts";
324 | \\ repo = "BufOnly.vim";
325 | \\ rev = "43dd92303979bdb234a3cb2f5662847f7a3affe7";
326 | \\ sha256 = "1gvpaqvvxjma0dl1zai68bpv42608api4054appwkw9pgczkkcdl";
327 | \\ };
328 | \\ meta.homepage = "https://github.com/vim-scripts/BufOnly.vim/";
329 | \\ };
330 | \\
331 | \\ // Garbage for fun
332 | \\ src = "fdafasdf";
333 | \\ version = "adfasdf";
334 | \\
335 | \\ // NeovimPlugin
336 | \\ fidget-nvim = buildNeovimPlugin {
337 | \\ pname = "fidget.nvim";
338 | \\ version = "2024-05-19";
339 | \\ src = fetchFromGitHub {
340 | \\ owner = "j-hui";
341 | \\ repo = "fidget.nvim";
342 | \\ rev = "ef99df04a1c53a453602421bc0f756997edc8289";
343 | \\ sha256 = "1j0s31k8dszb0sq46c492hj27w0ag2zmxy75y8204f3j80dkz68s";
344 | \\ };
345 | \\ meta.homepage = "https://github.com/j-hui/fidget.nvim/";
346 | \\ };
347 | \\
348 | \\ // Fetchgit plugin
349 | \\ hare-vim = buildVimPlugin {
350 | \\ pname = "hare.vim";
351 | \\ version = "2024-05-24";
352 | \\ src = fetchgit {
353 | \\ url = "https://git.sr.ht/~sircmpwn/hare.vim";
354 | \\ rev = "e0d38c0563224aa7b0101f64640788691f6c15b9";
355 | \\ sha256 = "1csc5923acy7awgix0qfkal39v4shzw5vyvw56vkmazvc8n8rqs6";
356 | \\ };
357 | \\ meta.homepage = "https://git.sr.ht/~sircmpwn/hare.vim";
358 | \\ };
359 | ;
360 |
361 | var plugins = [_]Plugin{
362 | Plugin{
363 | .pname = "BetterLua.vim",
364 | .version = "2020-08-14",
365 | .path = "path",
366 | .tag = .UrlNotFound,
367 | .url = undefined,
368 | },
369 | Plugin{
370 | .pname = "hare.vim",
371 | .version = "2024-05-24",
372 | .path = "path2",
373 | .tag = .UrlNotFound,
374 | .url = undefined,
375 | },
376 | Plugin{
377 | .pname = "fidget.nvim",
378 | .version = "2024-05-19",
379 | .path = "path3",
380 | .tag = .UrlNotFound,
381 | .url = undefined,
382 | },
383 | };
384 |
385 | try findPluginUrl(
386 | alloc,
387 | input_buf,
388 | plugins[0..],
389 | );
390 | defer {
391 | for (plugins) |plugin| {
392 | alloc.free(plugin.url);
393 | }
394 | }
395 |
396 | try std.testing.expectEqual(plugins.len, 3);
397 |
398 | const expected: []const Plugin = &.{
399 | Plugin{
400 | .pname = "BetterLua.vim",
401 | .version = "2020-08-14",
402 | .path = "path",
403 | .tag = .GithubUrl,
404 | .url = "https://github.com/euclidianAce/BetterLua.vim",
405 | },
406 | Plugin{
407 | .pname = "hare.vim",
408 | .version = "2024-05-24",
409 | .path = "path2",
410 | .tag = .GitUrl,
411 | .url = "https://git.sr.ht/~sircmpwn/hare.vim",
412 | },
413 | Plugin{
414 | .pname = "fidget.nvim",
415 | .version = "2024-05-19",
416 | .path = "path3",
417 | .tag = .GithubUrl,
418 | .url = "https://github.com/j-hui/fidget.nvim",
419 | },
420 | };
421 |
422 | for (0..3) |idx| {
423 | try eqlPlugin(plugins[idx], expected[idx]);
424 | }
425 | }
426 |
427 | // input parser utils
428 | const mem = std.mem;
429 |
430 | pub fn trim(input: []const u8) []const u8 {
431 | return mem.trim(u8, input, " /\\;{}\"\n");
432 | }
433 |
434 | fn splitOnEq(input: []const u8) mem.SplitIterator(u8, .sequence) {
435 | return mem.splitSequence(u8, input, "=");
436 | }
437 |
438 | fn eql(expected: []const u8, input: []const u8) bool {
439 | return mem.eql(u8, expected, trim(input));
440 | }
441 |
442 | const std = @import("std");
443 | const root = @import("root.zig");
444 | const fs = std.fs;
445 | const utils = root.utils;
446 | const assert = std.debug.assert;
447 |
448 | const Allocator = std.mem.Allocator;
449 | const File = std.fs.File;
450 | const Plugin = root.Plugin;
451 |
--------------------------------------------------------------------------------
/patcher/src/lib/root.zig:
--------------------------------------------------------------------------------
1 | pub const utils = @import("utils.zig");
2 | pub const nixpkgs_parser = @import("nixpkgs_parser.zig");
3 | pub const LuaIter = @import("LuaIter.zig");
4 | pub const LuaParser = @import("LuaParser.zig");
5 | pub const Plugin = types.Plugin;
6 | pub const Substitution = types.Substitution;
7 |
8 | const types = @import("types.zig");
9 |
10 | test {
11 | @import("std").testing.refAllDeclsRecursive(@This());
12 | }
13 |
--------------------------------------------------------------------------------
/patcher/src/lib/types.zig:
--------------------------------------------------------------------------------
1 | pub const Plugin = struct {
2 | pname: []const u8,
3 | version: []const u8,
4 | path: []const u8,
5 |
6 | tag: Tag,
7 | url: []const u8,
8 |
9 | const Tag = enum {
10 | /// url field in undefined
11 | UrlNotFound,
12 |
13 | /// url field is github url
14 | GithubUrl,
15 |
16 | /// url field is non specific url
17 | GitUrl,
18 | };
19 |
20 | pub fn deinit(self: Plugin, alloc: Allocator) void {
21 | alloc.free(self.pname);
22 | alloc.free(self.version);
23 | alloc.free(self.path);
24 |
25 | if (self.tag == .UrlNotFound) return;
26 |
27 | alloc.free(self.url);
28 | }
29 |
30 | pub fn deinitPlugins(slice: []const Plugin, alloc: Allocator) void {
31 | for (slice) |plugin| {
32 | plugin.deinit(alloc);
33 | }
34 | alloc.free(slice);
35 | }
36 | };
37 |
38 | pub const Substitution = struct {
39 | from: []const u8,
40 | to: []const u8,
41 | tag: Tag,
42 |
43 | pub const Tag = union(enum) {
44 | /// Extra data is the pname
45 | url: []const u8,
46 | /// Extra data is the key
47 | string: ?[]const u8,
48 | raw,
49 | };
50 |
51 | pub fn initUrlSub(
52 | alloc: Allocator,
53 | from: []const u8,
54 | to: []const u8,
55 | pname: []const u8,
56 | ) !Substitution {
57 | return Substitution{
58 | .from = try alloc.dupe(u8, from),
59 | .to = try alloc.dupe(u8, to),
60 | .tag = .{ .url = try alloc.dupe(u8, pname) },
61 | };
62 | }
63 |
64 | pub fn initStringSub(
65 | alloc: Allocator,
66 | from: []const u8,
67 | to: []const u8,
68 | key: ?[]const u8,
69 | ) !Substitution {
70 | return Substitution{
71 | .from = try alloc.dupe(u8, from),
72 | .to = try alloc.dupe(u8, to),
73 | .tag = .{ .string = if (key) |k| try alloc.dupe(u8, k) else null },
74 | };
75 | }
76 |
77 | pub fn deinit(self: Substitution, alloc: Allocator) void {
78 | alloc.free(self.to);
79 | alloc.free(self.from);
80 | switch (self.tag) {
81 | .raw => {},
82 | .url => |pname| alloc.free(pname),
83 | .string => |key| {
84 | if (key) |k| {
85 | alloc.free(k);
86 | }
87 | },
88 | }
89 | }
90 |
91 | pub fn deinitSubs(slice: []const Substitution, alloc: Allocator) void {
92 | for (slice) |sub| {
93 | sub.deinit(alloc);
94 | }
95 | alloc.free(slice);
96 | }
97 |
98 | pub fn format(sub: Substitution, comptime fmt: []const u8, options: FormatOptions, writer: anytype) !void {
99 | _ = options;
100 | _ = fmt;
101 |
102 | switch (sub.tag) {
103 | .url => |pname| {
104 | try writer.writeAll("Substitution(url){'");
105 | try writer.writeAll(sub.from);
106 | try writer.writeAll("' -> '");
107 | try writer.writeAll(sub.to);
108 | try writer.writeAll("', pname: ");
109 | try writer.writeAll(pname);
110 | try writer.writeAll("}");
111 | },
112 | .string => |key| {
113 | try writer.writeAll("Substitution(string){'");
114 | try writer.writeAll(sub.from);
115 | try writer.writeAll("' -> '");
116 | try writer.writeAll(sub.to);
117 | try writer.writeAll("', key: ");
118 | try writer.writeAll(key orelse "null");
119 | try writer.writeAll("}");
120 | },
121 | }
122 | }
123 | };
124 |
125 | const std = @import("std");
126 |
127 | const Allocator = std.mem.Allocator;
128 | const FormatOptions = std.fmt.FormatOptions;
129 |
--------------------------------------------------------------------------------
/patcher/src/lib/utils.zig:
--------------------------------------------------------------------------------
1 | pub const MmapConfig = struct {
2 | read: bool = true,
3 | write: bool = false,
4 | };
5 |
6 | pub fn mmapFile(file: File, config: MmapConfig) ![]align(page_size_min) u8 {
7 | assert(@import("builtin").os.tag != .windows);
8 |
9 | const md = try file.metadata();
10 | assert(md.size() <= std.math.maxInt(usize));
11 |
12 | var prot: u32 = 0;
13 | if (config.read) prot |= posix.PROT.READ;
14 | if (config.write) prot |= posix.PROT.WRITE;
15 |
16 | return try posix.mmap(
17 | null,
18 | @intCast(md.size()),
19 | prot,
20 | .{ .TYPE = .SHARED },
21 | file.handle,
22 | 0,
23 | );
24 | }
25 |
26 | pub fn unMmapFile(mapped_file: []align(page_size_min) u8) void {
27 | assert(@import("builtin").os.tag != .windows);
28 |
29 | posix.munmap(mapped_file);
30 | }
31 |
32 | pub fn trim(input: []const u8) []const u8 {
33 | return mem.trim(u8, input, " \\;{}\"\n");
34 | }
35 |
36 | pub fn split(input: []const u8) mem.SplitIterator(u8, .sequence) {
37 | return mem.splitSequence(u8, input, "=");
38 | }
39 |
40 | pub fn eql(expected: []const u8, input: []const u8) bool {
41 | return mem.eql(u8, expected, trim(input));
42 | }
43 |
44 | const std = @import("std");
45 | const fs = std.fs;
46 | const mem = std.mem;
47 | const posix = std.posix;
48 |
49 | const assert = std.debug.assert;
50 | const page_size_min = std.heap.page_size_min;
51 |
52 | const File = fs.File;
53 |
--------------------------------------------------------------------------------
/patcher/src/main.zig:
--------------------------------------------------------------------------------
1 | pub const std_options: std.Options = .{
2 | .log_level = .info,
3 | .logFn = logFn,
4 | };
5 |
6 | fn logFn(
7 | comptime message_level: std.log.Level,
8 | comptime scope: @TypeOf(.enum_literal),
9 | comptime format: []const u8,
10 | args: anytype,
11 | ) void {
12 | const stderr = std.io.getStdErr();
13 | const writer = stderr.writer();
14 | const config = std.io.tty.detectConfig(stderr);
15 |
16 | // Highlight warnings and errors
17 | switch (message_level) {
18 | .warn => config.setColor(writer, .bright_yellow) catch {},
19 | .err => {
20 | config.setColor(writer, .bright_red) catch {};
21 | config.setColor(writer, .bold) catch {};
22 | },
23 | else => {},
24 | }
25 |
26 | std.log.defaultLog(message_level, scope, format, args);
27 |
28 | // Reset colors after logging
29 | config.setColor(writer, .reset) catch {};
30 | }
31 |
32 | pub fn main() !void {
33 | if (@import("builtin").os.tag == .windows) {
34 | @compileError("nv does not support windows, please use a posix system");
35 | }
36 |
37 | const start_time = try std.time.Instant.now();
38 | defer {
39 | const end_time = std.time.Instant.now() catch unreachable;
40 | const elapsed = end_time.since(start_time);
41 | std.log.info("Patching took {d}ms", .{elapsed / std.time.ns_per_ms});
42 | }
43 |
44 | var gpa = std.heap.GeneralPurposeAllocator(.{}){};
45 | defer _ = gpa.deinit();
46 |
47 | const alloc = gpa.allocator();
48 |
49 | const args = try std.process.argsAlloc(alloc);
50 | defer std.process.argsFree(alloc, args);
51 |
52 | if (args.len != 7) {
53 | std.log.err(
54 | \\invalid arguments, expected 6 got {d}
55 | \\ Expected order:
56 | \\ path to nixpkgs
57 | \\ The path to read the config from
58 | \\ Path to put config
59 | \\ Plugins in the format `pname|version|path;pname|version|path;...`
60 | \\ Substitutions in the format `type|from|to|extra;`
61 | \\ Extra Lua config put at the top of init.lua
62 | \\ For more information read the readme (TODO: Make readme)
63 | ,
64 | .{args.len - 1},
65 | );
66 | std.process.exit(1);
67 | }
68 |
69 | const nixpkgs_path = args[1];
70 | const in_path = args[2];
71 | const out_path = args[3];
72 | const input_blob = args[4];
73 | const extra_subs: []const u8 = args[5];
74 | const extra_config: []const u8 = args[6];
75 |
76 | assert(std.fs.path.isAbsolute(nixpkgs_path));
77 | assert(std.fs.path.isAbsolute(in_path));
78 | assert(std.fs.path.isAbsolute(out_path));
79 |
80 | const plugins = try getPlugins(alloc, nixpkgs_path, input_blob);
81 | defer Plugin.deinitPlugins(plugins, alloc);
82 |
83 | const subs = try getSubs(alloc, plugins, extra_subs);
84 | defer Substitution.deinitSubs(subs, alloc);
85 |
86 | try patchConfig(alloc, subs, in_path, out_path, extra_config);
87 | }
88 |
89 | fn patchConfig(
90 | alloc: Allocator,
91 | subs: []const Substitution,
92 | in_path: []const u8,
93 | out_path: []const u8,
94 | extra_config: []const u8,
95 | ) !void {
96 | assert(fs.path.isAbsolute(in_path));
97 | assert(fs.path.isAbsolute(out_path));
98 |
99 | std.log.debug("Attempting to open dir '{s}'", .{in_path});
100 | const in_dir = try fs.openDirAbsolute(in_path, .{ .iterate = true });
101 |
102 | std.log.debug("Attempting to create '{s}'", .{out_path});
103 |
104 | // Go on if the dir already exists
105 | fs.accessAbsolute(out_path, .{}) catch {
106 | try fs.makeDirAbsolute(out_path);
107 | };
108 |
109 | std.log.debug("Attempting to open '{s}'", .{out_path});
110 | const out_dir = try fs.openDirAbsolute(out_path, .{});
111 |
112 | var lua_parser = try LuaParser.init(
113 | alloc,
114 | in_dir,
115 | out_dir,
116 | extra_config,
117 | );
118 | defer lua_parser.deinit();
119 |
120 | try lua_parser.createConfig(subs);
121 | }
122 |
123 | fn getPlugins(alloc: Allocator, nixpkgs_path: []const u8, input_blob: []const u8) ![]const Plugin {
124 | assert(fs.path.isAbsolute(nixpkgs_path));
125 |
126 | // Get the plugin file
127 | const vim_plugins_path = try fs.path.join(alloc, &.{
128 | nixpkgs_path,
129 | "pkgs",
130 | "applications",
131 | "editors",
132 | "vim",
133 | "plugins",
134 | "generated.nix",
135 | });
136 | defer alloc.free(vim_plugins_path);
137 |
138 | std.log.debug("Attempting to open file '{s}'", .{vim_plugins_path});
139 | const vim_plugins_file = try fs.openFileAbsolute(vim_plugins_path, .{});
140 | defer vim_plugins_file.close();
141 | //
142 | // Get the plugin file
143 | const lua_plugins_path = try fs.path.join(alloc, &.{
144 | nixpkgs_path,
145 | "pkgs",
146 | "development",
147 | "lua-modules",
148 | "generated-packages.nix",
149 | });
150 | defer alloc.free(lua_plugins_path);
151 |
152 | std.log.debug("Attempting to open file '{s}'", .{lua_plugins_path});
153 | const lua_plugins_file = try fs.openFileAbsolute(lua_plugins_path, .{});
154 | defer lua_plugins_file.close();
155 |
156 | const files: []const File = &.{ vim_plugins_file, lua_plugins_file };
157 |
158 | return try nixpkgs_parser.parseFiles(alloc, input_blob, files);
159 | }
160 |
161 | fn getSubs(alloc: Allocator, plugins: []const Plugin, extra_subs: []const u8) ![]const Substitution {
162 | // Most plugins are a github short and long url
163 | // extra subs is the amount of semicolons plus the final sub
164 | const estimated_cap = plugins.len * 2 + std.mem.count(u8, extra_subs, ";") + 1;
165 | var subs = try std.ArrayList(Substitution).initCapacity(alloc, estimated_cap);
166 | errdefer {
167 | for (subs.items) |sub| {
168 | sub.deinit(alloc);
169 | }
170 | subs.deinit();
171 | }
172 |
173 | try subsFromPlugins(alloc, plugins, &subs);
174 | try subsFromBlob(alloc, extra_subs, &subs);
175 |
176 | for (subs.items, 0..) |sub_haystack, idx_haystack| {
177 | for (subs.items, 0..) |sub_needle, idx_needle| {
178 | if (idx_haystack == idx_needle) continue;
179 |
180 | if (std.mem.eql(u8, sub_needle.from, sub_haystack.from) and
181 | !std.mem.eql(u8, sub_needle.to, sub_haystack.to))
182 | {
183 | std.log.err(
184 | "Trying to substitute '{s}' to both '{s}' and '{s}'",
185 | .{ sub_needle.from, sub_needle.to, sub_haystack.to },
186 | );
187 | std.log.err("This may be because you have a substitution that collides with a plugin", .{});
188 | std.log.err("or 2 substitutions that collide with eachother. exiting...", .{});
189 | std.process.exit(1);
190 | }
191 | }
192 | }
193 |
194 | return try subs.toOwnedSlice();
195 | }
196 |
197 | /// Memory owned by caller
198 | fn subsFromBlob(alloc: Allocator, subs_blob: []const u8, out: *std.ArrayList(Substitution)) !void {
199 | var iter = LuaIter.init(subs_blob);
200 |
201 | // If the blob is less than 3 characters, the blob must not contain any subs
202 | // as at least 3 seperator characters are required
203 | if (subs_blob.len < 3) {
204 | return;
205 | }
206 |
207 | while (!iter.isDone()) {
208 | const typ = iter.nextUntilBefore("|").?;
209 | _ = iter.next();
210 | const from = iter.nextUntilBefore("|").?;
211 | _ = iter.next();
212 | const to = iter.nextUntilBefore("|").?;
213 | _ = iter.next();
214 | const extra = iter.nextUntilBefore(";") orelse iter.rest() orelse return error.BadSub;
215 | _ = iter.next();
216 |
217 | if (std.mem.eql(u8, typ, "plugin")) {
218 | try out.append(try Substitution.initUrlSub(alloc, from, to, extra));
219 | } else if (std.mem.eql(u8, typ, "string")) {
220 | if (std.mem.eql(u8, extra, "-")) {
221 | try out.append(try Substitution.initStringSub(alloc, from, to, null));
222 | } else {
223 | try out.append(try Substitution.initStringSub(alloc, from, to, extra));
224 | }
225 | } else unreachable;
226 | }
227 | }
228 |
229 | /// Memory owned by caller
230 | fn subsFromPlugins(alloc: Allocator, plugins: []const Plugin, out: *std.ArrayList(Substitution)) !void {
231 | for (plugins) |plugin| {
232 | switch (plugin.tag) {
233 | .UrlNotFound => continue,
234 | .GitUrl => {
235 | try out.append(try Substitution.initUrlSub(
236 | alloc,
237 | plugin.url,
238 | plugin.path,
239 | plugin.pname,
240 | ));
241 | },
242 | .GithubUrl => {
243 | try out.append(try Substitution.initUrlSub(
244 | alloc,
245 | plugin.url,
246 | plugin.path,
247 | plugin.pname,
248 | ));
249 |
250 | var url_splitter = std.mem.splitSequence(u8, plugin.url, "://github.com/");
251 | _ = url_splitter.next().?;
252 | const short_url = url_splitter.rest();
253 |
254 | try out.append(try Substitution.initUrlSub(
255 | alloc,
256 | short_url,
257 | plugin.path,
258 | plugin.pname,
259 | ));
260 | },
261 | }
262 | }
263 | }
264 |
265 | const std = @import("std");
266 | const lib = @import("lib");
267 | const fs = std.fs;
268 | const nixpkgs_parser = lib.nixpkgs_parser;
269 |
270 | const assert = std.debug.assert;
271 | const util = lib.utils;
272 |
273 | const LuaParser = lib.LuaParser;
274 | const LuaIter = lib.LuaIter;
275 | const Plugin = lib.Plugin;
276 | const Substitution = lib.Substitution;
277 | const Allocator = std.mem.Allocator;
278 | const File = std.fs.File;
279 |
--------------------------------------------------------------------------------
/patcher/test/luaparser-integration/expected/deep/nested/file/found_me.lua:
--------------------------------------------------------------------------------
1 | return {
2 | "wow you found me",
3 | }
4 |
--------------------------------------------------------------------------------
/patcher/test/luaparser-integration/expected/init.lua:
--------------------------------------------------------------------------------
1 | [[dont_replace_me]]
2 | return {
3 | "Hello World",
4 | }
5 |
--------------------------------------------------------------------------------
/patcher/test/luaparser-integration/expected/other_ft.toml:
--------------------------------------------------------------------------------
1 | return {
2 | "replace_me",
3 | "replace_me",
4 | [[replace_me]],
5 | }
6 |
--------------------------------------------------------------------------------
/patcher/test/luaparser-integration/expected/plugin.lua:
--------------------------------------------------------------------------------
1 | return; -- Gotta make the lua invalid to not format
2 | local smth = {
3 | dir = [[plugin/path]], name = [[plugin-name]],
4 | }
5 |
6 | return { dir = [[third/path]], name = [[third-name]] }
7 |
8 | return {
9 | {
10 | dir = [[plugin/path]], name = [[plugin-name]],
11 | },
12 | {
13 | dir = [[other/path]], name = [[other-name]],
14 | },
15 | smth,
16 | }
17 |
--------------------------------------------------------------------------------
/patcher/test/luaparser-integration/expected/raw_plugin.lua:
--------------------------------------------------------------------------------
1 | return; -- Gotta make the lua invalid to not format
2 | return {
3 | {
4 | str = [[I_was_key_replaced]],
5 | },
6 | {
7 | "replace_keyed",
8 | },
9 | {
10 | adl = "replace_keyed",
11 | },
12 | }
13 |
--------------------------------------------------------------------------------
/patcher/test/luaparser-integration/expected/simple_replacements.lua:
--------------------------------------------------------------------------------
1 | return {
2 | [[I_was_replaced]],
3 | [[I_was_replaced]],
4 | [[I_was_replaced]],
5 | }
6 |
--------------------------------------------------------------------------------
/patcher/test/luaparser-integration/expected/symlink.lua:
--------------------------------------------------------------------------------
1 | deep/nested/file/found_me.lua
--------------------------------------------------------------------------------
/patcher/test/luaparser-integration/input/deep/nested/file/found_me.lua:
--------------------------------------------------------------------------------
1 | return {
2 | "wow you found me",
3 | }
4 |
--------------------------------------------------------------------------------
/patcher/test/luaparser-integration/input/init.lua:
--------------------------------------------------------------------------------
1 | return {
2 | "Hello World",
3 | }
4 |
--------------------------------------------------------------------------------
/patcher/test/luaparser-integration/input/other_ft.toml:
--------------------------------------------------------------------------------
1 | return {
2 | "replace_me",
3 | "replace_me",
4 | [[replace_me]],
5 | }
6 |
--------------------------------------------------------------------------------
/patcher/test/luaparser-integration/input/plugin.lua:
--------------------------------------------------------------------------------
1 | return; -- Gotta make the lua invalid to not format
2 | local smth = {
3 | "plugin/url",
4 | }
5 |
6 | return { "third/url" }
7 |
8 | return {
9 | {
10 | "plugin/url",
11 | },
12 | {
13 | "other/url",
14 | },
15 | smth,
16 | }
17 |
--------------------------------------------------------------------------------
/patcher/test/luaparser-integration/input/raw_plugin.lua:
--------------------------------------------------------------------------------
1 | return; -- Gotta make the lua invalid to not format
2 | return {
3 | {
4 | str = "replace_keyed",
5 | },
6 | {
7 | "replace_keyed",
8 | },
9 | {
10 | adl = "replace_keyed",
11 | },
12 | }
13 |
--------------------------------------------------------------------------------
/patcher/test/luaparser-integration/input/simple_replacements.lua:
--------------------------------------------------------------------------------
1 | return {
2 | "replace_me",
3 | "replace_me",
4 | [[replace_me]],
5 | }
6 |
--------------------------------------------------------------------------------
/patcher/test/luaparser-integration/input/symlink.lua:
--------------------------------------------------------------------------------
1 | deep/nested/file/found_me.lua
--------------------------------------------------------------------------------
/patcher/test/luaparser-integration/root.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const lib = @import("lib");
3 | const testing = std.testing;
4 | const fs = std.fs;
5 | const utils = lib.utils;
6 |
7 | const eqlString = testing.expectEqualStrings;
8 | const expect = testing.expect;
9 |
10 | const Dir = fs.Dir;
11 | const File = fs.File;
12 | const LuaParser = lib.LuaParser;
13 | const Subs = lib.Substitution;
14 |
15 | test {
16 | var cwd = try fs.cwd().openDir(".", .{ .iterate = true });
17 | defer cwd.close();
18 |
19 | try cwd.deleteTree("patcher/test/luaparser-integration/out");
20 | defer cwd.deleteTree("patcher/test/luaparser-integration/out") catch unreachable;
21 |
22 | const subs = try makeSubs();
23 | defer Subs.deinitSubs(subs, alloc);
24 |
25 | const extra_init_config = "[[dont_replace_me]]";
26 |
27 | try run(cwd, extra_init_config, subs);
28 |
29 | try verify(cwd);
30 | }
31 |
32 | fn makeSubs() ![]const Subs {
33 | var out = std.ArrayList(Subs).init(alloc);
34 |
35 | // Should not be replaced
36 | try out.append(try Subs.initStringSub(alloc, "dont_replace_me", "ERROR I WAS REPLACED", null));
37 |
38 | // String replacements
39 | try out.append(try Subs.initStringSub(alloc, "replace_me", "I_was_replaced", null));
40 | try out.append(try Subs.initStringSub(alloc, "replace_keyed", "I_was_key_replaced", "str"));
41 |
42 | // Url replacements
43 | try out.append(try Subs.initUrlSub(alloc, "plugin/url", "plugin/path", "plugin-name"));
44 | try out.append(try Subs.initUrlSub(alloc, "other/url", "other/path", "other-name"));
45 | try out.append(try Subs.initUrlSub(alloc, "third/url", "third/path", "third-name"));
46 |
47 | return out.toOwnedSlice();
48 | }
49 |
50 | const alloc = testing.allocator;
51 |
52 | fn run(cwd: Dir, extra_init_config: []const u8, subs: []const Subs) !void {
53 | var in_dir = try cwd.openDir("patcher/test/luaparser-integration/input", .{ .iterate = true });
54 | defer in_dir.close();
55 |
56 | try cwd.makeDir("patcher/test/luaparser-integration/out");
57 | var out_dir = try cwd.openDir("patcher/test/luaparser-integration/out", .{ .iterate = true });
58 | defer out_dir.close();
59 |
60 | var parser = try LuaParser.init(alloc, in_dir, out_dir, extra_init_config);
61 |
62 | try parser.createConfig(subs);
63 | }
64 |
65 | fn verify(cwd: Dir) !void {
66 | var expected_dir = try cwd.openDir("patcher/test/luaparser-integration/expected", .{ .iterate = true });
67 | defer expected_dir.close();
68 |
69 | var expected_walk = try expected_dir.walk(alloc);
70 | defer expected_walk.deinit();
71 |
72 | var out_dir = try cwd.openDir("patcher/test/luaparser-integration/out", .{ .iterate = true });
73 | defer out_dir.close();
74 |
75 | var out_walk = try out_dir.walk(alloc);
76 | defer out_walk.deinit();
77 |
78 | while (try expected_walk.next()) |expected_entry| {
79 | const out_entry = (try out_walk.next()).?;
80 | try eqlString(expected_entry.basename, out_entry.basename);
81 |
82 | switch (expected_entry.kind) {
83 | .file => {
84 | var expected_file = try expected_entry.dir.openFile(expected_entry.basename, .{});
85 | defer expected_file.close();
86 |
87 | var out_file = try out_entry.dir.openFile(out_entry.basename, .{});
88 | defer out_file.close();
89 |
90 | try eqlFile(expected_file, out_file, expected_entry.path);
91 | },
92 | else => {},
93 | }
94 | }
95 |
96 | try expect(try out_walk.next() == null);
97 | }
98 |
99 | fn eqlFile(expected_file: File, out_file: File, file_name: []const u8) !void {
100 | const expected_buf = try utils.mmapFile(expected_file, .{});
101 | defer utils.unMmapFile(expected_buf);
102 |
103 | const out_buf = try utils.mmapFile(out_file, .{});
104 | defer utils.unMmapFile(out_buf);
105 |
106 | eqlString(expected_buf, out_buf) catch |err| {
107 | std.log.err("File '{s}' errored", .{file_name});
108 | return err;
109 | };
110 | }
111 |
--------------------------------------------------------------------------------
/patcher/test/root.zig:
--------------------------------------------------------------------------------
1 | test {
2 | _ = @import("luaparser-integration/root.zig");
3 | }
4 |
--------------------------------------------------------------------------------
/scripts/README.md:
--------------------------------------------------------------------------------
1 | These scripts are meant to be run using `zig run` from the root project directory.
2 | Use Zig 0.14.0.
3 |
--------------------------------------------------------------------------------
/scripts/build-commands.zig:
--------------------------------------------------------------------------------
1 | pub fn main() !void {
2 | var gpa = std.heap.GeneralPurposeAllocator(.{}){};
3 | defer _ = gpa.deinit();
4 |
5 | const alloc = gpa.allocator();
6 |
7 | const args = try process.argsAlloc(alloc);
8 | defer process.argsFree(alloc, args);
9 |
10 | assert(args.len >= 1);
11 |
12 | // Zig build --verbose {args[1..]}
13 | // 1 2 3 args.len -1
14 | const build_args = try alloc.alloc([]const u8, 3 + args.len - 1);
15 | defer alloc.free(build_args);
16 |
17 | build_args[0..3].* = .{
18 | "zig",
19 | "build",
20 | "--verbose",
21 | };
22 |
23 | @memcpy(build_args[3..], args[1..]);
24 |
25 | std.log.info("Running command:\ninfo: {s} \\", .{build_args[0]});
26 | for (build_args[1..]) |item| {
27 | std.log.info("\t{s} \\", .{item});
28 | }
29 |
30 | var stdout: ArrayListUnmanaged(u8) = .empty;
31 | defer stdout.deinit(alloc);
32 |
33 | var stderr: ArrayListUnmanaged(u8) = .empty;
34 | defer stderr.deinit(alloc);
35 |
36 | const term = blk: {
37 | var build_process = Child.init(build_args, alloc);
38 | build_process.stdin_behavior = .Pipe;
39 | build_process.stdout_behavior = .Pipe;
40 | build_process.stderr_behavior = .Pipe;
41 |
42 | try build_process.spawn();
43 |
44 | try build_process.collectOutput(alloc, &stdout, &stderr, std.math.maxInt(isize));
45 |
46 | break :blk try build_process.wait();
47 | };
48 |
49 | if (term != .Exited or term.Exited != 0) {
50 | std.log.err(
51 | \\Build process exited with exit code {d}
52 | \\stderr:
53 | \\{s}
54 | \\
55 | \\stdout:
56 | \\{s}
57 | , .{ term.Exited, stderr.items, stdout.items });
58 |
59 | return;
60 | }
61 |
62 | const cwd = std.fs.cwd();
63 | const pwd = try cwd.realpathAlloc(alloc, ".");
64 | defer alloc.free(pwd);
65 |
66 | std.log.info("Assuming pwd is {s}", .{pwd});
67 |
68 | const io_stdout = std.io.getStdOut();
69 | const writer = io_stdout.writer();
70 |
71 | var lines = mem.splitScalar(u8, stderr.items, '\n');
72 | while (lines.next()) |line| {
73 | if (line.len == 0) continue;
74 | var line_args = mem.splitScalar(u8, line, ' ');
75 |
76 | if (!mem.endsWith(u8, line_args.first(), "zig")) continue;
77 | try writer.writeAll("zig");
78 |
79 | while (line_args.next()) |arg| {
80 | if (arg.len == 0 or
81 | mem.eql(u8, arg, "--listen=-")) continue;
82 |
83 | // add a space and "\n\t" to have a somewhat nice command
84 | try writer.writeAll(" \\\n\t");
85 |
86 | if (mem.eql(u8, arg, "--global-cache-dir")) {
87 | assert(line_args.next() != null); // --global-cache-dir should have an arg
88 |
89 | // If we see global-cache-dir, we need to replace the next arg
90 | // with $(pwd)/.cache
91 | try writer.writeAll("--global-cache-dir $(pwd)/.cache");
92 | } else if (mem.indexOf(u8, arg, pwd)) |index| {
93 | // If we reference pwd, replace that with $(pwd)
94 | try writer.writeAll(arg[0..index]);
95 | try writer.writeAll("$(pwd)");
96 | try writer.writeAll(arg[index + pwd.len ..]);
97 | } else {
98 | try writer.writeAll(arg);
99 | }
100 | }
101 | try writer.writeAll("\n\n");
102 | }
103 | }
104 |
105 | const std = @import("std");
106 | const process = std.process;
107 | const posix = std.posix;
108 | const windows = std.os.windows;
109 | const builtin = @import("builtin");
110 | const mem = std.mem;
111 |
112 | const assert = std.debug.assert;
113 |
114 | const Child = process.Child;
115 | const Allocator = std.mem.Allocator;
116 | const ArrayListUnmanaged = std.ArrayListUnmanaged;
117 |
--------------------------------------------------------------------------------
/subPatches.nix:
--------------------------------------------------------------------------------
1 | { pkgs }:
2 | plugins:
3 | let
4 | utils = pkgs.callPackage ./patchUtils.nix{};
5 |
6 | # For more information about the different types of subsitutions, go here:
7 | # https://github.com/NicoElbers/nixPatch-nvim/blob/main/patchUtils.nix
8 | inherit (utils)
9 | urlSub githubUrlSub
10 | stringSub keyedStringSub
11 | optPatch;
12 |
13 | # This `opt` function optionally enables a subsitution. This way I can put every
14 | # subsitution I want in here, but they'll only activate if you have the associated
15 | # plugin in your list of plugins
16 | opt = optPatch plugins;
17 | in with pkgs.vimPlugins;
18 | opt lazy-nvim (stringSub "lazy.nvim-plugin-path" "${lazy-nvim}")
19 | ++ opt comment-nvim (githubUrlSub "numToStr/Comment.nvim" comment-nvim)
20 | ++ opt luasnip (githubUrlSub "L3MON4D3/LuaSnip" luasnip)
21 |
22 |
23 |
--------------------------------------------------------------------------------
/templates/configV1/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | description = "My neovim config";
3 |
4 | inputs = {
5 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
6 |
7 | nv = {
8 | url = "github:NicoElbers/nv";
9 | inputs.nixpkgs.follows = "nixpkgs";
10 | };
11 | };
12 |
13 | outputs = { nixpkgs, nv, ... }:
14 | let
15 | # Copied from flake utils
16 | eachSystem = with builtins; systems: f:
17 | let
18 | # Merge together the outputs for all systems.
19 | op = attrs: system:
20 | let
21 | ret = f system;
22 | op = attrs: key: attrs //
23 | {
24 | ${key} = (attrs.${key} or { })
25 | // { ${system} = ret.${key}; };
26 | }
27 | ;
28 | in
29 | foldl' op attrs (attrNames ret);
30 | in
31 | foldl' op { }
32 | (systems
33 | ++ # add the current system if --impure is used
34 | (if builtins ? currentSystem then
35 | if elem currentSystem systems
36 | then []
37 | else [ currentSystem ]
38 | else []));
39 |
40 | forEachSystem = eachSystem nixpkgs.lib.platforms.all;
41 | in
42 | let
43 | # Easily configure a custom name, this will affect the name of the standard
44 | # executable, you can add as many aliases as you'd like in the configuration.
45 | name = "nv";
46 |
47 | # Any custom package config you would like to do.
48 | extra_pkg_config = {
49 | # allow_unfree = true;
50 | };
51 |
52 | configuration = { pkgs, ... }:
53 | let
54 | patchUtils = nv.patchUtils.${pkgs.system};
55 | in
56 | {
57 | # The path to your neovim configuration.
58 | luaPath = ./.;
59 |
60 | # Plugins you use in your configuration.
61 | plugins = with pkgs.vimPlugins; [ ];
62 |
63 | # Runtime dependencies. This is thing like tree-sitter, lsps or programs
64 | # like ripgrep.
65 | runtimeDeps = with pkgs; [ ];
66 |
67 | # Environment variables set during neovim runtime.
68 | environmentVariables = { };
69 |
70 | # Aliases for the patched config
71 | aliases = [ "vim" "vi" ];
72 |
73 | # Extra wrapper args you want to pass.
74 | # Look here if you don't know what those are:
75 | # https://github.com/NixOS/nixpkgs/blob/master/pkgs/build-support/setup-hooks/make-wrapper.sh
76 | extraWrapperArgs = [ ];
77 |
78 | # Extra python packages for the neovim provider.
79 | # This must be a list of functions returning lists.
80 | python3Packages = [ ];
81 |
82 | # Wrapper args but then for the python provider.
83 | extraPython3WrapperArgs = [ ];
84 |
85 | # Extra lua packages for the neovim lua runtime.
86 | luaPackages = [ ];
87 |
88 | # Extra shared libraries available at runtime.
89 | sharedLibraries = [ ];
90 |
91 | # Extra lua configuration put at the top of your init.lua
92 | # This cannot replace your init.lua, if none exists in your configuration
93 | # this will not be writtern.
94 | # Must be provided as a list of strings.
95 | extraConfig = [ ];
96 |
97 | # Custom subsitutions you want the patcher to make. Custom subsitutions
98 | # can be generated using
99 | customSubs = with patchUtils; [];
100 | # For example, if you want to add a plugin with the short url
101 | # "cool/plugin" which is in nixpkgs as plugin-nvim you would do:
102 | # ++ (patchUtils.githubUrlSub "cool/plugin" plugin-nvim);
103 | # If you would want to replace the string "replace_me" with "replaced"
104 | # you would have to do:
105 | # ++ (patchUtils.stringSub "replace_me" "replaced")
106 | # For more examples look here: https://github.com/NicoElbers/nv/blob/main/subPatches.nix
107 |
108 | settings = {
109 | # Enable the NodeJs provider
110 | withNodeJs = false;
111 |
112 | # Enable the ruby provider
113 | withRuby = false;
114 |
115 | # Enable the perl provider
116 | withPerl = false;
117 |
118 | # Enable the python3 provider
119 | withPython3 = false;
120 |
121 | # Any extra name
122 | extraName = "";
123 |
124 | # The default config directory for neovim
125 | configDirName = "nvim";
126 |
127 | # Any other neovim package you would like to use, for example nightly
128 | neovim-unwrapped = null;
129 |
130 | # Whether to add custom subsitution made in the original repo, makes for
131 | # a better out of the box experience
132 | patchSubs = true;
133 |
134 | # Whether to add runtime dependencies to the back of the path
135 | suffix-path = false;
136 |
137 | # Whether to add shared libraries dependencies to the back of the path
138 | suffix-LD = false;
139 | };
140 | };
141 | in
142 | forEachSystem (system: {
143 | packages.default =
144 | nv.configWrapper.${system} { inherit configuration extra_pkg_config name; };
145 | });
146 | }
147 |
--------------------------------------------------------------------------------
/templates/configV2/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | description = "My neovim config";
3 |
4 | inputs = {
5 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
6 |
7 | # In case you want nightly
8 | # neovim-nightly-overlay = {
9 | # url = "github:nix-community/neovim-nightly-overlay";
10 | # inputs.nixpkgs.follows = "nixpkgs";
11 | # };
12 |
13 | nixPatch = {
14 | url = "github:NicoElbers/nixPatch-nvim";
15 | inputs.nixpkgs.follows = "nixpkgs";
16 |
17 | # We do this so that we ensure neovim nightly actually updates
18 | # inputs.neovim-nightly-overlay.follows = "neovim-nightly-overlay";
19 | };
20 | };
21 |
22 | outputs = { nixpkgs, nixPatch, ... }:
23 | let
24 | # Copied from flake utils
25 | eachSystem = with builtins; systems: f:
26 | let
27 | # Merge together the outputs for all systems.
28 | op = attrs: system:
29 | let
30 | ret = f system;
31 | op = attrs: key: attrs //
32 | {
33 | ${key} = (attrs.${key} or { })
34 | // { ${system} = ret.${key}; };
35 | }
36 | ;
37 | in
38 | foldl' op attrs (attrNames ret);
39 | in
40 | foldl' op { }
41 | (systems
42 | ++ # add the current system if --impure is used
43 | (if builtins ? currentSystem then
44 | if elem currentSystem systems
45 | then []
46 | else [ currentSystem ]
47 | else []));
48 |
49 | forEachSystem = eachSystem nixpkgs.lib.platforms.all;
50 | in
51 | let
52 | # Easily configure a custom name, this will affect the name of the standard
53 | # executable, you can add as many aliases as you'd like in the configuration.
54 | name = "nixPatch";
55 |
56 | # Any custom package config you would like to do.
57 | extra_pkg_config = {
58 | # allow_unfree = true;
59 | };
60 |
61 | configuration = { pkgs, system, ... }:
62 | let
63 | patchUtils = nixPatch.patchUtils.${pkgs.system};
64 | in
65 | {
66 | # The path to your neovim configuration.
67 | luaPath = ./.;
68 |
69 | # Plugins you use in your configuration.
70 | plugins = with pkgs.vimPlugins; [ ];
71 |
72 | # Runtime dependencies. This is thing like tree-sitter, lsps or programs
73 | # like ripgrep.
74 | runtimeDeps = with pkgs; [ ];
75 |
76 | # Environment variables set during neovim runtime.
77 | environmentVariables = { };
78 |
79 | # Aliases for the patched config
80 | aliases = [ "vim" "vi" ];
81 |
82 | # Extra wrapper args you want to pass.
83 | # Look here if you don't know what those are:
84 | # https://github.com/NixOS/nixpkgs/blob/master/pkgs/build-support/setup-hooks/make-wrapper.sh
85 | extraWrapperArgs = [ ];
86 |
87 | # Extra python packages for the neovim provider.
88 | # This must be a list of functions returning lists.
89 | python3Packages = [ ];
90 |
91 | # Wrapper args but then for the python provider.
92 | extraPython3WrapperArgs = [ ];
93 |
94 | # Extra lua packages for the neovim lua runtime.
95 | luaPackages = [ ];
96 |
97 | # Extra shared libraries available at runtime.
98 | sharedLibraries = [ ];
99 |
100 | # Extra lua configuration put at the top of your init.lua
101 | # This cannot replace your init.lua, if none exists in your configuration
102 | # this will not be writtern.
103 | # Must be provided as a list of strings.
104 | extraConfig = [ ];
105 |
106 | # Custom subsitutions you want the patcher to make. Custom subsitutions
107 | # can be generated using
108 | customSubs = with patchUtils; [];
109 | # For example, if you want to add a plugin with the short url
110 | # "cool/plugin" which is in nixpkgs as plugin-nvim you would do:
111 | # ++ (patchUtils.githubUrlSub "cool/plugin" plugin-nvim);
112 | # If you would want to replace the string "replace_me" with "replaced"
113 | # you would have to do:
114 | # ++ (patchUtils.stringSub "replace_me" "replaced")
115 | # For more examples look here: https://github.com/NicoElbers/nixPatch-nvim/blob/main/subPatches.nix
116 |
117 | settings = {
118 | # Enable the NodeJs provider
119 | withNodeJs = false;
120 |
121 | # Enable the ruby provider
122 | withRuby = false;
123 |
124 | # Enable the perl provider
125 | withPerl = false;
126 |
127 | # Enable the python3 provider
128 | withPython3 = false;
129 |
130 | # Any extra name
131 | extraName = "";
132 |
133 | # The default config directory for neovim
134 | configDirName = "nvim";
135 |
136 | # Any other neovim package you would like to use, for example nightly
137 | neovim-unwrapped = null;
138 |
139 | # When using nightly, it's best to use the version nixPatch exposes,
140 | # this prevents potential linking errors if nixPatch isn't updated in a
141 | # while
142 | # neovim-unwrapped = inputs.nixPatch.neovim-nightly.${system};
143 |
144 | # Whether to add custom subsitution made in the original repo, makes for
145 | # a better out of the box experience
146 | patchSubs = true;
147 |
148 | # Whether to add runtime dependencies to the back of the path
149 | suffix-path = false;
150 |
151 | # Whether to add shared libraries dependencies to the back of the path
152 | suffix-LD = false;
153 | };
154 | };
155 | in
156 | forEachSystem (system: {
157 | packages.default =
158 | nixPatch.configWrapper.${system} { inherit configuration extra_pkg_config name; };
159 | });
160 | }
161 |
--------------------------------------------------------------------------------
/templates/default:
--------------------------------------------------------------------------------
1 | configV2
--------------------------------------------------------------------------------
/templates/default.nix:
--------------------------------------------------------------------------------
1 | {
2 | default = {
3 | path = ./default;
4 | description = "The most up to date version of the config";
5 | };
6 |
7 | v1 = {
8 | path = ./configV1;
9 | description = "The first version of the config";
10 | };
11 | }
12 |
--------------------------------------------------------------------------------