feat: remove the cpp implementation
This has not been used in some time and the rust version is way faster Ref: #28
This commit is contained in:
parent
1af1f0e252
commit
b57abf3787
14 changed files with 0 additions and 853 deletions
|
|
@ -1,3 +0,0 @@
|
|||
Language: Cpp
|
||||
BasedOnStyle: Google
|
||||
ColumnLimit: 0
|
||||
20
.clang-tidy
20
.clang-tidy
|
|
@ -1,20 +0,0 @@
|
|||
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'
|
||||
13
.github/workflows/ci.yml
vendored
13
.github/workflows/ci.yml
vendored
|
|
@ -49,19 +49,6 @@ jobs:
|
|||
version: latest
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,60 +0,0 @@
|
|||
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)
|
||||
35
cpp/cli.cpp
35
cpp/cli.cpp
|
|
@ -1,35 +0,0 @@
|
|||
#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;
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
#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
|
||||
|
|
@ -1,212 +0,0 @@
|
|||
// 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
|
||||
|
|
@ -1,198 +0,0 @@
|
|||
// 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
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
// 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
44
cpp/lib.cpp
|
|
@ -1,44 +0,0 @@
|
|||
#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();
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
#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
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
#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
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
// 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
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
// 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
|
||||
Loading…
Reference in a new issue