diff options
| author | Mike Vink <mike@pionative.com> | 2025-01-19 13:52:52 +0100 |
|---|---|---|
| committer | Mike Vink <mike@pionative.com> | 2025-01-19 13:52:52 +0100 |
| commit | f549cf95135fc321ff14cb22e0d978540340c463 (patch) | |
| tree | b1341f7fe4d83b166d594e6d24ef37b04ce0b8d7 /mut/neovim | |
| parent | 25b8e552377190d115e1c1e11b831b0b803e0c59 (diff) | |
| parent | b77413ff8f59f380612074f0c9bd49093d8db695 (diff) | |
Merge commit 'b77413ff8f59f380612074f0c9bd49093d8db695' as 'mut/neovim/pack/plugins/start/blink.cmp'
Diffstat (limited to 'mut/neovim')
147 files changed, 16996 insertions, 0 deletions
diff --git a/mut/neovim/pack/plugins/start/blink.cmp/.cargo/config.toml b/mut/neovim/pack/plugins/start/blink.cmp/.cargo/config.toml new file mode 100644 index 0000000..88bd7e3 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/.cargo/config.toml @@ -0,0 +1,27 @@ +[target.x86_64-apple-darwin] +rustflags = [ + "-C", "link-arg=-undefined", + "-C", "link-arg=dynamic_lookup", +] + +[target.aarch64-apple-darwin] +rustflags = [ + "-C", "link-arg=-undefined", + "-C", "link-arg=dynamic_lookup", +] + +[target.x86_64-unknown-linux-musl] +rustflags = ["-C", "target-feature=-crt-static"] + +[target.aarch64-unknown-linux-musl] +rustflags = ["-C", "target-feature=-crt-static"] + +[target.aarch64-linux-android] +rustflags = [ + "-C", + "linker=aarch64-linux-android-clang", + "-C", + "link-args=-rdynamic", + "-C", + "default-linker-libraries", +] diff --git a/mut/neovim/pack/plugins/start/blink.cmp/.github/ISSUE_TEMPLATE/bug_report.yml b/mut/neovim/pack/plugins/start/blink.cmp/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..b08076b --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,42 @@ +name: Bug Report +description: File a bug report +labels: ["bug"] +body: + - type: checkboxes + id: checklist + attributes: + label: Make sure you have done the following + options: + - label: Updated to the latest version of `blink.cmp` + required: true + - label: Searched for existing issues and documentation (try `<C-k>` on https://cmp.saghen.dev) + required: true + - type: textarea + id: bug-description + attributes: + label: Bug Description + description: If the issue may be related to your configuration, please include a [repro.lua](https://github.com/Saghen/blink.cmp/blob/main/repro.lua) + validations: { required: true } + - type: textarea + id: user-config + attributes: + label: Relevant configuration + description: Copypaste the part of the config relevant to the bug. Do not paste the entire default config. + render: lua + placeholder: | + sources = { + default = { 'lsp', 'path', 'snippets', 'buffer' }, + }, + validations: { required: false } + - type: input + id: version-info + attributes: + label: "`neovim` version" + placeholder: "output of `nvim --version`" + validations: { required: true } + - type: input + id: branch-or-tag + attributes: + label: "`blink.cmp` version" + placeholder: "examples: main, d2b411c or v0.9.2" + validations: { required: true } diff --git a/mut/neovim/pack/plugins/start/blink.cmp/.github/ISSUE_TEMPLATE/config.yml b/mut/neovim/pack/plugins/start/blink.cmp/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..0086358 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: true diff --git a/mut/neovim/pack/plugins/start/blink.cmp/.github/ISSUE_TEMPLATE/feature_request.yml b/mut/neovim/pack/plugins/start/blink.cmp/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..7ecff7c --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,10 @@ +name: Feature request +description: Suggest an idea +labels: ["feature"] +body: + - type: textarea + id: feature-description + attributes: + label: Feature Description + description: A clear and concise description of what the feature is. + validations: { required: true } diff --git a/mut/neovim/pack/plugins/start/blink.cmp/.github/workflows/nix.yaml b/mut/neovim/pack/plugins/start/blink.cmp/.github/workflows/nix.yaml new file mode 100644 index 0000000..54086f1 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/.github/workflows/nix.yaml @@ -0,0 +1,38 @@ +name: Test Nix + +on: + push: + pull_request: + +jobs: + build: + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + name: Test Nix Build on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + - uses: DeterminateSystems/nix-installer-action@main + with: + extra-conf: | + accept-flake-config = true + + - uses: DeterminateSystems/magic-nix-cache-action@main + + - name: Check the flake + run: nix flake check + + - name: Build devshell + run: nix develop --command "rustc" + + - name: Build the library + run: nix build .#blink-fuzzy-lib + + - name: Build the plugin in nix + run: nix build .#blink-cmp + + - name: Build the library (outside nix) + run: nix run .#build-plugin diff --git a/mut/neovim/pack/plugins/start/blink.cmp/.github/workflows/release.yaml b/mut/neovim/pack/plugins/start/blink.cmp/.github/workflows/release.yaml new file mode 100644 index 0000000..b2d643d --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/.github/workflows/release.yaml @@ -0,0 +1,130 @@ +name: Release + +on: + push: + tags: + - "v*" + +jobs: + build: + name: Build ${{ matrix.target }} + runs-on: ${{ matrix.os }} + permissions: + contents: read + strategy: + matrix: + include: + ## Linux builds + # Glibc 2.31 + - os: ubuntu-20.04 + target: x86_64-unknown-linux-gnu + artifact_name: target/x86_64-unknown-linux-gnu/release/libblink_cmp_fuzzy.so + - os: ubuntu-20.04 + target: aarch64-unknown-linux-gnu + artifact_name: target/aarch64-unknown-linux-gnu/release/libblink_cmp_fuzzy.so + # Musl 1.2.3 + - os: ubuntu-latest + target: x86_64-unknown-linux-musl + artifact_name: target/x86_64-unknown-linux-musl/release/libblink_cmp_fuzzy.so + - os: ubuntu-latest + target: aarch64-unknown-linux-musl + artifact_name: target/aarch64-unknown-linux-musl/release/libblink_cmp_fuzzy.so + # Android(Termux) + - os: ubuntu-latest + target: aarch64-linux-android + artifact_name: target/aarch64-linux-android/release/libblink_cmp_fuzzy.so + + ## macOS builds + - os: macos-latest + target: x86_64-apple-darwin + artifact_name: target/x86_64-apple-darwin/release/libblink_cmp_fuzzy.dylib + - os: macos-latest + target: aarch64-apple-darwin + artifact_name: target/aarch64-apple-darwin/release/libblink_cmp_fuzzy.dylib + + ## Windows builds + - os: windows-latest + target: x86_64-pc-windows-msvc + artifact_name: target/x86_64-pc-windows-msvc/release/blink_cmp_fuzzy.dll + + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + run: | + rustup toolchain install nightly + rustup default nightly + rustup target add ${{ matrix.target }} + + - name: Build for Linux + if: contains(matrix.os, 'ubuntu') + run: | + cargo install cross --git https://github.com/cross-rs/cross + cross build --release --target ${{ matrix.target }} + mv "${{ matrix.artifact_name }}" "${{ matrix.target }}.so" + + - name: Build for macOS + if: contains(matrix.os, 'macos') + run: | + # Ventura (https://en.wikipedia.org/wiki/MacOS_version_history#Releases) + MACOSX_DEPLOYMENT_TARGET="13" cargo build --release --target ${{ matrix.target }} + mv "${{ matrix.artifact_name }}" "${{ matrix.target }}.dylib" + + - name: Build for Windows + if: contains(matrix.os, 'windows') + run: | + cargo build --release --target ${{ matrix.target }} + mv "${{ matrix.artifact_name }}" "${{ matrix.target }}.dll" + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.target }} + path: ${{ matrix.target }}.* + + release: + name: Release + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + + - name: Generate checksums + run: | + for file in ./**/*; do + sha256sum "$file" > "${file}.sha256" + done + + - name: Upload Release Assets + uses: softprops/action-gh-release@v2 + with: + name: ${{ github.ref_name }} + tag_name: ${{ github.ref_name }} + token: ${{ github.token }} + files: ./**/* + draft: false + prerelease: false + generate_release_notes: true + + deploy-docs: + name: Deploy docs + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + + - name: Build + run: npm ci && npm run build:release + working-directory: docs + + - name: Deploy + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: pages deploy docs/.vitepress/dist --project-name=blink-cmp diff --git a/mut/neovim/pack/plugins/start/blink.cmp/.gitignore b/mut/neovim/pack/plugins/start/blink.cmp/.gitignore new file mode 100644 index 0000000..ab9ecd3 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/.gitignore @@ -0,0 +1,9 @@ +target/ +.archive.lua +_*.lua +.lazy.lua +dual/ +result +.direnv +.devenv +.repro/ diff --git a/mut/neovim/pack/plugins/start/blink.cmp/.stylua.toml b/mut/neovim/pack/plugins/start/blink.cmp/.stylua.toml new file mode 100644 index 0000000..6d75cf6 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/.stylua.toml @@ -0,0 +1,7 @@ +column_width = 120 +line_endings = "Unix" +indent_type = "Spaces" +indent_width = 2 +quote_style = "AutoPreferSingle" +call_parentheses = "Always" +collapse_simple_statement = "Always" diff --git a/mut/neovim/pack/plugins/start/blink.cmp/CHANGELOG.md b/mut/neovim/pack/plugins/start/blink.cmp/CHANGELOG.md new file mode 100644 index 0000000..e85bb7d --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/CHANGELOG.md @@ -0,0 +1,804 @@ +## [0.10.0](https://github.com/Saghen/blink.cmp/compare/v0.9.3...v0.10.0) (2025-01-08) + +### BREAKING CHANGES + +* mini.snippets and snippets presets (#877) +* support `preselect` with `auto_insert`, set as default + +### Features + +* add `get_selected_item` public function ([9e1e7e6](https://github.com/Saghen/blink.cmp/commit/9e1e7e604e3419fa0777a2b747ded74d35013c06)) +* mini.snippets and snippets presets ([#877](https://github.com/Saghen/blink.cmp/issues/877)) ([854ab87](https://github.com/Saghen/blink.cmp/commit/854ab87aefdac2b757d97595f98673d64f1878bc)) +* set default capabilities on 0.11 ([#897](https://github.com/Saghen/blink.cmp/issues/897)) ([af1febb](https://github.com/Saghen/blink.cmp/commit/af1febb17f9ddc87cf73e69d3f61218cdc18ed85)) +* support `preselect` with `auto_insert`, set as default ([8126d0e](https://github.com/Saghen/blink.cmp/commit/8126d0e6a2a0e62d3872d718c3d50313f9f7fe3a)), closes [#668](https://github.com/Saghen/blink.cmp/issues/668) + +### Bug Fixes + +* `get_char_at_cursor` attempting to get char on empty line ([7d6bf9a](https://github.com/Saghen/blink.cmp/commit/7d6bf9adea67a200067effe5ef589515e71230c8)), closes [#926](https://github.com/Saghen/blink.cmp/issues/926) +* `within_query_bounds` including 1 position after bounds ([36ba8eb](https://github.com/Saghen/blink.cmp/commit/36ba8eb9c166c21d6d2a8b5f88f9c55d1966b383)), closes [#890](https://github.com/Saghen/blink.cmp/issues/890) [#875](https://github.com/Saghen/blink.cmp/issues/875) +* assert vim.lsp.config fn exists before calling ([#927](https://github.com/Saghen/blink.cmp/issues/927)) ([47efef8](https://github.com/Saghen/blink.cmp/commit/47efef83802b26bd2ff7193b24af4c7f747dc145)) +* buildVimPlugin ([#933](https://github.com/Saghen/blink.cmp/issues/933)) ([3f5dcbc](https://github.com/Saghen/blink.cmp/commit/3f5dcbc1c28edd2ab31b9bac27cc63de4e56b87c)) +* clear context on ignored cursor moved when not on keyword ([0f8de3a](https://github.com/Saghen/blink.cmp/commit/0f8de3abd560f38415d71fc6ee9885c2bf53b814)), closes [#937](https://github.com/Saghen/blink.cmp/issues/937) +* ignore cursor moved when cursor equal before vs after ([17eea33](https://github.com/Saghen/blink.cmp/commit/17eea330a5d111f3cd67f59bb3832cc78f55db14)) +* **signature:** use `char_under_cursor` in `on_char_added` handler ([#935](https://github.com/Saghen/blink.cmp/issues/935)) ([275d407](https://github.com/Saghen/blink.cmp/commit/275d40713191e6c0012783ecf762a4faa138098b)), closes [#909](https://github.com/Saghen/blink.cmp/issues/909) + +## [0.9.3](https://github.com/Saghen/blink.cmp/compare/v0.9.2...v0.9.3) (2025-01-06) + +### Features + +* add plaintex, tex and context brackets ([9ffdb7b](https://github.com/Saghen/blink.cmp/commit/9ffdb7b71d0ee9abcccb61d3b8fb60defc4d47ff)) +* **path:** replace `/` in front of cursor on directory ([d2b411c](https://github.com/Saghen/blink.cmp/commit/d2b411ca2ec894ccab9d7dc0bd506e44920983ef)) + +### Bug Fixes + +* add .repro to gitignore ([0d1e3c3](https://github.com/Saghen/blink.cmp/commit/0d1e3c34b172bf93380f8675ec962c301f2b5aaa)) +* cmdline completion new text not including prefix ([bc480aa](https://github.com/Saghen/blink.cmp/commit/bc480aa927ef4afbf5431f566e8aea7458e9f8df)), closes [#883](https://github.com/Saghen/blink.cmp/issues/883) +* ignore buffer local treesitter option ([d704244](https://github.com/Saghen/blink.cmp/commit/d704244327c1bc1fdd9c0218fe4fff04ca78d3c0)), closes [#913](https://github.com/Saghen/blink.cmp/issues/913) +* ignore non-key char in cmdline completion ([cc0e632](https://github.com/Saghen/blink.cmp/commit/cc0e6329e7603b5749c7fe98a76e39ed17bab860)), closes [#893](https://github.com/Saghen/blink.cmp/issues/893) +* **nix:** use native gcc on macos ([3ab6832](https://github.com/Saghen/blink.cmp/commit/3ab6832b2fc3e49aad9c984089cfc0c5ec788531)), closes [#652](https://github.com/Saghen/blink.cmp/issues/652) +* **nix:** use nix gcc and provide libiconv ([#916](https://github.com/Saghen/blink.cmp/issues/916)) ([5d2d601](https://github.com/Saghen/blink.cmp/commit/5d2d6010d9a5376f9073c1182887e547e3c0ec17)) + +## [0.9.2](https://github.com/Saghen/blink.cmp/compare/v0.9.1...v0.9.2) (2025-01-03) + +### Bug Fixes + +* unicode range when checking if char is keyword ([100d3c8](https://github.com/Saghen/blink.cmp/commit/100d3c8bfc8059c2fd2347d00ab70ee91c7ff3ca)), closes [#878](https://github.com/Saghen/blink.cmp/issues/878) + +## [0.9.1](https://github.com/Saghen/blink.cmp/compare/v0.9.0...v0.9.1) (2025-01-03) + +### Features + +* ignore global min_keyword_length for manual trigger ([56f5d31](https://github.com/Saghen/blink.cmp/commit/56f5d314f772617b506d92e46b8e946535edc04e)), closes [#643](https://github.com/Saghen/blink.cmp/issues/643) +* **nix:** add formatter ([#867](https://github.com/Saghen/blink.cmp/issues/867)) ([a0274b1](https://github.com/Saghen/blink.cmp/commit/a0274b10f04ea625b602f6383e3cb2fc38dcfd71)), closes [#736](https://github.com/Saghen/blink.cmp/issues/736) +* normalize search paths ([8a64275](https://github.com/Saghen/blink.cmp/commit/8a64275948cead4de55cd78c7dc74b2c6465605e)), closes [#835](https://github.com/Saghen/blink.cmp/issues/835) +* smarter edit/fuzzy range guessing ([768bcc0](https://github.com/Saghen/blink.cmp/commit/768bcc08282919168cd9bdf29aa8fcbf968fc457)), closes [#46](https://github.com/Saghen/blink.cmp/issues/46) +* sort cmdline items starting with special characters last ([ae3bf0d](https://github.com/Saghen/blink.cmp/commit/ae3bf0d51902df20121378da2ee6893bcc92fa63)), closes [#818](https://github.com/Saghen/blink.cmp/issues/818) +* support custom/customlist cmdline completions directly ([7e7deaa](https://github.com/Saghen/blink.cmp/commit/7e7deaa8bfa578d147e2d1f04a3373fac2afd58f)), closes [#849](https://github.com/Saghen/blink.cmp/issues/849) + +### Bug Fixes + +* column alignment off by 1 when bounds length == 0 ([0d162bd](https://github.com/Saghen/blink.cmp/commit/0d162bd1b0bbd80a1b5a2dc23d98249d4f8c28f6)) +* get full unicode char at cursor position ([e831cab](https://github.com/Saghen/blink.cmp/commit/e831cab7a4c31da02c72044190e9afc1a9ed584c)), closes [#864](https://github.com/Saghen/blink.cmp/issues/864) +* hyphen not being considered a keyword ([8ca8ca4](https://github.com/Saghen/blink.cmp/commit/8ca8ca444e0801411e077cdee655e5efa3f77b36)), closes [#866](https://github.com/Saghen/blink.cmp/issues/866) +* ignore non custom/customlist completion types ([f7857fc](https://github.com/Saghen/blink.cmp/commit/f7857fcb98e52899eb06f07ecb972a430d0de6e0)), closes [#849](https://github.com/Saghen/blink.cmp/issues/849) +* keyword range not being respected for fuzzy matching ([4cc4e37](https://github.com/Saghen/blink.cmp/commit/4cc4e37dd39eec683a9e1a82e71cd1791bda7761)) +* path provider not respecting trailing_slash=false ([#862](https://github.com/Saghen/blink.cmp/issues/862)) ([0ff2ed5](https://github.com/Saghen/blink.cmp/commit/0ff2ed566e753844825cd8d2483933861cea55ff)) +* set undolevels to force undo point ([4c63b4e](https://github.com/Saghen/blink.cmp/commit/4c63b4e29738268950911bb0c70ffaaba26b53d7)), closes [#852](https://github.com/Saghen/blink.cmp/issues/852) +* use tmp file for downloading to prevent crash on mac on update ([84e065b](https://github.com/Saghen/blink.cmp/commit/84e065bef1504076a0cc3f75f9867b9bce6f328b)), closes [#68](https://github.com/Saghen/blink.cmp/issues/68) +* window direction sorting on Windows ([#846](https://github.com/Saghen/blink.cmp/issues/846)) ([00ad008](https://github.com/Saghen/blink.cmp/commit/00ad008cbea4d0d2b5880e7c7386caa9fc4e5e2b)) + +### Performance Improvements + +* use faster 0.11 vim.validate ([#868](https://github.com/Saghen/blink.cmp/issues/868)) ([a8957ba](https://github.com/Saghen/blink.cmp/commit/a8957bab8faad4436e7ad62244c39335b95450a4)) + +## [0.9.0](https://github.com/Saghen/blink.cmp/compare/v0.8.2...v0.9.0) (2024-12-31) + +### BREAKING CHANGES + +* rename `BlinkCmpCompletionMenu*` autocmds to `BlinkCmpMenu*` +* set default documentation max_width to 80 +* rename `align_to_component` to `align_to`, add `cursor` option + +### Features + +* add back support for showing when moving onto trigger character ([cf9cc6e](https://github.com/Saghen/blink.cmp/commit/cf9cc6e43edd2718294ef9801a223c463f50a4ce)), closes [#780](https://github.com/Saghen/blink.cmp/issues/780) [#745](https://github.com/Saghen/blink.cmp/issues/745) +* add callback option to cmp.show ([33b82e5](https://github.com/Saghen/blink.cmp/commit/33b82e5832757319c485ab45c0db4ace554e3183)), closes [#806](https://github.com/Saghen/blink.cmp/issues/806) +* add callback to hide/cancel, rework show callback ([73a5f4e](https://github.com/Saghen/blink.cmp/commit/73a5f4e387ade764a833d290dbb5da77b0d84b4c)), closes [#806](https://github.com/Saghen/blink.cmp/issues/806) +* add type annotation for keymap function params ([#829](https://github.com/Saghen/blink.cmp/issues/829)) ([3d7e773](https://github.com/Saghen/blink.cmp/commit/3d7e773d3e8a02720b23f58ffee631a0c1e2e1d1)) +* escape filenames in cmdline ([e53db6a](https://github.com/Saghen/blink.cmp/commit/e53db6a53f85b1c0d56eed66811bfbac520abd6c)), closes [#751](https://github.com/Saghen/blink.cmp/issues/751) +* **nix:** use Cargo.lock instead of hash ([#773](https://github.com/Saghen/blink.cmp/issues/773)) ([d9513ee](https://github.com/Saghen/blink.cmp/commit/d9513ee9f8b111a46e262be2b36172ca335051a2)) +* **nix:** use filesets ([#772](https://github.com/Saghen/blink.cmp/issues/772)) ([e524347](https://github.com/Saghen/blink.cmp/commit/e524347697b6664870536dfcdd17e3ab56177b99)) +* rename `align_to_component` to `align_to`, add `cursor` option ([9387c75](https://github.com/Saghen/blink.cmp/commit/9387c75af7f8ec1495f4ed5a35cd29f054647dfc)), closes [#344](https://github.com/Saghen/blink.cmp/issues/344) +* rename `BlinkCmpCompletionMenu*` autocmds to `BlinkCmpMenu*` ([fa4312c](https://github.com/Saghen/blink.cmp/commit/fa4312c11f9ab102333f5a18f1a30af5ae636c04)) +* run callback for cmp.show, even if menu is open ([a1476d3](https://github.com/Saghen/blink.cmp/commit/a1476d3596f032be3f2d77630c8eee3951d3f74c)) +* set default documentation max_width to 80 ([1a61625](https://github.com/Saghen/blink.cmp/commit/1a61625ad2a25c4e1ffffacb4bd0826c244af88f)) +* support `@` mode for cmdline ([4c2744d](https://github.com/Saghen/blink.cmp/commit/4c2744d99a13c687e4995fe0a050f40c15dbb2d9)), closes [#696](https://github.com/Saghen/blink.cmp/issues/696) +* support configuring clipboard register for snippets ([8f51a4e](https://github.com/Saghen/blink.cmp/commit/8f51a4ec23773cc96ec6b3ca336a5d70eebb2fb2)), closes [#800](https://github.com/Saghen/blink.cmp/issues/800) +* support unsafe no lock for fuzzy matcher ([6f8da35](https://github.com/Saghen/blink.cmp/commit/6f8da35fc8f1f8046d25b88b7708178cb4126abe)), closes [#817](https://github.com/Saghen/blink.cmp/issues/817) +* support windows drives for path source ([98fded2](https://github.com/Saghen/blink.cmp/commit/98fded25d772a749cbf26e569e735ca7a3fb9d12)), closes [#612](https://github.com/Saghen/blink.cmp/issues/612) +* use filter text on non-prefixed test in cmdline ([8c194b6](https://github.com/Saghen/blink.cmp/commit/8c194b6fa34b174b2ab30ff1d005c6b1b03ba523)) + +### Bug Fixes + +* **accept/brackets:** respect `item.kind` when moving cursor ([#779](https://github.com/Saghen/blink.cmp/issues/779)) ([c54dfbf](https://github.com/Saghen/blink.cmp/commit/c54dfbfdfabac3b5a66ba90c89fab86d7651d106)) +* add missing regex file for path source ([1118d07](https://github.com/Saghen/blink.cmp/commit/1118d07c1b720873fe3498a662a265ae8a9a7ee4)), closes [#834](https://github.com/Saghen/blink.cmp/issues/834) +* alignment double offset on align_to ([24d6868](https://github.com/Saghen/blink.cmp/commit/24d6868d0a18bb02cbee7fc5cc2a09fa309e3eb7)) +* apply non-snippet detection to non-snippet kinds ([434ea2b](https://github.com/Saghen/blink.cmp/commit/434ea2b05c2bae0cff6249893c8324fa3a56d865)), closes [#790](https://github.com/Saghen/blink.cmp/issues/790) +* avoid namespace collision with vim.api.keyset.keymap ([63718e9](https://github.com/Saghen/blink.cmp/commit/63718e93d46f07b869f033fd13b78597ebbde72b)), closes [#767](https://github.com/Saghen/blink.cmp/issues/767) +* check enabled before showing trigger and on mapping ([e670720](https://github.com/Saghen/blink.cmp/commit/e6707202772be974ae2d54239b806707bb72ccdb)), closes [#716](https://github.com/Saghen/blink.cmp/issues/716) +* clamp text edit end character to start character, if lines equal ([6891bcb](https://github.com/Saghen/blink.cmp/commit/6891bcb06b6f21de68278991f29e53452b822d48)), closes [#634](https://github.com/Saghen/blink.cmp/issues/634) +* create target/release dir, if it doesn't exist ([4020c23](https://github.com/Saghen/blink.cmp/commit/4020c2353b906950cb80be2fc1fabee8a9a9c291)), closes [#819](https://github.com/Saghen/blink.cmp/issues/819) +* documentation losing syntax highlighting on doc reopen ([#768](https://github.com/Saghen/blink.cmp/issues/768)) ([ef59763](https://github.com/Saghen/blink.cmp/commit/ef59763c8a58fb1dedfb2d58a2ebd0fbe247f96c)), closes [#703](https://github.com/Saghen/blink.cmp/issues/703) +* don't prevent show() when ghost-text is visible ([#796](https://github.com/Saghen/blink.cmp/issues/796)) ([59d6b4f](https://github.com/Saghen/blink.cmp/commit/59d6b4fbe94cfc350b1392772a61cbcf942619c7)) +* filter help tags by arg prefix ([21da714](https://github.com/Saghen/blink.cmp/commit/21da71413bf749f21d2174c1cd7e8efa40809a93)), closes [#818](https://github.com/Saghen/blink.cmp/issues/818) +* flatten leaving empty tables ([#799](https://github.com/Saghen/blink.cmp/issues/799)) ([021216d](https://github.com/Saghen/blink.cmp/commit/021216da4683db5627d4b321dbde075aa771b5e7)) +* getcmdcompltype returning empty string ([eb9e651](https://github.com/Saghen/blink.cmp/commit/eb9e651bca40bbfb4de2a77a293a1e18bb373ee8)), closes [#696](https://github.com/Saghen/blink.cmp/issues/696) +* remove redundant is enabled check ([f4add54](https://github.com/Saghen/blink.cmp/commit/f4add54f999962e6385d42bad341366b85184217)) +* return incomplete on err/nil from lsp ([1ef9bb9](https://github.com/Saghen/blink.cmp/commit/1ef9bb97740e7b55401e213da5dd6b04b77e56ff)), closes [#719](https://github.com/Saghen/blink.cmp/issues/719) +* set default details to empty array ([0350fee](https://github.com/Saghen/blink.cmp/commit/0350feedfa8adb07b6750f6d9150c26e13eae0d2)) +* trigger context initial_kind resetting ([3ef27bc](https://github.com/Saghen/blink.cmp/commit/3ef27bcd7ff2367c6053421d4a8981bedc33d53e)), closes [#803](https://github.com/Saghen/blink.cmp/issues/803) +* use correct regex for filenames ([8df826f](https://github.com/Saghen/blink.cmp/commit/8df826f168f102d0fbea92cbd85995ce66a821c7)), closes [#761](https://github.com/Saghen/blink.cmp/issues/761) +* use existing arg prefix for help filtering in cmdline ([c593e83](https://github.com/Saghen/blink.cmp/commit/c593e8385d9f8f82a6e108fbabcd1f64fce72684)) +* wait for all LSPs to respond before showing ([86a13ae](https://github.com/Saghen/blink.cmp/commit/86a13aeb104d6ea782557518ee1a350712df7bd7)), closes [#691](https://github.com/Saghen/blink.cmp/issues/691) + +## [0.8.2](https://github.com/Saghen/blink.cmp/compare/v0.8.1...v0.8.2) (2024-12-23) + +### Features + +* improve auto_show flexibility ([#697](https://github.com/Saghen/blink.cmp/issues/697)) ([a937edd](https://github.com/Saghen/blink.cmp/commit/a937edde979a8ff140779fa0d425af566bc73cb7)) +* improve error messages for pre built binaries ([c36b60c](https://github.com/Saghen/blink.cmp/commit/c36b60c22f7357d741c9166e4d509b745cc8b441)) +* sort cmdline completions case insensitive ([b68e924](https://github.com/Saghen/blink.cmp/commit/b68e92426af46d60f08a4d2f58ed1e44d4e56087)), closes [#715](https://github.com/Saghen/blink.cmp/issues/715) +* support dynamic selection mode ([c1017f0](https://github.com/Saghen/blink.cmp/commit/c1017f0a827736e3397f9b60dfe8e8ebb4a0ae72)) + +### Bug Fixes + +* add git to nix build dependencies and shell ([ed1d4f5](https://github.com/Saghen/blink.cmp/commit/ed1d4f573f8988353d6e437f5e70ee334ea099fe)) +* add java to blocked filetypes for semantic token auto_brackets ([#729](https://github.com/Saghen/blink.cmp/issues/729)) ([140ed36](https://github.com/Saghen/blink.cmp/commit/140ed3633419965e8f2228af0d5fbaa4c1956f78)) +* add missing git.lua for downloader ([f7bef25](https://github.com/Saghen/blink.cmp/commit/f7bef25052820d4d7604a296c739ba9d885117f8)) +* auto_show function logic ([#707](https://github.com/Saghen/blink.cmp/issues/707)) ([4ef6d1e](https://github.com/Saghen/blink.cmp/commit/4ef6d1ee29e8ae9138a47bba9374b7c0c97452b6)), closes [#697](https://github.com/Saghen/blink.cmp/issues/697) +* check version sha of locally built, better detection ([3ffd31d](https://github.com/Saghen/blink.cmp/commit/3ffd31d0c52a51d064f4761d5c0bfad64129c1e9)), closes [#68](https://github.com/Saghen/blink.cmp/issues/68) +* doc scrollbar render ([#724](https://github.com/Saghen/blink.cmp/issues/724)) ([8f71ccb](https://github.com/Saghen/blink.cmp/commit/8f71ccbe668860a4ebcaed3928d80d2119559ad9)) +* inherit package.cpath in worker thread ([#726](https://github.com/Saghen/blink.cmp/issues/726)) ([b6c7762](https://github.com/Saghen/blink.cmp/commit/b6c7762407b6c4521b46244f35fab05cfd1c6863)), closes [#725](https://github.com/Saghen/blink.cmp/issues/725) +* **notifications:** add title to notifications ([#722](https://github.com/Saghen/blink.cmp/issues/722)) ([f93af0f](https://github.com/Saghen/blink.cmp/commit/f93af0f486ada13e8c34f42c911788b9232b811f)) +* prebuilt binary error message always firing ([cab0e8e](https://github.com/Saghen/blink.cmp/commit/cab0e8e169a2c595018f9fdb981e056094bd5aeb)) + +## [0.8.1](https://github.com/Saghen/blink.cmp/compare/v0.8.0...v0.8.1) (2024-12-21) + +### Features + +* **path:** sort directories first, then by name lowercase ([400de65](https://github.com/Saghen/blink.cmp/commit/400de65da795b5939ace36978de3d1edeb84b0de)) + +### Bug Fixes + +* checkhealth after checksum changes ([d8ffbe9](https://github.com/Saghen/blink.cmp/commit/d8ffbe95190a776c6a28c86650efcbc23c5f6521)), closes [#669](https://github.com/Saghen/blink.cmp/issues/669) +* duplicate cursor moved event firing ([e360828](https://github.com/Saghen/blink.cmp/commit/e360828a188dc30658067eac63feded08857c076)) +* get global mapping for fallback in cmdline mode ([92da013](https://github.com/Saghen/blink.cmp/commit/92da0133b240e60100fcb04b32fcd7270f765d94)), closes [#674](https://github.com/Saghen/blink.cmp/issues/674) +* internal types for config not using strict config ([bdece4e](https://github.com/Saghen/blink.cmp/commit/bdece4e90e70baee956e2351220527a619d25052)) +* **path:** no items when file fails stat ([4218120](https://github.com/Saghen/blink.cmp/commit/421812086661bba3aa318030eee12719fc5da072)), closes [#688](https://github.com/Saghen/blink.cmp/issues/688) +* type signature for enabled indicating ctx could be passed ([3cb7208](https://github.com/Saghen/blink.cmp/commit/3cb7208546b4e1f0c5e492cbcfccd083a1c89351)), closes [#695](https://github.com/Saghen/blink.cmp/issues/695) +* use context.get_line() when getting preview undo text edit ([0f92fb8](https://github.com/Saghen/blink.cmp/commit/0f92fb8dcff634e880a60e266f041dfe175b82bf)), closes [#702](https://github.com/Saghen/blink.cmp/issues/702) +* wrong key upstreamed by cmdline_events ([4757317](https://github.com/Saghen/blink.cmp/commit/475731741bbd8266767d48ad46b63f715577ac8e)), closes [#700](https://github.com/Saghen/blink.cmp/issues/700) + +## [0.8.0](https://github.com/Saghen/blink.cmp/compare/v0.7.6...v0.8.0) (2024-12-20) + +> [!IMPORTANT] +> `sources.completion.enabled_providers` has been moved to `sources.default` + +### Highlights + +* Cmdline completions! ([#323](https://github.com/Saghen/blink.cmp/issues/323)) +* Sorting now respects LSP hints more directly and doesn't sort alphabetically or by kind by default +* Sources v2 ([#465](https://github.com/Saghen/blink.cmp/issues/465)), adds support for async sources, timeouts, smarter fallbacks, adding sources at runtime and more! + +### Features + +* `extra_curl_args` option for prebuilt binaries download ([4c2e9e7](https://github.com/Saghen/blink.cmp/commit/4c2e9e74905502e3662fbd4af7b0d1b680971a04)), closes [#481](https://github.com/Saghen/blink.cmp/issues/481) +* add [ to show_on_x_blocked_trigger_characters ([#632](https://github.com/Saghen/blink.cmp/issues/632)) ([046a2af](https://github.com/Saghen/blink.cmp/commit/046a2af7580ba90cda9ffebbab3f1fe68ca1fa59)) +* add `{` to `show_on_x_blocked_trigger_characters` ([712bd30](https://github.com/Saghen/blink.cmp/commit/712bd301fc2158e6443144ff9c8ce01b8bf5a77b)), closes [#597](https://github.com/Saghen/blink.cmp/issues/597) +* add global transform_items and min_keyword_length ([e07cb27](https://github.com/Saghen/blink.cmp/commit/e07cb2756d5cc339dfa4bf4d9bc91b3779dbb743)), closes [#502](https://github.com/Saghen/blink.cmp/issues/502) [#504](https://github.com/Saghen/blink.cmp/issues/504) +* allow providers customize documentation rendering ([#650](https://github.com/Saghen/blink.cmp/issues/650)) ([bc94c75](https://github.com/Saghen/blink.cmp/commit/bc94c7508379b4828206759562162ce10af82b68)) +* cmdline completions ([#323](https://github.com/Saghen/blink.cmp/issues/323)) ([414d615](https://github.com/Saghen/blink.cmp/commit/414d615afcd9268522160dca5855fef8132f6e9e)) +* **cmdline:** allow configuring separate cmdline preset ([#532](https://github.com/Saghen/blink.cmp/issues/532)) ([13b3e57](https://github.com/Saghen/blink.cmp/commit/13b3e572e863bafe8fad3a97473271a2c9c700ce)) +* **config:** add partial types for each config option ([#549](https://github.com/Saghen/blink.cmp/issues/549)) ([c3bba64](https://github.com/Saghen/blink.cmp/commit/c3bba64d6c32adf4156e9d1273b14494838a3058)), closes [#427](https://github.com/Saghen/blink.cmp/issues/427) +* **config:** allow plugins to disable blink for some buffers ([#556](https://github.com/Saghen/blink.cmp/issues/556)) ([c8e86a3](https://github.com/Saghen/blink.cmp/commit/c8e86a3ed1eff07e2c1108779a720d3b4c6b86a7)) +* demote snippets from LSP explicitly ([b7c84ac](https://github.com/Saghen/blink.cmp/commit/b7c84ac4e17f3e160a626a9a89b609e02151a135)) +* disable keymaps when no cmdline sources are defined ([88ec601](https://github.com/Saghen/blink.cmp/commit/88ec6010ddbb249257d22265b8a96842f10c7142)) +* enable auto-brackets by default ([4d099ee](https://github.com/Saghen/blink.cmp/commit/4d099eeb72cfbd6496376fbb3265a1887a7c85fe)) +* enable treesiter highlight in menu per source ([#526](https://github.com/Saghen/blink.cmp/issues/526)) ([f99b03c](https://github.com/Saghen/blink.cmp/commit/f99b03c756b32680eea28e89f95e3c6987cc6c80)), closes [#438](https://github.com/Saghen/blink.cmp/issues/438) +* ensure nvim 0.10+ on startup ([30a4a52](https://github.com/Saghen/blink.cmp/commit/30a4a52d2362e3a272ae1cf28552852ae09b38a9)) +* expose `cmp.is_visible()` api ([2c826d9](https://github.com/Saghen/blink.cmp/commit/2c826d9167c7236f6079790bc35bcb024021e683)), closes [#535](https://github.com/Saghen/blink.cmp/issues/535) +* filter out LSP text items by default ([814392a](https://github.com/Saghen/blink.cmp/commit/814392a7164336fe5fbd6d4b97a69dce9eb6e4ef)) +* honor extended luasnip filetypes and cache each ([#625](https://github.com/Saghen/blink.cmp/issues/625)) ([c3ef922](https://github.com/Saghen/blink.cmp/commit/c3ef9223a69ededed611b3ef617bac5651b87833)) +* ignore when source defining trigger character returns no items ([684950d](https://github.com/Saghen/blink.cmp/commit/684950d3c4027e10a46f4bd478182839760b8fde)), closes [#597](https://github.com/Saghen/blink.cmp/issues/597) +* include ghost text in is_visible ([1006662](https://github.com/Saghen/blink.cmp/commit/1006662ad53c92adf9ae6f2d05cee38f613d08ff)) +* increase max length of buffer entry to 512 characters ([4ab0860](https://github.com/Saghen/blink.cmp/commit/4ab0860d361234e714d3beac2828d215f3f481e1)), closes [#478](https://github.com/Saghen/blink.cmp/issues/478) +* merge resolved item with item ([7a83acf](https://github.com/Saghen/blink.cmp/commit/7a83acf5b3cba829b07a05009866548c8e948ac0)), closes [#553](https://github.com/Saghen/blink.cmp/issues/553) +* reset whole luasnip cache on snippets added ([bff6c0f](https://github.com/Saghen/blink.cmp/commit/bff6c0f06bdc1114c5816b0f6b19ad6a7e15a638)) +* resolve help tags ourselves in cmdline ([02051bf](https://github.com/Saghen/blink.cmp/commit/02051bf2d9c8f116680659f091b510598a4aea38)), closes [#631](https://github.com/Saghen/blink.cmp/issues/631) +* rework cmdline source ([8f718cc](https://github.com/Saghen/blink.cmp/commit/8f718cc0d845348fd19c964aa6a82b06ea49c210)) +* rework download logic with checksums ([#629](https://github.com/Saghen/blink.cmp/issues/629)) ([53d22cb](https://github.com/Saghen/blink.cmp/commit/53d22cbac470b5ed8bfa2c3c195b82e03b501629)) +* set cursor position for additional text edits ([f0ab5e5](https://github.com/Saghen/blink.cmp/commit/f0ab5e504b160d4bc60f52a02e8d2453052420d3)), closes [#223](https://github.com/Saghen/blink.cmp/issues/223) +* set path to fallback to buffer by default ([c9594d5](https://github.com/Saghen/blink.cmp/commit/c9594d5682ca421ee1bcb4284329f2d7dde71b50)) +* sort on score and sort_text only by default, disable frecency and proximity on no keyword ([76230d5](https://github.com/Saghen/blink.cmp/commit/76230d5a4a02cd1db8dec33b6eed0b4bc2dcbc53)), closes [#570](https://github.com/Saghen/blink.cmp/issues/570) +* sources v2 ([#465](https://github.com/Saghen/blink.cmp/issues/465)) ([533608f](https://github.com/Saghen/blink.cmp/commit/533608f56b912aba98250a3c1501ee687d7cf5eb)), closes [#386](https://github.com/Saghen/blink.cmp/issues/386) [#219](https://github.com/Saghen/blink.cmp/issues/219) [#328](https://github.com/Saghen/blink.cmp/issues/328) [#331](https://github.com/Saghen/blink.cmp/issues/331) [#312](https://github.com/Saghen/blink.cmp/issues/312) [#454](https://github.com/Saghen/blink.cmp/issues/454) [#444](https://github.com/Saghen/blink.cmp/issues/444) [#372](https://github.com/Saghen/blink.cmp/issues/372) [#475](https://github.com/Saghen/blink.cmp/issues/475) +* support callback on `cmp.accept()` ([be3e9cf](https://github.com/Saghen/blink.cmp/commit/be3e9cf435588b3ff4de7abcb04ec90c812f1871)) +* support configuring prefetch_on_insert, disable by default ([9d4286f](https://github.com/Saghen/blink.cmp/commit/9d4286f9a410af788ee8406ec45e268aa4b23c9f)) +* **trigger:** prefetch on InsertEnter ([#507](https://github.com/Saghen/blink.cmp/issues/507)) ([7e98665](https://github.com/Saghen/blink.cmp/commit/7e9866529768065e0e191e436fc60220bef5185e)) +* use block icon for tailwind items ([#544](https://github.com/Saghen/blink.cmp/issues/544)) ([1502c75](https://github.com/Saghen/blink.cmp/commit/1502c754b9c241eecab1393d74a4eb6ccdfe0e64)) +* use number[] for ui_cmdline_pos ([80a5198](https://github.com/Saghen/blink.cmp/commit/80a5198a357ddcee97d94ac2be9a3590cd5a63f5)) +* validate config doesn't have erroneous fields ([834163e](https://github.com/Saghen/blink.cmp/commit/834163eebdfdb1ca2a4a54b1e8d4c8d2c8184c12)), closes [#501](https://github.com/Saghen/blink.cmp/issues/501) +* **window:** add `filetype` configuration ([#499](https://github.com/Saghen/blink.cmp/issues/499)) ([eb6213b](https://github.com/Saghen/blink.cmp/commit/eb6213b974e604f9ef8560e6c2379d757e81954d)) + +### Bug Fixes + +* **accept:** schecule `fuzzy.access` using uv.new_work ([#522](https://github.com/Saghen/blink.cmp/issues/522)) ([f66f19c](https://github.com/Saghen/blink.cmp/commit/f66f19c864e68ee5e2fb452648b7f6995ddadaa3)) +* account for cmdheight in cmdline_position (thanks [@lnrds](https://github.com/lnrds)!) ([6b67d16](https://github.com/Saghen/blink.cmp/commit/6b67d16036b780f49e44d3f5de207d3c7301f3e4)), closes [#538](https://github.com/Saghen/blink.cmp/issues/538) +* add '=' to cmdline trigger characters ([fb03ca7](https://github.com/Saghen/blink.cmp/commit/fb03ca7dd41fc5c234bf5ec089568f4eae584efb)), closes [#541](https://github.com/Saghen/blink.cmp/issues/541) +* add back, skip undo point for snippet kinds ([1563079](https://github.com/Saghen/blink.cmp/commit/15630796fc8c3c45c345d2fe73de6b3a1dc9bb11)) +* add gcc to flake.nix ([380bccf](https://github.com/Saghen/blink.cmp/commit/380bccf6eb1e3135fbab986f54aabd9147ff5977)), closes [#581](https://github.com/Saghen/blink.cmp/issues/581) +* add icon gap on ellipsis, remove references to renderer ([793b6ac](https://github.com/Saghen/blink.cmp/commit/793b6ac94efe754d31299b7de2e953244fe0d4ab)) +* add mode to context type ([f1afb8c](https://github.com/Saghen/blink.cmp/commit/f1afb8c77686ba6f5159dcb7591bf21efcc5f410)) +* allow 'none' preset for keymaps in validation ([bf1fd6a](https://github.com/Saghen/blink.cmp/commit/bf1fd6a690882a9bf5e07ded70fb3bba5d8a5bdf)) +* always get latest keyword ([13853d5](https://github.com/Saghen/blink.cmp/commit/13853d5c9cf827fc051fa7adebe701cce2ecd22f)), closes [#539](https://github.com/Saghen/blink.cmp/issues/539) +* check raw key for space in cmdline_events ([7be970e](https://github.com/Saghen/blink.cmp/commit/7be970e278334482710e1f37936c8480b522a751)) +* check that scrollbar is not nil ([790369b](https://github.com/Saghen/blink.cmp/commit/790369bb9998d1f9a01f67378e407622b492cf69)), closes [#525](https://github.com/Saghen/blink.cmp/issues/525) +* clear LuaSnip cache on snippet updates ([#664](https://github.com/Saghen/blink.cmp/issues/664)) ([b1b58e7](https://github.com/Saghen/blink.cmp/commit/b1b58e7b9895f43e64891346f76238d697aaadb9)) +* cmdline event suppression and scrollbar rendering ([e3b3fde](https://github.com/Saghen/blink.cmp/commit/e3b3fdedbc14afe7361228f7d2c8ce84cee272a6)), closes [#523](https://github.com/Saghen/blink.cmp/issues/523) +* cmdline events firing cursor moved when changed ([97989c8](https://github.com/Saghen/blink.cmp/commit/97989c8ee257239566c4d08264b080703ccc923b)), closes [#520](https://github.com/Saghen/blink.cmp/issues/520) +* cmdline including current arg prefix ([49bff2b](https://github.com/Saghen/blink.cmp/commit/49bff2bf23f15ae31a245e9ffd1b79a9f95bed61)), closes [#609](https://github.com/Saghen/blink.cmp/issues/609) +* **cmdline:** not delete buf when hide scrollbar cause it seems not necessary ([#591](https://github.com/Saghen/blink.cmp/issues/591)) ([0046d0c](https://github.com/Saghen/blink.cmp/commit/0046d0cc3e9bdd2dc36c2ec7a79aee32e76afa73)) +* completion auto_insert replace incorrect range ([#621](https://github.com/Saghen/blink.cmp/issues/621)) ([5926869](https://github.com/Saghen/blink.cmp/commit/59268691492bc1abfb0ed91a1cb3ac9fcc01650c)), closes [#460](https://github.com/Saghen/blink.cmp/issues/460) +* **completion:** disable in prompt buffers ([#574](https://github.com/Saghen/blink.cmp/issues/574)) ([1097d4e](https://github.com/Saghen/blink.cmp/commit/1097d4e24909c5b1a15b1ac6907ec26f78f5d22c)) +* consider functions as snippet commands ([d065c87](https://github.com/Saghen/blink.cmp/commit/d065c87b59a301065f863134d3a8271bdff6f630)) +* disable ghost text in command mode ([ad17735](https://github.com/Saghen/blink.cmp/commit/ad17735a6ddb4255cad6f0af574150761baf5ee4)), closes [#524](https://github.com/Saghen/blink.cmp/issues/524) +* don't block trigger characters in command mode ([0a729ae](https://github.com/Saghen/blink.cmp/commit/0a729ae1c4ab48695fb327161768720a82ed698f)), closes [#541](https://github.com/Saghen/blink.cmp/issues/541) +* don't create undo point when kind equals snippet ([343e89d](https://github.com/Saghen/blink.cmp/commit/343e89d39deb14b5cc6de844ce069ae3d98d7403)) +* don't duplicate `.` when completing hidden files in path source ([#557](https://github.com/Saghen/blink.cmp/issues/557)) ([714e2b5](https://github.com/Saghen/blink.cmp/commit/714e2b5f3fdcabd6ad31f98c71f930b260644c72)) +* don't show when moving on trigger character, hide on no items after trigger ([7a04612](https://github.com/Saghen/blink.cmp/commit/7a046122de512db8194dae130d691526b5031456)), closes [#545](https://github.com/Saghen/blink.cmp/issues/545) +* duplicate snippets in luasnip when autosnippets are enabled ([12ffc10](https://github.com/Saghen/blink.cmp/commit/12ffc10c6283ac148a89d72b5540d819fc80e2ff)) +* fire cursor moved when jumping between tab stops in a snippet ([1e4808e](https://github.com/Saghen/blink.cmp/commit/1e4808e3429bc060fa538728115edcaebbfc5c35)), closes [#545](https://github.com/Saghen/blink.cmp/issues/545) +* **fuzzy:** initialize db only once ([7868d47](https://github.com/Saghen/blink.cmp/commit/7868d477018f73bff6ca60757c1171223084bd12)) +* **ghost_text:** correctly disable on cmdline ([54d1a98](https://github.com/Saghen/blink.cmp/commit/54d1a980595e056e7be45a10d1cc8c34159f6d74)) +* ignore snippets that only contain text ([284dd37](https://github.com/Saghen/blink.cmp/commit/284dd37f9bbc632f8281d6361e877db5b45e6ff0)), closes [#624](https://github.com/Saghen/blink.cmp/issues/624) +* ignore sort_text if either are nil ([3ba583c](https://github.com/Saghen/blink.cmp/commit/3ba583cedb321291f3145b6e85039ed315b06b17)), closes [#595](https://github.com/Saghen/blink.cmp/issues/595) +* include space for cmdline events ([38b9c4f](https://github.com/Saghen/blink.cmp/commit/38b9c4f36a815fd3d9094e6d5c236a83dbb68ff9)) +* incorrect bounds when removing word under cursor in buffer sources ([d682165](https://github.com/Saghen/blink.cmp/commit/d6821651b145c730ca59faee638947a067243b24)), closes [#560](https://github.com/Saghen/blink.cmp/issues/560) +* **keymap:** incorrect merging strategy ([f88bd66](https://github.com/Saghen/blink.cmp/commit/f88bd66d88e9248276996c0f5b5c2b7fa5aa851f)), closes [#599](https://github.com/Saghen/blink.cmp/issues/599) +* **keymap:** normalize mapping capitalization ([#599](https://github.com/Saghen/blink.cmp/issues/599)) ([596a7ab](https://github.com/Saghen/blink.cmp/commit/596a7ab89cca7cdcddc0422e8f5a449042b7ff80)) +* **luasnip:** add global_snippets with ft="all" ([#546](https://github.com/Saghen/blink.cmp/issues/546)) ([9f1fb75](https://github.com/Saghen/blink.cmp/commit/9f1fb75b3ec282253ce6392360a584d0234904d0)) +* on_key for cmdline events ([89479f3](https://github.com/Saghen/blink.cmp/commit/89479f3f4c9096330a321a6cc438f5bc3f1e596b)), closes [#534](https://github.com/Saghen/blink.cmp/issues/534) +* prefetch first item when selection == 'manual' | 'auto_insert' ([a8222cf](https://github.com/Saghen/blink.cmp/commit/a8222cf1ccbf24818ae926f94779267659809ab0)), closes [#627](https://github.com/Saghen/blink.cmp/issues/627) +* **provider:** add missing validations ([#516](https://github.com/Saghen/blink.cmp/issues/516)) ([1eda2b9](https://github.com/Saghen/blink.cmp/commit/1eda2b989213b54a66589b44236bfcb427c9a5fe)) +* **provider:** restore path completion source ([#506](https://github.com/Saghen/blink.cmp/issues/506)) ([b2d13ba](https://github.com/Saghen/blink.cmp/commit/b2d13ba7a0aa6f53d3b0db2cd5ede7827ec72f5b)), closes [#465](https://github.com/Saghen/blink.cmp/issues/465) +* re-enable scrollbar on menu ([d48bb17](https://github.com/Saghen/blink.cmp/commit/d48bb176ae3a8d2f3fa4240f9098b94f1f0947ca)), closes [#519](https://github.com/Saghen/blink.cmp/issues/519) +* remove vim.notify on snippet only containing text ([59ef8a4](https://github.com/Saghen/blink.cmp/commit/59ef8a45eeafef35d8196473d86acbe515116027)) +* respect opts.index when checking if cmp.accept can be run ([ea12c51](https://github.com/Saghen/blink.cmp/commit/ea12c516ef43f14683903064bad7612d6e6a6a02)), closes [#633](https://github.com/Saghen/blink.cmp/issues/633) +* revert enabled logic or ([cfd1b7f](https://github.com/Saghen/blink.cmp/commit/cfd1b7f1b24ed77049d978c0a8813097a6e3acc7)), closes [#574](https://github.com/Saghen/blink.cmp/issues/574) [#577](https://github.com/Saghen/blink.cmp/issues/577) +* run callback when LSP client returns nil ([f9b72e3](https://github.com/Saghen/blink.cmp/commit/f9b72e3c1a1b61984b9128fb3e024fdf8a3d07fa)), closes [#543](https://github.com/Saghen/blink.cmp/issues/543) +* schedule get_bufnrs for buffer source ([342c5ed](https://github.com/Saghen/blink.cmp/commit/342c5ed6336d2850c59937747daccb4e880319e0)) +* signature help window documentation rendering ([264aea4](https://github.com/Saghen/blink.cmp/commit/264aea42fb2de42a377ae573141cfb61ab849f47)) +* sort by sortText/label again ([30705ab](https://github.com/Saghen/blink.cmp/commit/30705aba472b5c67b3a34d84f40d36add75b4c44)), closes [#444](https://github.com/Saghen/blink.cmp/issues/444) +* **sources:** set default item kind to `Property` ([#505](https://github.com/Saghen/blink.cmp/issues/505)) ([08ff824](https://github.com/Saghen/blink.cmp/commit/08ff824de4b76d314f7871e0345f7990b3faccb4)) +* **tailwind:** color rendering ([#601](https://github.com/Saghen/blink.cmp/issues/601)) ([02528e8](https://github.com/Saghen/blink.cmp/commit/02528e8ccbe4d0cef5e1df52eda419c5ed557ad3)) +* uncomment event emitter autocmd ([e1cf25f](https://github.com/Saghen/blink.cmp/commit/e1cf25fea50593993777865b3cca1db556a4a90b)) +* use luasnip get_snippet_filetypes, remove global_snippets option ([c0b5ae9](https://github.com/Saghen/blink.cmp/commit/c0b5ae940d7516eb07ca499f5a46445f216c46d3)), closes [#603](https://github.com/Saghen/blink.cmp/issues/603) +* use transform_items on resolve ([85176f7](https://github.com/Saghen/blink.cmp/commit/85176f7e3264b8ac3b571db12191416a4dce0303)), closes [#614](https://github.com/Saghen/blink.cmp/issues/614) + +## [0.7.5](https://github.com/Saghen/blink.cmp/compare/v0.7.4...v0.7.5) (2024-12-10) + +### Features + +* use `enabled` function instead of blocked_filetypes ([a6636c1](https://github.com/Saghen/blink.cmp/commit/a6636c1c38704c1581750b29abb0addabd198b89)), closes [#440](https://github.com/Saghen/blink.cmp/issues/440) + +### Bug Fixes + +* **fallback:** make fallback work with buffer-local mappings ([#483](https://github.com/Saghen/blink.cmp/issues/483)) ([8b553f6](https://github.com/Saghen/blink.cmp/commit/8b553f65419d051fe84eeeda3e2071e104c4f272)) + +## [0.7.4](https://github.com/Saghen/blink.cmp/compare/v0.7.3...v0.7.4) (2024-12-09) + +### Features + +* support non-latin characters for keyword and buffer source ([51d5f59](https://github.com/Saghen/blink.cmp/commit/51d5f598adf7f1cd1bb188011bb761c1856083a9)), closes [#130](https://github.com/Saghen/blink.cmp/issues/130) [#388](https://github.com/Saghen/blink.cmp/issues/388) + +### Bug Fixes + +* check response.err instead of response.error ([#473](https://github.com/Saghen/blink.cmp/issues/473)) ([e720477](https://github.com/Saghen/blink.cmp/commit/e7204774a6e99c5e222c930565353c757d2d0ec1)) +* completion.trigger.show_in_snippet ([#452](https://github.com/Saghen/blink.cmp/issues/452)) ([a42afb6](https://github.com/Saghen/blink.cmp/commit/a42afb61ad455816aef6baa1992f8de45e9a5eb1)), closes [#443](https://github.com/Saghen/blink.cmp/issues/443) +* documentation window auto show once and for all ([624676e](https://github.com/Saghen/blink.cmp/commit/624676efda13aa78a042aba29ee13e109821fa76)), closes [#430](https://github.com/Saghen/blink.cmp/issues/430) +* fill in cargoHash ([aa70277](https://github.com/Saghen/blink.cmp/commit/aa70277f537c942f7e477fd135531fffc37d81f3)) +* **highlight:** fix invalid highlight for doc separator ([#449](https://github.com/Saghen/blink.cmp/issues/449)) ([283a6af](https://github.com/Saghen/blink.cmp/commit/283a6afee44e0aea9b17074d49779558354d3520)) +* luasnip resolve documentation ([85f318b](https://github.com/Saghen/blink.cmp/commit/85f318b6db5b48d825d4ef575b405a8d41233753)), closes [#437](https://github.com/Saghen/blink.cmp/issues/437) +* make buffer events options required ([d0b0e16](https://github.com/Saghen/blink.cmp/commit/d0b0e16671733432986953bf4ddff268eb5b2d7c)) +* **render:** not render two separator for doc window ([#451](https://github.com/Saghen/blink.cmp/issues/451)) ([fc12fa9](https://github.com/Saghen/blink.cmp/commit/fc12fa99d4e1274d331c2004e777981193f7d6f8)) +* revert luasnip source to use current cursor position ([5cfff34](https://github.com/Saghen/blink.cmp/commit/5cfff3433a2afc3f4e29eb4e3caa8f80953f0cfb)) + +## [0.7.3](https://github.com/Saghen/blink.cmp/compare/v0.7.2...v0.7.3) (2024-12-03) + +### Bug Fixes + +* revert to original logic for updating menu position ([99129b6](https://github.com/Saghen/blink.cmp/commit/99129b67759c1b78198e527eae9cc91121cded29)), closes [#436](https://github.com/Saghen/blink.cmp/issues/436) + +## [0.7.2](https://github.com/Saghen/blink.cmp/compare/v0.7.1...v0.7.2) (2024-12-03) + +> [!IMPORTANT] +> A native `luasnip` source has been added, please see the [README](https://github.com/Saghen/blink.cmp#luasnip) for the configuration + +### Features + +* add `auto_show` property for menu ([29fe017](https://github.com/Saghen/blink.cmp/commit/29fe017624030fa53ee053626762fa385a9adb19)), closes [#402](https://github.com/Saghen/blink.cmp/issues/402) +* clamp text edit range to bounds ([7ceff61](https://github.com/Saghen/blink.cmp/commit/7ceff61595aae682b421a68e208719b1523c7b44)), closes [#257](https://github.com/Saghen/blink.cmp/issues/257) +* expose reload function ([f4e53f2](https://github.com/Saghen/blink.cmp/commit/f4e53f2ac7a3d8c3ef47be0dffa97dca637bf696)), closes [#428](https://github.com/Saghen/blink.cmp/issues/428) +* native luasnip source ([08b59ed](https://github.com/Saghen/blink.cmp/commit/08b59edc59950be279f8c72a20bd7897e9f0d021)), closes [#378](https://github.com/Saghen/blink.cmp/issues/378) [#401](https://github.com/Saghen/blink.cmp/issues/401) [#432](https://github.com/Saghen/blink.cmp/issues/432) + +### Bug Fixes + +* avoid removing words for current line on out of focus buffers ([2cbb02d](https://github.com/Saghen/blink.cmp/commit/2cbb02da58ab40f2bfd3dd85f80cba76d6279987)), closes [#433](https://github.com/Saghen/blink.cmp/issues/433) +* documentation not updating after manually opened ([8c1fdc9](https://github.com/Saghen/blink.cmp/commit/8c1fdc901cfead1cd88ed3e652d45ca7d75a3d3f)), closes [#430](https://github.com/Saghen/blink.cmp/issues/430) +* handle nil line ([#429](https://github.com/Saghen/blink.cmp/issues/429)) ([38b3ad6](https://github.com/Saghen/blink.cmp/commit/38b3ad6d4af9d392d3e5e0dabcb14e7d8e348314)) + +## [0.7.1](https://github.com/Saghen/blink.cmp/compare/v0.7.0...v0.7.1) (2024-12-02) + +### Bug Fixes + +* arguments on curl ([f992b72](https://github.com/Saghen/blink.cmp/commit/f992b72017cac77d4f4e22dc05016e5d79adff68)) +* drop retry from curl ([6e9fb62](https://github.com/Saghen/blink.cmp/commit/6e9fb6254bb49eaf014a48049ff511bbfd6a66a3)), closes [#425](https://github.com/Saghen/blink.cmp/issues/425) + +## [0.7.0](https://github.com/Saghen/blink.cmp/compare/v0.6.2...v0.7.0) (2024-12-02) + +> [!IMPORTANT] +> Most of the configuration has been reworked, please see the README for the new schema + +* Includes an enormous refactor in preparation for sources v2, commandline completions, and the v1 release [#389](https://github.com/Saghen/blink.cmp/issues/389) +* Enable experimental Treesitter highlighting on the labels via `completion.menu.draw.treesitter = true` + +### BREAKING CHANGES + +* nuke the debt ([#389](https://github.com/Saghen/blink.cmp/issues/389)) ([1187172](https://github.com/Saghen/blink.cmp/commit/11871727278381febd05d1ee1a17f98fb2e32b26)), closes [#323](https://github.com/Saghen/blink.cmp/issues/323) + +### Features + +* add show_on_keyword and show_on_trigger_character trigger options ([69a69dd](https://github.com/Saghen/blink.cmp/commit/69a69dd7c66f2290dea849846402266b2303782c)), closes [#402](https://github.com/Saghen/blink.cmp/issues/402) +* allow completing buffer words with unicode ([#392](https://github.com/Saghen/blink.cmp/issues/392)) ([e1d3e9d](https://github.com/Saghen/blink.cmp/commit/e1d3e9d4a64466b521940b3ccb67c6fd534b0032)) +* call execute after accepting, but before applying semantic brackets ([073449a](https://github.com/Saghen/blink.cmp/commit/073449a872d49d0c61cb1cf020232d609b2b3d8c)) +* default to empty table for setup ([#412](https://github.com/Saghen/blink.cmp/issues/412)) ([4559ec5](https://github.com/Saghen/blink.cmp/commit/4559ec5cfb91ed8080e2f8df7d4784e12aa27f18)) +* error on download failure ([6054da2](https://github.com/Saghen/blink.cmp/commit/6054da23af87117afd1de59bb77df90037e84675)) +* nuke the debt ([#389](https://github.com/Saghen/blink.cmp/issues/389)) ([1187172](https://github.com/Saghen/blink.cmp/commit/11871727278381febd05d1ee1a17f98fb2e32b26)), closes [#323](https://github.com/Saghen/blink.cmp/issues/323) +* prebuilt binary retry, disable progress, and docs ([bc67391](https://github.com/Saghen/blink.cmp/commit/bc67391de57ce3e42302b13cccf9dd41207c0860)), closes [#68](https://github.com/Saghen/blink.cmp/issues/68) +* **render:** support `source_id` and `source_name` in menu render ([#400](https://github.com/Saghen/blink.cmp/issues/400)) ([d5f62f9](https://github.com/Saghen/blink.cmp/commit/d5f62f981cde0660944626aaeaab8541c9516346)) +* support accepting and drawing by index ([4b1a793](https://github.com/Saghen/blink.cmp/commit/4b1a79305d9acb22171062053a6c942383fefa72)), closes [#382](https://github.com/Saghen/blink.cmp/issues/382) +* support get_bufnrs for the buffer source ([#411](https://github.com/Saghen/blink.cmp/issues/411)) ([4c65dbd](https://github.com/Saghen/blink.cmp/commit/4c65dbde1709bed2cb87483b0ce4eb522098bebc)) +* treesitter highlighter ([#404](https://github.com/Saghen/blink.cmp/issues/404)) ([08a0777](https://github.com/Saghen/blink.cmp/commit/08a07776838e205c697a3d05bcf43104a2adacf5)) +* use sort_text over label for sorting ([0386120](https://github.com/Saghen/blink.cmp/commit/0386120c3bbe32a6746b73a8e38ec954c58575c9)), closes [#365](https://github.com/Saghen/blink.cmp/issues/365) + +### Bug Fixes + +* accept grabbing wrong config ([3dcf98d](https://github.com/Saghen/blink.cmp/commit/3dcf98d8a5c1c720d5a3d789ac14a9741dbe70eb)) +* allow border to be a table ([52f6387](https://github.com/Saghen/blink.cmp/commit/52f63878c0affef88023cd2a00a103644cb7ccfa)), closes [#398](https://github.com/Saghen/blink.cmp/issues/398) +* auto_insert scheduling and module reference ([1b3cd31](https://github.com/Saghen/blink.cmp/commit/1b3cd31e26066308f97075fee7744cd8694cd75e)) +* autocmd called in fast event ([9428983](https://github.com/Saghen/blink.cmp/commit/94289832dc7c148862fdf9326e173df265abe8ad)), closes [#396](https://github.com/Saghen/blink.cmp/issues/396) +* buffer events suppression, auto_insert selection ([96ceb56](https://github.com/Saghen/blink.cmp/commit/96ceb56f7b6e0abeacb01aa2b04abef33121d38b)), closes [#415](https://github.com/Saghen/blink.cmp/issues/415) +* convert additional text edits to utf-8 ([49981f2](https://github.com/Saghen/blink.cmp/commit/49981f2bc8c04967cf868574913f092392a267fe)), closes [#397](https://github.com/Saghen/blink.cmp/issues/397) +* cycling list skipping one item ([07b2ee1](https://github.com/Saghen/blink.cmp/commit/07b2ee14eaae6908f0da44bfa918177d167b12de)) +* deduplicate mode changes, dont hide on select mode ([04ff262](https://github.com/Saghen/blink.cmp/commit/04ff262f3590cd9b63dab03e2cecc759d4abdf69)), closes [#393](https://github.com/Saghen/blink.cmp/issues/393) +* default snippet active function not returning ([59add2d](https://github.com/Saghen/blink.cmp/commit/59add2d602d9a13003ed3430232b3689872ea9ac)), closes [#399](https://github.com/Saghen/blink.cmp/issues/399) +* don't set window properties when nil ([cb815af](https://github.com/Saghen/blink.cmp/commit/cb815afca7c32af7feeb3a90d5b450620d4bef2b)), closes [#407](https://github.com/Saghen/blink.cmp/issues/407) +* ensure failed curl doesn't update the version ([933052b](https://github.com/Saghen/blink.cmp/commit/933052b8e9b585c24c493fdc34a66519d4889c1b)), closes [#68](https://github.com/Saghen/blink.cmp/issues/68) +* ensure menu selection index is within bounds ([bb5407d](https://github.com/Saghen/blink.cmp/commit/bb5407d27e93dc71f8572571ab04b3fc02fc8259)), closes [#416](https://github.com/Saghen/blink.cmp/issues/416) +* filter text always being nil ([33f7d8d](https://github.com/Saghen/blink.cmp/commit/33f7d8df8119673b7eca3d7a04ed28b805cae296)), closes [#365](https://github.com/Saghen/blink.cmp/issues/365) +* incorrect context start_col 1 char after beginning of line ([e88da6a](https://github.com/Saghen/blink.cmp/commit/e88da6a123c857ec2da92ff488c3f82cfba718ef)), closes [#405](https://github.com/Saghen/blink.cmp/issues/405) +* invalid configuration and readme after refactor ([56f7cb6](https://github.com/Saghen/blink.cmp/commit/56f7cb679ef9e5c09351bfa67b081c68ad27349f)), closes [#394](https://github.com/Saghen/blink.cmp/issues/394) +* keyword range "full" when covering end of line ([160b687](https://github.com/Saghen/blink.cmp/commit/160b6875095977d49e16c4e33add4b0e6b0c8668)), closes [#268](https://github.com/Saghen/blink.cmp/issues/268) +* misc typing issues ([b94172c](https://github.com/Saghen/blink.cmp/commit/b94172c8b28f6030c0df3f846eec4a129a25c5bb)) +* only affect initial show for show_on_keyword and show_on_trigger_character ([ea61b1d](https://github.com/Saghen/blink.cmp/commit/ea61b1dc9ed2c4a092ab1365657bc4220b1b5488)), closes [#402](https://github.com/Saghen/blink.cmp/issues/402) +* signature window highlight ns ([0b9a128](https://github.com/Saghen/blink.cmp/commit/0b9a1282eb4f9e44de66fd689d4e301bb987abf5)) +* signature window setup ([cab7576](https://github.com/Saghen/blink.cmp/commit/cab7576350c12de902dc18a85d17f4733f1f9938)) +* super-tab preset keymap name ([f569aeb](https://github.com/Saghen/blink.cmp/commit/f569aeb9e684a2b18514077501e98b0f9ef873bd)) +* user autocmd called in fast event not being wrapped ([e9baeea](https://github.com/Saghen/blink.cmp/commit/e9baeeac1d05d8cbbbee560380853baeb8b316f3)) + +### Documentation + +* add note about reworked config ([180be7b](https://github.com/Saghen/blink.cmp/commit/180be7ba574033baa30fa8af0db4f59db7353584)) + +## [0.6.2](https://github.com/Saghen/blink.cmp/compare/v0.6.1...v0.6.2) (2024-11-26) + +### Features + +* add `cancel` command for use with `auto_insert` ([c58b3a8](https://github.com/Saghen/blink.cmp/commit/c58b3a8ec2cd71b422fbd4b1607e924996dfdebb)), closes [#215](https://github.com/Saghen/blink.cmp/issues/215) +* remove rust from blocked auto brackets filetypes ([8500a62](https://github.com/Saghen/blink.cmp/commit/8500a62e6f07a823b373df91b00c997734b3c664)), closes [#359](https://github.com/Saghen/blink.cmp/issues/359) + +### Bug Fixes + +* mark all config properties as optional ([e328bde](https://github.com/Saghen/blink.cmp/commit/e328bdedc4d12d01ff5c68bee8ea6ae6f33f42f7)), closes [#370](https://github.com/Saghen/blink.cmp/issues/370) +* path source not handling hidden files correctly ([22c5c0d](https://github.com/Saghen/blink.cmp/commit/22c5c0d2c96d5ab86cd23f8df76f005505138a5d)), closes [#369](https://github.com/Saghen/blink.cmp/issues/369) +* use offset encoding of first client ([0a2abab](https://github.com/Saghen/blink.cmp/commit/0a2ababaa450f50afeb4653c3d40b34344aa80d6)), closes [#380](https://github.com/Saghen/blink.cmp/issues/380) + +## [0.6.1](https://github.com/Saghen/blink.cmp/compare/v0.6.0...v0.6.1) (2024-11-24) + +### Features + +* add prebuilt binaries for android ([#362](https://github.com/Saghen/blink.cmp/issues/362)) ([11a50fe](https://github.com/Saghen/blink.cmp/commit/11a50fe006a4482ab5acb5bcd77efa4fb9f944f8)) + +## [0.6.0](https://github.com/Saghen/blink.cmp/compare/v0.5.1...v0.6.0) (2024-11-24) + +### BREAKING CHANGES + +* matched character highlighting, draw rework (#245) +* set default nerd_font_variant to mono + +### Features + +* add `execute` function for sources ([653b262](https://github.com/Saghen/blink.cmp/commit/653b2629e1dab0c6d0084d90f30a600d601812a1)) +* add get_filetype option for snippet source ([#352](https://github.com/Saghen/blink.cmp/issues/352)) ([7c3ad2b](https://github.com/Saghen/blink.cmp/commit/7c3ad2b1fcd0250df69162ad71439cfe547f9608)), closes [#292](https://github.com/Saghen/blink.cmp/issues/292) +* add scrollbar to autocomplete menu ([#259](https://github.com/Saghen/blink.cmp/issues/259)) ([4c2a36c](https://github.com/Saghen/blink.cmp/commit/4c2a36ce8efb2f02d12600b43b3de32898d07433)) +* add snippet indicator back to label on render ([6f5ae79](https://github.com/Saghen/blink.cmp/commit/6f5ae79218334e5d1ca783e22847bbc6b4daef16)) +* allow disabling keymap by passing an empty table ([e384594](https://github.com/Saghen/blink.cmp/commit/e384594deee2f7be225cb89dbcb72d9b6482fde8)) +* avoid taking up space when scrollbar is hidden ([77f037c](https://github.com/Saghen/blink.cmp/commit/77f037cae07358368f3b7548ba39cffceb49349e)) +* extract word from completion item for auto-insert preview ([#341](https://github.com/Saghen/blink.cmp/issues/341)) ([285f6f4](https://github.com/Saghen/blink.cmp/commit/285f6f498c8ba3ac0788edb1db2f8d2d3cb20fad)) +* matched character highlighting, draw rework ([#245](https://github.com/Saghen/blink.cmp/issues/245)) ([683c47a](https://github.com/Saghen/blink.cmp/commit/683c47ac8c6e538122dc0fe50187b78f8995a549)) +* option to disable treesitter highlighting ([1c14f8e](https://github.com/Saghen/blink.cmp/commit/1c14f8e8817015634c593eb3832a73e4993c561e)) +* position documentation based on desired size, not max size ([973f06a](https://github.com/Saghen/blink.cmp/commit/973f06a164835b74247f46b3c5b2ae895a1acb1b)) +* set default nerd_font_variant to mono ([d3e1c92](https://github.com/Saghen/blink.cmp/commit/d3e1c92e68b74f3d05f6ab7dfff2af8f83769149)) +* support editRange, use textEditText when editRange is defined ([db3d1ad](https://github.com/Saghen/blink.cmp/commit/db3d1ad8d6420ce29d548991468cc0107fe9d04b)), closes [#310](https://github.com/Saghen/blink.cmp/issues/310) +* temporarily disable markdown combining ([24b4d35](https://github.com/Saghen/blink.cmp/commit/24b4d350b469595ff39ce48a45ee12b59578aae6)) +* use filter_text when available ([12b4f11](https://github.com/Saghen/blink.cmp/commit/12b4f116648d87551a07def740a0375446105bbc)) +* validate provider names in enabled_providers ([e9c9b41](https://github.com/Saghen/blink.cmp/commit/e9c9b41ea0f8ae36b7c19c970bf313f1ca93bd1b)) + +### Bug Fixes + +* add ctx.icon_gap in kind_icon component ([ccf02f5](https://github.com/Saghen/blink.cmp/commit/ccf02f5e39e3ed7b4e65dbe667a3329313540eba)) +* applying preview text_edit ([#296](https://github.com/Saghen/blink.cmp/issues/296)) ([8372a6b](https://github.com/Saghen/blink.cmp/commit/8372a6bfce9499f3bb8a91a23db8fe1d83f2d625)) +* check if source is in `enabled_providers` before calling source:enabled ([#266](https://github.com/Saghen/blink.cmp/issues/266)) ([338d2a6](https://github.com/Saghen/blink.cmp/commit/338d2a6e81b9e0f9e66b691c36c9959a2705085a)) +* clear last_char on trigger hide ([1ce30c9](https://github.com/Saghen/blink.cmp/commit/1ce30c9d1aa539f05e99b9ecea0dcc35d4cc33fe)), closes [#228](https://github.com/Saghen/blink.cmp/issues/228) +* completion label details containing newline characters ([#265](https://github.com/Saghen/blink.cmp/issues/265)) ([1628800](https://github.com/Saghen/blink.cmp/commit/1628800e1747ecc767368cab45916177c723da82)) +* consider the border when calculating the position of the autocom… ([#325](https://github.com/Saghen/blink.cmp/issues/325)) ([41178d3](https://github.com/Saghen/blink.cmp/commit/41178d39670ce8db5e93a0028a7f23729559a326)) +* consider the border when calculating the width of the documentat… ([#326](https://github.com/Saghen/blink.cmp/issues/326)) ([130eb51](https://github.com/Saghen/blink.cmp/commit/130eb512e2849c021d73bd269b77cc3b0ecf8b74)) +* convert to utf-8 encoding on text edits ([2e37993](https://github.com/Saghen/blink.cmp/commit/2e379931090f3737b844598a18382241197aaa2a)), closes [#188](https://github.com/Saghen/blink.cmp/issues/188) [#200](https://github.com/Saghen/blink.cmp/issues/200) +* default highlight groups ([#317](https://github.com/Saghen/blink.cmp/issues/317)) ([69a987b](https://github.com/Saghen/blink.cmp/commit/69a987b96cf754a12b6d7dafce1d2d49ade591f2)) +* default to item when assigning defaults, only use known defaults ([fb9f374](https://github.com/Saghen/blink.cmp/commit/fb9f3744cbc4c8b0c6792ed1c072009864a1bd6d)), closes [#151](https://github.com/Saghen/blink.cmp/issues/151) +* documentation misplacement due to screenpos returning 0,0 ([cb0baa4](https://github.com/Saghen/blink.cmp/commit/cb0baa4403fe5cf6d5dc3af483176780e44ba071)) +* download mechanism works with GIT_DIR and GIT_WORK_TREE set ([#275](https://github.com/Saghen/blink.cmp/issues/275)) ([8c9930c](https://github.com/Saghen/blink.cmp/commit/8c9930c94e17ca0ab9956986b175cd91f4ac3a59)) +* drop unnecessary filetype configuration ([bec27d9](https://github.com/Saghen/blink.cmp/commit/bec27d9196fe3c0020b56e49533a8f08cc8ea45f)), closes [#295](https://github.com/Saghen/blink.cmp/issues/295) +* drop vim print ([c3447cc](https://github.com/Saghen/blink.cmp/commit/c3447cc2bd4afec7050230b49a3e889c43084400)) +* get the cursor position relative to the window instead of the sc… ([#327](https://github.com/Saghen/blink.cmp/issues/327)) ([5479abf](https://github.com/Saghen/blink.cmp/commit/5479abfbfb47bf4d23220a6e5a3eb11f23e57214)) +* **ghost-text:** flickering using autocmds ([#255](https://github.com/Saghen/blink.cmp/issues/255)) ([a94bbaf](https://github.com/Saghen/blink.cmp/commit/a94bbaf9f2c6329f4593233f069b3dea21b4cedc)) +* handle gap for empty text ([#301](https://github.com/Saghen/blink.cmp/issues/301)) ([371ad28](https://github.com/Saghen/blink.cmp/commit/371ad288544423531121c1abf0d519dda791e9f1)) +* handle not being in a git repository, fix error on flakes ([#281](https://github.com/Saghen/blink.cmp/issues/281)) ([d2a216d](https://github.com/Saghen/blink.cmp/commit/d2a216de72a6b3a741c214b66e70897ff6f16dc2)) +* ignore empty doc lines and detail lines ([aeaa2e7](https://github.com/Saghen/blink.cmp/commit/aeaa2e78dad7885e99b5a00a70b9c57c5a5302aa)), closes [#247](https://github.com/Saghen/blink.cmp/issues/247) +* join newlines in `label_description` ([#333](https://github.com/Saghen/blink.cmp/issues/333)) ([8ba2069](https://github.com/Saghen/blink.cmp/commit/8ba2069a57cf6580dea6a50bf71e5b3b2924b284)) +* make ghost-text extmark with pcall ([#287](https://github.com/Saghen/blink.cmp/issues/287)) ([a2f6cfb](https://github.com/Saghen/blink.cmp/commit/a2f6cfb2902e1410f5cdbf386b9af337754f1a07)) +* offset encoding conversion on nvim 0.11.0 ([#308](https://github.com/Saghen/blink.cmp/issues/308)) ([9822c6b](https://github.com/Saghen/blink.cmp/commit/9822c6b40ad91a14e2c75696db30999ae5cf1fc5)), closes [#307](https://github.com/Saghen/blink.cmp/issues/307) +* offset encoding for text edits ([c2a56e4](https://github.com/Saghen/blink.cmp/commit/c2a56e473ff5952211f7c890de0b831e8df3976d)) +* only undo if not snippet ([f4dcebf](https://github.com/Saghen/blink.cmp/commit/f4dcebfd720810b14eb2ad62102028c104bf2205)), closes [#244](https://github.com/Saghen/blink.cmp/issues/244) +* override typing and module ([f1647f7](https://github.com/Saghen/blink.cmp/commit/f1647f7fd97ac7129e1cb8a1ed242ae326f25d6e)) +* padded window ([#315](https://github.com/Saghen/blink.cmp/issues/315)) ([7a37c64](https://github.com/Saghen/blink.cmp/commit/7a37c643412f19b04a03ed4c71e94da175efcfb8)) +* prevent index out of bounds in get_code_block_range ([#271](https://github.com/Saghen/blink.cmp/issues/271)) ([e6c735b](https://github.com/Saghen/blink.cmp/commit/e6c735be455c90df4aa7c11cfe7542f111234de6)) +* remove offset from label detail highlight ([5262586](https://github.com/Saghen/blink.cmp/commit/52625866f5b9a9358313308276dcf110cf1a42ea)) +* reset documentation scroll on new item ([cd3aa32](https://github.com/Saghen/blink.cmp/commit/cd3aa32276308d0c1bddf7a14cd13a8776eb5575)), closes [#239](https://github.com/Saghen/blink.cmp/issues/239) +* scrollbar gutter not updating on window resize ([c8cf209](https://github.com/Saghen/blink.cmp/commit/c8cf209dc843c5a42945bb95a4b8598bcab8c6f8)) +* **scrollbar:** use cursorline to determine thumb position ([#267](https://github.com/Saghen/blink.cmp/issues/267)) ([28fcf95](https://github.com/Saghen/blink.cmp/commit/28fcf952d14a022cd64f89ff32b3442c6101b873)) +* signature help now highlights the right parameter ([#297](https://github.com/Saghen/blink.cmp/issues/297)) ([3fe4c75](https://github.com/Saghen/blink.cmp/commit/3fe4c75c69f208462c4e8957005f6ccb72b1da25)) +* **snippets:** fix nullpointer exception ([#355](https://github.com/Saghen/blink.cmp/issues/355)) ([3ac471b](https://github.com/Saghen/blink.cmp/commit/3ac471bbfe614adb77fc8179dd4adaa0d1576542)) +* tailwind colors ([#306](https://github.com/Saghen/blink.cmp/issues/306)) ([8e3af0e](https://github.com/Saghen/blink.cmp/commit/8e3af0ec0079b599fb57f97653b2f20f98e2a5bb)) +* **types:** allow resolving empty response from blink.cmd.Source ([#254](https://github.com/Saghen/blink.cmp/issues/254)) ([46a5f0b](https://github.com/Saghen/blink.cmp/commit/46a5f0b9fd8e6753d118853d384ae85bfdb70c30)) +* use pmenu scrollbar highlights ([5632376](https://github.com/Saghen/blink.cmp/commit/5632376d4f51d777013d5f48414a15f02be854af)) + +## [0.5.1](https://github.com/Saghen/blink.cmp/compare/v0.5.0...v0.5.1) (2024-11-03) + +### BREAKING CHANGES + +* set max_width to 80 for documentation + +### Features + +* 'enter' keymap ([4ec5cea](https://github.com/Saghen/blink.cmp/commit/4ec5cea4858eee31919cc2a5bc1850846073c5ec)) +* add label details to all draw functions ([f9c58ab](https://github.com/Saghen/blink.cmp/commit/f9c58ab26a427883965394959276fd347574b11e)), closes [#97](https://github.com/Saghen/blink.cmp/issues/97) +* add winblend option for windows ([#237](https://github.com/Saghen/blink.cmp/issues/237)) ([ca94ee0](https://github.com/Saghen/blink.cmp/commit/ca94ee0b1ec848bac6426811f12f6da39e48d02a)) +* align completion window ([#235](https://github.com/Saghen/blink.cmp/issues/235)) ([0c13fbd](https://github.com/Saghen/blink.cmp/commit/0c13fbd3d7bed1d4bab08d3831c95ee3dfb7277f)), closes [#221](https://github.com/Saghen/blink.cmp/issues/221) +* allow merging of keymap preset with custom keymap ([#233](https://github.com/Saghen/blink.cmp/issues/233)) ([6b46164](https://github.com/Saghen/blink.cmp/commit/6b46164eac2feb6dd49e6e8c434cb276f50c8132)) +* better extraction of detail from doc ([b0815e4](https://github.com/Saghen/blink.cmp/commit/b0815e461623d9a9ea06fb632167ca25656abcf5)) +* only offset window when using preset draw ([75cadbc](https://github.com/Saghen/blink.cmp/commit/75cadbcd2657ed01326ca2b0e5e4d78a77127ca3)) +* rework window positioning ([a67adaf](https://github.com/Saghen/blink.cmp/commit/a67adaf623f9c6e1803a693044608b73e02e8da3)), closes [#45](https://github.com/Saghen/blink.cmp/issues/45) [#194](https://github.com/Saghen/blink.cmp/issues/194) +* set max_width to 80 for documentation ([dc1de2b](https://github.com/Saghen/blink.cmp/commit/dc1de2bf962c67e8ba8647710817bbce04f92bdb)) +* TailwindCSS highlight support ([#143](https://github.com/Saghen/blink.cmp/issues/143)) ([b2bbef5](https://github.com/Saghen/blink.cmp/commit/b2bbef52f24799f0e79a3adf6038366b26e2451b)) + +### Bug Fixes + +* add "enter" keymap to types ([3ca68ef](https://github.com/Saghen/blink.cmp/commit/3ca68ef008e383a28c760de2d5ee65b35efbb5c5)) +* allow to be lazy loaded on InsertEnter ([#243](https://github.com/Saghen/blink.cmp/issues/243)) ([9d50661](https://github.com/Saghen/blink.cmp/commit/9d5066134b339c5e4aa6cec3daa086d3b0671892)) +* alpine linux detection ([a078c87](https://github.com/Saghen/blink.cmp/commit/a078c877ac17a912a51aba9d9e0068a0f1ed509b)) +* check LSP methods before requesting ([193423c](https://github.com/Saghen/blink.cmp/commit/193423ca584e4e1a9639d6c480a6b952db566c21)), closes [#220](https://github.com/Saghen/blink.cmp/issues/220) +* documentation width ([9bdd828](https://github.com/Saghen/blink.cmp/commit/9bdd828e474e69badb64a305179930cf66acf649)) +* **documentation:** better docs ([#234](https://github.com/Saghen/blink.cmp/issues/234)) ([a253b35](https://github.com/Saghen/blink.cmp/commit/a253b356092b8f64ac66200c249afe5978c3fc39)) +* enable show_in_snippet by default ([76d11a6](https://github.com/Saghen/blink.cmp/commit/76d11a617075dc53e89e1c9b9ce5c62435abdfba)) +* ensure treesitter does not run on windows ([2ac2f43](https://github.com/Saghen/blink.cmp/commit/2ac2f43513cdf63313192271427cc55608f0bedb)), closes [#193](https://github.com/Saghen/blink.cmp/issues/193) +* lazily call fuzzy access ([aeb6195](https://github.com/Saghen/blink.cmp/commit/aeb6195ba870c61e4e0f2d4e8ef1bcc80464af9b)) +* make all of source provider config optional ([055b943](https://github.com/Saghen/blink.cmp/commit/055b9435358f68ae26de75d9294749bd69c22ccc)) +* only check enabled fallback sources ([#232](https://github.com/Saghen/blink.cmp/issues/232)) ([ecb3520](https://github.com/Saghen/blink.cmp/commit/ecb3520c899eee9dbe738620f3c327b8089fe1f8)) +* window direction and autocomplete closing on position update ([4b3fd8f](https://github.com/Saghen/blink.cmp/commit/4b3fd8f5ce6ece4f84d6c6ddfd0a42f43b889574)), closes [#240](https://github.com/Saghen/blink.cmp/issues/240) + +## [0.5.0](https://github.com/Saghen/blink.cmp/compare/v0.4.1...v0.5.0) (2024-10-30) + +> [!IMPORTANT] +> The **keymap** configuration has been reworked, please see the README for the new schema + +You may now use `nvim-cmp` sources within `blink.cmp` using @stefanboca's compatibility layer: https://github.com/Saghen/blink.compat + +### BREAKING CHANGES + +* rework keymap config + +### Features + +* `enabled` function for sources ([c104663](https://github.com/Saghen/blink.cmp/commit/c104663e92c15dd59ee3b299249361cd095206f4)), closes [#208](https://github.com/Saghen/blink.cmp/issues/208) +* accept error handling, expose autocomplete.select ([9cd1236](https://github.com/Saghen/blink.cmp/commit/9cd123657fce6e563a7d24b438f61b012ca1559f)) +* cache resolve tasks ([83a8303](https://github.com/Saghen/blink.cmp/commit/83a8303e2744d01249f465f219f0dc5a41104a9e)) +* glibc 2.17 and musl prebuilt binaries ([c593835](https://github.com/Saghen/blink.cmp/commit/c593835fe1b0297dfbcabe46edcd1edb9d317b94)), closes [#160](https://github.com/Saghen/blink.cmp/issues/160) +* ignore _*.lua files ([f6eccaf](https://github.com/Saghen/blink.cmp/commit/f6eccaf3f2ef8939ea661ee8384e299a9428999c)) +* lsp capabilities ([e0e08cb](https://github.com/Saghen/blink.cmp/commit/e0e08cbfea667ff21b9e6e5acb0389ddd6d2de41)) +* output preview with ghost text, including for snippets ([#186](https://github.com/Saghen/blink.cmp/issues/186)) ([6d25187](https://github.com/Saghen/blink.cmp/commit/6d2518745db83da0b15f60e22c15c205fb1ed56f)) +* **perf:** call score_offset func once per source ([bd90e00](https://github.com/Saghen/blink.cmp/commit/bd90e007f33c60a3a11bb99ff2e8bfd897fe27b3)) +* prefetch resolve on select ([52ec2c9](https://github.com/Saghen/blink.cmp/commit/52ec2c985cb0ef9459b73bb8b08801f35f092f6d)) +* resolve item before accept ([3927128](https://github.com/Saghen/blink.cmp/commit/3927128e712806c22c20487ef0a1ed885bfec292)) +* rework keymap config ([3fd92f0](https://github.com/Saghen/blink.cmp/commit/3fd92f0bbceb31a3cd32b1d7a9d2a62071c85d91)) +* show completion window after accept if on trigger character ([28e0b5a](https://github.com/Saghen/blink.cmp/commit/28e0b5a873c6f4e687260384595a05c55a888ccf)), closes [#198](https://github.com/Saghen/blink.cmp/issues/198) +* support disabling accept on trigger character, block parenthesis ([125d4f1](https://github.com/Saghen/blink.cmp/commit/125d4f1288b3b309d219848559adfca3cc61f8b5)), closes [#212](https://github.com/Saghen/blink.cmp/issues/212) +* switch default keymap to select_and_accept ([f0f2672](https://github.com/Saghen/blink.cmp/commit/f0f26728c3e5c65cf2d27a1b24e4e3fbd26773fb)) +* use treesitter for signature help hl ([0271d79](https://github.com/Saghen/blink.cmp/commit/0271d7957324df68bd352fc7aef60606c96c88ca)) + +### Bug Fixes + +* add back cursor move after accept, but use current line ([ceeeb53](https://github.com/Saghen/blink.cmp/commit/ceeeb538b091c43aa6fb6fd6020531a37cef2191)) +* always return item in resolve ([6f0fc86](https://github.com/Saghen/blink.cmp/commit/6f0fc86f8fbb94ae23770c01dc2e3cf9e1886e99)), closes [#211](https://github.com/Saghen/blink.cmp/issues/211) +* documentation auto show no longer working ([#202](https://github.com/Saghen/blink.cmp/issues/202)) ([6290abd](https://github.com/Saghen/blink.cmp/commit/6290abd24b14723ba4827c28367a805bcc4773de)) +* dont move cursor after accepting ([cab91c5](https://github.com/Saghen/blink.cmp/commit/cab91c5f56eb15394d4cabddcd62eee6963129ec)) +* fallback show_documentation when window open ([bc311b7](https://github.com/Saghen/blink.cmp/commit/bc311b756ca89652bfb18b07a99ff52f424d63a2)) +* handle failed lsp resolve request gracefully ([4c40bf2](https://github.com/Saghen/blink.cmp/commit/4c40bf25f2371d6b3df6f130e154ebac0b9c3422)) +* ignore nil item for resolve prefetching ([b7d1233](https://github.com/Saghen/blink.cmp/commit/b7d1233d826a0406538955b4ef2448dc0e72c536)), closes [#209](https://github.com/Saghen/blink.cmp/issues/209) +* invalid insertTextMode capabilities ([4de7b7e](https://github.com/Saghen/blink.cmp/commit/4de7b7e64100cfdbfc564c475a1713ba2498ba25)) +* prevent treesitter from running on windows ([9b9be31](https://github.com/Saghen/blink.cmp/commit/9b9be318773dcce04f5017574fbe5ed638429852)) +* schedule non-expr fallback keymaps ([#196](https://github.com/Saghen/blink.cmp/issues/196)) ([1a55fd1](https://github.com/Saghen/blink.cmp/commit/1a55fd1e03193e10cb8bc866cc2bc47c9473061c)) +* sending erroneous fields to LSP on resolve ([e82c1b7](https://github.com/Saghen/blink.cmp/commit/e82c1b73607c4905582028e81bc40b10ce9eb8ea)) +* set default keymap to use accept ([7d265b4](https://github.com/Saghen/blink.cmp/commit/7d265b4a19f2c198eda06baf031cb0e41cc3095c)) +* snippet reload function ([407f2d5](https://github.com/Saghen/blink.cmp/commit/407f2d526fd07b651a8a7330df2c4fd05b32a014)) +* snippet resolve ([5d9fa1c](https://github.com/Saghen/blink.cmp/commit/5d9fa1c36cc9e43a9d7cd65ddcc417128a9d41c3)) + +## [0.4.1](https://github.com/Saghen/blink.cmp/compare/v0.4.0...v0.4.1) (2024-10-24) + +### Bug Fixes + +* check semantic token type ([0b493ff](https://github.com/Saghen/blink.cmp/commit/0b493ff3ce7fd8d318e7e1024fbadfe2ec3a624a)) +* exclude prefix including one char ([70438ac](https://github.com/Saghen/blink.cmp/commit/70438ac5016d3ab609f422d1ef084870cb9ceb29)) + +## [0.4.0](https://github.com/Saghen/blink.cmp/compare/v0.3.1...v0.4.0) (2024-10-24) + +> [!IMPORTANT] +> The sources configuration has been reworked, please see the README for the new schema + +### BREAKING CHANGES + +* rework sources config structure and available options +* rework sources system and configuration + +### Features + +* add extra space to ... on normal nerd font ([9b9647b](https://github.com/Saghen/blink.cmp/commit/9b9647bc23f52270ce43e579dda9b9eb5d00b7b8)) +* add nix build-plugin command, update devShell to not require ([32069be](https://github.com/Saghen/blink.cmp/commit/32069be108dda4cf2b0a7316a0be366398187003)) +* auto_show on autocomplete window ([#98](https://github.com/Saghen/blink.cmp/issues/98)) ([82e03b1](https://github.com/Saghen/blink.cmp/commit/82e03b1207b14845d8f29041e2f244693708425b)) +* **config:** add ignored filetypes option ([#108](https://github.com/Saghen/blink.cmp/issues/108)) ([b56a2b1](https://github.com/Saghen/blink.cmp/commit/b56a2b18804a9e4cce6450b886c33fb6b0a58e39)) +* custom documentation highlighting ([90d6394](https://github.com/Saghen/blink.cmp/commit/90d63948368982800ce886e93fc9c7d1c36cf74c)), closes [#113](https://github.com/Saghen/blink.cmp/issues/113) +* default to not showing in snippet ([49b033a](https://github.com/Saghen/blink.cmp/commit/49b033a11830652790603fe9b2a7e6275e626730)), closes [#131](https://github.com/Saghen/blink.cmp/issues/131) +* expose reload function for sources ([ff1f5fa](https://github.com/Saghen/blink.cmp/commit/ff1f5fa525312676f1aaba9b601bd0e13c52644b)), closes [#28](https://github.com/Saghen/blink.cmp/issues/28) +* expose typo resistance, update frizbee ([63b7b22](https://github.com/Saghen/blink.cmp/commit/63b7b2219f4595973225a13e6e664fc836ee305c)) +* **fuzzy:** lazy get lua properties ([5cc63f0](https://github.com/Saghen/blink.cmp/commit/5cc63f0f298ad31cab37d7b2a478e61a3601a768)) +* ignore empty fallback_for table ([9c9e0cc](https://github.com/Saghen/blink.cmp/commit/9c9e0cc78b0933c66916eaccc6a974608d2426de)), closes [#122](https://github.com/Saghen/blink.cmp/issues/122) +* ignore some characters at prefix of keyword ([569156f](https://github.com/Saghen/blink.cmp/commit/569156f432e67a4bd4dccee4fb9beafbf15a1d30)), closes [#135](https://github.com/Saghen/blink.cmp/issues/135) +* mark buffer completion items as plain text ([0f5f484](https://github.com/Saghen/blink.cmp/commit/0f5f484583b19484599b9dfb524180902d10e5b3)), closes [#148](https://github.com/Saghen/blink.cmp/issues/148) +* more robust preview for auto_insert mode ([6e15864](https://github.com/Saghen/blink.cmp/commit/6e158647bbc0628fe46c0175acf32d954c0c172f)), closes [#117](https://github.com/Saghen/blink.cmp/issues/117) +* notify user on json parsing error for snippets ([c5146a5](https://github.com/Saghen/blink.cmp/commit/c5146a5c23db48824fc7720f1265f3e75fc1c634)), closes [#132](https://github.com/Saghen/blink.cmp/issues/132) +* place cursor at first tab stop on snippet preview ([d3e8701](https://github.com/Saghen/blink.cmp/commit/d3e87015e891022a8fe36f43c60806c21f231cea)) +* re-enable typo resistance by default ([b35a559](https://github.com/Saghen/blink.cmp/commit/b35a559abea64a1b77abf69e8252ab5cd063868a)) +* reduce build-plugin command to minimal dependencies, add command to docs ([cfaf9fc](https://github.com/Saghen/blink.cmp/commit/cfaf9fc89a4b0ad36bba7a2605ae9b2f15efbf52)) +* remove snippet deduplication ([e296d8f](https://github.com/Saghen/blink.cmp/commit/e296d8ffcea78c400210d09c669359af89802618)), closes [#146](https://github.com/Saghen/blink.cmp/issues/146) +* rework sources config structure and available options ([e3a811b](https://github.com/Saghen/blink.cmp/commit/e3a811bc9bc9cf55de8bd7a7bcee84236e015dc2)), closes [#144](https://github.com/Saghen/blink.cmp/issues/144) +* rework sources system and configuration ([7fea65c](https://github.com/Saghen/blink.cmp/commit/7fea65c4e4c83d3b194a4d9e4d813204fd9f0ded)), closes [#144](https://github.com/Saghen/blink.cmp/issues/144) +* select_and_accept keymap ([6394508](https://github.com/Saghen/blink.cmp/commit/6394508b0c3f1f1f95e02c8057ccfc4b746dbd75)), closes [#118](https://github.com/Saghen/blink.cmp/issues/118) +* support detail only in doc window ([#147](https://github.com/Saghen/blink.cmp/issues/147)) ([57abdb8](https://github.com/Saghen/blink.cmp/commit/57abdb838cfcc624a87dba2b10f665c30e604b4e)) +* support LSP item defaults ([ffc4282](https://github.com/Saghen/blink.cmp/commit/ffc428208f292fa00cb7cced09d35de6e815ab55)) +* support LSPs with only full semantic tokens and cleanup ([0626cb5](https://github.com/Saghen/blink.cmp/commit/0626cb5446fd8d4ccfae53a93b648534ea1c7bf3)) +* support using suffix for fuzzy matching ([815f4df](https://github.com/Saghen/blink.cmp/commit/815f4dffa58d89d95c6f6e12133b0c1103fa0bdd)), closes [#88](https://github.com/Saghen/blink.cmp/issues/88) +* switch to mlua ([#105](https://github.com/Saghen/blink.cmp/issues/105)) ([873680d](https://github.com/Saghen/blink.cmp/commit/873680d16459d6747609804b08612f6a11f04591)) +* use textEditText as fallback for textEdit ([abcb2a0](https://github.com/Saghen/blink.cmp/commit/abcb2a0dab207e03d382acf62074f639a3572e20)) +* **windows:** add support for individual border character highlights ([#175](https://github.com/Saghen/blink.cmp/issues/175)) ([3c1a502](https://github.com/Saghen/blink.cmp/commit/3c1a5020ab9a993e8bc8c5c05d29588747f00b78)) + +### Bug Fixes + +* add back undo text edit for accept ([f62046a](https://github.com/Saghen/blink.cmp/commit/f62046a775605f597f2b370672d05ee0c9123142)) +* add missing select_and_accept keymap to config ([d2140dc](https://github.com/Saghen/blink.cmp/commit/d2140dc7615991ea88fa1fd75dd4fccb53a73e25)) +* always hide window on accept ([7f5a3d9](https://github.com/Saghen/blink.cmp/commit/7f5a3d9a820125e7da0a1816efaddb84d47a7f18)) +* auto insert breaking on single line text edit ([78ac56e](https://github.com/Saghen/blink.cmp/commit/78ac56e96144ed7475bb6d11981d3c8154bfd366)), closes [#169](https://github.com/Saghen/blink.cmp/issues/169) +* check if item contains brackets before defering to semantic token ([e5f543d](https://github.com/Saghen/blink.cmp/commit/e5f543da2a0ce91c8720b67f0ea6cfa941dc26d6)) +* **config:** set correct type def for blink.cmp.WindowBorderChar ([516190b](https://github.com/Saghen/blink.cmp/commit/516190bcdafa387d417cfb235cbcd7385e902089)) +* don't show completions when trigger context is nil ([5b39d83](https://github.com/Saghen/blink.cmp/commit/5b39d83ac4fed46c57d8db987ea56cb1c0e68b0e)) +* drop prints ([89259f9](https://github.com/Saghen/blink.cmp/commit/89259f936e413e0a324b2ea369eb8ccefc05a14f)), closes [#179](https://github.com/Saghen/blink.cmp/issues/179) +* drop prints ([67fa41f](https://github.com/Saghen/blink.cmp/commit/67fa41f0f0501beb64d4acc8678f8788331a470e)) +* frizbee not matching on capital letters ([722b41b](https://github.com/Saghen/blink.cmp/commit/722b41b0a7028581004888623f3f79c1d9eab8b8)), closes [#162](https://github.com/Saghen/blink.cmp/issues/162) [#173](https://github.com/Saghen/blink.cmp/issues/173) +* fuzzy get query returning extra characters ([b2380a0](https://github.com/Saghen/blink.cmp/commit/b2380a0301e4385e4964cd57e790d6ce169b2b71)), closes [#170](https://github.com/Saghen/blink.cmp/issues/170) [#172](https://github.com/Saghen/blink.cmp/issues/172) [#176](https://github.com/Saghen/blink.cmp/issues/176) +* fuzzy panic on too many items ([1e6dcbf](https://github.com/Saghen/blink.cmp/commit/1e6dcbffbe224fa10ef9fab490ad07dbd9dd19b0)) +* handle treesitter get_parser failure ([fe68c28](https://github.com/Saghen/blink.cmp/commit/fe68c288268f01d1e3b7e692abac2e1fb2093d78)), closes [#171](https://github.com/Saghen/blink.cmp/issues/171) +* item defaults not being applied ([42f8efb](https://github.com/Saghen/blink.cmp/commit/42f8efb43bed968050fcb8feb5fad4b6b27a9b05)), closes [#158](https://github.com/Saghen/blink.cmp/issues/158) +* missing access function ([d11f271](https://github.com/Saghen/blink.cmp/commit/d11f271ddd980e05545627093924e8359c5416b3)) +* only show autocomplete window on select if auto_show is disabled ([fa64556](https://github.com/Saghen/blink.cmp/commit/fa6455635b8f12504e2e892fd8ce8926e679cf68)) +* re-enable memory check for now ([6b24f48](https://github.com/Saghen/blink.cmp/commit/6b24f484d56eeb9b48cb4ce6b58481cdfc50a3bd)) +* remove debug prints ([9846c2d](https://github.com/Saghen/blink.cmp/commit/9846c2d2bfdeaa3088c9c0143030524402fffdf9)) +* select always triggering when auto_show enabled ([db635f2](https://github.com/Saghen/blink.cmp/commit/db635f201f5ac5f48e7e33cc268f23e7646fd946)) +* select_and_accept not working with auto_insert ([65eb336](https://github.com/Saghen/blink.cmp/commit/65eb336f6c33964becacfbfc850f18d6e3cd5581)), closes [#118](https://github.com/Saghen/blink.cmp/issues/118) +* signature window no longer overlaps cursor ([#149](https://github.com/Saghen/blink.cmp/issues/149)) ([7d6b50b](https://github.com/Saghen/blink.cmp/commit/7d6b50b140eadb51d1bf59b93a02293333a59519)) +* single char non-matching keyword ([3cb084c](https://github.com/Saghen/blink.cmp/commit/3cb084cc4e3cc989895d0eabf0ccc690c15d19ed)), closes [#141](https://github.com/Saghen/blink.cmp/issues/141) +* skip treesitter hl on nil lang ([cb9397c](https://github.com/Saghen/blink.cmp/commit/cb9397c89104ab09d85ba5ce1b40852635f9b142)) +* temporary workaround for insertReplaceEdit ([c218faf](https://github.com/Saghen/blink.cmp/commit/c218fafbf275725532f3cf2eaebdf863b958d48e)), closes [#178](https://github.com/Saghen/blink.cmp/issues/178) +* typo in signature.win ([#125](https://github.com/Saghen/blink.cmp/issues/125)) ([69ad25f](https://github.com/Saghen/blink.cmp/commit/69ad25f38e1eb833b7aa5a0efb2d6c485e191149)) +* use treesitter.language.get_lang when choosing parser ([213fd94](https://github.com/Saghen/blink.cmp/commit/213fd94de2ab83ff409e1fd240625959bf61624e)), closes [#133](https://github.com/Saghen/blink.cmp/issues/133) +* window positioning with folds ([819b978](https://github.com/Saghen/blink.cmp/commit/819b978328b244fc124cfcd74661b2a7f4259f4f)), closes [#95](https://github.com/Saghen/blink.cmp/issues/95) + +## [0.3.1](https://github.com/Saghen/blink.cmp/compare/v0.3.0...v0.3.1) (2024-10-14) + +### Bug Fixes + +* **ci:** use correct file ext for windows ([af68874](https://github.com/Saghen/blink.cmp/commit/af68874f1b2e628e0c72ec27f5225d0c6b2d6820)) + +## [0.3.0](https://github.com/Saghen/blink.cmp/compare/v0.2.1...v0.3.0) (2024-10-14) + +### BREAKING CHANGES + +* implement auto-insert option (#65) +* autocompletion window components alignment (#51) +* disable auto_show documentation by default, use <C-space> to toggle + +### Features + +* add back min_width to autocomplete ([9a008c9](https://github.com/Saghen/blink.cmp/commit/9a008c942f180a23671f598ed9680770b254a599)) +* add basic event trigger API ([#31](https://github.com/Saghen/blink.cmp/issues/31)) ([127f518](https://github.com/Saghen/blink.cmp/commit/127f51827cc038aab402abc6bacf9862dd2d72ad)) +* add detail to documentation window ([#33](https://github.com/Saghen/blink.cmp/issues/33)) ([588e4d4](https://github.com/Saghen/blink.cmp/commit/588e4d4a7e42bae0e26c82ebc1ea3c68fa4e7cf0)) +* add health.lua and basic healthchecks ([#101](https://github.com/Saghen/blink.cmp/issues/101)) ([a12617d](https://github.com/Saghen/blink.cmp/commit/a12617d1eb69484d2656ccc40c40e8254b1ea3ec)) +* add minimal render style ([#85](https://github.com/Saghen/blink.cmp/issues/85)) ([b4bbad1](https://github.com/Saghen/blink.cmp/commit/b4bbad181b0e1b9cdf1025b790cf720d707a8c26)) +* added a preselect option to the cmp menu ([#24](https://github.com/Saghen/blink.cmp/issues/24)) ([1749e32](https://github.com/Saghen/blink.cmp/commit/1749e32c524dc1815fe4abbad0b33439316c4596)) +* apply keymap on InsertEnter ([340370d](https://github.com/Saghen/blink.cmp/commit/340370d526996b99ff75c1858294f15502af0179)), closes [#37](https://github.com/Saghen/blink.cmp/issues/37) +* **ci:** support windows pre-built binaries ([#100](https://github.com/Saghen/blink.cmp/issues/100)) ([b378d50](https://github.com/Saghen/blink.cmp/commit/b378d5022743e56dc450ab1b6a75ab03de36f86b)) +* disable auto_show documentation by default, use <C-space> to toggle ([84361bd](https://github.com/Saghen/blink.cmp/commit/84361bdbd9e9ab2a7c06b0f458c2829cef46348d)) +* don't search forward when guessing text edit ([a7e1acc](https://github.com/Saghen/blink.cmp/commit/a7e1acc1ed9b0ad004af124bcb6c7d71a7eb5378)), closes [#58](https://github.com/Saghen/blink.cmp/issues/58) +* drop source groups in favor of fallback_for ([#83](https://github.com/Saghen/blink.cmp/issues/83)) ([1f0c0f3](https://github.com/Saghen/blink.cmp/commit/1f0c0f349488f5138757abec2d327ac6c143a4f0)) +* expose source provider config to sources ([deba523](https://github.com/Saghen/blink.cmp/commit/deba523406f45eb0a227c33d57a8d75a79abb4cf)) +* ignore repeated call at cursor position in trigger ([4883420](https://github.com/Saghen/blink.cmp/commit/48834207c143f5e84d7a71cd250b2049ec0a6d8c)) +* implement auto-insert option ([#65](https://github.com/Saghen/blink.cmp/issues/65)) ([1df7d33](https://github.com/Saghen/blink.cmp/commit/1df7d33e930c042dc91287a02a97e1ccf8a92d5d)) +* make fuzzy secondary min_score more lenient ([b330b61](https://github.com/Saghen/blink.cmp/commit/b330b61ffac753be3f1257eda92e1596c0ab3174)) +* stylize markdown in documentation window ([05229dd](https://github.com/Saghen/blink.cmp/commit/05229ddc2fd1695c979e2807aa96842978dd4779)) +* use faster shallow_copy for context ([98575f0](https://github.com/Saghen/blink.cmp/commit/98575f054db18bc763100b8d14a9eae0417209d5)) + +### Bug Fixes + +* accept replacing first char in line ([655d2ee](https://github.com/Saghen/blink.cmp/commit/655d2ee2673950451a491294fd3ce7e17cfb0a24)), closes [#38](https://github.com/Saghen/blink.cmp/issues/38) +* add union to utils ([88f71b1](https://github.com/Saghen/blink.cmp/commit/88f71b16ecd650775516bd2b30ab808283b7242c)) +* autocomplete positioning on first char in line ([7afb06c](https://github.com/Saghen/blink.cmp/commit/7afb06ca9962e3670b5ed01e7301709a53917edd)) +* autocompletion window components alignment ([#51](https://github.com/Saghen/blink.cmp/issues/51)) ([a4f5f8e](https://github.com/Saghen/blink.cmp/commit/a4f5f8eef9182515050d94d54f4c2bb97767987b)) +* binary symlink in flake only working on Linux ([#93](https://github.com/Saghen/blink.cmp/issues/93)) ([fc5feb8](https://github.com/Saghen/blink.cmp/commit/fc5feb887f3f379fff0756b2be2a35c8aa841a44)) +* check if LSP supports resolve provider ([957a57a](https://github.com/Saghen/blink.cmp/commit/957a57a9d3d90c1a9974b9af66f4a9a1f80fdb5f)), closes [#48](https://github.com/Saghen/blink.cmp/issues/48) +* close completion if the accepted item matches the current word ([2f1b85b](https://github.com/Saghen/blink.cmp/commit/2f1b85bc4f15e2f3660550ef92161a93482f2fd8)), closes [#41](https://github.com/Saghen/blink.cmp/issues/41) +* close completion window on ctrl+c in insert mode ([#63](https://github.com/Saghen/blink.cmp/issues/63)) ([e695c79](https://github.com/Saghen/blink.cmp/commit/e695c798b2d53a429f2f3ba1551a21ae2c4dc11a)) +* **config:** make blink lua config fields optional ([#18](https://github.com/Saghen/blink.cmp/issues/18)) ([9c73b0d](https://github.com/Saghen/blink.cmp/commit/9c73b0dc8c158c7162092258177ff8a03aa2919b)) +* context not clearing on trigger character, path regexes ([15cb871](https://github.com/Saghen/blink.cmp/commit/15cb871d1f8c52050a0fcd07d31115f3a63cf20c)), closes [#16](https://github.com/Saghen/blink.cmp/issues/16) +* correctly handle non-blink keymaps with string rhs ([#78](https://github.com/Saghen/blink.cmp/issues/78)) ([1ad59aa](https://github.com/Saghen/blink.cmp/commit/1ad59aa6ab142c19508ee6ed222b73a3ffd13521)) +* disable blink.cmp remaps when telescope prompt is open ([#104](https://github.com/Saghen/blink.cmp/issues/104)) ([7f2f74f](https://github.com/Saghen/blink.cmp/commit/7f2f74fe037ccad1a573e3d42f114d0d23b954d8)), closes [#102](https://github.com/Saghen/blink.cmp/issues/102) +* documentation manual trigger not updating on scroll ([cd15078](https://github.com/Saghen/blink.cmp/commit/cd15078763946522dddbb818803e99b2b321e742)) +* documentation of snippet cannot be shown when description is list ([#92](https://github.com/Saghen/blink.cmp/issues/92)) ([f99bf6b](https://github.com/Saghen/blink.cmp/commit/f99bf6bdabadc2b47fd7355ae2af912a30b9c3cc)) +* don't initialize first_fill with 1 ([#87](https://github.com/Saghen/blink.cmp/issues/87)) ([526f786](https://github.com/Saghen/blink.cmp/commit/526f786a8658f99dff36013b4e31d1f7e6b0a56b)) +* double send on append on trigger character ([ebbce90](https://github.com/Saghen/blink.cmp/commit/ebbce90400ea1ed3e14fdec88fdef59c0185ad46)), closes [#25](https://github.com/Saghen/blink.cmp/issues/25) +* enable kind auto brackets for TS/JS ([808f628](https://github.com/Saghen/blink.cmp/commit/808f628713ae78665511be42b2c054c92208a00e)) +* expand vars in snippets for insertText ([ce337cb](https://github.com/Saghen/blink.cmp/commit/ce337cb95f2172070c0e9333e3439eb20ae4c72a)), closes [#27](https://github.com/Saghen/blink.cmp/issues/27) +* **ffi:** handle cargo library naming conventions for windows binaries ([#74](https://github.com/Saghen/blink.cmp/issues/74)) ([e9493c6](https://github.com/Saghen/blink.cmp/commit/e9493c6aa4942da7e3a62c118195ea07df815dc2)) +* guess text edit once and for all ([fc348da](https://github.com/Saghen/blink.cmp/commit/fc348dac16f190042d20aee62ea61b66c7c1380a)) +* handle empty table in additionalTextEdits ([#99](https://github.com/Saghen/blink.cmp/issues/99)) ([65e9605](https://github.com/Saghen/blink.cmp/commit/65e9605924ff774fb3612441a1d18737b5c9f58a)) +* handle newlines in autocomplete suggestions ([#110](https://github.com/Saghen/blink.cmp/issues/110)) ([c39227a](https://github.com/Saghen/blink.cmp/commit/c39227adfaf66939b6a319bb1ed43d9ade5bbd9b)) +* passthrough bind on show/hide when shown/hidden ([a5145ae](https://github.com/Saghen/blink.cmp/commit/a5145ae69ef2d4193574a09fcd50bea20481f516)), closes [#49](https://github.com/Saghen/blink.cmp/issues/49) +* re-enable preselect by default ([64673ea](https://github.com/Saghen/blink.cmp/commit/64673ea454f46664ac6f6545f5d3577fd27421e9)) +* replace keycodes on callback alternate mappings ([df5c0de](https://github.com/Saghen/blink.cmp/commit/df5c0de57b443545d4fe04cff9cd97ca3d20bbbf)), closes [#47](https://github.com/Saghen/blink.cmp/issues/47) +* respect autocomplete min_width ([#86](https://github.com/Saghen/blink.cmp/issues/86)) ([c15aefe](https://github.com/Saghen/blink.cmp/commit/c15aefeea77345b21ed79cb9defc322ae19f7eda)) +* signature window failing when trigger context empty ([6a21d7c](https://github.com/Saghen/blink.cmp/commit/6a21d7c12d7186313e0dea2c04d0dd63b6534115)) +* snippet keymaps not applying in insert ([a89ae20](https://github.com/Saghen/blink.cmp/commit/a89ae200840de7661eb92d8ae202b279ecc56da9)), closes [#70](https://github.com/Saghen/blink.cmp/issues/70) +* snippet source markdown generation ([a6cf72a](https://github.com/Saghen/blink.cmp/commit/a6cf72ae58362c126f91993326b5c8b43366eb7f)) +* snippets source expanding vars ([5ffd608](https://github.com/Saghen/blink.cmp/commit/5ffd608dc4cd4df8fcfe43b366c5960f05056e45)) +* strip blink fields from lsp items for resolve ([ab99b02](https://github.com/Saghen/blink.cmp/commit/ab99b02f4b5c378c7c79e5d24954d00450e78f1b)) +* union_keys not using pairs ([8c2cb2e](https://github.com/Saghen/blink.cmp/commit/8c2cb2efb63411499f6746ae7be34e2b0a581bad)) +* update ffi.lua ([27903be](https://github.com/Saghen/blink.cmp/commit/27903bef41bc745c4d5419e86ca5bf09ed538f2b)) +* use correct prev/next keymap ([#53](https://github.com/Saghen/blink.cmp/issues/53)) ([f456c2a](https://github.com/Saghen/blink.cmp/commit/f456c2aa0994f709f9aec991ed2b4b705f787e48)), closes [/github.com/Saghen/blink.cmp/pull/23#issuecomment-2399876619](https://github.com/Saghen//github.com/Saghen/blink.cmp/pull/23/issues/issuecomment-2399876619) +* use empty separator for joining snippet description ([28f3a31](https://github.com/Saghen/blink.cmp/commit/28f3a316de01fc4a14c67689b0547428499e933d)) +* use internal CompletionItemKind table ([4daf96d](https://github.com/Saghen/blink.cmp/commit/4daf96d76e06d6c248587f860ddb5717ced9bbd3)), closes [#17](https://github.com/Saghen/blink.cmp/issues/17) + +## [0.2.1](https://github.com/Saghen/blink.cmp/compare/v0.2.0...v0.2.1) (2024-10-08) + +### Features + +* cycle completions ([#12](https://github.com/Saghen/blink.cmp/issues/12)) ([d20e34d](https://github.com/Saghen/blink.cmp/commit/d20e34d8c87925bd27dff12961588459f649cd92)) + +### Bug Fixes + +* autocomplete window positioning with borders ([ba62bda](https://github.com/Saghen/blink.cmp/commit/ba62bda5af9b5f2a8accb102eb4791fab94e2a90)), closes [#29](https://github.com/Saghen/blink.cmp/issues/29) +* check server capabilities ([#5](https://github.com/Saghen/blink.cmp/issues/5)) ([8d2615d](https://github.com/Saghen/blink.cmp/commit/8d2615d00a9892647a6d0e0e564b781a4e6afabe)) +* keymaps not replacing keycodes ([5dd7d66](https://github.com/Saghen/blink.cmp/commit/5dd7d667228e3a98d01146db7c4461f42644d0c1)) +* keymaps replacing buffer local bindings ([506ea74](https://github.com/Saghen/blink.cmp/commit/506ea74e53a825cc6efd40a46c4129576409e440)), closes [#39](https://github.com/Saghen/blink.cmp/issues/39) +* use buffer-local keymaps ([ecb3510](https://github.com/Saghen/blink.cmp/commit/ecb3510ef2132956fb2df3dcc927e0f84d1a1c1d)), closes [#20](https://github.com/Saghen/blink.cmp/issues/20) +* use correct prev/next keymap for k and j ([#23](https://github.com/Saghen/blink.cmp/issues/23)) ([43e7532](https://github.com/Saghen/blink.cmp/commit/43e753228fe4a722e29d4953cad74a61728183cb)) + +## [0.2.0](https://github.com/Saghen/blink.cmp/compare/v0.1.0...v0.2.0) (2024-10-07) + +### Features + +* blink cmp specific winhighlights and highlights ([a034865](https://github.com/Saghen/blink.cmp/commit/a034865d585800503a61995a850ecb622a3d36cc)) +* check for brackets in front of item ([2c6ee0d](https://github.com/Saghen/blink.cmp/commit/2c6ee0d5fa32e286255a4ca119ff74713676bf60)) +* custom drawing support ([3e55028](https://github.com/Saghen/blink.cmp/commit/3e550286534e68cff42f96747e58db0610f7b4b5)) +* customizable undo point ([876707f](https://github.com/Saghen/blink.cmp/commit/876707f214e7ca0875e05eae45b876396b6c33fb)) +* introduce customizable winhighlight for autocomplete and documentation windows ([1a9cb7a](https://github.com/Saghen/blink.cmp/commit/1a9cb7ac70a912689ab09b96c6f9e75c888faed6)) +* support keyword_length on sources ([77080a5](https://github.com/Saghen/blink.cmp/commit/77080a529f88064e0ff04ef69b08fbe7445bcd0d)) + +### Bug Fixes + +* autocomplete window placement ([4e9d7ca](https://github.com/Saghen/blink.cmp/commit/4e9d7ca62c83c3ff2d64e925aab4dac10266f33b)) +* frecency access scoring ([e736972](https://github.com/Saghen/blink.cmp/commit/e73697265ff9091c9cca3db060be60d8e3962c5e)) +* misc ([5f4db7a](https://github.com/Saghen/blink.cmp/commit/5f4db7a1507dcca3b0f4d4fbeaef1f42262aea8f)) +* path completions ([6a5cf05](https://github.com/Saghen/blink.cmp/commit/6a5cf05c704a42cfbfa3009d3ac8e727637567b8)) +* respect min/max width for autocomplete window rendering ([0843884](https://github.com/Saghen/blink.cmp/commit/08438846b8016a9457c3234f4066655dc62b97a0)) +* signature trigger config ([cf9e4aa](https://github.com/Saghen/blink.cmp/commit/cf9e4aaf778f56d2dda8f43c30cb68762aecc425)) +* signature window showing up after context deleted ([857b336](https://github.com/Saghen/blink.cmp/commit/857b336ccdc5a389564e6e2b58571bc07c5cce32)) +* window placement with border ([d6a81d3](https://github.com/Saghen/blink.cmp/commit/d6a81d320f8880e219a3f937ecea1f78aca680e3)) + +## [0.1.0](https://github.com/Saghen/blink.cmp/compare/1b282880e699be37c3719308d6660a68d9081b14...v0.1.0) (2024-10-05) + +### Features + +* .local/state db location and misc ([bf76a01](https://github.com/Saghen/blink.cmp/commit/bf76a01482f6a3f7e019d0050df73ccf8ad93cf6)) +* accept and auto brackets config ([fd32689](https://github.com/Saghen/blink.cmp/commit/fd32689fbd07b953a54b61cc871f714c9dd004d5)) +* add back to repo ([24422f2](https://github.com/Saghen/blink.cmp/commit/24422f2341acf6ebdf7c9bc798cd81fbf5029d03)) +* add documentation keymaps ([e248579](https://github.com/Saghen/blink.cmp/commit/e248579b5cfe939048b613de7a7cdfcb884cd078)) +* auto brackets support ([7203d51](https://github.com/Saghen/blink.cmp/commit/7203d5195970f300ee5529a8427060ee1db9ae41)) +* basic snippet expansion and text edit support ([451dd9e](https://github.com/Saghen/blink.cmp/commit/451dd9eeaa37f6c5598bf7293b8da2bfdfe9162e)) +* better documentation window positioning ([a7ee523](https://github.com/Saghen/blink.cmp/commit/a7ee523978ba653b6eb4d9bac2af1d70f6e89f7b)) +* complete rework ([1efdc8a](https://github.com/Saghen/blink.cmp/commit/1efdc8a0ff38d3f1ff89acd9b04aa14844f50e42)) +* consolidate context table ([ad9ba28](https://github.com/Saghen/blink.cmp/commit/ad9ba28d0a8c1fda91e24e33d29b8013dd4c760a)) +* drop performance logging ([2974bc0](https://github.com/Saghen/blink.cmp/commit/2974bc0569b2d611ce399a733753c90f6ab61a9d)) +* dynamic cmp and doc window width ([6b78c89](https://github.com/Saghen/blink.cmp/commit/6b78c89276f8a520b4b802c8893f30e0ee7a5c82)) +* enable path source by default ([e7362c0](https://github.com/Saghen/blink.cmp/commit/e7362c0786ae889b738c9f1f34a312d834005d37)) +* hack around LSPs returning filtered items ([b58a382](https://github.com/Saghen/blink.cmp/commit/b58a382640f2ddfe0b07ce70439e935e49e39e36)) +* handle no items in source provider ([82106a4](https://github.com/Saghen/blink.cmp/commit/82106a482e899c27d3fa830aa7f65c020848fc68)) +* immediate fuzzy on keystroke ([1d3d54f](https://github.com/Saghen/blink.cmp/commit/1d3d54f20f2412e33975db88a60c6f2c148e7903)) +* implement snippets without deps ([37dbee4](https://github.com/Saghen/blink.cmp/commit/37dbee453dc2655a0a0e74d9b95ee00c08a8cf32)) +* init flake ([87e0416](https://github.com/Saghen/blink.cmp/commit/87e041699169d4f837c5f430e26756f0c2f76623)) +* initial ([1b28288](https://github.com/Saghen/blink.cmp/commit/1b282880e699be37c3719308d6660a68d9081b14)) +* initial configuration support ([b101fc1](https://github.com/Saghen/blink.cmp/commit/b101fc117c4f161f78c1783391f8719c619d15a5)) +* keymaps in config ([d6bad7b](https://github.com/Saghen/blink.cmp/commit/d6bad7bca485ffe6c254daf1e3d2df581b37eebc)) +* lock position to context start ([6ee55d4](https://github.com/Saghen/blink.cmp/commit/6ee55d4e2d938b138246e6ab11adbb320b19f7e7)) +* maintain window on immediate new context while deleting ([4d1b785](https://github.com/Saghen/blink.cmp/commit/4d1b7854c2d4c373cfdc027074aa305927c5414a)) +* min score on fuzzy results, avoid trimming valid items ([14a014d](https://github.com/Saghen/blink.cmp/commit/14a014dce49e658f5eed32853c9241d7869bc5dd)) +* misc ([e8372ab](https://github.com/Saghen/blink.cmp/commit/e8372abf86861a8fde4612257a4f9586626ad05f)) +* multi-repo setup based on mini.nvim ([15e808b](https://github.com/Saghen/blink.cmp/commit/15e808b70704e5f909305c487cb7fbfd5a95fc46)) +* nerd font variant and misc cleanup ([6571c96](https://github.com/Saghen/blink.cmp/commit/6571c96b3aede5ae4f37b3d4999a4ac374593910)) +* nvim cmp as default highlight ([b93a5e3](https://github.com/Saghen/blink.cmp/commit/b93a5e3476b42fc8f79bdeb6fc0f5e0ca8b4bc68)) +* pre-built binary download support, misc refactors ([b1004ab](https://github.com/Saghen/blink.cmp/commit/b1004ab8c23656a5cb3d20b67bc3e5485f818ade)) +* put context via wrapper ([f5d4dae](https://github.com/Saghen/blink.cmp/commit/f5d4dae67c31c2239805187f6351a4dc99259e26)) +* reenable auto_show for documentation ([f1f7de4](https://github.com/Saghen/blink.cmp/commit/f1f7de496fa653518dea34bfe0446d0babac7d4e)) +* rework path source ([5787816](https://github.com/Saghen/blink.cmp/commit/5787816e5e28d1c61803552008545abc851505eb)) +* rework sources again ([7568de9](https://github.com/Saghen/blink.cmp/commit/7568de938a49a26cebf39369e39211f1c959cd9c)) +* rework sources system ([3ee91b5](https://github.com/Saghen/blink.cmp/commit/3ee91b50e7dfc0340b723ae06c4a06f9c8e1e437)) +* show on insert on trigger character ([a9ff243](https://github.com/Saghen/blink.cmp/commit/a9ff243cf0271904708b8a6ef6bf3150238cbc2d)) +* signature help and misc ([fbfdf29](https://github.com/Saghen/blink.cmp/commit/fbfdf2906ea145f4faaf94865eeb40bb30dd8db2)) +* smarter caching, misc fixes ([3f1c8bd](https://github.com/Saghen/blink.cmp/commit/3f1c8bd81b9499345fa50e3707fa127a58160062)) +* smarter fuzzy, drop logging ([6b09eaa](https://github.com/Saghen/blink.cmp/commit/6b09eaa8f47d9bba971a0fd1e8a9e93263bb69e1)) +* sort _ items last ([210f21f](https://github.com/Saghen/blink.cmp/commit/210f21fe73c150253b3dd1529852522ee47b23d3)) +* source should_show, windowing config/fixes, misc ([3d1c168](https://github.com/Saghen/blink.cmp/commit/3d1c1688c6888df50069d50b07e9641f53394ce0)) +* update flake to reflect merge with mono-repo ([aa80347](https://github.com/Saghen/blink.cmp/commit/aa80347f93fb95df2ca98be2c03116a27c554e04)) +* use naersk to simplify build, remove unused inputs ([5579688](https://github.com/Saghen/blink.cmp/commit/55796882a7354f9dbbea5997285e1cd4e92df905)) +* use remote fzrs for build ([04d5647](https://github.com/Saghen/blink.cmp/commit/04d5647009d74e6c14050d094e9d66cd1ace0b5a)) +* WIP sources rework ([ad347a1](https://github.com/Saghen/blink.cmp/commit/ad347a165d2f7e3030b0fe44261e4624d9826134)) + +### Bug Fixes + +* a lot ([8a599ba](https://github.com/Saghen/blink.cmp/commit/8a599ba6725cc0892f6d0155fbbb4bd51a02c9d5)) +* accept auto brackets ([3927e23](https://github.com/Saghen/blink.cmp/commit/3927e23926ef05fc729b065c771de6ee293a587f)) +* add version to pkg ([8983597](https://github.com/Saghen/blink.cmp/commit/89835978d6f6d820abb398ed01a32fcb69a10232)) +* avoid immediately showing on context change ([632e6ac](https://github.com/Saghen/blink.cmp/commit/632e6ac9f3ca7ad2f78fd5f5c100e9437fccf845)) +* avoid setting filetype for preview for now ([32ef1b9](https://github.com/Saghen/blink.cmp/commit/32ef1b9e79a85e9cccee274171c07034ac1a3fc3)) +* buffer response context ([4650a35](https://github.com/Saghen/blink.cmp/commit/4650a35d058aba78c8c59b0aad44f0a13a08a287)) +* cancel signature help request on hide ([b1fdee5](https://github.com/Saghen/blink.cmp/commit/b1fdee5277aba73791a1c991a51df7ac940d4321)) +* documentation delays ([01d5fd0](https://github.com/Saghen/blink.cmp/commit/01d5fd0fc3863e0cd2c9eb53739a369ed1ca4a4e)) +* keymap and simplify ([0924c8a](https://github.com/Saghen/blink.cmp/commit/0924c8a9d64121677f4ed165d4fe65b3ccb8a3ff)) +* keymaps ([863bad7](https://github.com/Saghen/blink.cmp/commit/863bad7d66d616b6498c7d9ba59249138517689a)) +* lazy.nvim loading ([9115fc2](https://github.com/Saghen/blink.cmp/commit/9115fc2e1dfa64d9ed974d11bc3562e8fdf67449)) +* maintain autocomplete pos when scrolling/resizing ([a720117](https://github.com/Saghen/blink.cmp/commit/a720117e49c47e9c45981b419427f5d636118338)) +* plugin paths ([ae4aeae](https://github.com/Saghen/blink.cmp/commit/ae4aeae0a32fde09ce64b2bb9b056ef2f1f50ad2)) +* proximity and frecency bonus ([7bb4000](https://github.com/Saghen/blink.cmp/commit/7bb40005fcfc713d04db1e33461170bc012344ed)) +* reference correct signature window ([30855cd](https://github.com/Saghen/blink.cmp/commit/30855cde1e9b76c351133411fad15c0f13d1dcd8)) +* remove debug prints ([013dc02](https://github.com/Saghen/blink.cmp/commit/013dc0276677741f7f8c1b436303355b975a0e73)) +* remove references to removed inputs ([d69b4d1](https://github.com/Saghen/blink.cmp/commit/d69b4d1c6866455d443bb9ca9dac45b1235d0757)) +* set pname instead of name ([addf204](https://github.com/Saghen/blink.cmp/commit/addf204b58014cd8a87b9af57e38406b896128ce)) +* snippets items ([d8a593d](https://github.com/Saghen/blink.cmp/commit/d8a593db311d83e5d8cf8db0a5ada01e92e88b16)) +* sources trigger character blocklist ([69d3854](https://github.com/Saghen/blink.cmp/commit/69d38546f166fff074d3e5458b0625653d7e2e91)) +* trigger, docs, so much stuff ([fae11d1](https://github.com/Saghen/blink.cmp/commit/fae11d16bb4efac3b74f84040b1f50776e4d55cb)) +* update package build dir to cmp/fuzzy ([13203e3](https://github.com/Saghen/blink.cmp/commit/13203e3cb0a9196635243d0b47c33a9cb7c1326c)) diff --git a/mut/neovim/pack/plugins/start/blink.cmp/Cargo.lock b/mut/neovim/pack/plugins/start/blink.cmp/Cargo.lock new file mode 100644 index 0000000..2165ecc --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/Cargo.lock @@ -0,0 +1,906 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +dependencies = [ + "serde", +] + +[[package]] +name = "blink-cmp-fuzzy" +version = "0.1.0" +dependencies = [ + "frizbee", + "heed", + "lazy_static", + "mlua", + "regex", + "serde", +] + +[[package]] +name = "bstr" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d6dbb628b8f8555f86d0323c2eb39e3ec81901f4b83e091db8a6a76d316a333" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.94", +] + +[[package]] +name = "doxygen-rs" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "415b6ec780d34dcf624666747194393603d0373b7141eef01d12ee58881507d9" +dependencies = [ + "phf", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "frizbee" +version = "0.1.0" +source = "git+https://github.com/saghen/frizbee#7c2aa4661a43c6f8565c2200a07428bdf675ce1b" +dependencies = [ + "memchr", + "smith_waterman_macro", +] + +[[package]] +name = "heed" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd54745cfacb7b97dee45e8fdb91814b62bccddb481debb7de0f9ee6b7bf5b43" +dependencies = [ + "bitflags", + "byteorder", + "heed-traits", + "heed-types", + "libc", + "lmdb-master-sys", + "once_cell", + "page_size", + "serde", + "synchronoise", + "url", +] + +[[package]] +name = "heed-traits" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3130048d404c57ce5a1ac61a903696e8fcde7e8c2991e9fcfc1f27c3ef74ff" + +[[package]] +name = "heed-types" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c255bdf46e07fb840d120a36dcc81f385140d7191c76a7391672675c01a55d" +dependencies = [ + "bincode", + "byteorder", + "heed-traits", + "serde", + "serde_json", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.94", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + +[[package]] +name = "lmdb-master-sys" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "472c3760e2a8d0f61f322fb36788021bb36d573c502b50fa3e2bcaac3ec326c9" +dependencies = [ + "cc", + "doxygen-rs", + "libc", +] + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mlua" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea43c3ffac2d0798bd7128815212dd78c98316b299b7a902dabef13dc7b6b8d" +dependencies = [ + "bstr", + "either", + "mlua-sys", + "mlua_derive", + "num-traits", + "parking_lot", + "rustc-hash", +] + +[[package]] +name = "mlua-sys" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63a11d485edf0f3f04a508615d36c7d50d299cf61a7ee6d3e2530651e0a31771" +dependencies = [ + "cc", + "cfg-if", + "pkg-config", +] + +[[package]] +name = "mlua_derive" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "870d71c172fcf491c6b5fb4c04160619a2ee3e5a42a1402269c66bcbf1dd4deb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.94", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.94", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc-hash" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.94", +] + +[[package]] +name = "serde_json" +version = "1.0.134" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "smith_waterman_macro" +version = "0.1.0" +source = "git+https://github.com/saghen/frizbee#7c2aa4661a43c6f8565c2200a07428bdf675ce1b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "987bc0be1cdea8b10216bd06e2ca407d40b9543468fafd3ddfb02f36e77f71f3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synchronoise" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dbc01390fc626ce8d1cffe3376ded2b72a11bb70e1c75f404a210e4daa4def2" +dependencies = [ + "crossbeam-queue", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.94", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.94", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.94", + "synstructure", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.94", +] diff --git a/mut/neovim/pack/plugins/start/blink.cmp/Cargo.toml b/mut/neovim/pack/plugins/start/blink.cmp/Cargo.toml new file mode 100644 index 0000000..55aa6ba --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "blink-cmp-fuzzy" +version = "0.1.0" +edition = "2021" + +[lib] +path = "lua/blink/cmp/fuzzy/lib.rs" +crate-type = ["cdylib"] + +[dependencies] +regex = "1.11.1" +lazy_static = "1.5.0" +frizbee = { git = "https://github.com/saghen/frizbee" } +serde = { version = "1.0.216", features = ["derive"] } +heed = "0.21.0" +mlua = { version = "0.10.2", features = ["module", "luajit"] } diff --git a/mut/neovim/pack/plugins/start/blink.cmp/LICENSE b/mut/neovim/pack/plugins/start/blink.cmp/LICENSE new file mode 100644 index 0000000..81d22ed --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Liam Dyer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/mut/neovim/pack/plugins/start/blink.cmp/README.md b/mut/neovim/pack/plugins/start/blink.cmp/README.md new file mode 100644 index 0000000..be1f6d0 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/README.md @@ -0,0 +1,40 @@ +> [!WARNING] +> This plugin is _beta_ quality. Expect breaking changes and many bugs + +# Blink Completion (blink.cmp) + +**blink.cmp** is a completion plugin with support for LSPs and external sources that updates on every keystroke with minimal overhead (0.5-4ms async). It use a [custom SIMD fuzzy searcher](https://github.com/saghen/frizbee) to easily handle >20k items. It provides extensibility via hooks into the trigger, sources and rendering pipeline. Plenty of work has been put into making each stage of the pipeline as intelligent as possible, such as frecency and proximity bonus on fuzzy matching, and this work is on-going. + +<https://github.com/user-attachments/assets/9849e57a-3c2c-49a8-959c-dbb7fef78c80> + +## Features + +- Works out of the box with no additional configuration +- Updates on every keystroke (0.5-4ms async, single core) +- [Typo resistant fuzzy](https://github.com/saghen/frizbee) with frecency and proximity bonus +- Extensive LSP support ([tracker](./docs/development/lsp-tracker.md)) +- Native `vim.snippet` support (including `friendly-snippets`) +- External sources support ([compatibility layer for `nvim-cmp` sources](https://github.com/saghen/blink.compat)) +- Auto-bracket support based on semantic tokens +- Signature help (experimental, opt-in) +- Command line completion +- [Comparison with nvim-cmp](https://cmp.saghen.dev/#compared-to-nvim-cmp) + +## Installation + +Head over to the [documentation website](https://cmp.saghen.dev/installation) for installation instructions and configuration options. + +## Special Thanks + +- [@hrsh7th](https://github.com/hrsh7th/) nvim-cmp used as inspiration and cmp-path/cmp-cmdline implementations modified for path/cmdline sources +- [@garymjr](https://github.com/garymjr) nvim-snippets implementation modified for snippets source +- [@redxtech](https://github.com/redxtech) Help with design and testing +- [@aaditya-sahay](https://github.com/aaditya-sahay) Help with rust, design and testing + +### Contributors + +- [@stefanboca](https://github.com/stefanboca) Author of [blink.compat](https://github.com/saghen/blink.compat) +- [@lopi-py](https://github.com/lopi-py) Contributes to the windowing code +- [@scottmckendry](https://github.com/scottmckendry) Contributes to the CI and prebuilt binaries +- [@balssh](https://github.com/Balssh) + [@konradmalik](https://github.com/konradmalik) Manages nix flake, nixpkg and nixvim +- [@abeldekat](https://github.com/abeldekat) Implemented mini.snippets source diff --git a/mut/neovim/pack/plugins/start/blink.cmp/build.rs b/mut/neovim/pack/plugins/start/blink.cmp/build.rs new file mode 100644 index 0000000..1744be2 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/build.rs @@ -0,0 +1,15 @@ +fn main() { + // delete existing version file created by downloader + let _ = std::fs::remove_file("target/release/version"); + + // get current sha from git + let output = std::process::Command::new("git") + .args(["rev-parse", "HEAD"]) + .output() + .unwrap(); + let sha = String::from_utf8(output.stdout).unwrap(); + + // write to version + std::fs::create_dir_all("target/release").unwrap(); + std::fs::write("target/release/version", sha.trim()).unwrap(); +} diff --git a/mut/neovim/pack/plugins/start/blink.cmp/docs/.gitignore b/mut/neovim/pack/plugins/start/blink.cmp/docs/.gitignore new file mode 100644 index 0000000..5506568 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/docs/.gitignore @@ -0,0 +1,3 @@ +node_modules +.vitepress/cache +.vitepress/dist diff --git a/mut/neovim/pack/plugins/start/blink.cmp/docs/.prettierrc b/mut/neovim/pack/plugins/start/blink.cmp/docs/.prettierrc new file mode 100644 index 0000000..31ba22d --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/docs/.prettierrc @@ -0,0 +1,5 @@ +{ + "semi": false, + "singleQuote": true, + "printWidth": 120 +} diff --git a/mut/neovim/pack/plugins/start/blink.cmp/docs/.vitepress/config.mts b/mut/neovim/pack/plugins/start/blink.cmp/docs/.vitepress/config.mts new file mode 100644 index 0000000..d7fadcc --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/docs/.vitepress/config.mts @@ -0,0 +1,68 @@ +import { defineConfig } from 'vitepress' +import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs' +import taskLists from 'markdown-it-task-lists' +import { execSync } from 'node:child_process' + +const isMain = process.env.IS_RELEASE !== 'true' +const version = execSync('git describe --tags --abbrev=0', { encoding: 'utf-8' }).trim() + +const siteUrl = isMain ? 'https://main.cmp.saghen.dev' : 'https://cmp.saghen.dev' +const otherSiteUrl = isMain ? 'https://cmp.saghen.dev' : 'https://main.cmp.saghen.dev' + +const title = isMain ? 'Main' : version +const otherTitle = isMain ? version : 'Main' + +// https://vitepress.dev/reference/site-config +export default defineConfig({ + title: 'Blink Completion (blink.cmp)', + description: 'Performant, batteries-included completion plugin for Neovim', + sitemap: { hostname: siteUrl }, + head: [['link', { rel: 'icon', href: '/favicon.png' }]], + themeConfig: { + nav: [{ text: `Version: ${title}`, items: [{ text: otherTitle, link: otherSiteUrl }] }], + sidebar: [ + { text: 'Introduction', link: '/' }, + { text: 'Installation', link: '/installation' }, + { text: 'Recipes', link: '/recipes' }, + { + text: 'Configuration', + items: [ + { text: 'General', link: '/configuration/general' }, + { text: 'Appearance', link: '/configuration/appearance' }, + { text: 'Completion', link: '/configuration/completion' }, + { text: 'Fuzzy', link: '/configuration/fuzzy' }, + { text: 'Keymap', link: '/configuration/keymap' }, + { text: 'Signature', link: '/configuration/signature' }, + { text: 'Sources', link: '/configuration/sources' }, + { text: 'Snippets', link: '/configuration/snippets' }, + { text: 'Reference', link: '/configuration/reference' }, + ], + }, + { + text: 'Development', + items: [ + { text: 'Architecture', link: '/development/architecture' }, + { text: 'Writing Sources', link: '/development/writing-sources' }, + { text: 'LSP Tracker', link: '/development/lsp-tracker' }, + ], + }, + ], + + socialLinks: [{ icon: 'github', link: 'https://github.com/saghen/blink.cmp' }], + + search: { + provider: 'local', + }, + }, + + markdown: { + theme: { + light: 'catppuccin-latte', + dark: 'catppuccin-mocha', + }, + config(md) { + md.use(tabsMarkdownPlugin) + md.use(taskLists) + }, + }, +}) diff --git a/mut/neovim/pack/plugins/start/blink.cmp/docs/.vitepress/theme/index.ts b/mut/neovim/pack/plugins/start/blink.cmp/docs/.vitepress/theme/index.ts new file mode 100644 index 0000000..f13e9f9 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/docs/.vitepress/theme/index.ts @@ -0,0 +1,14 @@ +// https://vitepress.dev/guide/custom-theme +import DefaultTheme from 'vitepress/theme' +import type { Theme } from 'vitepress' +import { enhanceAppWithTabs } from 'vitepress-plugin-tabs/client' + +import '@catppuccin/vitepress/theme/mocha/blue.css' +import './style.css' + +export default { + extends: DefaultTheme, + enhanceApp({ app }) { + enhanceAppWithTabs(app) + }, +} satisfies Theme diff --git a/mut/neovim/pack/plugins/start/blink.cmp/docs/.vitepress/theme/style.css b/mut/neovim/pack/plugins/start/blink.cmp/docs/.vitepress/theme/style.css new file mode 100644 index 0000000..05ce570 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/docs/.vitepress/theme/style.css @@ -0,0 +1,14 @@ +/* Wrap text in code blocks */ +code { + white-space: pre-wrap !important; + word-break: break-word !important; +} + +.content-container { + max-width: 800px !important; +} + +.VPBadge > a { + text-decoration: none !important; + color: inherit !important; +} diff --git a/mut/neovim/pack/plugins/start/blink.cmp/docs/configuration/appearance.md b/mut/neovim/pack/plugins/start/blink.cmp/docs/configuration/appearance.md new file mode 100644 index 0000000..9d13913 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/docs/configuration/appearance.md @@ -0,0 +1,29 @@ +# Appearance <Badge type="info"><a href="./reference#appearance">Go to default configuration</a></Badge> + +If you're looking for how to change the appearance of the completion menu, check out the [menu draw configuration](./completion#completion-menu-draw). + +## Highlight groups + +| Group | Default | Description | +| ----- | ------- | ----------- | +| `BlinkCmpMenu` | Pmenu | The completion menu window | +| `BlinkCmpMenuBorder` | Pmenu | The completion menu window border | +| `BlinkCmpMenuSelection` | PmenuSel | The completion menu window selected item | +| `BlinkCmpScrollBarThumb` | PmenuThumb | The scrollbar thumb | +| `BlinkCmpScrollBarGutter` | PmenuSbar | The scrollbar gutter | +| `BlinkCmpLabel` | Pmenu | Label of the completion item | +| `BlinkCmpLabelDeprecated` | NonText | Deprecated label of the completion item | +| `BlinkCmpLabelMatch` | Pmenu | (Currently unused) Label of the completion item when it matches the query | +| `BlinkCmpLabelDetail` | NonText | Label description of the completion item | +| `BlinkCmpLabelDescription` | NonText | Label description of the completion item | +| `BlinkCmpKind` | Special | Kind icon/text of the completion item | +| `BlinkCmpKind<kind>` | Special | Kind icon/text of the completion item | +| `BlinkCmpSource` | NonText | Source of the completion item | +| `BlinkCmpGhostText` | NonText | Preview item with ghost text | +| `BlinkCmpDoc` | NormalFloat | The documentation window | +| `BlinkCmpDocBorder` | NormalFloat | The documentation window border | +| `BlinkCmpDocSeparator` | NormalFloat | The documentation separator between doc and detail | +| `BlinkCmpDocCursorLine` | Visual | The documentation window cursor line | +| `BlinkCmpSignatureHelp` | NormalFloat | The signature help window | +| `BlinkCmpSignatureHelpBorder` | NormalFloat | The signature help window border | +| `BlinkCmpSignatureHelpActiveParameter` | LspSignatureActiveParameter | Active parameter of the signature help | diff --git a/mut/neovim/pack/plugins/start/blink.cmp/docs/configuration/completion.md b/mut/neovim/pack/plugins/start/blink.cmp/docs/configuration/completion.md new file mode 100644 index 0000000..be8ec1a --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/docs/configuration/completion.md @@ -0,0 +1,170 @@ +# Completion + +Blink cmp has *a lot* of configuration options, the following document tries to highlight the ones you'll likely care the most about for each section. For all options, click on the "Go to default configuration" button next to each header. + +## Keyword <Badge type="info"><a href="./reference#completion-keyword">Go to default configuration</a></Badge> + +Controls what the plugin considers to be a keyword, used for fuzzy matching and triggering completions. Most notably, the `range` option controls whether the keyword should match against the text before *and* after the cursor, or just before the cursor. + +:::tabs +== Prefix +<img src="https://github.com/user-attachments/assets/6398e470-58c7-4624-989a-bffe26c7f443" /> +== Full +<img src="https://github.com/user-attachments/assets/3e082492-6a5d-4dba-b4ba-6a1bfca50351" /> +::: + +## Trigger <Badge type="info"><a href="./reference#completion-trigger">Go to default configuration</a></Badge> + +Controls when to request completion items from the sources and show the completion menu. The following options are available, excluding their `show_on` prefix: + +:::tabs +== Keyword +Shows after typing a keyword, typically an alphanumeric character, `-` or `_` + +```lua +completion.trigger.show_on_keyword = true +``` + +<video src="https://github.com/user-attachments/assets/5e8f8f9f-bc6a-4d21-9cce-2e291b6a7de8" muted autoplay loop /> +== Trigger Character + +Shows after typing a trigger character, defined by the sources. For example for Lua or Rust, the LSP will define `.` as a trigger character. + +```lua +completion.trigger.show_on_trigger_character = true +``` + +<video src="https://github.com/user-attachments/assets/b4ee0069-2de8-44e7-b3ca-51b10bc4cb4a" muted autoplay loop /> +== Insert on Trigger Character + +Shows after entering insert mode on top of a trigger character. + +```lua +completion.trigger.show_on_insert_on_trigger_character = true +``` + +<video src="https://github.com/user-attachments/assets/9e7aa3c2-4756-4a5e-a0e8-303d3ae0fda9" muted autoplay loop /> +== Accept on Trigger Character + +Shows after accepting a completion item, where the cursor ends up on top of a trigger character. + +```lua +completion.trigger.show_on_accept_on_trigger_character = true +``` + +TODO: Find a case where this actually fires : ) +::: + +## List <Badge type="info"><a href="./reference#completion-list">Go to default configuration</a></Badge> + +Manages the completion list and its behavior when selecting items. The most commonly changed option is `selection.preselect/auto_insert`, which controls whether the list will automatically select the first item in the list, and whether a "preview" will be inserted on selection. + +:::tabs +== Preselect, Auto Insert (default) +```lua +completion.list.selection = { preselect = true, auto_insert = true } +``` +Selects the first item automatically, and inserts a preview of the item on selection. The `cancel` keymap (default `<C-e>`) will close the menu and undo the preview. + +<video src="https://github.com/user-attachments/assets/ef295526-8332-4ad0-9a2a-e2f6484081b2" muted autoplay loop /> + +== Preselect +```lua +completion.list.selection = { preselect = true, auto_insert = false } +``` +Selects the first item automatically + +<img src="https://github.com/user-attachments/assets/69079ced-43f1-437e-8a45-3cb13f841d61" /> +== Manual +```lua +completion.list.selection = { preselect = false, auto_insert = false } +``` + +No item will be selected by default. You may use the `select_and_accept` keymap command to select the first item and accept it when there's no selection. The `accept` keymap command, on the other hand, will only trigger if an item is selected. + +<video src="https://github.com/user-attachments/assets/09cd9b4b-18b3-456b-bb0a-074ae54e9d77" muted autoplay loop /> +== Manual, Auto Insert +```lua +completion.list.selection = { preselect = false, auto_insert = true } +``` + +Selecting an item will insert a "preview" of the item automatically. You may use the `select_and_accept` keymap command to select the first item and accept it when there's no selection. The `accept` keymap command will only trigger if an item is selected. The `cancel` keymap (default `<C-e>`) will close the menu and undo the preview. + +<video src="https://github.com/user-attachments/assets/4658b61d-1b95-404a-b6b5-3a4afbfb8112" muted autoplay loop /> +::: + +To control the selection behavior per mode, pass a function to `selection.preselect/auto_insert`: + +```lua +completion.list.selection = { + preselect = true, + auto_insert = true, + + -- or a function + preselect = function(ctx) + return ctx.mode ~= 'cmdline' and not require('blink.cmp').snippet_active({ direction = 1 }) + end, + -- auto_insert = function(ctx) return ctx.mode ~= 'cmdline' end, +} +``` + + +## Accept <Badge type="info"><a href="./reference#completion-accept">Go to default configuration</a></Badge> + +Manages the behavior when accepting an item in the completion menu. + +### Auto Brackets + +> [!NOTE] +> Some LSPs may add auto brackets themselves. You may be able to configure this behavior in your LSP client configuration + +LSPs provide a `kind` field for completion items, indicating whether the item is a function, method, variable, etc. The plugin will automatically add brackets for functions/methods and place the cursor inside the brackets. For items not marked as such, the plugin will asynchronously resolve the semantic tokens from the LSP and add brackets if marked as a function. A default list of brackets have been included in the default configuration, but you may add more in the configuration (contributions welcome!). + +If brackets are showing when you don't expect them, try disabling `kind_resolution` or `semantic_token_resolution` for that filetype (`echo &filetype`). If that fixes the issue, please open a PR setting this as the default! + +## Menu <Badge type="info"><a href="./reference#completion-menu">Go to default configuration</a></Badge> + +Manages the appearance of the completion menu. You may prevent the menu from automatically showing by setting `completion.menu.auto_show = false` and manually showing it with the `show` keymap command. + +### Menu Draw <Badge type="info"><a href="./reference#completion-menu-draw">Go to default configuration</a></Badge> + +blink.cmp uses a grid-based layout to render the completion menu. The components, defined in `draw.components[string]`, define `text` and `highlight` functions which are called for each completion item. The `highlight` function will be called only when the item appears on screen, so expensive operations such as Treesitter highlighting may be performed (contributions welcome!, [for example](https://www.reddit.com/r/neovim/comments/1ca4gm2/colorful_cmp_menu_powered_by_treesitter/)). The components may define their min and max width, where `ellipsis = true` (enabled by default), will draw the `…` character when the text is truncated. Setting `width.fill = true` will fill the remaining space, effectively making subsequent components right aligned, with respect to their column. + +Columns effectively allow you to vertically align a set of components. Each column, defined as an array in `draw.columns`, will be rendered for all of the completion items, where the longest rendered row will determine the width of the column. You may define `gap = number` in your column to insert a gap between components. + +For a setup similar to nvim-cmp, use the following config: + +```lua +completion.menu.draw.columns = { { "label", "label_description", gap = 1 }, { "kind_icon", "kind" } }, +``` + +### Treesitter + +You may use treesitter to highlight the label text for the given list of sources. This feature is experimental, contributions welcome! + +```lua +completion.menu.draw.treesitter = { 'lsp' } +``` + +## Documentation <Badge type="info"><a href="./reference#completion-documentation">Go to default configuration</a></Badge> + +By default, the documentation window will only show when triggered by the `show_documentation` keymap command. However, you may add the following configuration to show the documentation whenever an item is selected. + +```lua +completion.documentation = { + auto_show = true, + auto_show_delay_ms = 500, +} +``` + +If you're noticing high CPU usage or stuttering when opening the documentation, you may try setting `completion.documentation.treesitter_highlighting = false`. + +## Ghost Text <Badge type="info"><a href="./reference#completion-ghost-text">Go to default configuration</a></Badge> + +Ghost text shows a preview of the currently selected item as virtual text inline. You may want to try setting `completion.menu.auto_show = false` and enabling ghost text, or you may use both in parallel. + +```lua +completion.ghost_text.enabled = true +``` + +<img src="https://github.com/user-attachments/assets/1d30ef90-3ba4-43ca-a1a6-faa70f830e17" /> diff --git a/mut/neovim/pack/plugins/start/blink.cmp/docs/configuration/fuzzy.md b/mut/neovim/pack/plugins/start/blink.cmp/docs/configuration/fuzzy.md new file mode 100644 index 0000000..7ef1a5b --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/docs/configuration/fuzzy.md @@ -0,0 +1,30 @@ +# Fuzzy + +Blink uses a SIMD fuzzy matcher called [frizbee](https://github.com/saghen/frizbee) which achieves ~6x the performance of fzf while ignoring typos. Check out the repo for more information! + +## Installation + +### Prebuilt binaries (default on a release tag) + +By default, Blink will download a prebuilt binary from the latest release, when you're on a release tag (via `version = '*'` on `lazy.nvim` for example). If you're not on a release tag, you may force a specific version via `fuzzy.prebuilt_binaries.force_version`. See [the latest release](https://github.com/saghen/blink.cmp/releases/latest) for supported systems. See `prebuilt_binaries` section of the [reference configuration](./reference.md#prebuilt-binaries) for more options. + +You may instead install the prebuilt binaries manually by downloading the appropriate binary from the [latest release](https://github.com/saghen/blink.cmp/releases/latest) and placing it at `$data/lazy/blink.cmp/target/release/libblink_cmp_fuzzy.$ext`. Get the `$data` path via `:echo stdpath('data')`. Use `.so` for linux, `.dylib` for mac, and `.dll` for windows. If you're unsure whether you want `-musl` or `-gnu` for linux, you very likely want `-gnu`. + +```sh +# Linux +~/.local/share/nvim/lazy/blink.cmp/target/release/libblink_cmp_fuzzy.so + +# Mac +~/.local/share/nvim/lazy/blink.cmp/target/release/libblink_cmp_fuzzy.dylib + +# Windows +~/Appdata/Local/nvim/lazy/blink.cmp/target/release/libblink_cmp_fuzzy.dll +``` + +### Build from source (recommended for `main`) + +When on `main`, it's highly recommended to build from source via `cargo build --release` (via `build = '...'` on `lazy.nvim` for example). This requires a nightly rust toolchain, which will be automatically downloaded when using `rustup`. + +## Configuration + +See the [fuzzy section of the reference configuration](./reference.md#fuzzy) diff --git a/mut/neovim/pack/plugins/start/blink.cmp/docs/configuration/general.md b/mut/neovim/pack/plugins/start/blink.cmp/docs/configuration/general.md new file mode 100644 index 0000000..5dbe2d7 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/docs/configuration/general.md @@ -0,0 +1,66 @@ +# General + +Blink cmp has *a lot* of configuration options, the following code block highlights some changes you're most likely to care about. For more information, check out the additional pages. + +For more common configurations, see the [recipes](../recipes.md). + +> [!IMPORTANT] Do not copy this entire configuration! It contains only non-default options + +```lua +{ + -- Disable for some filetypes + enabled = function() + return not vim.tbl_contains({ "lua", "markdown" }, vim.bo.filetype) + and vim.bo.buftype ~= "prompt" + and vim.b.completion ~= false + end, + + completion = { + -- 'prefix' will fuzzy match on the text before the cursor + -- 'full' will fuzzy match on the text before *and* after the cursor + -- example: 'foo_|_bar' will match 'foo_' for 'prefix' and 'foo__bar' for 'full' + keyword = { range = 'full' }, + + -- Disable auto brackets + -- NOTE: some LSPs may add auto brackets themselves anyway + accept = { auto_brackets = { enabled = false }, }, + + -- Don't select by default, auto insert on selection + list = { selection = { preselect = false, auto_insert = true } }, + -- or set either per mode via a function + list = { selection = { preselect = function(ctx) return ctx.mode ~= 'cmdline' end } }, + + menu = { + -- Don't automatically show the completion menu + auto_show = false, + + -- nvim-cmp style menu + draw = { + columns = { + { "label", "label_description", gap = 1 }, + { "kind_icon", "kind" } + }, + } + }, + + -- Show documentation when selecting a completion item + documentation = { auto_show = true, auto_show_delay_ms = 500 }, + + -- Display a preview of the selected item on the current line + ghost_text = { enabled = true }, + }, + + sources = { + -- Remove 'buffer' if you don't want text completions, by default it's only enabled when LSP returns no items + default = { 'lsp', 'path', 'snippets', 'buffer' }, + -- Disable cmdline completions + cmdline = {}, + }, + + -- Use a preset for snippets, check the snippets documentation for more information + snippets = { preset = 'default' | 'luasnip' | 'mini_snippets' }, + + -- Experimental signature help support + signature = { enabled = true } +} +``` diff --git a/mut/neovim/pack/plugins/start/blink.cmp/docs/configuration/keymap.md b/mut/neovim/pack/plugins/start/blink.cmp/docs/configuration/keymap.md new file mode 100644 index 0000000..d82c5b8 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/docs/configuration/keymap.md @@ -0,0 +1,145 @@ +# Keymap + +Blink uses a special schema for defining keymaps since it needs to handle falling back to other mappings. However, there's nothing stopping you from using `require('blink.cmp')` and implementing these keymaps yourself. + +Your custom key mappings are merged with a `preset` and any conflicting keys will overwrite the preset mappings. The `fallback` command will run the next non blink keymap. + +## Example + +Each keymap may be a list of commands and/or functions, where commands map directly to `require('blink.cmp')[command]()`. If the command/function returns `false` or `nil`, the next command/function will be run. + +```lua +keymap = { + -- set to 'none' to disable the 'default' preset + preset = 'default', + + ['<Up>'] = { 'select_prev', 'fallback' }, + ['<Down>'] = { 'select_next', 'fallback' }, + + -- disable a keymap from the preset + ['<C-e>'] = {}, + + -- show with a list of providers + ['<C-space>'] = { function(cmp) cmp.show({ providers = { 'snippets' } }) end }, + + -- control whether the next command will be run when using a function + ['<C-n>'] = { + function(cmp) + if some_condition then return end -- runs the next command + return true -- doesn't run the next command + end, + 'select_next' + }, + + -- optionally, separate cmdline keymaps + -- cmdline = {} +} +``` + +## Commands + +- `show`: Shows the completion menu + - Optionally use `function(cmp) cmp.show({ providers = { 'snippets' } }) end` to show with a specific list of providers +- `hide`: Hides the completion menu +- `cancel`: Reverts `completion.list.selection.auto_insert` and hides the completion menu +- `accept`: Accepts the currently selected item + - Optionally pass an index to select a specific item in the list: `function(cmp) cmp.accept({ index = 1 }) end` + - Optionally pass a `callback` to run after the item is accepted: `function(cmp) cmp.accept({ callback = function() vim.api.nvim_feedkeys('\n', 'n', true) end }) end` +- `select_and_accept`: Accepts the currently selected item, or the first item if none are selected +- `select_prev`: Selects the previous item, cycling to the bottom of the list if at the top, if `completion.list.cycle.from_top == true` + - Optionally control the `auto_insert` property of `completion.list.selection`: `function(cmp) cmp.select_prev({ auto_insert = false }) end` +- `select_next`: Selects the next item, cycling to the top of the list if at the bottom, if `completion.list.cycle.from_bottom == true` + - Optionally control the `auto_insert` property of `completion.list.selection`: `function(cmp) cmp.select_next({ auto_insert = false }) end` +- `show_documentation`: Shows the documentation for the currently selected item +- `hide_documentation`: Hides the documentation +- `scroll_documentation_up`: Scrolls the documentation up by 4 lines + - Optionally use `function(cmp) cmp.scroll_documentation_up(4) end` to scroll by a specific number of lines +- `scroll_documentation_down`: Scrolls the documentation down by 4 lines + - Optionally use `function(cmp) cmp.scroll_documentation_down(4) end` to scroll by a specific number of lines +- `snippet_forward`: Jumps to the next snippet placeholder +- `snippet_backward`: Jumps to the previous snippet placeholder +- `fallback`: Runs the next non-blink keymap, or runs the built-in neovim binding + +## Cmdline + +You may set a separate keymap for cmdline by defining `keymap.cmdline`, with an identical structure to `keymap`. + +```lua +keymap = { + preset = 'default', + ... + cmdline = { + preset = 'enter', + ... + } +} +``` + +## Presets + +Set the preset to `none` to disable the presets + +### `default` + +```lua +['<C-space>'] = { 'show', 'show_documentation', 'hide_documentation' }, +['<C-e>'] = { 'hide' }, +['<C-y>'] = { 'select_and_accept' }, + +['<C-p>'] = { 'select_prev', 'fallback' }, +['<C-n>'] = { 'select_next', 'fallback' }, + +['<C-b>'] = { 'scroll_documentation_up', 'fallback' }, +['<C-f>'] = { 'scroll_documentation_down', 'fallback' }, + +['<Tab>'] = { 'snippet_forward', 'fallback' }, +['<S-Tab>'] = { 'snippet_backward', 'fallback' }, +``` + +### `super-tab` + +You may want to set `completion.trigger.show_in_snippet = false` or use `completion.list.selection.preselect = function(ctx) return not require('blink.cmp').snippet_active({ direction = 1 }) end`. See more info in: https://cmp.saghen.dev/configuration/completion.html#list + +```lua +['<C-space>'] = { 'show', 'show_documentation', 'hide_documentation' }, +['<C-e>'] = { 'hide', 'fallback' }, + +['<Tab>'] = { + function(cmp) + if cmp.snippet_active() then return cmp.accept() + else return cmp.select_and_accept() end + end, + 'snippet_forward', + 'fallback' +}, +['<S-Tab>'] = { 'snippet_backward', 'fallback' }, + +['<Up>'] = { 'select_prev', 'fallback' }, +['<Down>'] = { 'select_next', 'fallback' }, +['<C-p>'] = { 'select_prev', 'fallback' }, +['<C-n>'] = { 'select_next', 'fallback' }, + +['<C-b>'] = { 'scroll_documentation_up', 'fallback' }, +['<C-f>'] = { 'scroll_documentation_down', 'fallback' }, +``` + +### `enter` + +You may want to set `completion.list.selection.preselect = false`. See more info in: https://cmp.saghen.dev/configuration/completion.html#list + +```lua +['<C-space>'] = { 'show', 'show_documentation', 'hide_documentation' }, +['<C-e>'] = { 'hide', 'fallback' }, +['<CR>'] = { 'accept', 'fallback' }, + +['<Tab>'] = { 'snippet_forward', 'fallback' }, +['<S-Tab>'] = { 'snippet_backward', 'fallback' }, + +['<Up>'] = { 'select_prev', 'fallback' }, +['<Down>'] = { 'select_next', 'fallback' }, +['<C-p>'] = { 'select_prev', 'fallback' }, +['<C-n>'] = { 'select_next', 'fallback' }, + +['<C-b>'] = { 'scroll_documentation_up', 'fallback' }, +['<C-f>'] = { 'scroll_documentation_down', 'fallback' }, +``` diff --git a/mut/neovim/pack/plugins/start/blink.cmp/docs/configuration/reference.md b/mut/neovim/pack/plugins/start/blink.cmp/docs/configuration/reference.md new file mode 100644 index 0000000..cc916af --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/docs/configuration/reference.md @@ -0,0 +1,547 @@ +# Reference + +> [!IMPORTANT] +> Do not copy the default configuration! Only include options you want to change in your configuration +```lua +-- Enables keymaps, completions and signature help when true +enabled = function() return vim.bo.buftype ~= "prompt" and vim.b.completion ~= false end, + +-- See the "keymap" page for more information +keymap = 'default', +``` + +## Snippets + +```lua +snippets = { + -- Function to use when expanding LSP provided snippets + expand = function(snippet) vim.snippet.expand(snippet) end, + -- Function to use when checking if a snippet is active + active = function(filter) return vim.snippet.active(filter) end, + -- Function to use when jumping between tab stops in a snippet, where direction can be negative or positive + jump = function(direction) vim.snippet.jump(direction) end, +} +``` + +## Completion + +### Completion Keyword + +```lua +completion.keyword = { + -- 'prefix' will fuzzy match on the text before the cursor + -- 'full' will fuzzy match on the text before *and* after the cursor + -- example: 'foo_|_bar' will match 'foo_' for 'prefix' and 'foo__bar' for 'full' + range = 'prefix', +} +``` + +### Completion Trigger + +```lua +completion.trigger = { + -- When true, will prefetch the completion items when entering insert mode + prefetch_on_insert = false, + + -- When false, will not show the completion window automatically when in a snippet + show_in_snippet = true, + + -- When true, will show the completion window after typing any of alphanumerics, `-` or `_` + show_on_keyword = true, + + -- When true, will show the completion window after typing a trigger character + show_on_trigger_character = true, + + -- LSPs can indicate when to show the completion window via trigger characters + -- however, some LSPs (i.e. tsserver) return characters that would essentially + -- always show the window. We block these by default. + show_on_blocked_trigger_characters = function() + if vim.api.nvim_get_mode().mode == 'c' then return {} end + + -- you can also block per filetype, for example: + -- if vim.bo.filetype == 'markdown' then + -- return { ' ', '\n', '\t', '.', '/', '(', '[' } + -- end + + return { ' ', '\n', '\t' } + end, + + -- When both this and show_on_trigger_character are true, will show the completion window + -- when the cursor comes after a trigger character after accepting an item + show_on_accept_on_trigger_character = true, + + -- When both this and show_on_trigger_character are true, will show the completion window + -- when the cursor comes after a trigger character when entering insert mode + show_on_insert_on_trigger_character = true, + + -- List of trigger characters (on top of `show_on_blocked_trigger_characters`) that won't trigger + -- the completion window when the cursor comes after a trigger character when + -- entering insert mode/accepting an item + show_on_x_blocked_trigger_characters = { "'", '"', '(' }, + -- or a function, similar to show_on_blocked_trigger_character +} +``` + +### Completion List + +```lua +completion.list = { + -- Maximum number of items to display + max_items = 200, + + selection = { + -- When `true`, will automatically select the first item in the completion list + preselect = true, + -- preselect = function(ctx) return ctx.mode ~= 'cmdline' end, + + -- When `true`, inserts the completion item automatically when selecting it + -- You may want to bind a key to the `cancel` command (default <C-e>) when using this option, + -- which will both undo the selection and hide the completion menu + auto_insert = true, + -- auto_insert = function(ctx) return ctx.mode ~= 'cmdline' end + }, + + cycle = { + -- When `true`, calling `select_next` at the *bottom* of the completion list + -- will select the *first* completion item. + from_bottom = true, + -- When `true`, calling `select_prev` at the *top* of the completion list + -- will select the *last* completion item. + from_top = true, + }, +}, +``` + +### Completion Accept + +```lua +completion.accept = { + -- Create an undo point when accepting a completion item + create_undo_point = true, + -- Experimental auto-brackets support + auto_brackets = { + -- Whether to auto-insert brackets for functions + enabled = true, + -- Default brackets to use for unknown languages + default_brackets = { '(', ')' }, + -- Overrides the default blocked filetypes + override_brackets_for_filetypes = {}, + -- Synchronously use the kind of the item to determine if brackets should be added + kind_resolution = { + enabled = true, + blocked_filetypes = { 'typescriptreact', 'javascriptreact', 'vue' }, + }, + -- Asynchronously use semantic token to determine if brackets should be added + semantic_token_resolution = { + enabled = true, + blocked_filetypes = { 'java' }, + -- How long to wait for semantic tokens to return before assuming no brackets should be added + timeout_ms = 400, + }, + }, +}, +``` + +### Completion Menu + +```lua +completion.menu = { + enabled = true, + min_width = 15, + max_height = 10, + border = 'none', + winblend = 0, + winhighlight = 'Normal:BlinkCmpMenu,FloatBorder:BlinkCmpMenuBorder,CursorLine:BlinkCmpMenuSelection,Search:None', + -- Keep the cursor X lines away from the top/bottom of the window + scrolloff = 2, + -- Note that the gutter will be disabled when border ~= 'none' + scrollbar = true, + -- Which directions to show the window, + -- falling back to the next direction when there's not enough space + direction_priority = { 's', 'n' }, + + -- Whether to automatically show the window when new completion items are available + auto_show = true, + + -- Screen coordinates of the command line + cmdline_position = function() + if vim.g.ui_cmdline_pos ~= nil then + local pos = vim.g.ui_cmdline_pos -- (1, 0)-indexed + return { pos[1] - 1, pos[2] } + end + local height = (vim.o.cmdheight == 0) and 1 or vim.o.cmdheight + return { vim.o.lines - height, 0 } + end, +} +``` + +### Completion Menu Draw + +```lua +-- Controls how the completion items are rendered on the popup window +completion.menu.draw = { + -- Aligns the keyword you've typed to a component in the menu + align_to = 'label', -- or 'none' to disable, or 'cursor' to align to the cursor + -- Left and right padding, optionally { left, right } for different padding on each side + padding = 1, + -- Gap between columns + gap = 1, + -- Use treesitter to highlight the label text for the given list of sources + treesitter = {}, + -- treesitter = { 'lsp' } + + -- Components to render, grouped by column + columns = { { 'kind_icon' }, { 'label', 'label_description', gap = 1 } }, + + -- Definitions for possible components to render. Each defines: + -- ellipsis: whether to add an ellipsis when truncating the text + -- width: control the min, max and fill behavior of the component + -- text function: will be called for each item + -- highlight function: will be called only when the line appears on screen + components = { + kind_icon = { + ellipsis = false, + text = function(ctx) return ctx.kind_icon .. ctx.icon_gap end, + highlight = function(ctx) + return require('blink.cmp.completion.windows.render.tailwind').get_hl(ctx) or 'BlinkCmpKind' .. ctx.kind + end, + }, + + kind = { + ellipsis = false, + width = { fill = true }, + text = function(ctx) return ctx.kind end, + highlight = function(ctx) + return require('blink.cmp.completion.windows.render.tailwind').get_hl(ctx) or 'BlinkCmpKind' .. ctx.kind + end, + }, + + label = { + width = { fill = true, max = 60 }, + text = function(ctx) return ctx.label .. ctx.label_detail end, + highlight = function(ctx) + -- label and label details + local highlights = { + { 0, #ctx.label, group = ctx.deprecated and 'BlinkCmpLabelDeprecated' or 'BlinkCmpLabel' }, + } + if ctx.label_detail then + table.insert(highlights, { #ctx.label, #ctx.label + #ctx.label_detail, group = 'BlinkCmpLabelDetail' }) + end + + -- characters matched on the label by the fuzzy matcher + for _, idx in ipairs(ctx.label_matched_indices) do + table.insert(highlights, { idx, idx + 1, group = 'BlinkCmpLabelMatch' }) + end + + return highlights + end, + }, + + label_description = { + width = { max = 30 }, + text = function(ctx) return ctx.label_description end, + highlight = 'BlinkCmpLabelDescription', + }, + + source_name = { + width = { max = 30 }, + text = function(ctx) return ctx.source_name end, + highlight = 'BlinkCmpSource', + }, + }, +}, +``` + +### Completion Documentation + +```lua +completion.documentation = { + -- Controls whether the documentation window will automatically show when selecting a completion item + auto_show = false, + -- Delay before showing the documentation window + auto_show_delay_ms = 500, + -- Delay before updating the documentation window when selecting a new item, + -- while an existing item is still visible + update_delay_ms = 50, + -- Whether to use treesitter highlighting, disable if you run into performance issues + treesitter_highlighting = true, + window = { + min_width = 10, + max_width = 80, + max_height = 20, + border = 'padded', + winblend = 0, + winhighlight = 'Normal:BlinkCmpDoc,FloatBorder:BlinkCmpDocBorder,EndOfBuffer:BlinkCmpDoc', + -- Note that the gutter will be disabled when border ~= 'none' + scrollbar = true, + -- Which directions to show the documentation window, + -- for each of the possible menu window directions, + -- falling back to the next direction when there's not enough space + direction_priority = { + menu_north = { 'e', 'w', 'n', 's' }, + menu_south = { 'e', 'w', 's', 'n' }, + }, + }, +} +``` + +### Completion Ghost Text + +```lua +-- Displays a preview of the selected item on the current line +completion.ghost_text = { + enabled = false, +}, +``` + +## Signature + +```lua +-- Experimental signature help support +signature = { + enabled = false, + trigger = { + blocked_trigger_characters = {}, + blocked_retrigger_characters = {}, + -- When true, will show the signature help window when the cursor comes after a trigger character when entering insert mode + show_on_insert_on_trigger_character = true, + }, + window = { + min_width = 1, + max_width = 100, + max_height = 10, + border = 'padded', + winblend = 0, + winhighlight = 'Normal:BlinkCmpSignatureHelp,FloatBorder:BlinkCmpSignatureHelpBorder', + scrollbar = false, -- Note that the gutter will be disabled when border ~= 'none' + -- Which directions to show the window, + -- falling back to the next direction when there's not enough space, + -- or another window is in the way + direction_priority = { 'n', 's' }, + -- Disable if you run into performance issues + treesitter_highlighting = true, + }, +} +``` + +## Fuzzy + +```lua +fuzzy = { + -- When enabled, allows for a number of typos relative to the length of the query + -- Disabling this matches the behavior of fzf + use_typo_resistance = true, + -- Frecency tracks the most recently/frequently used items and boosts the score of the item + use_frecency = true, + -- Proximity bonus boosts the score of items matching nearby words + use_proximity = true, + -- UNSAFE!! When enabled, disables the lock and fsync when writing to the frecency database. This should only be used on unsupported platforms (i.e. alpine termux) + use_unsafe_no_lock = false, + -- Controls which sorts to use and in which order, falling back to the next sort if the first one returns nil + -- You may pass a function instead of a string to customize the sorting + sorts = { 'score', 'sort_text' }, + + prebuilt_binaries = { + -- Whether or not to automatically download a prebuilt binary from github. If this is set to `false` + -- you will need to manually build the fuzzy binary dependencies by running `cargo build --release` + download = true, + -- Ignores mismatched version between the built binary and the current git sha, when building locally + ignore_version_mismatch = false, + -- 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 fuzzy matcher 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 fuzzy matcher changes while tracking main then this may result in blink breaking. + force_system_triple = nil, + -- Extra arguments that will be passed to curl like { 'curl', ..extra_curl_args, ..built_in_args } + extra_curl_args = {} + }, +} +``` + +## Sources + +```lua +sources = { + -- Static list of providers to enable, or a function to dynamically enable/disable providers based on the context + default = { 'lsp', 'path', 'snippets', 'buffer' }, + + -- You may also define providers per filetype + per_filetype = { + -- lua = { 'lsp', 'path' }, + }, + + -- By default, we choose providers for the cmdline based on the current cmdtype + -- You may disable cmdline completions by replacing this with an empty table + cmdline = function() + local type = vim.fn.getcmdtype() + -- Search forward and backward + if type == '/' or type == '?' then return { 'buffer' } end + -- Commands + if type == ':' or type == '@' then return { 'cmdline' } end + return {} + end, + + -- Function to use when transforming the items before they're returned for all providers + -- The default will lower the score for snippets to sort them lower in the list + transform_items = function(_, items) return items end, + + -- Minimum number of characters in the keyword to trigger all providers + -- May also be `function(ctx: blink.cmp.Context): number` + min_keyword_length = 0, +} +``` + +### Providers + +```lua +-- Please see https://github.com/Saghen/blink.compat for using `nvim-cmp` sources +sources.providers = { + lsp = { + name = 'LSP', + module = 'blink.cmp.sources.lsp', + fallbacks = { 'buffer' }, + -- Filter text items from the LSP provider, since we have the buffer provider for that + transform_items = function(_, items) + for _, item in ipairs(items) do + if item.kind == require('blink.cmp.types').CompletionItemKind.Snippet then + item.score_offset = item.score_offset - 3 + end + end + + return vim.tbl_filter( + function(item) return item.kind ~= require('blink.cmp.types').CompletionItemKind.Text end, + items + ) + end, + + --- NOTE: All of these options may be functions to get dynamic behavior + --- See the type definitions for more information + enabled = true, -- Whether or not to enable the provider + async = false, -- Whether we should wait for the provider to return before showing the completions + timeout_ms = 2000, -- How long to wait for the provider to return before showing completions and treating it as asynchronous + transform_items = nil, -- Function to transform the items before they're returned + should_show_items = true, -- Whether or not to show the items + max_items = nil, -- Maximum number of items to display in the menu + min_keyword_length = 0, -- Minimum number of characters in the keyword to trigger the provider + -- If this provider returns 0 items, it will fallback to these providers. + -- If multiple providers falback to the same provider, all of the providers must return 0 items for it to fallback + fallbacks = {}, + score_offset = 0, -- Boost/penalize the score of the items + override = nil, -- Override the source's functions + }, + + path = { + name = 'Path', + module = 'blink.cmp.sources.path', + score_offset = 3, + fallbacks = { 'buffer' }, + opts = { + trailing_slash = true, + label_trailing_slash = true, + get_cwd = function(context) return vim.fn.expand(('#%d:p:h'):format(context.bufnr)) end, + show_hidden_files_by_default = false, + } + }, + + snippets = { + name = 'Snippets', + module = 'blink.cmp.sources.snippets', + + -- For `snippets.preset == 'default'` + opts = { + friendly_snippets = true, + search_paths = { vim.fn.stdpath('config') .. '/snippets' }, + global_snippets = { 'all' }, + extended_filetypes = {}, + ignored_filetypes = {}, + get_filetype = function(context) + return vim.bo.filetype + end + -- Set to '+' to use the system clipboard, or '"' to use the unnamed register + clipboard_register = nil, + } + + -- For `snippets.preset == 'luasnip'` + opts = { + -- Whether to use show_condition for filtering snippets + use_show_condition = true, + -- Whether to show autosnippets in the completion list + show_autosnippets = true, + } + + -- For `snippets.preset == 'mini_snippets'` + opts = { + -- Whether to use a cache for completion items + use_items_cache = true, + } + }, + + buffer = { + name = 'Buffer', + module = 'blink.cmp.sources.buffer', + opts = { + -- default to all visible buffers + get_bufnrs = function() + return vim + .iter(vim.api.nvim_list_wins()) + :map(function(win) return vim.api.nvim_win_get_buf(win) end) + :filter(function(buf) return vim.bo[buf].buftype ~= 'nofile' end) + :totable() + end, + } + }, +} +``` + +## Appearance + +```lua +appearance = { + highlight_ns = vim.api.nvim_create_namespace('blink_cmp'), + -- Sets the fallback highlight groups to nvim-cmp's highlight groups + -- Useful for when your theme doesn't support blink.cmp + -- Will be removed in a future release + use_nvim_cmp_as_default = false, + -- Set to 'mono' for 'Nerd Font Mono' or 'normal' for 'Nerd Font' + -- Adjusts spacing to ensure icons are aligned + nerd_font_variant = 'mono', + kind_icons = { + Text = '', + Method = '', + Function = '', + Constructor = '', + + Field = '', + Variable = '', + Property = '', + + Class = '', + Interface = '', + Struct = '', + Module = '', + + Unit = '', + Value = '', + Enum = '', + EnumMember = '', + + Keyword = '', + Constant = '', + + Snippet = '', + Color = '', + File = '', + Reference = '', + Folder = '', + Event = '', + Operator = '', + TypeParameter = '', + }, +} +``` diff --git a/mut/neovim/pack/plugins/start/blink.cmp/docs/configuration/signature.md b/mut/neovim/pack/plugins/start/blink.cmp/docs/configuration/signature.md new file mode 100644 index 0000000..4fc4d8f --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/docs/configuration/signature.md @@ -0,0 +1,13 @@ +# Signature <Badge type="info"><a href="./reference#signature">Go to default configuration</a></Badge> + +> [!IMPORTANT] +> This feature is *experimental*, contributions welcome! + +Blink supports signature help, automatically triggered when typing trigger characters, defined by the LSP, such as `(` for `lua`. The menu will be updated when pressing a retrigger character, such as `,`. Due to it being experimental, this feature is opt-in. + +```lua +signature = { enabled = true } +``` + +<img src="https://github.com/user-attachments/assets/9ab576c8-2a04-465f-88c0-9c130fef146c" /> + diff --git a/mut/neovim/pack/plugins/start/blink.cmp/docs/configuration/snippets.md b/mut/neovim/pack/plugins/start/blink.cmp/docs/configuration/snippets.md new file mode 100644 index 0000000..918bae0 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/docs/configuration/snippets.md @@ -0,0 +1,116 @@ +# Snippets <Badge type="info"><a href="./reference#snippets">Go to default configuration</a></Badge> + +Blink uses the `vim.snippet` API by default for expanding and navigating snippets. The built-in `snippets` source will load [friendly-snippets](https://github.com/rafamadriz/friendly-snippets), if available, and load any snippets found at `~/.config/nvim/snippets/`. For use with Luasnip, see the [Luasnip section](#luasnip). + +## Custom snippets + +By default, the `snippets` source will check `~/.config/nvim/snippets` for your custom snippets, but you may add additional folders via `sources.providers.snippets.opts.search_paths`. Currently, only VSCode style snippets are supported, but you may look into [Luasnip](https://github.com/L3MON4D3/LuaSnip) if you'd like more advanced functionality. + +[Chris Grieser](https://github.com/chrisgrieser) has made a great introduction to writing custom snippets [in the nvim-scissors repo](https://github.com/chrisgrieser/nvim-scissors?tab=readme-ov-file#cookbook--faq). Here's an example, using the linux/mac path for the neovim configuration: + +```jsonc +// ~/.config/nvim/snippets/package.json +{ + "name": "personal-snippets", + "contributes": { + "snippets": [ + { "language": "lua", "path": "./lua.json" } + ] + } +} +``` + +```jsonc +// ~/.config/nvim/snippets/lua.json +{ + "foo": { + "prefix": "foo", + "body": [ + "local ${1:foo} = ${2:bar}", + "return ${3:baz}" + ], + } +} +``` + +## Luasnip + +```lua +{ + 'saghen/blink.cmp', + version = '*', + -- !Important! Make sure you're using the latest release of LuaSnip + -- `main` does not work at the moment + dependencies = { 'L3MON4D3/LuaSnip', version = 'v2.*' }, + opts = { + snippets = { preset = 'luasnip' }, + -- ensure you have the `snippets` source (enabled by default) + sources = { + default = { 'lsp', 'path', 'snippets', 'buffer' }, + }, + } +} +``` + +## `mini.snippets` + +```lua +{ + 'saghen/blink.cmp', + dependencies = 'echasnovski/mini.snippets', + opts = { + snippets = { preset = 'mini_snippets' }, + -- ensure you have the `snippets` source (enabled by default) + sources = { + default = { 'lsp', 'path', 'snippets', 'buffer' }, + }, + } +} +``` + +## Disable all snippets + +```lua +sources.transform_items = function(_, items) + return vim.tbl_filter(function(item) + return item.kind ~= require('blink.cmp.types').CompletionItemKind.Snippet + end, items) +end +``` + +When setting up your capabilities with `lspconfig`, add the following: + +```lua +capabilities = require('blink.cmp').get_lsp_capabilities({ + textDocument = { completion = { completionItem = { snippetSupport = false } } }, +}) +``` + +Some LSPs may ignore the `snippetSupport` field, in which case, you need to set LSP specific options while setting them up. Some examples: + +```lua +-- If you're using `opts = { ['rust-analyzer'] = { } }` in your lspconfig configuration, simply put these options in there instead + +-- For `rust-analyzer` +lspconfig['rust-analyzer'].setup({ + completion = { + capable = { + snippets = 'add_parenthesis' + } + } +}) + +-- For `lua_ls` +lspconfig.lua_ls.setup({ + settings = { + Lua = { + completion = { + callSnippet = 'Disable', + keywordSnippet = 'Disable', + } + } + } +}) +``` + +Please open a PR if you know of any other LSPs that require special configuration! diff --git a/mut/neovim/pack/plugins/start/blink.cmp/docs/configuration/sources.md b/mut/neovim/pack/plugins/start/blink.cmp/docs/configuration/sources.md new file mode 100644 index 0000000..c19a2a8 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/docs/configuration/sources.md @@ -0,0 +1,65 @@ +# Sources <Badge type="info"><a href="./reference#sources">Go to default configuration</a></Badge> + +> [!NOTE] +> Check out the [recipes](../recipes.md) for some common configurations + +Blink provides a sources interface, modelled after LSPs, for getting completion items, trigger characters, documentation and signature help. The `lsp`, `path`, `snippets`, `luasnip` and `buffer` sources are built-in. You may add additional [community sources](#community-sources) as well. Check out [writing sources](../development/writing-sources.md) to learn how to write your own! + +## Providers + +Sources are configured via the `sources.providers` table, where each `id` (`key`) must have a `name` and `module` field. The `id` (`key`) may be used in the `sources.default/per_filetype/cmdline` to enable the source. + +```lua +sources = { + default = { 'lsp' }, + providers = { + lsp = { + name = 'LSP', + module = 'blink.cmp.sources.lsp', + } + } +} +``` + +### Provider options + +All of the fields shown below apply to all sources. The `opts` field is passed to the source directly, and will vary by source. + +```lua +sources.providers.lsp = { + name = 'LSP', + module = 'blink.cmp.sources.lsp', + opts = {} -- Passed to the source directly, varies by source + + --- NOTE: All of these options may be functions to get dynamic behavior + --- See the type definitions for more information + enabled = true, -- Whether or not to enable the provider + async = false, -- Whether we should wait for the provider to return before showing the completions + timeout_ms = 2000, -- How long to wait for the provider to return before showing completions and treating it as asynchronous + transform_items = nil, -- Function to transform the items before they're returned + should_show_items = true, -- Whether or not to show the items + max_items = nil, -- Maximum number of items to display in the menu + min_keyword_length = 0, -- Minimum number of characters in the keyword to trigger the provider + -- If this provider returns 0 items, it will fallback to these providers. + -- If multiple providers falback to the same provider, all of the providers must return 0 items for it to fallback + fallbacks = {}, + score_offset = 0, -- Boost/penalize the score of the items + override = nil, -- Override the source's functions +} +``` + +## Using `nvim-cmp` sources + +Blink can use `nvim-cmp` sources through a compatibility layer developed by [stefanboca](https://github.com/stefanboca): [blink.compat](https://github.com/Saghen/blink.compat). Please open any issues with `blink.compat` in that repo + +## Community sources + +- [lazydev](https://github.com/folke/lazydev.nvim) +- [vim-dadbod-completion](https://github.com/kristijanhusak/vim-dadbod-completion) +- [blink-ripgrep](https://github.com/mikavilpas/blink-ripgrep.nvim) +- [blink-cmp-ripgrep](https://github.com/niuiic/blink-cmp-rg.nvim) +- [blink-cmp-ctags](https://github.com/netmute/blink-cmp-ctags) +- [blink-cmp-copilot](https://github.com/giuxtaposition/blink-cmp-copilot) +- [minuet-ai.nvim](https://github.com/milanglacier/minuet-ai.nvim) +- [blink-emoji.nvim](https://github.com/moyiz/blink-emoji.nvim) +- [blink-cmp-dictionary](https://github.com/Kaiser-Yang/blink-cmp-dictionary) diff --git a/mut/neovim/pack/plugins/start/blink.cmp/docs/development/architecture.md b/mut/neovim/pack/plugins/start/blink.cmp/docs/development/architecture.md new file mode 100644 index 0000000..9904897 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/docs/development/architecture.md @@ -0,0 +1,9 @@ +# Architecture + +The plugin use a 4 stage pipeline: trigger -> sources -> fuzzy -> render +1. **Trigger:** Controls when to request completion items from the sources and provides a context downstream with the current query (i.e. `hello.wo|`, the query would be `wo`) and the treesitter object under the cursor (i.e. for intelligently enabling/disabling sources). It respects trigger characters passed by the LSP (or any other source) and includes it in the context for sending to the LSP. +2. **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` +3. **Fuzzy:** Rust <-> Lua FFI 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) + - **Sorting:** Combines fuzzy matching score with frecency and proximity bonus. Each completion item may also include a `score_offset` which will be added to this score to demote certain sources. The `snippets` source takes advantage of this to avoid taking precedence over the LSP source. The parameters here still need to be tuned, so please let me know if you find some magical parameters! +4. **Windows:** Responsible for placing the menu, documentation and function parameters windows. All of the rendering can be overridden following a syntax similar to incline.nvim. It uses the neovim window decoration provider to provide next to no overhead from highlighting. diff --git a/mut/neovim/pack/plugins/start/blink.cmp/docs/development/lsp-tracker.md b/mut/neovim/pack/plugins/start/blink.cmp/docs/development/lsp-tracker.md new file mode 100644 index 0000000..5f69417 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/docs/development/lsp-tracker.md @@ -0,0 +1,69 @@ +# LSP Support Tracker + +## Completion Items + +- [x] `completionItem/resolve` + +### Client Capabilities + +- [ ] `dynamicRegistration` +- [x] `CompletionItem` + - [x] `snippetSupport` + - [ ] `commitCharacterSupport` + - [x] `documentationFormat` + - [x] `deprecatedSupport` + - [ ] `preselectSupport` + - [x] `tagSupport` + - [ ] `insertReplaceSupport` + - [x] `resolveSupport` + - [x] `insertTextModeSupport` + - [x] `labelDetailsSupport` +- [x] `completionItemKind` +- [x] `contextSupport` + +### Server Capabilities + +- [x] `triggerCharacters` +- [ ] `allCommitCharacters` +- [x] `resolveProvider` +- [x] `CompletionItem` + - [x] `labelDetailsSupport` + +### Request Params + +- [x] `CompletionContext` + - [x] `triggerKind` + - [x] `triggerCharacter` + +### List + +- [x] `isIncomplete` +- [x] `itemDefaults` + - [x] `commitCharacters` + - [x] `editRange` + - [x] `insertTextFormat` + - [x] `insertTextMode` + - [x] `data` +- [x] `items` + +### Item + +- [x] `label` +- [x] `labelDetails` +- [x] `kind` +- [x] `tags` +- [x] `detail` +- [x] `documentation` <- both string and markup content +- [x] `deprecated` +- [ ] `preselect` +- [x] `sortText` +- [x] `filterText` +- [x] `insertText` +- [x] `insertTextFormat` <- regular or snippet +- [ ] `insertTextMode` <- asIs only, not sure we'll support adjustIndentation +- [x] `textEdit` +- [x] `textEditText` +- [x] `additionalTextEdits` <- known issue where applying the main text edit will cause this to be wrong if the additional text edit comes after since the indices will be offset +- [ ] `commitCharacters` +- [ ] `command` +- [x] `data` <- Don't think there's anything special to do here diff --git a/mut/neovim/pack/plugins/start/blink.cmp/docs/development/writing-sources.md b/mut/neovim/pack/plugins/start/blink.cmp/docs/development/writing-sources.md new file mode 100644 index 0000000..81048e9 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/docs/development/writing-sources.md @@ -0,0 +1,3 @@ +# Writing Sources + +TODO diff --git a/mut/neovim/pack/plugins/start/blink.cmp/docs/index.md b/mut/neovim/pack/plugins/start/blink.cmp/docs/index.md new file mode 100644 index 0000000..5f9b6e4 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/docs/index.md @@ -0,0 +1,47 @@ +# Blink Completion (blink.cmp) + +> [!IMPORTANT] +> This plugin is *beta* quality. Expect breaking changes and many bugs + +**blink.cmp** is a completion plugin with support for LSPs and external sources that updates on every keystroke with minimal overhead (0.5-4ms async). It use a [custom SIMD fuzzy searcher](https://github.com/saghen/frizbee) to easily handle >20k items. It provides extensibility via hooks into the trigger, sources and rendering pipeline. Plenty of work has been put into making each stage of the pipeline as intelligent as possible, such as frecency and proximity bonus on fuzzy matching, and this work is on-going. + +<video controls autoplay muted src="https://github.com/user-attachments/assets/9849e57a-3c2c-49a8-959c-dbb7fef78c80"></video> + +## Features + +- Works out of the box with no additional configuration +- Updates on every keystroke (0.5-4ms async, single core) +- [Typo resistant fuzzy](https://github.com/saghen/frizbee) with frecency and proximity bonus +- Extensive LSP support ([tracker](./development/lsp-tracker.md)) +- Native `vim.snippet` support (including `friendly-snippets`) +- External sources support ([compatibility layer for `nvim-cmp` sources](https://github.com/Saghen/blink.compat)) +- Auto-bracket support based on semantic tokens +- Signature help (experimental, opt-in) + +## Special Thanks + +- [@hrsh7th](https://github.com/hrsh7th/) nvim-cmp used as inspiration and cmp-path/cmp-cmdline implementations modified for path/cmdline sources +- [@garymjr](https://github.com/garymjr) nvim-snippets implementation modified for snippets source +- [@redxtech](https://github.com/redxtech) Help with design and testing +- [@aaditya-sahay](https://github.com/aaditya-sahay) Help with rust, design and testing + +### Contributors + +- [@stefanboca](https://github.com/stefanboca) Author of [blink.compat](https://github.com/saghen/blink.compat) +- [@lopi-py](https://github.com/lopi-py) Contributes to the windowing code +- [@scottmckendry](https://github.com/scottmckendry) Contributes to the CI and prebuilt binaries +- [@balssh](https://github.com/Balssh) + [@konradmalik](https://github.com/konradmalik) Manages nix flake, nixpkg and nixvim + +## Compared to nvim-cmp + +- Avoids the complexity of nvim-cmp's configuration by providing sensible defaults +- Updates on every keystroke with 0.5-4ms of overhead, versus nvim-cmp's default debounce of 60ms with 2-50ms hitches from processing + - Setting nvim-cmp's debounce to 0ms leads to visible stuttering. If you'd like to stick with nvim-cmp, try [yioneko's fork](https://github.com/yioneko/nvim-cmp) or the more recent [magazine.nvim](https://github.com/iguanacucumber/magazine.nvim) +- Boosts completion item score via frecency _and_ proximity bonus. nvim-cmp boosts score via proximity bonus and optionally by recency +- Typo-resistant fuzzy matching unlike nvim-cmp's fzf-style fuzzy matching +- Core sources (buffer, snippets, path, lsp) are built-in versus nvim-cmp's exclusively external sources +- Built-in auto bracket and signature help support + +### Planned missing features + +- Significantly more testing and documentation diff --git a/mut/neovim/pack/plugins/start/blink.cmp/docs/installation.md b/mut/neovim/pack/plugins/start/blink.cmp/docs/installation.md new file mode 100644 index 0000000..3a82c6f --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/docs/installation.md @@ -0,0 +1,135 @@ +# Installation + +> [!IMPORTANT] +> Blink uses a prebuilt binary for the fuzzy matcher which will be downloaded automatically when on a tag. +> You may build from source with rust nightly. See the [fuzzy documentation](./configuration/fuzzy.md) for more information. + +## Requirements + +- Neovim 0.10+ +- Using prebuilt binaries: + - curl + - git +- Building from source: + - Rust nightly or [rustup](https://rustup.rs/) + +## `lazy.nvim` + +```lua +{ + 'saghen/blink.cmp', + -- optional: provides snippets for the snippet source + dependencies = 'rafamadriz/friendly-snippets', + + -- use a release tag to download pre-built binaries + version = '*', + -- AND/OR build from source, 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', + + ---@module 'blink.cmp' + ---@type blink.cmp.Config + opts = { + -- 'default' for mappings similar to built-in completion + -- 'super-tab' for mappings similar to vscode (tab to accept, arrow keys to navigate) + -- 'enter' for mappings similar to 'super-tab' but with 'enter' to accept + -- See the full "keymap" documentation for information on defining your own keymap. + keymap = { preset = 'default' }, + + appearance = { + -- Sets the fallback highlight groups to nvim-cmp's highlight groups + -- Useful for when your theme doesn't support blink.cmp + -- Will be removed in a future release + use_nvim_cmp_as_default = true, + -- Set to 'mono' for 'Nerd Font Mono' or 'normal' for 'Nerd Font' + -- Adjusts spacing to ensure icons are aligned + nerd_font_variant = 'mono' + }, + + -- Default list of enabled providers defined so that you can extend it + -- elsewhere in your config, without redefining it, due to `opts_extend` + sources = { + default = { 'lsp', 'path', 'snippets', 'buffer' }, + }, + }, + opts_extend = { "sources.default" } +} +``` + +> [!IMPORTANT] +> On Neovim 0.11+ and Blink.cmp 0.10+, you may skip this step + +Setting capabilities for `nvim-lspconfig`: + +```lua +-- LSP servers and clients communicate which features they support through "capabilities". +-- By default, Neovim supports a subset of the LSP specification. +-- With blink.cmp, Neovim has *more* capabilities which are communicated to the LSP servers. +-- Explanation from TJ: https://youtu.be/m8C0Cq9Uv9o?t=1275 +-- +-- This can vary by config, but in general for nvim-lspconfig: + +{ + 'neovim/nvim-lspconfig', + dependencies = { 'saghen/blink.cmp' }, + + -- example using `opts` for defining servers + opts = { + servers = { + lua_ls = {} + } + }, + config = function(_, opts) + local lspconfig = require('lspconfig') + for server, config in pairs(opts.servers) do + -- passing config.capabilities to blink.cmp merges with the capabilities in your + -- `opts[server].capabilities, if you've defined it + config.capabilities = require('blink.cmp').get_lsp_capabilities(config.capabilities) + lspconfig[server].setup(config) + end + end + + -- example calling setup directly for each LSP + config = function() + local capabilities = require('blink.cmp').get_lsp_capabilities() + local lspconfig = require('lspconfig') + + lspconfig['lua-ls'].setup({ capabilities = capabilities }) + end +} +``` + +## `mini.deps` + +The following section includes only the installation and, optionally, building of the fuzzy matcher. Check the [lazy.nvim](#lazy.nvim) section for recommended configuration options and setting up `nvim-lspconfig`. + +```lua +-- use a release tag to download pre-built binaries +MiniDeps.add({ + source = "saghen/blink.cmp", + depends = { + "rafamadriz/friendly-snippets", + }, + checkout = "some.version", -- check releases for latest tag +}) + +-- OR build from source, requires nightly: https://rust-lang.github.io/rustup/concepts/channels.html#working-with-nightly-rust +local function build_blink(params) + vim.notify('Building blink.cmp', vim.log.levels.INFO) + local obj = vim.system({ 'cargo', 'build', '--release' }, { cwd = params.path }):wait() + if obj.code == 0 then + vim.notify('Building blink.cmp done', vim.log.levels.INFO) + else + vim.notify('Building blink.cmp failed', vim.log.levels.ERROR) + end +end + +MiniDeps.add({ + source = 'Saghen/blink.cmp', + hooks = { + post_install = build_blink, + post_checkout = build_blink, + }, +}) +``` diff --git a/mut/neovim/pack/plugins/start/blink.cmp/docs/package-lock.json b/mut/neovim/pack/plugins/start/blink.cmp/docs/package-lock.json new file mode 100644 index 0000000..9f52631 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/docs/package-lock.json @@ -0,0 +1,2525 @@ +{ + "name": "docs", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "@catppuccin/vitepress": "^0.1.0", + "@types/node": "^22.10.5", + "markdown-it-task-lists": "^2.1.1", + "vitepress": "^1.5.0", + "vitepress-plugin-tabs": "^0.5.0" + } + }, + "node_modules/@algolia/autocomplete-core": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz", + "integrity": "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.17.7", + "@algolia/autocomplete-shared": "1.17.7" + } + }, + "node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz", + "integrity": "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.17.7" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@algolia/autocomplete-preset-algolia": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz", + "integrity": "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.17.7" + }, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-shared": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz", + "integrity": "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/client-abtesting": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.18.0.tgz", + "integrity": "sha512-DLIrAukjsSrdMNNDx1ZTks72o4RH/1kOn8Wx5zZm8nnqFexG+JzY4SANnCNEjnFQPJTTvC+KpgiNW/CP2lumng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.18.0", + "@algolia/requester-browser-xhr": "5.18.0", + "@algolia/requester-fetch": "5.18.0", + "@algolia/requester-node-http": "5.18.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.18.0.tgz", + "integrity": "sha512-0VpGG2uQW+h2aejxbG8VbnMCQ9ary9/ot7OASXi6OjE0SRkYQ/+pkW+q09+IScif3pmsVVYggmlMPtAsmYWHng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.18.0", + "@algolia/requester-browser-xhr": "5.18.0", + "@algolia/requester-fetch": "5.18.0", + "@algolia/requester-node-http": "5.18.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-common": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.18.0.tgz", + "integrity": "sha512-X1WMSC+1ve2qlMsemyTF5bIjwipOT+m99Ng1Tyl36ZjQKTa54oajBKE0BrmM8LD8jGdtukAgkUhFoYOaRbMcmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-insights": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.18.0.tgz", + "integrity": "sha512-FAJRNANUOSs/FgYOJ/Njqp+YTe4TMz2GkeZtfsw1TMiA5mVNRS/nnMpxas9771aJz7KTEWvK9GwqPs0K6RMYWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.18.0", + "@algolia/requester-browser-xhr": "5.18.0", + "@algolia/requester-fetch": "5.18.0", + "@algolia/requester-node-http": "5.18.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.18.0.tgz", + "integrity": "sha512-I2dc94Oiwic3SEbrRp8kvTZtYpJjGtg5y5XnqubgnA15AgX59YIY8frKsFG8SOH1n2rIhUClcuDkxYQNXJLg+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.18.0", + "@algolia/requester-browser-xhr": "5.18.0", + "@algolia/requester-fetch": "5.18.0", + "@algolia/requester-node-http": "5.18.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-query-suggestions": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.18.0.tgz", + "integrity": "sha512-x6XKIQgKFTgK/bMasXhghoEjHhmgoP61pFPb9+TaUJ32aKOGc65b12usiGJ9A84yS73UDkXS452NjyP50Knh/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.18.0", + "@algolia/requester-browser-xhr": "5.18.0", + "@algolia/requester-fetch": "5.18.0", + "@algolia/requester-node-http": "5.18.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-search": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.18.0.tgz", + "integrity": "sha512-qI3LcFsVgtvpsBGR7aNSJYxhsR+Zl46+958ODzg8aCxIcdxiK7QEVLMJMZAR57jGqW0Lg/vrjtuLFDMfSE53qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.18.0", + "@algolia/requester-browser-xhr": "5.18.0", + "@algolia/requester-fetch": "5.18.0", + "@algolia/requester-node-http": "5.18.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/ingestion": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.18.0.tgz", + "integrity": "sha512-bGvJg7HnGGm+XWYMDruZXWgMDPVt4yCbBqq8DM6EoaMBK71SYC4WMfIdJaw+ABqttjBhe6aKNRkWf/bbvYOGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.18.0", + "@algolia/requester-browser-xhr": "5.18.0", + "@algolia/requester-fetch": "5.18.0", + "@algolia/requester-node-http": "5.18.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/monitoring": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.18.0.tgz", + "integrity": "sha512-lBssglINIeGIR+8KyzH05NAgAmn1BCrm5D2T6pMtr/8kbTHvvrm1Zvcltc5dKUQEFyyx3J5+MhNc7kfi8LdjVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.18.0", + "@algolia/requester-browser-xhr": "5.18.0", + "@algolia/requester-fetch": "5.18.0", + "@algolia/requester-node-http": "5.18.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/recommend": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.18.0.tgz", + "integrity": "sha512-uSnkm0cdAuFwdMp4pGT5vHVQ84T6AYpTZ3I0b3k/M3wg4zXDhl3aCiY8NzokEyRLezz/kHLEEcgb/tTTobOYVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.18.0", + "@algolia/requester-browser-xhr": "5.18.0", + "@algolia/requester-fetch": "5.18.0", + "@algolia/requester-node-http": "5.18.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.18.0.tgz", + "integrity": "sha512-1XFjW0C3pV0dS/9zXbV44cKI+QM4ZIz9cpatXpsjRlq6SUCpLID3DZHsXyE6sTb8IhyPaUjk78GEJT8/3hviqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.18.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-fetch": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.18.0.tgz", + "integrity": "sha512-0uodeNdAHz1YbzJh6C5xeQ4T6x5WGiUxUq3GOaT/R4njh5t78dq+Rb187elr7KtnjUmETVVuCvmEYaThfTHzNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.18.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-node-http": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.18.0.tgz", + "integrity": "sha512-tZCqDrqJ2YE2I5ukCQrYN8oiF6u3JIdCxrtKq+eniuLkjkO78TKRnXrVcKZTmfFJyyDK8q47SfDcHzAA3nHi6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.18.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", + "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.3" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", + "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@catppuccin/vitepress": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@catppuccin/vitepress/-/vitepress-0.1.0.tgz", + "integrity": "sha512-HJilKL8dzidGWwDBNcGVzDmjHocwhaxQ0tleRf0/Aab7MDCHK63jzoYHifXro6mH+AtK120WHfsHiJl54sSaww==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": "^5.0.0" + } + }, + "node_modules/@docsearch/css": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.8.2.tgz", + "integrity": "sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@docsearch/js": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.8.2.tgz", + "integrity": "sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docsearch/react": "3.8.2", + "preact": "^10.0.0" + } + }, + "node_modules/@docsearch/react": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.8.2.tgz", + "integrity": "sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-core": "1.17.7", + "@algolia/autocomplete-preset-algolia": "1.17.7", + "@docsearch/css": "3.8.2", + "algoliasearch": "^5.14.2" + }, + "peerDependencies": { + "@types/react": ">= 16.8.0 < 19.0.0", + "react": ">= 16.8.0 < 19.0.0", + "react-dom": ">= 16.8.0 < 19.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@iconify-json/simple-icons": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.16.tgz", + "integrity": "sha512-mnQ0Ih8CDIgOqbi0qz01AJNOeFVuGFRimelg3JmJtD0y5EpZVw+enPPcpcxJKipsRZ/oqhcP3AhYkF1kM7yomg==", + "dev": true, + "license": "CC0-1.0", + "dependencies": { + "@iconify/types": "*" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz", + "integrity": "sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.1.tgz", + "integrity": "sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.1.tgz", + "integrity": "sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.1.tgz", + "integrity": "sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.1.tgz", + "integrity": "sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.1.tgz", + "integrity": "sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.1.tgz", + "integrity": "sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.1.tgz", + "integrity": "sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.1.tgz", + "integrity": "sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.1.tgz", + "integrity": "sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.28.1.tgz", + "integrity": "sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.1.tgz", + "integrity": "sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.1.tgz", + "integrity": "sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.1.tgz", + "integrity": "sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.1.tgz", + "integrity": "sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.1.tgz", + "integrity": "sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.1.tgz", + "integrity": "sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.1.tgz", + "integrity": "sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.1.tgz", + "integrity": "sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/core": { + "version": "1.24.3", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.24.3.tgz", + "integrity": "sha512-VRcf4GYUIkxIchGM9DrapRcxtgojg4IWKUtX5EtW+4PJiGzF2xQqZSv27PJt+WLc18KT3CNLpNWow9JYV5n+Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/engine-javascript": "1.24.3", + "@shikijs/engine-oniguruma": "1.24.3", + "@shikijs/types": "1.24.3", + "@shikijs/vscode-textmate": "^9.3.1", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.4" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "1.24.3", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.24.3.tgz", + "integrity": "sha512-De8tNLvYjeK6V0Gb47jIH2M+OKkw+lWnSV1j3HVDFMlNIglmVcTMG2fASc29W0zuFbfEEwKjO8Fe4KYSO6Ce3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.24.3", + "@shikijs/vscode-textmate": "^9.3.1", + "oniguruma-to-es": "0.8.0" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "1.24.3", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.24.3.tgz", + "integrity": "sha512-iNnx950gs/5Nk+zrp1LuF+S+L7SKEhn8k9eXgFYPGhVshKppsYwRmW8tpmAMvILIMSDfrgqZ0w+3xWVQB//1Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.24.3", + "@shikijs/vscode-textmate": "^9.3.1" + } + }, + "node_modules/@shikijs/transformers": { + "version": "1.24.3", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-1.24.3.tgz", + "integrity": "sha512-Zdu+pVZwQkUy/KWIVJFQlSqZGvPySU6oYZ2TsBKp3Ay5m1XzykPSeiaVvAh6LtyaXYDLeCdA3LjZfHyTXFjGTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "shiki": "1.24.3" + } + }, + "node_modules/@shikijs/types": { + "version": "1.24.3", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.24.3.tgz", + "integrity": "sha512-FPMrJ69MNxhRtldRk69CghvaGlbbN3pKRuvko0zvbfa2dXp4pAngByToqS5OY5jvN8D7LKR4RJE8UvzlCOuViw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^9.3.1", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-9.3.1.tgz", + "integrity": "sha512-79QfK1393x9Ho60QFyLti+QfdJzRQCVLFb97kOIV7Eo9vQU/roINgk7m24uv0a7AUvN//RDH36FLjjK48v0s9g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz", + "integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.1.tgz", + "integrity": "sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.1.tgz", + "integrity": "sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", + "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/shared": "3.5.13", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", + "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", + "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/compiler-core": "3.5.13", + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.11", + "postcss": "^8.4.48", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", + "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.6.8.tgz", + "integrity": "sha512-ma6dY/sZR36zALVsV1W7eC57c6IJPXsy8SNgZn1PLVWU4z4dPn5TIBmnF4stmdJ4sQcixqKaQ8pwjbMPzEZwiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.6.8" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.6.8.tgz", + "integrity": "sha512-JhJ8M3sPU+v0P2iZBF2DkdmR9L0dnT5RXJabJqX6o8KtFs3tebdvfoXV2Dm3BFuqeECuMJIfF1aCzSt+WQ4wrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.6.8", + "birpc": "^0.2.19", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.1" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.6.8.tgz", + "integrity": "sha512-9MBPO5Z3X1nYGFqTJyohl6Gmf/J7UNN1oicHdyzBVZP4jnhZ4c20MgtaHDIzWmHDHCMYVS5bwKxT3jxh7gOOKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", + "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz", + "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", + "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.13", + "@vue/runtime-core": "3.5.13", + "@vue/shared": "3.5.13", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz", + "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13" + }, + "peerDependencies": { + "vue": "3.5.13" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-11.3.0.tgz", + "integrity": "sha512-7OC4Rl1f9G8IT6rUfi9JrKiXy4bfmHhZ5x2Ceojy0jnd3mHNEvV4JaRygH362ror6/NZ+Nl+n13LPzGiPN8cKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "11.3.0", + "@vueuse/shared": "11.3.0", + "vue-demi": ">=0.14.10" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/integrations": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-11.3.0.tgz", + "integrity": "sha512-5fzRl0apQWrDezmobchoiGTkGw238VWESxZHazfhP3RM7pDSiyXy18QbfYkILoYNTd23HPAfQTJpkUc5QbkwTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vueuse/core": "11.3.0", + "@vueuse/shared": "11.3.0", + "vue-demi": ">=0.14.10" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "async-validator": "^4", + "axios": "^1", + "change-case": "^5", + "drauu": "^0.4", + "focus-trap": "^7", + "fuse.js": "^7", + "idb-keyval": "^6", + "jwt-decode": "^4", + "nprogress": "^0.2", + "qrcode": "^1.5", + "sortablejs": "^1", + "universal-cookie": "^7" + }, + "peerDependenciesMeta": { + "async-validator": { + "optional": true + }, + "axios": { + "optional": true + }, + "change-case": { + "optional": true + }, + "drauu": { + "optional": true + }, + "focus-trap": { + "optional": true + }, + "fuse.js": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "jwt-decode": { + "optional": true + }, + "nprogress": { + "optional": true + }, + "qrcode": { + "optional": true + }, + "sortablejs": { + "optional": true + }, + "universal-cookie": { + "optional": true + } + } + }, + "node_modules/@vueuse/integrations/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-11.3.0.tgz", + "integrity": "sha512-pwDnDspTqtTo2HwfLw4Rp6yywuuBdYnPYDq+mO38ZYKGebCUQC/nVj/PXSiK9HX5otxLz8Fn7ECPbjiRz2CC3g==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-11.3.0.tgz", + "integrity": "sha512-P8gSSWQeucH5821ek2mn/ciCk+MS/zoRKqdQIM3bHq6p7GXDAJLmnRRKmF5F65sAVJIfzQlwR3aDzwCn10s8hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "vue-demi": ">=0.14.10" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/algoliasearch": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.18.0.tgz", + "integrity": "sha512-/tfpK2A4FpS0o+S78o3YSdlqXr0MavJIDlFK3XZrlXLy7vaRXJvW5jYg3v5e/wCaF8y0IpMjkYLhoV6QqfpOgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-abtesting": "5.18.0", + "@algolia/client-analytics": "5.18.0", + "@algolia/client-common": "5.18.0", + "@algolia/client-insights": "5.18.0", + "@algolia/client-personalization": "5.18.0", + "@algolia/client-query-suggestions": "5.18.0", + "@algolia/client-search": "5.18.0", + "@algolia/ingestion": "1.18.0", + "@algolia/monitoring": "1.18.0", + "@algolia/recommend": "5.18.0", + "@algolia/requester-browser-xhr": "5.18.0", + "@algolia/requester-fetch": "5.18.0", + "@algolia/requester-node-http": "5.18.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/birpc": { + "version": "0.2.19", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-0.2.19.tgz", + "integrity": "sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/emoji-regex-xs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", + "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/focus-trap": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.6.2.tgz", + "integrity": "sha512-9FhUxK1hVju2+AiQIDJ5Dd//9R2n2RAfJ0qfhF4IHGHgcoEUTMpbTeG/zbEuwaiYXfuAH6XE0/aCyxDdRM+W5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tabbable": "^6.2.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.4.tgz", + "integrity": "sha512-wxQzXtdbhiwGAUKrnQJXlOPmHnEehzphwkK7aluUPQ+lEc1xefC8pblMgpp2w5ldBTEfveRIrADcrhGIWrlTDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-what": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/markdown-it-task-lists": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/markdown-it-task-lists/-/markdown-it-task-lists-2.1.1.tgz", + "integrity": "sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==", + "dev": true, + "license": "ISC" + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.1.tgz", + "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/minisearch": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.1.1.tgz", + "integrity": "sha512-b3YZEYCEH4EdCAtYP7OlDyx7FdPwNzuNwLQ34SfJpM9dlbBZzeXndGavTrC+VCiRWomL21SWfMc6SCKO/U2ZNw==", + "dev": true, + "license": "MIT" + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/oniguruma-to-es": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-0.8.0.tgz", + "integrity": "sha512-rY+/a6b+uCgoYIL9itjY0x99UUDHXmGaw7Jjk5ZvM/3cxDJifyxFr/Zm4tTmF6Tre18gAakJo7AzhKUeMNLgHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex-xs": "^1.0.0", + "regex": "^5.0.2", + "regex-recursion": "^5.0.0" + } + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/preact": { + "version": "10.25.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.25.3.tgz", + "integrity": "sha512-dzQmIFtM970z+fP9ziQ3yG4e3ULIbwZzJ734vaMVUTaKQ2+Ru1Ou/gjshOYVHCcd1rpAelC6ngjvjDXph98unQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/regex": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/regex/-/regex-5.0.2.tgz", + "integrity": "sha512-/pczGbKIQgfTMRV0XjABvc5RzLqQmwqxLHdQao2RTXPk+pmTXB2P0IaUHYdYyk412YLwUIkaeMd5T+RzVgTqnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-5.0.0.tgz", + "integrity": "sha512-UwyOqeobrCCqTXPcsSqH4gDhOjD5cI/b8kjngWgSZbxYh5yVjAwTjO5+hAuPRNiuR70+5RlWSs+U9PVcVcW9Lw==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "dev": true, + "license": "MIT" + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.28.1.tgz", + "integrity": "sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.28.1", + "@rollup/rollup-android-arm64": "4.28.1", + "@rollup/rollup-darwin-arm64": "4.28.1", + "@rollup/rollup-darwin-x64": "4.28.1", + "@rollup/rollup-freebsd-arm64": "4.28.1", + "@rollup/rollup-freebsd-x64": "4.28.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.28.1", + "@rollup/rollup-linux-arm-musleabihf": "4.28.1", + "@rollup/rollup-linux-arm64-gnu": "4.28.1", + "@rollup/rollup-linux-arm64-musl": "4.28.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.28.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.28.1", + "@rollup/rollup-linux-riscv64-gnu": "4.28.1", + "@rollup/rollup-linux-s390x-gnu": "4.28.1", + "@rollup/rollup-linux-x64-gnu": "4.28.1", + "@rollup/rollup-linux-x64-musl": "4.28.1", + "@rollup/rollup-win32-arm64-msvc": "4.28.1", + "@rollup/rollup-win32-ia32-msvc": "4.28.1", + "@rollup/rollup-win32-x64-msvc": "4.28.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/search-insights": { + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/shiki": { + "version": "1.24.3", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.24.3.tgz", + "integrity": "sha512-eMeX/ehE2IDKVs71kB4zVcDHjutNcOtm+yIRuR4sA6ThBbdFI0DffGJiyoKCodj0xRGxIoWC3pk/Anmm5mzHmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/core": "1.24.3", + "@shikijs/engine-javascript": "1.24.3", + "@shikijs/engine-oniguruma": "1.24.3", + "@shikijs/types": "1.24.3", + "@shikijs/vscode-textmate": "^9.3.1", + "@types/hast": "^3.0.4" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/superjson": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz", + "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "copy-anything": "^3.0.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "dev": true, + "license": "MIT" + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/typescript": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "5.4.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", + "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vitepress": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.5.0.tgz", + "integrity": "sha512-q4Q/G2zjvynvizdB3/bupdYkCJe2umSAMv9Ju4d92E6/NXJ59z70xB0q5p/4lpRyAwflDsbwy1mLV9Q5+nlB+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docsearch/css": "^3.6.2", + "@docsearch/js": "^3.6.2", + "@iconify-json/simple-icons": "^1.2.10", + "@shikijs/core": "^1.22.2", + "@shikijs/transformers": "^1.22.2", + "@shikijs/types": "^1.22.2", + "@types/markdown-it": "^14.1.2", + "@vitejs/plugin-vue": "^5.1.4", + "@vue/devtools-api": "^7.5.4", + "@vue/shared": "^3.5.12", + "@vueuse/core": "^11.1.0", + "@vueuse/integrations": "^11.1.0", + "focus-trap": "^7.6.0", + "mark.js": "8.11.1", + "minisearch": "^7.1.0", + "shiki": "^1.22.2", + "vite": "^5.4.10", + "vue": "^3.5.12" + }, + "bin": { + "vitepress": "bin/vitepress.js" + }, + "peerDependencies": { + "markdown-it-mathjax3": "^4", + "postcss": "^8" + }, + "peerDependenciesMeta": { + "markdown-it-mathjax3": { + "optional": true + }, + "postcss": { + "optional": true + } + } + }, + "node_modules/vitepress-plugin-tabs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/vitepress-plugin-tabs/-/vitepress-plugin-tabs-0.5.0.tgz", + "integrity": "sha512-SIhFWwGsUkTByfc2b279ray/E0Jt8vDTsM1LiHxmCOBAEMmvzIBZSuYYT1DpdDTiS3SuJieBheJkYnwCq/yD9A==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "vitepress": "^1.0.0-rc.27", + "vue": "^3.3.8" + } + }, + "node_modules/vue": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz", + "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-sfc": "3.5.13", + "@vue/runtime-dom": "3.5.13", + "@vue/server-renderer": "3.5.13", + "@vue/shared": "3.5.13" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/mut/neovim/pack/plugins/start/blink.cmp/docs/package.json b/mut/neovim/pack/plugins/start/blink.cmp/docs/package.json new file mode 100644 index 0000000..b9e67d5 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/docs/package.json @@ -0,0 +1,15 @@ +{ + "scripts": { + "dev": "vitepress dev", + "build": "vitepress build", + "build:release": "IS_RELEASE=true vitepress build", + "preview": "vitepress preview" + }, + "devDependencies": { + "@catppuccin/vitepress": "^0.1.0", + "@types/node": "^22.10.5", + "markdown-it-task-lists": "^2.1.1", + "vitepress": "^1.5.0", + "vitepress-plugin-tabs": "^0.5.0" + } +} diff --git a/mut/neovim/pack/plugins/start/blink.cmp/docs/public/favicon.png b/mut/neovim/pack/plugins/start/blink.cmp/docs/public/favicon.png Binary files differnew file mode 100644 index 0000000..d40a396 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/docs/public/favicon.png diff --git a/mut/neovim/pack/plugins/start/blink.cmp/docs/recipes.md b/mut/neovim/pack/plugins/start/blink.cmp/docs/recipes.md new file mode 100644 index 0000000..9b5cf3d --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/docs/recipes.md @@ -0,0 +1,241 @@ +# Recipes + +[[toc]] + +## General + +### Disable per filetype + +```lua +enabled = function() + return not vim.tbl_contains({ "lua", "markdown" }, vim.bo.filetype) + and vim.bo.buftype ~= "prompt" + and vim.b.completion ~= false +end, +``` + +### Border + +```lua +completion = { + menu = { border = 'single' }, + documentation = { window = { border = 'single' } }, +}, +signature = { window = { border = 'single' } }, +``` + +### Change selection type per mode + +```lua +completion = { + list = { + selection = { + preselect = function(ctx) return ctx.mode ~= 'cmdline' end, + auto_insert = function(ctx) return ctx.mode ~= 'cmdline' end + } + } +} +``` + +### Don't show completion menu automatically in cmdline mode + +```lua +completion = { + menu = { auto_show = function(ctx) return ctx.mode ~= 'cmdline' end } +} +``` + +### Don't show completion menu automatically when searching + +```lua +completion = { + menu = { + auto_show = function(ctx) + return ctx.mode ~= "cmdline" or not vim.tbl_contains({ '/', '?' }, vim.fn.getcmdtype()) + end, + }, +} +``` + +### Select Nth item from the list + +Here's an example configuration that allows you to select the nth item from the list, based on [#382](https://github.com/Saghen/blink.cmp/issues/382): + +```lua +keymap = { + preset = 'default', + ['<A-1>'] = { function(cmp) cmp.accept({ index = 1 }) end }, + ['<A-2>'] = { function(cmp) cmp.accept({ index = 2 }) end }, + ['<A-3>'] = { function(cmp) cmp.accept({ index = 3 }) end }, + ['<A-4>'] = { function(cmp) cmp.accept({ index = 4 }) end }, + ['<A-5>'] = { function(cmp) cmp.accept({ index = 5 }) end }, + ['<A-6>'] = { function(cmp) cmp.accept({ index = 6 }) end }, + ['<A-7>'] = { function(cmp) cmp.accept({ index = 7 }) end }, + ['<A-8>'] = { function(cmp) cmp.accept({ index = 8 }) end }, + ['<A-9>'] = { function(cmp) cmp.accept({ index = 9 }) end }, + ['<A-0>'] = { function(cmp) cmp.accept({ index = 10 }) end }, +}, +completion = { + menu = { + draw = { + columns = { { 'item_idx' }, { 'kind_icon' }, { 'label', 'label_description', gap = 1 } }, + components = { + item_idx = { + text = function(ctx) return ctx.idx == 10 and '0' or ctx.idx >= 10 and ' ' or tostring(ctx.idx) end, + highlight = 'BlinkCmpItemIdx' -- optional, only if you want to change its color + } + } + } + } +} +``` + +### `mini.icons` + +[Original discussion](https://github.com/Saghen/blink.cmp/discussions/458) + +```lua +completion = { + menu = { + draw = { + components = { + kind_icon = { + ellipsis = false, + text = function(ctx) + local kind_icon, _, _ = require('mini.icons').get('lsp', ctx.kind) + return kind_icon + end, + -- Optionally, you may also use the highlights from mini.icons + highlight = function(ctx) + local _, hl, _ = require('mini.icons').get('lsp', ctx.kind) + return hl + end, + } + } + } + } +} +``` + +### Hide Copilot on suggestion + +```lua +vim.api.nvim_create_autocmd('User', { + pattern = 'BlinkCmpMenuOpen', + callback = function() + require("copilot.suggestion").dismiss() + vim.b.copilot_suggestion_hidden = true + end, +}) + +vim.api.nvim_create_autocmd('User', { + pattern = 'BlinkCmpMenuClose', + callback = function() + vim.b.copilot_suggestion_hidden = false + end, +}) +``` + +### Show on newline, tab and space + +Note that you may want to add the override to other sources as well, since if the LSP doesnt return any items, we won't show the menu if it was triggered by any of these three characters. + +```lua +-- by default, blink.cmp will block newline, tab and space trigger characters, disable that behavior +completion.trigger.blocked_trigger_characters = {} + +-- add newline, tab and space to LSP source trigger characters +sources.providers.lsp.override.get_trigger_characters = function(self) + local trigger_characters = self:get_trigger_characters() + vim.list_extend(trigger_characters, { '\n', '\t', ' ' }) + return trigger_characters +end +``` + +## Sources + +### Dynamically picking providers by treesitter node/filetype + +```lua +sources.default = function(ctx) + local success, node = pcall(vim.treesitter.get_node) + if vim.bo.filetype == 'lua' then + return { 'lsp', 'path' } + elseif success and node and vim.tbl_contains({ 'comment', 'line_comment', 'block_comment' }, node:type()) then + return { 'buffer' } + else + return { 'lsp', 'path', 'snippets', 'buffer' } + end +end +``` + +### Hide snippets after trigger character + +> [!NOTE] +> Untested, might not work well, please open a PR if you find a better solution! + +Trigger characters are defined by the sources. For example, for Lua, the trigger characters are `.`, `"`, `'`. + +```lua +sources.providers.snippets.should_show_items = function(ctx) + return ctx.trigger.initial_kind ~= 'trigger_character' +end +``` + +### Disable all snippets + +See the [relevant section in the snippets documentation](./configuration/snippets.md#disable-all-snippets) + +### Set minimum keyword length by filetype + +```lua +sources.min_keyword_length = function() + return vim.bo.filetype == 'markdown' and 2 or 0 +end +``` + +## For writers + +When writing prose, you may want significantly different behavior than typical LSP completions. If you find any interesting configurations, please open a PR adding it here! + +### Keep first letter capitalization on buffer source + +```lua +sources = { + providers = { + buffer = { + -- keep case of first char + transform_items = function (a, items) + local keyword = a.get_keyword() + local correct, case + if keyword:match('^%l') then + correct = '^%u%l+$' + case = string.lower + elseif keyword:match('^%u') then + correct = '^%l+$' + case = string.upper + else + return items + end + + -- avoid duplicates from the corrections + local seen = {} + local out = {} + for _, item in ipairs(items) do + local raw = item.insertText + if raw:match(correct) then + local text = case(raw:sub(1,1)) .. raw:sub(2) + item.insertText = text + item.label = text + end + if not seen[item.insertText] then + seen[item.insertText] = true + table.insert(out, item) + end + end + return out + end + } + } +} +``` diff --git a/mut/neovim/pack/plugins/start/blink.cmp/flake.lock b/mut/neovim/pack/plugins/start/blink.cmp/flake.lock new file mode 100644 index 0000000..7667448 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/flake.lock @@ -0,0 +1,97 @@ +{ + "nodes": { + "fenix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" + }, + "locked": { + "lastModified": 1734676450, + "narHash": "sha256-iwcxhTVe4h5TqW0HsNiOQP27eMBmbBshF+q2UjEy5aU=", + "owner": "nix-community", + "repo": "fenix", + "rev": "46e19fa0eb3260b2c3ee5b2cf89e73343c1296ab", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1733312601, + "narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1736166416, + "narHash": "sha256-U47xeACNBpkSO6IcCm0XvahsVXpJXzjPIQG7TZlOToU=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "b30f97d8c32d804d2d832ee837d0f1ca0695faa5", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1733096140, + "narHash": "sha256-1qRH7uAUsyQI7R1Uwl4T+XvdNv778H0Nb5njNrqvylY=", + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/5487e69da40cbd611ab2cadee0b4637225f7cfae.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/5487e69da40cbd611ab2cadee0b4637225f7cfae.tar.gz" + } + }, + "root": { + "inputs": { + "fenix": "fenix", + "flake-parts": "flake-parts", + "nixpkgs": "nixpkgs" + } + }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1734622712, + "narHash": "sha256-2Oc2LbFypF1EG3zTVIVcuT5XFJ7R3oAwu2tS8B0qQ0I=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "fe027d79d22f2a7645da4143f5cc0f5f56239b97", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/mut/neovim/pack/plugins/start/blink.cmp/flake.nix b/mut/neovim/pack/plugins/start/blink.cmp/flake.nix new file mode 100644 index 0000000..23e5799 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/flake.nix @@ -0,0 +1,117 @@ +{ + description = "Set of simple, performant neovim plugins"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; + flake-parts.url = "github:hercules-ci/flake-parts"; + fenix.url = "github:nix-community/fenix"; + fenix.inputs.nixpkgs.follows = "nixpkgs"; + }; + + outputs = inputs@{ 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, ... }: { + # use fenix overlay + _module.args.pkgs = import nixpkgs { + inherit system; + overlays = [ inputs.fenix.overlays.default ]; + }; + + # define the packages provided by this flake + packages = let + fs = lib.fileset; + # nix source files (*.nix) + nixFs = fs.fileFilter (file: file.hasExt == "nix") ./.; + # rust source files + rustFs = fs.unions [ + # Cargo.* + (fs.fileFilter (file: lib.hasPrefix "Cargo" file.name) ./.) + # *.rs + (fs.fileFilter (file: file.hasExt "rs") ./.) + # additional files + ./.cargo + ./rust-toolchain.toml + ]; + # nvim source files + # all that are not nix, nor rust, nor other ignored files + nvimFs = + fs.difference ./. (fs.unions [ nixFs rustFs ./docs ./repro.lua ]); + version = "0.10.0"; + in { + blink-fuzzy-lib = let + inherit (inputs'.fenix.packages.minimal) toolchain; + rustPlatform = pkgs.makeRustPlatform { + cargo = toolchain; + rustc = toolchain; + }; + in rustPlatform.buildRustPackage { + pname = "blink-fuzzy-lib"; + inherit version; + src = fs.toSource { + root = ./.; + fileset = rustFs; + }; + cargoLock = { + lockFile = ./Cargo.lock; + allowBuiltinFetchGit = true; + }; + + nativeBuildInputs = with pkgs; [ git ]; + }; + + blink-cmp = pkgs.vimUtils.buildVimPlugin { + pname = "blink-cmp"; + inherit version; + src = fs.toSource { + root = ./.; + fileset = nvimFs; + }; + preInstall = '' + mkdir -p target/release + ln -s ${self'.packages.blink-fuzzy-lib}/lib/libblink_cmp_fuzzy.* target/release/ + ''; + }; + + default = self'.packages.blink-cmp; + }; + + # builds the native module of the plugin + apps.build-plugin = { + type = "app"; + program = let + buildScript = pkgs.writeShellApplication { + name = "build-plugin"; + runtimeInputs = with pkgs; [ fenix.minimal.toolchain gcc ]; + text = '' + export LIBRARY_PATH="${lib.makeLibraryPath [ pkgs.libiconv ]}"; + cargo build --release + ''; + }; + in (lib.getExe buildScript); + }; + + # define the default dev environment + devShells.default = pkgs.mkShell { + name = "blink"; + inputsFrom = [ + self'.packages.blink-fuzzy-lib + self'.packages.blink-cmp + self'.apps.build-plugin + ]; + packages = with pkgs; [ rust-analyzer-nightly ]; + }; + + formatter = pkgs.nixfmt-classic; + }; + }; + + nixConfig = { + extra-substituters = [ "https://nix-community.cachix.org" ]; + extra-trusted-public-keys = [ + "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs" + ]; + }; +} diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink-cmp.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink-cmp.lua new file mode 100644 index 0000000..3009bed --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink-cmp.lua @@ -0,0 +1,2 @@ +return require('blink.cmp') + diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/accept/init.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/accept/init.lua new file mode 100644 index 0000000..c4041d3 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/accept/init.lua @@ -0,0 +1,110 @@ +local text_edits_lib = require('blink.cmp.lib.text_edits') +local brackets_lib = require('blink.cmp.completion.brackets') + +--- Applies a completion item to the current buffer +--- @param ctx blink.cmp.Context +--- @param item blink.cmp.CompletionItem +--- @param callback fun() +local function accept(ctx, item, callback) + local sources = require('blink.cmp.sources.lib') + require('blink.cmp.completion.trigger').hide() + + -- Start the resolve immediately since text changes can invalidate the item + -- with some LSPs (i.e. rust-analyzer) causing them to return the item as-is + -- without i.e. auto-imports + sources + .resolve(ctx, item) + :map(function(item) + item = vim.deepcopy(item) + + -- Get additional text edits, converted to utf-8 + local all_text_edits = vim.deepcopy(item.additionalTextEdits or {}) + all_text_edits = vim.tbl_map( + function(text_edit) return text_edits_lib.to_utf_8(text_edit, text_edits_lib.offset_encoding_from_item(item)) end, + all_text_edits + ) + + -- TODO: it's not obvious that this is converting to utf-8 + item.textEdit = text_edits_lib.get_from_item(item) + + -- Create an undo point, if it's not a snippet, since the snippet engine should handle undo + if + ctx.mode == 'default' + and require('blink.cmp.config').completion.accept.create_undo_point + and item.insertTextFormat ~= vim.lsp.protocol.InsertTextFormat.Snippet + -- HACK: We check the kind here because the Luasnip source returns PlainText and handles + -- expansion itself. Otherwise, Luasnip will fail to enter select mode + -- https://github.com/Saghen/blink.cmp/commit/284dd37f9bbc632f8281d6361e877db5b45e6ff0#r150498482 + and item.kind ~= require('blink.cmp.types').CompletionItemKind.Snippet + then + -- setting the undolevels forces neovim to create an undo point + vim.o.undolevels = vim.o.undolevels + end + + -- Ignore snippets that only contain text + -- FIXME: doesn't handle escaped snippet placeholders "\\$1" should output "$1", not "\$1" + if + item.insertTextFormat == vim.lsp.protocol.InsertTextFormat.Snippet + and item.kind ~= require('blink.cmp.types').CompletionItemKind.Snippet + then + local parsed_snippet = require('blink.cmp.sources.snippets.utils').safe_parse(item.textEdit.newText) + if + parsed_snippet ~= nil + and #parsed_snippet.data.children == 1 + and parsed_snippet.data.children[1].type == vim.lsp._snippet_grammar.NodeType.Text + then + item.insertTextFormat = vim.lsp.protocol.InsertTextFormat.PlainText + end + end + + -- Add brackets to the text edit if needed + local brackets_status, text_edit_with_brackets, offset = brackets_lib.add_brackets(ctx, vim.bo.filetype, item) + item.textEdit = text_edit_with_brackets + + -- Snippet + if item.insertTextFormat == vim.lsp.protocol.InsertTextFormat.Snippet then + assert(ctx.mode == 'default', 'Snippets are only supported in default mode') + + -- We want to handle offset_encoding and the text edit api can do this for us + -- so we empty the newText and apply + local temp_text_edit = vim.deepcopy(item.textEdit) + temp_text_edit.newText = '' + table.insert(all_text_edits, temp_text_edit) + text_edits_lib.apply(all_text_edits) + + -- Expand the snippet + require('blink.cmp.config').snippets.expand(item.textEdit.newText) + + -- OR Normal: Apply the text edit and move the cursor + else + table.insert(all_text_edits, item.textEdit) + text_edits_lib.apply(all_text_edits) + -- TODO: should move the cursor only by the offset since text edit handles everything else? + ctx.set_cursor({ ctx.get_cursor()[1], item.textEdit.range.start.character + #item.textEdit.newText + offset }) + end + + -- Let the source execute the item itself + sources.execute(ctx, item):map(function() + -- Check semantic tokens for brackets, if needed, and apply additional text edits + if brackets_status == 'check_semantic_token' then + -- TODO: since we apply the additional text edits after, auto imported functions will not + -- get auto brackets. If we apply them before, we have to modify the textEdit to compensate + brackets_lib.add_brackets_via_semantic_token(vim.bo.filetype, item, function() + require('blink.cmp.completion.trigger').show_if_on_trigger_character({ is_accept = true }) + require('blink.cmp.signature.trigger').show_if_on_trigger_character() + callback() + end) + else + require('blink.cmp.completion.trigger').show_if_on_trigger_character({ is_accept = true }) + require('blink.cmp.signature.trigger').show_if_on_trigger_character() + callback() + end + + -- Notify the rust module that the item was accessed + require('blink.cmp.fuzzy').access(item) + end) + end) + :catch(function(err) vim.notify(err, vim.log.levels.ERROR, { title = 'blink.cmp' }) end) +end + +return accept diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/accept/prefix.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/accept/prefix.lua new file mode 100644 index 0000000..3c51715 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/accept/prefix.lua @@ -0,0 +1,58 @@ +local PAIRS_AND_INVALID_CHARS = {} +string.gsub('\'"=$()[]<>{} \t\n\r', '.', function(char) PAIRS_AND_INVALID_CHARS[string.byte(char)] = true end) + +local CLOSING_PAIR = { + [string.byte('<')] = string.byte('>'), + [string.byte('[')] = string.byte(']'), + [string.byte('(')] = string.byte(')'), + [string.byte('{')] = string.byte('}'), + [string.byte('"')] = string.byte('"'), + [string.byte("'")] = string.byte("'"), +} + +local ALPHANUMERIC = {} +string.gsub( + 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', + '.', + function(char) ALPHANUMERIC[string.byte(char)] = true end +) + +--- Gets the prefix of the given text, stopping at brackets and quotes +--- @param text string +--- @return string +local function get_prefix_before_brackets_and_quotes(text) + local closing_pairs_stack = {} + local word = '' + + local add = function(char) + word = word .. string.char(char) + + -- if we've seen the opening pair, and we've just received the closing pair, + -- remove it from the closing pairs stack + if closing_pairs_stack[#closing_pairs_stack] == char then + table.remove(closing_pairs_stack, #closing_pairs_stack) + -- if the character is an opening pair, add it to the closing pairs stack + elseif CLOSING_PAIR[char] ~= nil then + table.insert(closing_pairs_stack, CLOSING_PAIR[char]) + end + end + + local has_alphanumeric = false + for i = 1, #text do + local char = string.byte(text, i) + if PAIRS_AND_INVALID_CHARS[char] == nil then + add(char) + has_alphanumeric = has_alphanumeric or ALPHANUMERIC[char] + elseif not has_alphanumeric or #closing_pairs_stack ~= 0 then + add(char) + -- if we had an alphanumeric, and the closing pairs stack *just* emptied, + -- because the current character is a closing pair, we exit + if has_alphanumeric and #closing_pairs_stack == 0 then break end + else + break + end + end + return word +end + +return get_prefix_before_brackets_and_quotes diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/accept/preview.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/accept/preview.lua new file mode 100644 index 0000000..88b46e2 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/accept/preview.lua @@ -0,0 +1,35 @@ +--- @param item blink.cmp.CompletionItem +--- @return lsp.TextEdit undo_text_edit, integer[]? undo_cursor_pos The text edit to apply and the original cursor +--- position to move to when undoing the preview, +local function preview(item) + local text_edits_lib = require('blink.cmp.lib.text_edits') + local text_edit = text_edits_lib.get_from_item(item) + + if item.insertTextFormat == vim.lsp.protocol.InsertTextFormat.Snippet then + local expanded_snippet = require('blink.cmp.sources.snippets.utils').safe_parse(text_edit.newText) + local snippet = expanded_snippet and tostring(expanded_snippet) or text_edit.newText + local get_prefix_before_brackets_and_quotes = require('blink.cmp.completion.accept.prefix') + text_edit.newText = get_prefix_before_brackets_and_quotes(snippet) + end + + local undo_text_edit = text_edits_lib.get_undo_text_edit(text_edit) + local cursor_pos = { + text_edit.range.start.line + 1, + text_edit.range.start.character + #text_edit.newText, + } + + text_edits_lib.apply({ text_edit }) + + local original_cursor = vim.api.nvim_win_get_cursor(0) + local cursor_moved = false + + -- TODO: remove when text_edits_lib.apply begins setting cursor position + if vim.api.nvim_get_mode().mode ~= 'c' then + vim.api.nvim_win_set_cursor(0, cursor_pos) + cursor_moved = true + end + + return undo_text_edit, cursor_moved and original_cursor or nil +end + +return preview diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/brackets/config.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/brackets/config.lua new file mode 100644 index 0000000..9201a08 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/brackets/config.lua @@ -0,0 +1,37 @@ +return { + -- stylua: ignore + blocked_filetypes = { + 'sql', 'ruby', 'perl', 'lisp', 'scheme', 'clojure', + 'prolog', 'vb', 'elixir', 'smalltalk', 'applescript' + }, + per_filetype = { + -- languages with a space + haskell = { ' ', '' }, + fsharp = { ' ', '' }, + ocaml = { ' ', '' }, + erlang = { ' ', '' }, + tcl = { ' ', '' }, + nix = { ' ', '' }, + helm = { ' ', '' }, + + shell = { ' ', '' }, + sh = { ' ', '' }, + bash = { ' ', '' }, + fish = { ' ', '' }, + zsh = { ' ', '' }, + powershell = { ' ', '' }, + + make = { ' ', '' }, + + -- languages with square brackets + wl = { '[', ']' }, + wolfram = { '[', ']' }, + mma = { '[', ']' }, + mathematica = { '[', ']' }, + context = { '[', ']' }, + + -- languages with curly brackets + tex = { '{', '}' }, + plaintex = { '{', '}' }, + }, +} diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/brackets/init.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/brackets/init.lua new file mode 100644 index 0000000..511b42b --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/brackets/init.lua @@ -0,0 +1,6 @@ +local brackets = {} + +brackets.add_brackets = require('blink.cmp.completion.brackets.kind') +brackets.add_brackets_via_semantic_token = require('blink.cmp.completion.brackets.semantic') + +return brackets diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/brackets/kind.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/brackets/kind.lua new file mode 100644 index 0000000..f09f180 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/brackets/kind.lua @@ -0,0 +1,52 @@ +local utils = require('blink.cmp.completion.brackets.utils') + +--- @param ctx blink.cmp.Context +--- @param filetype string +--- @param item blink.cmp.CompletionItem +--- @return 'added' | 'check_semantic_token' | 'skipped', lsp.TextEdit | lsp.InsertReplaceEdit, number +local function add_brackets(ctx, filetype, item) + local text_edit = item.textEdit + assert(text_edit ~= nil, 'Got nil text edit while adding brackets via kind') + local brackets_for_filetype = utils.get_for_filetype(filetype, item) + + -- skip if we're not in default mode + if ctx.mode ~= 'default' then return 'skipped', text_edit, 0 end + + -- if there's already the correct brackets in front, skip but indicate the cursor should move in front of the bracket + -- TODO: what if the brackets_for_filetype[1] == '' or ' ' (haskell/ocaml)? + -- TODO: should this check semantic tokens and still move the cursor in that case? + if utils.has_brackets_in_front(text_edit, brackets_for_filetype[1]) then + local offset = utils.can_have_brackets(item, brackets_for_filetype) and #brackets_for_filetype[1] or 0 + return 'skipped', text_edit, offset + end + + -- if the item already contains the brackets, conservatively skip adding brackets + -- todo: won't work for snippets when the brackets_for_filetype is { '{', '}' } + -- I've never seen a language like that though + if brackets_for_filetype[1] ~= ' ' and text_edit.newText:match('[\\' .. brackets_for_filetype[1] .. ']') ~= nil then + return 'skipped', text_edit, 0 + end + + -- check if configuration incidates we should skip + if not utils.should_run_resolution(filetype, 'kind') then return 'check_semantic_token', text_edit, 0 end + -- cannot have brackets, skip + if not utils.can_have_brackets(item, brackets_for_filetype) then return 'check_semantic_token', text_edit, 0 end + + text_edit = vim.deepcopy(text_edit) + -- For snippets, we add the cursor position between the brackets as the last placeholder + if item.insertTextFormat == vim.lsp.protocol.InsertTextFormat.Snippet then + local placeholders = utils.snippets_extract_placeholders(text_edit.newText) + local last_placeholder_index = math.max(0, unpack(placeholders)) + text_edit.newText = text_edit.newText + .. brackets_for_filetype[1] + .. '$' + .. tostring(last_placeholder_index + 1) + .. brackets_for_filetype[2] + -- Otherwise, we add as usual + else + text_edit.newText = text_edit.newText .. brackets_for_filetype[1] .. brackets_for_filetype[2] + end + return 'added', text_edit, -#brackets_for_filetype[2] +end + +return add_brackets diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/brackets/semantic.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/brackets/semantic.lua new file mode 100644 index 0000000..c64afbd --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/brackets/semantic.lua @@ -0,0 +1,109 @@ +local config = require('blink.cmp.config').completion.accept.auto_brackets +local utils = require('blink.cmp.completion.brackets.utils') + +local semantic = {} + +--- Asynchronously use semantic tokens to determine if brackets should be added +--- @param filetype string +--- @param item blink.cmp.CompletionItem +--- @param callback fun() +function semantic.add_brackets_via_semantic_token(filetype, item, callback) + if not utils.should_run_resolution(filetype, 'semantic_token') then return callback() end + + local text_edit = item.textEdit + assert(text_edit ~= nil, 'Got nil text edit while adding brackets via semantic tokens') + local client = vim.lsp.get_client_by_id(item.client_id) + if client == nil then return callback() end + + local capabilities = client.server_capabilities.semanticTokensProvider + if not capabilities or not capabilities.legend or (not capabilities.range and not capabilities.full) then + return callback() + end + + local token_types = client.server_capabilities.semanticTokensProvider.legend.tokenTypes + local params = { + textDocument = vim.lsp.util.make_text_document_params(), + range = capabilities.range and { + start = { line = text_edit.range.start.line, character = text_edit.range.start.character }, + ['end'] = { line = text_edit.range.start.line + 1, character = 0 }, + } or nil, + } + + local cursor_before_call = vim.api.nvim_win_get_cursor(0) + + local start_time = vim.uv.hrtime() + client.request( + capabilities.range and 'textDocument/semanticTokens/range' or 'textDocument/semanticTokens/full', + params, + function(err, result) + if err ~= nil or result == nil or #result.data == 0 then return callback() end + + -- cancel if it's been too long, or if the cursor moved + local ms_since_call = (vim.uv.hrtime() - start_time) / 1000000 + local cursor_after_call = vim.api.nvim_win_get_cursor(0) + if + ms_since_call > config.semantic_token_resolution.timeout_ms + or cursor_before_call[1] ~= cursor_after_call[1] + or cursor_before_call[2] ~= cursor_after_call[2] + then + return callback() + end + + for _, token in ipairs(semantic.process_semantic_token_data(result.data, token_types)) do + if + cursor_after_call[1] == token.line + and cursor_after_call[2] >= token.start_col + and cursor_after_call[2] <= token.end_col + and (token.type == 'function' or token.type == 'method') + then + -- add the brackets + local brackets_for_filetype = utils.get_for_filetype(filetype, item) + local line = vim.api.nvim_get_current_line() + local start_col = text_edit.range.start.character + #text_edit.newText + local new_line = line:sub(1, start_col) + .. brackets_for_filetype[1] + .. brackets_for_filetype[2] + .. line:sub(start_col + 1) + vim.api.nvim_set_current_line(new_line) + vim.api.nvim_win_set_cursor(0, { cursor_after_call[1], start_col + #brackets_for_filetype[1] }) + callback() + return + end + end + + callback() + end + ) +end + +function semantic.process_semantic_token_data(data, token_types) + local tokens = {} + local idx = 0 + local token_line = 0 + local token_start_col = 0 + + while (idx + 1) * 5 <= #data do + local delta_token_line = data[idx * 5 + 1] + local delta_token_start_col = data[idx * 5 + 2] + local delta_token_length = data[idx * 5 + 3] + local type = token_types[data[idx * 5 + 4] + 1] + + if delta_token_line > 0 then token_start_col = 0 end + token_line = token_line + delta_token_line + token_start_col = token_start_col + delta_token_start_col + + table.insert(tokens, { + line = token_line + 1, + start_col = token_start_col, + end_col = token_start_col + delta_token_length, + type = type, + }) + + token_start_col = token_start_col + delta_token_length + idx = idx + 1 + end + + return tokens +end + +return semantic.add_brackets_via_semantic_token diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/brackets/utils.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/brackets/utils.lua new file mode 100644 index 0000000..cf84f11 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/brackets/utils.lua @@ -0,0 +1,61 @@ +local config = require('blink.cmp.config').completion.accept.auto_brackets +local CompletionItemKind = require('blink.cmp.types').CompletionItemKind +local brackets = require('blink.cmp.completion.brackets.config') +local utils = {} + +--- @param snippet string +function utils.snippets_extract_placeholders(snippet) + local placeholders = {} + local pattern = [=[(\$\{(\d+)(:([^}\\]|\\.)*?)?\})]=] + + for _, number, _, _ in snippet:gmatch(pattern) do + table.insert(placeholders, tonumber(number)) + end + + return placeholders +end + +--- @param filetype string +--- @param item blink.cmp.CompletionItem +--- @return string[] +function utils.get_for_filetype(filetype, item) + local default = config.default_brackets + local per_filetype = config.override_brackets_for_filetypes[filetype] or brackets.per_filetype[filetype] + + if type(per_filetype) == 'function' then return per_filetype(item) or default end + return per_filetype or default +end + +--- @param filetype string +--- @param resolution_method 'kind' | 'semantic_token' +--- @return boolean +function utils.should_run_resolution(filetype, resolution_method) + -- resolution method specific + if not config[resolution_method .. '_resolution'].enabled then return false end + local resolution_blocked_filetypes = config[resolution_method .. '_resolution'].blocked_filetypes + if vim.tbl_contains(resolution_blocked_filetypes, filetype) then return false end + + -- global + if not config.enabled then return false end + if vim.tbl_contains(config.force_allow_filetypes, filetype) then return true end + return not vim.tbl_contains(config.blocked_filetypes, filetype) + and not vim.tbl_contains(brackets.blocked_filetypes, filetype) +end + +--- @param text_edit lsp.TextEdit | lsp.InsertReplaceEdit +--- @param bracket string +--- @return boolean +function utils.has_brackets_in_front(text_edit, bracket) + local line = vim.api.nvim_get_current_line() + local col = text_edit.range['end'].character + 1 + return line:sub(col, col) == bracket +end + +--- @param item blink.cmp.CompletionItem +--- @param _ string[] +-- TODO: for edge cases, we should probably also take brackets themselves into consideration +function utils.can_have_brackets(item, _) + return item.kind == CompletionItemKind.Function or item.kind == CompletionItemKind.Method +end + +return utils diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/init.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/init.lua new file mode 100644 index 0000000..49c6fc2 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/init.lua @@ -0,0 +1,88 @@ +local config = require('blink.cmp.config') +local completion = {} + +function completion.setup() + -- trigger controls when to show the window and the current context for caching + local trigger = require('blink.cmp.completion.trigger') + trigger.activate() + + -- sources fetch completion items and documentation + local sources = require('blink.cmp.sources.lib') + + -- manages the completion list state: + -- fuzzy matching items + -- when to show/hide the windows + -- selection + -- accepting and previewing items + local list = require('blink.cmp.completion.list') + + -- trigger -> sources: request completion items from the sources on show + trigger.show_emitter:on(function(event) sources.request_completions(event.context) end) + trigger.hide_emitter:on(function() + sources.cancel_completions() + list.hide() + end) + + -- sources -> list + sources.completions_emitter:on(function(event) + -- schedule for later to avoid adding 0.5-4ms to insertion latency + vim.schedule(function() + -- since this was performed asynchronously, we check if the context has changed + if trigger.context == nil or event.context.id ~= trigger.context.id then return end + -- don't show the list if prefetching results + if event.context.trigger.kind == 'prefetch' then return end + + -- don't show if all the sources that defined the trigger character returned no items + if event.context.trigger.character ~= nil then + local triggering_source_returned_items = false + for _, source in pairs(event.context.providers) do + local trigger_characters = sources.get_provider_by_id(source):get_trigger_characters() + if + event.items[source] + and #event.items[source] > 0 + and vim.tbl_contains(trigger_characters, trigger.context.trigger.character) + then + triggering_source_returned_items = true + break + end + end + + if not triggering_source_returned_items then return list.hide() end + end + + list.show(event.context, event.items) + end) + end) + + --- list -> windows: ghost text and completion menu + -- setup completion menu + if config.completion.menu.enabled then + list.show_emitter:on( + function(event) require('blink.cmp.completion.windows.menu').open_with_items(event.context, event.items) end + ) + list.hide_emitter:on(function() require('blink.cmp.completion.windows.menu').close() end) + list.select_emitter:on(function(event) + require('blink.cmp.completion.windows.menu').set_selected_item_idx(event.idx) + require('blink.cmp.completion.windows.documentation').auto_show_item(event.context, event.item) + end) + end + + -- setup ghost text + if config.completion.ghost_text.enabled then + list.select_emitter:on( + function(event) require('blink.cmp.completion.windows.ghost_text').show_preview(event.item) end + ) + list.hide_emitter:on(function() require('blink.cmp.completion.windows.ghost_text').clear_preview() end) + end + + -- run 'resolve' on the item ahead of time to avoid delays + -- when accepting the item or showing documentation + list.select_emitter:on(function(event) + -- when selection.preselect == false, we still want to prefetch the first item + local item = event.item or list.items[1] + if item == nil then return end + require('blink.cmp.completion.prefetch')(event.context, event.item) + end) +end + +return completion diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/list.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/list.lua new file mode 100644 index 0000000..61fa2c3 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/list.lua @@ -0,0 +1,250 @@ +--- Manages most of the state for the completion list such that downstream consumers can be mostly stateless +--- @class (exact) blink.cmp.CompletionList +--- @field config blink.cmp.CompletionListConfig +--- @field show_emitter blink.cmp.EventEmitter<blink.cmp.CompletionListShowEvent> +--- @field hide_emitter blink.cmp.EventEmitter<blink.cmp.CompletionListHideEvent> +--- @field select_emitter blink.cmp.EventEmitter<blink.cmp.CompletionListSelectEvent> +--- @field accept_emitter blink.cmp.EventEmitter<blink.cmp.CompletionListAcceptEvent> +--- +--- @field context? blink.cmp.Context +--- @field items blink.cmp.CompletionItem[] +--- @field selected_item_idx? number +--- @field preview_undo? { text_edit: lsp.TextEdit, cursor: integer[]?} +--- +--- @field show fun(context: blink.cmp.Context, items: table<string, blink.cmp.CompletionItem[]>) +--- @field fuzzy fun(context: blink.cmp.Context, items: table<string, blink.cmp.CompletionItem[]>): blink.cmp.CompletionItem[] +--- @field hide fun() +--- +--- @field get_selected_item fun(): blink.cmp.CompletionItem? +--- @field get_selection_mode fun(context: blink.cmp.Context): { preselect: boolean, auto_insert: boolean } +--- @field get_item_idx_in_list fun(item?: blink.cmp.CompletionItem): number? +--- @field select fun(idx?: number, opts?: { auto_insert?: boolean, undo_preview?: boolean, is_explicit_selection?: boolean }) +--- @field select_next fun(opts?: blink.cmp.CompletionListSelectOpts) +--- @field select_prev fun(opts?: blink.cmp.CompletionListSelectOpts) +--- +--- @field undo_preview fun() +--- @field apply_preview fun(item: blink.cmp.CompletionItem) +--- @field accept fun(opts?: blink.cmp.CompletionListAcceptOpts): boolean Applies the currently selected item, returning true if it succeeded + +--- @class blink.cmp.CompletionListSelectOpts +--- @field auto_insert? boolean When `true`, inserts the completion item automatically when selecting it + +--- @class blink.cmp.CompletionListSelectAndAcceptOpts +--- @field callback? fun() Called after the item is accepted + +--- @class blink.cmp.CompletionListAcceptOpts : blink.cmp.CompletionListSelectAndAcceptOpts +--- @field index? number The index of the item to accept, if not provided, the currently selected item will be accepted + +--- @class blink.cmp.CompletionListShowEvent +--- @field items blink.cmp.CompletionItem[] +--- @field context blink.cmp.Context + +--- @class blink.cmp.CompletionListHideEvent +--- @field context blink.cmp.Context + +--- @class blink.cmp.CompletionListSelectEvent +--- @field idx? number +--- @field item? blink.cmp.CompletionItem +--- @field items blink.cmp.CompletionItem[] +--- @field context blink.cmp.Context + +--- @class blink.cmp.CompletionListAcceptEvent +--- @field item blink.cmp.CompletionItem +--- @field context blink.cmp.Context + +--- @type blink.cmp.CompletionList +--- @diagnostic disable-next-line: missing-fields +local list = { + select_emitter = require('blink.cmp.lib.event_emitter').new('select', 'BlinkCmpListSelect'), + accept_emitter = require('blink.cmp.lib.event_emitter').new('accept', 'BlinkCmpAccept'), + show_emitter = require('blink.cmp.lib.event_emitter').new('show', 'BlinkCmpShow'), + hide_emitter = require('blink.cmp.lib.event_emitter').new('hide', 'BlinkCmpHide'), + config = require('blink.cmp.config').completion.list, + context = nil, + items = {}, + is_explicitly_selected = false, + preview_undo = nil, +} + +---------- State ---------- + +function list.show(context, items_by_source) + -- reset state for new context + local is_new_context = not list.context or list.context.id ~= context.id + if is_new_context then + list.preview_undo = nil + list.is_explicitly_selected = false + end + + -- if the keyword changed, the list is no longer explicitly selected + local bounds_equal = list.context ~= nil + and list.context.bounds.start_col == context.bounds.start_col + and list.context.bounds.length == context.bounds.length + if not bounds_equal then list.is_explicitly_selected = false end + + local previous_selected_item = list.get_selected_item() + + -- update the context/list and emit + list.context = context + list.items = list.fuzzy(context, items_by_source) + + if #list.items == 0 then + list.hide_emitter:emit({ context = context }) + else + list.show_emitter:emit({ items = list.items, context = context }) + end + + -- maintain the selection if the user selected an item + local previous_item_idx = list.get_item_idx_in_list(previous_selected_item) + if list.is_explicitly_selected and previous_item_idx ~= nil and previous_item_idx <= 10 then + list.select(previous_item_idx, { auto_insert = false, undo_preview = false }) + + -- otherwise, use the default selection + else + list.select( + list.get_selection_mode(list.context).preselect and 1 or nil, + { auto_insert = false, undo_preview = false, is_explicit_selection = false } + ) + end +end + +function list.fuzzy(context, items_by_source) + local fuzzy = require('blink.cmp.fuzzy') + local filtered_items = fuzzy.fuzzy( + context.get_line(), + context.get_cursor()[2], + items_by_source, + require('blink.cmp.config').completion.keyword.range + ) + + -- apply the per source max_items + filtered_items = require('blink.cmp.sources.lib').apply_max_items_for_completions(context, filtered_items) + + -- apply the global max_items + return require('blink.cmp.lib.utils').slice(filtered_items, 1, list.config.max_items) +end + +function list.hide() list.hide_emitter:emit({ context = list.context }) end + +---------- Selection ---------- + +function list.get_selected_item() return list.items[list.selected_item_idx] end + +function list.get_selection_mode(context) + assert(context ~= nil, 'Context must be set before getting selection mode') + + local preselect = list.config.selection.preselect + if type(preselect) == 'function' then preselect = preselect(context) end + --- @cast preselect boolean + + local auto_insert = list.config.selection.auto_insert + if type(auto_insert) == 'function' then auto_insert = auto_insert(context) end + --- @cast auto_insert boolean + + return { preselect = preselect, auto_insert = auto_insert } +end + +function list.get_item_idx_in_list(item) + if item == nil then return end + return require('blink.cmp.lib.utils').find_idx(list.items, function(i) return i.label == item.label end) +end + +function list.select(idx, opts) + opts = opts or {} + local item = list.items[idx] + + local auto_insert = opts.auto_insert + if auto_insert == nil then auto_insert = list.get_selection_mode(list.context).auto_insert end + + require('blink.cmp.completion.trigger').suppress_events_for_callback(function() + if opts.undo_preview ~= false then list.undo_preview() end + if auto_insert and item ~= nil then list.apply_preview(item) end + end) + + --- @diagnostic disable-next-line: assign-type-mismatch + list.is_explicitly_selected = opts.is_explicit_selection == nil and true or opts.is_explicit_selection + list.selected_item_idx = idx + list.select_emitter:emit({ idx = idx, item = item, items = list.items, context = list.context }) +end + +function list.select_next(opts) + if #list.items == 0 or list.context == nil then return end + + -- haven't selected anything yet, select the first item + if list.selected_item_idx == nil then return list.select(1, opts) end + + -- end of the list + if list.selected_item_idx == #list.items then + -- cycling around has been disabled, ignore + if not list.config.cycle.from_bottom then return end + + -- preselect is not enabled, we go back to no selection + if not list.get_selection_mode(list.context).preselect then return list.select(nil, opts) end + + -- otherwise, we cycle around + return list.select(1, opts) + end + + -- typical case, select the next item + list.select(list.selected_item_idx + 1, opts) +end + +function list.select_prev(opts) + if #list.items == 0 or list.context == nil then return end + + -- haven't selected anything yet, select the last item + if list.selected_item_idx == nil then return list.select(#list.items, opts) end + + -- start of the list + if list.selected_item_idx == 1 then + -- cycling around has been disabled, ignore + if not list.config.cycle.from_top then return end + + -- auto_insert is enabled, we go back to no selection + if list.get_selection_mode(list.context).auto_insert then return list.select(nil, opts) end + + -- otherwise, we cycle around + return list.select(#list.items, opts) + end + + -- typical case, select the previous item + list.select(list.selected_item_idx - 1, opts) +end + +---------- Preview ---------- + +function list.undo_preview() + if list.preview_undo == nil then return end + + require('blink.cmp.lib.text_edits').apply({ list.preview_undo.text_edit }) + if list.preview_undo.cursor then + require('blink.cmp.completion.trigger.context').set_cursor(list.preview_undo.cursor) + end + list.preview_undo = nil +end + +function list.apply_preview(item) + -- undo the previous preview if it exists + list.undo_preview() + -- apply the new preview + local undo_text_edit, undo_cursor = require('blink.cmp.completion.accept.preview')(item) + list.preview_undo = { text_edit = undo_text_edit, cursor = undo_cursor } +end + +---------- Accept ---------- + +function list.accept(opts) + opts = opts or {} + local item = list.items[opts.index or list.selected_item_idx] + if item == nil then return false end + + list.undo_preview() + local accept = require('blink.cmp.completion.accept') + accept(list.context, item, function() + list.accept_emitter:emit({ item = item, context = list.context }) + if opts.callback then opts.callback() end + end) + return true +end + +return list diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/prefetch.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/prefetch.lua new file mode 100644 index 0000000..c722a30 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/prefetch.lua @@ -0,0 +1,29 @@ +-- Run `resolve` on the item ahead of time to avoid delays +-- when accepting the item or showing documentation + +local last_context_id = nil +local last_request = nil +local timer = vim.uv.new_timer() + +--- @param context blink.cmp.Context +--- @param item blink.cmp.CompletionItem +local function prefetch_resolve(context, item) + if not item then return end + + local resolve = vim.schedule_wrap(function() + if last_request ~= nil then last_request:cancel() end + last_request = require('blink.cmp.sources.lib').resolve(context, item) + end) + + -- immediately resolve if the context has changed + if last_context_id ~= context.id then + last_context_id = context.id + resolve() + end + + -- otherwise, wait for the debounce period + timer:stop() + timer:start(50, 0, resolve) +end + +return prefetch_resolve diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/trigger/context.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/trigger/context.lua new file mode 100644 index 0000000..6eb367d --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/trigger/context.lua @@ -0,0 +1,118 @@ +-- TODO: remove the end_col field from ContextBounds + +--- @class blink.cmp.ContextBounds +--- @field line string +--- @field line_number number +--- @field start_col number +--- @field length number + +--- @class blink.cmp.Context +--- @field mode blink.cmp.Mode +--- @field id number +--- @field bufnr number +--- @field cursor number[] +--- @field line string +--- @field bounds blink.cmp.ContextBounds +--- @field trigger blink.cmp.ContextTrigger +--- @field providers string[] +--- +--- @field new fun(opts: blink.cmp.ContextOpts): blink.cmp.Context +--- @field get_keyword fun(): string +--- @field within_query_bounds fun(self: blink.cmp.Context, cursor: number[]): boolean +--- +--- @field get_mode fun(): blink.cmp.Mode +--- @field get_cursor fun(): number[] +--- @field set_cursor fun(cursor: number[]) +--- @field get_line fun(num?: number): string +--- @field get_bounds fun(range: blink.cmp.CompletionKeywordRange): blink.cmp.ContextBounds + +--- @class blink.cmp.ContextTrigger +--- @field initial_kind blink.cmp.CompletionTriggerKind The trigger kind when the context was first created +--- @field initial_character? string The trigger character when initial_kind == 'trigger_character' +--- @field kind blink.cmp.CompletionTriggerKind The current trigger kind +--- @field character? string The trigger character when kind == 'trigger_character' + +--- @class blink.cmp.ContextOpts +--- @field id number +--- @field providers string[] +--- @field initial_trigger_kind blink.cmp.CompletionTriggerKind +--- @field initial_trigger_character? string +--- @field trigger_kind blink.cmp.CompletionTriggerKind +--- @field trigger_character? string + +--- @type blink.cmp.Context +--- @diagnostic disable-next-line: missing-fields +local context = {} + +function context.new(opts) + local cursor = context.get_cursor() + local line = context.get_line() + + return setmetatable({ + mode = context.get_mode(), + id = opts.id, + bufnr = vim.api.nvim_get_current_buf(), + cursor = cursor, + line = line, + bounds = context.get_bounds('full'), + trigger = { + initial_kind = opts.initial_trigger_kind, + initial_character = opts.initial_trigger_character, + kind = opts.trigger_kind, + character = opts.trigger_character, + }, + providers = opts.providers, + }, { __index = context }) +end + +function context.get_keyword() + local keyword = require('blink.cmp.config').completion.keyword + local range = context.get_bounds(keyword.range) + return string.sub(context.get_line(), range.start_col, range.start_col + range.length - 1) +end + +--- @param cursor number[] +--- @return boolean +function context:within_query_bounds(cursor) + local row, col = cursor[1], cursor[2] + local bounds = self.bounds + return row == bounds.line_number and col >= bounds.start_col and col < (bounds.start_col + bounds.length) +end + +function context.get_mode() return vim.api.nvim_get_mode().mode == 'c' and 'cmdline' or 'default' end + +function context.get_cursor() + return context.get_mode() == 'cmdline' and { 1, vim.fn.getcmdpos() - 1 } or vim.api.nvim_win_get_cursor(0) +end + +function context.set_cursor(cursor) + local mode = context.get_mode() + if mode == 'default' then return vim.api.nvim_win_set_cursor(0, cursor) end + + assert(mode == 'cmdline', 'Unsupported mode for setting cursor: ' .. mode) + assert(cursor[1] == 1, 'Cursor must be on the first line in cmdline mode') + vim.fn.setcmdpos(cursor[2]) +end + +function context.get_line(num) + if context.get_mode() == 'cmdline' then + assert( + num == nil or num == 0, + 'Cannot get line number ' .. tostring(num) .. ' in cmdline mode. Only 0 is supported' + ) + return vim.fn.getcmdline() + end + + if num == nil then num = context.get_cursor()[1] - 1 end + return vim.api.nvim_buf_get_lines(0, num, num + 1, false)[1] +end + +--- Gets characters around the cursor and returns the range, 0-indexed +function context.get_bounds(range) + local line = context.get_line() + local cursor = context.get_cursor() + local start_col, end_col = require('blink.cmp.fuzzy').get_keyword_range(line, cursor[2], range) + return { line_number = cursor[1], start_col = start_col + 1, length = end_col - start_col } +end + +return context diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/trigger/init.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/trigger/init.lua new file mode 100644 index 0000000..1bd330f --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/trigger/init.lua @@ -0,0 +1,241 @@ +--- @alias blink.cmp.CompletionTriggerKind 'manual' | 'prefetch' | 'keyword' | 'trigger_character' +--- +-- Handles hiding and showing the completion window. When a user types a trigger character +-- (provided by the sources) or anything matching the `keyword_regex`, we create a new `context`. +-- This can be used downstream to determine if we should make new requests to the sources or not. +--- @class blink.cmp.CompletionTrigger +--- @field buffer_events blink.cmp.BufferEvents +--- @field cmdline_events blink.cmp.CmdlineEvents +--- @field current_context_id number +--- @field context? blink.cmp.Context +--- @field show_emitter blink.cmp.EventEmitter<{ context: blink.cmp.Context }> +--- @field hide_emitter blink.cmp.EventEmitter<{}> +--- +--- @field activate fun() +--- @field is_keyword_character fun(char: string): boolean +--- @field is_trigger_character fun(char: string, is_show_on_x?: boolean): boolean +--- @field suppress_events_for_callback fun(cb: fun()) +--- @field show_if_on_trigger_character fun(opts?: { is_accept?: boolean }) +--- @field show fun(opts?: { trigger_kind: blink.cmp.CompletionTriggerKind, trigger_character?: string, force?: boolean, send_upstream?: boolean, providers?: string[] }): blink.cmp.Context? +--- @field hide fun() +--- @field within_query_bounds fun(cursor: number[]): boolean +--- @field get_bounds fun(regex: vim.regex, line: string, cursor: number[]): blink.cmp.ContextBounds + +local config = require('blink.cmp.config').completion.trigger +local context = require('blink.cmp.completion.trigger.context') +local utils = require('blink.cmp.completion.trigger.utils') + +--- @type blink.cmp.CompletionTrigger +--- @diagnostic disable-next-line: missing-fields +local trigger = { + current_context_id = -1, + show_emitter = require('blink.cmp.lib.event_emitter').new('show'), + hide_emitter = require('blink.cmp.lib.event_emitter').new('hide'), +} + +function trigger.activate() + trigger.buffer_events = require('blink.cmp.lib.buffer_events').new({ + -- TODO: should this ignore trigger.kind == 'prefetch'? + has_context = function() return trigger.context ~= nil end, + show_in_snippet = config.show_in_snippet, + }) + trigger.cmdline_events = require('blink.cmp.lib.cmdline_events').new() + + local function on_char_added(char, is_ignored) + -- we were told to ignore the text changed event, so we update the context + -- but don't send an on_show event upstream + if is_ignored then + if trigger.context ~= nil then trigger.show({ send_upstream = false, trigger_kind = 'keyword' }) end + + -- character forces a trigger according to the sources, create a fresh context + elseif trigger.is_trigger_character(char) and (config.show_on_trigger_character or trigger.context ~= nil) then + trigger.context = nil + trigger.show({ trigger_kind = 'trigger_character', trigger_character = char }) + + -- character is part of a keyword + elseif trigger.is_keyword_character(char) and (config.show_on_keyword or trigger.context ~= nil) then + trigger.show({ trigger_kind = 'keyword' }) + + -- nothing matches so hide + else + trigger.hide() + end + end + + local function on_cursor_moved(event, is_ignored) + local cursor = context.get_cursor() + local cursor_col = cursor[2] + + local char_under_cursor = utils.get_char_at_cursor() + local is_keyword = trigger.is_keyword_character(char_under_cursor) + + -- we were told to ignore the cursor moved event, so we update the context + -- but don't send an on_show event upstream + if is_ignored and event == 'CursorMoved' then + if trigger.context ~= nil then + -- TODO: If we `auto_insert` with the `path` source, we may end up on a trigger character + -- i.e. `downloads/`. If we naively update the context, we'll show the menu with the + -- existing context. So we clear the context if we're not on a keyword character. + -- Is there a better solution here? + if not is_keyword then trigger.context = nil end + + trigger.show({ send_upstream = false, trigger_kind = 'keyword' }) + end + return + end + + local is_on_trigger_for_show = trigger.is_trigger_character(char_under_cursor) + + -- TODO: doesn't handle `a` where the cursor moves immediately after + -- Reproducable with `example.|a` and pressing `a`, should not show the menu + local insert_enter_on_trigger_character = config.show_on_trigger_character + and config.show_on_insert_on_trigger_character + and event == 'InsertEnter' + and trigger.is_trigger_character(char_under_cursor, true) + + -- check if we're still within the bounds of the query used for the context + if trigger.context ~= nil and trigger.context:within_query_bounds(cursor) then + trigger.show({ trigger_kind = 'keyword' }) + + -- check if we've entered insert mode on a trigger character + -- or if we've moved onto a trigger character while open + elseif + insert_enter_on_trigger_character + or (is_on_trigger_for_show and trigger.context ~= nil and trigger.context.trigger.kind ~= 'prefetch') + then + trigger.context = nil + trigger.show({ trigger_kind = 'trigger_character', trigger_character = char_under_cursor }) + + -- show if we currently have a context, and we've moved outside of it's bounds by 1 char + elseif is_keyword and trigger.context ~= nil and cursor_col == trigger.context.bounds.start_col - 1 then + trigger.context = nil + trigger.show({ trigger_kind = 'keyword' }) + + -- prefetch completions without opening window on InsertEnter + elseif event == 'InsertEnter' and config.prefetch_on_insert then + trigger.show({ trigger_kind = 'prefetch' }) + + -- otherwise hide + else + trigger.hide() + end + end + + trigger.buffer_events:listen({ + on_char_added = on_char_added, + on_cursor_moved = on_cursor_moved, + on_insert_leave = function() trigger.hide() end, + }) + trigger.cmdline_events:listen({ + on_char_added = on_char_added, + on_cursor_moved = on_cursor_moved, + on_leave = function() trigger.hide() end, + }) +end + +function trigger.is_keyword_character(char) + -- special case for hyphen, since we don't consider a lone hyphen to be a keyword + if char == '-' then return true end + + local keyword_start_col, keyword_end_col = require('blink.cmp.fuzzy').get_keyword_range(char, #char, 'prefix') + return keyword_start_col ~= keyword_end_col +end + +function trigger.is_trigger_character(char, is_show_on_x) + local sources = require('blink.cmp.sources.lib') + local is_trigger = vim.tbl_contains(sources.get_trigger_characters(context.get_mode()), char) + + local show_on_blocked_trigger_characters = type(config.show_on_blocked_trigger_characters) == 'function' + and config.show_on_blocked_trigger_characters() + or config.show_on_blocked_trigger_characters + --- @cast show_on_blocked_trigger_characters string[] + local show_on_x_blocked_trigger_characters = type(config.show_on_x_blocked_trigger_characters) == 'function' + and config.show_on_x_blocked_trigger_characters() + or config.show_on_x_blocked_trigger_characters + --- @cast show_on_x_blocked_trigger_characters string[] + + local is_blocked = vim.tbl_contains(show_on_blocked_trigger_characters, char) + or (is_show_on_x and vim.tbl_contains(show_on_x_blocked_trigger_characters, char)) + + return is_trigger and not is_blocked +end + +--- Suppresses on_hide and on_show events for the duration of the callback +function trigger.suppress_events_for_callback(cb) + local mode = vim.api.nvim_get_mode().mode == 'c' and 'cmdline' or 'default' + + local events = mode == 'default' and trigger.buffer_events or trigger.cmdline_events + if not events then return cb() end + + events:suppress_events_for_callback(cb) +end + +function trigger.show_if_on_trigger_character(opts) + if + (opts and opts.is_accept) + and (not config.show_on_trigger_character or not config.show_on_accept_on_trigger_character) + then + return + end + + local cursor_col = context.get_cursor()[2] + local char_under_cursor = context.get_line():sub(cursor_col, cursor_col) + + if trigger.is_trigger_character(char_under_cursor, true) then + trigger.show({ trigger_kind = 'trigger_character', trigger_character = char_under_cursor }) + end +end + +function trigger.show(opts) + if not require('blink.cmp.config').enabled() then return trigger.hide() end + + opts = opts or {} + + -- already triggered at this position, ignore + local mode = context.get_mode() + local cursor = context.get_cursor() + if + not opts.force + and trigger.context ~= nil + and trigger.context.mode == mode + and cursor[1] == trigger.context.cursor[1] + and cursor[2] == trigger.context.cursor[2] + then + return + end + + -- update the context id to indicate a new context, and not an update to an existing context + if trigger.context == nil or opts.providers ~= nil then + trigger.current_context_id = trigger.current_context_id + 1 + end + + local providers = opts.providers + or (trigger.context and trigger.context.providers) + or require('blink.cmp.sources.lib').get_enabled_provider_ids(context.get_mode()) + + local initial_trigger_kind = trigger.context and trigger.context.trigger.initial_kind or opts.trigger_kind + -- if we prefetched, don't keep that as the initial trigger kind + if initial_trigger_kind == 'prefetch' then initial_trigger_kind = opts.trigger_kind end + -- if we're manually triggering, set it as the initial trigger kind + if opts.trigger_kind == 'manual' then initial_trigger_kind = 'manual' end + + trigger.context = context.new({ + id = trigger.current_context_id, + providers = providers, + initial_trigger_kind = initial_trigger_kind, + initial_trigger_character = trigger.context and trigger.context.trigger.initial_character or opts.trigger_character, + trigger_kind = opts.trigger_kind, + trigger_character = opts.trigger_character, + }) + + if opts.send_upstream ~= false then trigger.show_emitter:emit({ context = trigger.context }) end + return trigger.context +end + +function trigger.hide() + if not trigger.context then return end + trigger.context = nil + trigger.hide_emitter:emit() +end + +return trigger diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/trigger/utils.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/trigger/utils.lua new file mode 100644 index 0000000..b2878c2 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/trigger/utils.lua @@ -0,0 +1,30 @@ +local context = require('blink.cmp.completion.trigger.context') +local utils = {} + +--- Gets the full Unicode character at cursor position +--- @return string +function utils.get_char_at_cursor() + local line = context.get_line() + if line == '' then return '' end + local cursor_col = context.get_cursor()[2] + + -- Find the start of the UTF-8 character + local start_col = cursor_col + while start_col > 1 do + local char = string.byte(line:sub(start_col, start_col)) + if char < 0x80 or char > 0xBF then break end + start_col = start_col - 1 + end + + -- Find the end of the UTF-8 character + local end_col = cursor_col + while end_col < #line do + local char = string.byte(line:sub(end_col + 1, end_col + 1)) + if char < 0x80 or char > 0xBF then break end + end_col = end_col + 1 + end + + return line:sub(start_col, end_col) +end + +return utils diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/windows/documentation.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/windows/documentation.lua new file mode 100644 index 0000000..69a6dd8 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/windows/documentation.lua @@ -0,0 +1,228 @@ +--- @class blink.cmp.CompletionDocumentationWindow +--- @field win blink.cmp.Window +--- @field last_context_id? number +--- @field auto_show_timer uv_timer_t +--- @field shown_item? blink.cmp.CompletionItem +--- +--- @field auto_show_item fun(context: blink.cmp.Context, item: blink.cmp.CompletionItem) +--- @field show_item fun(context: blink.cmp.Context, item: blink.cmp.CompletionItem) +--- @field update_position fun() +--- @field scroll_up fun(amount: number) +--- @field scroll_down fun(amount: number) +--- @field close fun() + +local config = require('blink.cmp.config').completion.documentation +local win_config = config.window + +local sources = require('blink.cmp.sources.lib') +local menu = require('blink.cmp.completion.windows.menu') + +--- @type blink.cmp.CompletionDocumentationWindow +--- @diagnostic disable-next-line: missing-fields +local docs = { + win = require('blink.cmp.lib.window').new({ + min_width = win_config.min_width, + max_width = win_config.max_width, + max_height = win_config.max_height, + border = win_config.border, + winblend = win_config.winblend, + winhighlight = win_config.winhighlight, + scrollbar = win_config.scrollbar, + wrap = true, + filetype = 'blink-cmp-documentation', + scrolloff = 0, + }), + last_context_id = nil, + auto_show_timer = vim.uv.new_timer(), +} + +menu.position_update_emitter:on(function() docs.update_position() end) +menu.close_emitter:on(function() docs.close() end) + +function docs.auto_show_item(context, item) + docs.auto_show_timer:stop() + if docs.win:is_open() then + docs.auto_show_timer:start(config.update_delay_ms, 0, function() + vim.schedule(function() docs.show_item(context, item) end) + end) + elseif config.auto_show then + docs.auto_show_timer:start(config.auto_show_delay_ms, 0, function() + vim.schedule(function() docs.show_item(context, item) end) + end) + end +end + +function docs.show_item(context, item) + docs.auto_show_timer:stop() + if item == nil or not menu.win:is_open() then return docs.win:close() end + + -- TODO: cancellation + -- TODO: only resolve if documentation does not exist + sources + .resolve(context, item) + ---@param item blink.cmp.CompletionItem + :map(function(item) + if item.documentation == nil and item.detail == nil then + docs.close() + return + end + + if docs.shown_item ~= item then + --- @type blink.cmp.RenderDetailAndDocumentationOpts + local default_render_opts = { + bufnr = docs.win:get_buf(), + detail = item.detail, + documentation = item.documentation, + max_width = docs.win.config.max_width, + use_treesitter_highlighting = config and config.treesitter_highlighting, + } + local render = require('blink.cmp.lib.window.docs').render_detail_and_documentation + + if item.documentation and item.documentation.render ~= nil then + -- let the provider render the documentation and optionally override + -- the default rendering + item.documentation.render({ + item = item, + window = docs.win, + default_implementation = function(opts) render(vim.tbl_extend('force', default_render_opts, opts)) end, + }) + else + render(default_render_opts) + end + end + docs.shown_item = item + + if menu.win:get_win() then + docs.win:open() + docs.win:set_cursor({ 1, 0 }) -- reset scroll + docs.update_position() + end + end) + :catch(function(err) vim.notify(err, vim.log.levels.ERROR, { title = 'blink.cmp' }) end) +end + +-- TODO: compensate for wrapped lines +function docs.scroll_up(amount) + local winnr = docs.win:get_win() + if winnr == nil then return end + + local top_line = math.max(1, vim.fn.line('w0', winnr)) + local desired_line = math.max(1, top_line - amount) + + docs.win:set_cursor({ desired_line, 0 }) +end + +-- TODO: compensate for wrapped lines +function docs.scroll_down(amount) + local winnr = docs.win:get_win() + if winnr == nil then return end + + local line_count = vim.api.nvim_buf_line_count(docs.win:get_buf()) + local bottom_line = math.max(1, vim.fn.line('w$', winnr)) + local desired_line = math.min(line_count, bottom_line + amount) + + docs.win:set_cursor({ desired_line, 0 }) +end + +function docs.update_position() + if not docs.win:is_open() or not menu.win:is_open() then return end + + docs.win:update_size() + + local menu_winnr = menu.win:get_win() + if not menu_winnr then return end + local menu_win_config = vim.api.nvim_win_get_config(menu_winnr) + local menu_win_height = menu.win:get_height() + local menu_border_size = menu.win:get_border_size() + + local cursor_win_row = vim.fn.winline() + + -- decide direction priority based on the menu window's position + local menu_win_is_up = menu_win_config.row - cursor_win_row < 0 + local direction_priority = menu_win_is_up and win_config.direction_priority.menu_north + or win_config.direction_priority.menu_south + + -- remove the direction priority of the signature window if it's open + local signature = require('blink.cmp.signature.window') + if signature.win and signature.win:is_open() then + direction_priority = vim.tbl_filter( + function(dir) return dir ~= (menu_win_is_up and 's' or 'n') end, + direction_priority + ) + end + + -- decide direction, width and height of window + local win_width = docs.win:get_width() + local win_height = docs.win:get_height() + local pos = docs.win:get_direction_with_window_constraints(menu.win, direction_priority, { + width = math.min(win_width, win_config.desired_min_width), + height = math.min(win_height, win_config.desired_min_height), + }) + + -- couldn't find anywhere to place the window + if not pos then + docs.win:close() + return + end + + -- set width and height based on available space + docs.win:set_height(pos.height) + docs.win:set_width(pos.width) + + -- set position based on provided direction + + local height = docs.win:get_height() + local width = docs.win:get_width() + + local function set_config(opts) + docs.win:set_win_config({ relative = 'win', win = menu_winnr, row = opts.row, col = opts.col }) + end + if pos.direction == 'n' then + if menu_win_is_up then + set_config({ row = -height - menu_border_size.top, col = -menu_border_size.left }) + else + set_config({ row = -1 - height - menu_border_size.top, col = -menu_border_size.left }) + end + elseif pos.direction == 's' then + if menu_win_is_up then + set_config({ + row = 1 + menu_win_height - menu_border_size.top, + col = -menu_border_size.left, + }) + else + set_config({ + row = menu_win_height - menu_border_size.top, + col = -menu_border_size.left, + }) + end + elseif pos.direction == 'e' then + if menu_win_is_up and menu_win_height < height then + set_config({ + row = menu_win_height - menu_border_size.top - height, + col = menu_win_config.width + menu_border_size.right, + }) + else + set_config({ + row = -menu_border_size.top, + col = menu_win_config.width + menu_border_size.right, + }) + end + elseif pos.direction == 'w' then + if menu_win_is_up and menu_win_height < height then + set_config({ + row = menu_win_height - menu_border_size.top - height, + col = -width - menu_border_size.left, + }) + else + set_config({ row = -menu_border_size.top, col = -width - menu_border_size.left }) + end + end +end + +function docs.close() + docs.win:close() + docs.auto_show_timer:stop() + docs.shown_item = nil +end + +return docs diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/windows/ghost_text.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/windows/ghost_text.lua new file mode 100644 index 0000000..2869e95 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/windows/ghost_text.lua @@ -0,0 +1,100 @@ +local config = require('blink.cmp.config').completion.ghost_text +local highlight_ns = require('blink.cmp.config').appearance.highlight_ns +local text_edits_lib = require('blink.cmp.lib.text_edits') +local snippets_utils = require('blink.cmp.sources.snippets.utils') + +--- @class blink.cmp.windows.GhostText +--- @field win integer? +--- @field selected_item blink.cmp.CompletionItem? +--- @field extmark_id integer? +--- +--- @field is_open fun(): boolean +--- @field show_preview fun(item: blink.cmp.CompletionItem) +--- @field clear_preview fun() +--- @field draw_preview fun(bufnr: number) + +--- @type blink.cmp.windows.GhostText +--- @diagnostic disable-next-line: missing-fields +local ghost_text = { + win = nil, + selected_item = nil, + extmark_id = nil, +} + +--- @param textEdit lsp.TextEdit +local function get_still_untyped_text(textEdit) + local type_text_length = textEdit.range['end'].character - textEdit.range.start.character + return textEdit.newText:sub(type_text_length + 1) +end + +-- immediately re-draw the preview when the cursor moves/text changes +vim.api.nvim_create_autocmd({ 'CursorMovedI', 'TextChangedI' }, { + callback = function() + if config.enabled and ghost_text.win then ghost_text.draw_preview(vim.api.nvim_win_get_buf(ghost_text.win)) end + end, +}) + +function ghost_text.is_open() return ghost_text.extmark_id ~= nil end + +--- @param selected_item? blink.cmp.CompletionItem +function ghost_text.show_preview(selected_item) + -- nothing to show, clear the preview + if not selected_item then + ghost_text.clear_preview() + return + end + + -- doesn't work in command mode + -- TODO: integrate with noice.nvim? + if vim.api.nvim_get_mode().mode == 'c' then return end + + -- update state and redraw + local changed = ghost_text.selected_item ~= selected_item + ghost_text.selected_item = selected_item + ghost_text.win = vim.api.nvim_get_current_win() + if changed then ghost_text.draw_preview(vim.api.nvim_win_get_buf(ghost_text.win)) end +end + +function ghost_text.clear_preview() + ghost_text.selected_item = nil + ghost_text.win = nil + if ghost_text.extmark_id ~= nil then + vim.api.nvim_buf_del_extmark(0, highlight_ns, ghost_text.extmark_id) + ghost_text.extmark_id = nil + end +end + +function ghost_text.draw_preview(bufnr) + if not ghost_text.selected_item then return end + + local text_edit = text_edits_lib.get_from_item(ghost_text.selected_item) + + if ghost_text.selected_item.insertTextFormat == vim.lsp.protocol.InsertTextFormat.Snippet then + local expanded_snippet = snippets_utils.safe_parse(text_edit.newText) + text_edit.newText = expanded_snippet and tostring(expanded_snippet) or text_edit.newText + end + + local display_lines = vim.split(get_still_untyped_text(text_edit), '\n', { plain = true }) or {} + + local virt_lines = {} + if #display_lines > 1 then + for i = 2, #display_lines do + virt_lines[i - 1] = { { display_lines[i], 'BlinkCmpGhostText' } } + end + end + + local cursor_pos = { + text_edit.range.start.line, + text_edit.range['end'].character, + } + + ghost_text.extmark_id = vim.api.nvim_buf_set_extmark(bufnr, highlight_ns, cursor_pos[1], cursor_pos[2], { + id = ghost_text.extmark_id, + virt_text_pos = 'inline', + virt_text = { { display_lines[1], 'BlinkCmpGhostText' } }, + virt_lines = virt_lines, + hl_mode = 'combine', + }) +end + +return ghost_text diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/windows/menu.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/windows/menu.lua new file mode 100644 index 0000000..a749ddd --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/windows/menu.lua @@ -0,0 +1,136 @@ +--- @class blink.cmp.CompletionMenu +--- @field win blink.cmp.Window +--- @field items blink.cmp.CompletionItem[] +--- @field renderer blink.cmp.Renderer +--- @field selected_item_idx? number +--- @field context blink.cmp.Context? +--- @field open_emitter blink.cmp.EventEmitter<{}> +--- @field close_emitter blink.cmp.EventEmitter<{}> +--- @field position_update_emitter blink.cmp.EventEmitter<{}> +--- +--- @field open_with_items fun(context: blink.cmp.Context, items: blink.cmp.CompletionItem[]) +--- @field open fun() +--- @field close fun() +--- @field set_selected_item_idx fun(idx?: number) +--- @field update_position fun() +--- @field redraw_if_needed fun() + +local config = require('blink.cmp.config').completion.menu + +--- @type blink.cmp.CompletionMenu +--- @diagnostic disable-next-line: missing-fields +local menu = { + win = require('blink.cmp.lib.window').new({ + min_width = config.min_width, + max_height = config.max_height, + border = config.border, + winblend = config.winblend, + winhighlight = config.winhighlight, + cursorline = false, + scrolloff = config.scrolloff, + scrollbar = config.scrollbar, + filetype = 'blink-cmp-menu', + }), + items = {}, + context = nil, + auto_show = config.auto_show, + open_emitter = require('blink.cmp.lib.event_emitter').new('completion_menu_open', 'BlinkCmpMenuOpen'), + close_emitter = require('blink.cmp.lib.event_emitter').new('completion_menu_close', 'BlinkCmpMenuClose'), + position_update_emitter = require('blink.cmp.lib.event_emitter').new( + 'completion_menu_position_update', + 'BlinkCmpMenuPositionUpdate' + ), +} + +vim.api.nvim_create_autocmd({ 'CursorMovedI', 'WinScrolled', 'WinResized' }, { + callback = function() menu.update_position() end, +}) + +function menu.open_with_items(context, items) + menu.context = context + menu.items = items + menu.selected_item_idx = menu.selected_item_idx ~= nil and math.min(menu.selected_item_idx, #items) or nil + + if not menu.renderer then menu.renderer = require('blink.cmp.completion.windows.render').new(config.draw) end + menu.renderer:draw(context, menu.win:get_buf(), items) + + local auto_show = menu.auto_show + if type(auto_show) == 'function' then auto_show = auto_show(context, items) end + if auto_show then + menu.open() + menu.update_position() + end +end + +function menu.open() + if menu.win:is_open() then return end + + menu.win:open() + if menu.selected_item_idx ~= nil then + vim.api.nvim_win_set_cursor(menu.win:get_win(), { menu.selected_item_idx, 0 }) + end + + menu.open_emitter:emit() +end + +function menu.close() + menu.auto_show = config.auto_show + if not menu.win:is_open() then return end + + menu.win:close() + menu.close_emitter:emit() +end + +function menu.set_selected_item_idx(idx) + menu.win:set_option_value('cursorline', idx ~= nil) + menu.selected_item_idx = idx + if menu.win:is_open() then menu.win:set_cursor({ idx or 1, 0 }) end +end + +--- TODO: Don't switch directions if the context is the same +function menu.update_position() + local context = menu.context + if context == nil then return end + + local win = menu.win + if not win:is_open() then return end + + win:update_size() + + local border_size = win:get_border_size() + local pos = win:get_vertical_direction_and_height(config.direction_priority) + + -- couldn't find anywhere to place the window + if not pos then + win:close() + return + end + + local alignment_start_col = menu.renderer:get_alignment_start_col() + + -- place the window at the start col of the current text we're fuzzy matching against + -- so the window doesnt move around as we type + local row = pos.direction == 's' and 1 or -pos.height - border_size.vertical + + if vim.api.nvim_get_mode().mode == 'c' then + local cmdline_position = config.cmdline_position() + win:set_win_config({ + relative = 'editor', + row = cmdline_position[1] + row, + col = math.max(cmdline_position[2] + context.bounds.start_col - alignment_start_col, 0), + }) + else + local cursor_col = context.get_cursor()[2] + + local col = context.bounds.start_col - alignment_start_col - cursor_col - 1 - border_size.left + if config.draw.align_to == 'cursor' then col = 0 end + + win:set_win_config({ relative = 'cursor', row = row, col = col }) + end + + win:set_height(pos.height) + + menu.position_update_emitter:emit() +end + +return menu diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/windows/render/column.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/windows/render/column.lua new file mode 100644 index 0000000..b9a75d0 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/windows/render/column.lua @@ -0,0 +1,120 @@ +--- @class blink.cmp.DrawColumn +--- @field components blink.cmp.DrawComponent[] +--- @field gap number +--- @field lines string[][] +--- @field width number +--- @field ctxs blink.cmp.DrawItemContext[] +--- +--- @field new fun(components: blink.cmp.DrawComponent[], gap: number): blink.cmp.DrawColumn +--- @field render fun(self: blink.cmp.DrawColumn, ctxs: blink.cmp.DrawItemContext[]) +--- @field get_line_text fun(self: blink.cmp.DrawColumn, line_idx: number): string +--- @field get_line_highlights fun(self: blink.cmp.DrawColumn, line_idx: number): blink.cmp.DrawHighlight[] + +local text_lib = require('blink.cmp.completion.windows.render.text') + +--- @type blink.cmp.DrawColumn +--- @diagnostic disable-next-line: missing-fields +local column = {} + +function column.new(components, gap) + local self = setmetatable({}, { __index = column }) + self.components = components + self.gap = gap + self.lines = {} + self.width = 0 + self.ctxs = {} + return self +end + +function column:render(ctxs) + --- render text and get the max widths of each component + --- @type string[][] + local lines = {} + local max_component_widths = {} + for _, ctx in ipairs(ctxs) do + --- @type string[] + local line = {} + for component_idx, component in ipairs(self.components) do + local text = text_lib.apply_component_width(component.text(ctx) or '', component) + table.insert(line, text) + max_component_widths[component_idx] = + math.max(max_component_widths[component_idx] or 0, vim.api.nvim_strwidth(text)) + end + table.insert(lines, line) + end + + --- get the total width of the column + local column_width = 0 + for _, max_component_width in ipairs(max_component_widths) do + if max_component_width > 0 then column_width = column_width + max_component_width + self.gap end + end + column_width = math.max(column_width - self.gap, 0) + + --- find the component that will fill the empty space + local fill_idx = -1 + for component_idx, component in ipairs(self.components) do + if component.width and component.width.fill then + fill_idx = component_idx + break + end + end + if fill_idx == -1 then fill_idx = #self.components end + + --- and add extra spaces until we reach the column width + for _, line in ipairs(lines) do + local line_width = 0 + for _, component_text in ipairs(line) do + if #component_text > 0 then line_width = line_width + vim.api.nvim_strwidth(component_text) + self.gap end + end + line_width = line_width - self.gap + local remaining_width = column_width - line_width + line[fill_idx] = text_lib.pad(line[fill_idx], vim.api.nvim_strwidth(line[fill_idx]) + remaining_width) + end + + -- store results for later + self.width = column_width + self.lines = lines + self.ctxs = ctxs +end + +function column:get_line_text(line_idx) + local text = '' + local line = self.lines[line_idx] + for _, component in ipairs(line) do + if #component > 0 then text = text .. component .. string.rep(' ', self.gap) end + end + return text:sub(1, -self.gap - 1) +end + +function column:get_line_highlights(line_idx) + local ctx = self.ctxs[line_idx] + local offset = 0 + local highlights = {} + + for component_idx, component in ipairs(self.components) do + local text = self.lines[line_idx][component_idx] + if #text > 0 then + local column_highlights = type(component.highlight) == 'function' and component.highlight(ctx, text) + or component.highlight + + if type(column_highlights) == 'string' then + table.insert(highlights, { offset, offset + #text, group = column_highlights }) + elseif type(column_highlights) == 'table' then + for _, highlight in ipairs(column_highlights) do + table.insert(highlights, { + offset + highlight[1], + offset + highlight[2], + group = highlight.group, + params = highlight.params, + }) + end + end + + offset = offset + #text + self.gap + end + end + + return highlights +end + +return column diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/windows/render/context.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/windows/render/context.lua new file mode 100644 index 0000000..301825f --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/windows/render/context.lua @@ -0,0 +1,85 @@ +--- @class blink.cmp.DrawItemContext +--- @field self blink.cmp.Draw +--- @field item blink.cmp.CompletionItem +--- @field idx number +--- @field label string +--- @field label_detail string +--- @field label_description string +--- @field label_matched_indices number[] +--- @field kind string +--- @field kind_icon string +--- @field icon_gap string +--- @field deprecated boolean +--- @field source_id string +--- @field source_name string + +local draw_context = {} + +--- @param context blink.cmp.Context +--- @param draw blink.cmp.Draw +--- @param items blink.cmp.CompletionItem[] +--- @return blink.cmp.DrawItemContext[] +function draw_context.get_from_items(context, draw, items) + local matched_indices = require('blink.cmp.fuzzy').fuzzy_matched_indices( + context.get_line(), + context.get_cursor()[2], + vim.tbl_map(function(item) return item.label end, items), + require('blink.cmp.config').completion.keyword.range + ) + + local ctxs = {} + for idx, item in ipairs(items) do + ctxs[idx] = draw_context.new(draw, idx, item, matched_indices[idx]) + end + return ctxs +end + +local config = require('blink.cmp.config').appearance +local kinds = require('blink.cmp.types').CompletionItemKind + +--- @param draw blink.cmp.Draw +--- @param item_idx number +--- @param item blink.cmp.CompletionItem +--- @param matched_indices number[] +--- @return blink.cmp.DrawItemContext +function draw_context.new(draw, item_idx, item, matched_indices) + local kind = kinds[item.kind] or 'Unknown' + local kind_icon = require('blink.cmp.completion.windows.render.tailwind').get_kind_icon(item) + or config.kind_icons[kind] + or config.kind_icons.Field + local icon_spacing = config.nerd_font_variant == 'mono' and '' or ' ' + + -- Some LSPs can return labels with newlines + -- Escape them to avoid errors in nvim_buf_set_lines when rendering the completion menu + local newline_char = '↲' .. icon_spacing + + local label = item.label:gsub('\n', newline_char) .. (kind == 'Snippet' and '~' or '') + if config.nerd_font_variant == 'normal' then label = label:gsub('…', '… ') end + + local label_detail = (item.labelDetails and item.labelDetails.detail or ''):gsub('\n', newline_char) + if config.nerd_font_variant == 'normal' then label_detail = label_detail:gsub('…', '… ') end + + local label_description = (item.labelDetails and item.labelDetails.description or ''):gsub('\n', newline_char) + if config.nerd_font_variant == 'normal' then label_description = label_description:gsub('…', '… ') end + + local source_id = item.source_id + local source_name = item.source_name + + return { + self = draw, + item = item, + idx = item_idx, + label = label, + label_detail = label_detail, + label_description = label_description, + label_matched_indices = matched_indices, + kind = kind, + kind_icon = kind_icon, + icon_gap = config.nerd_font_variant == 'mono' and '' or ' ', + deprecated = item.deprecated or (item.tags and vim.tbl_contains(item.tags, 1)) or false, + source_id = source_id, + source_name = source_name, + } +end + +return draw_context diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/windows/render/init.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/windows/render/init.lua new file mode 100644 index 0000000..1422ec2 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/windows/render/init.lua @@ -0,0 +1,146 @@ +--- @class blink.cmp.Renderer +--- @field def blink.cmp.Draw +--- @field padding number[] +--- @field gap number +--- @field columns blink.cmp.DrawColumn[] +--- +--- @field new fun(draw: blink.cmp.Draw): blink.cmp.Renderer +--- @field draw fun(self: blink.cmp.Renderer, context: blink.cmp.Context, bufnr: number, items: blink.cmp.CompletionItem[]) +--- @field get_component_column_location fun(self: blink.cmp.Renderer, component_name: string): { column_idx: number, component_idx: number } +--- @field get_component_start_col fun(self: blink.cmp.Renderer, component_name: string): number +--- @field get_alignment_start_col fun(self: blink.cmp.Renderer): number + +local ns = vim.api.nvim_create_namespace('blink_cmp_renderer') + +--- @type blink.cmp.Renderer +--- @diagnostic disable-next-line: missing-fields +local renderer = {} + +function renderer.new(draw) + --- Convert the component names in the columns to the component definitions + --- @type blink.cmp.DrawComponent[][] + local columns_definitions = vim.tbl_map(function(column) + local components = {} + for _, component_name in ipairs(column) do + local component = draw.components[component_name] + assert(component ~= nil, 'No component definition found for component: "' .. component_name .. '"') + table.insert(components, draw.components[component_name]) + end + + return { + components = components, + gap = column.gap or 0, + } + end, draw.columns) + + local padding = type(draw.padding) == 'number' and { draw.padding, draw.padding } or draw.padding + --- @cast padding number[] + + local self = setmetatable({}, { __index = renderer }) + self.padding = padding + self.gap = draw.gap + self.def = draw + self.columns = vim.tbl_map( + function(column_definition) + return require('blink.cmp.completion.windows.render.column').new( + column_definition.components, + column_definition.gap + ) + end, + columns_definitions + ) + return self +end + +function renderer:draw(context, bufnr, items) + -- gather contexts + local draw_contexts = require('blink.cmp.completion.windows.render.context').get_from_items(context, self.def, items) + + -- render the columns + for _, column in ipairs(self.columns) do + column:render(draw_contexts) + end + + -- apply to the buffer + local lines = {} + for idx, _ in ipairs(draw_contexts) do + local line = '' + if self.padding[1] > 0 then line = string.rep(' ', self.padding[1]) end + + for _, column in ipairs(self.columns) do + local text = column:get_line_text(idx) + if #text > 0 then line = line .. text .. string.rep(' ', self.gap) end + end + line = line:sub(1, -self.gap - 1) + + if self.padding[2] > 0 then line = line .. string.rep(' ', self.padding[2]) end + + table.insert(lines, line) + end + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) + vim.api.nvim_set_option_value('modified', false, { buf = bufnr }) + + -- Setting highlights is slow and we update on every keystroke so we instead use a decoration provider + -- which will only render highlights of the visible lines. This also avoids having to do virtual scroll + -- like nvim-cmp does, which breaks on UIs like neovide + vim.api.nvim_set_decoration_provider(ns, { + on_win = function(_, _, win_bufnr) return bufnr == win_bufnr end, + on_line = function(_, _, _, line) + local offset = self.padding[1] + for _, column in ipairs(self.columns) do + local text = column:get_line_text(line + 1) + if #text > 0 then + local highlights = column:get_line_highlights(line + 1) + for _, highlight in ipairs(highlights) do + local col = offset + highlight[1] + local end_col = offset + highlight[2] + vim.api.nvim_buf_set_extmark(bufnr, ns, line, col, { + end_col = end_col, + hl_group = highlight.group, + hl_mode = 'combine', + hl_eol = true, + ephemeral = true, + }) + end + offset = offset + #text + self.gap + end + end + end, + }) +end + +function renderer:get_component_column_location(component_name) + for column_idx, column in ipairs(self.def.columns) do + for component_idx, other_component_name in ipairs(column) do + if other_component_name == component_name then return { column_idx, component_idx } end + end + end + error('No component found with name: ' .. component_name) +end + +function renderer:get_component_start_col(component_name) + local column_idx, component_idx = unpack(self:get_component_column_location(component_name)) + + -- add previous columns + local start_col = self.padding[1] + for i = 1, column_idx - 1 do + start_col = start_col + self.columns[i].width + self.gap + end + + -- add previous components + local line = self.columns[column_idx].lines[1] + if not line then return start_col end + for i = 1, component_idx - 1 do + start_col = start_col + #line[i] + end + + return start_col +end + +function renderer:get_alignment_start_col() + local component_name = self.def.align_to + if component_name == nil or component_name == 'none' or component_name == 'cursor' then return 0 end + return self:get_component_start_col(component_name) +end + +return renderer diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/windows/render/tailwind.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/windows/render/tailwind.lua new file mode 100644 index 0000000..0bff6b2 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/windows/render/tailwind.lua @@ -0,0 +1,31 @@ +local tailwind = {} + +local kinds = require('blink.cmp.types').CompletionItemKind + +--- @param item blink.cmp.CompletionItem +--- @return string|nil +function tailwind.get_hex_color(item) + local doc = item.documentation + if item.kind ~= kinds.Color or not doc then return end + local content = type(doc) == 'string' and doc or doc.value + if content and content:match('^#%x%x%x%x%x%x$') then return content end +end + +--- @param item blink.cmp.CompletionItem +--- @return string? +function tailwind.get_kind_icon(item) + if tailwind.get_hex_color(item) then return '██' end +end + +--- @param ctx blink.cmp.DrawItemContext +--- @return string|nil +function tailwind.get_hl(ctx) + local hex_color = tailwind.get_hex_color(ctx.item) + if not hex_color then return end + + local hl_name = 'HexColor' .. hex_color:sub(2) + if #vim.api.nvim_get_hl(0, { name = hl_name }) == 0 then vim.api.nvim_set_hl(0, hl_name, { fg = hex_color }) end + return hl_name +end + +return tailwind diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/windows/render/text.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/windows/render/text.lua new file mode 100644 index 0000000..b614edc --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/windows/render/text.lua @@ -0,0 +1,72 @@ +local config = require('blink.cmp.config') +local text_lib = {} + +--- Applies the component width settings to the text +--- @param text string +--- @param component blink.cmp.DrawComponent +--- @return string text +function text_lib.apply_component_width(text, component) + local width = component.width or {} + if width.fixed ~= nil then return text_lib.set_width(text, width.fixed, component) end + if width.min ~= nil then text = text_lib.pad(text, width.min) end + if width.max ~= nil then text = text_lib.truncate(text, width.max, component.ellipsis) end + return text +end + +--- Sets the text width to the given width +--- @param text string +--- @param width number +--- @param component blink.cmp.DrawComponent +--- @return string text +function text_lib.set_width(text, width, component) + local length = vim.api.nvim_strwidth(text) + if length > width then + return text_lib.truncate(text, width, component.ellipsis) + elseif length < width then + return text_lib.pad(text, width) + else + return text + end +end + +--- Truncates the text to the given width +--- @param text string +--- @param target_width number +--- @param ellipsis? boolean +--- @return string truncated_text +function text_lib.truncate(text, target_width, ellipsis) + local ellipsis_str = ellipsis ~= false and '…' or '' + if ellipsis ~= false and config.nerd_font_variant == 'normal' then ellipsis_str = ellipsis_str .. ' ' end + + local text_width = vim.api.nvim_strwidth(text) + local ellipsis_width = vim.api.nvim_strwidth(ellipsis_str) + if text_width > target_width then + return vim.fn.strcharpart(text, 0, target_width - ellipsis_width) .. ellipsis_str + end + return text +end + +--- Pads the text to the given width +--- @param text string +--- @param target_width number +--- @return string padded_text The amount of padding added to the left and the padded text +function text_lib.pad(text, target_width) + local text_width = vim.api.nvim_strwidth(text) + if text_width >= target_width then return text end + return text .. string.rep(' ', target_width - text_width) + + -- if alignment == 'left' then + -- return 0, text .. string.rep(' ', target_width - text_width) + -- elseif alignment == 'center' then + -- local extra_space = target_width - text_width + -- local half_width_start = math.floor(extra_space / 2) + -- local half_width_end = math.ceil(extra_space / 2) + -- return half_width_start, string.rep(' ', half_width_start) .. text .. string.rep(' ', half_width_end) + -- elseif alignment == 'right' then + -- return target_width - text_width, string.rep(' ', target_width - text_width) .. text + -- else + -- error('Invalid alignment: ' .. alignment) + -- end +end + +return text_lib diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/windows/render/treesitter.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/windows/render/treesitter.lua new file mode 100644 index 0000000..901c46a --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/windows/render/treesitter.lua @@ -0,0 +1,70 @@ +local treesitter = {} + +---@type table<string, blink.cmp.DrawHighlight[]> +local cache = {} +local cache_size = 0 +local MAX_CACHE_SIZE = 1000 + +--- @param ctx blink.cmp.DrawItemContext +--- @param opts? {offset?: number} +function treesitter.highlight(ctx, opts) + local ret = cache[ctx.label] + if not ret then + -- cleanup cache if it's too big + cache_size = cache_size + 1 + if cache_size > MAX_CACHE_SIZE then + cache = {} + cache_size = 0 + end + ret = treesitter._highlight(ctx) + cache[ctx.label] = ret + end + + -- offset highlights if needed + if opts and opts.offset then + ret = vim.deepcopy(ret) + for _, hl in ipairs(ret) do + hl[1] = hl[1] + opts.offset + hl[2] = hl[2] + opts.offset + end + end + return ret +end + +--- @param ctx blink.cmp.DrawItemContext +function treesitter._highlight(ctx) + local ret = {} ---@type blink.cmp.DrawHighlight[] + + local source = ctx.label + local lang = vim.treesitter.language.get_lang(vim.bo.filetype) + if not lang then return ret end + + local ok, parser = pcall(vim.treesitter.get_string_parser, source, lang) + if not ok then return ret end + + parser:parse(true) + + parser:for_each_tree(function(tstree, tree) + if not tstree then return end + local query = vim.treesitter.query.get(tree:lang(), 'highlights') + -- Some injected languages may not have highlight queries. + if not query then return end + + for capture, node in query:iter_captures(tstree:root(), source) do + local _, start_col, _, end_col = node:range() + + ---@type string + local name = query.captures[capture] + if name ~= 'spell' then + ret[#ret + 1] = { + start_col, + end_col, + group = '@' .. name .. '.' .. lang, + } + end + end + end) + return ret +end + +return treesitter diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/windows/render/types.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/windows/render/types.lua new file mode 100644 index 0000000..186b3dc --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/completion/windows/render/types.lua @@ -0,0 +1,24 @@ +--- @class blink.cmp.Draw +--- @field align_to? string | 'none' | 'cursor' Align the window to the component with the given name, or to the cursor +--- @field padding? number | number[] Padding on the left and right of the grid +--- @field gap? number Gap between columns +--- @field columns? { [number]: string, gap?: number }[] Components to render, grouped by column +--- @field components? table<string, blink.cmp.DrawComponent> Component definitions +--- @field treesitter? string[] Use treesitter to highlight the label text of completions from these sources +--- +--- @class blink.cmp.DrawHighlight +--- @field [number] number Start and end index of the highlight +--- @field group? string Highlight group +--- @field params? table Additional parameters passed as the `params` field of the highlight +--- +--- @class blink.cmp.DrawWidth +--- @field fixed? number Fixed width +--- @field fill? boolean Fill the remaining space +--- @field min? number Minimum width +--- @field max? number Maximum width +--- +--- @class blink.cmp.DrawComponent +--- @field width? blink.cmp.DrawWidth +--- @field ellipsis? boolean Whether to add an ellipsis when truncating the text +--- @field text? fun(ctx: blink.cmp.DrawItemContext): string? Renders the text of the component +--- @field highlight? string | fun(ctx: blink.cmp.DrawItemContext, text: string): string | blink.cmp.DrawHighlight[] Renders the highlights of the component diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/appearance.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/appearance.lua new file mode 100644 index 0000000..08a97ba --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/appearance.lua @@ -0,0 +1,58 @@ +--- @class (exact) blink.cmp.AppearanceConfig +--- @field highlight_ns number +--- @field use_nvim_cmp_as_default boolean Sets the fallback highlight groups to nvim-cmp's highlight groups. Useful for when your theme doesn't support blink.cmp, will be removed in a future release. +--- @field nerd_font_variant 'mono' | 'normal' Set to 'mono' for 'Nerd Font Mono' or 'normal' for 'Nerd Font'. Adjusts spacing to ensure icons are aligned +--- @field kind_icons table<string, string> + +local validate = require('blink.cmp.config.utils').validate +local appearance = { + --- @type blink.cmp.AppearanceConfig + default = { + highlight_ns = vim.api.nvim_create_namespace('blink_cmp'), + use_nvim_cmp_as_default = false, + nerd_font_variant = 'mono', + kind_icons = { + Text = '', + Method = '', + Function = '', + Constructor = '', + + Field = '', + Variable = '', + Property = '', + + Class = '', + Interface = '', + Struct = '', + Module = '', + + Unit = '', + Value = '', + Enum = '', + EnumMember = '', + + Keyword = '', + Constant = '', + + Snippet = '', + Color = '', + File = '', + Reference = '', + Folder = '', + Event = '', + Operator = '', + TypeParameter = '', + }, + }, +} + +function appearance.validate(config) + validate('appearance', { + highlight_ns = { config.highlight_ns, 'number' }, + use_nvim_cmp_as_default = { config.use_nvim_cmp_as_default, 'boolean' }, + nerd_font_variant = { config.nerd_font_variant, 'string' }, + kind_icons = { config.kind_icons, 'table' }, + }, config) +end + +return appearance diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/completion/accept.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/completion/accept.lua new file mode 100644 index 0000000..a81aa55 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/completion/accept.lua @@ -0,0 +1,72 @@ +--- @class (exact) blink.cmp.CompletionAcceptConfig +--- @field create_undo_point boolean Create an undo point when accepting a completion item +--- @field auto_brackets blink.cmp.AutoBracketsConfig + +--- @class (exact) blink.cmp.AutoBracketsConfig +--- @field enabled boolean Whether to auto-insert brackets for functions +--- @field default_brackets string[] Default brackets to use for unknown languages +--- @field override_brackets_for_filetypes table<string, string[] | fun(item: blink.cmp.CompletionItem): string[]> +--- @field force_allow_filetypes string[] Overrides the default blocked filetypes +--- @field blocked_filetypes string[] +--- @field kind_resolution blink.cmp.AutoBracketResolutionConfig Synchronously use the kind of the item to determine if brackets should be added +--- @field semantic_token_resolution blink.cmp.AutoBracketSemanticTokenResolutionConfig Asynchronously use semantic token to determine if brackets should be added + +--- @class (exact) blink.cmp.AutoBracketResolutionConfig +--- @field enabled boolean +--- @field blocked_filetypes string[] + +--- @class (exact) blink.cmp.AutoBracketSemanticTokenResolutionConfig +--- @field enabled boolean +--- @field blocked_filetypes string[] +--- @field timeout_ms number How long to wait for semantic tokens to return before assuming no brackets should be added + +local validate = require('blink.cmp.config.utils').validate +local accept = { + --- @type blink.cmp.CompletionAcceptConfig + default = { + create_undo_point = true, + auto_brackets = { + enabled = true, + default_brackets = { '(', ')' }, + override_brackets_for_filetypes = {}, + force_allow_filetypes = {}, + blocked_filetypes = {}, + kind_resolution = { + enabled = true, + blocked_filetypes = { 'typescriptreact', 'javascriptreact', 'vue', 'rust' }, + }, + semantic_token_resolution = { + enabled = true, + blocked_filetypes = { 'java' }, + timeout_ms = 400, + }, + }, + }, +} + +function accept.validate(config) + validate('completion.accept', { + create_undo_point = { config.create_undo_point, 'boolean' }, + auto_brackets = { config.auto_brackets, 'table' }, + }, config) + validate('completion.accept.auto_brackets', { + enabled = { config.auto_brackets.enabled, 'boolean' }, + default_brackets = { config.auto_brackets.default_brackets, 'table' }, + override_brackets_for_filetypes = { config.auto_brackets.override_brackets_for_filetypes, 'table' }, + force_allow_filetypes = { config.auto_brackets.force_allow_filetypes, 'table' }, + blocked_filetypes = { config.auto_brackets.blocked_filetypes, 'table' }, + kind_resolution = { config.auto_brackets.kind_resolution, 'table' }, + semantic_token_resolution = { config.auto_brackets.semantic_token_resolution, 'table' }, + }, config.auto_brackets) + validate('completion.accept.auto_brackets.kind_resolution', { + enabled = { config.auto_brackets.kind_resolution.enabled, 'boolean' }, + blocked_filetypes = { config.auto_brackets.kind_resolution.blocked_filetypes, 'table' }, + }, config.auto_brackets.kind_resolution) + validate('completion.accept.auto_brackets.semantic_token_resolution', { + enabled = { config.auto_brackets.semantic_token_resolution.enabled, 'boolean' }, + blocked_filetypes = { config.auto_brackets.semantic_token_resolution.blocked_filetypes, 'table' }, + timeout_ms = { config.auto_brackets.semantic_token_resolution.timeout_ms, 'number' }, + }, config.auto_brackets.semantic_token_resolution) +end + +return accept diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/completion/documentation.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/completion/documentation.lua new file mode 100644 index 0000000..71784d1 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/completion/documentation.lua @@ -0,0 +1,98 @@ +--- @class (exact) blink.cmp.CompletionDocumentationConfig +--- @field auto_show boolean Controls whether the documentation window will automatically show when selecting a completion item +--- @field auto_show_delay_ms number Delay before showing the documentation window +--- @field update_delay_ms number Delay before updating the documentation window when selecting a new item, while an existing item is still visible +--- @field treesitter_highlighting boolean Whether to use treesitter highlighting, disable if you run into performance issues +--- @field window blink.cmp.CompletionDocumentationWindowConfig + +--- @class (exact) blink.cmp.CompletionDocumentationWindowConfig +--- @field min_width number +--- @field max_width number +--- @field max_height number +--- @field desired_min_width number +--- @field desired_min_height number +--- @field border blink.cmp.WindowBorder +--- @field winblend number +--- @field winhighlight string +--- @field scrollbar boolean Note that the gutter will be disabled when border ~= 'none' +--- @field direction_priority blink.cmp.CompletionDocumentationDirectionPriorityConfig Which directions to show the window, for each of the possible menu window directions, falling back to the next direction when there's not enough space + +--- @class (exact) blink.cmp.CompletionDocumentationDirectionPriorityConfig +--- @field menu_north ("n" | "s" | "e" | "w")[] +--- @field menu_south ("n" | "s" | "e" | "w")[] + +local validate = require('blink.cmp.config.utils').validate +local documentation = { + --- @type blink.cmp.CompletionDocumentationConfig + default = { + auto_show = false, + auto_show_delay_ms = 500, + update_delay_ms = 50, + treesitter_highlighting = true, + window = { + min_width = 10, + max_width = 80, + max_height = 20, + desired_min_width = 50, + desired_min_height = 10, + border = 'padded', + winblend = 0, + winhighlight = 'Normal:BlinkCmpDoc,FloatBorder:BlinkCmpDocBorder,EndOfBuffer:BlinkCmpDoc', + scrollbar = true, + direction_priority = { + menu_north = { 'e', 'w', 'n', 's' }, + menu_south = { 'e', 'w', 's', 'n' }, + }, + }, + }, +} + +function documentation.validate(config) + validate('completion.documentation', { + auto_show = { config.auto_show, 'boolean' }, + auto_show_delay_ms = { config.auto_show_delay_ms, 'number' }, + update_delay_ms = { config.update_delay_ms, 'number' }, + treesitter_highlighting = { config.treesitter_highlighting, 'boolean' }, + window = { config.window, 'table' }, + }, config) + + validate('completion.documentation.window', { + min_width = { config.window.min_width, 'number' }, + max_width = { config.window.max_width, 'number' }, + max_height = { config.window.max_height, 'number' }, + desired_min_width = { config.window.desired_min_width, 'number' }, + desired_min_height = { config.window.desired_min_height, 'number' }, + border = { config.window.border, { 'string', 'table' } }, + winblend = { config.window.winblend, 'number' }, + winhighlight = { config.window.winhighlight, 'string' }, + scrollbar = { config.window.scrollbar, 'boolean' }, + direction_priority = { config.window.direction_priority, 'table' }, + }, config.window) + + validate('completion.documentation.window.direction_priority', { + menu_north = { + config.window.direction_priority.menu_north, + function(directions) + if type(directions) ~= 'table' or #directions == 0 then return false end + for _, direction in ipairs(directions) do + if not vim.tbl_contains({ 'n', 's', 'e', 'w' }, direction) then return false end + end + return true + end, + 'one of: "n", "s", "e", "w"', + }, + menu_south = { + config.window.direction_priority.menu_south, + function(directions) + if type(directions) ~= 'table' or #directions == 0 then return false end + for _, direction in ipairs(directions) do + if not vim.tbl_contains({ 'n', 's', 'e', 'w' }, direction) then return false end + end + return true + end, + 'one of: "n", "s", "e", "w"', + }, + }, config.window.direction_priority) +end + +return documentation diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/completion/ghost_text.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/completion/ghost_text.lua new file mode 100644 index 0000000..47fb2cd --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/completion/ghost_text.lua @@ -0,0 +1,19 @@ +--- Displays a preview of the selected item on the current line +--- @class (exact) blink.cmp.CompletionGhostTextConfig +--- @field enabled boolean + +local validate = require('blink.cmp.config.utils').validate +local ghost_text = { + --- @type blink.cmp.CompletionGhostTextConfig + default = { + enabled = false, + }, +} + +function ghost_text.validate(config) + validate('completion.ghost_text', { + enabled = { config.enabled, 'boolean' }, + }, config) +end + +return ghost_text diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/completion/init.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/completion/init.lua new file mode 100644 index 0000000..24407ff --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/completion/init.lua @@ -0,0 +1,42 @@ +--- @class (exact) blink.cmp.CompletionConfig +--- @field keyword blink.cmp.CompletionKeywordConfig +--- @field trigger blink.cmp.CompletionTriggerConfig +--- @field list blink.cmp.CompletionListConfig +--- @field accept blink.cmp.CompletionAcceptConfig +--- @field menu blink.cmp.CompletionMenuConfig +--- @field documentation blink.cmp.CompletionDocumentationConfig +--- @field ghost_text blink.cmp.CompletionGhostTextConfig + +local validate = require('blink.cmp.config.utils').validate +local completion = { + default = { + keyword = require('blink.cmp.config.completion.keyword').default, + trigger = require('blink.cmp.config.completion.trigger').default, + list = require('blink.cmp.config.completion.list').default, + accept = require('blink.cmp.config.completion.accept').default, + menu = require('blink.cmp.config.completion.menu').default, + documentation = require('blink.cmp.config.completion.documentation').default, + ghost_text = require('blink.cmp.config.completion.ghost_text').default, + }, +} + +function completion.validate(config) + validate('completion', { + keyword = { config.keyword, 'table' }, + trigger = { config.trigger, 'table' }, + list = { config.list, 'table' }, + accept = { config.accept, 'table' }, + menu = { config.menu, 'table' }, + documentation = { config.documentation, 'table' }, + ghost_text = { config.ghost_text, 'table' }, + }, config) + require('blink.cmp.config.completion.keyword').validate(config.keyword) + require('blink.cmp.config.completion.trigger').validate(config.trigger) + require('blink.cmp.config.completion.list').validate(config.list) + require('blink.cmp.config.completion.accept').validate(config.accept) + require('blink.cmp.config.completion.menu').validate(config.menu) + require('blink.cmp.config.completion.documentation').validate(config.documentation) + require('blink.cmp.config.completion.ghost_text').validate(config.ghost_text) +end + +return completion diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/completion/keyword.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/completion/keyword.lua new file mode 100644 index 0000000..a922ac4 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/completion/keyword.lua @@ -0,0 +1,27 @@ +--- @class (exact) blink.cmp.CompletionKeywordConfig +--- 'prefix' will fuzzy match on the text before the cursor +--- 'full' will fuzzy match on the text before *and* after the cursor +--- example: 'foo_|_bar' will match 'foo_' for 'prefix' and 'foo__bar' for 'full' +--- @field range blink.cmp.CompletionKeywordRange +--- +--- @alias blink.cmp.CompletionKeywordRange +--- | 'prefix' Fuzzy match on the text before the cursor (example: 'foo_|bar' will match 'foo_') +--- | 'full' Fuzzy match on the text before *and* after the cursor (example: 'foo_|_bar' will match 'foo__bar') + +local validate = require('blink.cmp.config.utils').validate +local keyword = { + --- @type blink.cmp.CompletionKeywordConfig + default = { range = 'prefix' }, +} + +function keyword.validate(config) + validate('completion.keyword', { + range = { + config.range, + function(range) return vim.tbl_contains({ 'prefix', 'full' }, range) end, + 'one of: prefix, full', + }, + }, config) +end + +return keyword diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/completion/list.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/completion/list.lua new file mode 100644 index 0000000..c0c5690 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/completion/list.lua @@ -0,0 +1,54 @@ +--- @class (exact) blink.cmp.CompletionListConfig +--- @field max_items number Maximum number of items to display +--- @field selection blink.cmp.CompletionListSelectionConfig +--- @field cycle blink.cmp.CompletionListCycleConfig + +--- @class (exact) blink.cmp.CompletionListSelectionConfig +--- @field preselect boolean | fun(ctx: blink.cmp.Context): boolean When `true`, will automatically select the first item in the completion list +--- @field auto_insert boolean | fun(ctx: blink.cmp.Context): boolean When `true`, inserts the completion item automatically when selecting it. You may want to bind a key to the `cancel` command (default <C-e>) when using this option, which will both undo the selection and hide the completion menu + +--- @class (exact) blink.cmp.CompletionListCycleConfig +--- @field from_bottom boolean When `true`, calling `select_next` at the *bottom* of the completion list will select the *first* completion item. +--- @field from_top boolean When `true`, calling `select_prev` at the *top* of the completion list will select the *last* completion item. + +local validate = require('blink.cmp.config.utils').validate +local list = { + --- @type blink.cmp.CompletionListConfig + default = { + max_items = 200, + selection = { + preselect = true, + auto_insert = true, + }, + cycle = { + from_bottom = true, + from_top = true, + }, + }, +} + +function list.validate(config) + if type(config.selection) == 'function' then + error( + '`completion.list.selection` has been replaced with `completion.list.selection.preselect` and `completion.list.selection.auto_insert`. See the docs for more info: https://cmp.saghen.dev/configuration/completion.html#list' + ) + end + + validate('completion.list', { + max_items = { config.max_items, 'number' }, + selection = { config.selection, 'table' }, + cycle = { config.cycle, 'table' }, + }, config) + + validate('completion.list.selection', { + preselect = { config.selection.preselect, { 'boolean', 'function' } }, + auto_insert = { config.selection.auto_insert, { 'boolean', 'function' } }, + }, config.selection) + + validate('completion.list.cycle', { + from_bottom = { config.cycle.from_bottom, 'boolean' }, + from_top = { config.cycle.from_top, 'boolean' }, + }, config.cycle) +end + +return list diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/completion/menu.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/completion/menu.lua new file mode 100644 index 0000000..c807566 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/completion/menu.lua @@ -0,0 +1,223 @@ +local validate = require('blink.cmp.config.utils').validate + +--- @class (exact) blink.cmp.CompletionMenuConfig +--- @field enabled boolean +--- @field min_width number +--- @field max_height number +--- @field border blink.cmp.WindowBorder +--- @field winblend number +--- @field winhighlight string +--- @field scrolloff number Keep the cursor X lines away from the top/bottom of the window +--- @field scrollbar boolean Note that the gutter will be disabled when border ~= 'none' +--- @field direction_priority ("n" | "s")[] Which directions to show the window, falling back to the next direction when there's not enough space +--- @field order blink.cmp.CompletionMenuOrderConfig TODO: implement +--- @field auto_show boolean | fun(ctx: blink.cmp.Context, items: blink.cmp.CompletionItem[]): boolean Whether to automatically show the window when new completion items are available +--- @field cmdline_position fun(): number[] Screen coordinates (0-indexed) of the command line +--- @field draw blink.cmp.Draw Controls how the completion items are rendered on the popup window + +--- @class (exact) blink.cmp.CompletionMenuOrderConfig +--- @field n 'top_down' | 'bottom_up' +--- @field s 'top_down' | 'bottom_up' + +local window = { + --- @type blink.cmp.CompletionMenuConfig + default = { + enabled = true, + min_width = 15, + max_height = 10, + border = 'none', + winblend = 0, + winhighlight = 'Normal:BlinkCmpMenu,FloatBorder:BlinkCmpMenuBorder,CursorLine:BlinkCmpMenuSelection,Search:None', + -- keep the cursor X lines away from the top/bottom of the window + scrolloff = 2, + -- note that the gutter will be disabled when border ~= 'none' + scrollbar = true, + -- which directions to show the window, + -- falling back to the next direction when there's not enough space + direction_priority = { 's', 'n' }, + -- which direction previous/next items show up + -- TODO: implement + order = { n = 'bottom_up', s = 'top_down' }, + + -- Whether to automatically show the window when new completion items are available + auto_show = true, + + -- Screen coordinates of the command line + cmdline_position = function() + if vim.g.ui_cmdline_pos ~= nil then + local pos = vim.g.ui_cmdline_pos -- (1, 0)-indexed + return { pos[1] - 1, pos[2] } + end + local height = (vim.o.cmdheight == 0) and 1 or vim.o.cmdheight + return { vim.o.lines - height, 0 } + end, + + -- Controls how the completion items are rendered on the popup window + draw = { + -- Aligns the keyword you've typed to a component in the menu + align_to = 'label', -- or 'none' to disable + -- Left and right padding, optionally { left, right } for different padding on each side + padding = 1, + -- Gap between columns + gap = 1, + treesitter = {}, -- Use treesitter to highlight the label text of completions from these sources + -- Components to render, grouped by column + columns = { { 'kind_icon' }, { 'label', 'label_description', gap = 1 } }, + -- Definitions for possible components to render. Each component defines: + -- ellipsis: whether to add an ellipsis when truncating the text + -- width: control the min, max and fill behavior of the component + -- text function: will be called for each item + -- highlight function: will be called only when the line appears on screen + components = { + kind_icon = { + ellipsis = false, + text = function(ctx) return ctx.kind_icon .. ctx.icon_gap end, + highlight = function(ctx) + return require('blink.cmp.completion.windows.render.tailwind').get_hl(ctx) or ('BlinkCmpKind' .. ctx.kind) + end, + }, + + kind = { + ellipsis = false, + width = { fill = true }, + text = function(ctx) return ctx.kind end, + highlight = function(ctx) + return require('blink.cmp.completion.windows.render.tailwind').get_hl(ctx) or ('BlinkCmpKind' .. ctx.kind) + end, + }, + + label = { + width = { fill = true, max = 60 }, + text = function(ctx) return ctx.label .. ctx.label_detail end, + highlight = function(ctx) + -- label and label details + local label = ctx.label + local highlights = { + { 0, #label, group = ctx.deprecated and 'BlinkCmpLabelDeprecated' or 'BlinkCmpLabel' }, + } + if ctx.label_detail then + table.insert(highlights, { #label, #label + #ctx.label_detail, group = 'BlinkCmpLabelDetail' }) + end + + if vim.list_contains(ctx.self.treesitter, ctx.source_id) then + -- add treesitter highlights + vim.list_extend(highlights, require('blink.cmp.completion.windows.render.treesitter').highlight(ctx)) + end + + -- characters matched on the label by the fuzzy matcher + for _, idx in ipairs(ctx.label_matched_indices) do + table.insert(highlights, { idx, idx + 1, group = 'BlinkCmpLabelMatch' }) + end + + return highlights + end, + }, + + label_description = { + width = { max = 30 }, + text = function(ctx) return ctx.label_description end, + highlight = 'BlinkCmpLabelDescription', + }, + + source_name = { + width = { max = 30 }, + -- source_name or source_id are supported + text = function(ctx) return ctx.source_name end, + highlight = 'BlinkCmpSource', + }, + }, + }, + }, +} + +function window.validate(config) + validate('completion.menu', { + enabled = { config.enabled, 'boolean' }, + min_width = { config.min_width, 'number' }, + max_height = { config.max_height, 'number' }, + border = { config.border, { 'string', 'table' } }, + winblend = { config.winblend, 'number' }, + winhighlight = { config.winhighlight, 'string' }, + scrolloff = { config.scrolloff, 'number' }, + scrollbar = { config.scrollbar, 'boolean' }, + direction_priority = { + config.direction_priority, + function(direction_priority) + for _, direction in ipairs(direction_priority) do + if not vim.tbl_contains({ 'n', 's' }, direction) then return false end + end + return true + end, + 'one of: "n", "s"', + }, + order = { config.order, 'table' }, + auto_show = { config.auto_show, { 'boolean', 'function' } }, + cmdline_position = { config.cmdline_position, 'function' }, + draw = { config.draw, 'table' }, + }, config) + validate('completion.menu.order', { + n = { config.order.n, { 'string', 'nil' } }, + s = { config.order.s, { 'string', 'nil' } }, + }, config.order) + + validate('completion.menu.draw', { + align_to = { + config.draw.align_to, + function(align) + if align == 'none' or align == 'cursor' then return true end + for _, column in ipairs(config.draw.columns) do + for _, component in ipairs(column) do + if component == align then return true end + end + end + return false + end, + '"none" or one of the components defined in the "columns"', + }, + + padding = { + config.draw.padding, + function(padding) + if type(padding) == 'number' then return true end + if type(padding) ~= 'table' or #padding ~= 2 then return false end + if type(padding[1]) == 'number' and type(padding[2]) == 'number' then return true end + return false + end, + 'a number or a tuple of 2 numbers (i.e. [1, 2])', + }, + gap = { config.draw.gap, 'number' }, + + treesitter = { config.draw.treesitter, 'table' }, + + columns = { + config.draw.columns, + function(columns) + local available_components = vim.tbl_keys(config.draw.components) + + if type(columns) ~= 'table' or #columns == 0 then return false end + for _, column in ipairs(columns) do + if #column == 0 then return false end + for _, component in ipairs(column) do + if not vim.tbl_contains(available_components, component) then return false end + end + if column.gap ~= nil and type(column.gap) ~= 'number' then return false end + end + return true + end, + 'a table of tables, where each table contains a list of components and an optional gap. List of available components: ' + .. table.concat(vim.tbl_keys(config.draw.components), ', '), + }, + components = { config.draw.components, 'table' }, + }, config.draw) + + for component, definition in pairs(config.draw.components) do + validate('completion.menu.draw.components.' .. component, { + ellipsis = { definition.ellipsis, 'boolean', true }, + width = { definition.width, 'table', true }, + text = { definition.text, 'function' }, + highlight = { definition.highlight, { 'string', 'function' }, true }, + }, config.draw.components[component]) + end +end + +return window diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/completion/trigger.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/completion/trigger.lua new file mode 100644 index 0000000..94d0af8 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/completion/trigger.lua @@ -0,0 +1,42 @@ +--- @class (exact) blink.cmp.CompletionTriggerConfig +--- @field prefetch_on_insert boolean When true, will prefetch the completion items when entering insert mode. WARN: buggy, not recommended unless you'd like to help develop prefetching +--- @field show_in_snippet boolean When false, will not show the completion window when in a snippet +--- @field show_on_keyword boolean When true, will show the completion window after typing any of alphanumerics, `-` or `_` +--- @field show_on_trigger_character boolean When true, will show the completion window after typing a trigger character +--- @field show_on_blocked_trigger_characters string[] | (fun(): string[]) LSPs can indicate when to show the completion window via trigger characters. However, some LSPs (i.e. tsserver) return characters that would essentially always show the window. We block these by default. +--- @field show_on_accept_on_trigger_character boolean When both this and show_on_trigger_character are true, will show the completion window when the cursor comes after a trigger character after accepting an item +--- @field show_on_insert_on_trigger_character boolean When both this and show_on_trigger_character are true, will show the completion window when the cursor comes after a trigger character when entering insert mode +--- @field show_on_x_blocked_trigger_characters string[] | (fun(): string[]) List of trigger characters (on top of `show_on_blocked_trigger_characters`) that won't trigger the completion window when the cursor comes after a trigger character when entering insert mode/accepting an item + +local validate = require('blink.cmp.config.utils').validate +local trigger = { + --- @type blink.cmp.CompletionTriggerConfig + default = { + prefetch_on_insert = false, + show_in_snippet = true, + show_on_keyword = true, + show_on_trigger_character = true, + show_on_blocked_trigger_characters = function() + if vim.api.nvim_get_mode().mode == 'c' then return {} end + return { ' ', '\n', '\t' } + end, + show_on_accept_on_trigger_character = true, + show_on_insert_on_trigger_character = true, + show_on_x_blocked_trigger_characters = { "'", '"', '(', '{', '[' }, + }, +} + +function trigger.validate(config) + validate('completion.trigger', { + prefetch_on_insert = { config.prefetch_on_insert, 'boolean' }, + show_in_snippet = { config.show_in_snippet, 'boolean' }, + show_on_keyword = { config.show_on_keyword, 'boolean' }, + show_on_trigger_character = { config.show_on_trigger_character, 'boolean' }, + show_on_blocked_trigger_characters = { config.show_on_blocked_trigger_characters, { 'function', 'table' } }, + show_on_accept_on_trigger_character = { config.show_on_accept_on_trigger_character, 'boolean' }, + show_on_insert_on_trigger_character = { config.show_on_insert_on_trigger_character, 'boolean' }, + show_on_x_blocked_trigger_characters = { config.show_on_x_blocked_trigger_characters, { 'function', 'table' } }, + }, config) +end + +return trigger diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/fuzzy.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/fuzzy.lua new file mode 100644 index 0000000..aa50e0b --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/fuzzy.lua @@ -0,0 +1,66 @@ +--- @class (exact) blink.cmp.FuzzyConfig +--- @field use_typo_resistance boolean When enabled, allows for a number of typos relative to the length of the query. Disabling this matches the behavior of fzf +--- @field use_frecency boolean Tracks the most recently/frequently used items and boosts the score of the item +--- @field use_proximity boolean Boosts the score of items matching nearby words +--- @field use_unsafe_no_lock boolean UNSAFE!! When enabled, disables the lock and fsync when writing to the frecency database. This should only be used on unsupported platforms (i.e. alpine termux) +--- @field sorts ("label" | "sort_text" | "kind" | "score" | blink.cmp.SortFunction)[] Controls which sorts to use and in which order, these three are currently the only allowed options +--- @field prebuilt_binaries blink.cmp.PrebuiltBinariesConfig + +--- @class (exact) blink.cmp.PrebuiltBinariesConfig +--- @field download boolean Whenther or not to automatically download a prebuilt binary from github. If this is set to `false` you will need to manually build the fuzzy binary dependencies by running `cargo build --release` +--- @field ignore_version_mismatch boolean Ignores mismatched version between the built binary and the current git sha, when building locally +--- @field force_version? string 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). WARN: Beware that `main` may be incompatible with the version you select +--- @field force_system_triple? string 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. WARN: Beware that `main` may be incompatible with the version you select +--- @field extra_curl_args string[] Extra arguments that will be passed to curl like { 'curl', ..extra_curl_args, ..built_in_args } + +--- @alias blink.cmp.SortFunction fun(a: blink.cmp.CompletionItem, b: blink.cmp.CompletionItem): boolean | nil + +local validate = require('blink.cmp.config.utils').validate +local fuzzy = { + --- @type blink.cmp.FuzzyConfig + default = { + use_typo_resistance = true, + use_frecency = true, + use_proximity = true, + use_unsafe_no_lock = false, + sorts = { 'score', 'sort_text' }, + prebuilt_binaries = { + download = true, + ignore_version_mismatch = false, + force_version = nil, + force_system_triple = nil, + extra_curl_args = {}, + }, + }, +} + +function fuzzy.validate(config) + validate('fuzzy', { + use_typo_resistance = { config.use_typo_resistance, 'boolean' }, + use_frecency = { config.use_frecency, 'boolean' }, + use_proximity = { config.use_proximity, 'boolean' }, + use_unsafe_no_lock = { config.use_unsafe_no_lock, 'boolean' }, + sorts = { + config.sorts, + function(sorts) + for _, sort in ipairs(sorts) do + if not vim.tbl_contains({ 'label', 'sort_text', 'kind', 'score' }, sort) and type(sort) ~= 'function' then + return false + end + end + return true + end, + 'one of: "label", "sort_text", "kind", "score" or a function', + }, + prebuilt_binaries = { config.prebuilt_binaries, 'table' }, + }, config) + validate('fuzzy.prebuilt_binaries', { + download = { config.prebuilt_binaries.download, 'boolean' }, + ignore_version_mismatch = { config.prebuilt_binaries.ignore_version_mismatch, 'boolean' }, + force_version = { config.prebuilt_binaries.force_version, { 'string', 'nil' } }, + force_system_triple = { config.prebuilt_binaries.force_system_triple, { 'string', 'nil' } }, + extra_curl_args = { config.prebuilt_binaries.extra_curl_args, { 'table' } }, + }, config.prebuilt_binaries) +end + +return fuzzy diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/init.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/init.lua new file mode 100644 index 0000000..ac5678c --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/init.lua @@ -0,0 +1,57 @@ +--- @class (exact) blink.cmp.ConfigStrict +--- @field enabled fun(): boolean +--- @field keymap blink.cmp.KeymapConfig +--- @field completion blink.cmp.CompletionConfig +--- @field fuzzy blink.cmp.FuzzyConfig +--- @field sources blink.cmp.SourceConfig +--- @field signature blink.cmp.SignatureConfig +--- @field snippets blink.cmp.SnippetsConfig +--- @field appearance blink.cmp.AppearanceConfig + +local validate = require('blink.cmp.config.utils').validate +--- @type blink.cmp.ConfigStrict +local config = { + enabled = function() return vim.bo.buftype ~= 'prompt' and vim.b.completion ~= false end, + keymap = require('blink.cmp.config.keymap').default, + completion = require('blink.cmp.config.completion').default, + fuzzy = require('blink.cmp.config.fuzzy').default, + sources = require('blink.cmp.config.sources').default, + signature = require('blink.cmp.config.signature').default, + snippets = require('blink.cmp.config.snippets').default, + appearance = require('blink.cmp.config.appearance').default, +} + +--- @type blink.cmp.ConfigStrict +--- @diagnostic disable-next-line: missing-fields +local M = {} + +--- @param cfg blink.cmp.ConfigStrict +function M.validate(cfg) + validate('config', { + enabled = { cfg.enabled, 'function' }, + keymap = { cfg.keymap, 'table' }, + completion = { cfg.completion, 'table' }, + fuzzy = { cfg.fuzzy, 'table' }, + sources = { cfg.sources, 'table' }, + signature = { cfg.signature, 'table' }, + snippets = { cfg.snippets, 'table' }, + appearance = { cfg.appearance, 'table' }, + }, cfg) + require('blink.cmp.config.keymap').validate(cfg.keymap) + require('blink.cmp.config.completion').validate(cfg.completion) + require('blink.cmp.config.fuzzy').validate(cfg.fuzzy) + require('blink.cmp.config.sources').validate(cfg.sources) + require('blink.cmp.config.signature').validate(cfg.signature) + require('blink.cmp.config.snippets').validate(cfg.snippets) + require('blink.cmp.config.appearance').validate(cfg.appearance) +end + +--- @param user_config blink.cmp.Config +function M.merge_with(user_config) + config = vim.tbl_deep_extend('force', config, user_config) + M.validate(config) +end + +return setmetatable(M, { + __index = function(_, k) return config[k] end, +}) diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/keymap.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/keymap.lua new file mode 100644 index 0000000..86d74eb --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/keymap.lua @@ -0,0 +1,174 @@ +--- @alias blink.cmp.KeymapCommand +--- | 'fallback' Fallback to the built-in behavior +--- | 'show' Show the completion window +--- | 'hide' Hide the completion window +--- | 'cancel' Cancel the current completion, undoing the preview from auto_insert +--- | 'accept' Accept the current completion item +--- | 'select_and_accept' Select the first completion item, if there's no selection, and accept +--- | 'select_prev' Select the previous completion item +--- | 'select_next' Select the next completion item +--- | 'show_documentation' Show the documentation window +--- | 'hide_documentation' Hide the documentation window +--- | 'scroll_documentation_up' Scroll the documentation window up +--- | 'scroll_documentation_down' Scroll the documentation window down +--- | 'snippet_forward' Move the cursor forward to the next snippet placeholder +--- | 'snippet_backward' Move the cursor backward to the previous snippet placeholder +--- | (fun(cmp: blink.cmp.API): boolean?) Custom function where returning true will prevent the next command from running + +--- @alias blink.cmp.KeymapPreset +--- | 'none' No keymaps +--- Mappings similar to the built-in completion: +--- ```lua +--- { +--- ['<C-space>'] = { 'show', 'show_documentation', 'hide_documentation' }, +--- ['<C-e>'] = { 'cancel', 'fallback' }, +--- ['<C-y>'] = { 'select_and_accept' }, +--- +--- ['<C-p>'] = { 'select_prev', 'fallback' }, +--- ['<C-n>'] = { 'select_next', 'fallback' }, +--- +--- ['<C-b>'] = { 'scroll_documentation_up', 'fallback' }, +--- ['<C-f>'] = { 'scroll_documentation_down', 'fallback' }, +--- +--- ['<Tab>'] = { 'snippet_forward', 'fallback' }, +--- ['<S-Tab>'] = { 'snippet_backward', 'fallback' }, +--- } +--- ``` +--- | 'default' +--- Mappings similar to VSCode. +--- You may want to set `completion.trigger.show_in_snippet = false` or use `completion.list.selection.preselect = function(ctx) return not require('blink.cmp').snippet_active({ direction = 1 }) end` when using this mapping: +--- ```lua +--- { +--- ['<C-space>'] = { 'show', 'show_documentation', 'hide_documentation' }, +--- ['<C-e>'] = { 'cancel', 'fallback' }, +--- +--- ['<Tab>'] = { +--- function(cmp) +--- if cmp.snippet_active() then return cmp.accept() +--- else return cmp.select_and_accept() end +--- end, +--- 'snippet_forward', +--- 'fallback' +--- }, +--- ['<S-Tab>'] = { 'snippet_backward', 'fallback' }, +--- +--- ['<Up>'] = { 'select_prev', 'fallback' }, +--- ['<Down>'] = { 'select_next', 'fallback' }, +--- ['<C-p>'] = { 'select_prev', 'fallback' }, +--- ['<C-n>'] = { 'select_next', 'fallback' }, +--- +--- ['<C-b>'] = { 'scroll_documentation_up', 'fallback' }, +--- ['<C-f>'] = { 'scroll_documentation_down', 'fallback' }, +--- } +--- ``` +--- | 'super-tab' +--- Similar to 'super-tab' but with `enter` to accept +--- You may want to set `completion.list.selection.preselect = false` when using this keymap: +--- ```lua +--- { +--- ['<C-space>'] = { 'show', 'show_documentation', 'hide_documentation' }, +--- ['<C-e>'] = { 'cancel', 'fallback' }, +--- ['<CR>'] = { 'accept', 'fallback' }, +--- +--- ['<Tab>'] = { 'snippet_forward', 'fallback' }, +--- ['<S-Tab>'] = { 'snippet_backward', 'fallback' }, +--- +--- ['<Up>'] = { 'select_prev', 'fallback' }, +--- ['<Down>'] = { 'select_next', 'fallback' }, +--- ['<C-p>'] = { 'select_prev', 'fallback' }, +--- ['<C-n>'] = { 'select_next', 'fallback' }, +--- +--- ['<C-b>'] = { 'scroll_documentation_up', 'fallback' }, +--- ['<C-f>'] = { 'scroll_documentation_down', 'fallback' }, +--- } +--- ``` +--- | 'enter' + +--- When specifying 'preset' in the keymap table, the custom key mappings are merged with the preset, and any conflicting keys will overwrite the preset mappings. +--- The "fallback" command will run the next non blink keymap. +--- +--- Example: +--- +--- ```lua +--- keymap = { +--- preset = 'default', +--- ['<Up>'] = { 'select_prev', 'fallback' }, +--- ['<Down>'] = { 'select_next', 'fallback' }, +--- +--- -- disable a keymap from the preset +--- ['<C-e>'] = {}, +--- +--- -- optionally, define different keymaps for cmdline +--- cmdline = { +--- preset = 'cmdline', +--- } +--- } +--- ``` +--- +--- When defining your own keymaps without a preset, no keybinds will be assigned automatically. +--- @class (exact) blink.cmp.BaseKeymapConfig +--- @field preset? blink.cmp.KeymapPreset +--- @field [string] blink.cmp.KeymapCommand[] Table of keys => commands[] + +--- @class (exact) blink.cmp.KeymapConfig : blink.cmp.BaseKeymapConfig +--- @field cmdline? blink.cmp.BaseKeymapConfig Optionally, define a separate keymap for cmdline + +local keymap = { + --- @type blink.cmp.KeymapConfig + default = { + preset = 'default', + }, +} + +--- @param config blink.cmp.KeymapConfig +function keymap.validate(config) + local commands = { + 'fallback', + 'show', + 'hide', + 'cancel', + 'accept', + 'select_and_accept', + 'select_prev', + 'select_next', + 'show_documentation', + 'hide_documentation', + 'scroll_documentation_up', + 'scroll_documentation_down', + 'snippet_forward', + 'snippet_backward', + } + local presets = { 'default', 'super-tab', 'enter', 'none' } + + local validation_schema = {} + for key, value in pairs(config) do + -- nested cmdline keymap + if key == 'cmdline' then + keymap.validate(value) + + -- preset + elseif key == 'preset' then + validation_schema[key] = { + value, + function(preset) return vim.tbl_contains(presets, preset) end, + 'one of: ' .. table.concat(presets, ', '), + } + + -- key + else + validation_schema[key] = { + value, + function(key_commands) + for _, command in ipairs(key_commands) do + if type(command) ~= 'function' and not vim.tbl_contains(commands, command) then return false end + end + return true + end, + 'commands must be one of: ' .. table.concat(commands, ', '), + } + end + end + require('blink.cmp.config.utils')._validate(validation_schema) +end + +return keymap diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/shared.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/shared.lua new file mode 100644 index 0000000..1ab7a60 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/shared.lua @@ -0,0 +1,2 @@ +--- @alias blink.cmp.WindowBorderChar string | table +--- @alias blink.cmp.WindowBorder 'single' | 'double' | 'rounded' | 'solid' | 'shadow' | 'padded' | 'none' | blink.cmp.WindowBorderChar[] diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/signature.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/signature.lua new file mode 100644 index 0000000..987c7ca --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/signature.lua @@ -0,0 +1,72 @@ +--- @class (exact) blink.cmp.SignatureConfig +--- @field enabled boolean +--- @field trigger blink.cmp.SignatureTriggerConfig +--- @field window blink.cmp.SignatureWindowConfig + +--- @class (exact) blink.cmp.SignatureTriggerConfig +--- @field blocked_trigger_characters string[] +--- @field blocked_retrigger_characters string[] +--- @field show_on_insert_on_trigger_character boolean When true, will show the signature help window when the cursor comes after a trigger character when entering insert mode + +--- @class (exact) blink.cmp.SignatureWindowConfig +--- @field min_width number +--- @field max_width number +--- @field max_height number +--- @field border blink.cmp.WindowBorder +--- @field winblend number +--- @field winhighlight string +--- @field scrollbar boolean Note that the gutter will be disabled when border ~= 'none' +--- @field direction_priority ("n" | "s")[] Which directions to show the window, falling back to the next direction when there's not enough space, or another window is in the way. +--- @field treesitter_highlighting boolean Disable if you run into performance issues + +local validate = require('blink.cmp.config.utils').validate +local signature = { + --- @type blink.cmp.SignatureConfig + default = { + enabled = false, + trigger = { + enabled = true, + blocked_trigger_characters = {}, + blocked_retrigger_characters = {}, + show_on_insert_on_trigger_character = true, + }, + window = { + min_width = 1, + max_width = 100, + max_height = 10, + border = 'padded', + winblend = 0, + winhighlight = 'Normal:BlinkCmpSignatureHelp,FloatBorder:BlinkCmpSignatureHelpBorder', + scrollbar = false, + direction_priority = { 'n', 's' }, + treesitter_highlighting = true, + }, + }, +} + +function signature.validate(config) + validate('signature', { + enabled = { config.enabled, 'boolean' }, + trigger = { config.trigger, 'table' }, + window = { config.window, 'table' }, + }, config) + validate('signature.trigger', { + enabled = { config.trigger.enabled, 'boolean' }, + blocked_trigger_characters = { config.trigger.blocked_trigger_characters, 'table' }, + blocked_retrigger_characters = { config.trigger.blocked_retrigger_characters, 'table' }, + show_on_insert_on_trigger_character = { config.trigger.show_on_insert_on_trigger_character, 'boolean' }, + }, config.trigger) + validate('signature.window', { + min_width = { config.window.min_width, 'number' }, + max_width = { config.window.max_width, 'number' }, + max_height = { config.window.max_height, 'number' }, + border = { config.window.border, { 'string', 'table' } }, + winblend = { config.window.winblend, 'number' }, + winhighlight = { config.window.winhighlight, 'string' }, + scrollbar = { config.window.scrollbar, 'boolean' }, + direction_priority = { config.window.direction_priority, 'table' }, + treesitter_highlighting = { config.window.treesitter_highlighting, 'boolean' }, + }, config.window) +end + +return signature diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/snippets.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/snippets.lua new file mode 100644 index 0000000..cd804e9 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/snippets.lua @@ -0,0 +1,65 @@ +--- @class (exact) blink.cmp.SnippetsConfig +--- @field preset 'default' | 'luasnip' | 'mini_snippets' +--- @field expand fun(snippet: string) Function to use when expanding LSP provided snippets +--- @field active fun(filter?: { direction?: number }): boolean Function to use when checking if a snippet is active +--- @field jump fun(direction: number) Function to use when jumping between tab stops in a snippet, where direction can be negative or positive + +--- @param handlers table<'default' | 'luasnip' | 'mini_snippets', fun(...): any> +local function by_preset(handlers) + return function(...) + local preset = require('blink.cmp.config').snippets.preset + return handlers[preset](...) + end +end + +local validate = require('blink.cmp.config.utils').validate +local snippets = { + --- @type blink.cmp.SnippetsConfig + default = { + preset = 'default', + -- NOTE: we wrap `vim.snippet` calls to reduce startup by 1-2ms + expand = by_preset({ + default = function(snippet) vim.snippet.expand(snippet) end, + luasnip = function(snippet) require('luasnip').lsp_expand(snippet) end, + mini_snippets = function(snippet) + if not _G.MiniSnippets then error('mini.snippets has not been setup') end + local insert = MiniSnippets.config.expand.insert or MiniSnippets.default_insert + insert(snippet) + end, + }), + active = by_preset({ + default = function(filter) return vim.snippet.active(filter) end, + luasnip = function(filter) + if filter and filter.direction then return require('luasnip').jumpable(filter.direction) end + return require('luasnip').in_snippet() + end, + mini_snippets = function() + if not _G.MiniSnippets then error('mini.snippets has not been setup') end + return MiniSnippets.session.get(false) ~= nil + end, + }), + jump = by_preset({ + default = function(direction) vim.snippet.jump(direction) end, + luasnip = function(direction) require('luasnip').jump(direction) end, + mini_snippets = function(direction) + if not _G.MiniSnippets then error('mini.snippets has not been setup') end + MiniSnippets.session.jump(direction == -1 and 'prev' or 'next') + end, + }), + }, +} + +function snippets.validate(config) + validate('snippets', { + preset = { + config.preset, + function(preset) return vim.tbl_contains({ 'default', 'luasnip', 'mini_snippets' }, preset) end, + 'one of: "default", "luasnip", "mini_snippets"', + }, + expand = { config.expand, 'function' }, + active = { config.active, 'function' }, + jump = { config.jump, 'function' }, + }, config) +end + +return snippets diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/sources.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/sources.lua new file mode 100644 index 0000000..ab06b5f --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/sources.lua @@ -0,0 +1,149 @@ +--- @class blink.cmp.SourceConfig +--- Static list of providers to enable, or a function to dynamically enable/disable providers based on the context +--- +--- Example dynamically picking providers based on the filetype and treesitter node: +--- ```lua +--- function(ctx) +--- local node = vim.treesitter.get_node() +--- if vim.bo.filetype == 'lua' then +--- return { 'lsp', 'path' } +--- elseif node and vim.tbl_contains({ 'comment', 'line_comment', 'block_comment' }), node:type()) +--- return { 'buffer' } +--- else +--- return { 'lsp', 'path', 'snippets', 'buffer' } +--- end +--- end +--- ``` +--- @field default string[] | fun(): string[] +--- @field per_filetype table<string, string[] | fun(): string[]> +--- @field cmdline string[] | fun(): string[] +--- +--- @field transform_items fun(ctx: blink.cmp.Context, items: blink.cmp.CompletionItem[]): blink.cmp.CompletionItem[] Function to transform the items before they're returned +--- @field min_keyword_length number | fun(ctx: blink.cmp.Context): number Minimum number of characters in the keyword to trigger +--- +--- @field providers table<string, blink.cmp.SourceProviderConfig> + +--- @class blink.cmp.SourceProviderConfig +--- @field name string +--- @field module string +--- @field enabled? boolean | fun(): boolean Whether or not to enable the provider +--- @field opts? table +--- @field async? boolean | fun(ctx: blink.cmp.Context): boolean Whether blink should wait for the source to return before showing the completions +--- @field timeout_ms? number | fun(ctx: blink.cmp.Context): number How long to wait for the provider to return before showing completions and treating it as asynchronous +--- @field transform_items? fun(ctx: blink.cmp.Context, items: blink.cmp.CompletionItem[]): blink.cmp.CompletionItem[] Function to transform the items before they're returned +--- @field should_show_items? boolean | fun(ctx: blink.cmp.Context, items: blink.cmp.CompletionItem[]): boolean Whether or not to show the items +--- @field max_items? number | fun(ctx: blink.cmp.Context, items: blink.cmp.CompletionItem[]): number Maximum number of items to display in the menu +--- @field min_keyword_length? number | fun(ctx: blink.cmp.Context): number Minimum number of characters in the keyword to trigger the provider +--- @field fallbacks? string[] | fun(ctx: blink.cmp.Context, enabled_sources: string[]): string[] If this provider returns 0 items, it will fallback to these providers +--- @field score_offset? number | fun(ctx: blink.cmp.Context, enabled_sources: string[]): number Boost/penalize the score of the items +--- @field deduplicate? blink.cmp.DeduplicateConfig TODO: implement +--- @field override? blink.cmp.SourceOverride Override the source's functions + +local validate = require('blink.cmp.config.utils').validate +local sources = { + --- @type blink.cmp.SourceConfig + default = { + default = { 'lsp', 'path', 'snippets', 'buffer' }, + per_filetype = {}, + cmdline = function() + local type = vim.fn.getcmdtype() + -- Search forward and backward + if type == '/' or type == '?' then return { 'buffer' } end + -- Commands + if type == ':' or type == '@' then return { 'cmdline' } end + return {} + end, + + transform_items = function(_, items) return items end, + min_keyword_length = 0, + + providers = { + lsp = { + name = 'LSP', + module = 'blink.cmp.sources.lsp', + fallbacks = { 'buffer' }, + transform_items = function(_, items) + -- demote snippets + for _, item in ipairs(items) do + if item.kind == require('blink.cmp.types').CompletionItemKind.Snippet then + item.score_offset = item.score_offset - 3 + end + end + + -- filter out text items, since we have the buffer source + return vim.tbl_filter( + function(item) return item.kind ~= require('blink.cmp.types').CompletionItemKind.Text end, + items + ) + end, + }, + path = { + name = 'Path', + module = 'blink.cmp.sources.path', + score_offset = 3, + fallbacks = { 'buffer' }, + }, + snippets = { + name = 'Snippets', + module = 'blink.cmp.sources.snippets', + score_offset = -3, + }, + buffer = { + name = 'Buffer', + module = 'blink.cmp.sources.buffer', + score_offset = -3, + }, + cmdline = { + name = 'cmdline', + module = 'blink.cmp.sources.cmdline', + }, + }, + }, +} + +function sources.validate(config) + assert( + config.completion == nil, + '`sources.completion.enabled_providers` has been replaced with `sources.default`. !!Note!! Be sure to update `opts_extend` as well if you have it set' + ) + + validate('sources', { + default = { config.default, { 'function', 'table' } }, + per_filetype = { config.per_filetype, 'table' }, + cmdline = { config.cmdline, { 'function', 'table' } }, + + transform_items = { config.transform_items, 'function' }, + min_keyword_length = { config.min_keyword_length, { 'number', 'function' } }, + + providers = { config.providers, 'table' }, + }, config) + for id, provider in pairs(config.providers) do + sources.validate_provider(id, provider) + end +end + +function sources.validate_provider(id, provider) + assert( + provider.fallback_for == nil, + '`fallback_for` has been replaced with `fallbacks` which work in the opposite direction. For example, fallback_for = { "lsp" } on "buffer" would now be "fallbacks" = { "buffer" } on "lsp"' + ) + + validate('sources.providers.' .. id, { + name = { provider.name, 'string' }, + module = { provider.module, 'string' }, + enabled = { provider.enabled, { 'boolean', 'function' }, true }, + opts = { provider.opts, 'table', true }, + async = { provider.async, { 'boolean', 'function' }, true }, + timeout_ms = { provider.timeout_ms, { 'number', 'function' }, true }, + transform_items = { provider.transform_items, 'function', true }, + should_show_items = { provider.should_show_items, { 'boolean', 'function' }, true }, + max_items = { provider.max_items, { 'number', 'function' }, true }, + min_keyword_length = { provider.min_keyword_length, { 'number', 'function' }, true }, + fallbacks = { provider.fallback_for, { 'table', 'function' }, true }, + score_offset = { provider.score_offset, { 'number', 'function' }, true }, + deduplicate = { provider.deduplicate, 'table', true }, + override = { provider.override, 'table', true }, + }, provider) +end + +return sources diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/types_partial.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/types_partial.lua new file mode 100644 index 0000000..1f525d1 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/types_partial.lua @@ -0,0 +1,78 @@ +--- @class (exact) blink.cmp.Config : blink.cmp.ConfigStrict +--- @field enabled? fun(): boolean +--- @field keymap? blink.cmp.KeymapConfig +--- @field completion? blink.cmp.CompletionConfigPartial +--- @field fuzzy? blink.cmp.FuzzyConfigPartial +--- @field sources? blink.cmp.SourceConfigPartial +--- @field signature? blink.cmp.SignatureConfigPartial +--- @field snippets? blink.cmp.SnippetsConfigPartial +--- @field appearance? blink.cmp.AppearanceConfigPartial + +--- @class (exact) blink.cmp.CompletionConfigPartial : blink.cmp.CompletionConfig +--- @field keyword? blink.cmp.CompletionKeywordConfigPartial +--- @field trigger? blink.cmp.CompletionTriggerConfigPartial +--- @field list? blink.cmp.CompletionListConfigPartial +--- @field accept? blink.cmp.CompletionAcceptConfigPartial +--- @field menu? blink.cmp.CompletionMenuConfigPartial +--- @field documentation? blink.cmp.CompletionDocumentationConfigPartial +--- @field ghost_text? blink.cmp.CompletionGhostTextConfigPartial + +--- @class (exact) blink.cmp.CompletionKeywordConfigPartial : blink.cmp.CompletionKeywordConfig, {} + +--- @class (exact) blink.cmp.CompletionTriggerConfigPartial : blink.cmp.CompletionTriggerConfig, {} + +--- @class (exact) blink.cmp.CompletionListConfigPartial : blink.cmp.CompletionListConfig, {} +--- @field selection? blink.cmp.CompletionListSelectionConfigPartial +--- @field cycle? blink.cmp.CompletionListCycleConfigPartial + +--- @class (exact) blink.cmp.CompletionListSelectionConfigPartial : blink.cmp.CompletionListSelectionConfig, {} + +--- @class (exact) blink.cmp.CompletionListCycleConfigPartial : blink.cmp.CompletionListCycleConfig, {} + +--- @class (exact) blink.cmp.CompletionAcceptConfigPartial : blink.cmp.CompletionAcceptConfig, {} +--- @field auto_brackets? blink.cmp.AutoBracketsConfigPartial + +--- @class (exact) blink.cmp.AutoBracketsConfigPartial : blink.cmp.AutoBracketsConfig, {} +--- @field kind_resolution? blink.cmp.AutoBracketResolutionConfigPartial Synchronously use the kind of the item to determine if brackets should be added +--- @field semantic_token_resolution? blink.cmp.AutoBracketSemanticTokenResolutionConfigPartial Asynchronously use semantic token to determine if brackets should be added + +--- @class (exact) blink.cmp.AutoBracketResolutionConfigPartial : blink.cmp.AutoBracketResolutionConfig, {} + +--- @class (exact) blink.cmp.AutoBracketSemanticTokenResolutionConfigPartial : blink.cmp.AutoBracketSemanticTokenResolutionConfig, {} + +--- @class (exact) blink.cmp.CompletionMenuConfigPartial : blink.cmp.CompletionMenuConfig, {} +--- @field order? blink.cmp.CompletionMenuOrderConfigPartial TODO: implement + +--- @class (exact) blink.cmp.CompletionMenuOrderConfigPartial : blink.cmp.CompletionMenuOrderConfig, {} + +--- @class (exact) blink.cmp.CompletionDocumentationConfigPartial : blink.cmp.CompletionDocumentationConfig, {} +--- @field window? blink.cmp.CompletionDocumentationWindowConfigPartial + +--- @class (exact) blink.cmp.CompletionDocumentationWindowConfigPartial : blink.cmp.CompletionDocumentationWindowConfig, {} +--- @field direction_priority? blink.cmp.CompletionDocumentationDirectionPriorityConfigPartial Which directions to show the window, for each of the possible menu window directions, falling back to the next direction when there's not enough space + +--- @class (exact) blink.cmp.CompletionDocumentationDirectionPriorityConfigPartial : blink.cmp.CompletionDocumentationDirectionPriorityConfig, {} + +--- @class (exact) blink.cmp.CompletionGhostTextConfigPartial : blink.cmp.CompletionGhostTextConfig, {} + +--- @class (exact) blink.cmp.FuzzyConfigPartial : blink.cmp.FuzzyConfig, {} +--- @field prebuilt_binaries? blink.cmp.PrebuiltBinariesConfigPartial + +--- @class (exact) blink.cmp.PrebuiltBinariesConfigPartial : blink.cmp.PrebuiltBinariesConfig, {} + +--- @class blink.cmp.SourceConfigPartial : blink.cmp.SourceConfig, {} +--- @field providers? table<string, blink.cmp.SourceProviderConfigPartial> + +--- @class blink.cmp.SourceProviderConfigPartial : blink.cmp.SourceProviderConfig, {} + +--- @class (exact) blink.cmp.SignatureConfigPartial : blink.cmp.SignatureConfig, {} +--- @field trigger? blink.cmp.SignatureTriggerConfigPartial +--- @field window? blink.cmp.SignatureWindowConfigPartial + +--- @class (exact) blink.cmp.SignatureTriggerConfigPartial : blink.cmp.SignatureTriggerConfig, {} + +--- @class (exact) blink.cmp.SignatureWindowConfigPartial : blink.cmp.SignatureWindowConfig, {} + +--- @class (exact) blink.cmp.SnippetsConfigPartial : blink.cmp.SnippetsConfig, {} + +--- @class (exact) blink.cmp.AppearanceConfigPartial : blink.cmp.AppearanceConfig, {} diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/utils.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/utils.lua new file mode 100644 index 0000000..4cbc052 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/config/utils.lua @@ -0,0 +1,36 @@ +local utils = {} + +-- Code taken from @MariaSolOs in a indent-blankline.nvim PR: +-- https://github.com/lukas-reineke/indent-blankline.nvim/pull/934/files#diff-09ebcaa8c75cd1e92d25640e377ab261cfecaf8351c9689173fd36c2d0c23d94R16 +-- Saves a whopping 20% compared to vim.validate (0.8ms -> 0.64ms) + +--- Use the faster validate version if available +--- @param spec table<string, {[1]:any, [2]:function|string, [3]:string|true|nil}> +--- NOTE: We disable some Lua diagnostics here since lua_ls isn't smart enough to +--- realize that we're using an overloaded function. +function utils._validate(spec) + if vim.fn.has('nvim-0.11') == 0 then return vim.validate(spec) end + for key, key_spec in pairs(spec) do + local message = type(key_spec[3]) == 'string' and key_spec[3] or nil --[[@as string?]] + local optional = type(key_spec[3]) == 'boolean' and key_spec[3] or nil --[[@as boolean?]] + ---@diagnostic disable-next-line:param-type-mismatch, redundant-parameter + vim.validate(key, key_spec[1], key_spec[2], optional, message) + end +end + +--- @param path string The path to the field being validated +--- @param tbl table The table to validate +--- @param source table The original table that we're validating against +--- @see vim.validate +function utils.validate(path, tbl, source) + -- validate + local _, err = pcall(utils._validate, tbl) + if err then error(path .. '.' .. err) end + + -- check for erroneous fields + for k, _ in pairs(source) do + if tbl[k] == nil then error(path .. '.' .. k .. ': unexpected field found in configuration') end + end +end + +return utils diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/download/files.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/download/files.lua new file mode 100644 index 0000000..28bbac5 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/download/files.lua @@ -0,0 +1,181 @@ +local async = require('blink.cmp.lib.async') +local utils = require('blink.cmp.lib.utils') +local system = require('blink.cmp.fuzzy.download.system') + +local function get_lib_extension() + if jit.os:lower() == 'mac' or jit.os:lower() == 'osx' then return '.dylib' end + if jit.os:lower() == 'windows' then return '.dll' end + return '.so' +end + +local current_file_dir = debug.getinfo(1).source:match('@?(.*/)') +local current_file_dir_parts = vim.split(current_file_dir, '/') +local root_dir = table.concat(utils.slice(current_file_dir_parts, 1, #current_file_dir_parts - 6), '/') +local lib_folder = root_dir .. '/target/release' +local lib_filename = 'libblink_cmp_fuzzy' .. get_lib_extension() +local lib_path = lib_folder .. '/' .. lib_filename +local checksum_filename = lib_filename .. '.sha256' +local checksum_path = lib_path .. '.sha256' +local version_path = lib_folder .. '/version' + +local files = { + get_lib_extension = get_lib_extension, + root_dir = root_dir, + lib_folder = lib_folder, + lib_filename = lib_filename, + lib_path = lib_path, + checksum_path = checksum_path, + checksum_filename = checksum_filename, + version_path = version_path, +} + +--- Checksums --- + +function files.get_checksum() + return files.read_file(files.checksum_path):map(function(checksum) return vim.split(checksum, ' ')[1] end) +end + +function files.get_checksum_for_file(path) + return async.task.new(function(resolve, reject) + local os = system.get_info() + local args + if os == 'linux' then + args = { 'sha256sum', path } + elseif os == 'mac' or os == 'osx' then + args = { 'shasum', '-a', '256', path } + elseif os == 'windows' then + args = { 'certutil', '-hashfile', path, 'SHA256' } + end + + vim.system(args, {}, function(out) + if out.code ~= 0 then return reject('Failed to calculate checksum of pre-built binary: ' .. out.stderr) end + + local stdout = out.stdout or '' + if os == 'windows' then stdout = vim.split(stdout, '\r\n')[2] end + -- We get an output like 'sha256sum filename' on most systems, so we grab just the checksum + return resolve(vim.split(stdout, ' ')[1]) + end) + end) +end + +function files.verify_checksum() + return async.task + .await_all({ files.get_checksum(), files.get_checksum_for_file(files.lib_path) }) + :map(function(checksums) + assert(#checksums == 2, 'Expected 2 checksums, got ' .. #checksums) + assert(checksums[1] and checksums[2], 'Expected checksums to be non-nil') + assert( + checksums[1] == checksums[2], + 'Checksum of pre-built binary does not match. Expected "' .. checksums[1] .. '", got "' .. checksums[2] .. '"' + ) + end) +end + +--- Prebuilt binary --- + +function files.get_version() + return files + .read_file(files.version_path) + :map(function(version) + if #version == 40 then + return { sha = version } + else + return { tag = version } + end + end) + :catch(function() return {} end) +end + +--- @param version string +--- @return blink.cmp.Task +function files.set_version(version) + return files + .create_dir(files.root_dir .. '/target') + :map(function() return files.create_dir(files.lib_folder) end) + :map(function() return files.write_file(files.version_path, version) end) +end + +--- Filesystem helpers --- + +--- @param path string +--- @return blink.cmp.Task +function files.read_file(path) + return async.task.new(function(resolve, reject) + vim.uv.fs_open(path, 'r', 438, function(open_err, fd) + if open_err or fd == nil then return reject(open_err or 'Unknown error') end + vim.uv.fs_read(fd, 1024, 0, function(read_err, data) + vim.uv.fs_close(fd, function() end) + if read_err or data == nil then return reject(read_err or 'Unknown error') end + return resolve(data) + end) + end) + end) +end + +--- @param path string +--- @param data string +--- @return blink.cmp.Task +function files.write_file(path, data) + return async.task.new(function(resolve, reject) + vim.uv.fs_open(path, 'w', 438, function(open_err, fd) + if open_err or fd == nil then return reject(open_err or 'Unknown error') end + vim.uv.fs_write(fd, data, 0, function(write_err) + vim.uv.fs_close(fd, function() end) + if write_err then return reject(write_err) end + return resolve() + end) + end) + end) +end + +--- @param path string +--- @return blink.cmp.Task +function files.exists(path) + return async.task.new(function(resolve) + vim.uv.fs_stat(path, function(err) resolve(not err) end) + end) +end + +--- @param path string +--- @return blink.cmp.Task +function files.stat(path) + return async.task.new(function(resolve, reject) + vim.uv.fs_stat(path, function(err, stat) + if err then return reject(err) end + resolve(stat) + end) + end) +end + +--- @param path string +--- @return blink.cmp.Task +function files.create_dir(path) + return files + .stat(path) + :map(function(stat) return stat.type == 'directory' end) + :catch(function() return false end) + :map(function(exists) + if exists then return end + + return async.task.new(function(resolve, reject) + vim.uv.fs_mkdir(path, 511, function(err) + if err then return reject(err) end + resolve() + end) + end) + end) +end + +--- Renames a file +--- @param old_path string +--- @param new_path string +function files.rename(old_path, new_path) + return async.task.new(function(resolve, reject) + vim.uv.fs_rename(old_path, new_path, function(err) + if err then return reject(err) end + resolve() + end) + end) +end + +return files diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/download/git.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/download/git.lua new file mode 100644 index 0000000..63a7646 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/download/git.lua @@ -0,0 +1,70 @@ +local async = require('blink.cmp.lib.async') +local files = require('blink.cmp.fuzzy.download.files') +local git = {} + +function git.get_version() + return async.task.await_all({ git.get_tag(), git.get_sha() }):map( + function(results) + return { + tag = results[1], + sha = results[2], + } + end + ) +end + +function git.get_tag() + return async.task.new(function(resolve, reject) + -- If repo_dir is nil, no git reposiory is found, similar to `out.code == 128` + local repo_dir = vim.fs.root(files.root_dir, '.git') + if not repo_dir then resolve() end + + vim.system({ + 'git', + '--git-dir', + vim.fs.joinpath(repo_dir, '.git'), + '--work-tree', + repo_dir, + 'describe', + '--tags', + '--exact-match', + }, { cwd = files.root_dir }, function(out) + if out.code == 128 then return resolve() end + if out.code ~= 0 then + return reject('While getting git tag, git exited with code ' .. out.code .. ': ' .. out.stderr) + end + + local lines = vim.split(out.stdout, '\n') + if not lines[1] then return reject('Expected atleast 1 line of output from git describe') end + return resolve(lines[1]) + end) + end) +end + +function git.get_sha() + return async.task.new(function(resolve, reject) + -- If repo_dir is nil, no git reposiory is found, similar to `out.code == 128` + local repo_dir = vim.fs.root(files.root_dir, '.git') + if not repo_dir then resolve() end + + vim.system({ + 'git', + '--git-dir', + vim.fs.joinpath(repo_dir, '.git'), + '--work-tree', + repo_dir, + 'rev-parse', + 'HEAD', + }, { cwd = files.root_dir }, function(out) + if out.code == 128 then return resolve() end + if out.code ~= 0 then + return reject('While getting git sha, git exited with code ' .. out.code .. ': ' .. out.stderr) + end + + local sha = vim.split(out.stdout, '\n')[1] + return resolve(sha) + end) + end) +end + +return git diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/download/init.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/download/init.lua new file mode 100644 index 0000000..085becd --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/download/init.lua @@ -0,0 +1,170 @@ +local download_config = require('blink.cmp.config').fuzzy.prebuilt_binaries +local async = require('blink.cmp.lib.async') +local git = require('blink.cmp.fuzzy.download.git') +local files = require('blink.cmp.fuzzy.download.files') +local system = require('blink.cmp.fuzzy.download.system') + +local download = {} + +--- @param callback fun(err: string | nil) +function download.ensure_downloaded(callback) + callback = vim.schedule_wrap(callback) + + if not download_config.download then return callback() end + + async.task + .await_all({ git.get_version(), files.get_version() }) + :map(function(results) + return { + git = results[1], + current = results[2], + } + end) + :map(function(version) + local target_git_tag = download_config.force_version or version.git.tag + + -- not built locally, not on a git tag, error + assert( + version.current.sha ~= nil or target_git_tag ~= nil, + "\nDetected an out of date or missing fuzzy matching library. Can't download from github due to not being on a git tag and no `fuzzy.prebuilt_binaries.force_version` is set." + .. '\nEither run `cargo build --release` via your package manager, switch to a git tag, or set `fuzzy.prebuilt_binaries.force_version` in config. ' + .. 'See the docs for more info.' + ) + + -- built locally, ignore + if + not download_config.force_version + and ( + version.current.sha == version.git.sha + or version.current.sha ~= nil and download_config.ignore_version_mismatch + ) + then + return + end + + -- built locally but outdated and not on a git tag, error + if + not download_config.force_version + and version.current.sha ~= nil + and version.current.sha ~= version.git.sha + then + assert( + target_git_tag or download_config.ignore_version_mismatch, + "\nFound an outdated version of the fuzzy matching library, but can't download from github due to not being on a git tag. " + .. '\n!! FOR DEVELOPERS !!, set `fuzzy.prebuilt_binaries.ignore_version_mismatch = true` in config. ' + .. '\n!! FOR USERS !!, either run `cargo build --release` via your package manager, switch to a git tag, or set `fuzzy.prebuilt_binaries.force_version` in config. ' + .. 'See the docs for more info.' + ) + if not download_config.ignore_version_mismatch then + vim.schedule( + function() + vim.notify( + '[blink.cmp]: Found an outdated version of the fuzzy matching library built locally', + vim.log.levels.INFO, + { title = 'blink.cmp' } + ) + end + ) + end + end + + -- already downloaded and the correct version, just verify the checksum, and re-download if checksum fails + if version.current.tag ~= nil and version.current.tag == target_git_tag then + return files.verify_checksum():catch(function(err) + vim.schedule(function() + vim.notify(err, vim.log.levels.WARN, { title = 'blink.cmp' }) + vim.notify( + '[blink.cmp]: Pre-built binary failed checksum verification, re-downloading', + vim.log.levels.WARN, + { title = 'blink.cmp' } + ) + end) + return download.download(target_git_tag) + end) + end + + -- unknown state + if not target_git_tag then error('Unknown error while getting pre-built binary. Consider re-installing') end + + -- download as per usual + vim.schedule( + function() vim.notify('[blink.cmp]: Downloading pre-built binary', vim.log.levels.INFO, { title = 'blink.cmp' }) end + ) + return download.download(target_git_tag) + end) + :map(function() callback() end) + :catch(function(err) callback(err) end) +end + +function download.download(version) + -- NOTE: we set the version to 'v0.0.0' to avoid a failure causing the pre-built binary being marked as locally built + return files + .set_version('v0.0.0') + :map(function() return download.from_github(version) end) + :map(function() return files.verify_checksum() end) + :map(function() return files.set_version(version) end) +end + +--- @param tag string +--- @return blink.cmp.Task +function download.from_github(tag) + return system.get_triple():map(function(system_triple) + if not system_triple then + return error( + 'Your system is not supported by pre-built binaries. You must run cargo build --release via your package manager with rust nightly. See the README for more info.' + ) + end + + local base_url = 'https://github.com/saghen/blink.cmp/releases/download/' .. tag .. '/' + local library_url = base_url .. system_triple .. files.get_lib_extension() + local checksum_url = base_url .. system_triple .. files.get_lib_extension() .. '.sha256' + + return async + .task + .await_all({ + download.download_file(library_url, files.lib_filename .. '.tmp'), + download.download_file(checksum_url, files.checksum_filename), + }) + -- Mac caches the library in the kernel, so updating in place causes a crash + -- We instead write to a temporary file and rename it, as mentioned in: + -- https://developer.apple.com/documentation/security/updating-mac-software + :map( + function() + return files.rename( + files.lib_folder .. '/' .. files.lib_filename .. '.tmp', + files.lib_folder .. '/' .. files.lib_filename + ) + end + ) + end) +end + +--- @param url string +--- @param filename string +--- @return blink.cmp.Task +function download.download_file(url, filename) + return async.task.new(function(resolve, reject) + local args = { 'curl' } + vim.list_extend(args, download_config.extra_curl_args) + vim.list_extend(args, { + '--fail', -- Fail on 4xx/5xx + '--location', -- Follow redirects + '--silent', -- Don't show progress + '--show-error', -- Show errors, even though we're using --silent + '--create-dirs', + '--output', + files.lib_folder .. '/' .. filename, + url, + }) + + vim.system(args, {}, function(out) + if out.code ~= 0 then + reject('Failed to download ' .. filename .. 'for pre-built binaries: ' .. out.stderr) + else + resolve() + end + end) + end) +end + +return download diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/download/system.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/download/system.lua new file mode 100644 index 0000000..7b83a0f --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/download/system.lua @@ -0,0 +1,74 @@ +local download_config = require('blink.cmp.config').fuzzy.prebuilt_binaries +local async = require('blink.cmp.lib.async') +local system = {} + +system.triples = { + mac = { + arm = 'aarch64-apple-darwin', + x64 = 'x86_64-apple-darwin', + }, + windows = { + x64 = 'x86_64-pc-windows-msvc', + }, + linux = { + android = 'aarch64-linux-android', + arm = function(libc) return 'aarch64-unknown-linux-' .. libc end, + x64 = function(libc) return 'x86_64-unknown-linux-' .. libc end, + }, +} + +--- Gets the operating system and architecture of the current system +--- @return string, string +function system.get_info() + local os = jit.os:lower() + if os == 'osx' then os = 'mac' end + local arch = jit.arch:lower():match('arm') and 'arm' or jit.arch:lower():match('x64') and 'x64' or nil + return os, arch +end + +--- Gets the system triple for the current system +--- I.e. `x86_64-unknown-linux-gnu` or `aarch64-apple-darwin` +--- @return blink.cmp.Task +function system.get_triple() + return async.task.new(function(resolve) + if download_config.force_system_triple then return resolve(download_config.force_system_triple) end + + local os, arch = system.get_info() + local triples = system.triples[os] + + if os == 'linux' then + if vim.fn.has('android') == 1 then return resolve(triples.android) end + + vim.uv.fs_stat('/etc/alpine-release', function(err, is_alpine) + local libc = (not err and is_alpine) and 'musl' or 'gnu' + local triple = triples[arch] + return resolve(triple and type(triple) == 'function' and triple(libc) or triple) + end) + else + return resolve(triples[arch]) + end + end) +end + +--- Same as `system.get_triple` but synchronous +--- @see system.get_triple +--- @return string | nil +function system.get_triple_sync() + if download_config.force_system_triple then return download_config.force_system_triple end + + local os, arch = system.get_info() + local triples = system.triples[os] + + if os == 'linux' then + if vim.fn.has('android') == 1 then return triples.android end + + local success, is_alpine = pcall(vim.uv.fs_stat, '/etc/alpine-release') + local libc = (success and is_alpine) and 'musl' or 'gnu' + local triple = triples[arch] + return triple and type(triple) == 'function' and triple(libc) or triple + else + return triples[arch] + end +end + +return system diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/frecency.rs b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/frecency.rs new file mode 100644 index 0000000..a672598 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/frecency.rs @@ -0,0 +1,153 @@ +use crate::lsp_item::LspItem; +use heed::{types::*, EnvFlags}; +use heed::{Database, Env, EnvOpenOptions}; +use mlua::Result as LuaResult; +use serde::{Deserialize, Serialize}; +use std::fs; +use std::time::{SystemTime, UNIX_EPOCH}; + +#[derive(Clone, Serialize, Deserialize)] +struct CompletionItemKey { + label: String, + kind: u32, + source_id: String, +} + +impl From<&LspItem> for CompletionItemKey { + fn from(item: &LspItem) -> Self { + Self { + label: item.label.clone(), + kind: item.kind, + source_id: item.source_id.clone(), + } + } +} + +#[derive(Debug)] +pub struct FrecencyTracker { + env: Env, + db: Database<SerdeBincode<CompletionItemKey>, SerdeBincode<Vec<u64>>>, + access_thresholds: Vec<(f64, u64)>, +} + +impl FrecencyTracker { + pub fn new(db_path: &str, use_unsafe_no_lock: bool) -> LuaResult<Self> { + fs::create_dir_all(db_path).map_err(|err| { + mlua::Error::RuntimeError( + "Failed to create frecency database directory: ".to_string() + &err.to_string(), + ) + })?; + let env = unsafe { + let mut opts = EnvOpenOptions::new(); + if use_unsafe_no_lock { + opts.flags(EnvFlags::NO_LOCK | EnvFlags::NO_SYNC | EnvFlags::NO_META_SYNC); + } + opts.open(db_path).map_err(|err| { + mlua::Error::RuntimeError( + "Failed to open frecency database: ".to_string() + &err.to_string(), + ) + })? + }; + env.clear_stale_readers().map_err(|err| { + mlua::Error::RuntimeError( + "Failed to clear stale readers for frecency database: ".to_string() + + &err.to_string(), + ) + })?; + + // we will open the default unnamed database + let mut wtxn = env.write_txn().map_err(|err| { + mlua::Error::RuntimeError( + "Failed to open write transaction for frecency database: ".to_string() + + &err.to_string(), + ) + })?; + let db = env.create_database(&mut wtxn, None).map_err(|err| { + mlua::Error::RuntimeError( + "Failed to create frecency database: ".to_string() + &err.to_string(), + ) + })?; + + let access_thresholds = [ + (1., 1000 * 60 * 2), // 2 minutes + (0.2, 1000 * 60 * 60), // 1 hour + (0.1, 1000 * 60 * 60 * 24), // 1 day + (0.05, 1000 * 60 * 60 * 24 * 7), // 1 week + ] + .to_vec(); + + Ok(FrecencyTracker { + env: env.clone(), + db, + access_thresholds, + }) + } + + fn get_accesses(&self, item: &LspItem) -> LuaResult<Option<Vec<u64>>> { + let rtxn = self.env.read_txn().map_err(|err| { + mlua::Error::RuntimeError( + "Failed to start read transaction for frecency database: ".to_string() + + &err.to_string(), + ) + })?; + self.db + .get(&rtxn, &CompletionItemKey::from(item)) + .map_err(|err| { + mlua::Error::RuntimeError( + "Failed to read from frecency database: ".to_string() + &err.to_string(), + ) + }) + } + + fn get_now(&self) -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() + } + + pub fn access(&mut self, item: &LspItem) -> LuaResult<()> { + let mut wtxn = self.env.write_txn().map_err(|err| { + mlua::Error::RuntimeError( + "Failed to start write transaction for frecency database: ".to_string() + + &err.to_string(), + ) + })?; + + let mut accesses = self.get_accesses(item)?.unwrap_or_default(); + accesses.push(self.get_now()); + + self.db + .put(&mut wtxn, &CompletionItemKey::from(item), &accesses) + .map_err(|err| { + mlua::Error::RuntimeError( + "Failed to write to frecency database: ".to_string() + &err.to_string(), + ) + })?; + + wtxn.commit().map_err(|err| { + mlua::Error::RuntimeError( + "Failed to commit write transaction for frecency database: ".to_string() + + &err.to_string(), + ) + })?; + + Ok(()) + } + + pub fn get_score(&self, item: &LspItem) -> i64 { + let accesses = self.get_accesses(item).unwrap_or(None).unwrap_or_default(); + let now = self.get_now(); + let mut score = 0.0; + 'outer: for access in &accesses { + let duration_since = now - access; + for (rank, threshold_duration_since) in &self.access_thresholds { + if duration_since < *threshold_duration_since { + score += rank; + continue 'outer; + } + } + } + score.min(4.) as i64 + } +} diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/fuzzy.rs b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/fuzzy.rs new file mode 100644 index 0000000..b09bb99 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/fuzzy.rs @@ -0,0 +1,183 @@ +// TODO: refactor this heresy + +use crate::frecency::FrecencyTracker; +use crate::keyword; +use crate::lsp_item::LspItem; +use mlua::prelude::*; +use mlua::FromLua; +use mlua::Lua; +use std::collections::HashMap; +use std::collections::HashSet; + +#[derive(Clone, Hash)] +pub struct FuzzyOptions { + match_suffix: bool, + use_typo_resistance: bool, + use_frecency: bool, + use_proximity: bool, + nearby_words: Option<Vec<String>>, + min_score: u16, +} + +impl FromLua for FuzzyOptions { + fn from_lua(value: LuaValue, _lua: &'_ Lua) -> LuaResult<Self> { + if let Some(tab) = value.as_table() { + let match_suffix: bool = tab.get("match_suffix").unwrap_or_default(); + let use_typo_resistance: bool = tab.get("use_typo_resistance").unwrap_or_default(); + let use_frecency: bool = tab.get("use_frecency").unwrap_or_default(); + let use_proximity: bool = tab.get("use_proximity").unwrap_or_default(); + let nearby_words: Option<Vec<String>> = tab.get("nearby_words").ok(); + let min_score: u16 = tab.get("min_score").unwrap_or_default(); + + Ok(FuzzyOptions { + match_suffix, + use_typo_resistance, + use_frecency, + use_proximity, + nearby_words, + min_score, + }) + } else { + Err(mlua::Error::FromLuaConversionError { + from: "LuaValue", + to: "FuzzyOptions".to_string(), + message: None, + }) + } + } +} + +fn group_by_needle( + line: &str, + cursor_col: usize, + haystack: &[String], + match_suffix: bool, +) -> HashMap<String, Vec<(usize, String)>> { + let mut items_by_needle: HashMap<String, Vec<(usize, String)>> = HashMap::new(); + for (idx, item_text) in haystack.iter().enumerate() { + let needle = keyword::guess_keyword_from_item(item_text, line, cursor_col, match_suffix); + let entry = items_by_needle.entry(needle).or_default(); + entry.push((idx, item_text.to_string())); + } + items_by_needle +} + +pub fn fuzzy( + line: &str, + cursor_col: usize, + haystack: &[LspItem], + frecency: &FrecencyTracker, + opts: FuzzyOptions, +) -> (Vec<i32>, Vec<u32>) { + let haystack_labels = haystack + .iter() + .map(|s| s.filter_text.clone().unwrap_or(s.label.clone())) + .collect::<Vec<_>>(); + let options = frizbee::Options { + prefilter: !opts.use_typo_resistance, + min_score: opts.min_score, + stable_sort: false, + ..Default::default() + }; + + // Items may have different fuzzy matching ranges, so we split them up by needle + let mut matches = group_by_needle(line, cursor_col, &haystack_labels, opts.match_suffix) + .into_iter() + // Match on each needle and combine + .flat_map(|(needle, haystack)| { + let mut matches = frizbee::match_list( + &needle, + &haystack + .iter() + .map(|(_, str)| str.as_str()) + .collect::<Vec<_>>(), + options, + ); + for mtch in matches.iter_mut() { + mtch.index_in_haystack = haystack[mtch.index_in_haystack].0; + } + matches + }) + .collect::<Vec<_>>(); + + matches.sort_by_key(|mtch| mtch.index_in_haystack); + for (idx, mtch) in matches.iter_mut().enumerate() { + mtch.index = idx; + } + + // Get the score for each match, adding score_offset, frecency and proximity bonus + let nearby_words: HashSet<String> = HashSet::from_iter(opts.nearby_words.unwrap_or_default()); + let match_scores = matches + .iter() + .map(|mtch| { + let frecency_score = if opts.use_frecency { + frecency.get_score(&haystack[mtch.index_in_haystack]) as i32 + } else { + 0 + }; + let nearby_words_score = if opts.use_proximity { + nearby_words + .get(&haystack_labels[mtch.index_in_haystack]) + .map(|_| 2) + .unwrap_or(0) + } else { + 0 + }; + let score_offset = haystack[mtch.index_in_haystack].score_offset; + + (mtch.score as i32) + frecency_score + nearby_words_score + score_offset + }) + .collect::<Vec<_>>(); + + // Find the highest score and filter out matches that are unreasonably lower than it + if opts.use_typo_resistance { + let max_score = matches.iter().map(|mtch| mtch.score).max().unwrap_or(0); + let secondary_min_score = max_score.max(16) - 16; + matches = matches + .into_iter() + .filter(|mtch| mtch.score >= secondary_min_score) + .collect::<Vec<_>>(); + } + + // Return scores and indices + ( + matches + .iter() + .map(|mtch| match_scores[mtch.index]) + .collect::<Vec<_>>(), + matches + .iter() + .map(|mtch| mtch.index_in_haystack as u32) + .collect::<Vec<_>>(), + ) +} + +pub fn fuzzy_matched_indices( + line: &str, + cursor_col: usize, + haystack: &[String], + match_suffix: bool, +) -> Vec<Vec<usize>> { + let mut matches = group_by_needle(line, cursor_col, haystack, match_suffix) + .into_iter() + .flat_map(|(needle, haystack)| { + frizbee::match_list_for_matched_indices( + &needle, + &haystack + .iter() + .map(|(_, str)| str.as_str()) + .collect::<Vec<_>>(), + ) + .into_iter() + .enumerate() + .map(|(idx, matched_indices)| (haystack[idx].0, matched_indices)) + .collect::<Vec<_>>() + }) + .collect::<Vec<_>>(); + matches.sort_by_key(|mtch| mtch.0); + + matches + .into_iter() + .map(|(_, matched_indices)| matched_indices) + .collect::<Vec<_>>() +} diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/init.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/init.lua new file mode 100644 index 0000000..ad4db03 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/init.lua @@ -0,0 +1,118 @@ +local config = require('blink.cmp.config') + +--- @class blink.cmp.Fuzzy +local fuzzy = { + rust = require('blink.cmp.fuzzy.rust'), + haystacks_by_provider_cache = {}, + has_init_db = false, +} + +function fuzzy.init_db() + if fuzzy.has_init_db then return end + + fuzzy.rust.init_db(vim.fn.stdpath('data') .. '/blink/cmp/fuzzy.db', config.use_unsafe_no_lock) + + vim.api.nvim_create_autocmd('VimLeavePre', { + callback = fuzzy.rust.destroy_db, + }) + + fuzzy.has_init_db = true +end + +---@param item blink.cmp.CompletionItem +function fuzzy.access(item) + fuzzy.init_db() + + -- writing to the db takes ~10ms, so schedule writes in another thread + vim.uv + .new_work(function(itm, cpath) + package.cpath = cpath + require('blink.cmp.fuzzy.rust').access(vim.mpack.decode(itm)) + end, function() end) + :queue(vim.mpack.encode(item), package.cpath) +end + +---@param lines string +function fuzzy.get_words(lines) return fuzzy.rust.get_words(lines) end + +--- @param line string +--- @param cursor_col number +--- @param haystack string[] +--- @param range blink.cmp.CompletionKeywordRange +function fuzzy.fuzzy_matched_indices(line, cursor_col, haystack, range) + return fuzzy.rust.fuzzy_matched_indices(line, cursor_col, haystack, range == 'full') +end + +--- @param line string +--- @param cursor_col number +--- @param haystacks_by_provider table<string, blink.cmp.CompletionItem[]> +--- @param range blink.cmp.CompletionKeywordRange +--- @return blink.cmp.CompletionItem[] +function fuzzy.fuzzy(line, cursor_col, haystacks_by_provider, range) + fuzzy.init_db() + + for provider_id, haystack in pairs(haystacks_by_provider) do + -- set the provider items once since Lua <-> Rust takes the majority of the time + if fuzzy.haystacks_by_provider_cache[provider_id] ~= haystack then + fuzzy.haystacks_by_provider_cache[provider_id] = haystack + fuzzy.rust.set_provider_items(provider_id, haystack) + end + end + + -- get the nearby words + local cursor_row = vim.api.nvim_win_get_cursor(0)[1] + local start_row = math.max(0, cursor_row - 30) + local end_row = math.min(cursor_row + 30, vim.api.nvim_buf_line_count(0)) + local nearby_text = table.concat(vim.api.nvim_buf_get_lines(0, start_row, end_row, false), '\n') + local nearby_words = #nearby_text < 10000 and fuzzy.rust.get_words(nearby_text) or {} + + local keyword_start_col, keyword_end_col = + require('blink.cmp.fuzzy').get_keyword_range(line, cursor_col, config.completion.keyword.range) + local keyword_length = keyword_end_col - keyword_start_col + + local filtered_items = {} + for provider_id, haystack in pairs(haystacks_by_provider) do + -- perform fuzzy search + local scores, matched_indices = fuzzy.rust.fuzzy(line, cursor_col, provider_id, { + -- each matching char is worth 7 points (+ 1 for matching capitalization) + -- and it receives a bonus for capitalization, delimiter and prefix + -- so this should generally be good + -- TODO: make this configurable + -- TODO: instead of a min score, set X number of allowed typos + min_score = config.fuzzy.use_typo_resistance and (6 * keyword_length) or 0, + use_typo_resistance = config.fuzzy.use_typo_resistance, + use_frecency = config.fuzzy.use_frecency and keyword_length > 0, + use_proximity = config.fuzzy.use_proximity and keyword_length > 0, + sorts = config.fuzzy.sorts, + nearby_words = nearby_words, + match_suffix = range == 'full', + }) + + for idx, item_index in ipairs(matched_indices) do + local item = haystack[item_index + 1] + item.score = scores[idx] + table.insert(filtered_items, item) + end + end + + return require('blink.cmp.fuzzy.sort').sort(filtered_items, config.fuzzy.sorts) +end + +--- @param line string +--- @param col number +--- @param range? blink.cmp.CompletionKeywordRange +--- @return number, number +function fuzzy.get_keyword_range(line, col, range) + return require('blink.cmp.fuzzy.rust').get_keyword_range(line, col, range == 'full') +end + +--- @param item blink.cmp.CompletionItem +--- @param line string +--- @param col number +--- @param range blink.cmp.CompletionKeywordRange +--- @return number, number +function fuzzy.guess_edit_range(item, line, col, range) + return require('blink.cmp.fuzzy.rust').guess_edit_range(item, line, col, range == 'full') +end + +return fuzzy diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/keyword.rs b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/keyword.rs new file mode 100644 index 0000000..13d5020 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/keyword.rs @@ -0,0 +1,84 @@ +use lazy_static::lazy_static; +use regex::Regex; + +lazy_static! { + static ref BACKWARD_REGEX: Regex = Regex::new(r"[\p{L}0-9_][\p{L}0-9_\\-]*$").unwrap(); + static ref FORWARD_REGEX: Regex = Regex::new(r"^[\p{L}0-9_\\-]+").unwrap(); +} + +/// Given a line and cursor position, returns the start and end indices of the keyword +pub fn get_keyword_range(line: &str, col: usize, match_suffix: bool) -> (usize, usize) { + let before_match_start = BACKWARD_REGEX + .find(&line[0..col.min(line.len())]) + .map(|m| m.start()); + if !match_suffix { + return (before_match_start.unwrap_or(col), col); + } + + let after_match_end = FORWARD_REGEX + .find(&line[col.min(line.len())..]) + .map(|m| m.end() + col); + ( + before_match_start.unwrap_or(col), + after_match_end.unwrap_or(col), + ) +} + +/// Given a string, guesses the start and end indices in the line for the specific item +/// 1. Get the keyword range (alphanumeric, underscore, hyphen) on the line and end of the item +/// text +/// 2. Check if the suffix of the item text matches the suffix of the line text, if so, include the +/// suffix in the range +/// +/// Example: +/// line: example/str/trim +/// item: str/trim +/// matches on: str/trim +/// +/// line: example/trim +/// item: str/trim +/// matches on: trim +/// +/// TODO: +/// line: ' +/// item: 'tabline' +/// matches on: ' +pub fn guess_keyword_range_from_item( + item_text: &str, + line: &str, + cursor_col: usize, + match_suffix: bool, +) -> (usize, usize) { + let line_range = get_keyword_range(line, cursor_col, match_suffix); + let text_range = get_keyword_range(item_text, item_text.len(), false); + + let line_prefix = line.chars().take(line_range.0).collect::<String>(); + let text_prefix = item_text.chars().take(text_range.0).collect::<String>(); + if line_prefix.ends_with(&text_prefix) { + return (line_range.0 - text_prefix.len(), line_range.1); + } + + line_range +} + +pub fn guess_keyword_from_item( + item_text: &str, + line: &str, + cursor_col: usize, + match_suffix: bool, +) -> String { + let (start, end) = guess_keyword_range_from_item(item_text, line, cursor_col, match_suffix); + line[start..end].to_string() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get_keyword_range_unicode() { + let line = "'вest'"; + let col = line.len() - 1; + assert_eq!(get_keyword_range(line, col, false), (1, line.len() - 1)); + } +} diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/lib.rs b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/lib.rs new file mode 100644 index 0000000..99b74ad --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/lib.rs @@ -0,0 +1,156 @@ +use crate::frecency::FrecencyTracker; +use crate::fuzzy::FuzzyOptions; +use crate::lsp_item::LspItem; +use lazy_static::lazy_static; +use mlua::prelude::*; +use regex::Regex; +use std::collections::{HashMap, HashSet}; +use std::sync::RwLock; + +mod frecency; +mod fuzzy; +mod keyword; +mod lsp_item; + +lazy_static! { + static ref REGEX: Regex = Regex::new(r"\p{L}[\p{L}0-9_\\-]{2,}").unwrap(); + static ref FRECENCY: RwLock<Option<FrecencyTracker>> = RwLock::new(None); + static ref HAYSTACKS_BY_PROVIDER: RwLock<HashMap<String, Vec<LspItem>>> = + RwLock::new(HashMap::new()); +} + +pub fn init_db(_: &Lua, (db_path, use_unsafe_no_lock): (String, bool)) -> LuaResult<bool> { + let mut frecency = FRECENCY.write().map_err(|_| { + mlua::Error::RuntimeError("Failed to acquire lock for frecency".to_string()) + })?; + if frecency.is_some() { + return Ok(false); + } + *frecency = Some(FrecencyTracker::new(&db_path, use_unsafe_no_lock)?); + Ok(true) +} + +pub fn destroy_db(_: &Lua, _: ()) -> LuaResult<bool> { + let frecency = FRECENCY.write().map_err(|_| { + mlua::Error::RuntimeError("Failed to acquire lock for frecency".to_string()) + })?; + drop(frecency); + + let mut frecency = FRECENCY.write().map_err(|_| { + mlua::Error::RuntimeError("Failed to acquire lock for frecency".to_string()) + })?; + *frecency = None; + + Ok(true) +} + +pub fn access(_: &Lua, item: LspItem) -> LuaResult<bool> { + let mut frecency_handle = FRECENCY.write().map_err(|_| { + mlua::Error::RuntimeError("Failed to acquire lock for frecency".to_string()) + })?; + let frecency = frecency_handle.as_mut().ok_or_else(|| { + mlua::Error::RuntimeError("Attempted to use frencecy before initialization".to_string()) + })?; + frecency.access(&item)?; + Ok(true) +} + +pub fn set_provider_items( + _: &Lua, + (provider_id, items): (String, Vec<LspItem>), +) -> LuaResult<bool> { + let mut items_by_provider = HAYSTACKS_BY_PROVIDER.write().map_err(|_| { + mlua::Error::RuntimeError("Failed to acquire lock for items by provider".to_string()) + })?; + items_by_provider.insert(provider_id, items); + Ok(true) +} + +pub fn fuzzy( + _lua: &Lua, + (line, cursor_col, provider_id, opts): (String, usize, String, FuzzyOptions), +) -> LuaResult<(Vec<i32>, Vec<u32>)> { + let mut frecency_handle = FRECENCY.write().map_err(|_| { + mlua::Error::RuntimeError("Failed to acquire lock for frecency".to_string()) + })?; + let frecency = frecency_handle.as_mut().ok_or_else(|| { + mlua::Error::RuntimeError("Attempted to use frencecy before initialization".to_string()) + })?; + + let haystacks_by_provider = HAYSTACKS_BY_PROVIDER.read().map_err(|_| { + mlua::Error::RuntimeError("Failed to acquire lock for items by provider".to_string()) + })?; + let haystack = haystacks_by_provider.get(&provider_id).ok_or_else(|| { + mlua::Error::RuntimeError(format!( + "Attempted to fuzzy match for provider {} before setting the provider's items", + provider_id + )) + })?; + + Ok(fuzzy::fuzzy(&line, cursor_col, haystack, frecency, opts)) +} + +pub fn fuzzy_matched_indices( + _lua: &Lua, + (line, cursor_col, haystack, match_suffix): (String, usize, Vec<String>, bool), +) -> LuaResult<Vec<Vec<usize>>> { + Ok(fuzzy::fuzzy_matched_indices( + &line, + cursor_col, + &haystack, + match_suffix, + )) +} + +pub fn get_keyword_range( + _lua: &Lua, + (line, col, match_suffix): (String, usize, bool), +) -> LuaResult<(usize, usize)> { + Ok(keyword::get_keyword_range(&line, col, match_suffix)) +} + +pub fn guess_edit_range( + _lua: &Lua, + (item, line, cursor_col, match_suffix): (LspItem, String, usize, bool), +) -> LuaResult<(usize, usize)> { + // TODO: take the max range from insert_text and filter_text + Ok(keyword::guess_keyword_range_from_item( + item.insert_text.as_ref().unwrap_or(&item.label), + &line, + cursor_col, + match_suffix, + )) +} + +pub fn get_words(_: &Lua, text: String) -> LuaResult<Vec<String>> { + Ok(REGEX + .find_iter(&text) + .map(|m| m.as_str().to_string()) + .filter(|s| s.len() < 512) + .collect::<HashSet<String>>() + .into_iter() + .collect()) +} + +// NOTE: skip_memory_check greatly improves performance +// https://github.com/mlua-rs/mlua/issues/318 +#[mlua::lua_module(skip_memory_check)] +fn blink_cmp_fuzzy(lua: &Lua) -> LuaResult<LuaTable> { + let exports = lua.create_table()?; + exports.set("init_db", lua.create_function(init_db)?)?; + exports.set("destroy_db", lua.create_function(destroy_db)?)?; + exports.set("access", lua.create_function(access)?)?; + exports.set( + "set_provider_items", + lua.create_function(set_provider_items)?, + )?; + exports.set("fuzzy", lua.create_function(fuzzy)?)?; + exports.set( + "fuzzy_matched_indices", + lua.create_function(fuzzy_matched_indices)?, + )?; + exports.set("get_keyword_range", lua.create_function(get_keyword_range)?)?; + exports.set("guess_edit_range", lua.create_function(guess_edit_range)?)?; + exports.set("get_words", lua.create_function(get_words)?)?; + Ok(exports) +} diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/lsp_item.rs b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/lsp_item.rs new file mode 100644 index 0000000..a24669e --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/lsp_item.rs @@ -0,0 +1,46 @@ +use mlua::prelude::*; + +#[derive(Debug)] +pub struct LspItem { + pub label: String, + pub filter_text: Option<String>, + pub sort_text: Option<String>, + pub insert_text: Option<String>, + pub kind: u32, + pub score_offset: i32, + pub source_id: String, +} + +impl FromLua for LspItem { + fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> { + if let Some(tab) = value.as_table() { + let label = tab.get("label").unwrap_or_default(); + let filter_text = tab.get("filterText").ok(); + let sort_text = tab.get("sortText").ok(); + let insert_text = tab + .get::<LuaTable>("textEdit") + .and_then(|text_edit| text_edit.get("newText")) + .ok() + .or_else(|| tab.get("insertText").ok()); + let kind = tab.get("kind").unwrap_or_default(); + let score_offset = tab.get("score_offset").unwrap_or(0); + let source_id = tab.get("source_id").unwrap_or_default(); + + Ok(LspItem { + label, + filter_text, + sort_text, + insert_text, + kind, + score_offset, + source_id, + }) + } else { + Err(mlua::Error::FromLuaConversionError { + from: "LuaValue", + to: "LspItem".to_string(), + message: None, + }) + } + } +} diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/rust.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/rust.lua new file mode 100644 index 0000000..e2374cf --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/rust.lua @@ -0,0 +1,20 @@ +--- @return string +local function get_lib_extension() + if jit.os:lower() == 'mac' or jit.os:lower() == 'osx' then return '.dylib' end + if jit.os:lower() == 'windows' then return '.dll' end + return '.so' +end + +-- search for the lib in the /target/release directory with and without the lib prefix +-- since MSVC doesn't include the prefix +package.cpath = package.cpath + .. ';' + .. debug.getinfo(1).source:match('@?(.*/)') + .. '../../../../target/release/lib?' + .. get_lib_extension() + .. ';' + .. debug.getinfo(1).source:match('@?(.*/)') + .. '../../../../target/release/?' + .. get_lib_extension() + +return require('blink_cmp_fuzzy') diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/sort.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/sort.lua new file mode 100644 index 0000000..ec32ac3 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/sort.lua @@ -0,0 +1,48 @@ +local sort = {} + +--- @param list blink.cmp.CompletionItem[] +--- @param funcs ("label" | "sort_text" | "kind" | "score" | blink.cmp.SortFunction)[] +--- @return blink.cmp.CompletionItem[] +function sort.sort(list, funcs) + local sorting_funcs = vim.tbl_map( + function(name_or_func) return type(name_or_func) == 'string' and sort[name_or_func] or name_or_func end, + funcs + ) + table.sort(list, function(a, b) + for _, sorting_func in ipairs(sorting_funcs) do + local result = sorting_func(a, b) + if result ~= nil then return result end + end + end) + return list +end + +function sort.score(a, b) + if a.score == b.score then return end + return a.score > b.score +end + +function sort.kind(a, b) + if a.kind == b.kind then return end + return a.kind < b.kind +end + +function sort.sort_text(a, b) + if a.sortText == b.sortText or a.sortText == nil or b.sortText == nil then return end + return a.sortText < b.sortText +end + +function sort.label(a, b) + local _, entry1_under = a.label:find('^_+') + local _, entry2_under = b.label:find('^_+') + entry1_under = entry1_under or 0 + entry2_under = entry2_under or 0 + if entry1_under > entry2_under then + return false + elseif entry1_under < entry2_under then + return true + end + return a.label < b.label +end + +return sort diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/health.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/health.lua new file mode 100644 index 0000000..d8ff4a1 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/health.lua @@ -0,0 +1,35 @@ +local health = {} + +function health.check() + vim.health.start('blink.cmp healthcheck') + + local required_executables = { 'curl', 'git' } + for _, executable in ipairs(required_executables) do + if vim.fn.executable(executable) == 0 then + vim.health.error(executable .. ' is not installed') + else + vim.health.ok(executable .. ' is installed') + end + end + + -- check if os is supported + local download_system = require('blink.cmp.fuzzy.download.system') + local system_triple = download_system.get_triple_sync() + if system_triple then + vim.health.ok('Your system is supported by pre-built binaries (' .. system_triple .. ')') + else + vim.health.warn( + 'Your system is not supported by pre-built binaries. You must run cargo build --release via your package manager with rust nightly. See the README for more info.' + ) + end + + local download_files = require('blink.cmp.fuzzy.download.files') + local lib_path_without_prefix = string.gsub(download_files.lib_path, 'libblink_cmp_fuzzy', 'blink_cmp_fuzzy') + if vim.uv.fs_stat(download_files.lib_path) or vim.uv.fs_stat(lib_path_without_prefix) then + vim.health.ok('blink_cmp_fuzzy lib is downloaded/built') + else + vim.health.warn('blink_cmp_fuzzy lib is not downloaded/built') + end +end + +return health diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/highlights.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/highlights.lua new file mode 100644 index 0000000..97db66f --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/highlights.lua @@ -0,0 +1,47 @@ +local highlights = {} + +function highlights.setup() + local use_nvim_cmp = require('blink.cmp.config').appearance.use_nvim_cmp_as_default + + --- @param hl_group string Highlight group name, e.g. 'ErrorMsg' + --- @param opts vim.api.keyset.highlight Highlight definition map + --- @return nil + local set_hl = function(hl_group, opts) + opts.default = true -- Prevents overriding existing definitions + vim.api.nvim_set_hl(0, hl_group, opts) + end + + if use_nvim_cmp then + set_hl('BlinkCmpLabel', { link = 'CmpItemAbbr' }) + set_hl('BlinkCmpLabelMatch', { link = 'CmpItemAbbrMatch' }) + end + + set_hl('BlinkCmpLabelDeprecated', { link = use_nvim_cmp and 'CmpItemAbbrDeprecated' or 'NonText' }) + set_hl('BlinkCmpLabelDetail', { link = use_nvim_cmp and 'CmpItemMenu' or 'NonText' }) + set_hl('BlinkCmpLabelDescription', { link = use_nvim_cmp and 'CmpItemMenu' or 'NonText' }) + set_hl('BlinkCmpKind', { link = use_nvim_cmp and 'CmpItemKind' or 'Special' }) + set_hl('BlinkCmpSource', { link = use_nvim_cmp and 'CmpItemMenu' or 'NonText' }) + for _, kind in ipairs(require('blink.cmp.types').CompletionItemKind) do + set_hl('BlinkCmpKind' .. kind, { link = use_nvim_cmp and 'CmpItemKind' .. kind or 'BlinkCmpKind' }) + end + + set_hl('BlinkCmpScrollBarThumb', { link = 'PmenuThumb' }) + set_hl('BlinkCmpScrollBarGutter', { link = 'PmenuSbar' }) + + set_hl('BlinkCmpGhostText', { link = use_nvim_cmp and 'CmpGhostText' or 'NonText' }) + + set_hl('BlinkCmpMenu', { link = 'Pmenu' }) + set_hl('BlinkCmpMenuBorder', { link = 'Pmenu' }) + set_hl('BlinkCmpMenuSelection', { link = 'PmenuSel' }) + + set_hl('BlinkCmpDoc', { link = 'NormalFloat' }) + set_hl('BlinkCmpDocBorder', { link = 'NormalFloat' }) + set_hl('BlinkCmpDocSeparator', { link = 'NormalFloat' }) + set_hl('BlinkCmpDocCursorLine', { link = 'Visual' }) + + set_hl('BlinkCmpSignatureHelp', { link = 'NormalFloat' }) + set_hl('BlinkCmpSignatureHelpBorder', { link = 'NormalFloat' }) + set_hl('BlinkCmpSignatureHelpActiveParameter', { link = 'LspSignatureActiveParameter' }) +end + +return highlights diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/init.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/init.lua new file mode 100644 index 0000000..deb9116 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/init.lua @@ -0,0 +1,235 @@ +--- @class blink.cmp.API +local cmp = {} + +local has_setup = false +--- Initializes blink.cmp with the given configuration and initiates the download +--- for the fuzzy matcher's prebuilt binaries, if necessary +--- @param opts? blink.cmp.Config +function cmp.setup(opts) + if has_setup then return end + has_setup = true + + opts = opts or {} + + if vim.fn.has('nvim-0.10') == 0 then + vim.notify('blink.cmp requires nvim 0.10 and newer', vim.log.levels.ERROR, { title = 'blink.cmp' }) + return + end + + local config = require('blink.cmp.config') + config.merge_with(opts) + + require('blink.cmp.fuzzy.download').ensure_downloaded(function(err) + if err then vim.notify(err, vim.log.levels.ERROR) end + + -- setup highlights, keymap, completion and signature help + require('blink.cmp.highlights').setup() + require('blink.cmp.keymap').setup() + require('blink.cmp.completion').setup() + if config.signature.enabled then require('blink.cmp.signature').setup() end + end) +end + +------- Public API ------- + +--- Checks if the completion menu is currently visible +--- @return boolean +function cmp.is_visible() + return require('blink.cmp.completion.windows.menu').win:is_open() + or require('blink.cmp.completion.windows.ghost_text').is_open() +end + +--- Show the completion window +--- @params opts? { providers?: string[], callback?: fun() } +function cmp.show(opts) + opts = opts or {} + + -- TODO: when passed a list of providers, we should check if we're already showing the menu + -- with that list of providers + if require('blink.cmp.completion.windows.menu').win:is_open() and not (opts and opts.providers) then return end + + vim.schedule(function() + require('blink.cmp.completion.windows.menu').auto_show = true + + -- HACK: because blink is event based, we don't have an easy way to know when the "show" + -- event completes. So we wait for the list to trigger the show event and check if we're + -- still in the same context + local context + if opts.callback then + vim.api.nvim_create_autocmd('User', { + pattern = 'BlinkCmpShow', + callback = function(event) + if context ~= nil and event.data.context.id == context.id then opts.callback() end + end, + once = true, + }) + end + + context = require('blink.cmp.completion.trigger').show({ + force = true, + providers = opts and opts.providers, + trigger_kind = 'manual', + }) + end) + return true +end + +--- Hide the completion window +--- @params opts? { callback?: fun() } +function cmp.hide(opts) + if not cmp.is_visible() then return end + + vim.schedule(function() + require('blink.cmp.completion.trigger').hide() + if opts and opts.callback then opts.callback() end + end) + return true +end + +--- Cancel the current completion, undoing the preview from auto_insert +--- @params opts? { callback?: fun() } +function cmp.cancel(opts) + if not cmp.is_visible() then return end + vim.schedule(function() + require('blink.cmp.completion.list').undo_preview() + require('blink.cmp.completion.trigger').hide() + if opts and opts.callback then opts.callback() end + end) + return true +end + +--- Accept the current completion item +--- @param opts? blink.cmp.CompletionListAcceptOpts +function cmp.accept(opts) + opts = opts or {} + if not cmp.is_visible() then return end + + local completion_list = require('blink.cmp.completion.list') + local item = opts.index ~= nil and completion_list.items[opts.index] or completion_list.get_selected_item() + if item == nil then return end + + vim.schedule(function() completion_list.accept(opts) end) + return true +end + +--- Select the first completion item, if there's no selection, and accept +--- @param opts? blink.cmp.CompletionListSelectAndAcceptOpts +function cmp.select_and_accept(opts) + if not cmp.is_visible() then return end + + local completion_list = require('blink.cmp.completion.list') + vim.schedule( + function() + completion_list.accept({ + index = completion_list.selected_item_idx or 1, + callback = opts and opts.callback, + }) + end + ) + return true +end + +--- Select the previous completion item +--- @param opts? blink.cmp.CompletionListSelectOpts +function cmp.select_prev(opts) + if not cmp.is_visible() then return end + vim.schedule(function() require('blink.cmp.completion.list').select_prev(opts) end) + return true +end + +--- Select the next completion item +--- @param opts? blink.cmp.CompletionListSelectOpts +function cmp.select_next(opts) + if not cmp.is_visible() then return end + vim.schedule(function() require('blink.cmp.completion.list').select_next(opts) end) + return true +end + +--- Gets the currently selected completion item +function cmp.get_selected_item() return require('blink.cmp.completion.list').get_selected_item() end + +--- Show the documentation window +function cmp.show_documentation() + local menu = require('blink.cmp.completion.windows.menu') + local documentation = require('blink.cmp.completion.windows.documentation') + if documentation.win:is_open() or not menu.win:is_open() then return end + + local context = require('blink.cmp.completion.list').context + local item = require('blink.cmp.completion.list').get_selected_item() + if not item or not context then return end + + vim.schedule(function() documentation.show_item(context, item) end) + return true +end + +--- Hide the documentation window +function cmp.hide_documentation() + local documentation = require('blink.cmp.completion.windows.documentation') + if not documentation.win:is_open() then return end + + vim.schedule(function() documentation.close() end) + return true +end + +--- Scroll the documentation window up +--- @param count? number +function cmp.scroll_documentation_up(count) + local documentation = require('blink.cmp.completion.windows.documentation') + if not documentation.win:is_open() then return end + + vim.schedule(function() documentation.scroll_up(count or 4) end) + return true +end + +--- Scroll the documentation window down +--- @param count? number +function cmp.scroll_documentation_down(count) + local documentation = require('blink.cmp.completion.windows.documentation') + if not documentation.win:is_open() then return end + + vim.schedule(function() documentation.scroll_down(count or 4) end) + return true +end + +--- Check if a snippet is active, optionally filtering by direction +--- @param filter? { direction?: number } +function cmp.snippet_active(filter) return require('blink.cmp.config').snippets.active(filter) end + +--- Move the cursor forward to the next snippet placeholder +function cmp.snippet_forward() + local snippets = require('blink.cmp.config').snippets + if not snippets.active({ direction = 1 }) then return end + vim.schedule(function() snippets.jump(1) end) + return true +end + +--- Move the cursor backward to the previous snippet placeholder +function cmp.snippet_backward() + local snippets = require('blink.cmp.config').snippets + if not snippets.active({ direction = -1 }) then return end + vim.schedule(function() snippets.jump(-1) end) + return true +end + +--- Tells the sources to reload a specific provider or all providers (when nil) +--- @param provider? string +function cmp.reload(provider) require('blink.cmp.sources.lib').reload(provider) end + +--- Gets the capabilities to pass to the LSP client +--- @param override? lsp.ClientCapabilities Overrides blink.cmp's default capabilities +--- @param include_nvim_defaults? boolean Whether to include nvim's default capabilities +function cmp.get_lsp_capabilities(override, include_nvim_defaults) + return require('blink.cmp.sources.lib').get_lsp_capabilities(override, include_nvim_defaults) +end + +--- Add a new source provider at runtime +--- @param id string +--- @param provider_config blink.cmp.SourceProviderConfig +function cmp.add_provider(id, provider_config) + local config = require('blink.cmp.config') + assert(config.sources.providers[id] == nil, 'Provider with id ' .. id .. ' already exists') + require('blink.cmp.config.sources').validate_provider(id, provider_config) + config.sources.providers[id] = provider_config +end + +return cmp diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/keymap/apply.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/keymap/apply.lua new file mode 100644 index 0000000..b10bd0f --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/keymap/apply.lua @@ -0,0 +1,132 @@ +local apply = {} + +local snippet_commands = { 'snippet_forward', 'snippet_backward' } + +--- Applies the keymaps to the current buffer +--- @param keys_to_commands table<string, blink.cmp.KeymapCommand[]> +function apply.keymap_to_current_buffer(keys_to_commands) + -- skip if we've already applied the keymaps + for _, mapping in ipairs(vim.api.nvim_buf_get_keymap(0, 'i')) do + if mapping.desc == 'blink.cmp' then return end + end + + -- insert mode: uses both snippet and insert commands + for key, commands in pairs(keys_to_commands) do + if #commands == 0 then goto continue end + + local fallback = require('blink.cmp.keymap.fallback').wrap('i', key) + apply.set('i', key, function() + if not require('blink.cmp.config').enabled() then return fallback() end + + for _, command in ipairs(commands) do + -- special case for fallback + if command == 'fallback' then + return fallback() + + -- run user defined functions + elseif type(command) == 'function' then + if command(require('blink.cmp')) then return end + + -- otherwise, run the built-in command + elseif require('blink.cmp')[command]() then + return + end + end + end) + + ::continue:: + end + + -- snippet mode: uses only snippet commands + for key, commands in pairs(keys_to_commands) do + local has_snippet_command = false + for _, command in ipairs(commands) do + if vim.tbl_contains(snippet_commands, command) or type(command) == 'function' then has_snippet_command = true end + end + if not has_snippet_command or #commands == 0 then goto continue end + + local fallback = require('blink.cmp.keymap.fallback').wrap('s', key) + apply.set('s', key, function() + if not require('blink.cmp.config').enabled() then return fallback() end + + for _, command in ipairs(keys_to_commands[key] or {}) do + -- special case for fallback + if command == 'fallback' then + return fallback() + + -- run user defined functions + elseif type(command) == 'function' then + if command(require('blink.cmp')) then return end + + -- only run snippet commands + elseif vim.tbl_contains(snippet_commands, command) then + local did_run = require('blink.cmp')[command]() + if did_run then return end + end + end + end) + + ::continue:: + end +end + +function apply.cmdline_keymaps(keys_to_commands) + -- cmdline mode: uses only insert commands + for key, commands in pairs(keys_to_commands) do + local has_insert_command = false + for _, command in ipairs(commands) do + has_insert_command = has_insert_command or not vim.tbl_contains(snippet_commands, command) + end + if not has_insert_command or #commands == 0 then goto continue end + + local fallback = require('blink.cmp.keymap.fallback').wrap('c', key) + apply.set('c', key, function() + for _, command in ipairs(commands) do + -- special case for fallback + if command == 'fallback' then + return fallback() + + -- run user defined functions + elseif type(command) == 'function' then + if command(require('blink.cmp')) then return end + + -- otherwise, run the built-in command + elseif not vim.tbl_contains(snippet_commands, command) then + local did_run = require('blink.cmp')[command]() + if did_run then return end + end + end + end) + + ::continue:: + end +end + +--- @param mode string +--- @param key string +--- @param callback fun(): string | nil +function apply.set(mode, key, callback) + if mode == 'c' then + vim.api.nvim_set_keymap(mode, key, '', { + callback = callback, + expr = true, + -- silent must be false for fallback to work + -- otherwise, you get very weird behavior + silent = false, + noremap = true, + replace_keycodes = false, + desc = 'blink.cmp', + }) + else + vim.api.nvim_buf_set_keymap(0, mode, key, '', { + callback = callback, + expr = true, + silent = true, + noremap = true, + replace_keycodes = false, + desc = 'blink.cmp', + }) + end +end + +return apply diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/keymap/fallback.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/keymap/fallback.lua new file mode 100644 index 0000000..a73d69e --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/keymap/fallback.lua @@ -0,0 +1,91 @@ +local fallback = {} + +--- Add missing types. Remove when fixed upstream +---@class blink.cmp.Fallback : vim.api.keyset.keymap +---@field lhs string +---@field mode string +---@field rhs? string +---@field lhsraw? string +---@field buffer? number + +--- Gets the non blink.cmp global keymap for the given mode and key +--- @param mode string +--- @param key string +--- @return blink.cmp.Fallback | nil +function fallback.get_non_blink_global_mapping_for_key(mode, key) + local normalized_key = vim.api.nvim_replace_termcodes(key, true, true, true) + + -- get global mappings + local mappings = vim.api.nvim_get_keymap(mode) + + for _, mapping in ipairs(mappings) do + --- @cast mapping blink.cmp.Fallback + local mapping_key = vim.api.nvim_replace_termcodes(mapping.lhs, true, true, true) + if mapping_key == normalized_key and mapping.desc ~= 'blink.cmp' then return mapping end + end +end + +--- Gets the non blink.cmp buffer keymap for the given mode and key +--- @param mode string +--- @param key string +--- @return blink.cmp.Fallback? +function fallback.get_non_blink_buffer_mapping_for_key(mode, key) + local normalized_key = vim.api.nvim_replace_termcodes(key, true, true, true) + + local buffer_mappings = vim.api.nvim_buf_get_keymap(0, mode) + + for _, mapping in ipairs(buffer_mappings) do + --- @cast mapping blink.cmp.Fallback + local mapping_key = vim.api.nvim_replace_termcodes(mapping.lhs, true, true, true) + if mapping_key == normalized_key and mapping.desc ~= 'blink.cmp' then return mapping end + end +end + +--- Returns a function that will run the first non blink.cmp keymap for the given mode and key +--- @param mode string +--- @param key string +--- @return fun(): string? +function fallback.wrap(mode, key) + -- In default mode, there can't be multiple mappings on a single key for buffer local mappings + -- In cmdline mode, there can't be multiple mappings on a single key for global mappings + local buffer_mapping = mode ~= 'c' and fallback.get_non_blink_buffer_mapping_for_key(mode, key) + or fallback.get_non_blink_global_mapping_for_key(mode, key) + return function() + local mapping = buffer_mapping or fallback.get_non_blink_global_mapping_for_key(mode, key) + if mapping then return fallback.run_non_blink_keymap(mapping, key) end + return vim.api.nvim_replace_termcodes(key, true, true, true) + end +end + +--- Runs the first non blink.cmp keymap for the given mode and key +--- @param mapping blink.cmp.Fallback +--- @param key string +--- @return string | nil +function fallback.run_non_blink_keymap(mapping, key) + -- TODO: there's likely many edge cases here. the nvim-cmp version is lacking documentation + -- and is quite complex. we should look to see if we can simplify their logic + -- https://github.com/hrsh7th/nvim-cmp/blob/ae644feb7b67bf1ce4260c231d1d4300b19c6f30/lua/cmp/utils/keymap.lua + if type(mapping.callback) == 'function' then + -- with expr = true, which we use, we can't modify the buffer without scheduling + -- so if the keymap does not use expr, we must schedule it + if mapping.expr ~= 1 then + vim.schedule(mapping.callback) + return + end + + local expr = mapping.callback() + if type(expr) == 'string' and mapping.replace_keycodes == 1 then + expr = vim.api.nvim_replace_termcodes(expr, true, true, true) + end + return expr + elseif mapping.rhs then + local rhs = vim.api.nvim_replace_termcodes(mapping.rhs, true, true, true) + if mapping.expr == 1 then rhs = vim.api.nvim_eval(rhs) end + return rhs + end + + -- pass the key along as usual + return vim.api.nvim_replace_termcodes(key, true, true, true) +end + +return fallback diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/keymap/init.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/keymap/init.lua new file mode 100644 index 0000000..a5e7009 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/keymap/init.lua @@ -0,0 +1,74 @@ +local keymap = {} + +--- Lowercases all keys in the mappings table +--- @param existing_mappings table<string, blink.cmp.KeymapCommand[]> +--- @param new_mappings table<string, blink.cmp.KeymapCommand[]> +--- @return table<string, blink.cmp.KeymapCommand[]> +function keymap.merge_mappings(existing_mappings, new_mappings) + local merged_mappings = vim.deepcopy(existing_mappings) + for new_key, new_mapping in pairs(new_mappings) do + -- normalize the keys and replace, since naively merging would not handle <C-a> == <c-a> + for existing_key, _ in pairs(existing_mappings) do + if + vim.api.nvim_replace_termcodes(existing_key, true, true, true) + == vim.api.nvim_replace_termcodes(new_key, true, true, true) + then + merged_mappings[existing_key] = new_mapping + goto continue + end + end + + -- key wasn't found, add it as per usual + merged_mappings[new_key] = new_mapping + + ::continue:: + end + return merged_mappings +end + +---@param keymap_config blink.cmp.BaseKeymapConfig +function keymap.get_mappings(keymap_config) + local mappings = vim.deepcopy(keymap_config) + + -- Handle preset + if mappings.preset then + local preset_keymap = require('blink.cmp.keymap.presets').get(mappings.preset) + + -- Remove 'preset' key from opts to prevent it from being treated as a keymap + mappings.preset = nil + + -- Merge the preset keymap with the user-defined keymaps + -- User-defined keymaps overwrite the preset keymaps + mappings = keymap.merge_mappings(preset_keymap, mappings) + end + return mappings +end + +function keymap.setup() + local config = require('blink.cmp.config') + local mappings = keymap.get_mappings(config.keymap) + -- We set on the buffer directly to avoid buffer-local keymaps (such as from autopairs) + -- from overriding our mappings. We also use InsertEnter to avoid conflicts with keymaps + -- applied on other autocmds, such as LspAttach used by nvim-lspconfig and most configs + vim.api.nvim_create_autocmd('InsertEnter', { + callback = function() + if not require('blink.cmp.config').enabled() then return end + require('blink.cmp.keymap.apply').keymap_to_current_buffer(mappings) + end, + }) + + -- This is not called when the plugin loads since it first checks if the binary is + -- installed. As a result, when lazy-loaded on InsertEnter, the event may be missed + if vim.api.nvim_get_mode().mode == 'i' and require('blink.cmp.config').enabled() then + require('blink.cmp.keymap.apply').keymap_to_current_buffer(mappings) + end + + -- Apply cmdline keymaps since they're global, if any sources are defined + local cmdline_sources = require('blink.cmp.config').sources.cmdline + if type(cmdline_sources) ~= 'table' or #cmdline_sources > 0 then + local cmdline_mappings = keymap.get_mappings(config.keymap.cmdline or config.keymap) + require('blink.cmp.keymap.apply').cmdline_keymaps(cmdline_mappings) + end +end + +return keymap diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/keymap/presets.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/keymap/presets.lua new file mode 100644 index 0000000..1e7de16 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/keymap/presets.lua @@ -0,0 +1,72 @@ +local presets = { + none = {}, + + default = { + ['<C-space>'] = { 'show', 'show_documentation', 'hide_documentation' }, + ['<C-e>'] = { 'cancel', 'fallback' }, + ['<C-y>'] = { 'select_and_accept' }, + + ['<C-p>'] = { 'select_prev', 'fallback' }, + ['<C-n>'] = { 'select_next', 'fallback' }, + + ['<C-b>'] = { 'scroll_documentation_up', 'fallback' }, + ['<C-f>'] = { 'scroll_documentation_down', 'fallback' }, + + ['<Tab>'] = { 'snippet_forward', 'fallback' }, + ['<S-Tab>'] = { 'snippet_backward', 'fallback' }, + }, + + ['super-tab'] = { + ['<C-space>'] = { 'show', 'show_documentation', 'hide_documentation' }, + ['<C-e>'] = { 'cancel', 'fallback' }, + + ['<Tab>'] = { + function(cmp) + if cmp.snippet_active() then + return cmp.accept() + else + return cmp.select_and_accept() + end + end, + 'snippet_forward', + 'fallback', + }, + ['<S-Tab>'] = { 'snippet_backward', 'fallback' }, + + ['<Up>'] = { 'select_prev', 'fallback' }, + ['<Down>'] = { 'select_next', 'fallback' }, + ['<C-p>'] = { 'select_prev', 'fallback' }, + ['<C-n>'] = { 'select_next', 'fallback' }, + + ['<C-b>'] = { 'scroll_documentation_up', 'fallback' }, + ['<C-f>'] = { 'scroll_documentation_down', 'fallback' }, + }, + + enter = { + ['<C-space>'] = { 'show', 'show_documentation', 'hide_documentation' }, + ['<C-e>'] = { 'cancel', 'fallback' }, + ['<CR>'] = { 'accept', 'fallback' }, + + ['<Tab>'] = { 'snippet_forward', 'fallback' }, + ['<S-Tab>'] = { 'snippet_backward', 'fallback' }, + + ['<Up>'] = { 'select_prev', 'fallback' }, + ['<Down>'] = { 'select_next', 'fallback' }, + ['<C-p>'] = { 'select_prev', 'fallback' }, + ['<C-n>'] = { 'select_next', 'fallback' }, + + ['<C-b>'] = { 'scroll_documentation_up', 'fallback' }, + ['<C-f>'] = { 'scroll_documentation_down', 'fallback' }, + }, +} + +--- Gets the preset keymap for the given preset name +--- @param name string +--- @return table<string, blink.cmp.KeymapCommand[]> +function presets.get(name) + local preset = presets[name] + if preset == nil then error('Invalid blink.cmp keymap preset: ' .. name) end + return preset +end + +return presets diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/async.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/async.lua new file mode 100644 index 0000000..b9c39ac --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/async.lua @@ -0,0 +1,217 @@ +--- Allows chaining of async operations without callback hell +--- +--- @class blink.cmp.Task +--- @field status blink.cmp.TaskStatus +--- @field result any | nil +--- @field error any | nil +--- @field new fun(fn: fun(resolve: fun(result: any), reject: fun(err: any))): blink.cmp.Task +--- +--- @field cancel fun(self: blink.cmp.Task) +--- @field map fun(self: blink.cmp.Task, fn: fun(result: any): blink.cmp.Task | any): blink.cmp.Task +--- @field catch fun(self: blink.cmp.Task, fn: fun(err: any): blink.cmp.Task | any): blink.cmp.Task +--- +--- @field on_completion fun(self: blink.cmp.Task, cb: fun(result: any)) +--- @field on_failure fun(self: blink.cmp.Task, cb: fun(err: any)) +--- @field on_cancel fun(self: blink.cmp.Task, cb: fun()) +--- @field _completion_cbs function[] +--- @field _failure_cbs function[] +--- @field _cancel_cbs function[] +--- @field _cancel? fun() +local task = { + __task = true, +} + +---@enum blink.cmp.TaskStatus +local STATUS = { + RUNNING = 1, + COMPLETED = 2, + FAILED = 3, + CANCELLED = 4, +} + +function task.new(fn) + local self = setmetatable({}, { __index = task }) + self.status = STATUS.RUNNING + self._completion_cbs = {} + self._failure_cbs = {} + self._cancel_cbs = {} + self.result = nil + self.error = nil + + local resolve = function(result) + if self.status ~= STATUS.RUNNING then return end + + self.status = STATUS.COMPLETED + self.result = result + + for _, cb in ipairs(self._completion_cbs) do + cb(result) + end + end + + local reject = function(err) + if self.status ~= STATUS.RUNNING then return end + + self.status = STATUS.FAILED + self.error = err + + for _, cb in ipairs(self._failure_cbs) do + cb(err) + end + end + + local success, cancel_fn_or_err = pcall(function() return fn(resolve, reject) end) + + if not success then + reject(cancel_fn_or_err) + elseif type(cancel_fn_or_err) == 'function' then + self._cancel = cancel_fn_or_err + end + + return self +end + +function task:cancel() + if self.status ~= STATUS.RUNNING then return end + self.status = STATUS.CANCELLED + + if self._cancel ~= nil then self._cancel() end + for _, cb in ipairs(self._cancel_cbs) do + cb() + end +end + +--- mappings + +function task:map(fn) + local chained_task + chained_task = task.new(function(resolve, reject) + self:on_completion(function(result) + local success, mapped_result = pcall(fn, result) + if not success then + reject(mapped_result) + return + end + + if type(mapped_result) == 'table' and mapped_result.__task then + mapped_result:on_completion(resolve) + mapped_result:on_failure(reject) + mapped_result:on_cancel(function() chained_task:cancel() end) + return + end + resolve(mapped_result) + end) + self:on_failure(reject) + self:on_cancel(function() chained_task:cancel() end) + return function() chained_task:cancel() end + end) + return chained_task +end + +function task:catch(fn) + local chained_task + chained_task = task.new(function(resolve, reject) + self:on_completion(resolve) + self:on_failure(function(err) + local success, mapped_err = pcall(fn, err) + if not success then + reject(mapped_err) + return + end + + if type(mapped_err) == 'table' and mapped_err.__task then + mapped_err:on_completion(resolve) + mapped_err:on_failure(reject) + mapped_err:on_cancel(function() chained_task:cancel() end) + return + end + resolve(mapped_err) + end) + self:on_cancel(function() chained_task:cancel() end) + return function() chained_task:cancel() end + end) + return chained_task +end + +--- events + +function task:on_completion(cb) + if self.status == STATUS.COMPLETED then + cb(self.result) + elseif self.status == STATUS.RUNNING then + table.insert(self._completion_cbs, cb) + end + return self +end + +function task:on_failure(cb) + if self.status == STATUS.FAILED then + cb(self.error) + elseif self.status == STATUS.RUNNING then + table.insert(self._failure_cbs, cb) + end + return self +end + +function task:on_cancel(cb) + if self.status == STATUS.CANCELLED then + cb() + elseif self.status == STATUS.RUNNING then + table.insert(self._cancel_cbs, cb) + end + return self +end + +--- utils + +function task.await_all(tasks) + if #tasks == 0 then + return task.new(function(resolve) resolve({}) end) + end + + local all_task + all_task = task.new(function(resolve, reject) + local results = {} + local has_resolved = {} + + local function resolve_if_completed() + -- we can't check #results directly because a table like + -- { [2] = { ... } } has a length of 2 + for i = 1, #tasks do + if has_resolved[i] == nil then return end + end + resolve(results) + end + + for idx, task in ipairs(tasks) do + task:on_completion(function(result) + results[idx] = result + has_resolved[idx] = true + resolve_if_completed() + end) + task:on_failure(function(err) + reject(err) + for _, task in ipairs(tasks) do + task:cancel() + end + end) + task:on_cancel(function() + for _, sub_task in ipairs(tasks) do + sub_task:cancel() + end + if all_task == nil then + vim.schedule(function() all_task:cancel() end) + else + all_task:cancel() + end + end) + end + end) + return all_task +end + +function task.empty() + return task.new(function(resolve) resolve() end) +end + +return { task = task, STATUS = STATUS } diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/buffer_events.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/buffer_events.lua new file mode 100644 index 0000000..dcca8b8 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/buffer_events.lua @@ -0,0 +1,148 @@ +--- Exposes three events (cursor moved, char added, insert leave) for triggers to use. +--- Notably, when "char added" is fired, the "cursor moved" event will not be fired. +--- Unlike in regular neovim, ctrl + c and buffer switching will trigger "insert leave" + +--- @class blink.cmp.BufferEvents +--- @field has_context fun(): boolean +--- @field show_in_snippet boolean +--- @field ignore_next_text_changed boolean +--- @field ignore_next_cursor_moved boolean +--- +--- @field new fun(opts: blink.cmp.BufferEventsOptions): blink.cmp.BufferEvents +--- @field listen fun(self: blink.cmp.BufferEvents, opts: blink.cmp.BufferEventsListener) +--- @field suppress_events_for_callback fun(self: blink.cmp.BufferEvents, cb: fun()) + +--- @class blink.cmp.BufferEventsOptions +--- @field has_context fun(): boolean +--- @field show_in_snippet boolean + +--- @class blink.cmp.BufferEventsListener +--- @field on_char_added fun(char: string, is_ignored: boolean) +--- @field on_cursor_moved fun(event: 'CursorMoved' | 'InsertEnter', is_ignored: boolean) +--- @field on_insert_leave fun() + +--- @type blink.cmp.BufferEvents +--- @diagnostic disable-next-line: missing-fields +local buffer_events = {} + +function buffer_events.new(opts) + return setmetatable({ + has_context = opts.has_context, + show_in_snippet = opts.show_in_snippet, + ignore_next_text_changed = false, + ignore_next_cursor_moved = false, + }, { __index = buffer_events }) +end + +--- Normalizes the autocmds + ctrl+c into a common api and handles ignored events +function buffer_events:listen(opts) + local snippet = require('blink.cmp.config').snippets + + local last_char = '' + vim.api.nvim_create_autocmd('InsertCharPre', { + callback = function() + if snippet.active() and not self.show_in_snippet and not self.has_context() then return end + last_char = vim.v.char + end, + }) + + vim.api.nvim_create_autocmd('TextChangedI', { + callback = function() + if not require('blink.cmp.config').enabled() then return end + if snippet.active() and not self.show_in_snippet and not self.has_context() then return end + + local is_ignored = self.ignore_next_text_changed + self.ignore_next_text_changed = false + + -- no characters added so let cursormoved handle it + if last_char == '' then return end + + opts.on_char_added(last_char, is_ignored) + + last_char = '' + end, + }) + + vim.api.nvim_create_autocmd({ 'CursorMoved', 'CursorMovedI', 'InsertEnter' }, { + callback = function(ev) + -- only fire a CursorMoved event (notable not CursorMovedI) + -- when jumping between tab stops in a snippet while showing the menu + if + ev.event == 'CursorMoved' + and (vim.api.nvim_get_mode().mode ~= 'v' or not self.has_context() or not snippet.active()) + then + return + end + + local is_cursor_moved = ev.event == 'CursorMoved' or ev.event == 'CursorMovedI' + + local is_ignored = is_cursor_moved and self.ignore_next_cursor_moved + if is_cursor_moved then self.ignore_next_cursor_moved = false end + + -- characters added so let textchanged handle it + if last_char ~= '' then return end + + if not require('blink.cmp.config').enabled() then return end + if not self.show_in_snippet and not self.has_context() and snippet.active() then return end + + opts.on_cursor_moved(is_cursor_moved and 'CursorMoved' or ev.event, is_ignored) + end, + }) + + -- definitely leaving the context + vim.api.nvim_create_autocmd({ 'ModeChanged', 'BufLeave' }, { + callback = function() + last_char = '' + -- HACK: when using vim.snippet.expand, the mode switches from insert -> normal -> visual -> select + -- so we schedule to ignore the intermediary modes + -- TODO: deduplicate requests + vim.schedule(function() + if not vim.tbl_contains({ 'i', 's' }, vim.api.nvim_get_mode().mode) then opts.on_insert_leave() end + end) + end, + }) + + -- ctrl+c doesn't trigger InsertLeave so handle it separately + local ctrl_c = vim.api.nvim_replace_termcodes('<C-c>', true, true, true) + vim.on_key(function(key) + if key == ctrl_c then + vim.schedule(function() + local mode = vim.api.nvim_get_mode().mode + if mode ~= 'i' then + last_char = '' + opts.on_insert_leave() + end + end) + end + end) +end + +--- Suppresses autocmd events for the duration of the callback +--- HACK: there's likely edge cases with this since we can't know for sure +--- if the autocmds will fire for cursor_moved afaik +function buffer_events:suppress_events_for_callback(cb) + local cursor_before = vim.api.nvim_win_get_cursor(0) + local changed_tick_before = vim.api.nvim_buf_get_changedtick(0) + + cb() + + local cursor_after = vim.api.nvim_win_get_cursor(0) + local changed_tick_after = vim.api.nvim_buf_get_changedtick(0) + + local is_insert_mode = vim.api.nvim_get_mode().mode == 'i' + + self.ignore_next_text_changed = changed_tick_after ~= changed_tick_before and is_insert_mode + + -- HACK: the cursor may move from position (1, 1) to (1, 0) and back to (1, 1) during the callback + -- This will trigger a CursorMovedI event, but we can't detect it simply by checking the cursor position + -- since they're equal before vs after the callback. So instead, we always mark the cursor as ignored in + -- insert mode, but if the cursor was equal, we undo the ignore after a small delay, which practically guarantees + -- that the CursorMovedI event will fire + -- TODO: It could make sense to override the nvim_win_set_cursor function and mark as ignored if it's called + -- on the current buffer + local cursor_moved = cursor_after[1] ~= cursor_before[1] or cursor_after[2] ~= cursor_before[2] + self.ignore_next_cursor_moved = is_insert_mode + if not cursor_moved then vim.defer_fn(function() self.ignore_next_cursor_moved = false end, 10) end +end + +return buffer_events diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/cmdline_events.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/cmdline_events.lua new file mode 100644 index 0000000..6f23ed8 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/cmdline_events.lua @@ -0,0 +1,104 @@ +--- @class blink.cmp.CmdlineEvents +--- @field has_context fun(): boolean +--- @field ignore_next_text_changed boolean +--- @field ignore_next_cursor_moved boolean +--- +--- @field new fun(): blink.cmp.CmdlineEvents +--- @field listen fun(self: blink.cmp.CmdlineEvents, opts: blink.cmp.CmdlineEventsListener) +--- @field suppress_events_for_callback fun(self: blink.cmp.CmdlineEvents, cb: fun()) + +--- @class blink.cmp.CmdlineEventsListener +--- @field on_char_added fun(char: string, is_ignored: boolean) +--- @field on_cursor_moved fun(event: 'CursorMoved' | 'InsertEnter', is_ignored: boolean) +--- @field on_leave fun() + +--- @type blink.cmp.CmdlineEvents +--- @diagnostic disable-next-line: missing-fields +local cmdline_events = {} + +function cmdline_events.new() + return setmetatable({ + ignore_next_text_changed = false, + ignore_next_cursor_moved = false, + }, { __index = cmdline_events }) +end + +function cmdline_events:listen(opts) + -- TextChanged + local on_changed = function(key) opts.on_char_added(key, false) end + + -- We handle backspace as a special case, because the text will have changed + -- but we still want to fire the CursorMoved event, and not the TextChanged event + local did_backspace = false + local is_change_queued = false + vim.on_key(function(raw_key, escaped_key) + if vim.api.nvim_get_mode().mode ~= 'c' then return end + + -- ignore if it's a special key + -- FIXME: odd behavior when escaped_key has multiple keycodes, i.e. by pressing <C-p> and then "t" + local key = vim.fn.keytrans(escaped_key) + if key == '<BS>' and not is_change_queued then did_backspace = true end + if key:sub(1, 1) == '<' and key:sub(#key, #key) == '>' and raw_key ~= ' ' then return end + if key == '' then return end + + if not is_change_queued then + is_change_queued = true + did_backspace = false + vim.schedule(function() + on_changed(raw_key) + is_change_queued = false + end) + end + end) + + -- CursorMoved + local previous_cmdline = '' + vim.api.nvim_create_autocmd('CmdlineEnter', { + callback = function() previous_cmdline = '' end, + }) + + -- TODO: switch to CursorMovedC when nvim 0.11 is released + -- HACK: check every 16ms (60 times/second) to see if the cursor moved + -- for neovim < 0.11 + local timer = vim.uv.new_timer() + local previous_cursor + local callback + callback = vim.schedule_wrap(function() + timer:start(16, 0, callback) + if vim.api.nvim_get_mode().mode ~= 'c' then return end + + local cmdline_equal = vim.fn.getcmdline() == previous_cmdline + local cursor_equal = vim.fn.getcmdpos() == previous_cursor + + previous_cmdline = vim.fn.getcmdline() + previous_cursor = vim.fn.getcmdpos() + + if cursor_equal or (not cmdline_equal and not did_backspace) then return end + did_backspace = false + + local is_ignored = self.ignore_next_cursor_moved + self.ignore_next_cursor_moved = false + + opts.on_cursor_moved('CursorMoved', is_ignored) + end) + timer:start(16, 0, callback) + + vim.api.nvim_create_autocmd('CmdlineLeave', { + callback = function() opts.on_leave() end, + }) +end + +--- Suppresses autocmd events for the duration of the callback +--- HACK: there's likely edge cases with this +function cmdline_events:suppress_events_for_callback(cb) + local cursor_before = vim.fn.getcmdpos() + + cb() + + if not vim.api.nvim_get_mode().mode == 'c' then return end + + local cursor_after = vim.fn.getcmdpos() + self.ignore_next_cursor_moved = self.ignore_next_cursor_moved or cursor_after ~= cursor_before +end + +return cmdline_events diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/event_emitter.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/event_emitter.lua new file mode 100644 index 0000000..d3939cb --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/event_emitter.lua @@ -0,0 +1,37 @@ +--- @class blink.cmp.EventEmitter<T> : { event: string, autocmd?: string, listeners: table<fun(data: T)>, new: ( fun(event: string, autocmd: string): blink.cmp.EventEmitter ), on: ( fun(self: blink.cmp.EventEmitter, callback: fun(data: T)) ), off: ( fun(self: blink.cmp.EventEmitter, callback: fun(data: T)) ), emit: ( fun(self: blink.cmp.EventEmitter, data?: table) ) }; +--- TODO: is there a better syntax for this? + +local event_emitter = {} + +--- @param event string +--- @param autocmd? string +function event_emitter.new(event, autocmd) + local self = setmetatable({}, { __index = event_emitter }) + self.event = event + self.autocmd = autocmd + self.listeners = {} + return self +end + +function event_emitter:on(callback) table.insert(self.listeners, callback) end + +function event_emitter:off(callback) + for idx, cb in ipairs(self.listeners) do + if cb == callback then table.remove(self.listeners, idx) end + end +end + +function event_emitter:emit(data) + data = data or {} + data.event = self.event + for _, callback in ipairs(self.listeners) do + callback(data) + end + if self.autocmd then + require('blink.cmp.lib.utils').schedule_if_needed( + function() vim.api.nvim_exec_autocmds('User', { pattern = self.autocmd, modeline = false, data = data }) end + ) + end +end + +return event_emitter diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/text_edits.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/text_edits.lua new file mode 100644 index 0000000..2ce76fe --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/text_edits.lua @@ -0,0 +1,193 @@ +local config = require('blink.cmp.config') +local context = require('blink.cmp.completion.trigger.context') + +local text_edits = {} + +--- Applies one or more text edits to the current buffer, assuming utf-8 encoding +--- @param edits lsp.TextEdit[] +function text_edits.apply(edits) + local mode = context.get_mode() + if mode == 'default' then return vim.lsp.util.apply_text_edits(edits, vim.api.nvim_get_current_buf(), 'utf-8') end + + assert(mode == 'cmdline', 'Unsupported mode for text edits: ' .. mode) + assert(#edits == 1, 'Cmdline mode only supports one text edit. Contributions welcome!') + + local edit = edits[1] + local line = context.get_line() + local edited_line = line:sub(1, edit.range.start.character) + .. edit.newText + .. line:sub(edit.range['end'].character + 1) + -- FIXME: for some reason, we have to set the cursor here, instead of later, + -- because this will override the cursor position set later + vim.fn.setcmdline(edited_line, edit.range.start.character + #edit.newText + 1) +end + +------- Undo ------- + +--- Gets the reverse of the text edit, must be called before applying +--- @param text_edit lsp.TextEdit +--- @return lsp.TextEdit +function text_edits.get_undo_text_edit(text_edit) + return { + range = text_edits.get_undo_range(text_edit), + newText = text_edits.get_text_to_replace(text_edit), + } +end + +--- Gets the range for undoing an applied text edit +--- @param text_edit lsp.TextEdit +function text_edits.get_undo_range(text_edit) + text_edit = vim.deepcopy(text_edit) + local lines = vim.split(text_edit.newText, '\n') + local last_line_len = lines[#lines] and #lines[#lines] or 0 + + local range = text_edit.range + range['end'].line = range.start.line + #lines - 1 + range['end'].character = #lines > 1 and last_line_len or range.start.character + last_line_len + + return range +end + +--- Gets the text the text edit will replace +--- @param text_edit lsp.TextEdit +--- @return string +function text_edits.get_text_to_replace(text_edit) + local lines = {} + for line = text_edit.range.start.line, text_edit.range['end'].line do + local line_text = context.get_line() + local is_start_line = line == text_edit.range.start.line + local is_end_line = line == text_edit.range['end'].line + + if is_start_line and is_end_line then + table.insert(lines, line_text:sub(text_edit.range.start.character + 1, text_edit.range['end'].character)) + elseif is_start_line then + table.insert(lines, line_text:sub(text_edit.range.start.character + 1)) + elseif is_end_line then + table.insert(lines, line_text:sub(1, text_edit.range['end'].character)) + else + table.insert(lines, line_text) + end + end + return table.concat(lines, '\n') +end + +------- Get ------- + +--- Grabbed from vim.lsp.utils. Converts an offset_encoding to byte offset +--- @param position lsp.Position +--- @param offset_encoding? 'utf-8'|'utf-16'|'utf-32' +--- @return number +local function get_line_byte_from_position(position, offset_encoding) + local bufnr = vim.api.nvim_get_current_buf() + local col = position.character + + -- When on the first character, we can ignore the difference between byte and character + if col == 0 then return 0 end + + local line = vim.api.nvim_buf_get_lines(bufnr, position.line, position.line + 1, false)[1] or '' + if vim.fn.has('nvim-0.11.0') == 1 then + col = vim.str_byteindex(line, offset_encoding or 'utf-16', col, false) or 0 + else + col = vim.lsp.util._str_byteindex_enc(line, col, offset_encoding or 'utf-16') + end + return math.min(col, #line) +end + +--- Gets the text edit from an item, handling insert/replace ranges and converts +--- offset encodings (utf-16 | utf-32) to utf-8 +--- @param item blink.cmp.CompletionItem +--- @return lsp.TextEdit +function text_edits.get_from_item(item) + local text_edit = vim.deepcopy(item.textEdit) + + -- Guess the text edit if the item doesn't define it + if text_edit == nil then return text_edits.guess(item) end + + -- FIXME: temporarily convert insertReplaceEdit to regular textEdit + if text_edit.range == nil then + if config.completion.keyword.range == 'full' and text_edit.replace ~= nil then + text_edit.range = text_edit.replace + else + text_edit.range = text_edit.insert or text_edit.replace + end + end + text_edit.insert = nil + text_edit.replace = nil + --- @cast text_edit lsp.TextEdit + + -- Adjust the position of the text edit to be the current cursor position + -- since the data might be outdated. We compare the cursor column position + -- from when the items were fetched versus the current. + -- HACK: is there a better way? + -- TODO: take into account the offset_encoding + local offset = context.get_cursor()[2] - item.cursor_column + text_edit.range['end'].character = text_edit.range['end'].character + offset + + -- convert the offset encoding to utf-8 + -- TODO: we have to do this last because it applies a max on the position based on the length of the line + -- so it would break the offset code when removing characters at the end of the line + local offset_encoding = text_edits.offset_encoding_from_item(item) + text_edit = text_edits.to_utf_8(text_edit, offset_encoding) + + text_edit.range = text_edits.clamp_range_to_bounds(text_edit.range) + + return text_edit +end + +function text_edits.offset_encoding_from_item(item) + local client = vim.lsp.get_client_by_id(item.client_id) + return client ~= nil and client.offset_encoding or 'utf-8' +end + +function text_edits.to_utf_8(text_edit, offset_encoding) + if offset_encoding == 'utf-8' then return text_edit end + text_edit = vim.deepcopy(text_edit) + text_edit.range.start.character = get_line_byte_from_position(text_edit.range.start, offset_encoding) + text_edit.range['end'].character = get_line_byte_from_position(text_edit.range['end'], offset_encoding) + return text_edit +end + +--- Uses the keyword_regex to guess the text edit ranges +--- @param item blink.cmp.CompletionItem +--- TODO: doesnt work when the item contains characters not included in the context regex +function text_edits.guess(item) + local word = item.insertText or item.label + + local start_col, end_col = require('blink.cmp.fuzzy').guess_edit_range( + item, + context.get_line(), + context.get_cursor()[2], + config.completion.keyword.range + ) + local current_line = context.get_cursor()[1] + + -- convert to 0-index + return { + range = { + start = { line = current_line - 1, character = start_col }, + ['end'] = { line = current_line - 1, character = end_col }, + }, + newText = word, + } +end + +--- Clamps the range to the bounds of their respective lines +--- @param range lsp.Range +--- @return lsp.Range +--- TODO: clamp start and end lines +function text_edits.clamp_range_to_bounds(range) + range = vim.deepcopy(range) + + local start_line = context.get_line(range.start.line) + range.start.character = math.min(math.max(range.start.character, 0), #start_line) + + local end_line = context.get_line(range['end'].line) + range['end'].character = math.min( + math.max(range['end'].character, range.start.line == range['end'].line and range.start.character or 0), + #end_line + ) + + return range +end + +return text_edits diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/utils.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/utils.lua new file mode 100644 index 0000000..84bcc3d --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/utils.lua @@ -0,0 +1,112 @@ +local utils = {} + +--- Shallow copy table +--- @generic T +--- @param t T +--- @return T +function utils.shallow_copy(t) + local t2 = {} + for k, v in pairs(t) do + t2[k] = v + end + return t2 +end + +--- Returns the union of the keys of two tables +--- @generic T +--- @param t1 T[] +--- @param t2 T[] +--- @return T[] +function utils.union_keys(t1, t2) + local t3 = {} + for k, _ in pairs(t1) do + t3[k] = true + end + for k, _ in pairs(t2) do + t3[k] = true + end + return vim.tbl_keys(t3) +end + +--- Returns a list of unique values from the input array +--- @generic T +--- @param arr T[] +--- @return T[] +function utils.deduplicate(arr) + local hash = {} + for _, v in ipairs(arr) do + hash[v] = true + end + return vim.tbl_keys(hash) +end + +function utils.schedule_if_needed(fn) + if vim.in_fast_event() then + vim.schedule(fn) + else + fn() + end +end + +--- Flattens an arbitrarily deep table into a single level table +--- @param t table +--- @return table +function utils.flatten(t) + if t[1] == nil then return t end + + local flattened = {} + for _, v in ipairs(t) do + if type(v) == 'table' and vim.tbl_isempty(v) then goto continue end + + if v[1] == nil then + table.insert(flattened, v) + else + vim.list_extend(flattened, utils.flatten(v)) + end + + ::continue:: + end + return flattened +end + +--- Returns the index of the first occurrence of the value in the array +--- @generic T +--- @param arr T[] +--- @param val T +--- @return number? +function utils.index_of(arr, val) + for idx, v in ipairs(arr) do + if v == val then return idx end + end + return nil +end + +--- Finds an item in an array using a predicate function +--- @generic T +--- @param arr T[] +--- @param predicate fun(item: T): boolean +--- @return number? +function utils.find_idx(arr, predicate) + for idx, v in ipairs(arr) do + if predicate(v) then return idx end + end + return nil +end + +--- Slices an array +--- @generic T +--- @param arr T[] +--- @param start number? +--- @param finish number? +--- @return T[] +function utils.slice(arr, start, finish) + start = start or 1 + finish = finish or #arr + local sliced = {} + for i = start, finish do + sliced[#sliced + 1] = arr[i] + end + return sliced +end + +return utils diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/window/docs.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/window/docs.lua new file mode 100644 index 0000000..f250701 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/window/docs.lua @@ -0,0 +1,224 @@ +local highlight_ns = require('blink.cmp.config').appearance.highlight_ns + +local docs = {} + +--- @class blink.cmp.RenderDetailAndDocumentationOpts +--- @field bufnr number +--- @field detail? string|string[] +--- @field documentation? lsp.MarkupContent | string +--- @field max_width number +--- @field use_treesitter_highlighting boolean? + +--- @class blink.cmp.RenderDetailAndDocumentationOptsPartial +--- @field bufnr? number +--- @field detail? string +--- @field documentation? lsp.MarkupContent | string +--- @field max_width? number +--- @field use_treesitter_highlighting boolean? + +--- @param opts blink.cmp.RenderDetailAndDocumentationOpts +function docs.render_detail_and_documentation(opts) + local detail_lines = {} + local details = type(opts.detail) == 'string' and { opts.detail } or opts.detail or {} + --- @cast details string[] + details = require('blink.cmp.lib.utils').deduplicate(details) + for _, v in ipairs(details) do + vim.list_extend(detail_lines, docs.split_lines(v)) + end + + local doc_lines = {} + if opts.documentation ~= nil then + local doc = type(opts.documentation) == 'string' and opts.documentation or opts.documentation.value + doc_lines = docs.split_lines(doc) + end + + detail_lines, doc_lines = docs.extract_detail_from_doc(detail_lines, doc_lines) + + ---@type string[] + local combined_lines = vim.list_extend({}, detail_lines) + + -- add a blank line for the --- separator + local doc_already_has_separator = #doc_lines > 1 and (doc_lines[1] == '---' or doc_lines[1] == '***') + if #detail_lines > 0 and #doc_lines > 0 then table.insert(combined_lines, '') end + -- skip original separator in doc_lines, so we can highlight it later + vim.list_extend(combined_lines, doc_lines, doc_already_has_separator and 2 or 1) + + vim.api.nvim_buf_set_lines(opts.bufnr, 0, -1, true, combined_lines) + vim.api.nvim_set_option_value('modified', false, { buf = opts.bufnr }) + + -- Highlight with treesitter + vim.api.nvim_buf_clear_namespace(opts.bufnr, highlight_ns, 0, -1) + + if #detail_lines > 0 and opts.use_treesitter_highlighting then + docs.highlight_with_treesitter(opts.bufnr, vim.bo.filetype, 0, #detail_lines) + end + + -- Only add the separator if there are documentation lines (otherwise only display the detail) + if #detail_lines > 0 and #doc_lines > 0 then + vim.api.nvim_buf_set_extmark(opts.bufnr, highlight_ns, #detail_lines, 0, { + virt_text = { { string.rep('─', opts.max_width), 'BlinkCmpDocSeparator' } }, + virt_text_pos = 'overlay', + }) + end + + if #doc_lines > 0 and opts.use_treesitter_highlighting then + local start = #detail_lines + (#detail_lines > 0 and 1 or 0) + docs.highlight_with_treesitter(opts.bufnr, 'markdown', start, start + #doc_lines) + end +end + +--- Highlights the given range with treesitter with the given filetype +--- @param bufnr number +--- @param filetype string +--- @param start_line number +--- @param end_line number +--- TODO: fallback to regex highlighting if treesitter fails +--- TODO: only render what's visible +function docs.highlight_with_treesitter(bufnr, filetype, start_line, end_line) + local Range = require('vim.treesitter._range') + + local root_lang = vim.treesitter.language.get_lang(filetype) + if root_lang == nil then return end + + local success, trees = pcall(vim.treesitter.get_parser, bufnr, root_lang) + if not success or not trees then return end + + trees:parse({ start_line, end_line }) + + trees:for_each_tree(function(tree, tstree) + local lang = tstree:lang() + local highlighter_query = vim.treesitter.query.get(lang, 'highlights') + if not highlighter_query then return end + + local root_node = tree:root() + local _, _, root_end_row, _ = root_node:range() + + local iter = highlighter_query:iter_captures(tree:root(), bufnr, start_line, end_line) + local line = start_line + while line < end_line do + local capture, node, metadata, _ = iter(line) + if capture == nil then break end + + local range = { root_end_row + 1, 0, root_end_row + 1, 0 } + if node then range = vim.treesitter.get_range(node, bufnr, metadata and metadata[capture]) end + local start_row, start_col, end_row, end_col = Range.unpack4(range) + + if capture then + local name = highlighter_query.captures[capture] + local hl = 0 + if not vim.startswith(name, '_') then hl = vim.api.nvim_get_hl_id_by_name('@' .. name .. '.' .. lang) end + + -- The "priority" attribute can be set at the pattern level or on a particular capture + local priority = ( + tonumber(metadata.priority or metadata[capture] and metadata[capture].priority) + or vim.highlight.priorities.treesitter + ) + + -- The "conceal" attribute can be set at the pattern level or on a particular capture + local conceal = metadata.conceal or metadata[capture] and metadata[capture].conceal + + if hl and end_row >= line then + vim.api.nvim_buf_set_extmark(bufnr, highlight_ns, start_row, start_col, { + end_line = end_row, + end_col = end_col, + hl_group = hl, + priority = priority, + conceal = conceal, + }) + end + end + + if start_row > line then line = start_row end + end + end) +end + +--- Gets the start and end row of the code block for the given row +--- Or returns nil if there's no code block +--- @param lines string[] +--- @param row number +--- @return number?, number? +function docs.get_code_block_range(lines, row) + if row < 1 or row > #lines then return end + -- get the start of the code block + local code_block_start = nil + for i = 1, row do + local line = lines[i] + if line:match('^%s*```') then + if code_block_start == nil then + code_block_start = i + else + code_block_start = nil + end + end + end + if code_block_start == nil then return end + + -- get the end of the code block + local code_block_end = nil + for i = row, #lines do + local line = lines[i] + if line:match('^%s*```') then + code_block_end = i + break + end + end + if code_block_end == nil then return end + + return code_block_start, code_block_end +end + +--- Avoids showing the detail if it's part of the documentation +--- or, if the detail is in a code block in the doc, +--- extracts the code block into the detail +---@param detail_lines string[] +---@param doc_lines string[] +---@return string[], string[] +--- TODO: Also move the code block into detail if it's at the start of the doc +--- and we have no detail +function docs.extract_detail_from_doc(detail_lines, doc_lines) + local detail_str = table.concat(detail_lines, '\n') + local doc_str = table.concat(doc_lines, '\n') + local doc_str_detail_row = doc_str:find(detail_str, 1, true) + + -- didn't find the detail in the doc, so return as is + if doc_str_detail_row == nil or #detail_str == 0 or #doc_str == 0 then return detail_lines, doc_lines end + + -- get the line of the match + -- hack: surely there's a better way to do this but it's late + -- and I can't be bothered + local offset = 1 + local detail_line = 1 + for line_num, line in ipairs(doc_lines) do + if #line + offset > doc_str_detail_row then + detail_line = line_num + break + end + offset = offset + #line + 1 + end + + -- extract the code block, if it exists, and use it as the detail + local code_block_start, code_block_end = docs.get_code_block_range(doc_lines, detail_line) + if code_block_start ~= nil and code_block_end ~= nil then + detail_lines = vim.list_slice(doc_lines, code_block_start + 1, code_block_end - 1) + + local doc_lines_start = vim.list_slice(doc_lines, 1, code_block_start - 1) + local doc_lines_end = vim.list_slice(doc_lines, code_block_end + 1, #doc_lines) + vim.list_extend(doc_lines_start, doc_lines_end) + doc_lines = doc_lines_start + else + detail_lines = {} + end + + return detail_lines, doc_lines +end + +function docs.split_lines(text) + local lines = {} + for s in text:gmatch('[^\r\n]+') do + table.insert(lines, s) + end + return lines +end + +return docs diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/window/init.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/window/init.lua new file mode 100644 index 0000000..9a5d18b --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/window/init.lua @@ -0,0 +1,445 @@ +-- TODO: The scrollbar and redrawing logic should be done by wrapping the functions that would +-- trigger a redraw or update the window + +--- @class blink.cmp.WindowOptions +--- @field min_width? number +--- @field max_width? number +--- @field max_height? number +--- @field cursorline? boolean +--- @field border? blink.cmp.WindowBorder +--- @field wrap? boolean +--- @field winblend? number +--- @field winhighlight? string +--- @field scrolloff? number +--- @field scrollbar? boolean +--- @field filetype string + +--- @class blink.cmp.Window +--- @field id? number +--- @field buf? number +--- @field config blink.cmp.WindowOptions +--- @field scrollbar? blink.cmp.Scrollbar +--- @field redraw_queued boolean +--- +--- @field new fun(config: blink.cmp.WindowOptions): blink.cmp.Window +--- @field get_buf fun(self: blink.cmp.Window): number +--- @field get_win fun(self: blink.cmp.Window): number +--- @field is_open fun(self: blink.cmp.Window): boolean +--- @field open fun(self: blink.cmp.Window) +--- @field close fun(self: blink.cmp.Window) +--- @field set_option_value fun(self: blink.cmp.Window, option: string, value: any) +--- @field update_size fun(self: blink.cmp.Window) +--- @field get_content_height fun(self: blink.cmp.Window): number +--- @field get_border_size fun(self: blink.cmp.Window, border?: 'none' | 'single' | 'double' | 'rounded' | 'solid' | 'shadow' | 'padded' | string[]): { vertical: number, horizontal: number, left: number, right: number, top: number, bottom: number } +--- @field get_height fun(self: blink.cmp.Window): number +--- @field get_content_width fun(self: blink.cmp.Window): number +--- @field get_width fun(self: blink.cmp.Window): number +--- @field get_cursor_screen_position fun(): { distance_from_top: number, distance_from_bottom: number } +--- @field set_cursor fun(self: blink.cmp.Window, cursor: number[]) +--- @field set_height fun(self: blink.cmp.Window, height: number) +--- @field set_width fun(self: blink.cmp.Window, width: number) +--- @field set_win_config fun(self: blink.cmp.Window, config: table) +--- @field get_vertical_direction_and_height fun(self: blink.cmp.Window, direction_priority: ("n" | "s")[]): { height: number, direction: 'n' | 's' }? +--- @field get_direction_with_window_constraints fun(self: blink.cmp.Window, anchor_win: blink.cmp.Window, direction_priority: ("n" | "s" | "e" | "w")[], desired_min_size?: { width: number, height: number }): { width: number, height: number, direction: 'n' | 's' | 'e' | 'w' }? +--- @field redraw_if_needed fun(self: blink.cmp.Window) + +--- @type blink.cmp.Window +--- @diagnostic disable-next-line: missing-fields +local win = {} + +--- @param config blink.cmp.WindowOptions +function win.new(config) + local self = setmetatable({}, { __index = win }) + + self.id = nil + self.buf = nil + self.config = { + min_width = config.min_width, + max_width = config.max_width, + max_height = config.max_height or 10, + cursorline = config.cursorline or false, + border = config.border or 'none', + wrap = config.wrap or false, + winblend = config.winblend or 0, + winhighlight = config.winhighlight or 'Normal:NormalFloat,FloatBorder:NormalFloat', + scrolloff = config.scrolloff or 0, + scrollbar = config.scrollbar, + filetype = config.filetype, + } + self.redraw_queued = false + + if self.config.scrollbar then + self.scrollbar = require('blink.cmp.lib.window.scrollbar').new({ + enable_gutter = self.config.border == 'none' or self.config.border == 'padded', + }) + end + + return self +end + +function win:get_buf() + -- create buffer if it doesn't exist + if self.buf == nil or not vim.api.nvim_buf_is_valid(self.buf) then + self.buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_set_option_value('tabstop', 1, { buf = self.buf }) -- prevents tab widths from being unpredictable + end + return self.buf +end + +function win:get_win() + if self.id ~= nil and not vim.api.nvim_win_is_valid(self.id) then self.id = nil end + return self.id +end + +function win:is_open() return self.id ~= nil and vim.api.nvim_win_is_valid(self.id) end + +function win:open() + -- window already exists + if self.id ~= nil and vim.api.nvim_win_is_valid(self.id) then return end + + -- create window + self.id = vim.api.nvim_open_win(self:get_buf(), false, { + relative = 'cursor', + style = 'minimal', + width = self.config.min_width or 1, + height = self.config.max_height, + row = 1, + col = 1, + focusable = false, + zindex = 1001, + border = self.config.border == 'padded' and { ' ', '', '', ' ', '', '', ' ', ' ' } or self.config.border, + }) + vim.api.nvim_set_option_value('winblend', self.config.winblend, { win = self.id }) + vim.api.nvim_set_option_value('winhighlight', self.config.winhighlight, { win = self.id }) + vim.api.nvim_set_option_value('wrap', self.config.wrap, { win = self.id }) + vim.api.nvim_set_option_value('foldenable', false, { win = self.id }) + vim.api.nvim_set_option_value('conceallevel', 2, { win = self.id }) + vim.api.nvim_set_option_value('concealcursor', 'n', { win = self.id }) + vim.api.nvim_set_option_value('cursorlineopt', 'line', { win = self.id }) + vim.api.nvim_set_option_value('cursorline', self.config.cursorline, { win = self.id }) + vim.api.nvim_set_option_value('scrolloff', self.config.scrolloff, { win = self.id }) + vim.api.nvim_set_option_value('filetype', self.config.filetype, { buf = self.buf }) + + if self.scrollbar then self.scrollbar:update(self.id) end + self:redraw_if_needed() +end + +function win:set_option_value(option, value) + if self.id == nil or not vim.api.nvim_win_is_valid(self.id) then return end + vim.api.nvim_set_option_value(option, value, { win = self.id }) +end + +function win:close() + if self.id ~= nil then + vim.api.nvim_win_close(self.id, true) + self.id = nil + end + if self.scrollbar then self.scrollbar:update() end + self:redraw_if_needed() +end + +--- Updates the size of the window to match the max width and height of the content/config +function win:update_size() + if not self:is_open() then return end + local winnr = self:get_win() + local config = self.config + + -- todo: never go above the screen width and height + + -- set width to current content width, bounded by min and max + local width = self:get_content_width() + if config.max_width then width = math.min(width, config.max_width) end + if config.min_width then width = math.max(width, config.min_width) end + vim.api.nvim_win_set_width(winnr, width) + + -- set height to current line count, bounded by max + local height = math.min(self:get_content_height(), config.max_height) + vim.api.nvim_win_set_height(winnr, height) +end + +-- todo: fix nvim_win_text_height +-- @return number +function win:get_content_height() + if not self:is_open() then return 0 end + return vim.api.nvim_win_text_height(self:get_win(), {}).all +end + +--- Gets the size of the borders around the window +--- @return { vertical: number, horizontal: number, left: number, right: number, top: number, bottom: number } +function win:get_border_size() + if not self:is_open() then return { vertical = 0, horizontal = 0, left = 0, right = 0, top = 0, bottom = 0 } end + + local left = 0 + local right = 0 + local top = 0 + local bottom = 0 + + local border = self.config.border + if border == 'padded' then + left = 1 + right = 1 + elseif border == 'shadow' then + right = 1 + bottom = 1 + elseif type(border) == 'string' and border ~= 'none' then + left = 1 + right = 1 + top = 1 + bottom = 1 + elseif type(border) == 'table' then + -- borders can be a table of strings and act differently with different # of chars + -- so we normalize it: https://neovim.io/doc/user/api.html#nvim_open_win() + -- based on nvim-cmp + -- TODO: doesn't handle scrollbar + local resolved_border = {} + while #resolved_border <= 8 do + for _, b in ipairs(border) do + table.insert(resolved_border, type(b) == 'string' and b or b[1]) + end + end + + top = resolved_border[2] == '' and 0 or 1 + bottom = resolved_border[6] == '' and 0 or 1 + left = resolved_border[8] == '' and 0 or 1 + right = resolved_border[4] == '' and 0 or 1 + end + + if self.scrollbar and self.scrollbar:is_visible() then + local offset = (border == 'none' or border == 'padded') and 1 or 0 + right = right + offset + end + + return { vertical = top + bottom, horizontal = left + right, left = left, right = right, top = top, bottom = bottom } +end + +--- Gets the height of the window, taking into account the border +function win:get_height() + if not self:is_open() then return 0 end + return vim.api.nvim_win_get_height(self:get_win()) + self:get_border_size().vertical +end + +--- Gets the width of the longest line in the window +function win:get_content_width() + if not self:is_open() then return 0 end + local max_width = 0 + for _, line in ipairs(vim.api.nvim_buf_get_lines(self.buf, 0, -1, false)) do + max_width = math.max(max_width, vim.api.nvim_strwidth(line)) + end + return max_width +end + +--- Gets the width of the window, taking into account the border +function win:get_width() + if not self:is_open() then return 0 end + return vim.api.nvim_win_get_width(self:get_win()) + self:get_border_size().horizontal +end + +--- Gets the cursor's distance from all sides of the screen +function win.get_cursor_screen_position() + local screen_height = vim.o.lines + local screen_width = vim.o.columns + + -- command line + if vim.api.nvim_get_mode().mode == 'c' then + local config = require('blink.cmp.config').completion.menu + local cmdline_position = config.cmdline_position() + + return { + distance_from_top = cmdline_position[1], + distance_from_bottom = screen_height - cmdline_position[1] - 1, + distance_from_left = cmdline_position[2], + distance_from_right = screen_width - cmdline_position[2], + } + end + + -- default + local cursor_line, cursor_column = unpack(vim.api.nvim_win_get_cursor(0)) + -- todo: convert cursor_column to byte index + local pos = vim.fn.screenpos(vim.api.nvim_win_get_number(0), cursor_line, cursor_column) + + return { + distance_from_top = pos.row - 1, + distance_from_bottom = screen_height - pos.row, + distance_from_left = pos.col, + distance_from_right = screen_width - pos.col, + } +end + +function win:set_cursor(cursor) + local winnr = self:get_win() + assert(winnr ~= nil, 'Window must be open to set cursor') + + vim.api.nvim_win_set_cursor(winnr, cursor) + + if self.scrollbar then self.scrollbar:update(winnr) end + self:redraw_if_needed() +end + +function win:set_height(height) + local winnr = self:get_win() + assert(winnr ~= nil, 'Window must be open to set height') + + vim.api.nvim_win_set_height(winnr, height) + + if self.scrollbar then self.scrollbar:update(winnr) end + self:redraw_if_needed() +end + +function win:set_width(width) + local winnr = self:get_win() + assert(winnr ~= nil, 'Window must be open to set width') + + vim.api.nvim_win_set_width(winnr, width) + + if self.scrollbar then self.scrollbar:update(winnr) end + self:redraw_if_needed() +end + +function win:set_win_config(config) + local winnr = self:get_win() + assert(winnr ~= nil, 'Window must be open to set window config') + + vim.api.nvim_win_set_config(winnr, config) + + if self.scrollbar then self.scrollbar:update(winnr) end + self:redraw_if_needed() +end + +--- Gets the direction with the most space available, prioritizing the directions in the order of the +--- direction_priority list +function win:get_vertical_direction_and_height(direction_priority) + local constraints = self.get_cursor_screen_position() + local max_height = self:get_height() + local border_size = self:get_border_size() + local function get_distance(direction) + return direction == 's' and constraints.distance_from_bottom or constraints.distance_from_top + end + + local direction_priority_by_space = vim.fn.sort(vim.deepcopy(direction_priority), function(a, b) + local distance_a = math.min(max_height, get_distance(a)) + local distance_b = math.min(max_height, get_distance(b)) + return (distance_a < distance_b) and 1 or (distance_a > distance_b) and -1 or 0 + end) + + local direction = direction_priority_by_space[1] + local height = math.min(max_height, get_distance(direction)) + if height <= border_size.vertical then return end + return { height = height - border_size.vertical, direction = direction } +end + +function win:get_direction_with_window_constraints(anchor_win, direction_priority, desired_min_size) + local cursor_constraints = self.get_cursor_screen_position() + + -- nvim.api.nvim_win_get_position doesn't return the correct position most of the time + -- so we calculate the position ourselves + local anchor_config + local anchor_win_config = vim.api.nvim_win_get_config(anchor_win:get_win()) + if anchor_win_config.relative == 'win' then + local anchor_relative_win_position = vim.api.nvim_win_get_position(anchor_win_config.win) + anchor_config = { + row = anchor_win_config.row + anchor_relative_win_position[1] + 1, + col = anchor_win_config.col + anchor_relative_win_position[2] + 1, + } + elseif anchor_win_config.relative == 'editor' then + anchor_config = { + row = anchor_win_config.row + 1, + col = anchor_win_config.col + 1, + } + end + assert(anchor_config ~= nil, 'The anchor window must be relative to a window or the editor') + + -- compensate for the anchor window being too wide given the screen width and configured column + if anchor_config.col + anchor_win_config.width > vim.o.columns then + anchor_config.col = vim.o.columns - anchor_win_config.width + end + + local anchor_border_size = anchor_win:get_border_size() + local anchor_col = anchor_config.col - anchor_border_size.left + local anchor_row = anchor_config.row - anchor_border_size.top + local anchor_height = anchor_win:get_height() + local anchor_width = anchor_win:get_width() + + -- we want to avoid covering the cursor line, so we need to get the direction of the window + -- that we're anchoring against + local cursor_screen_row = vim.api.nvim_get_mode().mode == 'c' and vim.o.lines - 1 or vim.fn.winline() + local anchor_is_above_cursor = anchor_config.row - cursor_screen_row < 0 + + local screen_height = vim.o.lines + local screen_width = vim.o.columns + + local direction_constraints = { + n = { + vertical = anchor_is_above_cursor and (anchor_row - 1) or cursor_constraints.distance_from_top, + horizontal = screen_width - (anchor_col - 1), + }, + s = { + vertical = anchor_is_above_cursor and cursor_constraints.distance_from_bottom + or (screen_height - (anchor_height + anchor_row - 1 + anchor_border_size.vertical)), + horizontal = screen_width - (anchor_col - 1), + }, + e = { + vertical = anchor_is_above_cursor and cursor_constraints.distance_from_top + or cursor_constraints.distance_from_bottom, + horizontal = screen_width - (anchor_col - 1) - anchor_width - anchor_border_size.right, + }, + w = { + vertical = anchor_is_above_cursor and cursor_constraints.distance_from_top + or cursor_constraints.distance_from_bottom, + horizontal = anchor_col - 1 + anchor_border_size.left, + }, + } + + local max_height = self:get_height() + local max_width = self:get_width() + local direction_priority_by_space = vim.fn.sort(vim.deepcopy(direction_priority), function(a, b) + local constraints_a = direction_constraints[a] + local constraints_b = direction_constraints[b] + + local is_desired_a = desired_min_size.height <= constraints_a.vertical + and desired_min_size.width <= constraints_a.horizontal + local is_desired_b = desired_min_size.height <= constraints_b.vertical + and desired_min_size.width <= constraints_b.horizontal + + -- If both have desired size, preserve original priority + if is_desired_a and is_desired_b then return 0 end + + -- prioritize "a" if it has the desired size and "b" doesn't + if is_desired_a then return -1 end + + -- prioritize "b" if it has the desired size and "a" doesn't + if is_desired_b then return 1 end + + -- neither have the desired size, so pick based on which has the most space + local distance_a = math.min(max_height, constraints_a.vertical, constraints_a.horizontal) + local distance_b = math.min(max_height, constraints_b.vertical, constraints_b.horizontal) + return distance_a < distance_b and 1 or distance_a > distance_b and -1 or 0 + end) + + local border_size = self:get_border_size() + local direction = direction_priority_by_space[1] + local height = math.min(max_height, direction_constraints[direction].vertical) + if height <= border_size.vertical then return end + local width = math.min(max_width, direction_constraints[direction].horizontal) + if width <= border_size.horizontal then return end + + return { + width = width - border_size.horizontal, + height = height - border_size.vertical, + direction = direction, + } +end + +--- In cmdline mode, the window won't be redrawn automatically so we redraw ourselves on schedule +function win:redraw_if_needed() + if self.redraw_queued or vim.api.nvim_get_mode().mode ~= 'c' or self:get_win() == nil then return end + + -- We redraw on schedule to avoid the cmdline disappearing during redraw + -- and to batch multiple redraws together + self.redraw_queued = true + vim.schedule(function() + self.redraw_queued = false + vim.api.nvim__redraw({ win = self:get_win(), flush = true }) + end) +end + +return win diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/window/scrollbar/geometry.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/window/scrollbar/geometry.lua new file mode 100644 index 0000000..ad481a0 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/window/scrollbar/geometry.lua @@ -0,0 +1,92 @@ +--- Helper for calculating placement of the scrollbar thumb and gutter + +--- @class blink.cmp.ScrollbarGeometry +--- @field width number +--- @field height number +--- @field row number +--- @field col number +--- @field zindex number +--- @field relative string +--- @field win number + +local M = {} + +--- @param target_win number +--- @return number +local function get_win_buf_height(target_win) + local buf = vim.api.nvim_win_get_buf(target_win) + + -- not wrapping, so just get the line count + if not vim.wo[target_win].wrap then return vim.api.nvim_buf_line_count(buf) end + + local width = vim.api.nvim_win_get_width(target_win) + local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false) + local height = 0 + for _, l in ipairs(lines) do + height = height + math.max(1, (math.ceil(vim.fn.strwidth(l) / width))) + end + return height +end + +--- @param border string|string[] +--- @return number +local function get_col_offset(border) + -- we only need an extra offset when working with a padded window + if type(border) == 'table' and border[1] == ' ' and border[4] == ' ' and border[7] == ' ' and border[8] == ' ' then + return 1 + end + return 0 +end + +--- Gets the starting line, handling line wrapping if enabled +--- @param target_win number +--- @param width number +--- @return number +local get_content_start_line = function(target_win, width) + local start_line = math.max(1, vim.fn.line('w0', target_win)) + if not vim.wo[target_win].wrap then return start_line end + + local bufnr = vim.api.nvim_win_get_buf(target_win) + local wrapped_start_line = 1 + for _, text in ipairs(vim.api.nvim_buf_get_lines(bufnr, 0, start_line - 1, false)) do + -- nvim_buf_get_lines sometimes returns a blob. see hrsh7th/nvim-cmp#2050 + if vim.fn.type(text) == vim.v.t_blob then text = vim.fn.string(text) end + wrapped_start_line = wrapped_start_line + math.max(1, math.ceil(vim.fn.strdisplaywidth(text) / width)) + end + return wrapped_start_line +end + +--- @param target_win number +--- @return { should_hide: boolean, thumb: blink.cmp.ScrollbarGeometry, gutter: blink.cmp.ScrollbarGeometry } +function M.get_geometry(target_win) + local config = vim.api.nvim_win_get_config(target_win) + local width = config.width + local height = config.height + local zindex = config.zindex + + local buf_height = get_win_buf_height(target_win) + local thumb_height = math.max(1, math.floor(height * height / buf_height + 0.5) - 1) + + local start_line = get_content_start_line(target_win, width or 1) + + local pct = (start_line - 1) / (buf_height - height) + local thumb_offset = math.floor((pct * (height - thumb_height)) + 0.5) + thumb_height = thumb_offset + thumb_height > height and height - thumb_offset or thumb_height + thumb_height = math.max(1, thumb_height) + + local common_geometry = { + width = 1, + row = thumb_offset, + col = width + get_col_offset(config.border), + relative = 'win', + win = target_win, + } + + return { + should_hide = height >= buf_height, + thumb = vim.tbl_deep_extend('force', common_geometry, { height = thumb_height, zindex = zindex + 2 }), + gutter = vim.tbl_deep_extend('force', common_geometry, { row = 0, height = height, zindex = zindex + 1 }), + } +end + +return M diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/window/scrollbar/init.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/window/scrollbar/init.lua new file mode 100644 index 0000000..c72615a --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/window/scrollbar/init.lua @@ -0,0 +1,37 @@ +-- TODO: move the set_config and set_height calls from the menu/documentation/signature files +-- to helpers in the window lib, and call scrollbar updates from there. This way, consumers of +-- the window lib don't need to worry about scrollbars + +--- @class blink.cmp.ScrollbarConfig +--- @field enable_gutter boolean + +--- @class blink.cmp.Scrollbar +--- @field win blink.cmp.ScrollbarWin +--- +--- @field new fun(opts: blink.cmp.ScrollbarConfig): blink.cmp.Scrollbar +--- @field is_visible fun(self: blink.cmp.Scrollbar): boolean +--- @field update fun(self: blink.cmp.Scrollbar, target_win: number | nil) + +--- @type blink.cmp.Scrollbar +--- @diagnostic disable-next-line: missing-fields +local scrollbar = {} + +function scrollbar.new(opts) + local self = setmetatable({}, { __index = scrollbar }) + self.win = require('blink.cmp.lib.window.scrollbar.win').new(opts) + return self +end + +function scrollbar:is_visible() return self.win:is_visible() end + +function scrollbar:update(target_win) + if target_win == nil or not vim.api.nvim_win_is_valid(target_win) then return self.win:hide() end + + local geometry = require('blink.cmp.lib.window.scrollbar.geometry').get_geometry(target_win) + if geometry.should_hide then return self.win:hide() end + + self.win:show_thumb(geometry.thumb) + self.win:show_gutter(geometry.gutter) +end + +return scrollbar diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/window/scrollbar/win.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/window/scrollbar/win.lua new file mode 100644 index 0000000..9ac3193 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/lib/window/scrollbar/win.lua @@ -0,0 +1,107 @@ +--- Manages creating/updating scrollbar gutter and thumb windows + +--- @class blink.cmp.ScrollbarWin +--- @field enable_gutter boolean +--- @field thumb_win? number +--- @field gutter_win? number +--- @field buf? number +--- +--- @field new fun(opts: blink.cmp.ScrollbarConfig): blink.cmp.ScrollbarWin +--- @field is_visible fun(self: blink.cmp.ScrollbarWin): boolean +--- @field show_thumb fun(self: blink.cmp.ScrollbarWin, geometry: blink.cmp.ScrollbarGeometry) +--- @field show_gutter fun(self: blink.cmp.ScrollbarWin, geometry: blink.cmp.ScrollbarGeometry) +--- @field hide_thumb fun(self: blink.cmp.ScrollbarWin) +--- @field hide_gutter fun(self: blink.cmp.ScrollbarWin) +--- @field hide fun(self: blink.cmp.ScrollbarWin) +--- @field _make_win fun(self: blink.cmp.ScrollbarWin, geometry: blink.cmp.ScrollbarGeometry, hl_group: string): number +--- @field redraw_if_needed fun(self: blink.cmp.ScrollbarWin) + +--- @type blink.cmp.ScrollbarWin +--- @diagnostic disable-next-line: missing-fields +local scrollbar_win = {} + +function scrollbar_win.new(opts) return setmetatable(opts, { __index = scrollbar_win }) end + +function scrollbar_win:is_visible() return self.thumb_win ~= nil and vim.api.nvim_win_is_valid(self.thumb_win) end + +function scrollbar_win:show_thumb(geometry) + -- create window if it doesn't exist + if self.thumb_win == nil or not vim.api.nvim_win_is_valid(self.thumb_win) then + self.thumb_win = self:_make_win(geometry, 'BlinkCmpScrollBarThumb') + else + -- update with the geometry + local thumb_existing_config = vim.api.nvim_win_get_config(self.thumb_win) + local thumb_config = vim.tbl_deep_extend('force', thumb_existing_config, geometry) + vim.api.nvim_win_set_config(self.thumb_win, thumb_config) + end + + self:redraw_if_needed() +end + +function scrollbar_win:show_gutter(geometry) + if not self.enable_gutter then return end + + -- create window if it doesn't exist + if self.gutter_win == nil or not vim.api.nvim_win_is_valid(self.gutter_win) then + self.gutter_win = self:_make_win(geometry, 'BlinkCmpScrollBarGutter') + else + -- update with the geometry + local gutter_existing_config = vim.api.nvim_win_get_config(self.gutter_win) + local gutter_config = vim.tbl_deep_extend('force', gutter_existing_config, geometry) + vim.api.nvim_win_set_config(self.gutter_win, gutter_config) + end + + self:redraw_if_needed() +end + +function scrollbar_win:hide_thumb() + if self.thumb_win and vim.api.nvim_win_is_valid(self.thumb_win) then + vim.api.nvim_win_close(self.thumb_win, true) + self.thumb_win = nil + self:redraw_if_needed() + end +end + +function scrollbar_win:hide_gutter() + if self.gutter_win and vim.api.nvim_win_is_valid(self.gutter_win) then + vim.api.nvim_win_close(self.gutter_win, true) + self.gutter_win = nil + self:redraw_if_needed() + end +end + +function scrollbar_win:hide() + self:hide_thumb() + self:hide_gutter() +end + +function scrollbar_win:_make_win(geometry, hl_group) + if self.buf == nil or not vim.api.nvim_buf_is_valid(self.buf) then self.buf = vim.api.nvim_create_buf(false, true) end + + local win_config = vim.tbl_deep_extend('force', geometry, { + style = 'minimal', + focusable = false, + noautocmd = true, + }) + local win = vim.api.nvim_open_win(self.buf, false, win_config) + vim.api.nvim_set_option_value('winhighlight', 'Normal:' .. hl_group .. ',EndOfBuffer:' .. hl_group, { win = win }) + return win +end + +local redraw_queued = false +function scrollbar_win:redraw_if_needed() + if redraw_queued or vim.api.nvim_get_mode().mode ~= 'c' then return end + + redraw_queued = true + vim.schedule(function() + redraw_queued = false + if self.gutter_win ~= nil and vim.api.nvim_win_is_valid(self.gutter_win) then + vim.api.nvim__redraw({ win = self.gutter_win, flush = true }) + end + if self.thumb_win ~= nil and vim.api.nvim_win_is_valid(self.thumb_win) then + vim.api.nvim__redraw({ win = self.thumb_win, flush = true }) + end + end) +end + +return scrollbar_win diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/signature/init.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/signature/init.lua new file mode 100644 index 0000000..8c70032 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/signature/init.lua @@ -0,0 +1,25 @@ +local signature = {} + +function signature.setup() + local trigger = require('blink.cmp.signature.trigger') + trigger.activate() + local window = require('blink.cmp.signature.window') + + local sources = require('blink.cmp.sources.lib') + + trigger.show_emitter:on(function(event) + local context = event.context + sources.cancel_signature_help() + sources.get_signature_help(context, function(signature_help) + if signature_help ~= nil and trigger.context ~= nil and trigger.context.id == context.id then + trigger.set_active_signature_help(signature_help) + window.open_with_signature_help(context, signature_help) + else + trigger.hide() + end + end) + end) + trigger.hide_emitter:on(function() window.close() end) +end + +return signature diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/signature/list.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/signature/list.lua new file mode 100644 index 0000000..3a07f4a --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/signature/list.lua @@ -0,0 +1 @@ +-- TODO: manage signature help state diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/signature/trigger.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/signature/trigger.lua new file mode 100644 index 0000000..2d874c5 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/signature/trigger.lua @@ -0,0 +1,136 @@ +-- Handles hiding and showing the signature help window. When a user types a trigger character +-- (provided by the sources), we create a new `context`. This can be used downstream to determine +-- if we should make new requests to the sources or not. When a user types a re-trigger character, +-- we update the context's re-trigger counter. +-- TODO: ensure this always calls *after* the completion trigger to avoid increasing latency + +--- @class blink.cmp.SignatureHelpContext +--- @field id number +--- @field bufnr number +--- @field cursor number[] +--- @field line string +--- @field is_retrigger boolean +--- @field active_signature_help lsp.SignatureHelp | nil +--- @field trigger { kind: lsp.SignatureHelpTriggerKind, character?: string } + +--- @class blink.cmp.SignatureTrigger +--- @field current_context_id number +--- @field context? blink.cmp.SignatureHelpContext +--- @field show_emitter blink.cmp.EventEmitter<{ context: blink.cmp.SignatureHelpContext }> +--- @field hide_emitter blink.cmp.EventEmitter<{}> +--- @field buffer_events? blink.cmp.BufferEvents +--- +--- @field activate fun() +--- @field is_trigger_character fun(char: string, is_retrigger?: boolean): boolean +--- @field show_if_on_trigger_character fun() +--- @field show fun(opts?: { trigger_character: string }) +--- @field hide fun() +--- @field set_active_signature_help fun(signature_help: lsp.SignatureHelp) + +local config = require('blink.cmp.config').signature.trigger + +--- @type blink.cmp.SignatureTrigger +--- @diagnostic disable-next-line: missing-fields +local trigger = { + current_context_id = -1, + --- @type blink.cmp.SignatureHelpContext | nil + context = nil, + show_emitter = require('blink.cmp.lib.event_emitter').new('signature_help_show'), + hide_emitter = require('blink.cmp.lib.event_emitter').new('signature_help_hide'), +} + +function trigger.activate() + trigger.buffer_events = require('blink.cmp.lib.buffer_events').new({ + show_in_snippet = true, + has_context = function() return trigger.context ~= nil end, + }) + trigger.buffer_events:listen({ + on_char_added = function() + local cursor_col = vim.api.nvim_win_get_cursor(0)[2] + local char_under_cursor = vim.api.nvim_get_current_line():sub(cursor_col, cursor_col) + + -- ignore if disabled + if not require('blink.cmp.config').enabled() then + return trigger.hide() + -- character forces a trigger according to the sources, refresh the existing context if it exists + elseif trigger.is_trigger_character(char_under_cursor) then + return trigger.show({ trigger_character = char_under_cursor }) + -- character forces a re-trigger according to the sources, show if we have a context + elseif trigger.is_trigger_character(char_under_cursor, true) and trigger.context ~= nil then + return trigger.show() + end + end, + on_cursor_moved = function(event) + local cursor_col = vim.api.nvim_win_get_cursor(0)[2] + local char_under_cursor = vim.api.nvim_get_current_line():sub(cursor_col, cursor_col) + local is_on_trigger = trigger.is_trigger_character(char_under_cursor) + + if config.show_on_insert_on_trigger_character and is_on_trigger and event == 'InsertEnter' then + trigger.show({ trigger_character = char_under_cursor }) + elseif event == 'CursorMoved' and trigger.context ~= nil then + trigger.show() + end + end, + on_insert_leave = function() trigger.hide() end, + }) +end + +function trigger.is_trigger_character(char, is_retrigger) + -- TODO: should the get_mode() be moved to sources or somewhere else? + local mode = require('blink.cmp.completion.trigger.context').get_mode() + + local res = require('blink.cmp.sources.lib').get_signature_help_trigger_characters(mode) + local trigger_characters = is_retrigger and res.retrigger_characters or res.trigger_characters + local is_trigger = vim.tbl_contains(trigger_characters, char) + + local blocked_trigger_characters = is_retrigger and config.blocked_retrigger_characters + or config.blocked_trigger_characters + local is_blocked = vim.tbl_contains(blocked_trigger_characters, char) + + return is_trigger and not is_blocked +end + +function trigger.show_if_on_trigger_character() + if require('blink.cmp.completion.trigger.context').get_mode() ~= 'default' then return end + + local cursor_col = vim.api.nvim_win_get_cursor(0)[2] + local char_under_cursor = vim.api.nvim_get_current_line():sub(cursor_col, cursor_col) + if trigger.is_trigger_character(char_under_cursor) then trigger.show({ trigger_character = char_under_cursor }) end +end + +function trigger.show(opts) + opts = opts or {} + + -- update context + local cursor = vim.api.nvim_win_get_cursor(0) + if trigger.context == nil then trigger.current_context_id = trigger.current_context_id + 1 end + trigger.context = { + id = trigger.current_context_id, + bufnr = vim.api.nvim_get_current_buf(), + cursor = cursor, + line = vim.api.nvim_buf_get_lines(0, cursor[1] - 1, cursor[1], false)[1], + trigger = { + kind = opts.trigger_character and vim.lsp.protocol.CompletionTriggerKind.TriggerCharacter + or vim.lsp.protocol.CompletionTriggerKind.Invoked, + character = opts.trigger_character, + }, + is_retrigger = trigger.context ~= nil, + active_signature_help = trigger.context and trigger.context.active_signature_help or nil, + } + + trigger.show_emitter:emit({ context = trigger.context }) +end + +function trigger.hide() + if not trigger.context then return end + + trigger.context = nil + trigger.hide_emitter:emit() +end + +function trigger.set_active_signature_help(signature_help) + if not trigger.context then return end + trigger.context.active_signature_help = signature_help +end + +return trigger diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/signature/window.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/signature/window.lua new file mode 100644 index 0000000..59a60b0 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/signature/window.lua @@ -0,0 +1,160 @@ +--- @class blink.cmp.SignatureWindow +--- @field win blink.cmp.Window +--- @field context? blink.cmp.SignatureHelpContext +--- +--- @field open_with_signature_help fun(context: blink.cmp.SignatureHelpContext, signature_help?: lsp.SignatureHelp) +--- @field close fun() +--- @field scroll_up fun(amount: number) +--- @field scroll_down fun(amount: number) +--- @field update_position fun() + +local config = require('blink.cmp.config').signature.window +local sources = require('blink.cmp.sources.lib') +local menu = require('blink.cmp.completion.windows.menu') + +local signature = { + win = require('blink.cmp.lib.window').new({ + min_width = config.min_width, + max_width = config.max_width, + max_height = config.max_height, + border = config.border, + winblend = config.winblend, + winhighlight = config.winhighlight, + scrollbar = config.scrollbar, + wrap = true, + filetype = 'blink-cmp-signature', + }), + context = nil, +} + +-- todo: deduplicate this +menu.position_update_emitter:on(function() signature.update_position() end) +vim.api.nvim_create_autocmd({ 'CursorMovedI', 'WinScrolled', 'WinResized' }, { + callback = function() + if signature.context then signature.update_position() end + end, +}) + +--- @param context blink.cmp.SignatureHelpContext +--- @param signature_help lsp.SignatureHelp | nil +function signature.open_with_signature_help(context, signature_help) + signature.context = context + -- check if there are any signatures in signature_help, since + -- convert_signature_help_to_markdown_lines errors with no signatures + if + signature_help == nil + or #signature_help.signatures == 0 + or signature_help.signatures[(signature_help.activeSignature or 0) + 1] == nil + then + signature.win:close() + return + end + + local active_signature = signature_help.signatures[(signature_help.activeSignature or 0) + 1] + + local labels = vim.tbl_map(function(signature) return signature.label end, signature_help.signatures) + + if signature.shown_signature ~= active_signature then + require('blink.cmp.lib.window.docs').render_detail_and_documentation({ + bufnr = signature.win:get_buf(), + detail = labels, + documentation = active_signature.documentation, + max_width = config.max_width, + use_treesitter_highlighting = config.treesitter_highlighting, + }) + end + signature.shown_signature = active_signature + + -- highlight active parameter + local _, active_highlight = vim.lsp.util.convert_signature_help_to_markdown_lines( + signature_help, + vim.bo.filetype, + sources.get_signature_help_trigger_characters().trigger_characters + ) + if active_highlight ~= nil then + -- TODO: nvim 0.11+ returns the start and end line which we should use + local start_region = vim.fn.has('nvim-0.11.0') == 1 and active_highlight[2] or active_highlight[1] + local end_region = vim.fn.has('nvim-0.11.0') == 1 and active_highlight[4] or active_highlight[2] + + vim.api.nvim_buf_add_highlight( + signature.win:get_buf(), + require('blink.cmp.config').appearance.highlight_ns, + 'BlinkCmpSignatureHelpActiveParameter', + 0, + start_region, + end_region + ) + end + + signature.win:open() + signature.update_position() +end + +function signature.close() + if not signature.win:is_open() then return end + signature.win:close() +end + +function signature.scroll_up(amount) + local winnr = signature.win:get_win() + local top_line = math.max(1, vim.fn.line('w0', winnr) - 1) + local desired_line = math.max(1, top_line - amount) + + vim.api.nvim_win_set_cursor(signature.win:get_win(), { desired_line, 0 }) +end + +function signature.scroll_down(amount) + local winnr = signature.win:get_win() + local line_count = vim.api.nvim_buf_line_count(signature.win:get_buf()) + local bottom_line = math.max(1, vim.fn.line('w$', winnr) + 1) + local desired_line = math.min(line_count, bottom_line + amount) + + vim.api.nvim_win_set_cursor(signature.win:get_win(), { desired_line, 0 }) +end + +function signature.update_position() + local win = signature.win + if not win:is_open() then return end + local winnr = win:get_win() + + win:update_size() + + local direction_priority = config.direction_priority + + -- if the menu window is open, we want to place the signature window on the opposite side + local menu_win_config = menu.win:get_win() and vim.api.nvim_win_get_config(menu.win:get_win()) + if menu.win:is_open() then + local cursor_screen_row = vim.fn.winline() + local menu_win_is_up = menu_win_config.row - cursor_screen_row < 0 + direction_priority = menu_win_is_up and { 's' } or { 'n' } + end + + local pos = win:get_vertical_direction_and_height(direction_priority) + + -- couldn't find anywhere to place the window + if not pos then + win:close() + return + end + + -- set height + vim.api.nvim_win_set_height(winnr, pos.height) + local height = win:get_height() + + -- default to the user's preference but attempt to use the other options + if menu_win_config then + assert(menu_win_config.relative == 'win', 'The menu window must be relative to a window') + local cursor_screen_row = vim.fn.winline() + local menu_win_is_up = menu_win_config.row - cursor_screen_row < 0 + vim.api.nvim_win_set_config(winnr, { + relative = menu_win_config.relative, + win = menu_win_config.win, + row = menu_win_is_up and menu_win_config.row + menu.win:get_height() + 1 or menu_win_config.row - height - 1, + col = menu_win_config.col, + }) + else + vim.api.nvim_win_set_config(winnr, { relative = 'cursor', row = pos.direction == 's' and 1 or -height, col = 0 }) + end +end + +return signature diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/buffer.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/buffer.lua new file mode 100644 index 0000000..bb679f2 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/buffer.lua @@ -0,0 +1,118 @@ +-- todo: nvim-cmp only updates the lines that got changed which is better +-- but this is *speeeeeed* and simple. should add the better way +-- but ensure it doesn't add too much complexity + +local uv = vim.uv + +--- @param bufnr integer +--- @return string +local function get_buf_text(bufnr) + local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) + + if bufnr ~= vim.api.nvim_get_current_buf() then return table.concat(lines, '\n') end + + -- exclude word under the cursor for the current buffer + local line_number = vim.api.nvim_win_get_cursor(0)[1] + local column = vim.api.nvim_win_get_cursor(0)[2] + local line = lines[line_number] + local start_col = column + while start_col > 1 do + local char = line:sub(start_col, start_col) + if char:match('[%w_\\-]') == nil then break end + start_col = start_col - 1 + end + local end_col = column + while end_col < #line do + local char = line:sub(end_col + 1, end_col + 1) + if char:match('[%w_\\-]') == nil then break end + end_col = end_col + 1 + end + lines[line_number] = line:sub(1, start_col) .. ' ' .. line:sub(end_col + 1) + + return table.concat(lines, '\n') +end + +local function words_to_items(words) + local items = {} + for _, word in ipairs(words) do + table.insert(items, { + label = word, + kind = require('blink.cmp.types').CompletionItemKind.Text, + insertTextFormat = vim.lsp.protocol.InsertTextFormat.PlainText, + insertText = word, + }) + end + return items +end + +--- @param buf_text string +--- @param callback fun(items: blink.cmp.CompletionItem[]) +local function run_sync(buf_text, callback) callback(words_to_items(require('blink.cmp.fuzzy').get_words(buf_text))) end + +local function run_async(buf_text, callback) + local worker = uv.new_work( + -- must use ffi directly since the normal one requires the config which isnt present + function(items, cpath) + package.cpath = cpath + return table.concat(require('blink.cmp.fuzzy.rust').get_words(items), '\n') + end, + function(words) + local items = words_to_items(vim.split(words, '\n')) + vim.schedule(function() callback(items) end) + end + ) + worker:queue(buf_text, package.cpath) +end + +--- @class blink.cmp.BufferOpts +--- @field get_bufnrs fun(): integer[] + +--- Public API + +local buffer = {} + +function buffer.new(opts) + --- @cast opts blink.cmp.BufferOpts + + local self = setmetatable({}, { __index = buffer }) + self.get_bufnrs = opts.get_bufnrs + or function() + return vim + .iter(vim.api.nvim_list_wins()) + :map(function(win) return vim.api.nvim_win_get_buf(win) end) + :filter(function(buf) return vim.bo[buf].buftype ~= 'nofile' end) + :totable() + end + return self +end + +function buffer:get_completions(_, callback) + local transformed_callback = function(items) + callback({ is_incomplete_forward = false, is_incomplete_backward = false, items = items }) + end + + vim.schedule(function() + local bufnrs = require('blink.cmp.lib.utils').deduplicate(self.get_bufnrs()) + local buf_texts = {} + for _, buf in ipairs(bufnrs) do + table.insert(buf_texts, get_buf_text(buf)) + end + local buf_text = table.concat(buf_texts, '\n') + + -- should take less than 2ms + if #buf_text < 20000 then + run_sync(buf_text, transformed_callback) + -- should take less than 10ms + elseif #buf_text < 500000 then + run_async(buf_text, transformed_callback) + -- too big so ignore + else + transformed_callback({}) + end + end) + + -- TODO: cancel run_async + return function() end +end + +return buffer diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/cmdline/constants.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/cmdline/constants.lua new file mode 100644 index 0000000..fd2ee85 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/cmdline/constants.lua @@ -0,0 +1,40 @@ +return { + help_commands = { + 'help', + 'hel', + 'he', + 'h', + }, + file_commands = { + 'edit', + 'e', + 'read', + 'r', + 'write', + 'w', + 'saveas', + 'sav', + 'split', + 'sp', + 'vsplit', + 'vs', + 'tabedit', + 'tabe', + 'badd', + 'bad', + 'next', + 'n', + 'previous', + 'prev', + 'args', + 'source', + 'so', + 'find', + 'fin', + 'diffsplit', + 'diffs', + 'diffpatch', + 'diffp', + 'make', + }, +} diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/cmdline/help.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/cmdline/help.lua new file mode 100644 index 0000000..bae4988 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/cmdline/help.lua @@ -0,0 +1,53 @@ +local async = require('blink.cmp.lib.async') + +local help = {} + +--- Processes a help file and returns a list of tags asynchronously +--- @param file string +--- @return blink.cmp.Task +--- TODO: rewrite using async lib, shared as a library in lib/fs.lua +local function read_tags_from_file(file) + return async.task.new(function(resolve) + vim.uv.fs_open(file, 'r', 438, function(err, fd) + if err or fd == nil then return resolve({}) end + + -- Read file content + vim.uv.fs_fstat(fd, function(stat_err, stat) + if stat_err or stat == nil then + vim.uv.fs_close(fd) + return resolve({}) + end + + vim.uv.fs_read(fd, stat.size, 0, function(read_err, data) + vim.uv.fs_close(fd) + + if read_err or data == nil then return resolve({}) end + + -- Process the file content + local tags = {} + for line in data:gmatch('[^\r\n]+') do + local tag = line:match('^([^\t]+)') + if tag then table.insert(tags, tag) end + end + + resolve(tags) + end) + end) + end) + end) +end + +--- @param arg_prefix string +function help.get_completions(arg_prefix) + local help_files = vim.api.nvim_get_runtime_file('doc/tags', true) + + return async.task + .await_all(vim.tbl_map(read_tags_from_file, help_files)) + :map(function(tags_arrs) return require('blink.cmp.lib.utils').flatten(tags_arrs) end) + :map(function(tags) + -- TODO: remove after adding support for fuzzy matching on custom range + return vim.tbl_filter(function(tag) return vim.startswith(tag, arg_prefix) end, tags) + end) +end + +return help diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/cmdline/init.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/cmdline/init.lua new file mode 100644 index 0000000..5ff5e11 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/cmdline/init.lua @@ -0,0 +1,107 @@ +-- Credit goes to @hrsh7th for the code that this was based on +-- https://github.com/hrsh7th/cmp-cmdline +-- License: MIT + +local async = require('blink.cmp.lib.async') +local constants = require('blink.cmp.sources.cmdline.constants') + +--- @class blink.cmp.Source +local cmdline = {} + +function cmdline.new() + local self = setmetatable({}, { __index = cmdline }) + self.before_line = '' + self.offset = -1 + self.ctype = '' + self.items = {} + return self +end + +function cmdline:get_trigger_characters() return { ' ', '.', '#', '-', '=', '/', ':' } end + +function cmdline:get_completions(context, callback) + local arguments = vim.split(context.line, ' ', { plain = true }) + local arg_number = #vim.split(context.line:sub(1, context.cursor[2] + 1), ' ', { plain = true }) + local text_before_argument = table.concat(require('blink.cmp.lib.utils').slice(arguments, 1, arg_number - 1), ' ') + .. (arg_number > 1 and ' ' or '') + + local current_arg = arguments[arg_number] + local keyword_config = require('blink.cmp.config').completion.keyword + local keyword = context.get_bounds(keyword_config.range) + local current_arg_prefix = current_arg:sub(1, keyword.start_col - #text_before_argument - 1) + + local task = async.task + .empty() + :map(function() + -- Special case for help where we read all the tags ourselves + if vim.tbl_contains(constants.help_commands, arguments[1] or '') then + return require('blink.cmp.sources.cmdline.help').get_completions(current_arg_prefix) + end + + local completions = {} + local completion_type = vim.fn.getcmdcompltype() + -- Handle custom completions explicitly, since otherwise they won't work in input() mode (getcmdtype() == '@') + if vim.startswith(completion_type, 'custom,') or vim.startswith(completion_type, 'customlist,') then + local fun = completion_type:gsub('custom,', ''):gsub('customlist,', '') + completions = vim.fn.call(fun, { current_arg_prefix, vim.fn.getcmdline(), vim.fn.getcmdpos() }) + -- `custom,` type returns a string, delimited by newlines + if type(completions) == 'string' then completions = vim.split(completions, '\n') end + else + local query = (text_before_argument .. current_arg_prefix):gsub([[\\]], [[\\\\]]) + completions = vim.fn.getcompletion(query, 'cmdline') + end + + -- Special case for files, escape special characters + if vim.tbl_contains(constants.file_commands, arguments[1] or '') then + completions = vim.tbl_map(function(completion) return vim.fn.fnameescape(completion) end, completions) + end + + return completions + end) + :map(function(completions) + local items = {} + for _, completion in ipairs(completions) do + local has_prefix = string.find(completion, current_arg_prefix, 1, true) == 1 + + -- remove prefix from the filter text + local filter_text = completion + if has_prefix then filter_text = completion:sub(#current_arg_prefix + 1) end + + -- for lua, use the filter text as the label since it doesn't include the prefix + local label = arguments[1] == 'lua' and filter_text or completion + + -- add prefix to the newText + local new_text = completion + if not has_prefix then new_text = current_arg_prefix .. completion end + + table.insert(items, { + label = label, + filterText = filter_text, + -- move items starting with special characters to the end of the list + sortText = label:lower():gsub('^([!-@\\[-`])', '~%1'), + textEdit = { + newText = new_text, + range = { + start = { line = 0, character = #text_before_argument }, + ['end'] = { line = 0, character = #text_before_argument + #current_arg }, + }, + }, + kind = require('blink.cmp.types').CompletionItemKind.Property, + }) + end + + callback({ + is_incomplete_backward = true, + is_incomplete_forward = false, + items = items, + }) + end) + :catch(function(err) + vim.notify('Error while fetching completions: ' .. err, vim.log.levels.ERROR, { title = 'blink.cmp' }) + callback({ is_incomplete_backward = false, is_incomplete_forward = false, items = {} }) + end) + + return function() task:cancel() end +end + +return cmdline diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lib/init.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lib/init.lua new file mode 100644 index 0000000..9b56cf5 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lib/init.lua @@ -0,0 +1,304 @@ +local async = require('blink.cmp.lib.async') +local config = require('blink.cmp.config') + +--- @class blink.cmp.Sources +--- @field completions_queue blink.cmp.SourcesQueue | nil +--- @field current_signature_help blink.cmp.Task | nil +--- @field sources_registered boolean +--- @field providers table<string, blink.cmp.SourceProvider> +--- @field completions_emitter blink.cmp.EventEmitter<blink.cmp.SourceCompletionsEvent> +--- +--- @field get_all_providers fun(): blink.cmp.SourceProvider[] +--- @field get_enabled_provider_ids fun(mode: blink.cmp.Mode): string[] +--- @field get_enabled_providers fun(mode: blink.cmp.Mode): table<string, blink.cmp.SourceProvider> +--- @field get_provider_by_id fun(id: string): blink.cmp.SourceProvider +--- @field get_trigger_characters fun(mode: blink.cmp.Mode): string[] +--- +--- @field emit_completions fun(context: blink.cmp.Context, responses: table<string, blink.cmp.CompletionResponse>) +--- @field request_completions fun(context: blink.cmp.Context) +--- @field cancel_completions fun() +--- @field apply_max_items_for_completions fun(context: blink.cmp.Context, items: blink.cmp.CompletionItem[]): blink.cmp.CompletionItem[] +--- @field listen_on_completions fun(callback: fun(context: blink.cmp.Context, items: blink.cmp.CompletionItem[])) +--- @field resolve fun(context: blink.cmp.Context, item: blink.cmp.CompletionItem): blink.cmp.Task +--- @field execute fun(context: blink.cmp.Context, item: blink.cmp.CompletionItem): blink.cmp.Task +--- +--- @field get_signature_help_trigger_characters fun(mode: blink.cmp.Mode): { trigger_characters: string[], retrigger_characters: string[] } +--- @field get_signature_help fun(context: blink.cmp.SignatureHelpContext, callback: fun(signature_help: lsp.SignatureHelp | nil)) +--- @field cancel_signature_help fun() +--- +--- @field reload fun(provider?: string) +--- @field get_lsp_capabilities fun(override?: lsp.ClientCapabilities, include_nvim_defaults?: boolean): lsp.ClientCapabilities + +--- @class blink.cmp.SourceCompletionsEvent +--- @field context blink.cmp.Context +--- @field items table<string, blink.cmp.CompletionItem[]> + +--- @type blink.cmp.Sources +--- @diagnostic disable-next-line: missing-fields +local sources = { + completions_queue = nil, + providers = {}, + completions_emitter = require('blink.cmp.lib.event_emitter').new('source_completions', 'BlinkCmpSourceCompletions'), +} + +function sources.get_all_providers() + local providers = {} + for provider_id, _ in pairs(config.sources.providers) do + providers[provider_id] = sources.get_provider_by_id(provider_id) + end + return providers +end + +function sources.get_enabled_provider_ids(mode) + local enabled_providers = mode ~= 'default' and config.sources[mode] + or config.sources.per_filetype[vim.bo.filetype] + or config.sources.default + if type(enabled_providers) == 'function' then return enabled_providers() end + --- @cast enabled_providers string[] + return enabled_providers +end + +function sources.get_enabled_providers(mode) + local mode_providers = sources.get_enabled_provider_ids(mode) + + --- @type table<string, blink.cmp.SourceProvider> + local providers = {} + for _, provider_id in ipairs(mode_providers) do + local provider = sources.get_provider_by_id(provider_id) + if provider:enabled() then providers[provider_id] = sources.get_provider_by_id(provider_id) end + end + return providers +end + +function sources.get_provider_by_id(provider_id) + -- TODO: remove in v1.0 + if not sources.providers[provider_id] and provider_id == 'luasnip' then + error( + "Luasnip has been moved to the `snippets` source, alongside a new preset system (`snippets.preset = 'luasnip'`). See the documentation for more information." + ) + end + + assert( + sources.providers[provider_id] ~= nil or config.sources.providers[provider_id] ~= nil, + 'Requested provider "' + .. provider_id + .. '" has not been configured. Available providers: ' + .. vim.fn.join(vim.tbl_keys(sources.providers), ', ') + ) + + -- initialize the provider if it hasn't been initialized yet + if not sources.providers[provider_id] then + local provider_config = config.sources.providers[provider_id] + sources.providers[provider_id] = require('blink.cmp.sources.lib.provider').new(provider_id, provider_config) + end + + return sources.providers[provider_id] +end + +--- Completion --- + +function sources.get_trigger_characters(mode) + local providers = sources.get_enabled_providers(mode) + local trigger_characters = {} + for _, provider in pairs(providers) do + vim.list_extend(trigger_characters, provider:get_trigger_characters()) + end + return trigger_characters +end + +function sources.emit_completions(context, _items_by_provider) + local items_by_provider = {} + for id, items in pairs(_items_by_provider) do + if sources.providers[id]:should_show_items(context, items) then items_by_provider[id] = items end + end + sources.completions_emitter:emit({ context = context, items = items_by_provider }) +end + +function sources.request_completions(context) + -- create a new context if the id changed or if we haven't created one yet + if sources.completions_queue == nil or context.id ~= sources.completions_queue.id then + if sources.completions_queue ~= nil then sources.completions_queue:destroy() end + sources.completions_queue = + require('blink.cmp.sources.lib.queue').new(context, sources.get_all_providers(), sources.emit_completions) + -- send cached completions if they exist to immediately trigger updates + elseif sources.completions_queue:get_cached_completions() ~= nil then + sources.emit_completions( + context, + --- @diagnostic disable-next-line: param-type-mismatch + sources.completions_queue:get_cached_completions() + ) + end + + sources.completions_queue:get_completions(context) +end + +function sources.cancel_completions() + if sources.completions_queue ~= nil then + sources.completions_queue:destroy() + sources.completions_queue = nil + end +end + +--- Limits the number of items per source as configured +function sources.apply_max_items_for_completions(context, items) + -- get the configured max items for each source + local total_items_for_sources = {} + local max_items_for_sources = {} + for id, source in pairs(sources.providers) do + max_items_for_sources[id] = source.config.max_items(context, items) + total_items_for_sources[id] = 0 + end + + -- no max items configured, return as-is + if #vim.tbl_keys(max_items_for_sources) == 0 then return items end + + -- apply max items + local filtered_items = {} + for _, item in ipairs(items) do + local max_items = max_items_for_sources[item.source_id] + total_items_for_sources[item.source_id] = total_items_for_sources[item.source_id] + 1 + if max_items == nil or total_items_for_sources[item.source_id] <= max_items then + table.insert(filtered_items, item) + end + end + return filtered_items +end + +--- Resolve --- + +function sources.resolve(context, item) + --- @type blink.cmp.SourceProvider? + local item_source = nil + for _, source in pairs(sources.providers) do + if source.id == item.source_id then + item_source = source + break + end + end + if item_source == nil then + return async.task.new(function(resolve) resolve(item) end) + end + + return item_source + :resolve(context, item) + :catch(function(err) vim.print('failed to resolve item with error: ' .. err) end) +end + +--- Execute --- + +function sources.execute(context, item) + local item_source = nil + for _, source in pairs(sources.providers) do + if source.id == item.source_id then + item_source = source + break + end + end + if item_source == nil then + return async.task.new(function(resolve) resolve() end) + end + + return item_source + :execute(context, item) + :catch(function(err) vim.print('failed to execute item with error: ' .. err) end) +end + +--- Signature help --- + +function sources.get_signature_help_trigger_characters(mode) + local trigger_characters = {} + local retrigger_characters = {} + + -- todo: should this be all sources? or should it follow fallbacks? + for _, source in pairs(sources.get_enabled_providers(mode)) do + local res = source:get_signature_help_trigger_characters() + vim.list_extend(trigger_characters, res.trigger_characters) + vim.list_extend(retrigger_characters, res.retrigger_characters) + end + return { trigger_characters = trigger_characters, retrigger_characters = retrigger_characters } +end + +function sources.get_signature_help(context, callback) + local tasks = {} + for _, source in pairs(sources.providers) do + table.insert(tasks, source:get_signature_help(context)) + end + + sources.current_signature_help = async.task.await_all(tasks):map(function(signature_helps) + signature_helps = vim.tbl_filter(function(signature_help) return signature_help ~= nil end, signature_helps) + callback(signature_helps[1]) + end) +end + +function sources.cancel_signature_help() + if sources.current_signature_help ~= nil then + sources.current_signature_help:cancel() + sources.current_signature_help = nil + end +end + +--- Misc --- + +--- For external integrations to force reloading the source +function sources.reload(provider) + -- Reload specific provider + if provider ~= nil then + assert(type(provider) == 'string', 'Expected string for provider') + assert( + sources.providers[provider] ~= nil or config.sources.providers[provider] ~= nil, + 'Source ' .. provider .. ' does not exist' + ) + if sources.providers[provider] ~= nil then sources.providers[provider]:reload() end + return + end + + -- Reload all providers + for _, source in pairs(sources.providers) do + source:reload() + end +end + +function sources.get_lsp_capabilities(override, include_nvim_defaults) + return vim.tbl_deep_extend('force', include_nvim_defaults and vim.lsp.protocol.make_client_capabilities() or {}, { + textDocument = { + completion = { + completionItem = { + snippetSupport = true, + commitCharactersSupport = false, -- todo: + documentationFormat = { 'markdown', 'plaintext' }, + deprecatedSupport = true, + preselectSupport = false, -- todo: + tagSupport = { valueSet = { 1 } }, -- deprecated + insertReplaceSupport = true, -- todo: + resolveSupport = { + properties = { + 'documentation', + 'detail', + 'additionalTextEdits', + -- todo: support more properties? should test if it improves latency + }, + }, + insertTextModeSupport = { + -- todo: support adjustIndentation + valueSet = { 1 }, -- asIs + }, + labelDetailsSupport = true, + }, + completionList = { + itemDefaults = { + 'commitCharacters', + 'editRange', + 'insertTextFormat', + 'insertTextMode', + 'data', + }, + }, + + contextSupport = true, + insertTextMode = 1, -- asIs + }, + }, + }, override or {}) +end + +return sources diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lib/provider/config.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lib/provider/config.lua new file mode 100644 index 0000000..cda6a52 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lib/provider/config.lua @@ -0,0 +1,46 @@ +--- @class blink.cmp.SourceProviderConfigWrapper +--- @field new fun(config: blink.cmp.SourceProviderConfig): blink.cmp.SourceProviderConfigWrapper +--- +--- @field name string +--- @field module string +--- @field enabled fun(): boolean +--- @field async fun(ctx: blink.cmp.Context): boolean +--- @field timeout_ms fun(ctx: blink.cmp.Context): number +--- @field transform_items fun(ctx: blink.cmp.Context, items: blink.cmp.CompletionItem[]): blink.cmp.CompletionItem[] +--- @field should_show_items fun(ctx: blink.cmp.Context, items: blink.cmp.CompletionItem[]): boolean +--- @field max_items? fun(ctx: blink.cmp.Context, items: blink.cmp.CompletionItem[]): number +--- @field min_keyword_length fun(ctx: blink.cmp.Context): number +--- @field fallbacks fun(ctx: blink.cmp.Context): string[] +--- @field score_offset fun(ctx: blink.cmp.Context): number + +--- @class blink.cmp.SourceProviderConfigWrapper +--- @diagnostic disable-next-line: missing-fields +local wrapper = {} + +function wrapper.new(config) + local function call_or_get(fn_or_val, default) + if fn_or_val == nil then + return function() return default end + end + return function(...) + if type(fn_or_val) == 'function' then return fn_or_val(...) end + return fn_or_val + end + end + + local self = setmetatable({}, { __index = config }) + self.name = config.name + self.module = config.module + self.enabled = call_or_get(config.enabled, true) + self.async = call_or_get(config.async, false) + self.timeout_ms = call_or_get(config.timeout_ms, 2000) + self.transform_items = config.transform_items or function(_, items) return items end + self.should_show_items = call_or_get(config.should_show_items, true) + self.max_items = call_or_get(config.max_items, nil) + self.min_keyword_length = call_or_get(config.min_keyword_length, 0) + self.fallbacks = call_or_get(config.fallbacks, {}) + self.score_offset = call_or_get(config.score_offset, 0) + return self +end + +return wrapper diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lib/provider/init.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lib/provider/init.lua new file mode 100644 index 0000000..0494dcc --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lib/provider/init.lua @@ -0,0 +1,179 @@ +--- Wraps the sources to respect the configuration options and provide a unified interface +--- @class blink.cmp.SourceProvider +--- @field id string +--- @field name string +--- @field config blink.cmp.SourceProviderConfigWrapper +--- @field module blink.cmp.Source +--- @field list blink.cmp.SourceProviderList | nil +--- @field resolve_tasks table<blink.cmp.CompletionItem, blink.cmp.Task> +--- +--- @field new fun(id: string, config: blink.cmp.SourceProviderConfig): blink.cmp.SourceProvider +--- @field enabled fun(self: blink.cmp.SourceProvider): boolean +--- @field get_trigger_characters fun(self: blink.cmp.SourceProvider): string[] +--- @field get_completions fun(self: blink.cmp.SourceProvider, context: blink.cmp.Context, on_items: fun(items: blink.cmp.CompletionItem[], is_cached: boolean)) +--- @field should_show_items fun(self: blink.cmp.SourceProvider, context: blink.cmp.Context, items: blink.cmp.CompletionItem[]): boolean +--- @field transform_items fun(self: blink.cmp.SourceProvider, context: blink.cmp.Context, items: blink.cmp.CompletionItem[]): blink.cmp.CompletionItem[] +--- @field resolve fun(self: blink.cmp.SourceProvider, context: blink.cmp.Context, item: blink.cmp.CompletionItem): blink.cmp.Task +--- @field execute fun(self: blink.cmp.SourceProvider, context: blink.cmp.Context, item: blink.cmp.CompletionItem, callback: fun()): blink.cmp.Task +--- @field get_signature_help_trigger_characters fun(self: blink.cmp.SourceProvider): { trigger_characters: string[], retrigger_characters: string[] } +--- @field get_signature_help fun(self: blink.cmp.SourceProvider, context: blink.cmp.SignatureHelpContext): blink.cmp.Task +--- @field reload (fun(self: blink.cmp.SourceProvider): nil) | nil + +--- @type blink.cmp.SourceProvider +--- @diagnostic disable-next-line: missing-fields +local source = {} + +local async = require('blink.cmp.lib.async') + +function source.new(id, config) + assert(type(config.name) == 'string', 'Each source in config.sources.providers must have a "name" of type string') + assert(type(config.module) == 'string', 'Each source in config.sources.providers must have a "module" of type string') + + local self = setmetatable({}, { __index = source }) + self.id = id + self.name = config.name + self.module = require('blink.cmp.sources.lib.provider.override').new( + require(config.module).new(config.opts or {}, config), + config.override + ) + self.config = require('blink.cmp.sources.lib.provider.config').new(config) + self.list = nil + self.resolve_tasks = {} + + return self +end + +function source:enabled() + -- user defined + if not self.config.enabled() then return false end + + -- source defined + if self.module.enabled == nil then return true end + return self.module:enabled() +end + +--- Completion --- + +function source:get_trigger_characters() + if self.module.get_trigger_characters == nil then return {} end + return self.module:get_trigger_characters() +end + +function source:get_completions(context, on_items) + -- return the previous successful completions if the context is the same + -- and the data doesn't need to be updated + -- or if the list is async, since we don't want to cause a flash of no items + if self.list ~= nil and self.list:is_valid_for_context(context) then + self.list:set_on_items(on_items) + self.list:emit(true) + return + end + + -- the source indicates we should refetch when this character is typed + local trigger_character = context.trigger.character + and vim.tbl_contains(self:get_trigger_characters(), context.trigger.character) + + -- The TriggerForIncompleteCompletions kind is handled by the source provider itself + local source_context = require('blink.cmp.lib.utils').shallow_copy(context) + source_context.trigger = trigger_character + and { kind = vim.lsp.protocol.CompletionTriggerKind.TriggerCharacter, character = context.trigger.character } + or { kind = vim.lsp.protocol.CompletionTriggerKind.Invoked } + + local async_initial_items = self.list ~= nil and self.list.context.id == context.id and self.list.items or {} + if self.list ~= nil then self.list:destroy() end + + self.list = require('blink.cmp.sources.lib.provider.list').new( + self, + context, + on_items, + -- HACK: if the source is async, we're not reusing the previous list and the response was marked as incomplete, + -- the user will see a flash of no items from the provider, since the list emits immediately. So we hack around + -- this for now + { async_initial_items = async_initial_items } + ) +end + +function source:should_show_items(context, items) + -- if keyword length is configured, check if the context is long enough + local provider_min_keyword_length = self.config.min_keyword_length(context) + + -- for manual trigger, we ignore the min_keyword_length set globally, but still respect per-provider + local global_min_keyword_length = 0 + if context.trigger.initial_kind ~= 'manual' then + local global_min_keyword_length_func_or_num = require('blink.cmp.config').sources.min_keyword_length + if type(global_min_keyword_length_func_or_num) == 'function' then + global_min_keyword_length = global_min_keyword_length_func_or_num(context) + else + global_min_keyword_length = global_min_keyword_length_func_or_num + end + end + + local min_keyword_length = math.max(provider_min_keyword_length, global_min_keyword_length) + local current_keyword_length = context.bounds.length + if current_keyword_length < min_keyword_length then return false end + + if self.config.should_show_items == nil then return true end + return self.config.should_show_items(context, items) +end + +function source:transform_items(context, items) + if self.config.transform_items ~= nil then items = self.config.transform_items(context, items) end + items = require('blink.cmp.config').sources.transform_items(context, items) + return items +end + +--- Resolve --- + +function source:resolve(context, item) + local tasks = self.resolve_tasks + if tasks[item] == nil or tasks[item].status == async.STATUS.CANCELLED then + tasks[item] = async.task.new(function(resolve) + if self.module.resolve == nil then return resolve(item) end + + return self.module:resolve(item, function(resolved_item) + -- HACK: it's out of spec to update keys not in resolveSupport.properties but some LSPs do it anyway + local merged_item = vim.tbl_deep_extend('force', item, resolved_item or {}) + local transformed_item = self:transform_items(context, { merged_item })[1] or merged_item + vim.schedule(function() resolve(transformed_item) end) + end) + end) + end + return tasks[item] +end + +--- Execute --- + +function source:execute(context, item) + if self.module.execute == nil then + return async.task.new(function(resolve) resolve() end) + end + return async.task.new(function(resolve) self.module:execute(context, item, resolve) end) +end + +--- Signature help --- + +function source:get_signature_help_trigger_characters() + if self.module.get_signature_help_trigger_characters == nil then + return { trigger_characters = {}, retrigger_characters = {} } + end + return self.module:get_signature_help_trigger_characters() +end + +function source:get_signature_help(context) + return async.task.new(function(resolve) + if self.module.get_signature_help == nil then return resolve(nil) end + return self.module:get_signature_help(context, function(signature_help) + vim.schedule(function() resolve(signature_help) end) + end) + end) +end + +--- Misc --- + +--- For external integrations to force reloading the source +function source:reload() + if self.module.reload == nil then return end + self.module:reload() +end + +return source diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lib/provider/list.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lib/provider/list.lua new file mode 100644 index 0000000..17d4bdf --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lib/provider/list.lua @@ -0,0 +1,128 @@ +--- @class blink.cmp.SourceProviderList +--- @field provider blink.cmp.SourceProvider +--- @field context blink.cmp.Context +--- @field items blink.cmp.CompletionItem[] +--- @field on_items fun(items: blink.cmp.CompletionItem[], is_cached: boolean) +--- @field has_completed boolean +--- @field is_incomplete_backward boolean +--- @field is_incomplete_forward boolean +--- @field cancel_completions? fun(): nil +--- +--- @field new fun(provider: blink.cmp.SourceProvider,context: blink.cmp.Context, on_items: fun(items: blink.cmp.CompletionItem[], is_cached: boolean), opts: blink.cmp.SourceProviderListOpts): blink.cmp.SourceProviderList +--- @field append fun(self: blink.cmp.SourceProviderList, response: blink.cmp.CompletionResponse) +--- @field emit fun(self: blink.cmp.SourceProviderList, is_cached?: boolean) +--- @field destroy fun(self: blink.cmp.SourceProviderList): nil +--- @field set_on_items fun(self: blink.cmp.SourceProviderList, on_items: fun(items: blink.cmp.CompletionItem[], is_cached: boolean)) +--- @field is_valid_for_context fun(self: blink.cmp.SourceProviderList, context: blink.cmp.Context): boolean +--- +--- @class blink.cmp.SourceProviderListOpts +--- @field async_initial_items blink.cmp.CompletionItem[] + +--- @type blink.cmp.SourceProviderList +--- @diagnostic disable-next-line: missing-fields +local list = {} + +function list.new(provider, context, on_items, opts) + --- @type blink.cmp.SourceProviderList + local self = setmetatable({ + provider = provider, + context = context, + items = opts.async_initial_items, + on_items = on_items, + + has_completed = false, + is_incomplete_backward = true, + is_incomplete_forward = true, + }, { __index = list }) + + -- Immediately fetch completions + local default_response = { + is_incomplete_forward = true, + is_incomplete_backward = true, + items = {}, + } + if self.provider.module.get_completions == nil then + self:append(default_response) + else + self.cancel_completions = self.provider.module:get_completions( + self.context, + function(response) self:append(response or default_response) end + ) + end + + -- if async, immediately send the default response/initial items + local is_async = self.provider.config.async(self.context) + if is_async and not self.has_completed then self:emit() end + + -- if not async and timeout is set, send the default response after the timeout + local timeout_ms = self.provider.config.timeout_ms(self.context) + if not is_async and timeout_ms > 0 then + vim.defer_fn(function() + if not self.has_completed then self:append(default_response) end + end, timeout_ms) + end + + return self +end + +function list:append(response) + if self.has_completed and #response.items == 0 then return end + + if not self.has_completed then + self.has_completed = true + self.is_incomplete_backward = response.is_incomplete_backward + self.is_incomplete_forward = response.is_incomplete_forward + self.items = {} + end + + -- add metadata and default kind + local source_score_offset = self.provider.config.score_offset(self.context) or 0 + for _, item in ipairs(response.items) do + item.score_offset = (item.score_offset or 0) + source_score_offset + item.cursor_column = self.context.cursor[2] + item.source_id = self.provider.id + item.source_name = self.provider.name + item.kind = item.kind or require('blink.cmp.types').CompletionItemKind.Property + end + + -- combine with existing items + local new_items = {} + vim.list_extend(new_items, self.items) + vim.list_extend(new_items, response.items) + self.items = new_items + + -- run provider-local and global transform_items functions + self.items = self.provider:transform_items(self.context, self.items) + + self:emit() +end + +function list:emit(is_cached) + if is_cached == nil then is_cached = false end + self.on_items(self.items, is_cached) +end + +function list:destroy() + if self.cancel_completions ~= nil then self.cancel_completions() end + self.on_items = function() end +end + +function list:set_on_items(on_items) self.on_items = on_items end + +function list:is_valid_for_context(new_context) + if self.context.id ~= new_context.id then return false end + + -- get the text for the current and queued context + local old_context_query = self.context.line:sub(self.context.bounds.start_col, self.context.cursor[2]) + local new_context_query = new_context.line:sub(new_context.bounds.start_col, new_context.cursor[2]) + + -- check if the texts are overlapping + local is_before = vim.startswith(old_context_query, new_context_query) + local is_after = vim.startswith(new_context_query, old_context_query) + + return (is_before and not self.is_incomplete_backward) + or (is_after and not self.is_incomplete_forward) + or (is_after == is_before and not (self.is_incomplete_backward or self.is_incomplete_forward)) +end + +return list diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lib/provider/override.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lib/provider/override.lua new file mode 100644 index 0000000..96e8edd --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lib/provider/override.lua @@ -0,0 +1,17 @@ +--- @class blink.cmp.Override : blink.cmp.Source +--- @field new fun(module: blink.cmp.Source, override_config: blink.cmp.SourceOverride): blink.cmp.Override + +local override = {} + +function override.new(module, override_config) + override_config = override_config or {} + + return setmetatable({}, { + __index = function(_, key) + if override_config[key] ~= nil then return function(_, ...) return override_config[key](module, ...) end end + return module[key] + end, + }) +end + +return override diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lib/queue.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lib/queue.lua new file mode 100644 index 0000000..3947902 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lib/queue.lua @@ -0,0 +1,68 @@ +local async = require('blink.cmp.lib.async') + +--- @class blink.cmp.SourcesQueue +--- @field id number +--- @field providers table<string, blink.cmp.SourceProvider> +--- @field request blink.cmp.Task | nil +--- @field queued_request_context blink.cmp.Context | nil +--- @field cached_items_by_provider table<string, blink.cmp.CompletionResponse> | nil +--- @field on_completions_callback fun(context: blink.cmp.Context, responses: table<string, blink.cmp.CompletionResponse>) +--- +--- @field new fun(context: blink.cmp.Context, providers: table<string, blink.cmp.SourceProvider>, on_completions_callback: fun(context: blink.cmp.Context, responses: table<string, blink.cmp.CompletionResponse>)): blink.cmp.SourcesQueue +--- @field get_cached_completions fun(self: blink.cmp.SourcesQueue): table<string, blink.cmp.CompletionResponse> | nil +--- @field get_completions fun(self: blink.cmp.SourcesQueue, context: blink.cmp.Context) +--- @field destroy fun(self: blink.cmp.SourcesQueue) + +--- @type blink.cmp.SourcesQueue +--- @diagnostic disable-next-line: missing-fields +local queue = {} + +function queue.new(context, providers, on_completions_callback) + local self = setmetatable({}, { __index = queue }) + self.id = context.id + self.providers = providers + + self.request = nil + self.queued_request_context = nil + self.on_completions_callback = on_completions_callback + + return self +end + +function queue:get_cached_completions() return self.cached_items_by_provider end + +function queue:get_completions(context) + assert(context.id == self.id, 'Requested completions on a sources context with a different context ID') + + if self.request ~= nil then + if self.request.status == async.STATUS.RUNNING then + self.queued_request_context = context + return + else + self.request:cancel() + end + end + + -- Create a task to get the completions, send responses upstream + -- and run the queued request, if it exists + local tree = require('blink.cmp.sources.lib.tree').new(context, vim.tbl_values(self.providers)) + self.request = tree:get_completions(context, function(items_by_provider) + self.cached_items_by_provider = items_by_provider + self.on_completions_callback(context, items_by_provider) + + -- run the queued request, if it exists + local queued_context = self.queued_request_context + if queued_context ~= nil then + self.queued_request_context = nil + self.request:cancel() + self:get_completions(queued_context) + end + end) +end + +function queue:destroy() + self.on_completions_callback = function() end + if self.request ~= nil then self.request:cancel() end +end + +return queue diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lib/tree.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lib/tree.lua new file mode 100644 index 0000000..c89d04d --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lib/tree.lua @@ -0,0 +1,168 @@ +--- @class blink.cmp.SourceTreeNode +--- @field id string +--- @field source blink.cmp.SourceProvider +--- @field dependencies blink.cmp.SourceTreeNode[] +--- @field dependents blink.cmp.SourceTreeNode[] + +--- @class blink.cmp.SourceTree +--- @field nodes blink.cmp.SourceTreeNode[] +--- @field new fun(context: blink.cmp.Context, all_sources: blink.cmp.SourceProvider[]): blink.cmp.SourceTree +--- @field get_completions fun(self: blink.cmp.SourceTree, context: blink.cmp.Context, on_items_by_provider: fun(items_by_provider: table<string, blink.cmp.CompletionItem[]>)): blink.cmp.Task +--- @field emit_completions fun(self: blink.cmp.SourceTree, items_by_provider: table<string, blink.cmp.CompletionItem[]>, on_items_by_provider: fun(items_by_provider: table<string, blink.cmp.CompletionItem[]>)): nil +--- @field get_top_level_nodes fun(self: blink.cmp.SourceTree): blink.cmp.SourceTreeNode[] +--- @field detect_cycle fun(node: blink.cmp.SourceTreeNode, visited?: table<string, boolean>, path?: table<string, boolean>): boolean + +local utils = require('blink.cmp.lib.utils') +local async = require('blink.cmp.lib.async') + +--- @type blink.cmp.SourceTree +--- @diagnostic disable-next-line: missing-fields +local tree = {} + +--- @param context blink.cmp.Context +--- @param all_sources blink.cmp.SourceProvider[] +function tree.new(context, all_sources) + -- only include enabled sources for the given context + local sources = vim.tbl_filter( + function(source) return vim.tbl_contains(context.providers, source.id) and source:enabled(context) end, + all_sources + ) + local source_ids = vim.tbl_map(function(source) return source.id end, sources) + + -- create a node for each source + local nodes = vim.tbl_map( + function(source) return { id = source.id, source = source, dependencies = {}, dependents = {} } end, + sources + ) + + -- build the tree + for idx, source in ipairs(sources) do + local node = nodes[idx] + for _, fallback_source_id in ipairs(source.config.fallbacks(context, source_ids)) do + local fallback_node = nodes[utils.index_of(source_ids, fallback_source_id)] + if fallback_node ~= nil then + table.insert(node.dependents, fallback_node) + table.insert(fallback_node.dependencies, node) + end + end + end + + -- circular dependency check + for _, node in ipairs(nodes) do + tree.detect_cycle(node) + end + + return setmetatable({ nodes = nodes }, { __index = tree }) +end + +function tree:get_completions(context, on_items_by_provider) + local should_push_upstream = false + local items_by_provider = {} + local is_all_cached = true + local nodes_falling_back = {} + + --- @param node blink.cmp.SourceTreeNode + local function get_completions_for_node(node) + -- check that all the dependencies have been triggered, and are falling back + for _, dependency in ipairs(node.dependencies) do + if not nodes_falling_back[dependency.id] then return async.task.empty() end + end + + return async.task.new(function(resolve, reject) + return node.source:get_completions(context, function(items, is_cached) + items_by_provider[node.id] = items + is_all_cached = is_all_cached and is_cached + + if should_push_upstream then self:emit_completions(items_by_provider, on_items_by_provider) end + if #items ~= 0 then return resolve() end + + -- run dependents if the source returned 0 items + nodes_falling_back[node.id] = true + local tasks = vim.tbl_map(function(dependent) return get_completions_for_node(dependent) end, node.dependents) + async.task.await_all(tasks):map(resolve):catch(reject) + end) + end) + end + + -- run the top level nodes and let them fall back to their dependents if needed + local tasks = vim.tbl_map(function(node) return get_completions_for_node(node) end, self:get_top_level_nodes()) + return async.task + .await_all(tasks) + :map(function() + should_push_upstream = true + + -- if atleast one of the results wasn't cached, emit the results + if not is_all_cached then self:emit_completions(items_by_provider, on_items_by_provider) end + end) + :catch(function(err) vim.print('failed to get completions with error: ' .. err) end) +end + +function tree:emit_completions(items_by_provider, on_items_by_provider) + local nodes_falling_back = {} + local final_items_by_provider = {} + + local add_node_items + add_node_items = function(node) + for _, dependency in ipairs(node.dependencies) do + if not nodes_falling_back[dependency.id] then return end + end + local items = items_by_provider[node.id] + if items ~= nil and #items > 0 then + final_items_by_provider[node.id] = items + else + nodes_falling_back[node.id] = true + for _, dependent in ipairs(node.dependents) do + add_node_items(dependent) + end + end + end + + for _, node in ipairs(self:get_top_level_nodes()) do + add_node_items(node) + end + + on_items_by_provider(final_items_by_provider) +end + +--- Internal --- + +function tree:get_top_level_nodes() + local top_level_nodes = {} + for _, node in ipairs(self.nodes) do + if #node.dependencies == 0 then table.insert(top_level_nodes, node) end + end + return top_level_nodes +end + +--- Helper function to detect cycles using DFS +--- @param node blink.cmp.SourceTreeNode +--- @param visited? table<string, boolean> +--- @param path? table<string, boolean> +--- @return boolean +function tree.detect_cycle(node, visited, path) + visited = visited or {} + path = path or {} + + if path[node.id] then + -- Found a cycle - construct the cycle path for error message + local cycle = { node.id } + for id, _ in pairs(path) do + table.insert(cycle, id) + end + error('Circular dependency detected: ' .. table.concat(cycle, ' -> ')) + end + + if visited[node.id] then return false end + + visited[node.id] = true + path[node.id] = true + + for _, dependent in ipairs(node.dependents) do + if tree.detect_cycle(dependent, visited, path) then return true end + end + + path[node.id] = nil + return false +end + +return tree diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lib/types.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lib/types.lua new file mode 100644 index 0000000..b2bd708 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lib/types.lua @@ -0,0 +1,31 @@ +--- @class blink.cmp.CompletionTriggerContext +--- @field kind number +--- @field character string | nil + +--- @class blink.cmp.CompletionResponse +--- @field is_incomplete_forward boolean +--- @field is_incomplete_backward boolean +--- @field items blink.cmp.CompletionItem[] + +--- @class blink.cmp.Source +--- @field new fun(opts: table, config: blink.cmp.SourceProviderConfig): blink.cmp.Source +--- @field enabled? fun(self: blink.cmp.Source): boolean +--- @field get_trigger_characters? fun(self: blink.cmp.Source): string[] +--- @field get_completions? fun(self: blink.cmp.Source, context: blink.cmp.Context, callback: fun(response?: blink.cmp.CompletionResponse)): (fun(): nil) | nil +--- @field should_show_completions? fun(self: blink.cmp.Source, context: blink.cmp.Context, response: blink.cmp.CompletionResponse): boolean +--- @field resolve? fun(self: blink.cmp.Source, item: blink.cmp.CompletionItem, callback: fun(resolved_item?: lsp.CompletionItem)): ((fun(): nil) | nil) +--- @field execute? fun(self: blink.cmp.Source, context: blink.cmp.Context, item: blink.cmp.CompletionItem, callback: fun()): (fun(): nil) | nil +--- @field get_signature_help_trigger_characters? fun(self: blink.cmp.Source): string[] +--- @field get_signature_help? fun(self: blink.cmp.Source, context: blink.cmp.SignatureHelpContext, callback: fun(signature_help: lsp.SignatureHelp | nil)): (fun(): nil) | nil +--- @field reload? fun(self: blink.cmp.Source): nil + +--- @class blink.cmp.SourceOverride +--- @field enabled? fun(self: blink.cmp.Source): boolean +--- @field get_trigger_characters? fun(self: blink.cmp.Source): string[] +--- @field get_completions? fun(self: blink.cmp.Source, context: blink.cmp.Context, callback: fun(response: blink.cmp.CompletionResponse | nil)): (fun(): nil) | nil +--- @field should_show_completions? fun(self: blink.cmp.Source, context: blink.cmp.Context, response: blink.cmp.CompletionResponse): boolean +--- @field resolve? fun(self: blink.cmp.Source, item: blink.cmp.CompletionItem, callback: fun(resolved_item: lsp.CompletionItem | nil)): ((fun(): nil) | nil) +--- @field execute? fun(self: blink.cmp.Source, context: blink.cmp.Context, item: blink.cmp.CompletionItem, callback: fun()): (fun(): nil) | nil +--- @field get_signature_help_trigger_characters? fun(self: blink.cmp.Source): string[] +--- @field get_signature_help? fun(self: blink.cmp.Source, context: blink.cmp.SignatureHelpContext, callback: fun(signature_help: lsp.SignatureHelp | nil)): (fun(): nil) | nil +--- @field reload? fun(self: blink.cmp.Source): nil diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lib/utils.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lib/utils.lua new file mode 100644 index 0000000..97025bb --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lib/utils.lua @@ -0,0 +1,15 @@ +local utils = {} + +--- @param item blink.cmp.CompletionItem +--- @return lsp.CompletionItem +function utils.blink_item_to_lsp_item(item) + local lsp_item = vim.deepcopy(item) + lsp_item.score_offset = nil + lsp_item.source_id = nil + lsp_item.source_name = nil + lsp_item.cursor_column = nil + lsp_item.client_id = nil + return lsp_item +end + +return utils diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lsp/completion.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lsp/completion.lua new file mode 100644 index 0000000..5d54280 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lsp/completion.lua @@ -0,0 +1,74 @@ +local async = require('blink.cmp.lib.async') +local known_defaults = { + 'commitCharacters', + 'insertTextFormat', + 'insertTextMode', + 'data', +} +local CompletionTriggerKind = vim.lsp.protocol.CompletionTriggerKind + +local completion = {} + +--- @param context blink.cmp.Context +--- @param client vim.lsp.Client +--- @return blink.cmp.Task +function completion.get_completion_for_client(context, client) + return async.task.new(function(resolve) + local params = vim.lsp.util.make_position_params(0, client.offset_encoding) + params.context = { + triggerKind = context.trigger.kind == 'trigger_character' and CompletionTriggerKind.TriggerCharacter + or CompletionTriggerKind.Invoked, + } + if context.trigger.kind == 'trigger_character' then params.context.triggerCharacter = context.trigger.character end + + local _, request_id = client.request('textDocument/completion', params, function(err, result) + if err or result == nil then + resolve({ is_incomplete_forward = true, is_incomplete_backward = true, items = {} }) + return + end + + local items = result.items or result + local default_edit_range = result.itemDefaults and result.itemDefaults.editRange + for _, item in ipairs(items) do + item.client_id = client.id + + -- score offset for deprecated items + -- todo: make configurable + if item.deprecated or (item.tags and vim.tbl_contains(item.tags, 1)) then item.score_offset = -2 end + + -- set defaults + for key, value in pairs(result.itemDefaults or {}) do + if vim.tbl_contains(known_defaults, key) then item[key] = item[key] or value end + end + if default_edit_range and item.textEdit == nil then + local new_text = item.textEditText or item.insertText or item.label + if default_edit_range.replace ~= nil then + item.textEdit = { + replace = default_edit_range.replace, + insert = default_edit_range.insert, + newText = new_text, + } + else + item.textEdit = { + range = result.itemDefaults.editRange, + newText = new_text, + } + end + end + end + + resolve({ + is_incomplete_forward = result.isIncomplete or false, + is_incomplete_backward = true, + items = items, + }) + end) + + -- cancellation function + return function() + if request_id ~= nil then client.cancel_request(request_id) end + end + end) +end + +return completion diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lsp/init.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lsp/init.lua new file mode 100644 index 0000000..4dbfd8f --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/lsp/init.lua @@ -0,0 +1,131 @@ +local async = require('blink.cmp.lib.async') + +--- @type blink.cmp.Source +--- @diagnostic disable-next-line: missing-fields +local lsp = {} + +function lsp.new() return setmetatable({}, { __index = lsp }) end + +--- Completion --- + +function lsp:get_trigger_characters() + local clients = vim.lsp.get_clients({ bufnr = 0 }) + local trigger_characters = {} + + for _, client in pairs(clients) do + local completion_provider = client.server_capabilities.completionProvider + if completion_provider and completion_provider.triggerCharacters then + for _, trigger_character in pairs(completion_provider.triggerCharacters) do + table.insert(trigger_characters, trigger_character) + end + end + end + + return trigger_characters +end + +function lsp:get_completions(context, callback) + local completion_lib = require('blink.cmp.sources.lsp.completion') + local clients = vim.tbl_filter( + function(client) return client.server_capabilities and client.server_capabilities.completionProvider end, + vim.lsp.get_clients({ bufnr = 0, method = 'textDocument/completion' }) + ) + + -- TODO: implement a timeout before returning the menu as-is. In the future, it would be neat + -- to detect slow LSPs and consistently run them async + local task = async.task + .await_all(vim.tbl_map(function(client) return completion_lib.get_completion_for_client(context, client) end, clients)) + :map(function(responses) + local final = { is_incomplete_forward = false, is_incomplete_backward = false, items = {} } + for _, response in ipairs(responses) do + final.is_incomplete_forward = final.is_incomplete_forward or response.is_incomplete_forward + final.is_incomplete_backward = final.is_incomplete_backward or response.is_incomplete_backward + vim.list_extend(final.items, response.items) + end + callback(final) + end) + return function() task:cancel() end +end + +--- Resolve --- + +function lsp:resolve(item, callback) + local client = vim.lsp.get_client_by_id(item.client_id) + if client == nil or not client.server_capabilities.completionProvider.resolveProvider then + callback(item) + return + end + + -- strip blink specific fields to avoid decoding errors on some LSPs + item = require('blink.cmp.sources.lib.utils').blink_item_to_lsp_item(item) + + local success, request_id = client.request('completionItem/resolve', item, function(error, resolved_item) + if error or resolved_item == nil then callback(item) end + callback(resolved_item) + end) + if not success then callback(item) end + if request_id ~= nil then + return function() client.cancel_request(request_id) end + end +end + +--- Signature help --- + +function lsp:get_signature_help_trigger_characters() + local clients = vim.lsp.get_clients({ bufnr = 0 }) + local trigger_characters = {} + local retrigger_characters = {} + + for _, client in pairs(clients) do + local signature_help_provider = client.server_capabilities.signatureHelpProvider + if signature_help_provider and signature_help_provider.triggerCharacters then + for _, trigger_character in pairs(signature_help_provider.triggerCharacters) do + table.insert(trigger_characters, trigger_character) + end + end + if signature_help_provider and signature_help_provider.retriggerCharacters then + for _, retrigger_character in pairs(signature_help_provider.retriggerCharacters) do + table.insert(retrigger_characters, retrigger_character) + end + end + end + + return { trigger_characters = trigger_characters, retrigger_characters = retrigger_characters } +end + +function lsp:get_signature_help(context, callback) + -- no providers with signature help support + if #vim.lsp.get_clients({ bufnr = 0, method = 'textDocument/signatureHelp' }) == 0 then + callback(nil) + return function() end + end + + -- TODO: offset encoding is global but should be per-client + local first_client = vim.lsp.get_clients({ bufnr = 0 })[1] + local offset_encoding = first_client and first_client.offset_encoding or 'utf-16' + + local params = vim.lsp.util.make_position_params(nil, offset_encoding) + params.context = { + triggerKind = context.trigger.kind, + triggerCharacter = context.trigger.character, + isRetrigger = context.is_retrigger, + activeSignatureHelp = context.active_signature_help, + } + + -- otherwise, we call all clients + -- TODO: some LSPs never response (typescript-tools.nvim) + return vim.lsp.buf_request_all(0, 'textDocument/signatureHelp', params, function(result) + local signature_helps = {} + for client_id, res in pairs(result) do + local signature_help = res.result + if signature_help ~= nil then + signature_help.client_id = client_id + table.insert(signature_helps, signature_help) + end + end + -- todo: pick intelligently + callback(signature_helps[1]) + end) +end + +return lsp diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/path/fs.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/path/fs.lua new file mode 100644 index 0000000..4ac79f0 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/path/fs.lua @@ -0,0 +1,70 @@ +local async = require('blink.cmp.lib.async') +local uv = vim.uv +local fs = {} + +--- Scans a directory asynchronously in a loop until +--- it finds all entries +--- @param path string +--- @return blink.cmp.Task +function fs.scan_dir_async(path) + local max_entries = 200 + return async.task.new(function(resolve, reject) + uv.fs_opendir(path, function(err, handle) + if err ~= nil or handle == nil then return reject(err) end + + local all_entries = {} + + local function read_dir() + uv.fs_readdir(handle, function(err, entries) + if err ~= nil or entries == nil then return reject(err) end + + vim.list_extend(all_entries, entries) + if #entries == max_entries then + read_dir() + else + resolve(all_entries) + end + end) + end + read_dir() + end, max_entries) + end) +end + +--- @param entries { name: string, type: string }[] +--- @return blink.cmp.Task +function fs.fs_stat_all(cwd, entries) + local tasks = {} + for _, entry in ipairs(entries) do + table.insert( + tasks, + async.task.new(function(resolve) + uv.fs_stat(cwd .. '/' .. entry.name, function(err, stat) + if err then return resolve(nil) end + resolve({ name = entry.name, type = entry.type, stat = stat }) + end) + end) + ) + end + return async.task.await_all(tasks):map(function(entries) + return vim.tbl_filter(function(entry) return entry ~= nil end, entries) + end) +end + +--- @param path string +--- @param byte_limit number +--- @return blink.cmp.Task +function fs.read_file(path, byte_limit) + return async.task.new(function(resolve, reject) + uv.fs_open(path, 'r', 438, function(open_err, fd) + if open_err or fd == nil then return reject(open_err) end + uv.fs_read(fd, byte_limit, 0, function(read_err, data) + uv.fs_close(fd, function() end) + if read_err or data == nil then return reject(read_err) end + resolve(data) + end) + end) + end) +end + +return fs diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/path/init.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/path/init.lua new file mode 100644 index 0000000..bb5f508 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/path/init.lua @@ -0,0 +1,89 @@ +-- credit to https://github.com/hrsh7th/cmp-path for the original implementation +-- and https://codeberg.org/FelipeLema/cmp-async-path for the async implementation + +--- @class blink.cmp.PathOpts +--- @field trailing_slash boolean +--- @field label_trailing_slash boolean +--- @field get_cwd fun(context: blink.cmp.Context): string +--- @field show_hidden_files_by_default boolean + +--- @class blink.cmp.Source +--- @field opts blink.cmp.PathOpts +local path = {} + +function path.new(opts) + local self = setmetatable({}, { __index = path }) + + --- @type blink.cmp.PathOpts + opts = vim.tbl_deep_extend('keep', opts, { + trailing_slash = true, + label_trailing_slash = true, + get_cwd = function(context) return vim.fn.expand(('#%d:p:h'):format(context.bufnr)) end, + show_hidden_files_by_default = false, + }) + require('blink.cmp.config.utils').validate('sources.providers.path', { + trailing_slash = { opts.trailing_slash, 'boolean' }, + label_trailing_slash = { opts.label_trailing_slash, 'boolean' }, + get_cwd = { opts.get_cwd, 'function' }, + show_hidden_files_by_default = { opts.show_hidden_files_by_default, 'boolean' }, + }, opts) + + self.opts = opts + return self +end + +function path:get_trigger_characters() return { '/', '.' } end + +function path:get_completions(context, callback) + -- we use libuv, but the rest of the library expects to be synchronous + callback = vim.schedule_wrap(callback) + + local lib = require('blink.cmp.sources.path.lib') + + local dirname = lib.dirname(self.opts.get_cwd, context) + if not dirname then return callback({ is_incomplete_forward = false, is_incomplete_backward = false, items = {} }) end + + local include_hidden = self.opts.show_hidden_files_by_default + or (string.sub(context.line, context.bounds.start_col, context.bounds.start_col) == '.' and context.bounds.length == 0) + or ( + string.sub(context.line, context.bounds.start_col - 1, context.bounds.start_col - 1) == '.' + and context.bounds.length > 0 + ) + lib + .candidates(context, dirname, include_hidden, self.opts) + :map( + function(candidates) + callback({ is_incomplete_forward = false, is_incomplete_backward = false, items = candidates }) + end + ) + :catch(function() callback() end) +end + +function path:resolve(item, callback) + require('blink.cmp.sources.path.fs') + .read_file(item.data.full_path, 1024) + :map(function(content) + local is_binary = content:find('\0') + + -- binary file + if is_binary then + item.documentation = { + kind = 'plaintext', + value = 'Binary file', + } + -- highlight with markdown + else + local ext = vim.fn.fnamemodify(item.data.path, ':e') + item.documentation = { + kind = 'markdown', + value = '```' .. ext .. '\n' .. content .. '```', + } + end + + return item + end) + :map(function(resolved_item) callback(resolved_item) end) + :catch(function() callback(item) end) +end + +return path diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/path/lib.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/path/lib.lua new file mode 100644 index 0000000..53fd970 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/path/lib.lua @@ -0,0 +1,125 @@ +local regex = require('blink.cmp.sources.path.regex') +local lib = {} + +--- @param get_cwd fun(context: blink.cmp.Context): string +--- @param context blink.cmp.Context +function lib.dirname(get_cwd, context) + -- HACK: move this :sub logic into the context? + -- it's not obvious that you need to avoid going back a char if the start_col == end_col + local line_before_cursor = context.line:sub(1, context.bounds.start_col - (context.bounds.length == 0 and 1 or 0)) + local s = regex.PATH:match_str(line_before_cursor) + if not s then return nil end + + local dirname = string.gsub(string.sub(line_before_cursor, s + 2), regex.NAME .. '*$', '') -- exclude '/' + local prefix = string.sub(line_before_cursor, 1, s + 1) -- include '/' + + local buf_dirname = get_cwd(context) + if vim.api.nvim_get_mode().mode == 'c' then buf_dirname = vim.fn.getcwd() end + if prefix:match('%.%./$') then return vim.fn.resolve(buf_dirname .. '/../' .. dirname) end + if prefix:match('%./$') or prefix:match('"$') or prefix:match("'$") then + return vim.fn.resolve(buf_dirname .. '/' .. dirname) + end + if prefix:match('~/$') then return vim.fn.resolve(vim.fn.expand('~') .. '/' .. dirname) end + local env_var_name = prefix:match('%$([%a_]+)/$') + if env_var_name then + local env_var_value = vim.fn.getenv(env_var_name) + if env_var_value ~= vim.NIL then return vim.fn.resolve(env_var_value .. '/' .. dirname) end + end + if prefix:match('/$') then + local accept = true + -- Ignore URL components + accept = accept and not prefix:match('%a/$') + -- Ignore URL scheme + accept = accept and not prefix:match('%a+:/$') and not prefix:match('%a+://$') + -- Ignore HTML closing tags + accept = accept and not prefix:match('</$') + -- Ignore math calculation + accept = accept and not prefix:match('[%d%)]%s*/$') + -- Ignore / comment + accept = accept and (not prefix:match('^[%s/]*$') or not lib.is_slash_comment()) + if accept then return vim.fn.resolve('/' .. dirname) end + end + -- Windows drive letter (C:/) + if prefix:match('(%a:)[/\\]$') then return vim.fn.resolve(prefix:match('(%a:)[/\\]$') .. '/' .. dirname) end + return nil +end + +--- @param context blink.cmp.Context +--- @param dirname string +--- @param include_hidden boolean +--- @param opts table +function lib.candidates(context, dirname, include_hidden, opts) + local fs = require('blink.cmp.sources.path.fs') + local ranges = lib.get_text_edit_ranges(context) + return fs.scan_dir_async(dirname) + :map(function(entries) return fs.fs_stat_all(dirname, entries) end) + :map(function(entries) + return vim.tbl_filter(function(entry) return include_hidden or entry.name:sub(1, 1) ~= '.' end, entries) + end) + :map(function(entries) + return vim.tbl_map( + function(entry) + return lib.entry_to_completion_item( + entry, + dirname, + entry.type == 'directory' and ranges.directory or ranges.file, + opts + ) + end, + entries + ) + end) +end + +function lib.is_slash_comment() + local commentstring = vim.bo.commentstring or '' + local no_filetype = vim.bo.filetype == '' + local is_slash_comment = false + is_slash_comment = is_slash_comment or commentstring:match('/%*') + is_slash_comment = is_slash_comment or commentstring:match('//') + return is_slash_comment and not no_filetype +end + +--- @param entry { name: string, type: string, stat: table } +--- @param dirname string +--- @param range lsp.Range +--- @param opts table +--- @return blink.cmp.CompletionItem[] +function lib.entry_to_completion_item(entry, dirname, range, opts) + local is_dir = entry.type == 'directory' + local CompletionItemKind = require('blink.cmp.types').CompletionItemKind + local insert_text = is_dir and opts.trailing_slash and entry.name .. '/' or entry.name + return { + label = (opts.label_trailing_slash and is_dir) and entry.name .. '/' or entry.name, + kind = is_dir and CompletionItemKind.Folder or CompletionItemKind.File, + insertText = insert_text, + textEdit = { newText = insert_text, range = range }, + sortText = (is_dir and '1' or '2') .. entry.name:lower(), -- Sort directories before files + data = { path = entry.name, full_path = dirname .. '/' .. entry.name, type = entry.type, stat = entry.stat }, + } +end + +--- @param context blink.cmp.Context +--- @return { file: lsp.Range, directory: lsp.Range } +function lib.get_text_edit_ranges(context) + local line_before_cursor = context.line:sub(1, context.cursor[2]) + local next_letter_is_slash = context.line:sub(context.cursor[2] + 1, context.cursor[2] + 1) == '/' + + local parts = vim.split(line_before_cursor, '/') + local last_part = parts[#parts] + + -- TODO: return the insert and replace ranges, instead of only the insert range + return { + file = { + start = { line = context.cursor[1] - 1, character = context.cursor[2] - #last_part }, + ['end'] = { line = context.cursor[1] - 1, character = context.cursor[2] }, + }, + directory = { + start = { line = context.cursor[1] - 1, character = context.cursor[2] - #last_part }, + -- replace the slash after the cursor, if it exists + ['end'] = { line = context.cursor[1] - 1, character = context.cursor[2] + (next_letter_is_slash and 1 or 0) }, + }, + } +end + +return lib diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/path/regex.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/path/regex.lua new file mode 100644 index 0000000..af27d25 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/path/regex.lua @@ -0,0 +1,10 @@ +local NAME_REGEX = '\\%([^/\\\\:\\*?<>\'"`\\|]\\)' +local PATH_REGEX = + assert(vim.regex(([[\%(\%(/PAT*[^/\\\\:\\*?<>\'"`\\| .~]\)\|\%(/\.\.\)\)*/\zePAT*$]]):gsub('PAT', NAME_REGEX))) + +return { + --- Lua pattern for matching file names + NAME = '[^/\\:*?<>\'"`|]', + --- Vim regex for matching file paths + PATH = PATH_REGEX, +} diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/snippets/default/builtin.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/snippets/default/builtin.lua new file mode 100644 index 0000000..b66ca1c --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/snippets/default/builtin.lua @@ -0,0 +1,188 @@ +-- credit to https://github.com/L3MON4D3 for these variables +-- see: https://github.com/L3MON4D3/LuaSnip/blob/master/lua/luasnip/util/_builtin_vars.lua +-- and credit to https://github.com/garymjr for his changes +-- see: https://github.com/garymjr/nvim-snippets/blob/main/lua/snippets/utils/builtin.lua + +local builtin = { + lazy = {}, +} + +function builtin.lazy.TM_FILENAME() return vim.fn.expand('%:t') end + +function builtin.lazy.TM_FILENAME_BASE() return vim.fn.expand('%:t:s?\\.[^\\.]\\+$??') end + +function builtin.lazy.TM_DIRECTORY() return vim.fn.expand('%:p:h') end + +function builtin.lazy.TM_FILEPATH() return vim.fn.expand('%:p') end + +function builtin.lazy.CLIPBOARD(opts) return vim.fn.getreg(opts.clipboard_register or vim.v.register, true) end + +local function buf_to_ws_part() + local LSP_WORSKPACE_PARTS = 'LSP_WORSKPACE_PARTS' + local ok, ws_parts = pcall(vim.api.nvim_buf_get_var, 0, LSP_WORSKPACE_PARTS) + if not ok then + local file_path = vim.fn.expand('%:p') + + for _, ws in pairs(vim.lsp.buf.list_workspace_folders()) do + if file_path:find(ws, 1, true) == 1 then + ws_parts = { ws, file_path:sub(#ws + 2, -1) } + break + end + end + -- If it can't be extracted from lsp, then we use the file path + if not ok and not ws_parts then ws_parts = { vim.fn.expand('%:p:h'), vim.fn.expand('%:p:t') } end + vim.api.nvim_buf_set_var(0, LSP_WORSKPACE_PARTS, ws_parts) + end + return ws_parts +end + +function builtin.lazy.RELATIVE_FILEPATH() -- The relative (to the opened workspace or folder) file path of the current document + return buf_to_ws_part()[2] +end + +function builtin.lazy.WORKSPACE_FOLDER() -- The path of the opened workspace or folder + return buf_to_ws_part()[1] +end + +function builtin.lazy.WORKSPACE_NAME() -- The name of the opened workspace or folder + local parts = vim.split(buf_to_ws_part()[1] or '', '[\\/]') + return parts[#parts] +end + +function builtin.lazy.CURRENT_YEAR() return os.date('%Y') end + +function builtin.lazy.CURRENT_YEAR_SHORT() return os.date('%y') end + +function builtin.lazy.CURRENT_MONTH() return os.date('%m') end + +function builtin.lazy.CURRENT_MONTH_NAME() return os.date('%B') end + +function builtin.lazy.CURRENT_MONTH_NAME_SHORT() return os.date('%b') end + +function builtin.lazy.CURRENT_DATE() return os.date('%d') end + +function builtin.lazy.CURRENT_DAY_NAME() return os.date('%A') end + +function builtin.lazy.CURRENT_DAY_NAME_SHORT() return os.date('%a') end + +function builtin.lazy.CURRENT_HOUR() return os.date('%H') end + +function builtin.lazy.CURRENT_MINUTE() return os.date('%M') end + +function builtin.lazy.CURRENT_SECOND() return os.date('%S') end + +function builtin.lazy.CURRENT_SECONDS_UNIX() return tostring(os.time()) end + +local function get_timezone_offset(ts) + local utcdate = os.date('!*t', ts) + local localdate = os.date('*t', ts) + localdate.isdst = false -- this is the trick + local diff = os.difftime(os.time(localdate), os.time(utcdate)) + local h, m = math.modf(diff / 3600) + return string.format('%+.4d', 100 * h + 60 * m) +end + +function builtin.lazy.CURRENT_TIMEZONE_OFFSET() + return get_timezone_offset(os.time()):gsub('([+-])(%d%d)(%d%d)$', '%1%2:%3') +end + +math.randomseed(os.time()) + +function builtin.lazy.RANDOM() return string.format('%06d', math.random(999999)) end + +function builtin.lazy.RANDOM_HEX() + return string.format('%06x', math.random(16777216)) --16^6 +end + +function builtin.lazy.UUID() + local random = math.random + local template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' + local out + local function subs(c) + local v = (((c == 'x') and random(0, 15)) or random(8, 11)) + return string.format('%x', v) + end + + out = template:gsub('[xy]', subs) + return out +end + +local _comments_cache = {} +local function buffer_comment_chars() + local commentstring = vim.bo.commentstring + if _comments_cache[commentstring] then return _comments_cache[commentstring] end + local comments = { '//', '/*', '*/' } + local placeholder = '%s' + local index_placeholder = commentstring:find(vim.pesc(placeholder)) + if index_placeholder then + index_placeholder = index_placeholder - 1 + if index_placeholder + #placeholder == #commentstring then + comments[1] = vim.trim(commentstring:sub(1, -#placeholder - 1)) + else + comments[2] = vim.trim(commentstring:sub(1, index_placeholder)) + comments[3] = vim.trim(commentstring:sub(index_placeholder + #placeholder + 1, -1)) + end + end + _comments_cache[commentstring] = comments + return comments +end + +function builtin.lazy.LINE_COMMENT() return buffer_comment_chars()[1] end + +function builtin.lazy.BLOCK_COMMENT_START() return buffer_comment_chars()[2] end + +function builtin.lazy.BLOCK_COMMENT_END() return buffer_comment_chars()[3] end + +local function get_cursor() + local c = vim.api.nvim_win_get_cursor(0) + c[1] = c[1] - 1 + return c +end + +local function get_current_line() + local pos = get_cursor() + return vim.api.nvim_buf_get_lines(0, pos[1], pos[1] + 1, false)[1] +end + +local function word_under_cursor(cur, line) + if line == nil then return end + + local ind_start = 1 + local ind_end = #line + + while true do + local tmp = string.find(line, '%W%w', ind_start) + if not tmp then break end + if tmp > cur[2] + 1 then break end + ind_start = tmp + 1 + end + + local tmp = string.find(line, '%w%W', cur[2] + 1) + if tmp then ind_end = tmp end + + return string.sub(line, ind_start, ind_end) +end + +local function get_selected_text() + if vim.fn.visualmode() == 'V' then return vim.fn.trim(vim.fn.getreg(vim.v.register, true), '\n', 2) end + return '' +end + +vim.api.nvim_create_autocmd('InsertEnter', { + group = vim.api.nvim_create_augroup('BlinkSnippetsEagerEnter', { clear = true }), + callback = function() + builtin.eager = {} + builtin.eager.TM_CURRENT_LINE = get_current_line() + builtin.eager.TM_CURRENT_WORD = word_under_cursor(get_cursor(), builtin.eager.TM_CURRENT_LINE) + builtin.eager.TM_LINE_INDEX = tostring(get_cursor()[1]) + builtin.eager.TM_LINE_NUMBER = tostring(get_cursor()[1] + 1) + builtin.eager.TM_SELECTED_TEXT = get_selected_text() + end, +}) + +vim.api.nvim_create_autocmd('InsertLeave', { + group = vim.api.nvim_create_augroup('BlinkSnippetsEagerLeave', { clear = true }), + callback = function() builtin.eager = nil end, +}) + +return builtin diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/snippets/default/init.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/snippets/default/init.lua new file mode 100644 index 0000000..db7fece --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/snippets/default/init.lua @@ -0,0 +1,65 @@ +--- @class blink.cmp.SnippetsOpts +--- @field friendly_snippets? boolean +--- @field search_paths? string[] +--- @field global_snippets? string[] +--- @field extended_filetypes? table<string, string[]> +--- @field ignored_filetypes? string[] +--- @field get_filetype? fun(context: blink.cmp.Context): string +--- @field clipboard_register? string + +local snippets = {} + +function snippets.new(opts) + --- @cast opts blink.cmp.SnippetsOpts + + local self = setmetatable({}, { __index = snippets }) + --- @type table<string, blink.cmp.CompletionItem[]> + self.cache = {} + self.registry = require('blink.cmp.sources.snippets.default.registry').new(opts) + self.get_filetype = opts.get_filetype or function() return vim.bo.filetype end + return self +end + +function snippets:get_completions(context, callback) + local filetype = self.get_filetype(context) + if vim.tbl_contains(self.registry.config.ignored_filetypes, filetype) then return callback() end + + if not self.cache[filetype] then + local global_snippets = self.registry:get_global_snippets() + local extended_snippets = self.registry:get_extended_snippets(filetype) + local ft_snippets = self.registry:get_snippets_for_ft(filetype) + local snips = vim.list_extend({}, global_snippets) + vim.list_extend(snips, extended_snippets) + vim.list_extend(snips, ft_snippets) + + self.cache[filetype] = snips + end + + local items = vim.tbl_map( + function(item) return self.registry:snippet_to_completion_item(item) end, + self.cache[filetype] + ) + callback({ + is_incomplete_forward = false, + is_incomplete_backward = false, + items = items, + }) +end + +function snippets:resolve(item, callback) + local parsed_snippet = require('blink.cmp.sources.snippets.utils').safe_parse(item.insertText) + local snippet = parsed_snippet and tostring(parsed_snippet) or item.insertText + + local resolved_item = vim.deepcopy(item) + resolved_item.detail = snippet + resolved_item.documentation = { + kind = 'markdown', + value = item.description, + } + callback(resolved_item) +end + +--- For external integrations to force reloading the snippets +function snippets:reload() self.cache = {} end + +return snippets diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/snippets/default/registry.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/snippets/default/registry.lua new file mode 100644 index 0000000..5be225c --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/snippets/default/registry.lua @@ -0,0 +1,144 @@ +--- Credit to https://github.com/garymjr/nvim-snippets/blob/main/lua/snippets/utils/init.lua +--- for the original implementation +--- Original License: MIT + +--- @class blink.cmp.Snippet +--- @field prefix string +--- @field body string[] | string +--- @field description? string + +local registry = { + builtin_vars = require('blink.cmp.sources.snippets.default.builtin'), +} + +local utils = require('blink.cmp.sources.snippets.utils') +local default_config = { + friendly_snippets = true, + search_paths = { vim.fn.stdpath('config') .. '/snippets' }, + global_snippets = { 'all' }, + extended_filetypes = {}, + ignored_filetypes = {}, + --- @type string? + clipboard_register = nil, +} + +--- @param config blink.cmp.SnippetsOpts +function registry.new(config) + local self = setmetatable({}, { __index = registry }) + self.config = vim.tbl_deep_extend('force', default_config, config) + self.config.search_paths = vim.tbl_map(function(path) return vim.fs.normalize(path) end, self.config.search_paths) + + if self.config.friendly_snippets then + for _, path in ipairs(vim.api.nvim_list_runtime_paths()) do + if string.match(path, 'friendly.snippets') then table.insert(self.config.search_paths, path) end + end + end + self.registry = require('blink.cmp.sources.snippets.default.scan').register_snippets(self.config.search_paths) + + return self +end + +--- @param filetype string +--- @return blink.cmp.Snippet[] +function registry:get_snippets_for_ft(filetype) + local loaded_snippets = {} + local files = self.registry[filetype] + if not files then return loaded_snippets end + + files = type(files) == 'table' and files or { files } + + for _, f in ipairs(files) do + local contents = utils.read_file(f) + if contents then + local snippets = utils.parse_json_with_error_msg(f, contents) + for _, key in ipairs(vim.tbl_keys(snippets)) do + local snippet = utils.read_snippet(snippets[key], key) + for _, snippet_def in pairs(snippet) do + table.insert(loaded_snippets, snippet_def) + end + end + end + end + + return loaded_snippets +end + +--- @param filetype string +--- @return blink.cmp.Snippet[] +function registry:get_extended_snippets(filetype) + local loaded_snippets = {} + if not filetype then return loaded_snippets end + + local extended_snippets = self.config.extended_filetypes[filetype] or {} + for _, ft in ipairs(extended_snippets) do + if vim.tbl_contains(self.config.extended_filetypes, filetype) then + vim.list_extend(loaded_snippets, self:get_extended_snippets(ft)) + else + vim.list_extend(loaded_snippets, self:get_snippets_for_ft(ft)) + end + end + return loaded_snippets +end + +--- @return blink.cmp.Snippet[] +function registry:get_global_snippets() + local loaded_snippets = {} + local global_snippets = self.config.global_snippets + for _, ft in ipairs(global_snippets) do + if vim.tbl_contains(self.config.extended_filetypes, ft) then + vim.list_extend(loaded_snippets, self:get_extended_snippets(ft)) + else + vim.list_extend(loaded_snippets, self:get_snippets_for_ft(ft)) + end + end + return loaded_snippets +end + +--- @param snippet blink.cmp.Snippet +--- @return blink.cmp.CompletionItem +function registry:snippet_to_completion_item(snippet) + local body = type(snippet.body) == 'string' and snippet.body or table.concat(snippet.body, '\n') + return { + kind = require('blink.cmp.types').CompletionItemKind.Snippet, + label = snippet.prefix, + insertTextFormat = vim.lsp.protocol.InsertTextFormat.Snippet, + insertText = self:expand_vars(body), + description = snippet.description, + } +end + +--- @param snippet string +--- @return string +function registry:parse_body(snippet) + local parse = utils.safe_parse(self:expand_vars(snippet)) + return parse and tostring(parse) or snippet +end + +--- @param snippet string +--- @return string +function registry:expand_vars(snippet) + local lazy_vars = self.builtin_vars.lazy + local eager_vars = self.builtin_vars.eager or {} + + local resolved_snippet = snippet + local parsed_snippet = utils.safe_parse(snippet) + if not parsed_snippet then return snippet end + + for _, child in ipairs(parsed_snippet.data.children) do + local type, data = child.type, child.data + if type == vim.lsp._snippet_grammar.NodeType.Variable then + if eager_vars[data.name] then + resolved_snippet = resolved_snippet:gsub('%$[{]?(' .. data.name .. ')[}]?', eager_vars[data.name]) + elseif lazy_vars[data.name] then + resolved_snippet = resolved_snippet:gsub( + '%$[{]?(' .. data.name .. ')[}]?', + lazy_vars[data.name]({ clipboard_register = self.config.clipboard_register }) + ) + end + end + end + + return resolved_snippet +end + +return registry diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/snippets/default/scan.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/snippets/default/scan.lua new file mode 100644 index 0000000..6971691 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/snippets/default/scan.lua @@ -0,0 +1,94 @@ +local utils = require('blink.cmp.sources.snippets.utils') +local scan = {} + +function scan.register_snippets(search_paths) + local registry = {} + + for _, path in ipairs(search_paths) do + local files = scan.load_package_json(path) or scan.scan_for_snippets(path) + for ft, file in pairs(files) do + local key + if type(ft) == 'number' then + key = vim.fn.fnamemodify(files[ft], ':t:r') + else + key = ft + end + + if not key then return end + + registry[key] = registry[key] or {} + if type(file) == 'table' then + vim.list_extend(registry[key], file) + else + table.insert(registry[key], file) + end + end + end + + return registry +end + +---@type fun(self: utils, dir: string, result?: string[]): string[] +---@return string[] +function scan.scan_for_snippets(dir, result) + result = result or {} + + local stat = vim.uv.fs_stat(dir) + if not stat then return result end + + if stat.type == 'directory' then + local req = vim.uv.fs_scandir(dir) + if not req then return result end + + local function iter() return vim.uv.fs_scandir_next(req) end + + for name, ftype in iter do + local path = string.format('%s/%s', dir, name) + + if ftype == 'directory' then + result[name] = scan.scan_for_snippets(path, result[name] or {}) + else + scan.scan_for_snippets(path, result) + end + end + elseif stat.type == 'file' then + local name = vim.fn.fnamemodify(dir, ':t') + + if name:match('%.json$') then table.insert(result, dir) end + elseif stat.type == 'link' then + local target = vim.uv.fs_readlink(dir) + + if target then scan.scan_for_snippets(target, result) end + end + + return result +end + +--- This will try to load the snippets from the package.json file +---@param path string +function scan.load_package_json(path) + local file = path .. '/package.json' + -- todo: ideally this is async, although it takes 0.5ms on my system so it might not matter + local data = utils.read_file(file) + if not data then return end + + local pkg = require('blink.cmp.sources.snippets.utils').parse_json_with_error_msg(file, data) + + ---@type {path: string, language: string|string[]}[] + local snippets = vim.tbl_get(pkg, 'contributes', 'snippets') + if not snippets then return end + + local ret = {} ---@type table<string, string[]> + for _, s in ipairs(snippets) do + local langs = s.language or {} + langs = type(langs) == 'string' and { langs } or langs + ---@cast langs string[] + for _, lang in ipairs(langs) do + ret[lang] = ret[lang] or {} + table.insert(ret[lang], vim.fs.normalize(vim.fs.joinpath(path, s.path))) + end + end + return ret +end + +return scan diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/snippets/init.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/snippets/init.lua new file mode 100644 index 0000000..2a4b0ba --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/snippets/init.lua @@ -0,0 +1,9 @@ +local source = {} + +function source.new(opts) + local preset = opts.preset or require('blink.cmp.config').snippets.preset + local module = 'blink.cmp.sources.snippets.' .. preset + return require(module).new(opts) +end + +return source diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/snippets/luasnip.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/snippets/luasnip.lua new file mode 100644 index 0000000..91716d2 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/snippets/luasnip.lua @@ -0,0 +1,168 @@ +--- @class blink.cmp.LuasnipSourceOptions +--- @field use_show_condition? boolean Whether to use show_condition for filtering snippets +--- @field show_autosnippets? boolean Whether to show autosnippets in the completion list + +--- @class blink.cmp.LuasnipSource : blink.cmp.Source +--- @field config blink.cmp.LuasnipSourceOptions +--- @field items_cache table<string, blink.cmp.CompletionItem[]> + +--- @type blink.cmp.LuasnipSource +--- @diagnostic disable-next-line: missing-fields +local source = {} + +local defaults_config = { + use_show_condition = true, + show_autosnippets = true, +} + +function source.new(opts) + local config = vim.tbl_deep_extend('keep', opts, defaults_config) + require('blink.cmp.config.utils').validate('sources.providers.luasnip', { + use_show_condition = { config.use_show_condition, 'boolean' }, + show_autosnippets = { config.show_autosnippets, 'boolean' }, + }, config) + + local self = setmetatable({}, { __index = source }) + self.config = config + self.items_cache = {} + + local luasnip_ag = vim.api.nvim_create_augroup('BlinkCmpLuaSnipReload', { clear = true }) + vim.api.nvim_create_autocmd('User', { + pattern = 'LuasnipSnippetsAdded', + callback = function() self:reload() end, + group = luasnip_ag, + desc = 'Reset internal cache of luasnip source of blink.cmp when new snippets are added', + }) + vim.api.nvim_create_autocmd('User', { + pattern = 'LuasnipCleanup', + callback = function() self:reload() end, + group = luasnip_ag, + desc = 'Reload luasnip source of blink.cmp when snippets are cleared', + }) + + return self +end + +function source:enabled() + local ok, _ = pcall(require, 'luasnip') + return ok +end + +function source:get_completions(ctx, callback) + --- @type blink.cmp.CompletionItem[] + local items = {} + + -- gather snippets from relevant filetypes, including extensions + for _, ft in ipairs(require('luasnip.util.util').get_snippet_filetypes()) do + if self.items_cache[ft] then + vim.list_extend(items, self.items_cache[ft]) + goto continue + end + + -- cache not yet available for this filetype + self.items_cache[ft] = {} + -- Gather filetype snippets and, optionally, autosnippets + local snippets = require('luasnip').get_snippets(ft, { type = 'snippets' }) + if self.config.show_autosnippets then + local autosnippets = require('luasnip').get_snippets(ft, { type = 'autosnippets' }) + snippets = require('blink.cmp.lib.utils').shallow_copy(snippets) + vim.list_extend(snippets, autosnippets) + end + snippets = vim.tbl_filter(function(snip) return not snip.hidden end, snippets) + + -- Get the max priority for use with sortText + local max_priority = 0 + for _, snip in ipairs(snippets) do + max_priority = math.max(max_priority, snip.effective_priority or 0) + end + + for _, snip in ipairs(snippets) do + -- Convert priority of 1000 (with max of 8000) to string like "00007000|||asd" for sorting + -- This will put high priority snippets at the top of the list, and break ties based on the trigger + local inversed_priority = max_priority - (snip.effective_priority or 0) + local sort_text = ('0'):rep(8 - #tostring(inversed_priority), '') .. inversed_priority .. '|||' .. snip.trigger + + --- @type lsp.CompletionItem + local item = { + kind = require('blink.cmp.types').CompletionItemKind.Snippet, + label = snip.trigger, + insertText = snip.trigger, + insertTextFormat = vim.lsp.protocol.InsertTextFormat.PlainText, + sortText = sort_text, + data = { snip_id = snip.id, show_condition = snip.show_condition }, + } + -- populate snippet cache for this filetype + table.insert(self.items_cache[ft], item) + -- while we're at it, also populate completion items for this request + table.insert(items, item) + end + + ::continue:: + end + + -- Filter items based on show_condition, if configured + if self.config.use_show_condition then + local line_to_cursor = ctx.line:sub(0, ctx.cursor[2] - 1) + items = vim.tbl_filter(function(item) return item.data.show_condition(line_to_cursor) end, items) + end + + callback({ + is_incomplete_forward = false, + is_incomplete_backward = false, + items = items, + context = ctx, + }) +end + +function source:resolve(item, callback) + local snip = require('luasnip').get_id_snippet(item.data.snip_id) + + local resolved_item = vim.deepcopy(item) + + local detail = snip:get_docstring() + if type(detail) == 'table' then detail = table.concat(detail, '\n') end + resolved_item.detail = detail + + if snip.dscr then + resolved_item.documentation = { + kind = 'markdown', + value = table.concat(vim.lsp.util.convert_input_to_markdown_lines(snip.dscr), '\n'), + } + end + + callback(resolved_item) +end + +function source:execute(_, item) + local luasnip = require('luasnip') + local snip = luasnip.get_id_snippet(item.data.snip_id) + + -- if trigger is a pattern, expand "pattern" instead of actual snippet + if snip.regTrig then snip = snip:get_pattern_expand_helper() end + + -- get (0, 0) indexed cursor position + -- the completion has been accepted by this point, so ctx.cursor is out of date + local cursor = vim.api.nvim_win_get_cursor(0) + cursor[1] = cursor[1] - 1 + + local expand_params = snip:matches(require('luasnip.util.util').get_current_line_to_cursor()) + + local clear_region = { + from = { cursor[1], cursor[2] - #item.insertText }, + to = cursor, + } + if expand_params ~= nil and expand_params.clear_region ~= nil then + clear_region = expand_params.clear_region + elseif expand_params ~= nil and expand_params.trigger ~= nil then + clear_region = { + from = { cursor[1], cursor[2] - #expand_params.trigger }, + to = cursor, + } + end + + luasnip.snip_expand(snip, { expand_params = expand_params, clear_region = clear_region }) +end + +function source:reload() self.items_cache = {} end + +return source diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/snippets/mini_snippets.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/snippets/mini_snippets.lua new file mode 100644 index 0000000..3923f20 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/snippets/mini_snippets.lua @@ -0,0 +1,143 @@ +--- @module 'mini.snippets' + +--- @class blink.cmp.MiniSnippetsSourceOptions +--- @field use_items_cache? boolean completion items are cached using default mini.snippets context + +--- @class blink.cmp.MiniSnippetsSource : blink.cmp.Source +--- @field config blink.cmp.MiniSnippetsSourceOptions +--- @field items_cache table<string, blink.cmp.CompletionItem[]> + +--- @class blink.cmp.MiniSnippetsSnippet +--- @field prefix string string snippet identifier. +--- @field body string string snippet content with appropriate syntax. +--- @field desc string string snippet description in human readable form. + +--- @type blink.cmp.MiniSnippetsSource +--- @diagnostic disable-next-line: missing-fields +local source = {} + +local defaults_config = { + --- Whether to use a cache for completion items + use_items_cache = true, +} + +function source.new(opts) + local config = vim.tbl_deep_extend('keep', opts, defaults_config) + vim.validate({ + use_items_cache = { config.use_items_cache, 'boolean' }, + }) + + local self = setmetatable({}, { __index = source }) + self.config = config + self.items_cache = {} + return self +end + +function source:enabled() + ---@diagnostic disable-next-line: undefined-field + return _G.MiniSnippets ~= nil -- ensure that user has explicitly setup mini.snippets +end + +local function to_completion_items(snippets) + local result = {} + + for _, snip in ipairs(snippets) do + --- @type lsp.CompletionItem + local item = { + kind = require('blink.cmp.types').CompletionItemKind.Snippet, + label = snip.prefix, + insertText = snip.prefix, + insertTextFormat = vim.lsp.protocol.InsertTextFormat.PlainText, + data = { snip = snip }, + } + table.insert(result, item) + end + return result +end + +-- NOTE: Completion items are cached by default using the default 'mini.snippets' context +-- +-- vim.b.minisnippets_config can contain buffer-local snippets. +-- a buffer can contain code in multiple languages +-- +-- See :h MiniSnippets.default_prepare +-- +-- Return completion items produced from snippets either directly or from cache +local function get_completion_items(cache) + if not cache then return to_completion_items(MiniSnippets.expand({ match = false, insert = false })) end + + -- Compute cache id + local _, context = MiniSnippets.default_prepare({}) + local id = 'buf=' .. context.buf_id .. ',lang=' .. context.lang + + -- Return the completion items for this context from cache + if cache[id] then return cache[id] end + + -- Retrieve all raw snippets in context and transform into completion items + local snippets = MiniSnippets.expand({ match = false, insert = false }) + --- @cast snippets table + local items = to_completion_items(vim.deepcopy(snippets)) + cache[id] = items + + return items +end + +function source:get_completions(ctx, callback) + local cache = self.config.use_items_cache and self.items_cache or nil + + --- @type blink.cmp.CompletionItem[] + local items = get_completion_items(cache) + callback({ + is_incomplete_forward = false, + is_incomplete_backward = false, + items = items, + context = ctx, + ---@diagnostic disable-next-line: missing-return + }) +end + +function source:resolve(item, callback) + --- @type blink.cmp.MiniSnippetsSnippet + local snip = item.data.snip + + local desc = snip.desc + if desc and not item.documentation then + item.documentation = { + kind = 'markdown', + value = table.concat(vim.lsp.util.convert_input_to_markdown_lines(desc), '\n'), + } + end + + local detail = snip.body + if not item.detail then + if type(detail) == 'table' then detail = table.concat(detail, '\n') end + item.detail = detail + end + + callback(item) +end + +function source:execute(_, item) + -- Remove the word inserted by blink and insert snippet + -- It's safe to assume that mode is insert during completion + + --- @type blink.cmp.MiniSnippetsSnippet + local snip = item.data.snip + + local cursor = vim.api.nvim_win_get_cursor(0) + cursor[1] = cursor[1] - 1 -- nvim_buf_set_text: line is zero based + local start_col = cursor[2] - #item.insertText + vim.api.nvim_buf_set_text(0, cursor[1], start_col, cursor[1], cursor[2], {}) + + local insert = MiniSnippets.config.expand.insert or MiniSnippets.default_insert + ---@diagnostic disable-next-line: missing-return + insert({ body = snip.body }) -- insert at cursor +end + +-- For external integrations to force reloading the snippets +function source:reload() + MiniSnippets.setup(MiniSnippets.config) + self.items_cache = {} +end + +return source diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/snippets/utils.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/snippets/utils.lua new file mode 100644 index 0000000..f0903b7 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/sources/snippets/utils.lua @@ -0,0 +1,89 @@ +local utils = { + parse_cache = {}, +} + +--- Parses the json file and notifies the user if there's an error +---@param path string +---@param json string +function utils.parse_json_with_error_msg(path, json) + local ok, parsed = pcall(vim.json.decode, json) + if not ok then + vim.notify( + 'Failed to parse json file "' .. path .. '" for blink.cmp snippets. Error: ' .. parsed, + vim.log.levels.ERROR, + { title = 'blink.cmp' } + ) + return {} + end + return parsed +end + +---@type fun(path: string): string|nil +function utils.read_file(path) + local file = io.open(path, 'r') + if not file then return nil end + local content = file:read('*a') + file:close() + return content +end + +---@type fun(input: string): vim.snippet.Node<vim.snippet.SnippetData>|nil +function utils.safe_parse(input) + if utils.parse_cache[input] then return utils.parse_cache[input] end + + local safe, parsed = pcall(vim.lsp._snippet_grammar.parse, input) + if not safe then return nil end + + utils.parse_cache[input] = parsed + return parsed +end + +---@type fun(snippet: blink.cmp.Snippet, fallback: string): table +function utils.read_snippet(snippet, fallback) + local snippets = {} + local prefix = snippet.prefix or fallback + local description = snippet.description or fallback + local body = snippet.body + + if type(description) == 'table' then description = vim.fn.join(description, '') end + + if type(prefix) == 'table' then + for _, p in ipairs(prefix) do + snippets[p] = { + prefix = p, + body = body, + description = description, + } + end + else + snippets[prefix] = { + prefix = prefix, + body = body, + description = description, + } + end + return snippets +end + +function utils.get_tab_stops(snippet) + local expanded_snippet = require('blink.cmp.sources.snippets.utils').safe_parse(snippet) + if not expanded_snippet then return end + + local tabstops = {} + local grammar = require('vim.lsp._snippet_grammar') + local line = 1 + local character = 1 + for _, child in ipairs(expanded_snippet.data.children) do + local lines = tostring(child) == '' and {} or vim.split(tostring(child), '\n') + line = line + math.max(#lines - 1, 0) + character = #lines == 0 and character or #lines > 1 and #lines[#lines] or (character + #lines[#lines]) + if child.type == grammar.NodeType.Placeholder or child.type == grammar.NodeType.Tabstop then + table.insert(tabstops, { index = child.data.tabstop, line = line, character = character }) + end + end + + table.sort(tabstops, function(a, b) return a.index < b.index end) + return tabstops +end + +return utils diff --git a/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/types.lua b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/types.lua new file mode 100644 index 0000000..3904f9d --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/types.lua @@ -0,0 +1,74 @@ +--- @alias blink.cmp.Mode 'cmdline' | 'default' + +--- @class blink.cmp.CompletionItem : lsp.CompletionItem +--- @field documentation? string | { kind: lsp.MarkupKind, value: string, render?: blink.cmp.SourceRenderDocumentation } +--- @field score_offset? number +--- @field source_id string +--- @field source_name string +--- @field cursor_column number +--- @field client_id? number + +--- @class blink.cmp.SourceRenderDocumentationOpts +--- @field item blink.cmp.CompletionItem +--- @field window blink.cmp.Window +--- @field default_implementation fun(opts: blink.cmp.RenderDetailAndDocumentationOptsPartial) + +--- @alias blink.cmp.SourceRenderDocumentation fun(opts: blink.cmp.SourceRenderDocumentationOpts) + +return { + -- some plugins mutate the vim.lsp.protocol.CompletionItemKind table + -- so we use our own copy + CompletionItemKind = { + 'Text', + 'Method', + 'Function', + 'Constructor', + 'Field', + 'Variable', + 'Class', + 'Interface', + 'Module', + 'Property', + 'Unit', + 'Value', + 'Enum', + 'Keyword', + 'Snippet', + 'Color', + 'File', + 'Reference', + 'Folder', + 'EnumMember', + 'Constant', + 'Struct', + 'Event', + 'Operator', + 'TypeParameter', + + Text = 1, + Method = 2, + Function = 3, + Constructor = 4, + Field = 5, + Variable = 6, + Class = 7, + Interface = 8, + Module = 9, + Property = 10, + Unit = 11, + Value = 12, + Enum = 13, + Keyword = 14, + Snippet = 15, + Color = 16, + File = 17, + Reference = 18, + Folder = 19, + EnumMember = 20, + Constant = 21, + Struct = 22, + Event = 23, + Operator = 24, + TypeParameter = 25, + }, +} diff --git a/mut/neovim/pack/plugins/start/blink.cmp/plugin/blink-cmp.lua b/mut/neovim/pack/plugins/start/blink.cmp/plugin/blink-cmp.lua new file mode 100644 index 0000000..3b7c46a --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/plugin/blink-cmp.lua @@ -0,0 +1,5 @@ +if vim.fn.has('nvim-0.11') == 1 and vim.lsp.config then + vim.lsp.config('*', { + capabilities = require('blink.cmp').get_lsp_capabilities(), + }) +end diff --git a/mut/neovim/pack/plugins/start/blink.cmp/repro.lua b/mut/neovim/pack/plugins/start/blink.cmp/repro.lua new file mode 100644 index 0000000..ec4bdb0 --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/repro.lua @@ -0,0 +1,34 @@ +-- Run with `nvim -u repro.lua` + +vim.env.LAZY_STDPATH = '.repro' +load(vim.fn.system('curl -s https://raw.githubusercontent.com/folke/lazy.nvim/main/bootstrap.lua'))() + +---@diagnostic disable-next-line: missing-fields +require('lazy.minit').repro({ + spec = { + { + 'saghen/blink.cmp', + -- please test on `main` if possible + -- otherwise, remove this line and set `version = '*'` + build = 'cargo build --release', + opts = {}, + }, + { + 'neovim/nvim-lspconfig', + opts = { + servers = { + lua_ls = {}, + }, + }, + config = function(_, opts) + local lspconfig = require('lspconfig') + for server, config in pairs(opts.servers) do + -- passing config.capabilities to blink.cmp merges with the capabilities in your + -- `opts[server].capabilities, if you've defined it + config.capabilities = require('blink.cmp').get_lsp_capabilities() + lspconfig[server].setup(config) + end + end, + }, + }, +}) diff --git a/mut/neovim/pack/plugins/start/blink.cmp/rust-toolchain.toml b/mut/neovim/pack/plugins/start/blink.cmp/rust-toolchain.toml new file mode 100644 index 0000000..5d56faf --- /dev/null +++ b/mut/neovim/pack/plugins/start/blink.cmp/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" |
