mirror of
https://github.com/jarun/nnn.git
synced 2025-01-05 23:54:17 +00:00
Implementing sessions support (#360)
* Initial commit of sessions implementation * Reduce code duplication * Move load session to program flag -e * Fix context initialization problem when loading session * Add pinned directory to session and reduce session file size * Make load_session print an error if exists and few minor adjustments * Refactor session's file structure * Initialize required structures in load_session before loading * Add load session dynamically, restore last session, and extra fixes * Fix indentation * Add sessions documentation to man page * Update fish completions with sessions and make some improvements * Move to single keybinding session management and add help info * ESC when asked to insert session name behaves better * Add sessions completion for bash * Remove pinned dir from session and minor code refactors
This commit is contained in:
parent
2da5602a4f
commit
60dac94a5e
|
@ -34,6 +34,9 @@ _nnn () {
|
|||
COMPREPLY=( $(compgen -W "$bookmarks" -- "$cur") )
|
||||
elif [[ $prev == -p ]]; then
|
||||
COMPREPLY=( $(compgen -f -d -- "$cur") )
|
||||
elif [[ $prev == -e ]]; then
|
||||
local sessions_dir=${XDG_CONFIG_HOME:-$HOME/.config}/nnn/sessions
|
||||
COMPREPLY=( $(compgen -W "$(ls $sessions_dir)" -- "$cur") )
|
||||
elif [[ $cur == -* ]]; then
|
||||
COMPREPLY=( $(compgen -W "${opts[*]}" -- "$cur") )
|
||||
else
|
||||
|
|
|
@ -5,17 +5,24 @@
|
|||
# Arun Prakash Jana <engineerarun@gmail.com>
|
||||
#
|
||||
|
||||
if test -n "$XDG_CONFIG_HOME"
|
||||
set sessions_dir $XDG_CONFIG_HOME/.config/nnn/sessions
|
||||
else
|
||||
set sessions_dir $HOME/.config/nnn/sessions
|
||||
end
|
||||
|
||||
complete -c nnn -s a -d 'use access time'
|
||||
complete -c nnn -s b -r -d 'bookmark key to open'
|
||||
complete -c nnn -s b -r -d 'bookmark key to open' -x -a '(echo $NNN_BMS | awk -F: -v RS=\; \'{print $1"\t"$2}\')'
|
||||
complete -c nnn -s c -d 'cli-only opener'
|
||||
complete -c nnn -s d -d 'start in detail mode'
|
||||
complete -c nnn -s e -r -d 'load session by name' -x -a '@\t"last session" (ls $nnn_config)'
|
||||
complete -c nnn -s f -d 'run filter as cmd on prompt key'
|
||||
complete -c nnn -s H -d 'show hidden files'
|
||||
complete -c nnn -s i -d 'start in navigate-as-you-type mode'
|
||||
complete -c nnn -s K -d 'detect key collision'
|
||||
complete -c nnn -s n -d 'use version compare to sort files'
|
||||
complete -c nnn -s o -d 'open files only on Enter'
|
||||
complete -c nnn -s p -r -d 'copy selection to file'
|
||||
complete -c nnn -s p -r -d 'copy selection to file' -a '-\tstdout'
|
||||
complete -c nnn -s r -d 'show cp, mv progress (Linux-only)'
|
||||
complete -c nnn -s s -d 'use substring match for filters'
|
||||
complete -c nnn -s S -d 'start in disk usage analyzer mode'
|
||||
|
|
18
nnn.1
18
nnn.1
|
@ -10,6 +10,7 @@
|
|||
.Op Ar -b key
|
||||
.Op Ar -c
|
||||
.Op Ar -d
|
||||
.Op Ar -e name
|
||||
.Op Ar -f
|
||||
.Op Ar -H
|
||||
.Op Ar -i
|
||||
|
@ -52,6 +53,9 @@ supports the following options:
|
|||
.Fl d
|
||||
detail mode
|
||||
.Pp
|
||||
.Fl "e name"
|
||||
Load a session by name
|
||||
.Pp
|
||||
.Fl f
|
||||
run filter as command when the prompt key is pressed
|
||||
.Pp
|
||||
|
@ -108,6 +112,20 @@ are available. The status of the contexts are shown in the top left corner:
|
|||
On context creation, the state of the previous context is copied. Each context remembers its last visited directory.
|
||||
.Pp
|
||||
Each context can have its own directory color specified. See ENVIRONMENT section below.
|
||||
.Sh SESSIONS
|
||||
Sessions are a way to save and restore states of work. A session stores the settings and contexts.
|
||||
.Pp
|
||||
Sessions can be loaded dynamically from within a running
|
||||
.Nm
|
||||
instance, or with a flag to
|
||||
.Nm .
|
||||
.Pp
|
||||
When a session is loaded dynamically, the last working session is saved automatically to a dedicated
|
||||
-- "last session" -- session file.
|
||||
.Pp
|
||||
All the session files are located in \fBnnn\fR's
|
||||
configuration directory under the "sessions" directory and named after the session.
|
||||
"@" is the "last session" file.
|
||||
.Sh FILTERS
|
||||
Filters support regexes (default) to instantly (search-as-you-type) list the matching
|
||||
entries in the current directory.
|
||||
|
|
204
src/nnn.c
204
src/nnn.c
|
@ -105,6 +105,7 @@
|
|||
/* Macro definitions */
|
||||
#define VERSION "2.7"
|
||||
#define GENERAL_INFO "BSD 2-Clause\nhttps://github.com/jarun/nnn"
|
||||
#define SESSIONS_VERSION 0
|
||||
|
||||
#ifndef S_BLKSIZE
|
||||
#define S_BLKSIZE 512 /* S_BLKSIZE is missing on Android NDK (Termux) */
|
||||
|
@ -254,6 +255,14 @@ typedef struct {
|
|||
uint color; /* Color code for directories */
|
||||
} context;
|
||||
|
||||
typedef struct {
|
||||
size_t ver;
|
||||
size_t pathln[CTX_MAX];
|
||||
size_t lastln[CTX_MAX];
|
||||
size_t nameln[CTX_MAX];
|
||||
size_t fltrln[CTX_MAX];
|
||||
} session_header_t;
|
||||
|
||||
/* GLOBALS */
|
||||
|
||||
/* Configuration, contexts */
|
||||
|
@ -305,6 +314,7 @@ static char *initpath;
|
|||
static char *cfgdir;
|
||||
static char *g_selpath;
|
||||
static char *plugindir;
|
||||
static char *sessiondir;
|
||||
static char *pnamebuf, *pselbuf;
|
||||
static struct entry *dents;
|
||||
static blkcnt_t ent_blocks;
|
||||
|
@ -2150,7 +2160,7 @@ static char *getreadline(char *prompt, char *path, char *curpath, int *presel)
|
|||
* Updates out with "dir/name or "/name"
|
||||
* Returns the number of bytes copied including the terminating NULL byte
|
||||
*/
|
||||
static size_t mkpath(char *dir, char *name, char *out)
|
||||
static size_t mkpath(const char *dir, const char *name, char *out)
|
||||
{
|
||||
size_t len;
|
||||
|
||||
|
@ -2634,6 +2644,122 @@ static void savecurctx(settings *curcfg, char *path, char *curname, int r /* nex
|
|||
*curcfg = cfg;
|
||||
}
|
||||
|
||||
static void save_session(bool last_session, int *presel)
|
||||
{
|
||||
char session_path[PATH_MAX + 1];
|
||||
int status = _FAILURE;
|
||||
int i;
|
||||
session_header_t header;
|
||||
char *session_name;
|
||||
|
||||
header.ver = SESSIONS_VERSION;
|
||||
|
||||
for (i = 0; i < CTX_MAX; ++i) {
|
||||
if (!g_ctx[i].c_cfg.ctxactive) {
|
||||
header.pathln[i] = header.nameln[i]
|
||||
= header.lastln[i] = header.fltrln[i] = 0;
|
||||
} else {
|
||||
header.pathln[i] = strnlen(g_ctx[i].c_path, PATH_MAX) + 1;
|
||||
header.nameln[i] = strnlen(g_ctx[i].c_name, NAME_MAX) + 1;
|
||||
header.lastln[i] = strnlen(g_ctx[i].c_last, PATH_MAX) + 1;
|
||||
header.fltrln[i] = strnlen(g_ctx[i].c_fltr, REGEX_MAX) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
session_name = !last_session ? xreadline("", "session name: ") : "@";
|
||||
if (session_name[0] != '\0')
|
||||
mkpath(sessiondir, session_name, session_path);
|
||||
else
|
||||
return;
|
||||
|
||||
FILE *fsession = fopen(session_path, "wb");
|
||||
if (!fsession) {
|
||||
printwait("failed to open session file", presel);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((fwrite(&header, sizeof(header), 1, fsession) != 1)
|
||||
|| (fwrite(&cfg, sizeof(cfg), 1, fsession) != 1))
|
||||
goto END;
|
||||
|
||||
for (i = 0; i < CTX_MAX; ++i)
|
||||
if ((fwrite(&g_ctx[i].c_cfg, sizeof(settings), 1, fsession) != 1)
|
||||
|| (fwrite(&g_ctx[i].color, sizeof(uint), 1, fsession) != 1)
|
||||
|| (header.nameln[i] > 0 && fwrite(g_ctx[i].c_name, header.nameln[i], 1, fsession) != 1)
|
||||
|| (header.lastln[i] > 0 && fwrite(g_ctx[i].c_last, header.lastln[i], 1, fsession) != 1)
|
||||
|| (header.fltrln[i] > 0 && fwrite(g_ctx[i].c_fltr, header.fltrln[i], 1, fsession) != 1)
|
||||
|| (header.pathln[i] > 0 && fwrite(g_ctx[i].c_path, header.pathln[i], 1, fsession) != 1))
|
||||
goto END;
|
||||
|
||||
status = _SUCCESS;
|
||||
|
||||
END:
|
||||
fclose(fsession);
|
||||
|
||||
if (status == _FAILURE)
|
||||
printwait("failed to write session data", presel);
|
||||
}
|
||||
|
||||
static bool load_session(const char *session_name, char **path, char **lastdir
|
||||
, char **lastname, bool restore_session) {
|
||||
char session_path[PATH_MAX + 1];
|
||||
int status = _FAILURE;
|
||||
int i = 0;
|
||||
session_header_t header;
|
||||
bool has_loaded_dynamically = !(session_name || restore_session);
|
||||
|
||||
if (!restore_session) {
|
||||
session_name = session_name ? session_name : xreadline("", "session name: ");
|
||||
if (session_name[0] != '\0')
|
||||
mkpath(sessiondir, session_name ? session_name : xreadline("", "session name: "), session_path);
|
||||
else
|
||||
return _FAILURE;
|
||||
} else
|
||||
mkpath(sessiondir, "@", session_path);
|
||||
|
||||
if (has_loaded_dynamically)
|
||||
save_session(TRUE, NULL);
|
||||
|
||||
FILE *fsession = fopen(session_path, "rb");
|
||||
if (!fsession) {
|
||||
printmsg("failed to open session file");
|
||||
xdelay();
|
||||
return _FAILURE;
|
||||
}
|
||||
|
||||
if ((fread(&header, sizeof(header), 1, fsession) != 1)
|
||||
|| (header.ver != SESSIONS_VERSION)
|
||||
|| (fread(&cfg, sizeof(cfg), 1, fsession) != 1))
|
||||
goto END;
|
||||
|
||||
g_ctx[cfg.curctx].c_name[0] = g_ctx[cfg.curctx].c_last[0]
|
||||
= g_ctx[cfg.curctx].c_fltr[0] = g_ctx[cfg.curctx].c_fltr[1] = '\0';
|
||||
|
||||
for (; i < CTX_MAX; ++i)
|
||||
if ((fread(&g_ctx[i].c_cfg, sizeof(settings), 1, fsession) != 1)
|
||||
|| (fread(&g_ctx[i].color, sizeof(uint), 1, fsession) != 1)
|
||||
|| (header.nameln[i] > 0 && fread(g_ctx[i].c_name, header.nameln[i], 1, fsession) != 1)
|
||||
|| (header.lastln[i] > 0 && fread(g_ctx[i].c_last, header.lastln[i], 1, fsession) != 1)
|
||||
|| (header.fltrln[i] > 0 && fread(g_ctx[i].c_fltr, header.fltrln[i], 1, fsession) != 1)
|
||||
|| (header.pathln[i] > 0 && fread(g_ctx[i].c_path, header.pathln[i], 1, fsession) != 1))
|
||||
goto END;
|
||||
|
||||
*path = g_ctx[cfg.curctx].c_path;
|
||||
*lastdir = g_ctx[cfg.curctx].c_last;
|
||||
*lastname = g_ctx[cfg.curctx].c_name;
|
||||
status = _SUCCESS;
|
||||
|
||||
END:
|
||||
fclose(fsession);
|
||||
|
||||
if (status == _FAILURE) {
|
||||
printmsg("failed to read session data");
|
||||
xdelay();
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets only a single line (that's what we need
|
||||
* for now) or shows full command output in pager.
|
||||
|
@ -3078,8 +3204,9 @@ static void show_help(const char *path)
|
|||
"cA Apparent du S du\n"
|
||||
"cs Size E Extn t Time\n"
|
||||
"1MISC\n"
|
||||
"9! ^] Shell = Launch C Execute entry\n"
|
||||
"9! ^] Shell C Execute entry\n"
|
||||
"9R ^V Pick plugin :K xK Execute plugin K\n"
|
||||
"cU Manage session = Launch\n"
|
||||
"cc SSHFS mount u Unmount\n"
|
||||
"b^P Prompt/run cmd L Lock\n"};
|
||||
|
||||
|
@ -3620,7 +3747,7 @@ static void redraw(char *path)
|
|||
printmsg("0/0");
|
||||
}
|
||||
|
||||
static void browse(char *ipath)
|
||||
static void browse(char *ipath, const char *session)
|
||||
{
|
||||
char newpath[PATH_MAX] __attribute__ ((aligned));
|
||||
char mark[PATH_MAX] __attribute__ ((aligned));
|
||||
|
@ -3639,17 +3766,23 @@ static void browse(char *ipath)
|
|||
|
||||
atexit(dentfree);
|
||||
|
||||
/* setup first context */
|
||||
xstrlcpy(g_ctx[0].c_path, ipath, PATH_MAX); /* current directory */
|
||||
path = g_ctx[0].c_path;
|
||||
g_ctx[0].c_last[0] = g_ctx[0].c_name[0] = newpath[0] = mark[0] = '\0';
|
||||
rundir[0] = runfile[0] = '\0';
|
||||
lastdir = g_ctx[0].c_last; /* last visited directory */
|
||||
lastname = g_ctx[0].c_name; /* last visited filename */
|
||||
g_ctx[0].c_fltr[0] = g_ctx[0].c_fltr[1] = '\0';
|
||||
g_ctx[0].c_cfg = cfg; /* current configuration */
|
||||
xlines = LINES;
|
||||
xcols = COLS;
|
||||
|
||||
cfg.filtermode ? (presel = FILTER) : (presel = 0);
|
||||
/* setup first context */
|
||||
if (!session || load_session(session, &path, &lastdir, &lastname, FALSE) == _FAILURE) {
|
||||
xstrlcpy(g_ctx[0].c_path, ipath, PATH_MAX); /* current directory */
|
||||
path = g_ctx[0].c_path;
|
||||
g_ctx[0].c_last[0] = g_ctx[0].c_name[0] = '\0';
|
||||
lastdir = g_ctx[0].c_last; /* last visited directory */
|
||||
lastname = g_ctx[0].c_name; /* last visited filename */
|
||||
g_ctx[0].c_fltr[0] = g_ctx[0].c_fltr[1] = '\0';
|
||||
g_ctx[0].c_cfg = cfg; /* current configuration */
|
||||
}
|
||||
|
||||
newpath[0] = rundir[0] = runfile[0] = mark[0] = '\0';
|
||||
|
||||
presel = cfg.filtermode ? FILTER : 0;
|
||||
|
||||
dents = xrealloc(dents, total_dents * sizeof(struct entry));
|
||||
if (!dents)
|
||||
|
@ -4878,6 +5011,22 @@ nochange:
|
|||
}
|
||||
}
|
||||
return;
|
||||
case SEL_SESSIONS:
|
||||
r = get_input("'s'(ave) / 'l'(oad) / 'r'(estore) session?");
|
||||
|
||||
if (r == 's') {
|
||||
save_session(FALSE, &presel);
|
||||
goto nochange;
|
||||
} else if (r == 'l' || r == 'r') {
|
||||
if (load_session(NULL, &path, &lastdir, &lastname, r == 'r') == _SUCCESS) {
|
||||
setdirwatch();
|
||||
goto begin;
|
||||
}
|
||||
|
||||
presel = MSGWAIT;
|
||||
goto nochange;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (xlines != LINES || xcols != COLS) {
|
||||
idle = 0;
|
||||
|
@ -4929,6 +5078,7 @@ static void usage(void)
|
|||
" -b key open bookmark key\n"
|
||||
" -c cli-only opener\n"
|
||||
" -d detail mode\n"
|
||||
" -e name load session by name\n"
|
||||
" -f run filter as cmd on prompt key\n"
|
||||
" -H show hidden files\n"
|
||||
" -i nav-as-you-type mode\n"
|
||||
|
@ -4966,16 +5116,17 @@ static bool setup_config(void)
|
|||
return FALSE;
|
||||
}
|
||||
|
||||
len = strlen(xdgcfg) + 1 + 12; /* add length of "/nnn/plugins" */
|
||||
len = strlen(xdgcfg) + 1 + 13; /* add length of "/nnn/sessions" */
|
||||
xdg = TRUE;
|
||||
}
|
||||
|
||||
if (!xdg)
|
||||
len = strlen(home) + 1 + 20; /* add length of "/.config/nnn/plugins" */
|
||||
len = strlen(home) + 1 + 21; /* add length of "/.config/nnn/sessions" */
|
||||
|
||||
cfgdir = (char *)malloc(len);
|
||||
plugindir = (char *)malloc(len);
|
||||
if (!cfgdir || !plugindir) {
|
||||
sessiondir = (char *)malloc(len);
|
||||
if (!cfgdir || !plugindir || !sessiondir) {
|
||||
xerror();
|
||||
return FALSE;
|
||||
}
|
||||
|
@ -5017,6 +5168,18 @@ static bool setup_config(void)
|
|||
return FALSE;
|
||||
}
|
||||
|
||||
/* Create ~/.config/nnn/sessions */
|
||||
xstrlcpy(cfgdir + r + 4 - 1, "/sessions", 10);
|
||||
DPRINTF_S(cfgdir);
|
||||
|
||||
xstrlcpy(sessiondir, cfgdir, len);
|
||||
DPRINTF_S(sessiondir);
|
||||
|
||||
if (!create_dir(cfgdir)) {
|
||||
xerror();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Reset to config path */
|
||||
cfgdir[r + 3] = '\0';
|
||||
DPRINTF_S(cfgdir);
|
||||
|
@ -5056,6 +5219,7 @@ static void cleanup(void)
|
|||
{
|
||||
free(g_selpath);
|
||||
free(plugindir);
|
||||
free(sessiondir);
|
||||
free(cfgdir);
|
||||
free(initpath);
|
||||
free(bmstr);
|
||||
|
@ -5070,12 +5234,13 @@ int main(int argc, char *argv[])
|
|||
{
|
||||
mmask_t mask;
|
||||
char *arg = NULL;
|
||||
char *session = NULL;
|
||||
int opt;
|
||||
#ifdef __linux__
|
||||
bool progress = FALSE;
|
||||
#endif
|
||||
|
||||
while ((opt = getopt(argc, argv, "HSKiab:cdfnop:rstvh")) != -1) {
|
||||
while ((opt = getopt(argc, argv, "HSKiab:cde:fnop:rstvh")) != -1) {
|
||||
switch (opt) {
|
||||
case 'S':
|
||||
cfg.blkorder = 1;
|
||||
|
@ -5097,6 +5262,9 @@ int main(int argc, char *argv[])
|
|||
case 'c':
|
||||
cfg.cliopener = 1;
|
||||
break;
|
||||
case 'e':
|
||||
session = optarg;
|
||||
break;
|
||||
case 'f':
|
||||
cfg.filtercmd = 1;
|
||||
break;
|
||||
|
@ -5348,7 +5516,7 @@ int main(int argc, char *argv[])
|
|||
if (!initcurses(&mask))
|
||||
return _FAILURE;
|
||||
|
||||
browse(initpath);
|
||||
browse(initpath, session);
|
||||
mousemask(mask, NULL);
|
||||
exitcurses();
|
||||
|
||||
|
|
Loading…
Reference in a new issue