Page MenuHomePhorge

No OneTemporary

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

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)

Event Timeline