mirror of
https://github.com/KingDuckZ/dindexer.git
synced 2025-08-23 16:10:51 +00:00
Implement basic tab completion in navigate.
It doesn't work super well and it's missing set number completion, but it's a step forward.
This commit is contained in:
parent
1ef879e9c1
commit
b71a94ef70
11 changed files with 266 additions and 70 deletions
|
@ -1,7 +1,5 @@
|
|||
project(${bare_name}-navigate CXX)
|
||||
|
||||
find_package(Readline 6.3 REQUIRED)
|
||||
|
||||
add_executable(${PROJECT_NAME}
|
||||
main.cpp
|
||||
commandline.cpp
|
||||
|
@ -15,14 +13,10 @@ add_executable(${PROJECT_NAME}
|
|||
target_include_directories(${PROJECT_NAME}
|
||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/..
|
||||
)
|
||||
target_include_directories(${PROJECT_NAME} SYSTEM
|
||||
PRIVATE ${Readline_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PRIVATE ${bare_name}-if
|
||||
PRIVATE ${bare_name}-common
|
||||
PRIVATE ${Readline_LIBRARY}
|
||||
)
|
||||
|
||||
target_compile_features(${PROJECT_NAME}
|
||||
|
|
|
@ -153,4 +153,24 @@ namespace din {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> DBSource::paths_starting_by (uint32_t parGroupID, uint16_t parLevel, boost::string_ref parPath) {
|
||||
std::ostringstream oss;
|
||||
oss << "SELECT \"path\" FROM \"files\" WHERE \"group_id\"=$1 AND \"level\"=$2 AND str_begins_with(\"path\", COALESCE($3, '')) ORDER BY \"is_directory\" DESC, \"path\" ASC LIMIT " << g_files_query_limit << ';';
|
||||
|
||||
auto& conn = get_conn();
|
||||
auto result = conn.query(
|
||||
oss.str(),
|
||||
parGroupID,
|
||||
parLevel,
|
||||
parPath
|
||||
);
|
||||
std::vector<std::string> retval;
|
||||
retval.reserve(retval.size());
|
||||
for (auto row : result) {
|
||||
assert(not row.empty());
|
||||
retval.push_back(row[0]);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
} //namespace din
|
||||
|
|
|
@ -79,6 +79,8 @@ namespace din {
|
|||
template <FileDetails... D>
|
||||
auto file_details ( uint32_t parSetID, uint16_t parLevel, boost::string_ref parDir ) -> std::vector<MaxSizedArray<std::string, sizeof...(D)>>;
|
||||
|
||||
std::vector<std::string> paths_starting_by ( uint32_t parGroupID, uint16_t parLevel, boost::string_ref parPath );
|
||||
|
||||
private:
|
||||
struct LocalData;
|
||||
typedef std::map<SetDetails, std::string> SetDetailsMap;
|
||||
|
|
|
@ -17,67 +17,38 @@
|
|||
|
||||
#include "linereader.hpp"
|
||||
#include "listdircontent.hpp"
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <readline/readline.h>
|
||||
#include <readline/history.h>
|
||||
#include "dindexer-common/readline_wrapper.hpp"
|
||||
#include "genericpath.hpp"
|
||||
#include <cassert>
|
||||
#include <ciso646>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
namespace din {
|
||||
namespace {
|
||||
char* custom_generator (const char* parText, int parState) {
|
||||
static int list_index, len;
|
||||
|
||||
if (not parState) {
|
||||
list_index = 0;
|
||||
len = std::strlen(parText);
|
||||
std::vector<std::string> list_matches (const ListDirContent& parLS, const std::string& parCurrPath, const std::string& parPrefix) {
|
||||
GenericPath full_prefix;
|
||||
if (not parCurrPath.empty()) {
|
||||
full_prefix.push_piece(parCurrPath);
|
||||
}
|
||||
if (not parPrefix.empty()) {
|
||||
return parLS.ls(full_prefix, parPrefix);
|
||||
}
|
||||
else {
|
||||
return parLS.ls(full_prefix);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//char* custom_generator (const char* parText, int parState) {
|
||||
//}
|
||||
|
||||
//char** custom_completion (const char* parText, int parStart, int parEnd) {
|
||||
// char** matches = nullptr;
|
||||
|
||||
// if (0 == parStart) {
|
||||
// matches = rl_completion_matches(const_cast<char*>(parText), &custom_generator);
|
||||
// }
|
||||
// else {
|
||||
// //See the hack described here:
|
||||
// //http://cc.byexamples.com/2008/06/16/gnu-readline-implement-custom-auto-complete/
|
||||
// rl_bind_key('\t', &rl_abort);
|
||||
// }
|
||||
// return matches;
|
||||
//}
|
||||
} //unnamed namespace
|
||||
|
||||
LineReader::LineReader (const ListDirContent* parLS) :
|
||||
m_ls(parLS)
|
||||
{
|
||||
assert(m_ls);
|
||||
|
||||
//rl_attempted_completion_function = &custom_completion;
|
||||
rl_completion_entry_function = &custom_generator;
|
||||
rl_bind_key('\t', &rl_complete);
|
||||
}
|
||||
|
||||
std::string LineReader::read (const std::string& parMessage) {
|
||||
typedef std::unique_ptr<char, void(*)(void*)> RawCharMemory;
|
||||
std::string LineReader::read (const std::string& parMessage, const std::string& parCurrPath) {
|
||||
dinlib::ReadlineWrapper rl(std::bind(&list_matches, std::cref(*m_ls), std::cref(parCurrPath), std::placeholders::_1));
|
||||
|
||||
RawCharMemory line(readline(parMessage.c_str()), &std::free);
|
||||
|
||||
if (line) {
|
||||
if (*line) {
|
||||
add_history(line.get());
|
||||
}
|
||||
return std::string(line.get());
|
||||
}
|
||||
else {
|
||||
return std::string();
|
||||
}
|
||||
return rl.read(parMessage);
|
||||
}
|
||||
} //namespace din
|
||||
|
|
|
@ -28,7 +28,7 @@ namespace din {
|
|||
explicit LineReader ( const ListDirContent* parLS );
|
||||
~LineReader ( void ) noexcept = default;
|
||||
|
||||
std::string read ( const std::string& parMessage );
|
||||
std::string read ( const std::string& parMessage, const std::string& parCurrPath );
|
||||
|
||||
private:
|
||||
const ListDirContent* m_ls;
|
||||
|
|
|
@ -44,6 +44,29 @@ namespace din {
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string>* find_and_refresh_in_cache (boost::circular_buffer<std::pair<std::string, std::vector<std::string>>>& parCache, const std::string& parCurrPath) {
|
||||
using CachedItemType = std::pair<std::string, std::vector<std::string>>;
|
||||
|
||||
//Check if requested item is already cached
|
||||
auto it_found = std::find_if(parCache.begin(), parCache.end(), [&parCurrPath](const CachedItemType& itm) { return itm.first == parCurrPath; });
|
||||
if (it_found != parCache.end()) {
|
||||
CachedItemType tmp;
|
||||
std::swap(tmp, *it_found);
|
||||
assert(it_found->first.empty());
|
||||
assert(it_found->second.empty());
|
||||
assert(tmp.first == parCurrPath);
|
||||
parCache.erase(it_found);
|
||||
assert(parCache.size() < g_max_cached_lists);
|
||||
parCache.push_back(std::move(tmp));
|
||||
assert(not parCache.empty());
|
||||
assert(parCache.back().first == parCurrPath);
|
||||
return &parCache.back().second;
|
||||
}
|
||||
else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
} //unnamed namespace
|
||||
|
||||
ListDirContent::ListDirContent (DBSource* parDB) :
|
||||
|
@ -54,22 +77,11 @@ namespace din {
|
|||
}
|
||||
|
||||
auto ListDirContent::ls (const GenericPath& parDir) const -> const ListType& {
|
||||
const auto curr_path = parDir.to_string();
|
||||
|
||||
//Check if requested item is already cached
|
||||
auto it_found = std::find_if(m_cache.begin(), m_cache.end(), [&curr_path](const CachedItemType& itm) { return itm.first == curr_path; });
|
||||
if (it_found != m_cache.end()) {
|
||||
CachedItemType tmp;
|
||||
std::swap(tmp, *it_found);
|
||||
assert(it_found->first.empty());
|
||||
assert(it_found->second.empty());
|
||||
assert(tmp.first == curr_path);
|
||||
m_cache.erase(it_found);
|
||||
assert(m_cache.size() < g_max_cached_lists);
|
||||
m_cache.push_back(std::move(tmp));
|
||||
assert(not m_cache.empty());
|
||||
assert(m_cache.back().first == curr_path);
|
||||
return m_cache.back().second;
|
||||
const std::string curr_path = parDir.to_string();
|
||||
{
|
||||
const auto* cached_item = find_and_refresh_in_cache(m_cache, curr_path);
|
||||
if (cached_item)
|
||||
return *cached_item;
|
||||
}
|
||||
|
||||
//Requested item is not cached, so we need to query the db now
|
||||
|
@ -89,4 +101,30 @@ namespace din {
|
|||
assert(m_cache.back().first == curr_path);
|
||||
return m_cache.back().second;
|
||||
}
|
||||
|
||||
auto ListDirContent::ls ( GenericPath parDir, const std::string& parStartWith ) const -> const ListType& {
|
||||
parDir.push_piece(parStartWith);
|
||||
const std::string curr_path = parDir.to_string();
|
||||
|
||||
{
|
||||
const auto* cached_item = find_and_refresh_in_cache(m_cache, curr_path);
|
||||
if (cached_item)
|
||||
return *cached_item;
|
||||
}
|
||||
|
||||
if ("/" == curr_path) {
|
||||
assert(false); //not implemented
|
||||
}
|
||||
else {
|
||||
const auto start_from = curr_path.find('/', 1);
|
||||
auto path_prefix = boost::string_ref(curr_path).substr(start_from == curr_path.npos ? curr_path.size() : start_from + 1);
|
||||
const auto set_id = boost::lexical_cast<uint32_t>(parDir[0]);
|
||||
assert(parDir.level() > 0);
|
||||
auto file_list = m_db->paths_starting_by(set_id, parDir.level() - 1, path_prefix);
|
||||
m_cache.push_back(std::make_pair(curr_path, file_list));
|
||||
}
|
||||
assert(not m_cache.empty());
|
||||
assert(m_cache.back().first == curr_path);
|
||||
return m_cache.back().second;
|
||||
}
|
||||
} //namespace din
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <boost/circular_buffer.hpp>
|
||||
#include <utility>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace din {
|
||||
class GenericPath;
|
||||
|
@ -34,6 +35,7 @@ namespace din {
|
|||
~ListDirContent ( void ) noexcept = default;
|
||||
|
||||
const ListType& ls ( const GenericPath& parDir ) const;
|
||||
const ListType& ls ( GenericPath parDir, const std::string& parStartWith ) const;
|
||||
|
||||
private:
|
||||
mutable boost::circular_buffer<CachedItemType> m_cache;
|
||||
|
|
|
@ -97,7 +97,7 @@ namespace {
|
|||
proc.add_command("ls", std::function<void()>(std::bind(on_ls, std::ref(ls), std::ref(dir_man))), 0);
|
||||
do {
|
||||
do {
|
||||
curr_line = lines.read(prompt);
|
||||
curr_line = lines.read(prompt, dir_man.to_string());
|
||||
} while (curr_line.empty());
|
||||
running = proc.exec_command(curr_line);
|
||||
} while (running);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue