1
0
Fork 0
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:
King_DuckZ 2016-04-28 00:05:22 +02:00
parent 1ef879e9c1
commit b71a94ef70
11 changed files with 266 additions and 70 deletions

View 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

View file

@ -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}

View 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

View file

@ -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}

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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);