diff --git a/.busted b/.busted new file mode 100644 index 00000000..529debc7 --- /dev/null +++ b/.busted @@ -0,0 +1,14 @@ +return { + _all = { + coverage = false, + lpath = "lua/?.lua;lua/?/init.lua", + cpath = "./.luarocks/lib/lua/5.1/?.so", + lua = "nlua", + }, + default = { + verbose = true, + }, + tests = { + verbose = true, + }, +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..8c5189d4 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,2 @@ +[Makefile] +indent_style = tab diff --git a/.envrc b/.envrc new file mode 100644 index 00000000..3550a30f --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.github/rockspec.template b/.github/rockspec.template new file mode 100644 index 00000000..ee8f1c6f --- /dev/null +++ b/.github/rockspec.template @@ -0,0 +1,48 @@ +local git_ref = '$git_ref' +local modrev = '$modrev' +local specrev = '$specrev' + +local repo_url = '$repo_url' + +rockspec_format = '3.0' +package = '$package' +version = modrev ..'-'.. specrev + +description = { + summary = '$summary', + detailed = $detailed_description, + labels = $labels, + homepage = '$homepage', + $license +} + +dependencies = { + 'lua == 5.1', +} + +build_dependencies = { + 'luarocks-build-rust-mlua', +} + +test_dependencies = { + 'nlua', +} + +source = { + url = repo_url .. '/archive/' .. git_ref .. '.zip', + dir = '$repo_name-' .. '$archive_dir_suffix', +} + +build = { + type = 'rust-mlua', + modules = { + 'blink_cmp_fuzzy', + }, + install = { + lua = { + ['blink-cmp.init'] = 'lua/blink-cmp.lua', + }, + }, + default_features = false, + features = { 'lua51' }, +} diff --git a/.github/workflows/luarocks.yml b/.github/workflows/luarocks.yml new file mode 100644 index 00000000..2234cb6f --- /dev/null +++ b/.github/workflows/luarocks.yml @@ -0,0 +1,31 @@ +name: Publish to luarocks + +on: + push: + tags: + - 'v*' + release: + types: [published] + pull_request: # Will test a local install without uploading to luarocks.org + workflow_dispatch: # Allow triggering manually + +jobs: + luarocks-upload: + runs-on: ubuntu-latest + name: Publish to luarocks + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + rustflags: "" + toolchain: nightly + - name: LuaRocks upload + uses: nvim-neorocks/luarocks-tag-release@v7 + env: + LUAROCKS_API_KEY: ${{ secrets.LUAROCKS_API_KEY }} + with: + name: blink.cmp + summary: "Performant, batteries-included completion plugin for Neovim" + template: .github/rockspec.template diff --git a/.github/workflows/nix-build.yaml b/.github/workflows/nix-build.yaml index 4b36941a..edd68dff 100644 --- a/.github/workflows/nix-build.yaml +++ b/.github/workflows/nix-build.yaml @@ -2,6 +2,9 @@ name: Test Nix Build on: push: + branches: + - main + pull_request: jobs: build: @@ -18,4 +21,4 @@ jobs: - uses: DeterminateSystems/magic-nix-cache-action@main - name: Build with Nix - run: nix build .#blink-cmp + run: nix build .#blink-cmp -Lv diff --git a/.gitignore b/.gitignore index 3c9660c6..930aa8bc 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ dual/ result .direnv .devenv +.luarocks diff --git a/Cargo.toml b/Cargo.toml index 35f9e0eb..6583e872 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,13 +4,17 @@ version = "0.1.0" edition = "2021" [lib] -path = "lua/blink/cmp/fuzzy/lib.rs" crate-type = ["cdylib"] +[features] +default = ["luajit"] +luajit = ["mlua/luajit"] +lua51 = ["mlua/lua51"] + [dependencies] regex = "1.10.5" lazy_static = "1.5.0" frizbee = { git = "https://github.com/saghen/frizbee" } serde = { version = "1.0.204", features = ["derive"] } heed = "0.20.3" -mlua = { version = "0.10.0", features = ["module", "luajit"] } +mlua = { version = "0.10.0", features = ["module"] } diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..86427500 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +test: + ./test.sh diff --git a/README.md b/README.md index de85602a..c7f7cd0d 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,14 @@ ## Installation +> [!NOTE] +> +> `lazy.nvim` (with luarocks enabled) and `rocks.nvim` will configure luarocks +> to fetch a pre-built package with the fuzzy binary included. +> If you are using a platform for which there are no pre-built packages, +> luarocks needs a [nightly rust toolchain](https://rust-lang.github.io/rustup/concepts/channels.html#working-with-nightly-rust) +> to build the fuzzy binary. + `lazy.nvim` ```lua @@ -36,9 +44,11 @@ -- optional: provides snippets for the snippet source dependencies = 'rafamadriz/friendly-snippets', - -- use a release tag to download pre-built binaries + -- recommended: use a release tag for stable releases and prebuilt binaries, when not using luarocks version = 'v0.*', - -- OR build from source, requires nightly: https://rust-lang.github.io/rustup/concepts/channels.html#working-with-nightly-rust + + -- optionally: build from source, when not using luarocks + -- (requires nightly: https://rust-lang.github.io/rustup/concepts/channels.html#working-with-nightly-rust) -- build = 'cargo build --release', -- If you use nix, you can build from source using latest nightly rust with: -- build = 'nix run .#build-plugin', @@ -94,6 +104,12 @@ } ``` +`rocks.nvim` + +```vim +:Rocks install blink.cmp +``` +
mini.deps @@ -327,14 +343,10 @@ MiniDeps.add({ download = true, -- When downloading a prebuilt binary, force the downloader to resolve this version. If this is unset -- then the downloader will attempt to infer the version from the checked out git tag (if any). - -- - -- Beware that if the FFI ABI changes while tracking main then this may result in blink breaking. force_version = nil, -- When downloading a prebuilt binary, force the downloader to use this system triple. If this is unset -- then the downloader will attempt to infer the system triple from `jit.os` and `jit.arch`. -- Check the latest release for all available system triples - -- - -- Beware that if the FFI ABI changes while tracking main then this may result in blink breaking. force_system_triple = nil, }, }, @@ -612,7 +624,7 @@ The plugin use a 4 stage pipeline: trigger -> sources -> fuzzy -> render **Sources:** Provides a common interface for and merges the results of completion, trigger character, resolution of additional information and cancellation. Some sources are builtin: `LSP`, `buffer`, `path`, `snippets` -**Fuzzy:** Rust <-> Lua FFI which performs both filtering and sorting of the items +**Fuzzy:** Native Lua library written in Rust, which performs both filtering and sorting of the items     **Filtering:** The fuzzy matching uses smith-waterman, same as FZF, but implemented in SIMD for ~6x the performance of FZF (TODO: add benchmarks). Due to the SIMD's performance, the prefiltering phase on FZF was dropped to allow for typos. Similar to fzy/fzf, additional points are given to prefix matches, characters with capitals (to promote camelCase/PascalCase first char matching) and matches after delimiters (to promote snake_case first char matching) diff --git a/blink.cmp-scm-1.rockspec b/blink.cmp-scm-1.rockspec new file mode 100644 index 00000000..45e70c06 --- /dev/null +++ b/blink.cmp-scm-1.rockspec @@ -0,0 +1,45 @@ +local MODREV, SPECREV = 'scm', '-1' +rockspec_format = '3.0' +package = 'blink.cmp' +version = MODREV .. SPECREV + +description = { + summary = 'Performant, batteries-included completion plugin for Neovim', + labels = { 'neovim' }, + homepage = 'https://github.com/Saghen/blink.cmp', + license = 'MIT', +} + +source = { + url = 'https://github.com/Saghen/blink.cmp/archive/v' .. MODREV .. '.zip', +} + +if MODREV == 'scm' then source = { + url = 'git://github.com/Saghen/blink.cmp', +} end + +dependencies = { + 'lua == 5.1', +} + +test_dependencies = { + 'nlua', -- neovim lua interpreter +} + +build_dependencies = { + 'luarocks-build-rust-mlua', +} + +build = { + type = 'rust-mlua', + modules = { + 'blink_cmp_fuzzy', + }, + install = { + lua = { + ['blink-cmp.init'] = 'lua/blink-cmp.lua', + }, + }, + default_features = false, + features = { 'lua51' }, +} diff --git a/flake.lock b/flake.lock index ecddaa6a..e6c6969d 100644 --- a/flake.lock +++ b/flake.lock @@ -41,11 +41,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1731245184, - "narHash": "sha256-vmLS8+x+gHRv1yzj3n+GTAEObwmhxmkkukB2DwtJRdU=", + "lastModified": 1731890469, + "narHash": "sha256-D1FNZ70NmQEwNxpSSdTXCSklBH1z2isPR84J6DQrJGs=", "owner": "nixos", "repo": "nixpkgs", - "rev": "aebe249544837ce42588aa4b2e7972222ba12e8f", + "rev": "5083ec887760adfe12af64830a66807423a859a7", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index ab8820eb..84457e66 100644 --- a/flake.nix +++ b/flake.nix @@ -8,67 +8,24 @@ fenix.inputs.nixpkgs.follows = "nixpkgs"; }; - outputs = inputs@{ flake-parts, nixpkgs, ... }: + outputs = inputs@{ self, flake-parts, nixpkgs, ... }: flake-parts.lib.mkFlake { inherit inputs; } { systems = [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ]; - perSystem = { self, config, self', inputs', pkgs, system, lib, ... }: { + perSystem = { config, self', pkgs, system, lib, ... }: { # use fenix overlay _module.args.pkgs = import nixpkgs { inherit system; - overlays = [ inputs.fenix.overlays.default ]; + overlays = [ + inputs.fenix.overlays.default + self.overlays.default + ]; }; # define the packages provided by this flake - packages = let - inherit (inputs.fenix.packages.${system}.minimal) toolchain; - inherit (pkgs.stdenv) hostPlatform; - - rustPlatform = pkgs.makeRustPlatform { - cargo = toolchain; - rustc = toolchain; - }; - - src = ./.; - version = "2024-11-11"; - - blink-fuzzy-lib = rustPlatform.buildRustPackage { - pname = "blink-fuzzy-lib"; - inherit src version; - cargoLock = { - lockFile = ./Cargo.lock; - outputHashes = { - "frizbee-0.1.0" = - "sha256-pt6sMsRyjXrbrTK7t/YvWeen/n3nU8UUaiNYTY1LczE="; - }; - }; - }; - - libExt = if hostPlatform.isDarwin then - "dylib" - else if hostPlatform.isWindows then - "dll" - else - "so"; - in { - blink-cmp = pkgs.vimUtils.buildVimPlugin { - pname = "blink-cmp"; - inherit src version; - preInstall = '' - mkdir -p target/release - ln -s ${blink-fuzzy-lib}/lib/libblink_cmp_fuzzy.${libExt} target/release/libblink_cmp_fuzzy.${libExt} - ''; - - meta = { - description = - "Performant, batteries-included completion plugin for Neovim "; - homepage = "https://github.com/saghen/blink.cmp"; - license = lib.licenses.mit; - maintainers = with lib.maintainers; [ redxtech ]; - }; - }; - + packages = { + blink-cmp = pkgs.vimPlugins.blink-cmp; default = self'.packages.blink-cmp; }; @@ -92,7 +49,89 @@ # define the default dev environment devShells.default = pkgs.mkShell { name = "blink"; - packages = with pkgs; [ fenix.minimal.toolchain ]; + packages = with pkgs; [ + fenix.minimal.toolchain + rust-analyzer + rustfmt + clippy + lua-language-server + luarocks + (lua5_1.withPackages (ps: with ps; [nlua busted])) + ]; + }; + }; + flake = { + overlays.default = final: prev: let + lib = final.lib; + inherit (inputs.fenix.packages.${final.system}.minimal) toolchain cargo rustc; + + rustPlatform = final.makeRustPlatform { + cargo = toolchain; + rustc = toolchain; + }; + luaPackage-override = luafinal: luaprev: { + blink-cmp = luafinal.callPackage ({ + buildLuarocksPackage, + fetchzip, + fetchurl, + lua, + luaOlder, + luarocks-build-rust-mlua, + busted, + nlua, + }: + buildLuarocksPackage { + pname = "blink.cmp"; + version = "scm-1"; + knownRockspec = "${self}/blink.cmp-scm-1.rockspec"; + src = self; + disabled = luaOlder "5.1"; + cargoDeps = final.rustPlatform.importCargoLock { + lockFile = self + "/Cargo.lock"; + outputHashes = { + "frizbee-0.1.0" = "sha256-pt6sMsRyjXrbrTK7t/YvWeen/n3nU8UUaiNYTY1LczE="; + }; + }; + NIX_LDFLAGS = lib.optionalString final.stdenv.hostPlatform.isDarwin + (if lua.pkgs.isLuaJIT then "-lluajit-${lua.luaversion}" else "-llua"); + buildInputs = [ + cargo + rustc + rustPlatform.cargoSetupHook + luarocks-build-rust-mlua + ]; + propagatedBuildInputs = [ + # HACK: These packages shouldn't be propagated, but without this, + # luarocks make fails in the preCheck (needs to be fixed in nixpkgs). + luarocks-build-rust-mlua + busted + nlua + ]; + doCheck = true; + preCheck = '' + export HOME="$(mktemp -d)" + mkdir -p .luarocks + luarocks $LUAROCKS_EXTRA_ARGS make --tree=.luarocks --deps-mode=all + ''; + meta = { + description = + "Performant, batteries-included completion plugin for Neovim "; + homepage = "https://github.com/saghen/blink.cmp"; + license = lib.licenses.mit; + maintainers = with lib.maintainers; [ redxtech ]; + }; + }) {}; + }; + in { + lua5_1 = prev.lua5_1.override { + packageOverrides = luaPackage-override; + }; + lua51Packages = prev.lua51Packages // final.lua5_1.pkgs; + vimPlugins = prev.vimPlugins // { + blink-cmp = final.neovimUtils.buildNeovimPlugin { + luaAttr = final.lua51Packages.blink-cmp; + }; + }; }; }; }; diff --git a/lua/blink/cmp/fuzzy/download.lua b/lua/blink/cmp/fuzzy/download.lua index da999ce8..91d69c6c 100644 --- a/lua/blink/cmp/fuzzy/download.lua +++ b/lua/blink/cmp/fuzzy/download.lua @@ -17,7 +17,10 @@ local version_path = root_dir .. '../../../../target/release/version.txt' function download.ensure_downloaded(callback) callback = vim.schedule_wrap(callback) - if not download_config.download then return callback() end + if not download_config.download or pcall(require, 'blink_cmp_fuzzy') then + -- download disabled or already loaded (built with luarocks) + return callback() + end download.get_git_tag(function(git_version_err, git_version) if git_version_err then return callback(git_version_err) end diff --git a/lua/blink/cmp/fuzzy/rust.lua b/lua/blink/cmp/fuzzy/rust.lua index e2374cfc..f10eae89 100644 --- a/lua/blink/cmp/fuzzy/rust.lua +++ b/lua/blink/cmp/fuzzy/rust.lua @@ -1,3 +1,11 @@ +local ok, rust = pcall(require, 'blink_cmp_fuzzy') + +if ok then + return rust +end + +-- Set up the path do use the downloaded binary + --- @return string local function get_lib_extension() if jit.os:lower() == 'mac' or jit.os:lower() == 'osx' then return '.dylib' end diff --git a/spec/blink_cmp_fuzzy_spec.lua b/spec/blink_cmp_fuzzy_spec.lua new file mode 100644 index 00000000..83a5bc0f --- /dev/null +++ b/spec/blink_cmp_fuzzy_spec.lua @@ -0,0 +1,5 @@ +describe("rust-mlua library", function() + it("can load blink_cmp_fuzzy module", function() + require("blink_cmp_fuzzy") + end) +end) diff --git a/spec/fuzzy_spec.lua b/spec/fuzzy_spec.lua new file mode 100644 index 00000000..0e2c3319 --- /dev/null +++ b/spec/fuzzy_spec.lua @@ -0,0 +1,17 @@ +local tempdir = vim.fn.tempname() +vim.system({ "rm", "-r", tempdir }):wait() +vim.system({ "mkdir", "-p", tempdir }):wait() + +describe("blink.cmp.fuzzy", function() + local fuzzy = require("blink.cmp.fuzzy") + it("init_db", function() + local db_path = vim.fs.joinpath(tempdir, "fuzzy.db") + fuzzy.init_db(db_path) + assert.is_not_nil(vim.uv.fs_stat(db_path)) + end) + it("get_words", function() + local result = fuzzy.get_words("first\nsecond\tthird") + table.sort(result) -- make sure it's not flaky + assert.same({"first", "second", "third"}, result) + end) +end) diff --git a/spec/setup_spec.lua b/spec/setup_spec.lua new file mode 100644 index 00000000..738c2883 --- /dev/null +++ b/spec/setup_spec.lua @@ -0,0 +1,5 @@ +describe("setup", function() + it("can setup", function() + require("blink.cmp").setup {}; + end) +end) diff --git a/lua/blink/cmp/fuzzy/frecency.rs b/src/frecency.rs similarity index 100% rename from lua/blink/cmp/fuzzy/frecency.rs rename to src/frecency.rs diff --git a/lua/blink/cmp/fuzzy/fuzzy.rs b/src/fuzzy.rs similarity index 100% rename from lua/blink/cmp/fuzzy/fuzzy.rs rename to src/fuzzy.rs diff --git a/lua/blink/cmp/fuzzy/lib.rs b/src/lib.rs similarity index 100% rename from lua/blink/cmp/fuzzy/lib.rs rename to src/lib.rs diff --git a/lua/blink/cmp/fuzzy/lsp_item.rs b/src/lsp_item.rs similarity index 100% rename from lua/blink/cmp/fuzzy/lsp_item.rs rename to src/lsp_item.rs diff --git a/test.sh b/test.sh new file mode 100755 index 00000000..3796da09 --- /dev/null +++ b/test.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +bash -c "mkdir -p .luarocks" +luarocks make --tree=.luarocks --deps-mode=all +eval "$(luarocks --tree=.luarocks path --append)" +luarocks test