Compare commits
5 Commits
f3fffe4b25
...
36454fb90c
Author | SHA1 | Date | |
---|---|---|---|
36454fb90c | |||
6b1ff048b8 | |||
3790f8dfa0 | |||
5c086016b5 | |||
232ecffa2d |
118
sdn.cpp
118
sdn.cpp
@ -29,6 +29,7 @@
|
|||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
@ -279,6 +280,12 @@ fun decolor (cchar_t &ch) {
|
|||||||
setcchar (&ch, c, attrs &~ A_REVERSE, 0, nullptr);
|
setcchar (&ch, c, attrs &~ A_REVERSE, 0, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun invert (cchar_t &ch) {
|
||||||
|
wchar_t c[CCHARW_MAX]; attr_t attrs; short pair;
|
||||||
|
getcchar (&ch, c, &attrs, &pair, nullptr);
|
||||||
|
setcchar (&ch, c, attrs ^ A_REVERSE, 0, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
fun apply_attrs (const wstring &w, attr_t attrs) -> ncstring {
|
fun apply_attrs (const wstring &w, attr_t attrs) -> ncstring {
|
||||||
ncstring res;
|
ncstring res;
|
||||||
for (auto c : w)
|
for (auto c : w)
|
||||||
@ -372,11 +379,11 @@ enum { ALT = 1 << 24, SYM = 1 << 25 }; // Outside the range of Unicode
|
|||||||
#define CTRL 31 &
|
#define CTRL 31 &
|
||||||
|
|
||||||
#define ACTIONS(XX) XX(NONE) XX(HELP) XX(QUIT) XX(QUIT_NO_CHDIR) \
|
#define ACTIONS(XX) XX(NONE) XX(HELP) XX(QUIT) XX(QUIT_NO_CHDIR) \
|
||||||
XX(CHOOSE) XX(CHOOSE_FULL) \
|
XX(CHOOSE) XX(CHOOSE_FULL) XX(SORT_LEFT) XX(SORT_RIGHT) \
|
||||||
XX(UP) XX(DOWN) XX(TOP) XX(BOTTOM) XX(PAGE_PREVIOUS) XX(PAGE_NEXT) \
|
XX(UP) XX(DOWN) XX(TOP) XX(BOTTOM) XX(PAGE_PREVIOUS) XX(PAGE_NEXT) \
|
||||||
XX(SCROLL_UP) XX(SCROLL_DOWN) XX(CHDIR) XX(GO_START) XX(GO_HOME) \
|
XX(SCROLL_UP) XX(SCROLL_DOWN) XX(CHDIR) XX(GO_START) XX(GO_HOME) \
|
||||||
XX(SEARCH) XX(RENAME) XX(RENAME_PREFILL) \
|
XX(SEARCH) XX(RENAME) XX(RENAME_PREFILL) \
|
||||||
XX(TOGGLE_FULL) XX(REDRAW) XX(RELOAD) \
|
XX(TOGGLE_FULL) XX(REVERSE_SORT) XX(SHOW_HIDDEN) XX(REDRAW) XX(RELOAD) \
|
||||||
XX(INPUT_ABORT) XX(INPUT_CONFIRM) XX(INPUT_B_DELETE)
|
XX(INPUT_ABORT) XX(INPUT_CONFIRM) XX(INPUT_B_DELETE)
|
||||||
|
|
||||||
#define XX(name) ACTION_ ## name,
|
#define XX(name) ACTION_ ## name,
|
||||||
@ -393,6 +400,7 @@ static map<wint_t, action> g_normal_actions {
|
|||||||
{'q', ACTION_QUIT}, {ALT | 'q', ACTION_QUIT_NO_CHDIR},
|
{'q', ACTION_QUIT}, {ALT | 'q', ACTION_QUIT_NO_CHDIR},
|
||||||
// M-o ought to be the same shortcut the navigator is launched with
|
// M-o ought to be the same shortcut the navigator is launched with
|
||||||
{ALT | 'o', ACTION_QUIT},
|
{ALT | 'o', ACTION_QUIT},
|
||||||
|
{'<', ACTION_SORT_LEFT}, {'>', ACTION_SORT_RIGHT},
|
||||||
{'k', ACTION_UP}, {CTRL 'p', ACTION_UP}, {KEY (UP), ACTION_UP},
|
{'k', ACTION_UP}, {CTRL 'p', ACTION_UP}, {KEY (UP), ACTION_UP},
|
||||||
{'j', ACTION_DOWN}, {CTRL 'n', ACTION_DOWN}, {KEY (DOWN), ACTION_DOWN},
|
{'j', ACTION_DOWN}, {CTRL 'n', ACTION_DOWN}, {KEY (DOWN), ACTION_DOWN},
|
||||||
{'g', ACTION_TOP}, {ALT | '<', ACTION_TOP}, {KEY (HOME), ACTION_TOP},
|
{'g', ACTION_TOP}, {ALT | '<', ACTION_TOP}, {KEY (HOME), ACTION_TOP},
|
||||||
@ -403,6 +411,7 @@ static map<wint_t, action> g_normal_actions {
|
|||||||
{'/', ACTION_SEARCH}, {'s', ACTION_SEARCH},
|
{'/', ACTION_SEARCH}, {'s', ACTION_SEARCH},
|
||||||
{ALT | 'e', ACTION_RENAME_PREFILL}, {'e', ACTION_RENAME},
|
{ALT | 'e', ACTION_RENAME_PREFILL}, {'e', ACTION_RENAME},
|
||||||
{'t', ACTION_TOGGLE_FULL}, {ALT | 't', ACTION_TOGGLE_FULL},
|
{'t', ACTION_TOGGLE_FULL}, {ALT | 't', ACTION_TOGGLE_FULL},
|
||||||
|
{'R', ACTION_REVERSE_SORT}, {ALT | '.', ACTION_SHOW_HIDDEN},
|
||||||
{CTRL 'L', ACTION_REDRAW}, {'r', ACTION_RELOAD},
|
{CTRL 'L', ACTION_REDRAW}, {'r', ACTION_RELOAD},
|
||||||
};
|
};
|
||||||
static map<wint_t, action> g_input_actions {
|
static map<wint_t, action> g_input_actions {
|
||||||
@ -443,12 +452,6 @@ struct entry {
|
|||||||
|
|
||||||
enum { MODES, USER, GROUP, SIZE, MTIME, FILENAME, COLUMNS };
|
enum { MODES, USER, GROUP, SIZE, MTIME, FILENAME, COLUMNS };
|
||||||
ncstring cols[COLUMNS];
|
ncstring cols[COLUMNS];
|
||||||
|
|
||||||
auto operator< (const entry &other) -> bool {
|
|
||||||
auto a = S_ISDIR (info.st_mode);
|
|
||||||
auto b = S_ISDIR (other.info.st_mode);
|
|
||||||
return (a && !b) || (a == b && filename < other.filename);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct level {
|
struct level {
|
||||||
@ -464,7 +467,11 @@ static struct {
|
|||||||
int offset, cursor; ///< Scroll offset and cursor position
|
int offset, cursor; ///< Scroll offset and cursor position
|
||||||
bool full_view; ///< Show extended information
|
bool full_view; ///< Show extended information
|
||||||
bool gravity; ///< Entries are shoved to the bottom
|
bool gravity; ///< Entries are shoved to the bottom
|
||||||
|
bool reverse_sort; ///< Reverse sort
|
||||||
|
bool show_hidden; ///< Show hidden files
|
||||||
int max_widths[entry::COLUMNS]; ///< Column widths
|
int max_widths[entry::COLUMNS]; ///< Column widths
|
||||||
|
int sort_column = entry::FILENAME; ///< Sorting column
|
||||||
|
int sort_flash_ttl; ///< Sorting column flash TTL
|
||||||
|
|
||||||
wstring message; ///< Message for the user
|
wstring message; ///< Message for the user
|
||||||
int message_ttl; ///< Time to live for the message
|
int message_ttl; ///< Time to live for the message
|
||||||
@ -659,6 +666,8 @@ fun update () {
|
|||||||
for (int col = start_column; col < entry::COLUMNS; col++) {
|
for (int col = start_column; col < entry::COLUMNS; col++) {
|
||||||
const auto &field = g.entries[index].cols[col];
|
const auto &field = g.entries[index].cols[col];
|
||||||
auto aligned = align (field, alignment[col] * g.max_widths[col]);
|
auto aligned = align (field, alignment[col] * g.max_widths[col]);
|
||||||
|
if (g.sort_flash_ttl && col == g.sort_column)
|
||||||
|
for_each (begin (aligned), end (aligned), invert);
|
||||||
if (selected)
|
if (selected)
|
||||||
for_each (begin (aligned), end (aligned), decolor);
|
for_each (begin (aligned), end (aligned), decolor);
|
||||||
used += print (aligned + apply_attrs (L" ", 0), COLS - used);
|
used += print (aligned + apply_attrs (L" ", 0), COLS - used);
|
||||||
@ -667,6 +676,8 @@ fun update () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto bar = apply_attrs (to_wide (g.cwd), g.attrs[g.AT_CWD]);
|
auto bar = apply_attrs (to_wide (g.cwd), g.attrs[g.AT_CWD]);
|
||||||
|
if (!g.show_hidden)
|
||||||
|
bar += apply_attrs (L" (hidden)", 0);
|
||||||
if (g.out_of_date)
|
if (g.out_of_date)
|
||||||
bar += apply_attrs (L" [+]", 0);
|
bar += apply_attrs (L" [+]", 0);
|
||||||
|
|
||||||
@ -689,6 +700,39 @@ fun update () {
|
|||||||
refresh ();
|
refresh ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun operator< (const entry &e1, const entry &e2) -> bool {
|
||||||
|
auto t1 = make_tuple (e1.filename != "..", !S_ISDIR (e1.info.st_mode));
|
||||||
|
auto t2 = make_tuple (e2.filename != "..", !S_ISDIR (e2.info.st_mode));
|
||||||
|
if (t1 != t2)
|
||||||
|
return t1 < t2;
|
||||||
|
|
||||||
|
const auto &a = g.reverse_sort ? e2 : e1;
|
||||||
|
const auto &b = g.reverse_sort ? e1 : e2;
|
||||||
|
switch (g.sort_column) {
|
||||||
|
case entry::MODES:
|
||||||
|
if (a.info.st_mode != b.info.st_mode)
|
||||||
|
return a.info.st_mode < b.info.st_mode;
|
||||||
|
break;
|
||||||
|
case entry::USER:
|
||||||
|
if (a.info.st_uid != b.info.st_uid)
|
||||||
|
return a.info.st_uid < b.info.st_uid;
|
||||||
|
break;
|
||||||
|
case entry::GROUP:
|
||||||
|
if (a.info.st_gid != b.info.st_gid)
|
||||||
|
return a.info.st_gid < b.info.st_gid;
|
||||||
|
break;
|
||||||
|
case entry::SIZE:
|
||||||
|
if (a.info.st_size != b.info.st_size)
|
||||||
|
return a.info.st_size < b.info.st_size;
|
||||||
|
break;
|
||||||
|
case entry::MTIME:
|
||||||
|
if (a.info.st_mtime != b.info.st_mtime)
|
||||||
|
return a.info.st_mtime < b.info.st_mtime;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return a.filename < b.filename;
|
||||||
|
}
|
||||||
|
|
||||||
fun reload () {
|
fun reload () {
|
||||||
g.unames.clear();
|
g.unames.clear();
|
||||||
while (auto *ent = getpwent ()) g.unames.emplace(ent->pw_uid, ent->pw_name);
|
while (auto *ent = getpwent ()) g.unames.emplace(ent->pw_uid, ent->pw_name);
|
||||||
@ -698,20 +742,31 @@ fun reload () {
|
|||||||
while (auto *ent = getgrent ()) g.gnames.emplace(ent->gr_gid, ent->gr_name);
|
while (auto *ent = getgrent ()) g.gnames.emplace(ent->gr_gid, ent->gr_name);
|
||||||
endgrent();
|
endgrent();
|
||||||
|
|
||||||
|
string anchor;
|
||||||
|
if (!g.entries.empty ())
|
||||||
|
anchor = g.entries[g.cursor].filename;
|
||||||
|
|
||||||
|
auto old_cwd = g.cwd;
|
||||||
auto now = time (NULL); g.now = *localtime (&now);
|
auto now = time (NULL); g.now = *localtime (&now);
|
||||||
char buf[4096]; g.cwd = getcwd (buf, sizeof buf);
|
char buf[4096]; g.cwd = getcwd (buf, sizeof buf);
|
||||||
|
|
||||||
auto dir = opendir (".");
|
auto dir = opendir (".");
|
||||||
g.entries.clear ();
|
g.entries.clear ();
|
||||||
while (auto f = readdir (dir)) {
|
while (auto f = readdir (dir)) {
|
||||||
|
string name = f->d_name;
|
||||||
// Two dots are for navigation but this ain't as useful
|
// Two dots are for navigation but this ain't as useful
|
||||||
if (f->d_name != string ("."))
|
if (name != "." && (name == ".." || name[0] != '.' || g.show_hidden))
|
||||||
g.entries.push_back (make_entry (f));
|
g.entries.push_back (make_entry (f));
|
||||||
}
|
}
|
||||||
closedir (dir);
|
closedir (dir);
|
||||||
sort (begin (g.entries), end (g.entries));
|
sort (begin (g.entries), end (g.entries));
|
||||||
g.out_of_date = false;
|
g.out_of_date = false;
|
||||||
|
|
||||||
|
if (g.cwd == old_cwd && !anchor.empty ()) {
|
||||||
|
for (size_t i = 0; i < g.entries.size (); i++)
|
||||||
|
if (g.entries[i].filename == anchor)
|
||||||
|
g.cursor = i;
|
||||||
|
}
|
||||||
for (int col = 0; col < entry::COLUMNS; col++) {
|
for (int col = 0; col < entry::COLUMNS; col++) {
|
||||||
auto &longest = g.max_widths[col] = 0;
|
auto &longest = g.max_widths[col] = 0;
|
||||||
for (const auto &entry : g.entries)
|
for (const auto &entry : g.entries)
|
||||||
@ -917,6 +972,17 @@ fun handle (wint_t c) -> bool {
|
|||||||
case ACTION_QUIT:
|
case ACTION_QUIT:
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
case ACTION_SORT_LEFT:
|
||||||
|
g.sort_column = (g.sort_column + entry::COLUMNS - 1) % entry::COLUMNS;
|
||||||
|
g.sort_flash_ttl = 2;
|
||||||
|
reload ();
|
||||||
|
break;
|
||||||
|
case ACTION_SORT_RIGHT:
|
||||||
|
g.sort_column = (g.sort_column + entry::COLUMNS + 1) % entry::COLUMNS;
|
||||||
|
g.sort_flash_ttl = 2;
|
||||||
|
reload ();
|
||||||
|
break;
|
||||||
|
|
||||||
case ACTION_UP:
|
case ACTION_UP:
|
||||||
g.cursor--;
|
g.cursor--;
|
||||||
break;
|
break;
|
||||||
@ -980,6 +1046,14 @@ fun handle (wint_t c) -> bool {
|
|||||||
case ACTION_TOGGLE_FULL:
|
case ACTION_TOGGLE_FULL:
|
||||||
g.full_view = !g.full_view;
|
g.full_view = !g.full_view;
|
||||||
break;
|
break;
|
||||||
|
case ACTION_REVERSE_SORT:
|
||||||
|
g.reverse_sort = !g.reverse_sort;
|
||||||
|
reload ();
|
||||||
|
break;
|
||||||
|
case ACTION_SHOW_HIDDEN:
|
||||||
|
g.show_hidden = !g.show_hidden;
|
||||||
|
reload ();
|
||||||
|
break;
|
||||||
case ACTION_REDRAW:
|
case ACTION_REDRAW:
|
||||||
clear ();
|
clear ();
|
||||||
break;
|
break;
|
||||||
@ -1224,7 +1298,7 @@ fun load_bindings () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun load_history_level (const vector<string> &v) {
|
fun load_history_level (const vector<string> &v) {
|
||||||
if (v.size () != 6)
|
if (v.size () != 7)
|
||||||
return;
|
return;
|
||||||
// Not checking the hostname and parent PID right now since we can't merge
|
// Not checking the hostname and parent PID right now since we can't merge
|
||||||
g.levels.push_back ({stoi (v.at (4)), stoi (v.at (5)), v.at (3), v.at (6)});
|
g.levels.push_back ({stoi (v.at (4)), stoi (v.at (5)), v.at (3), v.at (6)});
|
||||||
@ -1240,10 +1314,16 @@ fun load_config () {
|
|||||||
if (tokens.empty ())
|
if (tokens.empty ())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (tokens.front () == "full-view")
|
if (tokens.front () == "full-view" && tokens.size () > 1)
|
||||||
g.full_view = tokens.size () > 1 && tokens.at (1) == "1";
|
g.full_view = tokens.at (1) == "1";
|
||||||
else if (tokens.front () == "gravity")
|
else if (tokens.front () == "gravity" && tokens.size () > 1)
|
||||||
g.gravity = tokens.size () > 1 && tokens.at (1) == "1";
|
g.gravity = tokens.at (1) == "1";
|
||||||
|
else if (tokens.front () == "reverse-sort" && tokens.size () > 1)
|
||||||
|
g.reverse_sort = tokens.at (1) == "1";
|
||||||
|
else if (tokens.front () == "show-hidden" && tokens.size () > 1)
|
||||||
|
g.show_hidden = tokens.at (1) == "1";
|
||||||
|
else if (tokens.front () == "sort-column" && tokens.size () > 1)
|
||||||
|
g.sort_column = stoi (tokens.at (1));
|
||||||
else if (tokens.front () == "history")
|
else if (tokens.front () == "history")
|
||||||
load_history_level (tokens);
|
load_history_level (tokens);
|
||||||
}
|
}
|
||||||
@ -1254,8 +1334,12 @@ fun save_config () {
|
|||||||
if (!config)
|
if (!config)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
write_line (*config, {"full-view", g.full_view ? "1" : "0"});
|
write_line (*config, {"full-view", g.full_view ? "1" : "0"});
|
||||||
write_line (*config, {"gravity", g.gravity ? "1" : "0"});
|
write_line (*config, {"gravity", g.gravity ? "1" : "0"});
|
||||||
|
write_line (*config, {"reverse-sort", g.reverse_sort ? "1" : "0"});
|
||||||
|
write_line (*config, {"show-hidden", g.show_hidden ? "1" : "0"});
|
||||||
|
|
||||||
|
write_line (*config, {"sort-column", to_string (g.sort_column)});
|
||||||
|
|
||||||
char hostname[256];
|
char hostname[256];
|
||||||
if (gethostname (hostname, sizeof hostname))
|
if (gethostname (hostname, sizeof hostname))
|
||||||
@ -1319,6 +1403,8 @@ int main (int argc, char *argv[]) {
|
|||||||
wint_t c;
|
wint_t c;
|
||||||
while (!read_key (c) || handle (c)) {
|
while (!read_key (c) || handle (c)) {
|
||||||
inotify_check ();
|
inotify_check ();
|
||||||
|
if (g.sort_flash_ttl && !--g.sort_flash_ttl)
|
||||||
|
update ();
|
||||||
if (g.message_ttl && !--g.message_ttl) {
|
if (g.message_ttl && !--g.message_ttl) {
|
||||||
g.message.clear ();
|
g.message.clear ();
|
||||||
update ();
|
update ();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user