Skip to content

Commit

Permalink
Add subscorer for Rust extension (#176)
Browse files Browse the repository at this point in the history
* Add subscorer for Rust extension

Follow up of #159

* Jump to the end when building the extension

* Update CHANGELOG.md

* Refine subscorer test

* Add fuzzymatch_rs ci

* Use nightly for fuzzymatch_rs

* .
  • Loading branch information
liuchengxu authored Dec 22, 2019
1 parent 73bdb3c commit 5971c4d
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 3 deletions.
12 changes: 12 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,15 @@ jobs:
run: pip install vim-vint
- name: Run Vimscript Linter
run: test/run_vint.sh

fuzzymatch_rs:
strategy:
fail-fast: false

runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@master
- name: Run Cargo test
run: cd pythonx/clap/fuzzymatch-rs && cargo test --no-default-features
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,18 @@ CHANGELOG
### Added

- Add Python version subscorer fuzzy filter.([#159](https://github.com/liuchengxu/vim-clap/pull/159))
- Add Rust version subscorer fuzzy filter.([#176](https://github.com/liuchengxu/vim-clap/pull/176))
- New provider `:Clap quickfix` by @kit494way.
- New provider `:Clap git_diff_files` by @kit494way.
- Add the preview support for `:Clap registers`. If the content of some register is too much to fit on one line, then it will be shown in the preview window, otherwise do nothing.
- Add the preview support for `:Clap tags`.
- Add the helper function for building Rust extension easily. Now you can use `:call clap#helper#build_all()` to build the optional Rust dependency.
- Make the built-in fuzzy filter 10x faster using Rust.([#147](https://github.com/liuchengxu/vim-clap/pull/147))

### Fixed

- Fix vim popup sign not showing.([#141](https://github.com/liuchengxu/vim-clap/pull/141))

## [0.2] 2019-12-10

### Added
Expand Down
2 changes: 2 additions & 0 deletions autoload/clap/helper.vim
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ function! s:run_term(cmd, cwd, success_info) abort

let bufnr = bufnr('')

normal! G

noautocmd wincmd p
endfunction

Expand Down
7 changes: 6 additions & 1 deletion pythonx/clap/fuzzymatch-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,9 @@ version = "0.3.0"

[dependencies.pyo3]
version = "0.8.4"
features = ["extension-module"]

# https://github.com/PyO3/pyo3/issues/340
# For running PyO3 test using `cargo test --no-default-features`
[features]
default = ["extension-module"]
extension-module = ["pyo3/extension-module"]
1 change: 1 addition & 0 deletions pythonx/clap/fuzzymatch-rs/rust-toolchain
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nightly
79 changes: 77 additions & 2 deletions pythonx/clap/fuzzymatch-rs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,58 @@
#![feature(pattern)]

use pyo3::prelude::*;
use pyo3::wrap_pyfunction;
use rff::match_and_score_with_positions;

use std::str::pattern::Pattern;

#[inline]
fn find_start_at<'a, P: Pattern<'a>>(slice: &'a str, at: usize, pat: P) -> Option<usize> {
slice[at..].find(pat).map(|i| at + i)
}

fn substr_scorer(niddle: &str, haystack: &str) -> Option<(f64, Vec<usize>)> {
let niddle = niddle.to_lowercase();
let haystack = haystack.to_lowercase();
let indices: Vec<usize> = (0..haystack.len()).collect();
let haystack = haystack.as_str();

let mut offset = 0;
let mut positions = Vec::new();
for sub_niddle in niddle.split_whitespace() {
match find_start_at(haystack, offset, sub_niddle) {
Some(idx) => {
offset = idx;
let niddle_len = sub_niddle.len();
positions.extend_from_slice(&indices[offset..offset + niddle_len]);
offset += niddle_len;
}
None => return None,
}
}

if positions.is_empty() {
return Some((0f64, positions));
}

let last_pos = positions.last().unwrap();
let match_len = (last_pos + 1 - positions[0]) as f64;

Some((
((2f64 / (positions[0] + 1) as f64) + 1f64 / (last_pos + 1) as f64 - match_len),
positions,
))
}

#[pyfunction]
/// Filter the candidates given query using the fzy algorithm
fn fuzzy_match(query: &str, candidates: Vec<String>) -> PyResult<(Vec<Vec<usize>>, Vec<String>)> {
let scorer = |line: &str| {
match_and_score_with_positions(query, line).map(|(_, score, indices)| (score, indices))
let scorer: Box<dyn Fn(&str) -> Option<(f64, Vec<usize>)>> = if query.contains(" ") {
Box::new(|line: &str| substr_scorer(query, line))
} else {
Box::new(|line: &str| {
match_and_score_with_positions(query, line).map(|(_, score, indices)| (score, indices))
})
};

let mut ranked = candidates
Expand All @@ -33,3 +79,32 @@ fn fuzzymatch_rs(_py: Python, m: &PyModule) -> PyResult<()> {

Ok(())
}

#[test]
fn py_and_rs_subscore_should_work() {
use pyo3::{prelude::*, types::PyModule};
use std::fs;

let cur_dir = std::env::current_dir().unwrap();
let py_path = cur_dir.parent().unwrap().join("scorer.py");
let py_source_code = fs::read_to_string(py_path).unwrap();

let gil = Python::acquire_gil();
let py = gil.python();
let py_scorer = PyModule::from_code(py, &py_source_code, "scorer.py", "scorer").unwrap();

let test_cases = vec![
("su ou", "substr_scorer_should_work"),
("su ork", "substr_scorer_should_work"),
];

for (niddle, haystack) in test_cases.into_iter() {
let py_result: (f64, Vec<usize>) = py_scorer
.call1("substr_scorer", (niddle, haystack))
.unwrap()
.extract()
.unwrap();
let rs_result = substr_scorer(niddle, haystack).unwrap();
assert_eq!(py_result, rs_result);
}
}

0 comments on commit 5971c4d

Please # to comment.