Browse Source

Added natural sort to Find File

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

105
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())

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

13
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() {
@ -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;

18
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