Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F132186
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
29 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..04fd719
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,3 @@
+Language: Cpp
+BasedOnStyle: Google
+ColumnLimit: 0
diff --git a/.clang-tidy b/.clang-tidy
new file mode 100644
index 0000000..237092a
--- /dev/null
+++ b/.clang-tidy
@@ -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'
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..88eb5a1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+build
+.cache
+compile_commands.json
+.luacheckcache
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..a0e4cd8
--- /dev/null
+++ b/CMakeLists.txt
@@ -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)
diff --git a/README.md b/README.md
index 14b5ab0..1b92924 100644
--- a/README.md
+++ b/README.md
@@ -1,61 +1,76 @@
<div align="center">
# ivy.nvim
An [ivy-mode](https://github.com/abo-abo/swiper#ivy) port to neovim. Ivy is a
generic completion mechanism for ~~Emacs~~ Nvim
</div>
## Installation
### Manually
```sh
git clone https://github.com/AdeAttwood/ivy.nvim ~/.config/nvim/pack/bundle/start/ivy.nvim
```
### Plugin managers
TODO: Add docs in the plugin managers I don't use any
+### Compiling
+
+For the native searching, you will need to compile the shard library. You can
+do that by running the below command in the root of the plugin.
+
+```sh
+cmake -DCMAKE_BUILD_TYPE=Release -B build/Release && (cd build/Release; make -j)
+```
+
+If you are missing build dependencies, you can install them via apt.
+
+```sh
+sudo apt-get install build-essential pkg-config cmake
+```
+
## Features
### Commands
A command can be run that will launch the completion UI
| 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 | | Search though open buffers |
### Actions
Action can be run on selected candidates provide functionality
| 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 |
## API
```lua
vim.ivy.run(
-- 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 { "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
)
```
## Other stuff you might like
- [ivy-mode](https://github.com/abo-abo/swiper#ivy) - An emacs package that was the inspiration for this nvim plugin
- [Command-T](https://github.com/wincent/command-t) - Vim plugin I used before I started this one
- [telescope.nvim](https://github.com/nvim-telescope/telescope.nvim) - Another competition plugin, lots of people are using
diff --git a/cpp/cli.cpp b/cpp/cli.cpp
new file mode 100644
index 0000000..9bd6d15
--- /dev/null
+++ b/cpp/cli.cpp
@@ -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;
+}
diff --git a/cpp/file_scanner.hpp b/cpp/file_scanner.hpp
new file mode 100644
index 0000000..3fc8209
--- /dev/null
+++ b/cpp/file_scanner.hpp
@@ -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
diff --git a/cpp/fuzzy_match.cpp b/cpp/fuzzy_match.cpp
new file mode 100644
index 0000000..6a5d15d
--- /dev/null
+++ b/cpp/fuzzy_match.cpp
@@ -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
diff --git a/cpp/fuzzy_match.hpp b/cpp/fuzzy_match.hpp
new file mode 100644
index 0000000..9a02670
--- /dev/null
+++ b/cpp/fuzzy_match.hpp
@@ -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
diff --git a/cpp/lib.cpp b/cpp/lib.cpp
new file mode 100644
index 0000000..aadf00a
--- /dev/null
+++ b/cpp/lib.cpp
@@ -0,0 +1,41 @@
+#include <cstring>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "./file_scanner.hpp"
+#include "./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) {
+ auto matcher = ivy::FuzzyMatcher(pattern, 0);
+ return matcher.match(text, false);
+}
+
+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();
+}
diff --git a/cpp/match.hpp b/cpp/match.hpp
new file mode 100644
index 0000000..202227e
--- /dev/null
+++ b/cpp/match.hpp
@@ -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
diff --git a/cpp/sorter.hpp b/cpp/sorter.hpp
new file mode 100644
index 0000000..506cf02
--- /dev/null
+++ b/cpp/sorter.hpp
@@ -0,0 +1,45 @@
+#pragma once
+
+#include "./fuzzy_match.hpp"
+#include "./match.hpp"
+#include "./thread_pool.hpp"
+
+namespace ivy {
+
+class Sorter {
+ ivy::ThreadPool m_thread_pool;
+
+ std::string_view m_term;
+
+ std::mutex m_matches_lock;
+ std::vector<Match> m_matches;
+
+ inline void add_entry(const std::string& file) {
+ ivy::FuzzyMatcher matcher(m_term, 0);
+ int score = matcher.match(file, false);
+
+ if (score > -200) {
+ std::unique_lock<std::mutex> lock(m_matches_lock);
+ m_matches.emplace_back(Match{score, file});
+ }
+ }
+
+ public:
+ explicit Sorter(std::string_view term) : m_term(term) {}
+ ~Sorter() { m_thread_pool.shutdown(); }
+
+ inline std::vector<Match> sort(std::vector<std::string> list) {
+ for (auto 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
diff --git a/cpp/thread_pool.cpp b/cpp/thread_pool.cpp
new file mode 100644
index 0000000..710bf13
--- /dev/null
+++ b/cpp/thread_pool.cpp
@@ -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
diff --git a/cpp/thread_pool.hpp b/cpp/thread_pool.hpp
new file mode 100644
index 0000000..21c33fe
--- /dev/null
+++ b/cpp/thread_pool.hpp
@@ -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
diff --git a/lua/ivy/libivy.lua b/lua/ivy/libivy.lua
new file mode 100644
index 0000000..5bc4a10
--- /dev/null
+++ b/lua/ivy/libivy.lua
@@ -0,0 +1,30 @@
+local library_path = (function()
+ local dirname = string.sub(debug.getinfo(1).source, 2, #"/fzf_lib.lua" * -1)
+ -- return dirname .. "/../../build/Debug/lib/libivy.so"
+ return dirname .. "/../../build/Release/lib/libivy.so"
+end)()
+
+local ffi = require "ffi"
+local ivy_c = ffi.load(library_path)
+
+ffi.cdef [[
+ void ivy_init(const char*);
+ int ivy_match(const char*, const char*);
+ char* ivy_files(const char*, const char*);
+]]
+
+local libivy = {}
+
+libivy.ivy_init = function(dir)
+ ivy_c.ivy_init(dir)
+end
+
+libivy.ivy_match = function(pattern, text)
+ return ivy_c.ivy_match(pattern, text)
+end
+
+libivy.ivy_files = function(pattern, base_dir)
+ return ffi.string(ivy_c.ivy_files(pattern, base_dir))
+end
+
+return libivy
diff --git a/lua/ivy/window.lua b/lua/ivy/window.lua
index 1c0c7c1..0a33f65 100644
--- a/lua/ivy/window.lua
+++ b/lua/ivy/window.lua
@@ -1,139 +1,142 @@
-- Constent options that will be used for the keymaps
local opts = { noremap = true, silent = true, nowait = true }
-- All of the base chars that will be used for an "input" operation on the
-- prompt
-- stylua: ignore
local chars = {
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W",
"X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t",
"u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "<", ">", "`", "@", "#", "~", "!",
"\"", "$", "%", "^", "&", "/", "(", ")", "=", "+", "*", "-", "_", ".", ",", ";", ":", "?", "\\", "|", "'", "{", "}",
"[", "]", " ",
}
-local function parse_lines(lines)
+local function string_to_table(lines)
local items = {}
for line in lines:gmatch "[^\r\n]+" do
table.insert(items, line)
end
return items
end
-local function parse_array(arr)
- return arr
+local function set_items_string(buffer, lines)
+ vim.api.nvim_buf_set_lines(buffer, 0, 9999, false, string_to_table(lines))
+end
+
+local function set_items_array(buffer, lines)
+ if type(lines[1]) == "string" then
+ vim.api.nvim_buf_set_lines(buffer, 0, 9999, false, lines)
+ else
+ for i = 1, #lines do
+ vim.api.nvim_buf_set_lines(buffer, i - 1, 9999, false, { lines[i][2] })
+ end
+ end
end
local window = {}
window.index = 0
window.previous = nil
window.window = nil
window.buffer = nil
window.initialize = function()
window.make_buffer()
end
window.make_buffer = function()
window.previous = vim.api.nvim_get_current_win()
vim.api.nvim_command "botright split new"
window.buffer = vim.api.nvim_win_get_buf(0)
window.window = vim.api.nvim_get_current_win()
vim.api.nvim_win_set_option(window.window, "number", false)
vim.api.nvim_win_set_option(window.window, "relativenumber", false)
vim.api.nvim_win_set_option(window.window, "signcolumn", "no")
vim.api.nvim_buf_set_option(window.buffer, "filetype", "ivy")
vim.api.nvim_buf_set_var(window.buffer, "bufftype", "nofile")
for index = 1, #chars do
local char = chars[index]
if char == "'" then
char = "\\'"
end
if char == "\\" then
char = "\\\\\\\\"
end
vim.api.nvim_buf_set_keymap(window.buffer, "n", chars[index], "<cmd>lua vim.ivy.input('" .. char .. "')<CR>", 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-w>", "<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()<CR>", opts)
vim.api.nvim_buf_set_keymap(window.buffer, "n", "<BS>", "<cmd>lua vim.ivy.input('BACKSPACE')<CR>", opts)
end
window.get_current_selection = function()
local line = vim.api.nvim_buf_get_lines(window.buffer, window.index, window.index + 1, true)
if line == nil then
line = { "" }
end
return line[1]
end
window.get_buffer = function()
if window.buffer == nil then
window.make_buffer()
end
return window.buffer
end
window.update = function()
-- TODO(ade): Add a guard in so we can not go out of range on the results buffer
vim.api.nvim_win_set_cursor(window.window, { window.index + 1, 0 })
end
window.set_items = function(items)
- local lines = {}
-
- if type(items) == "string" then
- lines = parse_lines(items)
+ if #items == 0 then
+ vim.api.nvim_buf_set_lines(window.get_buffer(), 0, 9999, false, { "-- No Items --" })
+ elseif type(items) == "string" then
+ set_items_string(window.get_buffer(), items)
elseif type(items) == "table" then
- lines = parse_array(items)
- end
-
- if #lines == 0 then
- lines = { "-- No Items --" }
+ set_items_array(window.get_buffer(), items)
end
- vim.api.nvim_buf_set_lines(window.get_buffer(), 0, 9999, false, lines)
-
- local line_count = #lines
- window.index = 0
-
+ local line_count = vim.api.nvim_buf_line_count(window.buffer)
+ window.index = line_count - 1
if line_count > 10 then
line_count = 10
end
- vim.api.nvim_win_set_height(window.window, line_count)
+ vim.api.nvim_win_set_height(window.window, line_count)
window.update()
end
window.destroy = function()
if type(window.buffer) == "number" then
vim.api.nvim_buf_delete(window.buffer, { force = true })
end
window.buffer = nil
window.window = nil
window.previous = nil
window.index = 0
end
return window
diff --git a/plugin/ivy.lua b/plugin/ivy.lua
index 96bdf5f..2032c54 100644
--- a/plugin/ivy.lua
+++ b/plugin/ivy.lua
@@ -1,33 +1,36 @@
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`.
vim.ivy = controller
vim.api.nvim_create_user_command("IvyAg", function()
vim.ivy.run(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(utils.command_finder("fd --hidden --type f --exclude .git", 0), utils.file_action())
+ vim.ivy.run(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(function(input)
local list = {}
local buffers = vim.api.nvim_list_bufs()
for index = 1, #buffers do
local buffer = buffers[index]
local buffer_name = vim.api.nvim_buf_get_name(buffer)
if vim.api.nvim_buf_is_loaded(buffer) and #buffer_name > 0 then
table.insert(list, buffer_name)
end
end
return list
end, utils.file_action())
end, { bang = true, desc = "List all of the current open buffers" })
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 })
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, May 7, 6:24 PM (1 w, 1 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1054
Default Alt Text
(29 KB)
Attached To
Mode
R1 ivy.nvim
Attached
Detach File
Event Timeline
Log In to Comment