Compare commits
No commits in common. "d6846e63276365ae554671621532a4f63a86cbf4" and "2c595100ae8189de7c2cd29140a185d7f4e3673b" have entirely different histories.
d6846e6327
...
2c595100ae
218
sdn.cpp
218
sdn.cpp
@ -114,7 +114,7 @@ fun needs_shell_quoting (const string &v) -> bool {
|
||||
for (auto c : v)
|
||||
if (strchr ("|&;<>()$`\\\"' \t\n" "*?[#˜=%" "!", c))
|
||||
return true;
|
||||
return v.empty ();
|
||||
return false;
|
||||
}
|
||||
|
||||
fun shell_escape (const string &v) -> string {
|
||||
@ -130,59 +130,6 @@ fun shell_escape (const string &v) -> string {
|
||||
return "'" + result + "'";
|
||||
}
|
||||
|
||||
fun parse_line (istream &is, vector<string> &out) -> bool {
|
||||
enum {STA, DEF, COM, ESC, WOR, QUO, STATES};
|
||||
enum {TAKE = 1 << 3, PUSH = 1 << 4, STOP = 1 << 5, ERROR = 1 << 6};
|
||||
enum {TWOR = TAKE | WOR};
|
||||
|
||||
// We never transition back to the start state, so it can stay as a noop
|
||||
static char table[STATES][7] = {
|
||||
// state EOF SP, TAB ' # \ LF default
|
||||
/* STA */ {ERROR, DEF, QUO, COM, ESC, STOP, TWOR},
|
||||
/* DEF */ {STOP, 0, QUO, COM, ESC, STOP, TWOR},
|
||||
/* COM */ {STOP, 0, 0, 0, 0, STOP, 0},
|
||||
/* ESC */ {ERROR, TWOR, TWOR, TWOR, TWOR, TWOR, TWOR},
|
||||
/* WOR */ {STOP | PUSH, DEF | PUSH, QUO, TAKE, ESC, STOP | PUSH, TAKE},
|
||||
/* QUO */ {ERROR, TAKE, WOR, TAKE, TAKE, TAKE, TAKE},
|
||||
};
|
||||
|
||||
out.clear (); string token; int state = STA;
|
||||
constexpr auto eof = istream::traits_type::eof ();
|
||||
while (1) {
|
||||
int ch = is.get (), edge = 0;
|
||||
switch (ch) {
|
||||
case eof: edge = table[state][0]; break;
|
||||
case '\t':
|
||||
case ' ': edge = table[state][1]; break;
|
||||
case '\'': edge = table[state][2]; break;
|
||||
case '#': edge = table[state][3]; break;
|
||||
case '\\': edge = table[state][4]; break;
|
||||
case '\n': edge = table[state][5]; break;
|
||||
default: edge = table[state][6]; break;
|
||||
}
|
||||
if (edge & TAKE)
|
||||
token += ch;
|
||||
if (edge & PUSH) {
|
||||
out.push_back (token);
|
||||
token.clear ();
|
||||
}
|
||||
if (edge & STOP)
|
||||
return true;
|
||||
if (edge & ERROR)
|
||||
return false;
|
||||
if (edge &= 7)
|
||||
state = edge;
|
||||
}
|
||||
}
|
||||
|
||||
fun write_line (ostream &os, const vector<string> &in) {
|
||||
if (!in.empty ())
|
||||
os << shell_escape (in.at (0));
|
||||
for (size_t i = 1; i < in.size (); i++)
|
||||
os << " " << shell_escape (in.at (i));
|
||||
os << endl;
|
||||
}
|
||||
|
||||
fun decode_type (mode_t m) -> wchar_t {
|
||||
if (S_ISDIR (m)) return L'd'; if (S_ISBLK (m)) return L'b';
|
||||
if (S_ISCHR (m)) return L'c'; if (S_ISLNK (m)) return L'l';
|
||||
@ -193,7 +140,7 @@ fun decode_type (mode_t m) -> wchar_t {
|
||||
|
||||
/// Return the modes of a file in the usual stat/ls format
|
||||
fun decode_mode (mode_t m) -> wstring {
|
||||
return {decode_type (m),
|
||||
return { decode_type (m),
|
||||
L"r-"[!(m & S_IRUSR)],
|
||||
L"w-"[!(m & S_IWUSR)],
|
||||
((m & S_ISUID) ? L"sS" : L"x-")[!(m & S_IXUSR)],
|
||||
@ -202,7 +149,8 @@ fun decode_mode (mode_t m) -> wstring {
|
||||
((m & S_ISGID) ? L"sS" : L"x-")[!(m & S_IXGRP)],
|
||||
L"r-"[!(m & S_IROTH)],
|
||||
L"w-"[!(m & S_IWOTH)],
|
||||
((m & S_ISVTX) ? L"tT" : L"x-")[!(m & S_IXOTH)]};
|
||||
((m & S_ISVTX) ? L"tT" : L"x-")[!(m & S_IXOTH)],
|
||||
};
|
||||
}
|
||||
|
||||
template<class T> fun shift (vector<T> &v) -> T {
|
||||
@ -243,23 +191,12 @@ fun xdg_config_find (const string &suffix) -> unique_ptr<ifstream> {
|
||||
for (const auto &dir : dirs) {
|
||||
if (dir[0] != '/')
|
||||
continue;
|
||||
if (ifstream ifs {dir + "/" PROJECT_NAME "/" + suffix})
|
||||
if (ifstream ifs {dir + suffix})
|
||||
return make_unique<ifstream> (move (ifs));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
fun xdg_config_write (const string &suffix) -> unique_ptr<fstream> {
|
||||
auto dir = xdg_config_home ();
|
||||
if (dir[0] == '/') {
|
||||
// TODO: try to create the end directory
|
||||
if (fstream fs {dir + "/" PROJECT_NAME "/" + suffix,
|
||||
fstream::in | fstream::out | fstream::trunc})
|
||||
return make_unique<fstream> (move (fs));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
using ncstring = basic_string<cchar_t>;
|
||||
@ -370,7 +307,7 @@ enum { ALT = 1 << 24, SYM = 1 << 25 }; // Outside the range of Unicode
|
||||
|
||||
#define ACTIONS(XX) XX(NONE) XX(CHOOSE) XX(CHOOSE_FULL) XX(HELP) XX(QUIT) \
|
||||
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(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)
|
||||
@ -383,7 +320,7 @@ enum action { ACTIONS(XX) ACTION_COUNT };
|
||||
static const char *g_action_names[] = {ACTIONS(XX)};
|
||||
#undef XX
|
||||
|
||||
static map<wint_t, action> g_normal_actions {
|
||||
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}, {'h', ACTION_HELP},
|
||||
// M-o ought to be the same shortcut the navigator is launched with
|
||||
@ -394,18 +331,18 @@ static map<wint_t, action> g_normal_actions {
|
||||
{'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},
|
||||
{'c', ACTION_CHDIR}, {'&', ACTION_GO_START}, {'~', ACTION_GO_HOME},
|
||||
{'&', 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 {
|
||||
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 {
|
||||
static const map<string, map<wint_t, action>*> g_binding_contexts = {
|
||||
{"normal", &g_normal_actions}, {"input", &g_input_actions},
|
||||
};
|
||||
|
||||
@ -458,12 +395,8 @@ static struct {
|
||||
vector<level> levels; ///< Upper directory levels
|
||||
int offset, cursor; ///< Scroll offset and cursor position
|
||||
bool full_view; ///< Show extended information
|
||||
bool gravity; ///< Entries are shoved to the bottom
|
||||
int max_widths[entry::COLUMNS]; ///< Column widths
|
||||
|
||||
wstring message; ///< Message for the user
|
||||
int message_ttl; ///< Time to live for the message
|
||||
|
||||
string chosen; ///< Chosen item for the command line
|
||||
bool chosen_full; ///< Use the full path
|
||||
|
||||
@ -648,7 +581,7 @@ fun update () {
|
||||
auto index = g.offset + i;
|
||||
bool selected = index == g.cursor;
|
||||
attrset (selected ? g.attrs[g.AT_CURSOR] : 0);
|
||||
move (g.gravity ? (available - used + i) : i, 0);
|
||||
move (available - used + i, 0);
|
||||
|
||||
auto used = 0;
|
||||
for (int col = start_column; col < entry::COLUMNS; col++) {
|
||||
@ -670,16 +603,13 @@ fun update () {
|
||||
hline (' ', COLS - print (bar, COLS));
|
||||
|
||||
attrset (g.attrs[g.AT_INPUT]);
|
||||
curs_set (0);
|
||||
if (g.editor) {
|
||||
move (LINES - 1, 0);
|
||||
auto p = apply_attrs (wstring (g.editor) + L": ", 0);
|
||||
move (LINES - 1, print (p + apply_attrs (g.editor_line, 0), COLS - 1));
|
||||
curs_set (1);
|
||||
} else if (!g.message.empty ()) {
|
||||
move (LINES - 1, 0);
|
||||
print (apply_attrs (g.message, 0), COLS);
|
||||
}
|
||||
} else
|
||||
curs_set (0);
|
||||
|
||||
refresh ();
|
||||
}
|
||||
@ -724,11 +654,7 @@ fun reload () {
|
||||
(IN_ALL_EVENTS | IN_ONLYDIR | IN_EXCL_UNLINK) & ~(IN_ACCESS | IN_OPEN));
|
||||
}
|
||||
|
||||
fun show_message (const string &message, int ttl = 30) {
|
||||
g.message = to_wide (message);
|
||||
g.message_ttl = ttl;
|
||||
}
|
||||
|
||||
// TODO: we should be able to signal failures to the user
|
||||
fun run_pager (FILE *contents) {
|
||||
// We don't really need to set O_CLOEXEC, so we're not going to
|
||||
rewind (contents);
|
||||
@ -809,28 +735,12 @@ fun search (const wstring &needle) {
|
||||
fun is_ancestor_dir (const string &ancestor, const string &of) -> bool {
|
||||
if (strncmp (ancestor.c_str (), of.c_str (), ancestor.length ()))
|
||||
return false;
|
||||
return of[ancestor.length ()] == '/' || (ancestor == "/" && ancestor != of);
|
||||
}
|
||||
|
||||
fun pop_levels () {
|
||||
string anchor; auto i = g.levels.rbegin ();
|
||||
while (i != g.levels.rend () && !is_ancestor_dir (i->path, g.cwd)) {
|
||||
if (i->path == g.cwd) {
|
||||
g.offset = i->offset;
|
||||
g.cursor = i->cursor;
|
||||
anchor = i->filename;
|
||||
}
|
||||
i++;
|
||||
g.levels.pop_back ();
|
||||
}
|
||||
if (!anchor.empty () && (g.cursor >= g.entries.size ()
|
||||
|| g.entries[g.cursor].filename != anchor))
|
||||
search (to_wide (anchor));
|
||||
return of.c_str ()[ancestor.length ()] == '/'
|
||||
|| (ancestor == "/" && ancestor != of);
|
||||
}
|
||||
|
||||
fun change_dir (const string &path) {
|
||||
if (chdir (path.c_str ())) {
|
||||
show_message (strerror (errno));
|
||||
beep ();
|
||||
return;
|
||||
}
|
||||
@ -842,7 +752,20 @@ fun change_dir (const string &path) {
|
||||
g.levels.push_back (last);
|
||||
g.offset = g.cursor = 0;
|
||||
} else {
|
||||
pop_levels ();
|
||||
string anchor;
|
||||
auto i = g.levels.rbegin ();
|
||||
while (i != g.levels.rend () && !is_ancestor_dir (i->path, g.cwd)) {
|
||||
if (i->path == g.cwd) {
|
||||
g.offset = i->offset;
|
||||
g.cursor = i->cursor;
|
||||
anchor = i->filename;
|
||||
}
|
||||
i++;
|
||||
g.levels.pop_back ();
|
||||
}
|
||||
if (!anchor.empty () && (g.cursor >= g.entries.size ()
|
||||
|| g.entries[g.cursor].filename != anchor))
|
||||
search (to_wide (anchor));
|
||||
}
|
||||
}
|
||||
|
||||
@ -935,12 +858,6 @@ fun handle (wint_t c) -> bool {
|
||||
g.offset--;
|
||||
break;
|
||||
|
||||
case ACTION_CHDIR:
|
||||
g.editor = L"chdir";
|
||||
g.editor_on_confirm = [] {
|
||||
change_dir (to_mb (g.editor_line));
|
||||
};
|
||||
break;
|
||||
case ACTION_GO_START:
|
||||
change_dir (g.start_dir);
|
||||
break;
|
||||
@ -1086,13 +1003,14 @@ fun load_colors () {
|
||||
if (const char *colors = getenv ("LS_COLORS"))
|
||||
load_ls_colors (split (colors, ":"));
|
||||
|
||||
auto config = xdg_config_find ("look");
|
||||
auto config = xdg_config_find ("/" PROJECT_NAME "/look");
|
||||
if (!config)
|
||||
return;
|
||||
|
||||
vector<string> tokens;
|
||||
while (parse_line (*config, tokens)) {
|
||||
if (tokens.empty ())
|
||||
string line;
|
||||
while (getline (*config, line)) {
|
||||
auto tokens = split (line, " ");
|
||||
if (tokens.empty () || line.front () == '#')
|
||||
continue;
|
||||
auto name = shift (tokens);
|
||||
for (int i = 0; i < g.AT_COUNT; i++)
|
||||
@ -1173,7 +1091,7 @@ fun load_bindings () {
|
||||
learn_named_key (filtered, SYM | kc);
|
||||
}
|
||||
|
||||
auto config = xdg_config_find ("bindings");
|
||||
auto config = xdg_config_find ("/" PROJECT_NAME "/bindings");
|
||||
if (!config)
|
||||
return;
|
||||
|
||||
@ -1188,9 +1106,10 @@ fun load_bindings () {
|
||||
actions[name] = action (a++);
|
||||
}
|
||||
|
||||
vector<string> tokens;
|
||||
while (parse_line (*config, tokens)) {
|
||||
if (tokens.empty ())
|
||||
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";
|
||||
@ -1215,53 +1134,6 @@ fun load_bindings () {
|
||||
}
|
||||
}
|
||||
|
||||
fun load_history_level (const vector<string> &v) {
|
||||
if (v.size () != 6)
|
||||
return;
|
||||
// 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)});
|
||||
}
|
||||
|
||||
fun load_config () {
|
||||
auto config = xdg_config_find ("config");
|
||||
if (!config)
|
||||
return;
|
||||
|
||||
vector<string> tokens;
|
||||
while (parse_line (*config, tokens)) {
|
||||
if (tokens.empty ())
|
||||
continue;
|
||||
|
||||
if (tokens.front () == "full-view")
|
||||
g.full_view = tokens.size () > 1 && tokens.at (1) == "1";
|
||||
else if (tokens.front () == "gravity")
|
||||
g.gravity = tokens.size () > 1 && tokens.at (1) == "1";
|
||||
else if (tokens.front () == "history")
|
||||
load_history_level (tokens);
|
||||
}
|
||||
}
|
||||
|
||||
fun save_config () {
|
||||
auto config = xdg_config_write ("config");
|
||||
if (!config)
|
||||
return;
|
||||
|
||||
write_line (*config, {"full-view", g.full_view ? "1" : "0"});
|
||||
write_line (*config, {"gravity", g.gravity ? "1" : "0"});
|
||||
|
||||
char hostname[256];
|
||||
if (gethostname (hostname, sizeof hostname))
|
||||
*hostname = 0;
|
||||
|
||||
auto ppid = std::to_string (getppid ());
|
||||
for (auto i = g.levels.begin (); i != g.levels.end (); i++)
|
||||
write_line (*config, {"history", hostname, ppid, i->path,
|
||||
to_string (i->offset), to_string (i->cursor), i->filename});
|
||||
write_line (*config, {"history", hostname, ppid, g.cwd,
|
||||
to_string (g.offset), to_string (g.cursor),
|
||||
g.entries[g.cursor].filename});
|
||||
}
|
||||
|
||||
int main (int argc, char *argv[]) {
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
@ -1287,7 +1159,6 @@ int main (int argc, char *argv[]) {
|
||||
|
||||
locale::global (locale (""));
|
||||
load_bindings ();
|
||||
load_config ();
|
||||
|
||||
if (!initscr () || cbreak () == ERR || noecho () == ERR || nonl () == ERR) {
|
||||
cerr << "cannot initialize screen" << endl;
|
||||
@ -1297,7 +1168,6 @@ int main (int argc, char *argv[]) {
|
||||
load_colors ();
|
||||
reload ();
|
||||
g.start_dir = g.cwd;
|
||||
pop_levels ();
|
||||
update ();
|
||||
|
||||
// Invoking keypad() earlier would make ncurses flush its output buffer,
|
||||
@ -1309,15 +1179,9 @@ int main (int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
wint_t c;
|
||||
while (!read_key (c) || handle (c)) {
|
||||
while (!read_key (c) || handle (c))
|
||||
inotify_check ();
|
||||
if (g.message_ttl && !--g.message_ttl) {
|
||||
g.message.clear ();
|
||||
update ();
|
||||
}
|
||||
}
|
||||
endwin ();
|
||||
save_config ();
|
||||
|
||||
// Presumably it is going to end up as an argument, so quote it
|
||||
if (!g.chosen.empty ())
|
||||
|
Loading…
x
Reference in New Issue
Block a user