-
-
Notifications
You must be signed in to change notification settings - Fork 787
Persistent selection #1086
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Persistent selection #1086
Changes from 24 commits
2692508
65b44e9
15774a6
fd3e3b7
ece3172
89eebb5
3a12fcf
8a5b670
2c30731
a75e0b7
bd4e9d3
7ac146b
e405619
e482df4
75efe13
1fe70dd
e3f8156
bd6f5c9
a575000
2647b1d
e2f42c0
d1fdcc8
5dc95df
c94e6cd
04a6c1d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -198,6 +198,11 @@ | |
| #define SED "sed" | ||
| #endif | ||
|
|
||
| /* Large selection threshold */ | ||
| #ifndef LARGESEL | ||
| #define LARGESEL 1000 | ||
| #endif | ||
|
|
||
| #define MIN_DISPLAY_COL (CTX_MAX * 2) | ||
| #define ARCHIVE_CMD_LEN 16 | ||
| #define BLK_SHIFT_512 9 | ||
|
|
@@ -271,6 +276,12 @@ typedef struct entry { | |
| #endif | ||
| } *pEntry; | ||
|
|
||
| /* Selection marker */ | ||
| typedef struct { | ||
| char *startpos; | ||
| size_t len; | ||
| } selmark; | ||
|
|
||
| /* Key-value pairs from env */ | ||
| typedef struct { | ||
| int key; | ||
|
|
@@ -408,7 +419,7 @@ static int nselected; | |
| #ifndef NOFIFO | ||
| static int fifofd = -1; | ||
| #endif | ||
| static uint_t idletimeout, selbufpos, lastappendpos, selbuflen; | ||
| static uint_t idletimeout, selbufpos, selbuflen; | ||
| static ushort_t xlines, xcols; | ||
| static ushort_t idle; | ||
| static uchar_t maxbm, maxplug; | ||
|
|
@@ -598,8 +609,9 @@ static char * const utils[] = { | |
| #define MSG_RM_TMP 39 | ||
| #define MSG_INVALID_KEY 40 | ||
| #define MSG_NOCHANGE 41 | ||
| #define MSG_LARGESEL 42 | ||
| #ifndef DIR_LIMITED_SELECTION | ||
| #define MSG_DIR_CHANGED 42 /* Must be the last entry */ | ||
| #define MSG_DIR_CHANGED 43 /* Must be the last entry */ | ||
| #endif | ||
|
|
||
| static const char * const messages[] = { | ||
|
|
@@ -645,6 +657,7 @@ static const char * const messages[] = { | |
| "remove tmp file?", | ||
| "invalid key", | ||
| "unchanged", | ||
| "inversion may be slow, continue?", | ||
| #ifndef DIR_LIMITED_SELECTION | ||
| "dir changed, range sel off", /* Must be the last entry */ | ||
| #endif | ||
|
|
@@ -817,6 +830,7 @@ static void redraw(char *path); | |
| static int spawn(char *file, char *arg1, char *arg2, char *arg3, ushort_t flag); | ||
| static void move_cursor(int target, int ignore_scrolloff); | ||
| static char *load_input(int fd, const char *path); | ||
| static int editselection(void); | ||
| static int set_sort_flags(int r); | ||
| #ifndef NOFIFO | ||
| static void notify_fifo(bool force); | ||
|
|
@@ -1493,8 +1507,6 @@ static void startselection(void) | |
| writesel(NULL, 0); | ||
| selbufpos = 0; | ||
| } | ||
|
|
||
| lastappendpos = 0; | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -1520,32 +1532,146 @@ static size_t appendslash(char *path) | |
| return len; | ||
| } | ||
|
|
||
| static void invertselbuf(char *path, bool toggle) | ||
| static char *findinsel(int len) | ||
| { | ||
| selbufpos = lastappendpos; | ||
| if (!selbufpos) | ||
| return FALSE; | ||
|
|
||
| if (toggle || nselected) { | ||
| size_t len = appendslash(path); | ||
| char *found = pselbuf; | ||
| while (1) { | ||
| /* | ||
| * memmem(3): | ||
| * This function is not specified in POSIX.1, but is present on a number of other systems. | ||
| */ | ||
| found = memmem(found, selbufpos - (found - pselbuf), g_buf, len); | ||
| if (!found) | ||
| return NULL; | ||
|
|
||
| for (int i = 0; i < ndents; ++i) { | ||
| if (toggle) { /* Toggle selection status */ | ||
| pdents[i].flags ^= FILE_SELECTED; | ||
| pdents[i].flags & FILE_SELECTED ? ++nselected : --nselected; | ||
| } | ||
| if (found == pselbuf || *(found - 1) == '\0') | ||
| return found; | ||
|
|
||
| /* We found g_buf as a substring of a path, move forward */ | ||
| found += len; | ||
| if (found >= pselbuf + selbufpos) | ||
| return NULL; | ||
| } | ||
| } | ||
|
|
||
| if (pdents[i].flags & FILE_SELECTED) | ||
| appendfpath(path, | ||
| len + xstrsncpy(path + len, pdents[i].name, PATH_MAX - len)); | ||
| static int markcmp(const void *va, const void *vb) | ||
| { | ||
| const selmark *ma = (selmark*)va; | ||
| const selmark *mb = (selmark*)vb; | ||
|
|
||
| return ma->startpos - mb->startpos; | ||
| } | ||
|
|
||
| static void invertselbuf(char *path) | ||
| { | ||
| /* This may be slow for large selection, ask for confirmation */ | ||
| if (nselected > LARGESEL && !xconfirm(get_input(messages[MSG_LARGESEL]))) | ||
| return; | ||
|
|
||
| size_t len, endpos, offset = 0; | ||
| char *found; | ||
| int nmarked = 0, prev = 0; | ||
| selmark *marked = malloc(nselected * sizeof(selmark)); | ||
|
|
||
| /* First pass: inversion */ | ||
| for (int i = 0; i < ndents; ++i) { | ||
| /* Toggle selection status */ | ||
| pdents[i].flags ^= FILE_SELECTED; | ||
|
|
||
| /* Find where the files marked for deselection are in selection buffer */ | ||
| if (!(pdents[i].flags & FILE_SELECTED)) { | ||
| len = mkpath(path, pdents[i].name, g_buf); | ||
| found = findinsel(len); | ||
|
|
||
| marked[nmarked].startpos = found; | ||
| marked[nmarked].len = len; | ||
| ++nmarked; | ||
|
|
||
| --nselected; | ||
| offset += len; /* buffer size adjustment */ | ||
| } else | ||
| ++nselected; | ||
| } | ||
|
|
||
| /* | ||
| * Files marked for deselection could be found in arbitrary order. | ||
| * Sort by appearance in selection buffer. | ||
| * With entries sorted we can merge adjacent ones allowing us to | ||
| * move them in a single go. | ||
| */ | ||
| qsort(marked, nmarked, sizeof(selmark), &markcmp); | ||
KlzXS marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /* Some files might be adjacent. Merge them into a single entry */ | ||
| for (int i = 1; i < nmarked; ++i) { | ||
| if (marked[i].startpos == marked[prev].startpos + marked[prev].len) | ||
jarun marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| marked[prev].len += marked[i].len; | ||
| else { | ||
| ++prev; | ||
| marked[prev].startpos = marked[i].startpos; | ||
| marked[prev].len = marked[i].len; | ||
| } | ||
| } | ||
|
|
||
| /* | ||
| * Number of entries is increased by encountering a non-adjacent entry | ||
| * After we finish the loop we should increment it once more. | ||
| */ | ||
|
|
||
| if (nmarked) /* Make sure there is something to deselect */ | ||
| nmarked = prev + 1; | ||
|
|
||
| /* Using merged entries remove unselected chunks from selection buffer */ | ||
| for (int i = 0; i < nmarked; ++i) { | ||
| /* | ||
| * found: points to where the current block starts | ||
| * variable is recycled from previous for readability | ||
| * endpos: points to where the the next block starts | ||
| * area between the end of current block (found + len) | ||
| * and endpos is selected entries. This is what we are | ||
| * moving back. | ||
| */ | ||
| found = marked[i].startpos; | ||
| endpos = (i + 1 == nmarked ? selbufpos : marked[i + 1].startpos - pselbuf); | ||
| len = marked[i].len; | ||
|
|
||
| /* Move back only selected entries. No selected memory is moved twice */ | ||
| memmove(found, found + len, endpos - (found + len - pselbuf)); | ||
| } | ||
|
|
||
| /* Buffer size adjustment */ | ||
| selbufpos -= offset; | ||
|
|
||
| free(marked); | ||
|
|
||
| /* Second pass: append newly selected to buffer */ | ||
| for (int i = 0; i < ndents; ++i) { | ||
jarun marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| /* Skip unselected */ | ||
| if (!(pdents[i].flags & FILE_SELECTED)) | ||
| continue; | ||
|
|
||
| if (len > 1) | ||
| --len; | ||
| path[len] = '\0'; | ||
| len = mkpath(path, pdents[i].name, g_buf); | ||
| appendfpath(g_buf, len); | ||
| } | ||
|
|
||
| nselected ? writesel(pselbuf, selbufpos - 1) : clearselection(); | ||
| } | ||
|
|
||
| /* removes g_buf from selbuf */ | ||
| static void rmfromselbuf(size_t len) | ||
| { | ||
| char *found = findinsel(len); | ||
| if (!found) | ||
| return; | ||
|
|
||
| memmove(found, found + len, selbufpos - (found + len - pselbuf)); | ||
| selbufpos -= len; | ||
|
|
||
| nselected ? writesel(pselbuf, selbufpos - 1) : clearselection(); | ||
| } | ||
|
|
||
| static void addtoselbuf(char *path, int startid, int endid) | ||
| { | ||
| size_t len = appendslash(path); | ||
|
|
@@ -5139,9 +5265,10 @@ static int dentfill(char *path, struct entry **ppdents) | |
| uchar_t entflags = 0; | ||
| int flags = 0; | ||
| struct dirent *dp; | ||
| char *namep, *pnb, *buf = NULL; | ||
| bool found; | ||
| char *namep, *pnb, *buf = g_buf; | ||
| struct entry *dentp; | ||
| size_t off = 0, namebuflen = NAMEBUF_INCR; | ||
| size_t off, namebuflen = NAMEBUF_INCR; | ||
| struct stat sb_path, sb; | ||
| DIR *dirp = opendir(path); | ||
|
|
||
|
|
@@ -5157,9 +5284,6 @@ static int dentfill(char *path, struct entry **ppdents) | |
| if (cfg.blkorder) { | ||
| num_files = 0; | ||
| dir_blocks = 0; | ||
| buf = (char *)alloca(xstrlen(path) + NAME_MAX + 2); | ||
| if (!buf) | ||
| return 0; | ||
|
|
||
| if (fstatat(fd, path, &sb_path, 0) == -1) | ||
| goto exit; | ||
|
|
@@ -5199,6 +5323,15 @@ static int dentfill(char *path, struct entry **ppdents) | |
| } | ||
| #endif | ||
|
|
||
| if (path[1]) { /* path should always be at least two bytes (including NULL) */ | ||
| off = xstrsncpy(buf, path, PATH_MAX); | ||
| buf[off - 1] = '/'; | ||
| found = findinsel(off) != NULL; | ||
|
||
| } else | ||
| found = TRUE; | ||
|
|
||
| off = 0; | ||
|
|
||
| do { | ||
| namep = dp->d_name; | ||
|
|
||
|
|
@@ -5340,6 +5473,9 @@ static int dentfill(char *path, struct entry **ppdents) | |
| entflags = 0; | ||
| } | ||
|
|
||
| if (found && findinsel(mkpath(path, dentp->name, buf)) != NULL) | ||
| dentp->flags |= FILE_SELECTED; | ||
|
|
||
| if (cfg.blkorder) { | ||
| if (S_ISDIR(sb.st_mode)) { | ||
| mkpath(path, namep, buf); | ||
|
|
@@ -5611,9 +5747,6 @@ static int handle_context_switch(enum action sel) | |
| else | ||
| return -1; | ||
| } | ||
|
|
||
| if (g_state.selmode) /* Remember the position from where to continue selection */ | ||
| lastappendpos = selbufpos; | ||
| } | ||
|
|
||
| return r; | ||
|
|
@@ -6182,9 +6315,6 @@ static bool browse(char *ipath, const char *session, int pkey) | |
| } | ||
| #endif | ||
|
|
||
| if (g_state.selmode && lastdir[0]) | ||
| lastappendpos = selbufpos; | ||
|
|
||
| #ifdef LINUX_INOTIFY | ||
| if ((presel == FILTER || watch) && inotify_wd >= 0) { | ||
| inotify_rm_watch(inotify_fd, inotify_wd); | ||
|
|
@@ -6267,9 +6397,6 @@ static bool browse(char *ipath, const char *session, int pkey) | |
| if (r >= CTX_MAX) | ||
| sel = SEL_BACK; | ||
| else if (r >= 0 && r != cfg.curctx) { | ||
| if (g_state.selmode) | ||
| lastappendpos = selbufpos; | ||
|
|
||
| savecurctx(path, pdents[cur].name, r); | ||
|
|
||
| /* Reset the pointers */ | ||
|
|
@@ -6850,7 +6977,7 @@ static bool browse(char *ipath, const char *session, int pkey) | |
| writesel(pselbuf, selbufpos - 1); /* Truncate NULL from end */ | ||
| } else { | ||
| --nselected; | ||
| invertselbuf(path, FALSE); | ||
| rmfromselbuf(mkpath(path, pdents[cur].name, g_buf)); | ||
| } | ||
|
|
||
| #ifndef NOX11 | ||
|
|
@@ -6919,7 +7046,7 @@ static bool browse(char *ipath, const char *session, int pkey) | |
| } | ||
|
|
||
| (sel == SEL_SELINV) | ||
| ? invertselbuf(path, TRUE) : addtoselbuf(path, selstartid, selendid); | ||
| ? invertselbuf(path) : addtoselbuf(path, selstartid, selendid); | ||
|
|
||
| #ifndef NOX11 | ||
| if (cfg.x11) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.