├── .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 | --------------------------------------------------------------------------------