mirror of
https://github.com/KingDuckZ/dindexer.git
synced 2024-11-25 00:53:43 +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
43
include/dindexer-common/readline_wrapper.hpp
Normal file
43
include/dindexer-common/readline_wrapper.hpp
Normal file
|
@ -0,0 +1,43 @@
|
|||
/* Copyright 2015, 2016, Michele Santullo
|
||||
* This file is part of "dindexer".
|
||||
*
|
||||
* "dindexer" is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* "dindexer" is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with "dindexer". If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef id4DDE1979258D40A2973E2FC2AC3DF854
|
||||
#define id4DDE1979258D40A2973E2FC2AC3DF854
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
namespace dinlib {
|
||||
class ReadlineWrapper {
|
||||
public:
|
||||
using MatchesCallback = std::function<std::vector<std::string>(const std::string&)>;
|
||||
|
||||
explicit ReadlineWrapper ( MatchesCallback parMatchesCallback );
|
||||
~ReadlineWrapper ( void ) noexcept;
|
||||
|
||||
std::string read ( const std::string& parMessage ) const;
|
||||
const std::vector<std::string>& matches ( const std::string& parPrefix ) const;
|
||||
|
||||
private:
|
||||
mutable std::string m_last_prefix;
|
||||
mutable std::vector<std::string> m_last_matches;
|
||||
MatchesCallback m_fetch_matches;
|
||||
};
|
||||
} //namespace dinlib
|
||||
|
||||
#endif
|
|
@ -1,10 +1,13 @@
|
|||
project(${bare_name}-common CXX C)
|
||||
|
||||
find_package(Readline 6.3 REQUIRED)
|
||||
|
||||
add_library(${PROJECT_NAME}
|
||||
commandline.cpp
|
||||
settings.cpp
|
||||
validationerror.cpp
|
||||
common_info.cpp
|
||||
readline_wrapper.cpp
|
||||
)
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
|
@ -12,11 +15,13 @@ target_include_directories(${PROJECT_NAME}
|
|||
)
|
||||
target_include_directories(${PROJECT_NAME} SYSTEM
|
||||
PRIVATE ${YAMLCPP_INCLUDE_DIR}
|
||||
PRIVATE ${Readline_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PRIVATE ${bare_name}-if
|
||||
PRIVATE ${YAMLCPP_LIBRARY}
|
||||
PRIVATE ${Readline_LIBRARY}
|
||||
)
|
||||
|
||||
#install(TARGETS ${PROJECT_NAME}
|
||||
|
|
121
src/common/readline_wrapper.cpp
Normal file
121
src/common/readline_wrapper.cpp
Normal file
|
@ -0,0 +1,121 @@
|
|||
/* Copyright 2015, 2016, Michele Santullo
|
||||
* This file is part of "dindexer".
|
||||
*
|
||||
* "dindexer" is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* "dindexer" is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with "dindexer". If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "dindexer-common/readline_wrapper.hpp"
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <readline/readline.h>
|
||||
#include <readline/history.h>
|
||||
#include <cassert>
|
||||
#include <ciso646>
|
||||
#include <memory>
|
||||
|
||||
#define WITH_MULTITHREAD_READLINE
|
||||
|
||||
#if defined(WITH_MULTITHREAD_READLINE)
|
||||
# include <mutex>
|
||||
#endif
|
||||
|
||||
namespace dinlib {
|
||||
namespace {
|
||||
const ReadlineWrapper* g_current_wrapper = nullptr;
|
||||
bool g_readline_initialized = false;
|
||||
#if defined(WITH_MULTITHREAD_READLINE)
|
||||
std::mutex g_lock_wrapper;
|
||||
#endif
|
||||
|
||||
char* custom_generator (const char* parText, int parState) {
|
||||
assert(g_current_wrapper);
|
||||
assert(parText);
|
||||
|
||||
static std::size_t list_index;
|
||||
static std::vector<std::string> possible_values;
|
||||
|
||||
if (not parState) {
|
||||
list_index = 0;
|
||||
possible_values = g_current_wrapper->matches(parText);
|
||||
}
|
||||
if (static_cast<std::size_t>(parState) >= possible_values.size()) {
|
||||
possible_values.clear();
|
||||
list_index = 0;
|
||||
return nullptr;
|
||||
}
|
||||
else {
|
||||
const auto& match = possible_values[list_index];
|
||||
++list_index;
|
||||
return strdup(match.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
//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
|
||||
|
||||
ReadlineWrapper::ReadlineWrapper (MatchesCallback parMatchesCallback) :
|
||||
m_fetch_matches(parMatchesCallback)
|
||||
{
|
||||
if (not g_readline_initialized) {
|
||||
g_readline_initialized = true;
|
||||
//rl_attempted_completion_function = &custom_completion;
|
||||
rl_completion_entry_function = &custom_generator;
|
||||
rl_bind_key('\t', &rl_complete);
|
||||
}
|
||||
}
|
||||
|
||||
ReadlineWrapper::~ReadlineWrapper() noexcept {
|
||||
}
|
||||
|
||||
std::string ReadlineWrapper::read (const std::string& parMessage) const {
|
||||
typedef std::unique_ptr<char, void(*)(void*)> RawCharMemory;
|
||||
|
||||
#if defined(WITH_MULTITHREAD_READLINE)
|
||||
std::lock_guard<std::mutex> guard(g_lock_wrapper);
|
||||
#endif
|
||||
|
||||
g_current_wrapper = this;
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<std::string>& ReadlineWrapper::matches (const std::string& parPrefix) const {
|
||||
if (parPrefix != m_last_prefix) {
|
||||
m_last_prefix = parPrefix;
|
||||
m_last_matches = m_fetch_matches(parPrefix);
|
||||
}
|
||||
return m_last_matches;
|
||||
}
|
||||
} //namespace dinlib
|
|
@ -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…
Reference in a new issue