Compare commits
4 Commits
1ba2709fda
...
6f66aa3c06
Author | SHA1 | Date | |
---|---|---|---|
6f66aa3c06 | |||
86b520006c | |||
2484c94b39 | |||
6e34f480a9 |
393
sdn.cpp
393
sdn.cpp
@ -26,6 +26,7 @@
|
|||||||
#include <cwchar>
|
#include <cwchar>
|
||||||
#include <climits>
|
#include <climits>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
@ -264,6 +265,8 @@ fun decode_attrs (const vector<string> &attrs) -> chtype {
|
|||||||
|
|
||||||
// --- Application -------------------------------------------------------------
|
// --- Application -------------------------------------------------------------
|
||||||
|
|
||||||
|
enum { ALT = 1 << 24, SYM = 1 << 25 }; // Outside the range of Unicode
|
||||||
|
#define KEY(name) (SYM | KEY_ ## name)
|
||||||
#define CTRL 31 &
|
#define CTRL 31 &
|
||||||
|
|
||||||
struct entry {
|
struct entry {
|
||||||
@ -280,6 +283,47 @@ struct entry {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define ACTIONS(XX) XX(NONE) XX(CHOOSE) XX(CHOOSE_FULL) XX(QUIT) \
|
||||||
|
XX(UP) XX(DOWN) XX(TOP) XX(BOTTOM) XX(PAGE_PREVIOUS) XX(PAGE_NEXT) \
|
||||||
|
XX(SCROLL_UP) XX(SCROLL_DOWN) XX(GO_START) XX(GO_HOME) \
|
||||||
|
XX(SEARCH) XX(RENAME) XX(RENAME_PREFILL) \
|
||||||
|
XX(TOGGLE_FULL) XX(REDRAW) XX(RELOAD) \
|
||||||
|
XX(INPUT_ABORT) XX(INPUT_CONFIRM) XX(INPUT_B_DELETE)
|
||||||
|
|
||||||
|
#define XX(name) ACTION_ ## name,
|
||||||
|
enum action { ACTIONS(XX) ACTION_COUNT };
|
||||||
|
#undef XX
|
||||||
|
|
||||||
|
#define XX(name) #name,
|
||||||
|
static const char *g_action_names[] = {ACTIONS(XX)};
|
||||||
|
#undef XX
|
||||||
|
|
||||||
|
static map<wint_t, action> g_normal_actions = {
|
||||||
|
{ALT | '\r', ACTION_CHOOSE_FULL}, {ALT | KEY (ENTER), ACTION_CHOOSE_FULL},
|
||||||
|
{'\r', ACTION_CHOOSE}, {KEY (ENTER), ACTION_CHOOSE},
|
||||||
|
// M-o ought to be the same shortcut the navigator is launched with
|
||||||
|
{ALT | 'o', ACTION_QUIT}, {'q', ACTION_QUIT},
|
||||||
|
{'k', ACTION_UP}, {CTRL 'p', ACTION_UP}, {KEY (UP), ACTION_UP},
|
||||||
|
{'j', ACTION_DOWN}, {CTRL 'n', ACTION_DOWN}, {KEY (DOWN), ACTION_DOWN},
|
||||||
|
{'g', ACTION_TOP}, {ALT | '<', ACTION_TOP}, {KEY (HOME), ACTION_TOP},
|
||||||
|
{'G', ACTION_BOTTOM}, {ALT | '>', ACTION_BOTTOM}, {KEY(END), ACTION_BOTTOM},
|
||||||
|
{KEY (PPAGE), ACTION_PAGE_PREVIOUS}, {KEY (NPAGE), ACTION_PAGE_NEXT},
|
||||||
|
{CTRL 'y', ACTION_SCROLL_UP}, {CTRL 'e', ACTION_SCROLL_DOWN},
|
||||||
|
{'&', ACTION_GO_START}, {'~', ACTION_GO_HOME},
|
||||||
|
{'/', ACTION_SEARCH}, {'s', ACTION_SEARCH},
|
||||||
|
{ALT | 'e', ACTION_RENAME_PREFILL}, {'e', ACTION_RENAME},
|
||||||
|
{'t', ACTION_TOGGLE_FULL}, {ALT | 't', ACTION_TOGGLE_FULL},
|
||||||
|
{CTRL 'L', ACTION_REDRAW}, {'r', ACTION_RELOAD},
|
||||||
|
};
|
||||||
|
static map<wint_t, action> g_input_actions = {
|
||||||
|
{27, ACTION_INPUT_ABORT}, {CTRL 'g', ACTION_INPUT_ABORT},
|
||||||
|
{L'\r', ACTION_INPUT_CONFIRM}, {KEY (ENTER), ACTION_INPUT_CONFIRM},
|
||||||
|
{KEY (BACKSPACE), ACTION_INPUT_B_DELETE},
|
||||||
|
};
|
||||||
|
static const map<string, map<wint_t, action>*> g_binding_contexts = {
|
||||||
|
{"normal", &g_normal_actions}, {"input", &g_input_actions},
|
||||||
|
};
|
||||||
|
|
||||||
#define LS(XX) XX(NORMAL, "no") XX(FILE, "fi") XX(RESET, "rs") \
|
#define LS(XX) XX(NORMAL, "no") XX(FILE, "fi") XX(RESET, "rs") \
|
||||||
XX(DIRECTORY, "di") XX(SYMLINK, "ln") XX(MULTIHARDLINK, "mh") \
|
XX(DIRECTORY, "di") XX(SYMLINK, "ln") XX(MULTIHARDLINK, "mh") \
|
||||||
XX(FIFO, "pi") XX(SOCKET, "so") XX(DOOR, "do") XX(BLOCK, "bd") \
|
XX(FIFO, "pi") XX(SOCKET, "so") XX(DOOR, "do") XX(BLOCK, "bd") \
|
||||||
@ -295,6 +339,14 @@ enum { LS(XX) LS_COUNT };
|
|||||||
static const char *g_ls_colors[] = {LS(XX)};
|
static const char *g_ls_colors[] = {LS(XX)};
|
||||||
#undef XX
|
#undef XX
|
||||||
|
|
||||||
|
struct stringcaseless {
|
||||||
|
bool operator () (const string &a, const string &b) const {
|
||||||
|
const auto &c = locale::classic();
|
||||||
|
return lexicographical_compare (begin (a), end (a), begin (b), end (b),
|
||||||
|
[&](char m, char n) { return tolower (m, c) < tolower (n, c); });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
static struct {
|
static struct {
|
||||||
string cwd; ///< Current working directory
|
string cwd; ///< Current working directory
|
||||||
string start_dir; ///< Starting directory
|
string start_dir; ///< Starting directory
|
||||||
@ -318,6 +370,9 @@ static struct {
|
|||||||
|
|
||||||
map<int, chtype> ls_colors; ///< LS_COLORS decoded
|
map<int, chtype> ls_colors; ///< LS_COLORS decoded
|
||||||
map<string, chtype> ls_exts; ///< LS_COLORS file extensions
|
map<string, chtype> ls_exts; ///< LS_COLORS file extensions
|
||||||
|
bool ls_symlink_as_target; ///< ln=target in dircolors
|
||||||
|
|
||||||
|
map<string, wint_t, stringcaseless> key_names;
|
||||||
|
|
||||||
// Refreshed by reload():
|
// Refreshed by reload():
|
||||||
|
|
||||||
@ -326,17 +381,34 @@ static struct {
|
|||||||
struct tm now; ///< Current local time for display
|
struct tm now; ///< Current local time for display
|
||||||
} g;
|
} g;
|
||||||
|
|
||||||
fun ls_format (const string &filename, const struct stat &info) -> chtype {
|
// The coloring logic has been more or less exactly copied from GNU ls,
|
||||||
|
// simplified and rewritten to reflect local implementation specifics
|
||||||
|
fun ls_is_colored (int type) -> bool {
|
||||||
|
auto i = g.ls_colors.find (type);
|
||||||
|
return i != g.ls_colors.end () && i->second != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ls_format (const entry &e, bool for_target) -> chtype {
|
||||||
int type = LS_ORPHAN;
|
int type = LS_ORPHAN;
|
||||||
auto set = [&](int t) { if (g.ls_colors.count (t)) type = t; };
|
auto set = [&](int t) { if (ls_is_colored (t)) type = t; };
|
||||||
// TODO: LS_MISSING if available and this is a missing symlink target
|
|
||||||
if (S_ISREG (info.st_mode)) {
|
const auto &name = for_target
|
||||||
|
? e.target_path : e.filename;
|
||||||
|
const auto &info =
|
||||||
|
(for_target || (g.ls_symlink_as_target && e.target_info.st_mode))
|
||||||
|
? e.target_info : e.info;
|
||||||
|
|
||||||
|
if (for_target && info.st_mode == 0) {
|
||||||
|
// This differs from GNU ls: we use ORPHAN when MISSING is not set,
|
||||||
|
// but GNU ls colors by dirent::d_type
|
||||||
|
set (LS_MISSING);
|
||||||
|
} else if (S_ISREG (info.st_mode)) {
|
||||||
type = LS_FILE;
|
type = LS_FILE;
|
||||||
if (info.st_nlink > 1)
|
if (info.st_nlink > 1)
|
||||||
set (LS_MULTIHARDLINK);
|
set (LS_MULTIHARDLINK);
|
||||||
if ((info.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
|
if ((info.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
|
||||||
set (LS_EXECUTABLE);
|
set (LS_EXECUTABLE);
|
||||||
if (lgetxattr (filename.c_str (), "security.capability", NULL, 0) >= 0)
|
if (lgetxattr (name.c_str (), "security.capability", NULL, 0) >= 0)
|
||||||
set (LS_CAPABILITY);
|
set (LS_CAPABILITY);
|
||||||
if ((info.st_mode & S_ISGID))
|
if ((info.st_mode & S_ISGID))
|
||||||
set (LS_SETGID);
|
set (LS_SETGID);
|
||||||
@ -351,10 +423,10 @@ fun ls_format (const string &filename, const struct stat &info) -> chtype {
|
|||||||
if ((info.st_mode & S_ISVTX) && (info.st_mode & S_IWOTH))
|
if ((info.st_mode & S_ISVTX) && (info.st_mode & S_IWOTH))
|
||||||
set (LS_STICKY_OTHER_WRITABLE);
|
set (LS_STICKY_OTHER_WRITABLE);
|
||||||
} else if (S_ISLNK (info.st_mode)) {
|
} else if (S_ISLNK (info.st_mode)) {
|
||||||
// TODO: LS_ORPHAN when symlink target is missing and either
|
|
||||||
// a/ "li" is "target", or
|
|
||||||
// b/ LS_ORPHAN is available
|
|
||||||
type = LS_SYMLINK;
|
type = LS_SYMLINK;
|
||||||
|
if (!e.target_info.st_mode
|
||||||
|
&& (ls_is_colored (LS_ORPHAN) || g.ls_symlink_as_target))
|
||||||
|
type = LS_ORPHAN;
|
||||||
} else if (S_ISFIFO (info.st_mode)) {
|
} else if (S_ISFIFO (info.st_mode)) {
|
||||||
type = LS_FIFO;
|
type = LS_FIFO;
|
||||||
} else if (S_ISSOCK (info.st_mode)) {
|
} else if (S_ISSOCK (info.st_mode)) {
|
||||||
@ -370,9 +442,9 @@ fun ls_format (const string &filename, const struct stat &info) -> chtype {
|
|||||||
if (x != g.ls_colors.end ())
|
if (x != g.ls_colors.end ())
|
||||||
format = x->second;
|
format = x->second;
|
||||||
|
|
||||||
auto dot = filename.find_last_of ('.');
|
auto dot = name.find_last_of ('.');
|
||||||
if (dot != string::npos && type == LS_FILE) {
|
if (dot != string::npos && type == LS_FILE) {
|
||||||
const auto x = g.ls_exts.find (filename.substr (++dot));
|
const auto x = g.ls_exts.find (name.substr (++dot));
|
||||||
if (x != g.ls_exts.end ())
|
if (x != g.ls_exts.end ())
|
||||||
format = x->second;
|
format = x->second;
|
||||||
}
|
}
|
||||||
@ -395,8 +467,8 @@ fun make_entry (const struct dirent *f) -> entry {
|
|||||||
e.cols[entry::USER] = e.cols[entry::GROUP] =
|
e.cols[entry::USER] = e.cols[entry::GROUP] =
|
||||||
e.cols[entry::SIZE] = e.cols[entry::MTIME] = apply_attrs (L"?", 0);
|
e.cols[entry::SIZE] = e.cols[entry::MTIME] = apply_attrs (L"?", 0);
|
||||||
|
|
||||||
auto format = ls_format (e.filename, info);
|
e.cols[entry::FILENAME] =
|
||||||
e.cols[entry::FILENAME] = apply_attrs (to_wide (e.filename), format);
|
apply_attrs (to_wide (e.filename), ls_format (e, false));
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -407,7 +479,8 @@ fun make_entry (const struct dirent *f) -> entry {
|
|||||||
e.target_path = "?";
|
e.target_path = "?";
|
||||||
} else {
|
} else {
|
||||||
e.target_path = buf;
|
e.target_path = buf;
|
||||||
(void) lstat (buf, &e.target_info);
|
// If a symlink links to another symlink, we follow all the way
|
||||||
|
(void) stat (buf, &e.target_info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -441,11 +514,10 @@ fun make_entry (const struct dirent *f) -> entry {
|
|||||||
e.cols[entry::MTIME] = apply_attrs (to_wide (buf), 0);
|
e.cols[entry::MTIME] = apply_attrs (to_wide (buf), 0);
|
||||||
|
|
||||||
auto &fn = e.cols[entry::FILENAME] =
|
auto &fn = e.cols[entry::FILENAME] =
|
||||||
apply_attrs (to_wide (e.filename), ls_format (e.filename, info));
|
apply_attrs (to_wide (e.filename), ls_format (e, false));
|
||||||
if (!e.target_path.empty ()) {
|
if (!e.target_path.empty ()) {
|
||||||
fn.append (apply_attrs (to_wide (" -> "), 0));
|
fn.append (apply_attrs (to_wide (" -> "), 0));
|
||||||
fn.append (apply_attrs (to_wide (e.target_path),
|
fn.append (apply_attrs (to_wide (e.target_path), ls_format (e, true)));
|
||||||
ls_format (e.target_path, e.target_info)));
|
|
||||||
}
|
}
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
@ -549,30 +621,6 @@ fun search (const wstring &needle) {
|
|||||||
g.cursor = best;
|
g.cursor = best;
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handle_editor (wint_t c, bool is_char) {
|
|
||||||
if (c == 27 || c == (CTRL L'g')) {
|
|
||||||
g.editor_line.clear ();
|
|
||||||
g.editor = 0;
|
|
||||||
} else if (c == L'\r' || (!is_char && c == KEY_ENTER)) {
|
|
||||||
if (g.editor == L'e') {
|
|
||||||
auto mb = to_mb (g.editor_line);
|
|
||||||
rename (g.entries[g.cursor].filename.c_str (), mb.c_str ());
|
|
||||||
reload ();
|
|
||||||
}
|
|
||||||
g.editor_line.clear ();
|
|
||||||
g.editor = 0;
|
|
||||||
} else if (is_char) {
|
|
||||||
g.editor_line += c;
|
|
||||||
if (g.editor == L'/'
|
|
||||||
|| g.editor == L's')
|
|
||||||
search (g.editor_line);
|
|
||||||
} else if (c == KEY_BACKSPACE) {
|
|
||||||
if (!g.editor_line.empty ())
|
|
||||||
g.editor_line.erase (g.editor_line.length () - 1);
|
|
||||||
} else
|
|
||||||
beep ();
|
|
||||||
}
|
|
||||||
|
|
||||||
fun change_dir (const string& path) {
|
fun change_dir (const string& path) {
|
||||||
if (chdir (path.c_str ())) {
|
if (chdir (path.c_str ())) {
|
||||||
beep ();
|
beep ();
|
||||||
@ -594,96 +642,117 @@ fun choose (const entry &entry) -> bool {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handle (wint_t c, bool is_char) -> bool {
|
fun handle_editor (wint_t c) {
|
||||||
|
// FIXME: do not check editor actions by the prompt letter
|
||||||
|
auto i = g_input_actions.find (c);
|
||||||
|
switch (i == g_input_actions.end () ? ACTION_NONE : i->second) {
|
||||||
|
case ACTION_INPUT_ABORT:
|
||||||
|
g.editor_line.clear ();
|
||||||
|
g.editor = 0;
|
||||||
|
break;
|
||||||
|
case ACTION_INPUT_CONFIRM:
|
||||||
|
if (g.editor == L'e') {
|
||||||
|
auto mb = to_mb (g.editor_line);
|
||||||
|
rename (g.entries[g.cursor].filename.c_str (), mb.c_str ());
|
||||||
|
reload ();
|
||||||
|
}
|
||||||
|
g.editor_line.clear ();
|
||||||
|
g.editor = 0;
|
||||||
|
break;
|
||||||
|
case ACTION_INPUT_B_DELETE:
|
||||||
|
if (!g.editor_line.empty ())
|
||||||
|
g.editor_line.erase (g.editor_line.length () - 1);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (c & (ALT | SYM)) {
|
||||||
|
beep ();
|
||||||
|
} else {
|
||||||
|
g.editor_line += c;
|
||||||
|
if (g.editor == L'/'
|
||||||
|
|| g.editor == L's')
|
||||||
|
search (g.editor_line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handle (wint_t c) -> bool {
|
||||||
// If an editor is active, let it handle the key instead and eat it
|
// If an editor is active, let it handle the key instead and eat it
|
||||||
if (g.editor) {
|
if (g.editor) {
|
||||||
handle_editor (c, is_char);
|
handle_editor (c);
|
||||||
c = WEOF;
|
c = WEOF;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translate the Alt key into a bit outside the range of Unicode
|
|
||||||
enum { ALT = 1 << 24 };
|
|
||||||
if (c == 27) {
|
|
||||||
if (get_wch (&c) == ERR) {
|
|
||||||
beep ();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
c |= ALT;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto ¤t = g.entries[g.cursor];
|
const auto ¤t = g.entries[g.cursor];
|
||||||
switch (c) {
|
auto i = g_normal_actions.find (c);
|
||||||
case ALT | L'\r':
|
switch (i == g_normal_actions.end () ? ACTION_NONE : i->second) {
|
||||||
case ALT | KEY_ENTER:
|
case ACTION_CHOOSE_FULL:
|
||||||
g.chosen_full = true;
|
g.chosen_full = true;
|
||||||
g.chosen = current.filename;
|
g.chosen = current.filename;
|
||||||
return false;
|
return false;
|
||||||
case L'\r':
|
case ACTION_CHOOSE:
|
||||||
case KEY_ENTER:
|
|
||||||
if (choose (current))
|
if (choose (current))
|
||||||
break;
|
break;
|
||||||
return false;
|
return false;
|
||||||
|
case ACTION_QUIT:
|
||||||
// M-o ought to be the same shortcut the navigator is launched with
|
|
||||||
case ALT | L'o':
|
|
||||||
case L'q':
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
case L'k': case CTRL L'p': case KEY_UP:
|
case ACTION_UP:
|
||||||
g.cursor--;
|
g.cursor--;
|
||||||
break;
|
break;
|
||||||
case L'j': case CTRL L'n': case KEY_DOWN:
|
case ACTION_DOWN:
|
||||||
g.cursor++;
|
g.cursor++;
|
||||||
break;
|
break;
|
||||||
case L'g': case ALT | L'<': case KEY_HOME:
|
case ACTION_TOP:
|
||||||
g.cursor = 0;
|
g.cursor = 0;
|
||||||
break;
|
break;
|
||||||
case L'G': case ALT | L'>': case KEY_END:
|
case ACTION_BOTTOM:
|
||||||
g.cursor = int (g.entries.size ()) - 1;
|
g.cursor = int (g.entries.size ()) - 1;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KEY_PPAGE: g.cursor -= LINES; break;
|
case ACTION_PAGE_PREVIOUS:
|
||||||
case KEY_NPAGE: g.cursor += LINES; break;
|
g.cursor -= LINES;
|
||||||
|
break;
|
||||||
|
case ACTION_PAGE_NEXT:
|
||||||
|
g.cursor += LINES;
|
||||||
|
break;
|
||||||
|
case ACTION_SCROLL_DOWN:
|
||||||
|
g.offset++;
|
||||||
|
break;
|
||||||
|
case ACTION_SCROLL_UP:
|
||||||
|
g.offset--;
|
||||||
|
break;
|
||||||
|
|
||||||
case CTRL L'e': g.offset++; break;
|
case ACTION_GO_START:
|
||||||
case CTRL L'y': g.offset--; break;
|
|
||||||
|
|
||||||
case '&':
|
|
||||||
change_dir (g.start_dir);
|
change_dir (g.start_dir);
|
||||||
break;
|
break;
|
||||||
case '~':
|
case ACTION_GO_HOME:
|
||||||
if (const auto *home = getenv ("HOME"))
|
if (const auto *home = getenv ("HOME"))
|
||||||
change_dir (home);
|
change_dir (home);
|
||||||
else if (const auto *pw = getpwuid (getuid ()))
|
else if (const auto *pw = getpwuid (getuid ()))
|
||||||
change_dir (pw->pw_dir);
|
change_dir (pw->pw_dir);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case L't':
|
case ACTION_SEARCH:
|
||||||
case ALT | L't':
|
|
||||||
g.full_view = !g.full_view;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ALT | L'e':
|
|
||||||
g.editor_line = to_wide (current.filename);
|
|
||||||
// Fall-through
|
|
||||||
case L'e':
|
|
||||||
g.editor = c & ~ALT;
|
|
||||||
break;
|
|
||||||
case L'/':
|
|
||||||
case L's':
|
|
||||||
g.editor = c;
|
g.editor = c;
|
||||||
break;
|
break;
|
||||||
|
case ACTION_RENAME_PREFILL:
|
||||||
|
g.editor_line = to_wide (current.filename);
|
||||||
|
// Fall-through
|
||||||
|
case ACTION_RENAME:
|
||||||
|
g.editor = c & ~ALT;
|
||||||
|
break;
|
||||||
|
|
||||||
case CTRL L'L':
|
case ACTION_TOGGLE_FULL:
|
||||||
|
g.full_view = !g.full_view;
|
||||||
|
break;
|
||||||
|
case ACTION_REDRAW:
|
||||||
clear ();
|
clear ();
|
||||||
break;
|
break;
|
||||||
case L'r':
|
case ACTION_RELOAD:
|
||||||
reload ();
|
reload ();
|
||||||
break;
|
break;
|
||||||
case KEY_RESIZE:
|
|
||||||
case WEOF:
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
|
if (c != KEY (RESIZE) && c != WEOF)
|
||||||
beep ();
|
beep ();
|
||||||
}
|
}
|
||||||
g.cursor = max (g.cursor, 0);
|
g.cursor = max (g.cursor, 0);
|
||||||
@ -767,11 +836,11 @@ fun load_ls_colors (vector<string> colors) {
|
|||||||
auto equal = pair.find ('=');
|
auto equal = pair.find ('=');
|
||||||
if (equal == string::npos)
|
if (equal == string::npos)
|
||||||
continue;
|
continue;
|
||||||
attrs[pair.substr (0, equal)] =
|
auto key = pair.substr (0, equal), value = pair.substr (equal + 1);
|
||||||
decode_ansi_sgr (split (pair.substr (equal + 1), ";"));
|
if (key != g_ls_colors[LS_SYMLINK]
|
||||||
|
|| !(g.ls_symlink_as_target = value == "target"))
|
||||||
|
attrs[key] = decode_ansi_sgr (split (value, ";"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// `LINK target` i.e. `ln=target` is not supported now
|
|
||||||
for (int i = 0; i < LS_COUNT; i++) {
|
for (int i = 0; i < LS_COUNT; i++) {
|
||||||
auto m = attrs.find (g_ls_colors[i]);
|
auto m = attrs.find (g_ls_colors[i]);
|
||||||
if (m != attrs.end ())
|
if (m != attrs.end ())
|
||||||
@ -783,14 +852,16 @@ fun load_ls_colors (vector<string> colors) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun load_configuration () {
|
fun load_colors () {
|
||||||
auto config = xdg_config_find ("/" PROJECT_NAME "/look");
|
|
||||||
if (!config)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Bail out on dumb terminals, there's not much one can do about them
|
// Bail out on dumb terminals, there's not much one can do about them
|
||||||
if (!has_colors () || start_color () == ERR || use_default_colors () == ERR)
|
if (!has_colors () || start_color () == ERR || use_default_colors () == ERR)
|
||||||
return;
|
return;
|
||||||
|
if (const char *colors = getenv ("LS_COLORS"))
|
||||||
|
load_ls_colors (split (colors, ":"));
|
||||||
|
|
||||||
|
auto config = xdg_config_find ("/" PROJECT_NAME "/look");
|
||||||
|
if (!config)
|
||||||
|
return;
|
||||||
|
|
||||||
string line;
|
string line;
|
||||||
while (getline (*config, line)) {
|
while (getline (*config, line)) {
|
||||||
@ -802,9 +873,117 @@ fun load_configuration () {
|
|||||||
if (name == g.attr_names[i])
|
if (name == g.attr_names[i])
|
||||||
g.attrs[i] = decode_attrs (tokens);
|
g.attrs[i] = decode_attrs (tokens);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (const char *colors = getenv ("LS_COLORS"))
|
fun read_key (wint_t &c) -> bool {
|
||||||
load_ls_colors (split (colors, ":"));
|
int res = get_wch (&c);
|
||||||
|
if (res == ERR)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
wint_t metafied{};
|
||||||
|
if (c == 27 && (res = get_wch (&metafied)) != ERR)
|
||||||
|
c = ALT | metafied;
|
||||||
|
if (res == KEY_CODE_YES)
|
||||||
|
c |= SYM;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parse_key (const string &key_name) -> wint_t {
|
||||||
|
wint_t c{};
|
||||||
|
auto p = key_name.c_str ();
|
||||||
|
if (!strncmp (p, "M-", 2)) {
|
||||||
|
c |= ALT;
|
||||||
|
p += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strncmp (p, "C-", 2)) {
|
||||||
|
p += 2;
|
||||||
|
if (*p < 32) {
|
||||||
|
cerr << "bindings: invalid combination: " << key_name << endl;
|
||||||
|
return WEOF;
|
||||||
|
}
|
||||||
|
c |= CTRL *p;
|
||||||
|
p += 1;
|
||||||
|
} else if (g.key_names.count (p)) {
|
||||||
|
c |= g.key_names.at (p);
|
||||||
|
p += strlen (p);
|
||||||
|
} else {
|
||||||
|
wchar_t w; mbstate_t mb {};
|
||||||
|
auto len = strlen (p) + 1, res = mbrtowc (&w, p, len, &mb);
|
||||||
|
if (res == 0) {
|
||||||
|
cerr << "bindings: missing key name: " << key_name << endl;
|
||||||
|
return WEOF;
|
||||||
|
}
|
||||||
|
if (res == size_t (-1) || res == size_t (-2)) {
|
||||||
|
cerr << "bindings: invalid encoding: " << key_name << endl;
|
||||||
|
return WEOF;
|
||||||
|
}
|
||||||
|
c |= w;
|
||||||
|
p += res;
|
||||||
|
}
|
||||||
|
if (*p) {
|
||||||
|
cerr << "key name has unparsable trailing part: " << key_name << endl;
|
||||||
|
return WEOF;
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
fun load_bindings () {
|
||||||
|
g.key_names["space"] = ' ';
|
||||||
|
for (int kc = KEY_MIN; kc < KEY_MAX; kc++) {
|
||||||
|
const char *name = keyname (kc);
|
||||||
|
if (!name)
|
||||||
|
continue;
|
||||||
|
if (!strncmp (name, "KEY_", 4))
|
||||||
|
name += 4;
|
||||||
|
string filtered;
|
||||||
|
for (; *name; name++) {
|
||||||
|
if (*name != '(' && *name != ')')
|
||||||
|
filtered += *name;
|
||||||
|
}
|
||||||
|
g.key_names[filtered] = kc;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto config = xdg_config_find ("/" PROJECT_NAME "/bindings");
|
||||||
|
if (!config)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Stringization in the preprocessor is a bit limited, we want lisp-case
|
||||||
|
map<string, action> actions;
|
||||||
|
int a;
|
||||||
|
for (auto p : g_action_names) {
|
||||||
|
string name;
|
||||||
|
for (; *p; p++)
|
||||||
|
name += *p == '_' ? '-' : *p + 'a' - 'A';
|
||||||
|
actions[name] = action (a++);
|
||||||
|
}
|
||||||
|
|
||||||
|
string line;
|
||||||
|
while (getline (*config, line)) {
|
||||||
|
auto tokens = split (line, " ");
|
||||||
|
if (tokens.empty () || line.front () == '#')
|
||||||
|
continue;
|
||||||
|
if (tokens.size () < 3) {
|
||||||
|
cerr << "bindings: expected: context binding action";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto context = tokens[0], key_name = tokens[1], action = tokens[2];
|
||||||
|
auto m = g_binding_contexts.find (context);
|
||||||
|
if (m == g_binding_contexts.end ()) {
|
||||||
|
cerr << "bindings: invalid context: " << context << endl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
wint_t c = parse_key (key_name);
|
||||||
|
if (c == WEOF)
|
||||||
|
continue;
|
||||||
|
auto i = actions.find (action);
|
||||||
|
if (i == actions.end ()) {
|
||||||
|
cerr << "bindings: invalid action: " << action << endl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
(*m->second)[c] = i->second;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int main (int argc, char *argv[]) {
|
int main (int argc, char *argv[]) {
|
||||||
@ -828,12 +1007,14 @@ int main (int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
locale::global (locale (""));
|
locale::global (locale (""));
|
||||||
|
load_bindings ();
|
||||||
|
|
||||||
if (!initscr () || cbreak () == ERR || noecho () == ERR || nonl () == ERR) {
|
if (!initscr () || cbreak () == ERR || noecho () == ERR || nonl () == ERR) {
|
||||||
cerr << "cannot initialize screen" << endl;
|
cerr << "cannot initialize screen" << endl;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
load_configuration ();
|
load_colors ();
|
||||||
reload ();
|
reload ();
|
||||||
g.start_dir = g.cwd;
|
g.start_dir = g.cwd;
|
||||||
update ();
|
update ();
|
||||||
@ -842,17 +1023,13 @@ int main (int argc, char *argv[]) {
|
|||||||
// which would worsen start-up flickering
|
// which would worsen start-up flickering
|
||||||
if (halfdelay (1) == ERR || keypad (stdscr, TRUE) == ERR) {
|
if (halfdelay (1) == ERR || keypad (stdscr, TRUE) == ERR) {
|
||||||
endwin ();
|
endwin ();
|
||||||
cerr << "cannot initialize screen" << endl;
|
cerr << "cannot configure input" << endl;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
wint_t c;
|
wint_t c;
|
||||||
while (1) {
|
while (!read_key (c) || handle (c))
|
||||||
inotify_check ();
|
inotify_check ();
|
||||||
int res = get_wch (&c);
|
|
||||||
if (res != ERR && !handle (c, res == OK))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
endwin ();
|
endwin ();
|
||||||
|
|
||||||
// Presumably it is going to end up as an argument, so quote it
|
// Presumably it is going to end up as an argument, so quote it
|
||||||
|
Loading…
x
Reference in New Issue
Block a user