Compare commits

..

4 commits
0.x ... fix/26

Author SHA1 Message Date
e7263a1b50 fix: exscpe test when passing it to vim syntax match
This was causing an issue an invalid regex. Now we are escapeing the
text to make it a valid regex.

There is also a small improvement where we no longer try and highlight
matched words if the "text" is empty.

Ref: #26
2022-10-06 07:45:58 +01:00
03ab57da3b test: add more mocks 2022-10-06 07:44:12 +01:00
f0baf7d480 test: improve style of the test output
Add more space around the text, also make the text a readable color on
dark backgrounds.
2022-10-06 07:41:44 +01:00
3e7cd988b5 test: fix the after_each hook not getting called
When writing tests the `after_each` function was never getting called
due to the `before_each` function getting called instead
2022-10-06 07:32:46 +01:00
66 changed files with 1550 additions and 1664 deletions

3
.clang-format Normal file
View file

@ -0,0 +1,3 @@
Language: Cpp
BasedOnStyle: Google
ColumnLimit: 0

20
.clang-tidy Normal file
View file

@ -0,0 +1,20 @@
Checks: '
-*,
google-*,
-google-runtime-references,
-google-readability-avoid-underscore-in-googletest-name,
llvm-include-order,
llvm-namespace-comment,
misc-throw-by-value-catch-by-reference,
modernize*,
-modernize-use-trailing-return-type,
readability-container-size-empty,
'
WarningsAsErrors: '*'
HeaderFilterRegex: './src/**/*'
CheckOptions:
- key: google-readability-braces-around-statements.ShortStatementLines
value: '3'

View file

@ -1,24 +0,0 @@
---
name: "\U0001F41B Bug report"
about: Create a report to help us improve
labels: 'bug'
---
## Description
<!-- Please add a detailed description of the issue you are having -->
## Expected Behaviour
<!-- Please describe the way you think this should work -->
## Actual Behaviour
<!-- Please describe the way it is currently working -->
## Steps to Reproduce the Problem
<!-- Please add all the steps you need to take to reproduce the issue -->
1)
2)

View file

@ -1,33 +0,0 @@
---
name: "\U0001F680 Feature request"
about: Suggest an idea for this project
labels: 'enhancement'
---
## Problem to solve
<!-- What is the user problem you are trying to solve with this issue? -->
## Proposal
<!--
Use this section to explain the feature and how it will work. It can be helpful
to add technical details and design proposals.
-->
## Further details
<!--
Include use cases, benefits, goals, or any other details that will help us
understand the problem better.
-->
## Links / References
<!-- Add any links or references to support this request. -->

20
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,20 @@
version: 2
updates:
- package-ecosystem: cargo
directory: '/'
schedule:
interval: daily
commit-message:
prefix: 'chore:'
assignees:
- 'AdeAttwood'
- package-ecosystem: github-actions
directory: '/'
schedule:
interval: daily
commit-message:
prefix: 'chore:'
assignees:
- 'AdeAttwood'

View file

@ -5,12 +5,26 @@ on:
pull_request: { branches: ["0.x"] }
jobs:
commits:
name: Commitlint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 1000
- name: Lint commits
uses: docker://registry.k1.zportal.co.uk/practically-oss/conventional-tools:0.x
with:
args: conventional-tools commitlint -l1 -f39febd82e236a9c79f5b408e98cbd20410f11e9e
luacheck:
name: Luacheck
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Install luarocks
run: sudo apt update && sudo apt install -y luarocks
@ -26,59 +40,57 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Run stylua
run: npx @johnnymorganz/stylua-bin --check .
uses: JohnnyMorganz/stylua-action@v1.1.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --check .
clang-format:
name: Clang Format
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Dependencies
run: sudo apt-get update && sudo apt-get install --no-install-recommends -y clang-format findutils
- name: Run clang format
run: find ./cpp -name "*.cpp" -o -name "*.hpp" | xargs clang-format -Werror --dry-run
cargo-format:
name: Cargo Format
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Set up rust
uses: dtolnay/rust-toolchain@stable
- name: Run cargo format
uses: actions-rs/cargo@v1
with:
components: rustfmt
- name: Lint
run: cargo fmt --check
command: fmt
args: --all -- --check
test:
name: Build and test
strategy:
matrix:
nvim-version: ["v0.9.5", "stable", "nightly"]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Set up rust
uses: dtolnay/rust-toolchain@stable
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Install dependencies
run: sudo apt update && sudo apt install -y build-essential luarocks
- name: Install busted
run: sudo luarocks install busted
- name: Install neovim
run: |
test -d _neovim || {
mkdir -p _neovim
curl -sL "https://github.com/neovim/neovim/releases/download/${{ matrix.nvim-version }}/nvim-linux64.tar.gz" | tar xzf - --strip-components=1 -C "${PWD}/_neovim"
}
run: sudo apt update && sudo apt install -y luajit build-essential
- name: Build
run: cargo build --release
- name: Run tests
run: |
export PATH="${PWD}/_neovim/bin:${PATH}"
export VIM="${PWD}/_neovim/share/nvim/runtime"
nvim --version
nvim -l ./scripts/busted.lua -o TAP ./lua/ivy/ 2> /dev/null
- name: Test
run: find lua -name "*_test.lua" | xargs luajit scripts/test.lua

View file

@ -1,21 +0,0 @@
name: Conventional Tools Commitlint
on:
push: { branches: ["0.x"] }
pull_request: { branches: ["0.x"] }
jobs:
commits:
name: Commitlint
runs-on: ubuntu-latest
container: practically/conventional-tools:1.x@sha256:a3e98697743d8801c694b92553da733aff0fbae6bf92876b13c92343a569f049
steps:
- name: Checkout
uses: actions/checkout@v4
with: {fetch-depth: 1000}
- name: Git safe.directory
run: git config --global --add safe.directory $PWD
- name: Lint commits
run: conventional-tools commitlint -l1 -f39febd82e236a9c79f5b408e98cbd20410f11e9e

View file

@ -10,12 +10,9 @@ self = false
read_globals = {
"vim",
"it",
"after",
"after_each",
"assert",
"before",
"before_each",
"describe",
"it",
"spy",
}

60
CMakeLists.txt Normal file
View file

@ -0,0 +1,60 @@
cmake_minimum_required(VERSION 3.16)
set(PROJECT_VERSION_NAME "v0.0.1")
# Split and sanatize the project version so it can be uses as pars and used as
# the project version "v1.1.1" is not a valida version number
string(REPLACE "v" "" PROJECT_VERSION ${PROJECT_VERSION_NAME})
string(REPLACE "." ";" VERSION_LIST ${PROJECT_VERSION})
list(GET VERSION_LIST 0 PROJECT_VERSION_MAJOR)
list(GET VERSION_LIST 1 PROJECT_VERSION_MINOR)
list(GET VERSION_LIST 2 PROJECT_VERSION_PATCH)
project ("Ivy" VERSION ${PROJECT_VERSION})
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Set the build type if its not test
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release")
endif()
# Ensure the build type is valid
if(NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Debug" AND
NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Release" AND
NOT "${CMAKE_BUILD_TYPE}" STREQUAL "MinSizeRel" AND
NOT "${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo")
message(FATAL_ERROR "Unknown build type \"${CMAKE_BUILD_TYPE}\". Allowed values are Debug, Release, RelWithDebInfo, and MinSizeRel.")
endif()
# detect operating system and host processor
message(STATUS "We are on a ${CMAKE_SYSTEM_NAME} system")
message(STATUS "The host processor is ${CMAKE_HOST_SYSTEM_PROCESSOR}")
# Place binaries and libraries according to GNU standards. For example
# executables created with `add_executable` will be built into the `bin`
# directory
include(GNUInstallDirs)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})
# Set the default compiler flags for GNU
if(CMAKE_CXX_COMPILER_ID MATCHES GNU)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wunreachable-code -Wno-unknown-pragmas -Wno-sign-compare -Wwrite-strings -Wno-unused")
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g3")
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG")
endif()
find_package(PkgConfig REQUIRED)
find_package(Threads REQUIRED)
file(GLOB_RECURSE IVY_HEADER "${CMAKE_CURRENT_LIST_DIR}/cpp/*.hpp")
file(GLOB_RECURSE IVY_SOURCE "${CMAKE_CURRENT_LIST_DIR}/cpp/*.cpp")
list(FILTER IVY_SOURCE EXCLUDE REGEX "_test\\.cpp$")
list(FILTER IVY_SOURCE EXCLUDE REGEX "cli\\.cpp$")
add_library(ivy SHARED ${IVY_SOURCE} ${IVY_HEADER})
target_link_libraries(ivy Threads::Threads)
add_executable(ivycli ${IVY_SOURCE} ${IVY_HEADER} ${CMAKE_CURRENT_LIST_DIR}/cpp/cli.cpp)
target_link_libraries(ivycli Threads::Threads)

487
Cargo.lock generated
View file

@ -4,9 +4,9 @@ version = 3
[[package]]
name = "aho-corasick"
version = "1.1.3"
version = "0.7.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
dependencies = [
"memchr",
]
@ -18,32 +18,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]]
name = "anstyle"
version = "1.0.9"
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8365de52b16c035ff4fcafe0092ba9390540e3e352870ac09933bebcaa2c8c56"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.4.0"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bstr"
version = "1.10.0"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c"
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
dependencies = [
"memchr",
"serde",
]
[[package]]
name = "bumpalo"
version = "3.16.0"
version = "3.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d"
[[package]]
name = "cast"
@ -59,9 +69,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "ciborium"
version = "0.2.2"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f"
dependencies = [
"ciborium-io",
"ciborium-ll",
@ -70,15 +80,15 @@ dependencies = [
[[package]]
name = "ciborium-io"
version = "0.2.2"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369"
[[package]]
name = "ciborium-ll"
version = "0.2.2"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b"
dependencies = [
"ciborium-io",
"half",
@ -86,44 +96,40 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.20"
version = "3.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.5.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54"
dependencies = [
"anstyle",
"bitflags",
"clap_lex",
"indexmap",
"textwrap",
]
[[package]]
name = "clap_lex"
version = "0.7.2"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
dependencies = [
"os_str_bytes",
]
[[package]]
name = "criterion"
version = "0.5.1"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb"
dependencies = [
"anes",
"atty",
"cast",
"ciborium",
"clap",
"criterion-plot",
"is-terminal",
"itertools",
"lazy_static",
"num-traits",
"once_cell",
"oorandom",
"plotters",
"rayon",
@ -146,41 +152,61 @@ dependencies = [
]
[[package]]
name = "crossbeam-deque"
version = "0.8.5"
name = "crossbeam-channel"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset",
"once_cell",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.20"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]]
name = "either"
version = "1.13.0"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "fuzzy-matcher"
@ -193,74 +219,80 @@ dependencies = [
[[package]]
name = "globset"
version = "0.4.15"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19"
checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a"
dependencies = [
"aho-corasick",
"bstr",
"fnv",
"log",
"regex-automata",
"regex-syntax",
"regex",
]
[[package]]
name = "half"
version = "2.4.1"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
dependencies = [
"cfg-if",
"crunchy",
]
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hermit-abi"
version = "0.4.0"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "ignore"
version = "0.4.23"
version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b"
checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
"globset",
"lazy_static",
"log",
"memchr",
"regex-automata",
"regex",
"same-file",
"thread_local",
"walkdir",
"winapi-util",
]
[[package]]
name = "is-terminal"
version = "0.4.13"
name = "indexmap"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b"
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
dependencies = [
"hermit-abi",
"libc",
"windows-sys 0.52.0",
"autocfg",
"hashbrown",
]
[[package]]
name = "itertools"
version = "0.10.5"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
checksum = "d8bf247779e67a9082a4790b45e71ac7cfd1321331a5c856a74a9faebdab78d0"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.11"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
[[package]]
name = "ivy"
@ -269,62 +301,97 @@ dependencies = [
"criterion",
"fuzzy-matcher",
"ignore",
"lazy_static",
"rayon",
]
[[package]]
name = "js-sys"
version = "0.3.72"
version = "0.3.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9"
checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.161"
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.132"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
[[package]]
name = "log"
version = "0.4.22"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.7.4"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "num-traits"
version = "0.2.19"
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.20.2"
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "once_cell"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0"
[[package]]
name = "oorandom"
version = "11.1.4"
version = "11.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9"
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]]
name = "os_str_bytes"
version = "6.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff"
[[package]]
name = "plotters"
version = "0.3.7"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97"
dependencies = [
"num-traits",
"plotters-backend",
@ -335,74 +402,66 @@ dependencies = [
[[package]]
name = "plotters-backend"
version = "0.3.7"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142"
[[package]]
name = "plotters-svg"
version = "0.3.7"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f"
dependencies = [
"plotters-backend",
]
[[package]]
name = "proc-macro2"
version = "1.0.89"
version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.37"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rayon"
version = "1.10.0"
version = "1.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d"
dependencies = [
"autocfg",
"crossbeam-deque",
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.12.1"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-utils",
"num_cpus",
]
[[package]]
name = "regex"
version = "1.11.1"
version = "1.6.0"
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.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
dependencies = [
"aho-corasick",
"memchr",
@ -411,15 +470,15 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.8.5"
version = "0.6.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
[[package]]
name = "ryu"
version = "1.0.18"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
[[package]]
name = "same-file"
@ -431,19 +490,25 @@ dependencies = [
]
[[package]]
name = "serde"
version = "1.0.213"
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.144"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.213"
version = "1.0.144"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5"
checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00"
dependencies = [
"proc-macro2",
"quote",
@ -452,21 +517,20 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.132"
version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "syn"
version = "2.0.85"
version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56"
checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
dependencies = [
"proc-macro2",
"quote",
@ -474,12 +538,17 @@ dependencies = [
]
[[package]]
name = "thread_local"
version = "1.1.8"
name = "textwrap"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16"
[[package]]
name = "thread_local"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
dependencies = [
"cfg-if",
"once_cell",
]
@ -495,36 +564,36 @@ dependencies = [
[[package]]
name = "unicode-ident"
version = "1.0.13"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd"
[[package]]
name = "walkdir"
version = "2.5.0"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
dependencies = [
"same-file",
"winapi",
"winapi-util",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.95"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
dependencies = [
"cfg-if",
"once_cell",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.95"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
dependencies = [
"bumpalo",
"log",
@ -537,9 +606,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.95"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@ -547,9 +616,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.95"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
dependencies = [
"proc-macro2",
"quote",
@ -560,107 +629,47 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.95"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
[[package]]
name = "web-sys"
version = "0.3.72"
version = "0.3.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112"
checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[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-util"
version = "0.1.9"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"windows-sys 0.59.0",
"winapi",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[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"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View file

@ -9,12 +9,13 @@ crate-type = ["cdylib", "rlib"]
path = "rust/lib.rs"
[dependencies]
ignore = "0.4.22"
ignore = "0.4.18"
fuzzy-matcher = "0.3.7"
rayon = "1.10.0"
lazy_static = "1.4.0"
rayon = "1.5.3"
[dev-dependencies]
criterion = "0.5.1"
criterion = "0.4.0"
[profile.release]
opt-level = 3

188
README.md
View file

@ -1,9 +1,6 @@
<div align="center">
<img src="assets/logo.svg" alt="ivy.vim" />
<br />
<br />
# ivy.nvim
An [ivy-mode](https://github.com/abo-abo/swiper#ivy) port to neovim. Ivy is a
generic completion mechanism for ~~Emacs~~ Nvim
@ -20,90 +17,7 @@ git clone https://github.com/AdeAttwood/ivy.nvim ~/.config/nvim/pack/bundle/star
### Plugin managers
Using [lazy.nvim](https://github.com/folke/lazy.nvim)
```lua
{
"AdeAttwood/ivy.nvim",
build = "cargo build --release",
},
```
Using [mini.deps](https://github.com/echasnovski/mini.deps)
```lua
local deps = require "mini.deps"
deps.later(function() -- Or `deps.now` if you want this to be loaded immediately
local build = function(args)
local obj = vim
.system(
{ "cargo", "build", "--release", string.format("%s%s%s", "--manifest-path=", args.path, "/Cargo.toml") },
{ text = true }
)
:wait()
vim.print(vim.inspect(obj))
end
deps.add {
source = "AdeAttwood/ivy.nvim",
hooks = {
post_install = build,
post_checkout = build,
},
}
end)
```
TODO: Add more plugin managers
### Setup / Configuration
Ivy can be configured with minimal config that will give you all the defaults
provided by Ivy.
```lua
require("ivy").setup()
```
With Ivy you can configure your own backends.
```lua
require("ivy").setup {
backends = {
-- A backend module that will be registered
"ivy.backends.buffers",
-- Using a table so you can configure a custom keymap overriding the
-- default one.
{ "ivy.backends.files", { keymap = "<C-p>" } },
},
-- Set mappings of your own, you can use the action `key` to define the
-- action you want.
mappings = {
["<CR>"] = "complete"
}
}
```
When defining config overrides in the setup function, this will overwrite any
default config, not merge it. To merge the configuration you can use the
`vim.tbl_extend` use the default configuration and add any extra.
```lua
require("ivy").setup {
mappings = vim.tbl_extend("force", config:get { "mappings" }, {
["<esc>"] = "destroy",
}),
}
```
The `setup` function can only be called once, if its called a second time any
backends or config will not be used. Ivy does expose the `register_backend`
function, this can be used to load backends before or after the setup function
is called.
```lua
require("ivy").register_backend "ivy.backends.files"
```
TODO: Add docs in the plugin managers I don't use any
### Compiling
@ -127,88 +41,32 @@ error: linker `cc` not found
= note: No such file or directory (os error 2)
```
To configure auto compiling you can use the [`post-merge`](./post-merge.sample)
git hook to compile the library automatically when you update the package. This
works well if you are managing your plugins via git. For an example
installation you can run the following command. **NOTE:** This will overwrite
any post-merge hooks you have already installed.
```sh
cp ./post-merge.sample ./.git/hooks/post-merge
```
## Features
### Backends
### Commands
A backend is a module that will provide completion candidates for the UI to
show. It will also provide functionality when actions are taken. The Command
and Key Map are the default options provided by the backend, they can be
customized when you register it.
A command can be run that will launch the completion UI
| Module | Command | Default Key Map | Description |
| ------------------------------------ | ------------------ | --------------- | ----------------------------------------------------------- |
| `ivy.backends.files` | IvyFd | \<leader\>p | Find files in your project with a custom rust file finder |
| `ivy.backends.ag` | IvyAg | \<leader\>/ | Find content in files using the silver searcher |
| `ivy.backends.rg` | IvyRg | \<leader\>/ | Find content in files using ripgrep cli tool |
| `ivy.backends.buffers` | IvyBuffers | \<leader\>b | Search though open buffers |
| `ivy.backends.lines` | IvyLines | | Search the lines in the current buffer |
| `ivy.backends.lsp-workspace-symbols` | IvyWorkspaceSymbol | | Search for workspace symbols using the lsp workspace/symbol |
| Command | Key Map | Description |
| ---------- | ----------- | ------------------------------------------------------ |
| IvyFd | \<leader\>p | Find files in your project with the fd cli file finder |
| IvyAg | \<leader\>/ | Find content in files using the silver searcher |
| IvyBuffers | \<leader\>b | Search though open buffers |
| IvyLines | | Search the lines in the current buffer |
### Actions
Action can be run on selected candidates provide functionality
| Action | Key | Default Key Map | Description |
| ------------------- | --------------------- | --------------- | ---------------------------------------------------------------- |
| Complete | `complete` | \<CR\> | Run the completion function, usually this will be opening a file |
| Vertical Split | `vsplit` | \<C-v\> | Run the completion function in a new vertical split |
| Split | `split` | \<C-s\> | Run the completion function in a new split |
| Destroy | `destroy` | \<C-c\> | Close the results window |
| Clear | `clear` | \<C-u\> | Clear the results window |
| Delete word | `delete_word` | \<C-w\> | Delete the word under the cursor |
| Next | `next` | \<C-n\> | Move to the next candidate |
| Previous | `previous` | \<C-p\> | Move to the previous candidate |
| Next Checkpoint | `next_checkpoint` | \<C-M-n\> | Move to the next candidate and keep Ivy open and focussed |
| Previous Checkpoint | `previous_checkpoint` | \<C-M-n\> | Move to the previous candidate and keep Ivy open and focussed |
| Action | Description |
| -------------- | ------------------------------------------------------------------------------ |
| Complete | Run the completion function, usually this will be opening a file |
| Peek | Run the completion function on a selection, but don't close the results window |
| Vertical Split | Run the completion function in a new vertical split |
| Split | Run the completion function in a new split |
## API
### ivy.run
The `ivy.run` function is the core function in the plugin, it will launch the
completion window and display the items from your items function. When the
users accept one of the candidates with an [action](#actions), it will call the
callback function to in most cases open the item in the desired location.
```lua
---@param name string
---@param items fun(input: string): { content: string }[] | string
---@param callback fun(result: string, action: string)
vim.ivy.run = function(name, items, callback) end
```
#### Name `string`
The name is the display name for the command and will be the name of the buffer
in the completion window
#### Items `fun(input: string): { content: string }[] | string`
The items function is a function that will return the candidates to display in
the completion window. This can return a string where each line will be a
completion item. Or an array of tables where the `content` will be the
completion item.
#### Callback `fun(result: string, action: string)`
The function that will run when the user selects a completion item. Generally
this will open the item in the desired location. For example, in the file
finder with will open the file in a new buffer. If the user selects the
vertical split action it will open the buffer in a new `vsplit`
#### Example
```lua
vim.ivy.run(
-- The name given to the results window and displayed to the user
@ -216,18 +74,10 @@ vertical split action it will open the buffer in a new `vsplit`
-- Call back function to get all the candidates that will be displayed in
-- the results window, The `input` will be passed in, so you can filter
-- your results with the value from the prompt
function(input)
return {
{ content = "One" },
{ content = "Two" },
{ content = "Three" },
}
end,
-- Action callback that will be called on the completion or checkpoint actions.
function(input) return { "One", "Two", Three } end,
-- Action callback that will be called on the completion or peek actions.
-- The currently selected item is passed in as the result.
function(result)
vim.cmd("edit " .. result)
end
function(result) vim.cmd("edit " .. result) end
)
```

View file

@ -1,5 +0,0 @@
<svg width="307" height="78" viewBox="0 0 307 78" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="39" cy="39" r="35" stroke="#FF7D1F" stroke-width="8"/>
<path d="M37.432 51.704L14.488 41.432V35.048L37.432 24.824V32.024L22.696 38.216L37.432 44.456V51.704ZM40.4099 51.704V44.456L55.0979 38.216L40.4099 32.024V24.824L63.3539 35.048V41.432L40.4099 51.704Z" fill="#FF7D1F"/>
<path d="M93.92 53V22.088H104.24V53H93.92ZM118.972 53L107.116 22.088H117.868L124.348 40.904L130.876 22.088H141.676L129.772 53H118.972ZM153.034 53V41.72L141.898 22.088H152.65L158.218 32.84L163.786 22.088H174.538L163.354 41.72V53H153.034ZM176.444 53V45.704H184.46V53H176.444ZM187.342 53V22.088H196.03L206.206 36.344V22.088H216.526V53H207.742L197.662 38.792V53H187.342ZM231.285 53L219.429 22.088H230.181L236.661 40.904L243.189 22.088H253.989L242.085 53H231.285ZM256.858 53V22.088H267.178V53H256.858ZM271.014 53V22.088H280.902L287.91 36.344L294.822 22.088H304.71V53H294.39V39.848L290.55 47.72H285.174L281.334 39.848V53H271.014Z" fill="#FF7D1F"/>
</svg>

Before

Width:  |  Height:  |  Size: 1 KiB

35
cpp/cli.cpp Normal file
View file

@ -0,0 +1,35 @@
#include <filesystem>
#include <iostream>
#include <optional>
#include <regex>
#include <string>
#include "./file_scanner.hpp"
#include "./sorter.hpp"
int main(int argc, char* argv[]) {
std::vector<std::string> args;
args.reserve(argc);
// Skip the first argument because that will be the programme name.
for (int i = 1; i < argc; i++) {
args.emplace_back(argv[i]);
}
if (args.empty()) {
std::cout << "Missing required search term" << std::endl;
return 1;
}
auto base_dir = std::filesystem::current_path();
std::string search = args.at(0);
auto sorter = ivy::Sorter(search);
auto scanner = ivy::FileScanner(base_dir);
std::regex pattern("([" + search + "])");
for (ivy::Match const& match : sorter.sort(scanner.scan())) {
std::cout << match.score << " " << std::regex_replace(match.content, pattern, "\033[1m$&\033[0m") << std::endl;
}
return 0;
}

35
cpp/file_scanner.hpp Normal file
View file

@ -0,0 +1,35 @@
#pragma once
#include <filesystem>
#include <string>
#include <vector>
namespace fs = std::filesystem;
namespace ivy {
class FileScanner {
std::string m_base_dir;
public:
explicit FileScanner(const std::string base_dir) : m_base_dir(base_dir) {}
std::vector<std::string> scan() {
std::vector<std::string> results;
for (const fs::directory_entry& dir_entry : fs::recursive_directory_iterator(m_base_dir)) {
fs::path path = dir_entry.path();
// TODO(ade): sort out some kind of ignore thing. This will be needed
// when we start adding wildcard ignore functionality
if (path.string().find(".git") != std::string::npos) {
continue;
}
if (dir_entry.is_regular_file()) {
results.emplace_back(fs::relative(path, m_base_dir));
}
}
return results;
}
};
} // namespace ivy

212
cpp/fts_fuzzy_match.hpp Normal file
View file

@ -0,0 +1,212 @@
// LICENSE
//
// This software is dual-licensed to the public domain and under the following
// license: you are granted a perpetual, irrevocable license to copy, modify,
// publish, and distribute this file as you see fit.
//
// VERSION
// 0.2.0 (2017-02-18) Scored matches perform exhaustive search for best score
// 0.1.0 (2016-03-28) Initial release
//
// AUTHOR
// Forrest Smith
//
// NOTES
// Compiling
// You MUST add '#define FTS_FUZZY_MATCH_IMPLEMENTATION' before including this header in ONE source file to create implementation.
//
// fuzzy_match_simple(...)
// Returns true if each character in pattern is found sequentially within str
//
// fuzzy_match(...)
// Returns true if pattern is found AND calculates a score.
// Performs exhaustive search via recursion to find all possible matches and match with highest score.
// Scores values have no intrinsic meaning. Possible score range is not normalized and varies with pattern.
// Recursion is limited internally (default=10) to prevent degenerate cases (pattern="aaaaaa" str="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
// Uses uint8_t for match indices. Therefore patterns are limited to 256 characters.
// Score system should be tuned for YOUR use case. Words, sentences, file names, or method names all prefer different tuning.
#ifndef FTS_FUZZY_MATCH_H
#define FTS_FUZZY_MATCH_H
#include <ctype.h> // ::tolower, ::toupper
#include <cstdint> // uint8_t
#include <cstdio>
#include <cstring> // memcpy
#include <iostream>
// Public interface
namespace fts {
static bool fuzzy_match_simple(char const* pattern, char const* str);
static bool fuzzy_match(char const* pattern, char const* str, int& outScore);
static bool fuzzy_match(char const* pattern, char const* str, int& outScore, uint8_t* matches, int maxMatches);
} // namespace fts
#ifdef FTS_FUZZY_MATCH_IMPLEMENTATION
namespace fts {
// Forward declarations for "private" implementation
namespace fuzzy_internal {
static bool fuzzy_match_recursive(const char* pattern, const char* str, int& outScore, const char* strBegin,
uint8_t const* srcMatches, uint8_t* newMatches, int maxMatches, int nextMatch,
int& recursionCount, int recursionLimit);
}
// Public interface
static bool fuzzy_match_simple(char const* pattern, char const* str) {
while (*pattern != '\0' && *str != '\0') {
if (tolower(*pattern) == tolower(*str))
++pattern;
++str;
}
return *pattern == '\0' ? true : false;
}
static bool fuzzy_match(char const* pattern, char const* str, int& outScore) {
uint8_t matches[256];
return fuzzy_match(pattern, str, outScore, matches, sizeof(matches));
}
static bool fuzzy_match(char const* pattern, char const* str, int& outScore, uint8_t* matches, int maxMatches) {
int recursionCount = 0;
int recursionLimit = 10;
return fuzzy_internal::fuzzy_match_recursive(pattern, str, outScore, str, nullptr, matches, maxMatches, 0, recursionCount, recursionLimit);
}
// Private implementation
static bool fuzzy_internal::fuzzy_match_recursive(const char* pattern, const char* str, int& outScore,
const char* strBegin, uint8_t const* srcMatches, uint8_t* matches, int maxMatches,
int nextMatch, int& recursionCount, int recursionLimit) {
// Count recursions
++recursionCount;
if (recursionCount >= recursionLimit)
return false;
// Detect end of strings
if (*pattern == '\0' || *str == '\0')
return false;
// Recursion params
bool recursiveMatch = false;
uint8_t bestRecursiveMatches[256];
int bestRecursiveScore = 0;
// Loop through pattern and str looking for a match
bool first_match = true;
while (*pattern != '\0' && *str != '\0') {
// Found match. We assume a space in the pattern is match so we can match paths better
if (tolower(*pattern) == tolower(*str) || *pattern == ' ') {
// Supplied matches buffer was too short
if (nextMatch >= maxMatches) {
return false;
}
// "Copy-on-Write" srcMatches into matches
if (first_match && srcMatches) {
memcpy(matches, srcMatches, nextMatch);
first_match = false;
}
// Recursive call that "skips" this match
uint8_t recursiveMatches[256];
int recursiveScore;
if (fuzzy_match_recursive(pattern, str + 1, recursiveScore, strBegin, matches, recursiveMatches, sizeof(recursiveMatches), nextMatch, recursionCount, recursionLimit)) {
// Pick best recursive score
if (!recursiveMatch || recursiveScore > bestRecursiveScore) {
memcpy(bestRecursiveMatches, recursiveMatches, 256);
bestRecursiveScore = recursiveScore;
}
recursiveMatch = true;
}
// Advance
matches[nextMatch++] = (uint8_t)(str - strBegin);
++pattern;
}
++str;
}
// Determine if full pattern was matched
bool matched = *pattern == '\0' ? true : false;
// Calculate score
if (matched) {
const int sequential_bonus = 15; // bonus for adjacent matches
const int separator_bonus = 30; // bonus if match occurs after a separator
const int camel_bonus = 30; // bonus if match is uppercase and prev is lower
const int first_letter_bonus = 15; // bonus if the first letter is matched
const int leading_letter_penalty = -5; // penalty applied for every letter in str before the first match
const int max_leading_letter_penalty = -15; // maximum penalty for leading letters
const int unmatched_letter_penalty = -1; // penalty for every letter that doesn't matter
// Iterate str to end
while (*str != '\0')
++str;
// Initialize score
outScore = 100;
// Apply leading letter penalty
int penalty = leading_letter_penalty * matches[0];
if (penalty < max_leading_letter_penalty)
penalty = max_leading_letter_penalty;
outScore += penalty;
// Apply unmatched penalty
int unmatched = (int)(str - strBegin) - nextMatch;
outScore += unmatched_letter_penalty * unmatched;
// Apply ordering bonuses
for (int i = 0; i < nextMatch; ++i) {
uint8_t currIdx = matches[i];
if (i > 0) {
uint8_t prevIdx = matches[i - 1];
// Sequential
if (currIdx == (prevIdx + 1))
outScore += sequential_bonus;
}
// Check for bonuses based on neighbor character value
if (currIdx > 0) {
// Camel case
char neighbor = strBegin[currIdx - 1];
char curr = strBegin[currIdx];
if (::islower(neighbor) && ::isupper(curr))
outScore += camel_bonus;
// Separator
bool neighborSeparator = neighbor == '_' || neighbor == ' ' || neighbor == '/' || neighbor == '-';
if (neighborSeparator)
outScore += separator_bonus;
} else {
// First letter
outScore += first_letter_bonus;
}
}
}
// Return best result
if (recursiveMatch && (!matched || bestRecursiveScore > outScore)) {
// Recursive score is better than "this"
memcpy(matches, bestRecursiveMatches, maxMatches);
outScore = bestRecursiveScore;
return true;
} else if (matched) {
// "this" score is better than recursive
return true;
} else {
// no match
return false;
}
}
} // namespace fts
#endif // FTS_FUZZY_MATCH_IMPLEMENTATION
#endif // FTS_FUZZY_MATCH_H

198
cpp/fuzzy_match.cpp Normal file
View file

@ -0,0 +1,198 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
// https://github.com/MaskRay/ccls/blob/master/src/fuzzy_match.cc
#include "fuzzy_match.hpp"
#include <ctype.h>
#include <stdio.h>
#include <algorithm>
#include <vector>
namespace ivy {
namespace {
enum CharClass { Other,
Lower,
Upper };
enum CharRole { None,
Tail,
Head };
CharClass getCharClass(int c) {
if (islower(c))
return Lower;
if (isupper(c))
return Upper;
return Other;
}
void calculateRoles(std::string_view s, int roles[], int *class_set) {
if (s.empty()) {
*class_set = 0;
return;
}
CharClass pre = Other, cur = getCharClass(s[0]), suc;
*class_set = 1 << cur;
auto fn = [&]() {
if (cur == Other)
return None;
// U(U)L is Head while U(U)U is Tail
return pre == Other || (cur == Upper && (pre == Lower || suc == Lower))
? Head
: Tail;
};
for (size_t i = 0; i < s.size() - 1; i++) {
suc = getCharClass(s[i + 1]);
*class_set |= 1 << suc;
roles[i] = fn();
pre = cur;
cur = suc;
}
roles[s.size() - 1] = fn();
}
} // namespace
int FuzzyMatcher::missScore(int j, bool last) {
int s = -3;
if (last)
s -= 10;
if (text_role[j] == Head)
s -= 10;
return s;
}
int FuzzyMatcher::matchScore(int i, int j, bool last) {
int s = 0;
// Case matching.
if (pat[i] == text[j]) {
s++;
// pat contains uppercase letters or prefix matching.
if ((pat_set & 1 << Upper) || i == j)
s++;
}
if (pat_role[i] == Head) {
if (text_role[j] == Head)
s += 30;
else if (text_role[j] == Tail)
s -= 10;
}
// Matching a tail while previous char wasn't matched.
if (text_role[j] == Tail && i && !last)
s -= 30;
// First char of pat matches a tail.
if (i == 0 && text_role[j] == Tail)
s -= 40;
return s;
}
FuzzyMatcher::FuzzyMatcher(std::string_view pattern, int sensitivity) {
calculateRoles(pattern, pat_role, &pat_set);
if (sensitivity == 1)
sensitivity = pat_set & 1 << Upper ? 2 : 0;
case_sensitivity = sensitivity;
size_t n = 0;
for (size_t i = 0; i < pattern.size(); i++)
if (pattern[i] != ' ') {
pat += pattern[i];
low_pat[n] = (char)::tolower(pattern[i]);
pat_role[n] = pat_role[i];
n++;
}
}
int FuzzyMatcher::match(std::string_view text, bool strict) {
if (pat.empty() != text.empty())
return kMinScore;
int n = int(text.size());
if (n > kMaxText)
return kMinScore + 1;
this->text = text;
for (int i = 0; i < n; i++)
low_text[i] = (char)::tolower(text[i]);
calculateRoles(text, text_role, &text_set);
if (strict && n && !!pat_role[0] != !!text_role[0])
return kMinScore;
dp[0][0][0] = dp[0][0][1] = 0;
for (int j = 0; j < n; j++) {
dp[0][j + 1][0] = dp[0][j][0] + missScore(j, false);
dp[0][j + 1][1] = kMinScore * 2;
}
for (int i = 0; i < int(pat.size()); i++) {
int(*pre)[2] = dp[i & 1];
int(*cur)[2] = dp[(i + 1) & 1];
cur[i][0] = cur[i][1] = kMinScore;
for (int j = i; j < n; j++) {
cur[j + 1][0] = std::max(cur[j][0] + missScore(j, false),
cur[j][1] + missScore(j, true));
// For the first char of pattern, apply extra restriction to filter bad
// candidates (e.g. |int| in |PRINT|)
cur[j + 1][1] = (case_sensitivity ? pat[i] == text[j]
: low_pat[i] == low_text[j] &&
(i || text_role[j] != Tail ||
pat[i] == text[j]))
? std::max(pre[j][0] + matchScore(i, j, false),
pre[j][1] + matchScore(i, j, true))
: kMinScore * 2;
}
}
// Enumerate the end position of the match in str. Each removed trailing
// character has a penulty.
int ret = kMinScore;
for (int j = pat.size(); j <= n; j++)
ret = std::max(ret, dp[pat.size() & 1][j][1] - 2 * (n - j));
return ret;
}
} // namespace ivy
#if 0
TEST_SUITE("fuzzy_match") {
bool Ranks(std::string_view pat, std::vector<const char*> texts) {
FuzzyMatcher fuzzy(pat, 0);
std::vector<int> scores;
for (auto text : texts)
scores.push_back(fuzzy.Match(text));
bool ret = true;
for (size_t i = 0; i < texts.size() - 1; i++)
if (scores[i] < scores[i + 1]) {
ret = false;
break;
}
if (!ret) {
for (size_t i = 0; i < texts.size(); i++)
printf("%s %d ", texts[i], scores[i]);
puts("");
}
return ret;
}
TEST_CASE("test") {
FuzzyMatcher fuzzy("", 0);
CHECK(fuzzy.Match("") == 0);
CHECK(fuzzy.Match("aaa") < 0);
// case
CHECK(Ranks("monad", {"monad", "Monad", "mONAD"}));
// initials
CHECK(Ranks("ab", {"ab", "aoo_boo", "acb"}));
CHECK(Ranks("CC", {"CamelCase", "camelCase", "camelcase"}));
CHECK(Ranks("cC", {"camelCase", "CamelCase", "camelcase"}));
CHECK(Ranks("c c", {"camelCase", "camel case", "CamelCase", "camelcase",
"camel ace"}));
CHECK(Ranks("Da.Te",
{"Data.Text", "Data.Text.Lazy", "Data.Aeson.Encoding.text"}));
CHECK(Ranks("foo bar.h", {"foo/bar.h", "foobar.h"}));
// prefix
CHECK(Ranks("is", {"isIEEE", "inSuf"}));
// shorter
CHECK(Ranks("ma", {"map", "many", "maximum"}));
CHECK(Ranks("print", {"printf", "sprintf"}));
// score(PRINT) = kMinScore
CHECK(Ranks("ast", {"ast", "AST", "INT_FAST16_MAX"}));
// score(PRINT) > kMinScore
CHECK(Ranks("Int", {"int", "INT", "PRINT"}));
}
}
#endif

37
cpp/fuzzy_match.hpp Normal file
View file

@ -0,0 +1,37 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <climits>
#include <string>
#include <string_view>
namespace ivy {
class FuzzyMatcher {
public:
constexpr static int kMaxPat = 100;
constexpr static int kMaxText = 200;
// Negative but far from INT_MIN so that intermediate results are hard to
// overflow.
constexpr static int kMinScore = INT_MIN / 4;
// 0: case-insensitive
// 1: case-folded, i.e. insensitive if no input character is uppercase.
// 2: case-sensitive
FuzzyMatcher(std::string_view pattern, int case_sensitivity);
int match(std::string_view text, bool strict);
private:
int case_sensitivity;
std::string pat;
std::string_view text;
int pat_set, text_set;
char low_pat[kMaxPat], low_text[kMaxText];
int pat_role[kMaxPat], text_role[kMaxText];
int dp[2][kMaxText + 1][2];
int matchScore(int i, int j, bool last);
int missScore(int j, bool last);
};
} // namespace ivy

44
cpp/lib.cpp Normal file
View file

@ -0,0 +1,44 @@
#include <cstring>
#include <map>
#include <string>
#include <vector>
#define FTS_FUZZY_MATCH_IMPLEMENTATION
#include "./file_scanner.hpp"
#include "./fts_fuzzy_match.hpp"
#include "./match.hpp"
#include "./sorter.hpp"
namespace ivy {
static std::map<std::string, std::vector<std::string>> file_cache;
}; // namespace ivy
extern "C" void ivy_init(const char* dir) {
auto scanner = ivy::FileScanner(dir);
ivy::file_cache[std::string(dir)] = scanner.scan();
}
extern "C" int ivy_match(const char* pattern, const char* text) {
int score = 0;
fts::fuzzy_match(pattern, text, score);
return score;
}
extern "C" char* ivy_files(const char* search, const char* base_dir) {
if (!ivy::file_cache.count(base_dir)) {
auto scanner = ivy::FileScanner(base_dir);
ivy::file_cache[std::string(base_dir)] = scanner.scan();
}
auto sorter = ivy::Sorter(search);
// TODO(ade): Sort out how this memory is freed. I am assuming its in lua
// land via ffi
auto* s = new std::string();
for (ivy::Match const& match : sorter.sort(ivy::file_cache.at(base_dir))) {
s->append(match.content + "\n");
}
return s->data();
}

14
cpp/match.hpp Normal file
View file

@ -0,0 +1,14 @@
#pragma once
#include <string>
namespace ivy {
struct Match {
int score;
std::string content;
};
static bool sort_match(const Match& a, const Match& b) { return a.score < b.score; }
} // namespace ivy

46
cpp/sorter.hpp Normal file
View file

@ -0,0 +1,46 @@
#pragma once
#define FTS_FUZZY_MATCH_IMPLEMENTATION
#include "./fts_fuzzy_match.hpp"
#include "./match.hpp"
#include "./thread_pool.hpp"
namespace ivy {
class Sorter {
ivy::ThreadPool m_thread_pool;
std::string m_term;
std::mutex m_matches_lock;
std::vector<Match> m_matches;
inline void add_entry(const std::string& file) {
int score = 0;
fts::fuzzy_match(m_term.c_str(), file.c_str(), score);
if (score > 50) {
std::unique_lock<std::mutex> lock(m_matches_lock);
m_matches.emplace_back(Match{score, std::move(file)});
}
}
public:
explicit Sorter(std::string_view term) : m_term(term) {}
~Sorter() { m_thread_pool.shutdown(); }
inline std::vector<Match> sort(const std::vector<std::string>& list) {
for (const std::string& item : list) {
m_thread_pool.push([&item, this]() { add_entry(item); });
}
while (!m_thread_pool.empty()) {
// Wait for all of the jobs to be finished
}
std::sort(m_matches.begin(), m_matches.end(), sort_match);
return m_matches;
}
};
} // namespace ivy

70
cpp/thread_pool.cpp Normal file
View file

@ -0,0 +1,70 @@
// Copyright 2021 Practically.io All rights reserved
//
// Use of this source is governed by a BSD-style
// licence that can be found in the LICENCE file or at
// https://www.practically.io/copyright/
#include "thread_pool.hpp"
namespace ivy {
void ThreadPool::run_job() {
std::function<void()> job;
while (true) {
{
std::unique_lock<std::mutex> lock(m_queue_lock);
m_condition.wait(lock, [&]() { return !m_queue.empty() || m_stop; });
if (m_queue.empty()) {
return;
}
job = m_queue.front();
m_queue.pop();
}
job();
{
// Only decrement the job count when the job has finished running.
std::unique_lock<std::mutex> lock(m_count_lock);
m_job_count--;
}
}
}
void ThreadPool::create_threads(unsigned int thread_count) {
for (int i = 0; i < thread_count; i++) {
m_threads.emplace_back(std::thread([this] { run_job(); }));
}
}
void ThreadPool::push(std::function<void()> job) {
{
{
std::unique_lock<std::mutex> lock(m_count_lock);
m_job_count++;
}
std::unique_lock<std::mutex> lock(m_queue_lock);
m_queue.push(job);
}
m_condition.notify_one();
}
bool ThreadPool::empty() {
std::unique_lock<std::mutex> lock(m_count_lock);
return m_job_count == 0;
}
void ThreadPool::shutdown() {
{
std::unique_lock<std::mutex> lock(m_queue_lock);
m_stop = true;
}
m_condition.notify_all();
for (auto &thread : m_threads) {
thread.join();
}
}
} // namespace ivy

66
cpp/thread_pool.hpp Normal file
View file

@ -0,0 +1,66 @@
// Copyright 2021 Practically.io All rights reserved
//
// Use of this source is governed by a BSD-style
// licence that can be found in the LICENCE file or at
// https://www.practically.io/copyright/
#pragma once
#include <condition_variable>
#include <functional>
#include <queue>
#include <thread>
namespace ivy {
// Basic thread pool implementation to run callbacks distributed across
// specified number of threads
//
// Example:
//
// ivy::ThreadPool thread_pool;
// for (int i = 0; i < 10; i++) {
// thread_pool.push([i]() {
// std::cout << "The number is " << i << std::endl;
// });
// }
//
// thread_pool.shutdown();
//
class ThreadPool {
bool m_stop = false;
// Need to track the number of jobs that need to be processed separately
// because we cant rely on the queue length to check if pool has finished all
// the jobs. It dose not take into account the jobs that have already been
// picked up by a thread.
int m_job_count = 0;
std::mutex m_queue_lock;
std::queue<std::function<void()>> m_queue;
std::mutex m_count_lock;
std::vector<std::thread> m_threads;
std::condition_variable m_condition;
void run_job();
void create_threads(unsigned int thread_count);
public:
// Create a new thread pool with the maximum number of threads you can have on
// the current machine
ThreadPool() { create_threads(std::thread::hardware_concurrency()); }
// Create a thread pool that will use the specified number of threads
explicit ThreadPool(unsigned int thread_count) {
create_threads(thread_count);
}
// Push a call back function into the queue that will be run on the thread
// pool as some time.
void push(std::function<void()>);
// Tests to see if there is any jobs that still need to be processed by the
// queue
bool empty();
// Shuts down the thread pool and waits for the queue to be empty. This must
// be called when all of the jobs have been pushed into the queue. This is a
// blocking operation and will not exit until the queue is empty and all of
// the pushed jobs have been handled.
void shutdown();
};
} // namespace ivy

View file

@ -1,12 +0,0 @@
local utils = require "ivy.utils"
local ag = {
name = "AG",
command = "IvyAg",
description = "Run ag to search for content in files",
keymap = "<leader>/",
items = utils.command_finder "ag",
callback = utils.vimgrep_action(),
}
return ag

View file

@ -1,38 +0,0 @@
local libivy = require "ivy.libivy"
local utils = require "ivy.utils"
local function items(input)
local list = {}
local buffers = vim.api.nvim_list_bufs()
for index = 1, #buffers do
local buffer = buffers[index]
-- Get the relative path from the current working directory. We need to
-- substring +2 to remove the `/` from the start of the path to give us a
-- true relative path
local buffer_name = vim.api.nvim_buf_get_name(buffer):sub(#vim.fn.getcwd() + 2, -1)
local file_type = vim.api.nvim_buf_get_option(buffer, "filetype")
if vim.api.nvim_buf_is_loaded(buffer) and file_type ~= "ivy" and #buffer_name > 0 then
local score = libivy.ivy_match(input, buffer_name)
if score > -200 or #input == 0 then
table.insert(list, { score = score, content = buffer_name })
end
end
end
table.sort(list, function(a, b)
return a.score < b.score
end)
return list
end
local buffers = {
name = "Buffers",
command = "IvyBuffers",
description = "List all of the current open buffers",
keymap = "<leader>b",
items = items,
callback = utils.file_action(),
}
return buffers

View file

@ -1,17 +0,0 @@
local libivy = require "ivy.libivy"
local utils = require "ivy.utils"
local function items(term)
return libivy.ivy_files(term, vim.fn.getcwd())
end
local files = {
name = "Files",
command = "IvyFd",
description = "Find files in the project",
keymap = "<leader>p",
items = items,
callback = utils.file_action(),
}
return files

View file

@ -1,32 +0,0 @@
local utils = require "ivy.utils"
local libivy = require "ivy.libivy"
local function items(input)
local list = {}
local lines = vim.api.nvim_buf_get_lines(vim.ivy.origin(), 0, -1, false)
for index = 1, #lines do
local line = lines[index]
local score = libivy.ivy_match(input, line)
if score > -200 then
local prefix = string.rep(" ", 4 - #tostring(index)) .. index .. ": "
table.insert(list, { score = score, content = prefix .. line })
end
end
table.sort(list, function(a, b)
return a.score < b.score
end)
return list
end
local lines = {
name = "Lines",
command = "IvyLines",
description = "Search though the lines in the current buffer",
items = items,
callback = utils.line_action(),
}
return lines

View file

@ -1,40 +0,0 @@
local window = require "ivy.window"
local utils = require "ivy.utils"
local previous_results = {}
local function set_items(items)
window.set_items(items)
previous_results = items
end
local function items(input)
local buffer_number = window.origin_buffer
local cwd = vim.fn.getcwd()
local results = {}
vim.lsp.buf_request(buffer_number, "workspace/symbol", { query = input }, function(err, server_result, _, _)
if err ~= nil then
set_items { content = "-- There was an error with workspace/symbol --" }
return
end
local locations = vim.lsp.util.symbols_to_items(server_result or {}, buffer_number) or {}
for index = 1, #locations do
local item = locations[index]
local relative_path = item.filename:sub(#cwd + 2, -1)
table.insert(results, { content = relative_path .. ":" .. item.lnum .. ": " .. item.text })
end
set_items(results)
end)
return previous_results
end
local lsp_workspace_symbols = {
name = "WorkspaceSymbols",
command = "IvyWorkspaceSymbols",
description = "Search for workspace symbols using the lsp workspace/symbol",
items = items,
callback = utils.vimgrep_action(),
}
return lsp_workspace_symbols

View file

@ -1,12 +0,0 @@
local utils = require "ivy.utils"
local rg = {
name = "RG",
command = "IvyRg",
description = "Run ripgrep to search for content in files",
keymap = "<leader>/",
items = utils.command_finder "rg --no-require-git --max-columns 200 --vimgrep --",
callback = utils.vimgrep_action(),
}
return rg

View file

@ -1,48 +0,0 @@
local config_mt = {}
config_mt.__index = config_mt
function config_mt:get_in(config, key_table)
local current_value = config
for _, key in ipairs(key_table) do
if current_value == nil then
return nil
end
current_value = current_value[key]
end
return current_value
end
function config_mt:get(key_table)
return self:get_in(self.user_config, key_table) or self:get_in(self.default_config, key_table)
end
local config = { user_config = {} }
config.default_config = {
backends = {
"ivy.backends.buffers",
"ivy.backends.files",
"ivy.backends.lines",
"ivy.backends.rg",
"ivy.backends.lsp-workspace-symbols",
},
mappings = {
["<C-c>"] = "destroy",
["<C-u>"] = "clear",
["<C-n>"] = "next",
["<C-p>"] = "previous",
["<C-M-n>"] = "next_checkpoint",
["<C-M-p>"] = "previous_checkpoint",
["<CR>"] = "complete",
["<C-v>"] = "vsplit",
["<C-s>"] = "split",
["<BS>"] = "backspace",
["<Left>"] = "left",
["<Right>"] = "right",
["<C-w>"] = "delete_word",
},
}
return setmetatable(config, config_mt)

View file

@ -1,27 +0,0 @@
local config = require "ivy.config"
describe("config", function()
before_each(function()
config.user_config = {}
end)
it("gets the first item when there is only default values", function()
local first_backend = config:get { "backends", 1 }
assert.is_equal("ivy.backends.buffers", first_backend)
end)
it("returns nil if we access a key that is not a valid config item", function()
assert.is_nil(config:get { "not", "a", "thing" })
end)
it("returns the users overridden config value", function()
config.user_config = { backends = { "ivy.my.backend" } }
local first_backend = config:get { "backends", 1 }
assert.is_equal("ivy.my.backend", first_backend)
end)
it("returns a nested value", function()
config.user_config = { some = { nested = "value" } }
assert.is_equal(config:get { "some", "nested" }, "value")
end)
end)

View file

@ -3,6 +3,7 @@ local prompt = require "ivy.prompt"
local utils = require "ivy.utils"
local controller = {}
controller.action = utils.actions
controller.items = nil
controller.callback = nil
@ -29,10 +30,6 @@ controller.search = function(value)
controller.update(prompt.text())
end
controller.paste = function()
controller.search(prompt.text() .. vim.fn.getreg "+p")
end
controller.update = function(text)
vim.schedule(function()
window.set_items(controller.items(text))
@ -40,7 +37,7 @@ controller.update = function(text)
if #text > 0 then
-- Escape characters so they do not throw an error when vim tries to use
-- the "text" as a regex
local escaped_text = string.gsub(text, "([-/\\])", "\\%1")
local escaped_text = string.gsub(text, "([-/])", "\\%1")
vim.cmd("syntax match IvyMatch '[" .. escaped_text .. "]'")
end
end)
@ -55,7 +52,7 @@ end
controller.checkpoint = function()
vim.api.nvim_set_current_win(window.origin)
controller.callback(window.get_current_selection(), utils.actions.CHECKPOINT)
controller.callback(window.get_current_selection(), controller.action.CHECKPOINT)
vim.api.nvim_set_current_win(window.window)
end

View file

@ -1,57 +0,0 @@
local window = require "ivy.window"
local controller = require "ivy.controller"
describe("controller", function()
before_each(function()
vim.cmd "highlight IvyMatch cterm=bold gui=bold"
window.initialize()
end)
after_each(function()
controller.destroy()
end)
it("will run the completion", function()
controller.run("Testing", function()
return { { content = "Some content" } }
end, function()
return {}
end)
-- Run all the scheduled tasks
vim.wait(0)
local lines = vim.api.nvim_buf_get_lines(0, 0, -1, true)
assert.is_equal(#lines, 1)
assert.is_equal(lines[1], "Some content")
end)
it("will not try and highlight the buffer if there is nothing to highlight", function()
spy.on(vim, "cmd")
controller.items = function()
return { { content = "Hello" } }
end
controller.update ""
vim.wait(0)
assert.spy(vim.cmd).was_called_with "syntax clear IvyMatch"
assert.spy(vim.cmd).was_not_called_with "syntax match IvyMatch '[H]'"
end)
it("will escape a - when passing it to be highlighted", function()
spy.on(vim, "cmd")
controller.items = function()
return { { content = "Hello" } }
end
controller.update "some-file"
vim.wait(0)
assert.spy(vim.cmd).was_called_with "syntax match IvyMatch '[some\\-file]'"
end)
end)

View file

@ -0,0 +1,51 @@
local vim_mock = require "ivy.vim_mock"
local window = require "ivy.window"
local controller = require "ivy.controller"
-- The number of the mock buffer where all the test completions gets put
local buffer_number = 10
before_each(function()
vim_mock.reset()
window.initialize()
end)
after_each(function()
controller.destroy()
end)
it("will run", function(t)
controller.run("Testing", function()
return { { content = "Some content" } }
end, function()
return {}
end)
local lines = vim_mock.get_lines()
local completion_lines = lines[buffer_number]
t.assert_equal(#completion_lines, 1)
t.assert_equal(completion_lines[1], "Some content")
end)
it("will not try and highlight the buffer if there is nothing to highlight", function(t)
controller.items = function()
return { { content = "Hello" } }
end
controller.update ""
local commands = vim_mock.get_commands()
t.assert_equal(#commands, 1)
end)
it("will escape a - when passing it to be highlighted", function(t)
controller.items = function()
return { { content = "Hello" } }
end
controller.update "some-file"
local commands = vim_mock.get_commands()
local syntax_command = commands[2]
t.assert_equal("syntax match IvyMatch '[some\\-file]'", syntax_command)
end)

View file

@ -1,49 +0,0 @@
local controller = require "ivy.controller"
local libivy = require "ivy.libivy"
local config = require "ivy.config"
local utils = require "ivy.utils"
local register_backend = require "ivy.register_backend"
local ivy = {}
ivy.action = utils.actions
ivy.utils = utils
ivy.match = libivy.ivy_match
ivy.run = controller.run
ivy.register_backend = register_backend
ivy.checkpoint = controller.checkpoint
ivy.paste = controller.paste
ivy.complete = controller.complete
ivy.destroy = controller.destroy
ivy.input = controller.input
ivy.next = controller.next
ivy.previous = controller.previous
ivy.search = controller.search
-- Private variable to check if ivy has been setup, this is to prevent multiple
-- setups of ivy. This is only exposed for testing purposes.
---@private
ivy.has_setup = false
---@class IvySetupOptions
---@field backends (IvyBackend | { ["1"]: string, ["2"]: IvyBackendOptions} | string)[]
---@param user_config IvySetupOptions
function ivy.setup(user_config)
if ivy.has_setup then
return
end
config.user_config = user_config or {}
for _, backend in ipairs(config:get { "backends" } or {}) do
register_backend(backend)
end
ivy.has_setup = true
end
return ivy

View file

@ -1,30 +0,0 @@
local ivy = require "ivy"
local config = require "ivy.config"
describe("ivy.setup", function()
before_each(function()
ivy.has_setup = false
config.user_config = {}
end)
it("sets the users config options", function()
ivy.setup { backends = { "ivy.backends.files" } }
assert.is_equal("ivy.backends.files", config:get { "backends", 1 })
end)
it("will not reconfigure if its called twice", function()
ivy.setup { backends = { "ivy.backends.files" } }
ivy.setup { backends = { "some.backend" } }
assert.is_equal("ivy.backends.files", config:get { "backends", 1 })
end)
it("does not crash if you don't pass in any params to the setup function", function()
ivy.setup()
assert.is_equal("ivy.backends.buffers", config:get { "backends", 1 })
end)
it("will fallback if the key is not set at all in the users config", function()
ivy.setup { some_key = "some_value" }
assert.is_equal("ivy.backends.buffers", config:get { "backends", 1 })
end)
end)

View file

@ -1,65 +1,18 @@
local library_path = (function()
local root = string.sub(debug.getinfo(1).source, 2, #"/libivy.lua" * -1)
local release_path = root .. "../../target/release"
local current_vim_version = vim.version()
local minimum_supported_version = vim.version.parse "0.9.5"
local is_windows
if vim.version.gt(current_vim_version, minimum_supported_version) then
is_windows = vim.uv.os_uname().sysname == "Windows_NT"
else
is_windows = vim.loop.os_uname().sysname == "Windows_NT"
end
if is_windows then
return package.searchpath("ivyrs", release_path .. "/?.dll;")
else
return package.searchpath("libivyrs", release_path .. "/?.so;" .. release_path .. "/?.dylib;")
end
local dirname = string.sub(debug.getinfo(1).source, 2, #"/fzf_lib.lua" * -1)
return dirname .. "/../../target/release/libivyrs.so"
end)()
local ffi = require "ffi"
local ok, ivy_c = pcall(ffi.load, library_path)
if not ok then
vim.api.nvim_err_writeln(
"libivyrs.so not found! Please ensure you have complied the shared library."
.. " For more info refer to the documentation, https://github.com/AdeAttwood/ivy.nvim#compiling"
)
return
end
local ivy_c = ffi.load(library_path)
ffi.cdef [[
void ivy_init(const char*);
char* ivy_cwd();
int ivy_match(const char*, const char*);
char* ivy_files(const char*, const char*);
int ivy_files_iter(const char*, const char*);
int ivy_files_iter_len(int);
char* ivy_files_iter_at(int, int);
void ivy_files_iter_delete(int);
]]
local iter_mt = {
__len = function(self)
return self.length
end,
__index = function(self, index)
-- Pass in our index -1. This will map lua's one based indexing to zero
-- based indexing that we are using in the rust lib.
local item = ffi.string(ivy_c.ivy_files_iter_at(self.id, index - 1))
return { content = item }
end,
__newindex = function(_, _, _)
error("attempt to update a read-only table", 2)
end,
__gc = function(self)
ivy_c.ivy_files_iter_delete(self.id)
end,
}
local libivy = {}
libivy.ivy_init = function(dir)
@ -75,12 +28,7 @@ libivy.ivy_match = function(pattern, text)
end
libivy.ivy_files = function(pattern, base_dir)
local iter_id = ivy_c.ivy_files_iter(pattern, base_dir)
local iter_len = ivy_c.ivy_files_iter_len(iter_id)
local iter = { id = iter_id, length = iter_len }
setmetatable(iter, iter_mt)
return iter
return ffi.string(ivy_c.ivy_files(pattern, base_dir))
end
return libivy

View file

@ -1,36 +0,0 @@
require "busted.runner"()
local libivy = require "ivy.libivy"
describe("libivy", function()
it("should run a simple match", function()
local score = libivy.ivy_match("term", "I am a serch term")
assert.is_true(score > 0)
end)
it("should find a dot file", function()
local current_dir = libivy.ivy_cwd()
local results = libivy.ivy_files(".github/workflows/ci.yml", current_dir)
assert.is_equal(2, results.length, "Incorrect number of results found")
assert.is_equal(".github/workflows/ci.yml", results[2].content, "Invalid matches")
end)
it("will allow you to access the length via the metatable", function()
local current_dir = libivy.ivy_cwd()
local results = libivy.ivy_files(".github/workflows/ci.yml", current_dir)
local mt = getmetatable(results)
assert.is_equal(results.length, mt.__len(results), "The `length` property does not match the __len metamethod")
end)
it("will create an iterator", function()
local iter = libivy.ivy_files(".github/workflows/ci.yml", libivy.ivy_cwd())
local mt = getmetatable(iter)
assert.is_equal(type(mt["__index"]), "function")
assert.is_equal(type(mt["__len"]), "function")
end)
end)

18
lua/ivy/libivy_test.lua Normal file
View file

@ -0,0 +1,18 @@
local libivy = require "ivy.libivy"
it("should run a simple match", function(t)
local score = libivy.ivy_match("term", "I am a serch term")
if score <= 0 then
t.error("Score should not be less than 0 found " .. score)
end
end)
it("should find a dot file", function(t)
local current_dir = libivy.ivy_cwd()
local matches = libivy.ivy_files("ci.yml", current_dir)
if matches ~= ".github/workflows/ci.yml\n" then
t.error("Invalid matches: " .. matches)
end
end)

View file

@ -1,29 +0,0 @@
local libivy = require "ivy.libivy"
-- Helper function to test a that string `one` has a higher match score than
-- string `two`. If string `one` has a lower score than string `two` a string
-- will be returned that can be used in body of an error. If not then `nil` is
-- returned and all is good.
local match_test = function(term, one, two)
local score_one = libivy.ivy_match(term, one)
local score_two = libivy.ivy_match(term, two)
assert.is_true(
score_one > score_two,
("The score of %s (%d) ranked higher than %s (%d)"):format(one, score_one, two, score_two)
)
end
describe("ivy matcher", function()
it("should match path separator", function()
match_test("file", "some/file.lua", "somefile.lua")
end)
-- it("should match pattern with spaces", function()
-- match_test("so fi", "some/file.lua", "somefile.lua")
-- end)
it("should match the start of a string", function()
match_test("file", "file.lua", "somefile.lua")
end)
end)

37
lua/ivy/matcher_test.lua Normal file
View file

@ -0,0 +1,37 @@
local libivy = require "ivy.libivy"
-- Helper function to test a that string `one` has a higher match score than
-- string `two`. If string `one` has a lower score than string `two` a string
-- will be returned that can be used in body of an error. If not then `nil` is
-- returned and all is good.
local match_test = function(term, one, two)
local score_one = libivy.ivy_match(term, one)
local score_two = libivy.ivy_match(term, two)
if score_one < score_two then
return one .. " should be ranked higher than " .. two
end
return nil
end
it("sould match path separator", function(t)
local result = match_test("file", "some/file.lua", "somefile.lua")
if result then
t.error(result)
end
end)
it("sould match pattern with spaces", function(t)
local result = match_test("so fi", "some/file.lua", "somefile.lua")
if result then
t.error(result)
end
end)
it("sould match the start of a string", function(t)
local result = match_test("file", "file.lua", "somefile.lua")
if result then
t.error(result)
end
end)

View file

@ -1,23 +1,6 @@
-- The prefix that will be before the search text for the user
local prompt_prefix = ">> "
-- Gets the suffix to delete from some text biased on what happens in a bash
-- prompt. If the text dose not end in a letter then the last word and all of
-- the tailing special characters will be returned. If the text dose end in a
-- letter then only the last word will be returned leaving the special
-- characters that are before the last word. For example
--
-- `some word` -> `some `
-- `some word` -> `some `
-- `some word ` -> `some `
local function get_delete_suffix(text)
if text:match "([A-Za-z]+)$" == nil then
return text:match "([A-Za-z]+[^A-Za-z]+)$"
end
return text:match "([A-Za-z]+)$"
end
local prompt = {}
prompt.suffix = ""
@ -26,7 +9,6 @@ prompt.value = ""
prompt.text = function()
return prompt.value .. prompt.suffix
end
prompt.update = function()
vim.api.nvim_echo({
{ prompt_prefix, "None" },
@ -50,12 +32,9 @@ prompt.input = function(char)
prompt.suffix = prompt.suffix:sub(2, -1)
end
elseif char == "DELETE_WORD" then
local suffix = get_delete_suffix(prompt.value)
if suffix == nil then
prompt.value = prompt.value:match "(.*)%s+.*$"
if prompt.value == nil then
prompt.value = ""
else
prompt.value = prompt.value:sub(1, #prompt.value - #suffix)
end
elseif char == "\\\\" then
prompt.value = prompt.value .. "\\"

View file

@ -1,91 +0,0 @@
local prompt = require "ivy.prompt"
-- Input a list of strings into the prompt
local input = function(input_table)
for index = 1, #input_table do
prompt.input(input_table[index])
end
end
describe("prompt", function()
before_each(function()
prompt.destroy()
end)
it("starts with empty text", function()
assert.is_same(prompt.text(), "")
end)
it("can input some text", function()
input { "A", "d", "e" }
assert.is_same(prompt.text(), "Ade")
end)
it("can delete a char", function()
input { "A", "d", "e", "BACKSPACE" }
assert.is_same(prompt.text(), "Ad")
end)
it("will reset the text", function()
input { "A", "d", "e" }
prompt.set "New"
assert.is_same(prompt.text(), "New")
end)
it("can move around the a word", function()
input { "P", "r", "o", "p", "t", "LEFT", "LEFT", "LEFT", "RIGHT", "m" }
assert.is_same(prompt.text(), "Prompt")
end)
it("can delete a word", function()
prompt.set "Ade Attwood"
input { "DELETE_WORD" }
assert.is_same(prompt.text(), "Ade ")
end)
it("can delete a word in the middle and leave the cursor at that word", function()
prompt.set "Ade middle A"
input { "LEFT", "LEFT", "DELETE_WORD", "a" }
assert.is_same(prompt.text(), "Ade a A")
end)
it("will delete the space and the word if the last word is single space", function()
prompt.set "some.thing "
input { "DELETE_WORD" }
assert.is_same(prompt.text(), "some.")
end)
it("will only delete one word from path", function()
prompt.set "some/nested/path"
input { "DELETE_WORD" }
assert.is_same(prompt.text(), "some/nested/")
end)
it("will delete tailing space", function()
prompt.set "word "
input { "DELETE_WORD" }
assert.is_same(prompt.text(), "")
end)
it("will leave a random space", function()
prompt.set "some word "
input { "DELETE_WORD" }
assert.is_same(prompt.text(), "some ")
end)
local special_characters = { ".", "/", "^" }
for _, char in ipairs(special_characters) do
it(string.format("will stop at a %s", char), function()
prompt.set(string.format("key%sValue", char))
input { "DELETE_WORD" }
assert.is_same(prompt.text(), string.format("key%s", char))
end)
end
end)

61
lua/ivy/prompt_test.lua Normal file
View file

@ -0,0 +1,61 @@
local prompt = require "ivy.prompt"
local vim_mock = require "ivy.vim_mock"
before_each(function()
vim_mock.reset()
prompt.destroy()
end)
-- Input a list of strings into the prompt
local input = function(input_table)
for index = 1, #input_table do
prompt.input(input_table[index])
end
end
-- Asserts the prompt contains the correct value
local assert_prompt = function(t, expected)
local text = prompt.text()
if text ~= expected then
t.error("The prompt text should be '" .. expected .. "' found '" .. text .. "'")
end
end
it("starts with empty text", function(t)
if prompt.text() ~= "" then
t.error "The prompt should start with empty text"
end
end)
it("can input some text", function(t)
input { "A", "d", "e" }
assert_prompt(t, "Ade")
end)
it("can delete a char", function(t)
input { "A", "d", "e", "BACKSPACE" }
assert_prompt(t, "Ad")
end)
it("will reset the text", function(t)
input { "A", "d", "e" }
prompt.set "New"
assert_prompt(t, "New")
end)
it("can move around the a word", function(t)
input { "P", "r", "o", "p", "t", "LEFT", "LEFT", "LEFT", "RIGHT", "m" }
assert_prompt(t, "Prompt")
end)
it("can delete a word", function(t)
prompt.set "Ade Attwood"
input { "DELETE_WORD" }
assert_prompt(t, "Ade")
end)
it("can delete a word in the middle", function(t)
prompt.set "Ade middle A"
input { "LEFT", "LEFT", "DELETE_WORD" }
assert_prompt(t, "Ade A")
end)

View file

@ -1,54 +0,0 @@
---@class IvyBackend
---@field command string The command this backend will have
---@field items fun(input: string): { content: string }[] | string The callback function to get the items to select from
---@field callback fun(result: string, action: string) The callback function to run when a item is selected
---@field description string? The description of the backend, this will be used in the keymaps
---@field name string? The name of the backend, this will fallback to the command if its not set
---@field keymap string? The keymap to trigger this backend
---@class IvyBackendOptions
---@field command string The command this backend will have
---@field keymap string? The keymap to trigger this backend
---Register a new backend
---
---This will create all the commands and set all the keymaps for the backend
---@param backend IvyBackend
local register_backend_class = function(backend)
local user_command_options = { bang = true }
if backend.description ~= nil then
user_command_options.desc = backend.description
end
local name = backend.name or backend.command
vim.api.nvim_create_user_command(backend.command, function()
vim.ivy.run(name, backend.items, backend.callback)
end, user_command_options)
if backend.keymap ~= nil then
vim.api.nvim_set_keymap("n", backend.keymap, "<cmd>" .. backend.command .. "<CR>", { nowait = true, silent = true })
end
end
---@param backend IvyBackend | { ["1"]: string, ["2"]: IvyBackendOptions} | string The backend or backend module
---@param options IvyBackendOptions? The options for the backend, that will be merged with the backend
local register_backend = function(backend, options)
if type(backend[1]) == "string" then
options = backend[2]
backend = require(backend[1])
end
if type(backend) == "string" then
backend = require(backend)
end
if options then
for key, value in pairs(options) do
backend[key] = value
end
end
register_backend_class(backend)
end
return register_backend

View file

@ -1,71 +0,0 @@
local register_backend = require "ivy.register_backend"
local function get_command(name)
local command_iter = vim.api.nvim_get_commands {}
for _, cmd in pairs(command_iter) do
if cmd.name == name then
return cmd
end
end
return nil
end
local function get_keymap(mode, rhs)
local keymap_iter = vim.api.nvim_get_keymap(mode)
for _, keymap in pairs(keymap_iter) do
if keymap.rhs == rhs then
return keymap
end
end
return nil
end
describe("register_backend", function()
after_each(function()
vim.api.nvim_del_user_command "IvyFd"
local keymap = get_keymap("n", "<Cmd>IvyFd<CR>")
if keymap then
vim.api.nvim_del_keymap("n", keymap.lhs)
end
end)
it("registers a backend from a string with the default options", function()
register_backend "ivy.backends.files"
local command = get_command "IvyFd"
assert.is_not_nil(command)
local keymap = get_keymap("n", "<Cmd>IvyFd<CR>")
assert.is_not_nil(keymap)
end)
it("allows you to override the keymap", function()
register_backend("ivy.backends.files", { keymap = "<C-p>" })
local keymap = get_keymap("n", "<Cmd>IvyFd<CR>")
assert(keymap ~= nil)
assert.are.equal("<C-P>", keymap.lhs)
end)
it("allows you to pass in a hole backend module", function()
register_backend(require "ivy.backends.files")
local command = get_command "IvyFd"
assert.is_not_nil(command)
local keymap = get_keymap("n", "<Cmd>IvyFd<CR>")
assert.is_not_nil(keymap)
end)
it("allows you to pass in a hole backend module", function()
register_backend { "ivy.backends.files", { keymap = "<C-p>" } }
local keymap = get_keymap("n", "<Cmd>IvyFd<CR>")
assert(keymap ~= nil)
assert.are.equal("<C-P>", keymap.lhs)
end)
end)

View file

@ -17,13 +17,6 @@ utils.command_map = {
[utils.actions.SPLIT] = "split",
}
utils.existing_command_map = {
[utils.actions.EDIT] = "buffer",
[utils.actions.CHECKPOINT] = "buffer",
[utils.actions.VSPLIT] = "vsplit | buffer",
[utils.actions.SPLIT] = "split | buffer",
}
utils.command_finder = function(command, min)
if min == nil then
min = 3
@ -40,18 +33,14 @@ utils.command_finder = function(command, min)
-- TODO(ade): Think if we want to start escaping the command here. I
-- dont know if its causing issues while trying to use regex especially
-- with word boundaries `input:gsub("'", "\\'"):gsub('"', '\\"')`
local handle = io.popen(command .. " " .. input .. " 2>&1 || true")
local handle = io.popen(command .. " " .. input .. " 2>&1")
if handle == nil then
return {}
end
local results = {}
for line in handle:lines() do
table.insert(results, { content = line })
end
local result = handle:read "*a"
handle:close()
return results
return result
end
end
@ -79,35 +68,21 @@ utils.file_action = function()
return
end
local buffer_number = vim.fn.bufnr(file)
local command
if buffer_number > -1 then
command = utils.existing_command_map[action]
else
command = utils.command_map[action]
end
local command = utils.command_map[action]
if command == nil then
vim.api.nvim_err_writeln("[IVY] The file action is unable the handel the action " .. action)
return
end
vim.cmd(command .. " " .. utils.escape_file_name(file))
vim.cmd(command .. " " .. file)
end
end
utils.line_action = function()
return function(item)
local line = item:match "^%s+(%d+):"
if line ~= nil then
vim.cmd(line)
end
vim.cmd(line)
end
end
utils.escape_file_name = function(input)
local file, _ = string.gsub(input, "([$%]\\[])", "\\%1")
return file
end
return utils

View file

@ -1,11 +0,0 @@
local utils = require "ivy.utils"
it("will escape a dollar in the file name", function()
local result = utils.escape_file_name "/path/to/$file/$name.lua"
assert.is_same(result, "/path/to/\\$file/\\$name.lua")
end)
it("will escape a brackets in the file name", function()
local result = utils.escape_file_name "/path/to/[file]/[name].lua"
assert.is_same(result, "/path/to/\\[file\\]/\\[name\\].lua")
end)

View file

@ -1,28 +0,0 @@
local utils = require "ivy.utils"
local line_action = utils.line_action()
describe("utils line_action", function()
before_each(function()
spy.on(vim, "cmd")
end)
it("will run the line command", function()
line_action " 4: Some text"
assert.is_equal(#vim.cmd.calls, 1, "The `vim.cmd` function should be called once")
assert.spy(vim.cmd).was_called_with "4"
end)
it("will run with more numbers", function()
line_action " 44: Some text"
assert.is_equal(#vim.cmd.calls, 1, "The `vim.cmd` function should be called once")
assert.spy(vim.cmd).was_called_with "44"
end)
it("dose not run any action if no line is found", function()
line_action "Some text"
assert.spy(vim.cmd).was_not_called()
end)
end)

View file

@ -0,0 +1,39 @@
local utils = require "ivy.utils"
local line_action = utils.line_action()
local vim_mock = require "ivy.vim_mock"
before_each(function()
vim_mock.reset()
end)
it("will run the line command", function(t)
line_action " 4: Some text"
if #vim_mock.commands ~= 1 then
t.error "`line_action` command length should be 1"
end
if vim_mock.commands[1] ~= "4" then
t.error "`line_action` command should be 4"
end
end)
it("will run with more numbers", function(t)
line_action " 44: Some text"
if #vim_mock.commands ~= 1 then
t.error "`line_action` command length should be 1"
end
if vim_mock.commands[1] ~= "44" then
t.error "`line_action` command should be 44"
end
end)
it("dose not run any action if no line is found", function(t)
line_action "Some text"
if #vim_mock.commands ~= 0 then
t.error "`line_action` command length should be 1"
end
end)

View file

@ -1,5 +1,10 @@
local utils = require "ivy.utils"
local vimgrep_action = utils.vimgrep_action()
local vim_mock = require "ivy.vim_mock"
before_each(function()
vim_mock.reset()
end)
local test_data = {
{
@ -15,42 +20,37 @@ local test_data = {
it = "will skip the line if its not matched",
completion = "some/file.lua: This is some text",
action = utils.actions.EDIT,
commands = { "buffer some/file.lua" },
commands = { "edit some/file.lua" },
},
{
it = "will run the vsplit command",
completion = "some/file.lua: This is some text",
action = utils.actions.VSPLIT,
commands = { "vsplit | buffer some/file.lua" },
commands = { "vsplit some/file.lua" },
},
{
it = "will run the split command",
completion = "some/file.lua: This is some text",
action = utils.actions.SPLIT,
commands = { "split | buffer some/file.lua" },
commands = { "split some/file.lua" },
},
}
describe("utils vimgrep_action", function()
before_each(function()
spy.on(vim, "cmd")
end)
for i = 1, #test_data do
local data = test_data[i]
it(data.it, function(t)
vimgrep_action(data.completion, data.action)
after_each(function()
vim.cmd:revert()
end)
if #vim_mock.commands ~= #data.commands then
t.error("Incorrect number of commands run expected " .. #data.commands .. " but found " .. #vim_mock.commands)
end
for i = 1, #test_data do
local data = test_data[i]
it(data.it, function()
assert.is_true(#data.commands > 0, "You must assert that at least one command is run")
vimgrep_action(data.completion, data.action)
assert.is_equal(#vim.cmd.calls, #data.commands, "The `vim.cmd` function should be called once")
for j = 1, #data.commands do
assert.spy(vim.cmd).was_called_with(data.commands[j])
for j = 1, #data.commands do
if vim_mock.commands[j] ~= data.commands[j] then
t.error(
"Incorrect command run expected '" .. data.commands[j] .. "' but found '" .. vim_mock.commands[j] .. "'"
)
end
end)
end
end)
end
end)
end

View file

@ -75,11 +75,6 @@ mock.reset = function()
return lines
end,
},
fn = {
bufnr = function()
return -1
end,
},
schedule = function(callback)
callback()
end,

View file

@ -1,5 +1,3 @@
local config = require "ivy.config"
-- Constent options that will be used for the keymaps
local opts = { noremap = true, silent = true, nowait = true }
@ -23,47 +21,12 @@ local function string_to_table(lines)
return matches
end
local function get_items_length(items)
local mt = getmetatable(items)
if mt ~= nil and mt.__len ~= nil then
return mt.__len(items)
end
return #items
end
local function call_gc(items)
local mt = getmetatable(items)
if mt ~= nil and mt.__gc ~= nil then
return mt.__gc(items)
end
end
local callbacks = {
destroy = "<cmd>lua vim.ivy.destroy()<CR>",
clear = "<cmd>lua vim.ivy.search('')<CR>",
next = "<cmd>lua vim.ivy.next()<CR>",
previous = "<cmd>lua vim.ivy.previous()<CR>",
next_checkpoint = "<cmd>lua vim.ivy.next(); vim.ivy.checkpoint()<CR>",
previous_checkpoint = "<cmd>lua vim.ivy.previous(); vim.ivy.checkpoint()<CR>",
complete = "<cmd>lua vim.ivy.complete(vim.ivy.action.EDIT)<CR>",
vsplit = "<cmd>lua vim.ivy.complete(vim.ivy.action.VSPLIT)<CR>",
split = "<cmd>lua vim.ivy.complete(vim.ivy.action.SPLIT)<CR>",
backspace = "<cmd>lua vim.ivy.input('BACKSPACE')<CR>",
left = "<cmd>lua vim.ivy.input('LEFT')<CR>",
right = "<cmd>lua vim.ivy.input('RIGHT')<CR>",
delete_word = "<cmd>lua vim.ivy.input('DELETE_WORD')<CR>",
}
local window = {}
window.index = 0
window.origin = nil
window.window = nil
window.buffer = nil
window.origin_buffer = nil
window.initialize = function()
window.make_buffer()
@ -71,7 +34,6 @@ end
window.make_buffer = function()
window.origin = vim.api.nvim_get_current_win()
window.origin_buffer = vim.api.nvim_win_get_buf(0)
vim.api.nvim_command "botright split new"
window.buffer = vim.api.nvim_win_get_buf(0)
@ -95,15 +57,25 @@ window.make_buffer = function()
vim.api.nvim_buf_set_keymap(window.buffer, "n", chars[index], "<cmd>lua vim.ivy.input('" .. char .. "')<CR>", opts)
end
local mappings = config:get { "mappings" }
assert(mappings, "The mappings key is missing from the config, something has gone horribly wrong")
for key, value in pairs(mappings) do
if callbacks[value] == nil then
error("The mapping '" .. value .. "' is not a valid ivy callback")
end
vim.api.nvim_buf_set_keymap(window.buffer, "n", key, callbacks[value], opts)
end
vim.api.nvim_buf_set_keymap(window.buffer, "n", "<C-c>", "<cmd>lua vim.ivy.destroy()<CR>", opts)
vim.api.nvim_buf_set_keymap(window.buffer, "n", "<C-u>", "<cmd>lua vim.ivy.search('')<CR>", opts)
vim.api.nvim_buf_set_keymap(window.buffer, "n", "<C-n>", "<cmd>lua vim.ivy.next()<CR>", opts)
vim.api.nvim_buf_set_keymap(window.buffer, "n", "<C-p>", "<cmd>lua vim.ivy.previous()<CR>", opts)
vim.api.nvim_buf_set_keymap(window.buffer, "n", "<C-M-n>", "<cmd>lua vim.ivy.next(); vim.ivy.checkpoint()<CR>", opts)
vim.api.nvim_buf_set_keymap(
window.buffer,
"n",
"<C-M-p>",
"<cmd>lua vim.ivy.previous(); vim.ivy.checkpoint()<CR>",
opts
)
vim.api.nvim_buf_set_keymap(window.buffer, "n", "<CR>", "<cmd>lua vim.ivy.complete(vim.ivy.action.EDIT)<CR>", opts)
vim.api.nvim_buf_set_keymap(window.buffer, "n", "<C-v>", "<cmd>lua vim.ivy.complete(vim.ivy.action.VSPLIT)<CR>", opts)
vim.api.nvim_buf_set_keymap(window.buffer, "n", "<C-s>", "<cmd>lua vim.ivy.complete(vim.ivy.action.SPLIT)<CR>", opts)
vim.api.nvim_buf_set_keymap(window.buffer, "n", "<BS>", "<cmd>lua vim.ivy.input('BACKSPACE')<CR>", opts)
vim.api.nvim_buf_set_keymap(window.buffer, "n", "<Left>", "<cmd>lua vim.ivy.input('LEFT')<CR>", opts)
vim.api.nvim_buf_set_keymap(window.buffer, "n", "<Right>", "<cmd>lua vim.ivy.input('RIGHT')<CR>", opts)
vim.api.nvim_buf_set_keymap(window.buffer, "n", "<C-w>", "<cmd>lua vim.ivy.input('DELETE_WORD')<CR>", opts)
end
window.get_current_selection = function()
@ -132,17 +104,15 @@ window.set_items = function(items)
items = string_to_table(items)
end
local items_length = get_items_length(items)
-- TODO(ade): Validate the items are in the correct format. This also need to
-- come with some descriptive messages and possible help.
-- Display no items text if there are no items to dispaly
if items_length == 0 then
items_length = 1
if #items == 0 then
items = { { content = "-- No Items --" } }
end
local items_length = #items
window.index = items_length - 1
for index = 1, items_length do
@ -158,8 +128,6 @@ window.set_items = function(items)
vim.api.nvim_win_set_height(window.window, line_count)
window.update()
call_gc(items)
end
window.destroy = function()
@ -170,7 +138,6 @@ window.destroy = function()
window.buffer = nil
window.window = nil
window.origin = nil
window.origin_buffer = nil
window.index = 0
end

View file

@ -1,32 +0,0 @@
local window = require "ivy.window"
local controller = require "ivy.controller"
describe("window", function()
before_each(function()
vim.cmd "highlight IvyMatch cterm=bold gui=bold"
window.initialize()
end)
after_each(function()
controller.destroy()
end)
it("can initialize and destroy the window", function()
assert.is_equal(vim.api.nvim_get_current_buf(), window.buffer)
window.destroy()
assert.is_equal(nil, window.buffer)
end)
it("can set items", function()
window.set_items { { content = "Line one" } }
assert.is_equal("Line one", window.get_current_selection())
end)
it("will set the items when a string is passed in", function()
local items = table.concat({ "One", "Two", "Three" }, "\n")
window.set_items(items)
assert.is_equal(items, table.concat(vim.api.nvim_buf_get_lines(window.buffer, 0, -1, true), "\n"))
end)
end)

23
lua/ivy/window_test.lua Normal file
View file

@ -0,0 +1,23 @@
local vim_mock = require "ivy.vim_mock"
local window = require "ivy.window"
before_each(function()
vim_mock.reset()
end)
it("can initialize and destroy the window", function(t)
window.initialize()
t.assert_equal(10, window.get_buffer())
t.assert_equal(10, window.buffer)
window.destroy()
t.assert_equal(nil, window.buffer)
end)
it("can set items", function(t)
window.initialize()
window.set_items { { content = "Line one" } }
t.assert_equal("Line one", window.get_current_selection())
end)

View file

@ -1,19 +1,73 @@
local api = require "ivy"
local controller = require "ivy.controller"
local utils = require "ivy.utils"
local libivy = require "ivy.libivy"
-- Put the controller in to the vim global so we can access it in mappings
-- better without requires. You can call controller commands like `vim.ivy.xxx`.
-- luacheck: ignore
vim.ivy = api
vim.ivy = controller
vim.paste = (function(overridden)
return function(lines, phase)
local file_type = vim.api.nvim_buf_get_option(0, "filetype")
if file_type == "ivy" then
vim.ivy.paste()
else
overridden(lines, phase)
vim.api.nvim_create_user_command("IvyAg", function()
vim.ivy.run("AG", utils.command_finder "ag", utils.vimgrep_action())
end, { bang = true, desc = "Run ag to search for content in files" })
vim.api.nvim_create_user_command("IvyFd", function()
vim.ivy.run("Files", function(term)
return libivy.ivy_files(term, vim.fn.getcwd())
end, utils.file_action())
end, { bang = true, desc = "Find files in the project" })
vim.api.nvim_create_user_command("IvyBuffers", function()
vim.ivy.run("Buffers", function(input)
local list = {}
local buffers = vim.api.nvim_list_bufs()
for index = 1, #buffers do
local buffer = buffers[index]
-- Get the relative path from the current working directory. We need to
-- substring +2 to remove the `/` from the start of the path to give us a
-- true relative path
local buffer_name = vim.api.nvim_buf_get_name(buffer):sub(#vim.fn.getcwd() + 2, -1)
local file_type = vim.api.nvim_buf_get_option(buffer, "filetype")
if vim.api.nvim_buf_is_loaded(buffer) and file_type ~= "ivy" and #buffer_name > 0 then
local score = libivy.ivy_match(input, buffer_name)
if score > -200 or #input == 0 then
table.insert(list, { score = score, content = buffer_name })
end
end
end
end
end)(vim.paste)
table.sort(list, function(a, b)
return a.score < b.score
end)
return list
end, utils.file_action())
end, { bang = true, desc = "List all of the current open buffers" })
vim.api.nvim_create_user_command("IvyLines", function()
vim.ivy.run("Lines", function(input)
local list = {}
local lines = vim.api.nvim_buf_get_lines(vim.ivy.origin(), 0, -1, false)
for index = 1, #lines do
local line = lines[index]
local score = libivy.ivy_match(input, line)
if score > -200 then
local prefix = string.rep(" ", 4 - #tostring(index)) .. index .. ": "
table.insert(list, { score = score, content = prefix .. line })
end
end
table.sort(list, function(a, b)
return a.score < b.score
end)
return list
end, utils.line_action())
end, { bang = true, desc = "List all of the current open buffers" })
vim.api.nvim_set_keymap("n", "<leader>b", "<cmd>IvyBuffers<CR>", { nowait = true, silent = true })
vim.api.nvim_set_keymap("n", "<leader>p", "<cmd>IvyFd<CR>", { nowait = true, silent = true })
vim.api.nvim_set_keymap("n", "<leader>/", "<cmd>IvyAg<CR>", { nowait = true, silent = true })
vim.cmd "highlight IvyMatch cterm=bold gui=bold"

View file

@ -1,5 +0,0 @@
#! /bin/bash
# Automatically compile libivy.so when we pull down the repo.
# https://github.com/AdeAttwood/ivy.nvim#compiling
cargo build --release

View file

@ -1,6 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
]
}

View file

@ -10,27 +10,18 @@ pub fn find_files(options: Options) -> Vec<String> {
let base_path = &fs::canonicalize(options.directory).unwrap();
let mut builder = WalkBuilder::new(base_path);
// Search for hidden files and directories
builder.hidden(false);
// Don't require a git repo to use .gitignore files. We want to use the .gitignore files
// wherever we are
builder.require_git(false);
// TODO(ade): Remove unwraps and find a good way to get the errors into the UI. Currently there
// is no way to handel errors in the rust library
let mut override_builder = OverrideBuilder::new("");
override_builder.add("!.git").unwrap();
override_builder.add("!.sl").unwrap();
let overrides = override_builder.build().unwrap();
builder.overrides(overrides);
for result in builder.build() {
let absolute_candidate = match result {
Ok(absolute_candidate) => absolute_candidate,
Err(..) => continue,
};
let absolute_candidate = result.unwrap();
let candidate_path = absolute_candidate.path().strip_prefix(base_path).unwrap();
if candidate_path.is_dir() {
continue;

View file

@ -7,38 +7,12 @@ use std::ffi::CStr;
use std::ffi::CString;
use std::os::raw::{c_char, c_int};
use std::sync::Mutex;
use std::sync::OnceLock;
// A store to the singleton instance of the ivy struct. This must not be accessed directly it must
// be use via the Ivy::global() function. Accessing this directly may cause a panic if its been
// initialized correctly.
static INSTANCE: OnceLock<Mutex<Ivy>> = OnceLock::new();
#[macro_use]
extern crate lazy_static;
struct Ivy {
// The file cache so we don't have to keep iterating the filesystem. The map key is the root
// directory that has been search and the value an a vector containing all of the files that as
// in the root. The value will be relative from the root.
pub file_cache: HashMap<String, Vec<String>>,
// The sequence number of the last iterator created. This will use as a pointer value to the
// iterator so we can access it though lua and rust without having to copy strings.
pub iter_sequence: i32,
// A store of all the iterators that have been created. The key is the sequence number and the
// value is the vector of matches that were matched in the search.
pub iter_map: HashMap<i32, Vec<CString>>,
}
impl Ivy {
// Get the global instance of the ivy struct. This will initialize the struct if it has not
// initialized yet.
pub fn global() -> &'static Mutex<Ivy> {
INSTANCE.get_or_init(|| {
Mutex::new(Ivy {
file_cache: HashMap::new(),
iter_sequence: 0,
iter_map: HashMap::new(),
})
})
}
lazy_static! {
static ref GLOBAL_FILE_CACHE: Mutex<HashMap<String, Vec<String>>> = Mutex::new(HashMap::new());
}
fn to_string(input: *const c_char) -> String {
@ -49,17 +23,15 @@ fn to_string(input: *const c_char) -> String {
}
fn get_files(directory: &String) -> Vec<String> {
let mut ivy = Ivy::global().lock().unwrap();
if !ivy.file_cache.contains_key(directory) {
let mut cache = GLOBAL_FILE_CACHE.lock().unwrap();
if !cache.contains_key(directory) {
let finder_options = finder::Options {
directory: directory.clone(),
};
ivy.file_cache
.insert(directory.clone(), finder::find_files(finder_options));
cache.insert(directory.clone(), finder::find_files(finder_options));
}
return ivy.file_cache.get(directory).unwrap().to_vec();
return cache.get(directory).unwrap().to_vec();
}
#[no_mangle]
@ -89,64 +61,6 @@ pub fn inner_match(pattern: String, text: String) -> i32 {
m.score(text.as_str()) as i32
}
// Create a new iterator that will iterate over all the files in the given directory that match a
// pattern. It will return the pointer to the iterator so it can be retrieve later. The iterator
// can be deleted with `ivy_files_iter_delete`
#[no_mangle]
pub extern "C" fn ivy_files_iter(c_pattern: *const c_char, c_base_dir: *const c_char) -> i32 {
let directory = to_string(c_base_dir);
let pattern = to_string(c_pattern);
let files = get_files(&directory);
let mut ivy = Ivy::global().lock().unwrap();
// Convert the matches into CStrings so we can pass the pointers out while still maintaining
// ownership. If we didn't do this the CString would be dropped and the pointer would be freed
// while its being used externally.
let sorter_options = sorter::Options::new(pattern);
let matches = sorter::sort_strings(sorter_options, files)
.into_iter()
.map(|m| CString::new(m.content.as_str()).unwrap())
.collect::<Vec<CString>>();
ivy.iter_sequence += 1;
let new_sequence = ivy.iter_sequence;
ivy.iter_map.insert(new_sequence, matches);
new_sequence
}
// Delete the iterator with the given id. This will free the memory used by the iterator that was
// created with `ivy_files_iter`
#[no_mangle]
pub extern "C" fn ivy_files_iter_delete(iter_id: i32) {
let mut ivy = Ivy::global().lock().unwrap();
ivy.iter_map.remove(&iter_id);
}
// Returns the length of a given iterator. This will return the number of items that were matched
// when the iterator was created with `ivy_files_iter`
#[no_mangle]
pub extern "C" fn ivy_files_iter_len(iter_id: i32) -> i32 {
let ivy = Ivy::global().lock().unwrap();
let items = ivy.iter_map.get(&iter_id).unwrap();
items.len() as i32
}
// Returns the item at the given index in the iterator. This will return the full match that was
// given in the iterator. This will return a pointer to the string so it can be used in lua.
#[no_mangle]
pub extern "C" fn ivy_files_iter_at(iter_id: i32, index: i32) -> *const c_char {
let ivy = Ivy::global().lock().unwrap();
let items = ivy.iter_map.get(&iter_id).unwrap();
let item = items.get(index as usize).unwrap();
item.as_ptr()
}
#[no_mangle]
pub extern "C" fn ivy_files(c_pattern: *const c_char, c_base_dir: *const c_char) -> *const c_char {
let pattern = to_string(c_pattern);

View file

@ -17,7 +17,8 @@ impl Matcher {
pub fn score(&self, text: &str) -> i64 {
self.matcher
.fuzzy_match(text, &self.pattern)
.fuzzy_indices(text, &self.pattern)
.map(|(score, _indices)| score)
.unwrap_or_default()
}
}

View file

@ -25,19 +25,12 @@ pub fn sort_strings(options: Options, strings: Vec<String>) -> Vec<Match> {
let mut matches = strings
.into_par_iter()
.filter_map(|candidate| {
let score = matcher.score(candidate.as_str());
if score < options.minimum_score {
None
} else {
Some(Match {
score,
content: candidate,
})
}
.map(|candidate| Match {
score: matcher.score(candidate.as_str()),
content: candidate,
})
.filter(|m| m.score > options.minimum_score)
.collect::<Vec<Match>>();
matches.par_sort_unstable_by(|a, b| a.score.cmp(&b.score));
matches
}

View file

@ -1,24 +0,0 @@
#!/bin/bash
target_rev="$(git rev-parse $1)"
current_rev="$(git rev-parse HEAD)"
current_branch="$(git rev-parse --abbrev-ref HEAD)"
trap "git checkout "$current_branch"" EXIT
hyperfine \
--style full \
--warmup 3 \
--export-json /tmp/bench.json \
--parameter-list rev "${target_rev},${current_rev}" \
--prepare "git checkout {rev} && cargo build --release" -n '{rev}' 'luajit ./scripts/benchmark.lua'
old_value=$(cat /tmp/bench.json | jq '.results[0].mean')
new_value=$(cat /tmp/bench.json | jq '.results[1].mean')
percentage_difference=$(echo "scale=2; (($new_value - $old_value) / $old_value) * 100" | bc)
echo ""
echo "-------------------------------------"
echo "The percentage difference is $percentage_difference%"
echo "-------------------------------------"
echo ""

View file

@ -1,7 +1,5 @@
package.path = "lua/?.lua;" .. package.path
local libivy = require "ivy.libivy"
local vim_mock = require "ivy.vim_mock"
local window = require "ivy.window"
local benchmark = function(name, n, callback)
local status = {
@ -27,7 +25,7 @@ local benchmark = function(name, n, callback)
print(
string.format(
"| %-41s | %09.6f (s) | %09.6f (s) | %09.6f (s) | %09.6f (s) |",
"| %-30s | %09.6f (s) | %09.6f (s) | %09.6f (s) | %09.6f (s) |",
name,
status.running_total,
status.running_total / n,
@ -37,8 +35,8 @@ local benchmark = function(name, n, callback)
)
end
print "| Name | Total | Average | Min | Max |"
print "|-------------------------------------------|---------------|---------------|---------------|---------------|"
print "| Name | Total | Average | Min | Max |"
print "|--------------------------------|---------------|---------------|---------------|---------------|"
benchmark("ivy_match(file.lua) 1000000x", 1000000, function()
libivy.ivy_match("file.lua", "some/long/path/to/file/file.lua")
@ -48,17 +46,3 @@ libivy.ivy_init "/tmp/ivy-trees/kubernetes"
benchmark("ivy_files(kubernetes) 100x", 100, function()
libivy.ivy_files("file.go", "/tmp/ivy-trees/kubernetes")
end)
-- Mock the vim API so we can run `vim.` functions. Override the
-- `nvim_buf_set_lines` function, this is so very slow. It saves all of the
-- lines so we can assert on them in the tests. For benchmarking we don't need
-- any of this, we can't control the vim internals.
vim_mock.reset()
_G.vim.api.nvim_buf_set_lines = function() end
window.initialize()
benchmark("ivy_files_with_set_items(kubernetes) 100x", 100, function()
local items = libivy.ivy_files(".go", "/tmp/ivy-trees/kubernetes")
window.set_items(items)
end)

View file

@ -1,8 +0,0 @@
-- Script to run the busted cli tool. You can use this under nvim using be
-- below command. Any arguments can be passed in the same as the busted cli.
--
-- ```bash
-- nvim -l scripts/busted.lua
-- ```
vim.opt.rtp:append(vim.fn.getcwd())
require "busted.runner" { standalone = false }

View file

@ -1,30 +0,0 @@
vim.opt.rtp:append(vim.fn.getcwd())
local ivy = require "ivy"
local prompt = require "ivy.prompt"
require "plugin.ivy"
if #vim.v.argv ~= 5 then
print "[ERROR] Expected 5 arguments"
print " Usage: nvim -l ./scripts/integration.lua <directory> <search>"
return
end
ivy.setup()
vim.fn.chdir(vim.v.argv[4])
print("Working in " .. vim.fn.getcwd())
vim.cmd "IvyFd"
for _, value in pairs(vim.split(vim.v.argv[5], "")) do
local start_time = os.clock()
vim.ivy.input(value)
vim.wait(0)
local running_time = os.clock() - start_time
io.stdout:write(prompt.text() .. "\t" .. running_time .. "\n")
end