Browse Source

Directories and files in directory view are now naturally sorted

merge-requests/393/head
eidheim 7 years ago
parent
commit
d9e49d8ba2
  1. 140
      src/directories.cc
  2. 4
      src/directories.h

140
src/directories.cc

@ -4,6 +4,7 @@
#include "notebook.h" #include "notebook.h"
#include "source.h" #include "source.h"
#include "terminal.h" #include "terminal.h"
#include "utility.h"
#include <algorithm> #include <algorithm>
bool Directories::TreeStore::row_drop_possible_vfunc(const Gtk::TreeModel::Path &path, const Gtk::SelectionData &selection_data) const { bool Directories::TreeStore::row_drop_possible_vfunc(const Gtk::TreeModel::Path &path, const Gtk::SelectionData &selection_data) const {
@ -119,7 +120,129 @@ Directories::Directories() : Gtk::ListViewText(1) {
get_style_context()->add_class("juci_directories"); get_style_context()->add_class("juci_directories");
tree_store->set_sort_column(column_record.id, Gtk::SortType::SORT_ASCENDING); 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::toupper(s1[i1]);
auto bt = std::toupper(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];
u1 = u1.uppercase();
u2 = u2.uppercase();
i1 += u1.bytes() - 1;
i2 += u2.bytes() - 1;
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())
return -1;
if(name2.empty())
return 1;
std::string prefix1, prefix2;
prefix1 += it1->get_value(column_record.is_directory) ? 'a' : 'b';
prefix2 += it2->get_value(column_record.is_directory) ? 'a' : 'b';
prefix1 += name1[0] == '.' ? 'a' : 'b';
prefix2 += name2[0] == '.' ? 'a' : 'b';
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); set_search_column(column_record.name);
@ -585,24 +708,19 @@ void Directories::add_or_update_path(const boost::filesystem::path &dir_path, co
if(!already_added) { if(!already_added) {
auto child = tree_store->append(children); auto child = tree_store->append(children);
not_deleted.emplace(filename); not_deleted.emplace(filename);
auto is_directory = boost::filesystem::is_directory(it->path());
child->set_value(column_record.is_directory, is_directory);
child->set_value(column_record.name, filename); child->set_value(column_record.name, filename);
child->set_value(column_record.markup, Glib::Markup::escape_text(filename)); child->set_value(column_record.markup, Glib::Markup::escape_text(filename));
child->set_value(column_record.path, it->path()); child->set_value(column_record.path, it->path());
auto sortable_filename = filename; if(is_directory) {
for(auto &e : sortable_filename) {
if(e == '.')
e = '$';
}
if(boost::filesystem::is_directory(it->path())) {
child->set_value(column_record.id, '1' + sortable_filename);
auto grandchild = tree_store->append(child->children()); auto grandchild = tree_store->append(child->children());
grandchild->set_value(column_record.is_directory, false);
grandchild->set_value(column_record.name, std::string("(empty)")); grandchild->set_value(column_record.name, std::string("(empty)"));
grandchild->set_value(column_record.markup, Glib::Markup::escape_text("(empty)")); grandchild->set_value(column_record.markup, Glib::Markup::escape_text("(empty)"));
grandchild->set_value(column_record.type, PathType::UNKNOWN); grandchild->set_value(column_record.type, PathType::UNKNOWN);
} }
else { else {
child->set_value(column_record.id, '2' + sortable_filename);
auto language = Source::guess_language(it->path().filename()); auto language = Source::guess_language(it->path().filename());
if(!language) if(!language)
child->set_value(column_record.type, PathType::UNKNOWN); child->set_value(column_record.type, PathType::UNKNOWN);
@ -620,6 +738,7 @@ void Directories::add_or_update_path(const boost::filesystem::path &dir_path, co
} }
if(!children) { if(!children) {
auto child = tree_store->append(children); auto child = tree_store->append(children);
child->set_value(column_record.is_directory, false);
child->set_value(column_record.name, std::string("(empty)")); child->set_value(column_record.name, std::string("(empty)"));
child->set_value(column_record.markup, Glib::Markup::escape_text("(empty)")); child->set_value(column_record.markup, Glib::Markup::escape_text("(empty)"));
child->set_value(column_record.type, PathType::UNKNOWN); child->set_value(column_record.type, PathType::UNKNOWN);
@ -646,6 +765,7 @@ void Directories::remove_path(const boost::filesystem::path &dir_path) {
tree_store->erase(children.begin()); tree_store->erase(children.begin());
} }
auto child = tree_store->append(children); auto child = tree_store->append(children);
child->set_value(column_record.is_directory, false);
child->set_value(column_record.name, std::string("(empty)")); child->set_value(column_record.name, std::string("(empty)"));
child->set_value(column_record.markup, Glib::Markup::escape_text("(empty)")); child->set_value(column_record.markup, Glib::Markup::escape_text("(empty)"));
child->set_value(column_record.type, PathType::UNKNOWN); child->set_value(column_record.type, PathType::UNKNOWN);

4
src/directories.h

@ -34,13 +34,13 @@ class Directories : public Gtk::ListViewText {
class ColumnRecord : public Gtk::TreeModel::ColumnRecord { class ColumnRecord : public Gtk::TreeModel::ColumnRecord {
public: public:
ColumnRecord() { ColumnRecord() {
add(id); add(is_directory);
add(name); add(name);
add(markup); add(markup);
add(path); add(path);
add(type); add(type);
} }
Gtk::TreeModelColumn<std::string> id; Gtk::TreeModelColumn<bool> is_directory;
Gtk::TreeModelColumn<std::string> name; Gtk::TreeModelColumn<std::string> name;
Gtk::TreeModelColumn<Glib::ustring> markup; Gtk::TreeModelColumn<Glib::ustring> markup;
Gtk::TreeModelColumn<boost::filesystem::path> path; Gtk::TreeModelColumn<boost::filesystem::path> path;

Loading…
Cancel
Save