Browse Source

Added natural sort to Find File

merge-requests/413/head
eidheim 4 years ago
parent
commit
103e5519bc
  1. 119
      src/directories.cpp
  2. 107
      src/utility.cpp
  3. 12
      src/utility.hpp
  4. 21
      src/window.cpp
  5. 20
      tests/utility_test.cpp

119
src/directories.cpp

@ -119,111 +119,6 @@ Directories::Directories() : Gtk::ListViewText(1) {
tree_store->set_sort_column(column_record.name, Gtk::SortType::SORT_ASCENDING);
tree_store->set_sort_func(column_record.name, [this](const Gtk::TreeModel::iterator &it1, const Gtk::TreeModel::iterator &it2) {
/// Natural comparison supporting UTF-8 and locale
struct Natural {
static bool is_digit(char chr) {
return chr >= '0' && chr <= '9';
}
static int compare_characters(size_t &i1, size_t &i2, const std::string &s1, const std::string &s2) {
ScopeGuard scope_guard{[&i1, &i2] {
++i1;
++i2;
}};
auto c1 = static_cast<unsigned char>(s1[i1]);
auto c2 = static_cast<unsigned char>(s2[i2]);
if(c1 < 0b10000000 && c2 < 0b10000000) { // Both characters are ascii
auto at = std::tolower(s1[i1]);
auto bt = std::tolower(s2[i2]);
if(at < bt)
return -1;
else if(at == bt)
return 0;
else
return 1;
}
Glib::ustring u1;
if(c1 >= 0b11110000)
u1 = s1.substr(i1, 4);
else if(c1 >= 0b11100000)
u1 = s1.substr(i1, 3);
else if(c1 >= 0b11000000)
u1 = s1.substr(i1, 2);
else
u1 = s1[i1];
Glib::ustring u2;
if(c2 >= 0b11110000)
u2 = s2.substr(i2, 4);
else if(c2 >= 0b11100000)
u2 = s2.substr(i2, 3);
else if(c2 >= 0b11000000)
u2 = s2.substr(i2, 2);
else
u2 = s2[i2];
i1 += u1.bytes() - 1;
i2 += u2.bytes() - 1;
u1 = u1.lowercase();
u2 = u2.lowercase();
if(u1 < u2)
return -1;
else if(u1 == u2)
return 0;
else
return 1;
}
static int compare_numbers(size_t &i1, size_t &i2, const std::string &s1, const std::string &s2) {
int result = 0;
while(true) {
if(i1 >= s1.size() || !is_digit(s1[i1])) {
if(i2 >= s2.size() || !is_digit(s2[i2])) // a and b has equal number of digits
return result;
return -1; // a has fewer digits
}
if(i2 >= s2.size() || !is_digit(s2[i2]))
return 1; // b has fewer digits
if(result == 0) {
if(s1[i1] < s2[i2])
result = -1;
if(s1[i1] > s2[i2])
result = 1;
}
++i1;
++i2;
}
}
static int compare(const std::string &s1, const std::string &s2) {
size_t i1 = 0;
size_t i2 = 0;
while(i1 < s1.size() && i2 < s2.size()) {
if(is_digit(s1[i1]) && !is_digit(s2[i2]))
return -1;
if(!is_digit(s1[i1]) && is_digit(s2[i2]))
return 1;
if(!is_digit(s1[i1]) && !is_digit(s2[i2])) {
auto result = compare_characters(i1, i2, s1, s2);
if(result != 0)
return result;
}
else {
auto result = compare_numbers(i1, i2, s1, s2);
if(result != 0)
return result;
}
}
if(i1 >= s1.size())
return -1;
return 1;
}
};
auto name1 = it1->get_value(column_record.name);
auto name2 = it2->get_value(column_record.name);
if(name1.empty())
@ -240,7 +135,7 @@ Directories::Directories() : Gtk::ListViewText(1) {
return Natural::compare(prefix1 + name1, prefix2 + name2);
});
set_enable_search(true); //TODO: why does this not work in OS X?
set_enable_search(true); // TODO: why does this not work in OS X?
set_search_column(column_record.name);
signal_row_activated().connect([this](const Gtk::TreeModel::Path &path, Gtk::TreeViewColumn *column) {
@ -522,7 +417,7 @@ void Directories::open(const boost::filesystem::path &dir_path) {
path = filesystem::get_normal_path(dir_path);
//TODO: report that set_title does not handle '_' correctly?
// TODO: report that set_title does not handle '_' correctly?
auto title = path.filename().string();
size_t pos = 0;
while((pos = title.find('_', pos)) != std::string::npos) {
@ -582,7 +477,7 @@ void Directories::select(const boost::filesystem::path &select_path) {
if(!filesystem::file_in_path(select_path, path))
return;
//return if the select_path is already selected
// return if the select_path is already selected
auto iter = get_selection()->get_selected();
if(iter) {
if(iter->get_value(column_record.path) == select_path)
@ -597,9 +492,9 @@ void Directories::select(const boost::filesystem::path &select_path) {
else
parent_path = select_path.parent_path();
//check if select_path is already expanded
// check if select_path is already expanded
if(directories.find(parent_path.string()) != directories.end()) {
//set cursor at select_path and return
// set cursor at select_path and return
tree_store->foreach_iter([this, &select_path](const Gtk::TreeModel::iterator &iter) {
if(iter->get_value(column_record.path) == select_path) {
auto tree_path = Gtk::TreePath(iter);
@ -618,7 +513,7 @@ void Directories::select(const boost::filesystem::path &select_path) {
paths.emplace_front(parent_path);
}
//expand to select_path
// expand to select_path
for(auto &a_path : paths) {
tree_store->foreach_iter([this, &a_path](const Gtk::TreeModel::iterator &iter) {
if(iter->get_value(column_record.path) == a_path) {
@ -629,7 +524,7 @@ void Directories::select(const boost::filesystem::path &select_path) {
});
}
//set cursor at select_path
// set cursor at select_path
tree_store->foreach_iter([this, &select_path](const Gtk::TreeModel::iterator &iter) {
if(iter->get_value(column_record.path) == select_path) {
auto tree_path = Gtk::TreePath(iter);

107
src/utility.cpp

@ -1,6 +1,7 @@
#include "utility.hpp"
#include <algorithm>
#include <cstring>
#include <gtkmm.h>
#include <vector>
ScopeGuard::~ScopeGuard() {
@ -232,3 +233,109 @@ int version_compare(const std::string &lhs, const std::string &rhs) {
return -1;
return 1;
}
bool Natural::is_digit(char chr) {
return chr >= '0' && chr <= '9';
}
int Natural::compare_characters(size_t &i1, size_t &i2, const std::string &s1, const std::string &s2) {
ScopeGuard scope_guard{[&i1, &i2] {
++i1;
++i2;
}};
auto c1 = static_cast<unsigned char>(s1[i1]);
auto c2 = static_cast<unsigned char>(s2[i2]);
if(c1 < 0b10000000 && c2 < 0b10000000) { // Both characters are ascii
auto at = std::tolower(s1[i1]);
auto bt = std::tolower(s2[i2]);
if(at < bt)
return -1;
else if(at == bt)
return 0;
else
return 1;
}
Glib::ustring u1;
if(c1 >= 0b11110000)
u1 = s1.substr(i1, 4);
else if(c1 >= 0b11100000)
u1 = s1.substr(i1, 3);
else if(c1 >= 0b11000000)
u1 = s1.substr(i1, 2);
else
u1 = s1[i1];
Glib::ustring u2;
if(c2 >= 0b11110000)
u2 = s2.substr(i2, 4);
else if(c2 >= 0b11100000)
u2 = s2.substr(i2, 3);
else if(c2 >= 0b11000000)
u2 = s2.substr(i2, 2);
else
u2 = s2[i2];
i1 += u1.bytes() - 1;
i2 += u2.bytes() - 1;
u1 = u1.lowercase();
u2 = u2.lowercase();
if(u1 < u2)
return -1;
else if(u1 == u2)
return 0;
else
return 1;
}
int Natural::compare_numbers(size_t &i1, size_t &i2, const std::string &s1, const std::string &s2) {
int result = 0;
while(true) {
if(i1 >= s1.size() || !is_digit(s1[i1])) {
if(i2 >= s2.size() || !is_digit(s2[i2])) // a and b has equal number of digits
return result;
return -1; // a has fewer digits
}
if(i2 >= s2.size() || !is_digit(s2[i2]))
return 1; // b has fewer digits
if(result == 0) {
if(s1[i1] < s2[i2])
result = -1;
if(s1[i1] > s2[i2])
result = 1;
}
++i1;
++i2;
}
}
int Natural::compare(const std::string &s1, const std::string &s2) {
size_t i1 = 0;
size_t i2 = 0;
while(i1 < s1.size() && i2 < s2.size()) {
if(is_digit(s1[i1]) && !is_digit(s2[i2]))
return -1;
if(!is_digit(s1[i1]) && is_digit(s2[i2]))
return 1;
if(!is_digit(s1[i1]) && !is_digit(s2[i2])) {
auto result = compare_characters(i1, i2, s1, s2);
if(result != 0)
return result;
}
else {
auto result = compare_numbers(i1, i2, s1, s2);
if(result != 0)
return result;
}
}
if(i1 == s1.size()) {
if(i2 == s2.size())
return 0;
return -1;
}
return 1;
}

12
src/utility.hpp

@ -35,3 +35,15 @@ std::string to_hex_string(const std::string &input);
/// Returns -1 if lhs is smaller than rhs, 0 if equal, and 1 if lhs is larger than rhs
int version_compare(const std::string &lhs, const std::string &rhs);
class Natural {
static bool is_digit(char chr);
static int compare_characters(size_t &i1, size_t &i2, const std::string &s1, const std::string &s2);
static int compare_numbers(size_t &i1, size_t &i2, const std::string &s1, const std::string &s2);
public:
/// Natural comparison supporting UTF-8 and locale
static int compare(const std::string &s1, const std::string &s2);
};

21
src/window.cpp

@ -17,6 +17,7 @@
#include "project.hpp"
#include "selection_dialog.hpp"
#include "terminal.hpp"
#include "utility.hpp"
#include <boost/algorithm/string.hpp>
Window::Window() {
@ -186,7 +187,7 @@ void Window::configure() {
}
else
css_provider_theme = Gtk::CssProvider::get_named(Config::get().theme.name, Config::get().theme.variant);
//TODO: add check if theme exists, or else write error to terminal
// TODO: add check if theme exists, or else write error to terminal
Gtk::StyleContext::add_provider_for_screen(screen, css_provider_theme, GTK_STYLE_PROVIDER_PRIORITY_SETTINGS);
static Glib::RefPtr<Gtk::CssProvider> css_provider_fonts;
@ -992,12 +993,18 @@ void Window::set_menu_actions() {
it.no_push();
continue;
}
auto row = filesystem::get_relative_path(path, view_folder).string();
SelectionDialog::get()->add_row(open_files.count(path.string()) ? "<b>" + row + "</b>" : row);
files.emplace_back(path);
}
std::sort(files.begin(), files.end(), [](const boost::filesystem::path &path1, const boost::filesystem::path &path2) {
return Natural::compare(path1.string(), path2.string()) < 0;
});
for(auto &file : files) {
auto row = filesystem::get_relative_path(file, view_folder).string();
SelectionDialog::get()->add_row(open_files.count(file.string()) ? "<b>" + row + "</b>" : row);
}
if(files.empty()) {
Info::get().print("No files found in current project");
return;
@ -1997,7 +2004,7 @@ bool Window::on_key_press_event(GdkEventKey *event) {
if(event->keyval == GDK_KEY_Escape)
EntryBox::get().hide();
#ifdef __APPLE__ //For Apple's Command-left, right, up, down keys
#ifdef __APPLE__ // For Apple's Command-left, right, up, down keys
else if((event->state & GDK_META_MASK) > 0 && (event->state & GDK_MOD1_MASK) == 0) {
if(event->keyval == GDK_KEY_Left || event->keyval == GDK_KEY_KP_Left) {
event->keyval = GDK_KEY_Home;
@ -2291,8 +2298,8 @@ void Window::rename_token_entry() {
auto spelling = std::make_shared<std::string>(view->get_token_spelling());
if(!spelling->empty()) {
EntryBox::get().entries.emplace_back(*spelling, [view, spelling, iter = view->get_buffer()->get_insert()->get_iter()](const std::string &content) {
//TODO: gtk needs a way to check if iter is valid without dumping g_error message
//iter->get_buffer() will print such a message, but no segfault will occur
// TODO: gtk needs a way to check if iter is valid without dumping g_error message
// iter->get_buffer() will print such a message, but no segfault will occur
if(Notebook::get().get_current_view() == view && content != *spelling && iter.get_buffer() && view->get_buffer()->get_insert()->get_iter() == iter)
view->rename_similar_tokens(content);
else

20
tests/utility_test.cpp

@ -180,4 +180,22 @@ int main() {
g_assert(version_compare("1.2.3x", "1.2.3z") == -1);
g_assert(version_compare("1.2.3a", "1.2.3z") == -1);
}
}
{
g_assert(Natural::compare("0", "0") == 0);
g_assert(Natural::compare("0", "1") == -1);
g_assert(Natural::compare("1", "0") == 1);
g_assert(Natural::compare("10", "9") == 1);
g_assert(Natural::compare("9", "10") == -1);
g_assert(Natural::compare("99", "99") == 0);
g_assert(Natural::compare("a", "a") == 0);
g_assert(Natural::compare("a", "b") == -1);
g_assert(Natural::compare("b", "a") == 1);
g_assert(Natural::compare("", "") == 0);
g_assert(Natural::compare("z", "") == 1);
g_assert(Natural::compare("", "z") == -1);
g_assert(Natural::compare("zz", "z") == 1);
g_assert(Natural::compare("z", "zz") == -1);
g_assert(Natural::compare("z2", "z11") == -1);
g_assert(Natural::compare("z11", "z2") == 1);
}
}

Loading…
Cancel
Save