Commit Diff


commit - 2ec364f923b60b8b1e68ffe4c705b82d9a487056
commit + c9f421369f5ad2e6d0b1b8201891466f792f42b9
blob - 8fea6626d79d26ed8cdaf21bf7a9a582bc8a142e
blob + bd6dc9235419f72b7ec2fb5abccc565d99eea933
--- Makefile
+++ Makefile
@@ -4,21 +4,21 @@ MANPREFIX=$(PREFIX)/man
 BINDIR=$(DESTDIR)$(PREFIX)/bin
 MANDIR=$(DESTDIR)$(MANPREFIX)/man1
 
-all: rover
+all: fm
 
-rover: rover.c config.h
+fm: fm.c config.h
 	$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(LDLIBS)
 
-install: rover
-	rm -f $(BINDIR)/rover
+install: fm
+	rm -f $(BINDIR)/fm
 	mkdir -p $(BINDIR)
-	cp rover $(BINDIR)/rover
+	cp fm $(BINDIR)/fm
 	mkdir -p $(MANDIR)
-	cp rover.1 $(MANDIR)/rover.1
+	cp fm.1 $(MANDIR)/fm.1
 
 uninstall:
-	rm -f $(BINDIR)/rover
-	rm -f $(MANDIR)/rover.1
+	rm -f $(BINDIR)/fm
+	rm -f $(MANDIR)/fm.1
 
 clean:
-	rm -f rover
+	rm -f fm
blob - 40c09ed04b6356f1f6e29cd483f885d7604c095c
blob + 9757550ede48b77a84144b2f6cc01df5c5745dc6
--- README.md
+++ README.md
@@ -1,17 +1,16 @@
 Introduction
 ============
 
- Rover is a file browser for the terminal.
+> fm is a file browser for the terminal.
 
-![Rover screenshot](/../screenshots/screenshot.png?raw=true "Screenshot")
+The main goal is to provide a faster way to explore a file system from
+the terminal, compared to what's possible by using `cd`, `ls`, etc.  fm
+has vi-like key bindings for navigation and can open files in $PAGER and
+$EDITOR.  Basic file system operations are also implemented (see fm(1)
+for details).  fm is designed to be simple, fast and portable.
 
- The main goal is to provide a faster way to explore a file system from the
-terminal, compared to what's possible by using `cd`, `ls`, etc. Rover has
-vi-like key bindings for navigation and can open files in $PAGER and $EDITOR.
-Basic file system operations are also implemented (see rover(1) for details).
-Rover is designed to be simple, fast and portable.
+fm was forked from [Rover](https://github.com/lecram/rover).
 
-
 Quick Start
 ===========
 
@@ -23,13 +22,13 @@ Quick Start
 
  Running:
  ```
- $ rover [DIR1 [DIR2 [DIR3 [...]]]]
+ $ fm [DIR1 [DIR2 [DIR3 [...]]]]
  ```
 
  Basic Usage:
  ```
-       q - quit Rover
-       ? - show Rover manual
+       q - quit fm
+       ? - show fm manual
      j/k - move cursor down/up
      J/K - move cursor down/up 10 lines
      g/G - move cursor to top/bottom of listing
@@ -46,7 +45,7 @@ Quick Start
        D - delete selected file or (empty) directory
  ```
 
- Please read rover(1) for more information.
+ Please read fm(1) for more information.
 
 
 Requirements
@@ -59,26 +58,29 @@ Requirements
 Configuration
 =============
 
- Rover configuration (mostly key bindings and colors) can only be changed by
+fm configuration (mostly key bindings and colors) can only be changed by
 editing the file `config.h` and rebuilding the binary.
 
- Note that the external programs executed by some Rover commands may be changed
-via the appropriate environment variables. For example, to specify an editor:
- ```
- $ VISUAL=vi rover
- ```
+Note that the external programs executed by some fm commands may be
+changed via the appropriate environment variables. For example, to
+specify an editor:
 
- Rover will first check for variables prefixed  with ROVER_. This can be used to
-change Rover behavior without interfering with the global environment:
- ```
- $ ROVER_VISUAL=vi rover
- ```
+```
+$ VISUAL=vi fm
+```
 
- Please read rover(1) for more information.
+fm will first check for variables prefixed with ROVER_. This can be used
+to change fm behavior without interfering with the global environment:
 
+```
+$ ROVER_VISUAL=vi rover
+```
 
+Please read fm(1) for more information.
+
+
 Copying
 =======
 
- All of the source code and documentation for Rover is released into the public
-domain and provided without warranty of any kind.
+All of the source code and documentation for fm is released into the
+public domain and provided without warranty of any kind.
blob - 15aed3440b764786999cfb6ca1e943b6affb4891 (mode 644)
blob + /dev/null
--- rover.1
+++ /dev/null
@@ -1,235 +0,0 @@
-.TH ROVER 1 2020-06-04 rover\-1.0.1
-.SH NAME
-rover \- file browser for the terminal
-.SH SYNOPSIS
-.B rover
-[\fB\-d\fR|\fB\-\-save\-cwd\fR \fIFILE\fR]
-[\fB\-m\fR|\fB\-\-save\-marks\fR \fIFILE\fR]
-[\fIDIR\fR [\fIDIR\fR [\fIDIR\fR [...]]]]
-.br
-.B rover
-\fB\-h\fR|\fB\-\-help\fR
-.br
-.B rover
-\fB\-v\fR|\fB\-\-version\fR
-.SH DESCRIPTION
-Browse current working directory or the ones specified.
-.SH OPTIONS
-.TP
-\fB\-d\fR, \fB\-\-save\-cwd\fR
-write last visited path to \fIFILE\fR before exiting
-.TP
-\fB\-m\fR, \fB\-\-save\-marks\fR
-append path of marked entries to \fIFILE\fR before exiting;
-if \fIFILE\fR doesn't exist, it'll be created
-.TP
-\fB\-h\fR, \fB\-\-help\fR
-print help message and exit
-.TP
-\fB\-v\fR, \fB\-\-version\fR
-print program version and exit
-.SH CONCEPTS
-.SS TABS
-.PP
-Rover is operated via a tabbed interface. There are always ten tabs in total,
-numbered from 0 to 9. At any given moment, one (and only one) tab is visible on
-the screen. The number of the tab currently visible is shown on the top right
-corner of the screen. To view a different tab, one just needs to press its
-corresponding number. Each tab has a \fBcurrent working directory\fR (\fBCWD\fR)
-associated to it, also shown on the top of the screen. The \fBCWD\fR of the
-visible tab is changed as the user navigates through the file system.
-.PP
-It's also possible to set the \fBCWD\fR of tabs at Rover start-up by passing the
-desired paths as command-line arguments. The tab number 0 always starts at
-\fB$HOME\fR. If more than nine directories are specified, only the first nine
-are used for tabs 1\-9 and the rest are ignored. Tabs for which an invalid path
-was assigned will also start at \fB$HOME\fR. Remaining tabs not specified on
-the command line start at the current working directory of the parent process.
-.SS ENTRIES
-.PP
-An \fBentry\fR is anything that may be found inside a directory in some file
-system. It may be a file, a directory, a link, a socket, a FIFO, etc. In its
-most basic usage, Rover will gather a list of entries inside the \fBCWD\fR
-and present it on the screen.
-.PP
-There is always one (and only one) highlighted entry visible. This is both the
-\fBcursor\fR location and the current \fBselection\fR. The highlighting moves as
-the user navigates the entry list with cursor commands. Simple operations, like
-entering a subdirectory or renaming a file, are done by first selecting the
-relevant entry and then issuing the appropriate command (see the \fBCOMMANDS\fR
-section). For commands that operate on more than one entry at once (batch
-commands), selection is not sufficient, since it's not possible to select more
-than one entry. Batch commands are performed on marked entries.
-.SS MARKS
-.PP
-For some file operations, it is convenient to first \fBmark\fR all entries that
-are to be processed, so that the appropriate command only needs to be issued
-once. For such purpose, Rover allows an arbitrary number of entries to be
-marked, with the limitation that they must be all in the same directory.
-.PP
-Unlike simple selection, the effect of marks is not constrained by visibility.
-This is so one can mark a number of entries in some directory, navigate to another
-directory, and then finally issue a copy or move command. Moreover, marks are
-shared over all tabs. This allows one to mark some entries in one tab that is
-pointed to the "source" directory of the operation and then issue the command on
-another tab that is pointed to the "destination" directory.
-.SH COMMANDS
-.TP
-.B q
-Quit rover.
-.TP
-.B j/k
-Move cursor down/up.
-.TP
-.B J/K
-Move cursor down/up 10 lines.
-.TP
-.B g/G
-Move cursor to top/bottom of listing.
-.TP
-.B l
-Enter selected directory.
-.TP
-.B h
-Go to parent directory.
-.TP
-.B H
-Go to \fB$HOME\fR directory.
-.TP
-.B t
-Go to the target of the selected link.
-.TP
-.B y
-Copy location to clipboard.
-.TP
-.B p
-Go to location in clipboard.
-.TP
-.B r
-Refresh directory listing.
-.TP
-.B <RETURN>
-Open \fB$SHELL\fR on the current directory.
-.TP
-.B <SPACE>
-Open \fB$PAGER\fR with the selected file.
-.TP
-.B e
-Open \fB$VISUAL\fR or \fB$EDITOR\fR with the selected file.
-.TP
-.B o
-Open \fB$OPEN\fR with the selected file.
-.TP
-.B /
-Start incremental search.
-.TP
-.B f/d/s
-Toggle file/directory/hidden listing.
-.TP
-.B n/N
-Create new file/directory.
-.TP
-.B R
-Rename selected file or directory.
-.TP
-.B E
-Toggle execute permission of the selected file.
-.TP
-.B D
-Delete selected file or (empty) directory.
-.TP
-.B m
-Toggle mark on the selected entry.
-.TP
-.B M
-Toggle mark on all visible entries.
-.TP
-.B a
-Mark all visible entries.
-.TP
-.B X/C/V
-Delete/copy/move all marked entries.
-.TP
-.B 0-9
-Change tab.
-.SH LINE EDITING
-.PP
-Some commands will prompt for an input string. For example, in order to rename a
-file, the user must supply the new name. This string will appear at the bottom
-of the screen and must be edited interactively using the keyboard. Printable
-keys will insert characters at the cursor position. The following shortcuts are
-available for line editing:
-.TP
-.B <TAB>
-Finish editing and \fBcancel\fR command.
-.TP
-.B <RETURN>
-Finish editing and \fBconfirm\fR command.
-.TP
-.B <LEFT>/<RIGHT>
-Move insertion cursor left/right.
-.TP
-.B <UP>/<DOWN>
-Move insertion cursor to beginning/end of string.
-.TP
-.B <BACKSPACE>
-Remove one character before cursor.
-.TP
-.B <DELETE>
-Remove one character after cursor.
-.TP
-.B <CONTROL>+u
-Clear line (remove all characters).
-.SH ENVIRONMENT VARIABLES
-.TP
-.B HOME
-Full path of the home directory.
-.TP
-.B PATH
-Colon\-separated path list for program directories.
-.TP
-.B SHELL
-Name of shell program (e.g. \fI/bin/sh\fP).
-.TP
-.B PAGER
-Name of pager program (e.g. \fIless\fP).
-.TP
-.B VISUAL
-Name of visual editor program (e.g. \fIvim\fP or \fIemacs\fP).
-.TP
-.B EDITOR
-Name of line editor program (e.g. \fIed\fP or \fIex\fP).
-.TP
-.B CLIP
-Path of clipboard file (e.g. \fI/tmp/clipboard\fP).
-The user must have read and write permissions on this path.
-If this variable is not defined, Rover will use an internal, in-memory, clipboard.
-.TP
-.B RVSEL
-Rover writes the name of the selected entry to this variable before running a
-subprocess. This allows one to use the selection as part of an arbitrary command
-by first invoking a shell from Rover (see the \fBCOMMANDS\fR section) and then
-typing something like \fBgrep abc "$RVSEL"\fR.
-.TP
-.B OPEN
-This variable can be set to a command accepting a single argument: a filename.
-The command is supposed to open the given file with an appropriate program.
-.TP
-.B ROVER_SHELL, ROVER_PAGER, ROVER_VISUAL, ROVER_EDITOR, ROVER_OPEN
-If any of these variables are set, they override \fBSHELL\fR, \fBPAGER\fR,
-\fBVISUAL\fR, \fBEDITOR\fR and \fBOPEN\fR, respectively.
-.SH CONFIGURATION
-.PP
-If you want to change Rover key bindings or colors, you can edit the
-\fIconfig.h\fP file in the source distribution and recompile the program. Rover
-will not use or create any external file during its execution, except when asked
-to do so by user commands or command-line options.
-.SH NOTES
-.PP
-\fBImportant\fR: Currently, Rover never asks for confirmation before overwriting
-existing files while copying/moving marked entries. Please be careful to not
-accidentally lose your data.
-.SH LINKS
-Rover homepage: <http://lecram.github.io/p/rover/>.
-.SH SEE ALSO
-\fBnoice(1)\fR, \fBmc(1)\fR, \fBvifm(1)\fR, \fBranger(1)\fR
blob - /dev/null
blob + 15aed3440b764786999cfb6ca1e943b6affb4891 (mode 644)
--- /dev/null
+++ fm.1
@@ -0,0 +1,235 @@
+.TH ROVER 1 2020-06-04 rover\-1.0.1
+.SH NAME
+rover \- file browser for the terminal
+.SH SYNOPSIS
+.B rover
+[\fB\-d\fR|\fB\-\-save\-cwd\fR \fIFILE\fR]
+[\fB\-m\fR|\fB\-\-save\-marks\fR \fIFILE\fR]
+[\fIDIR\fR [\fIDIR\fR [\fIDIR\fR [...]]]]
+.br
+.B rover
+\fB\-h\fR|\fB\-\-help\fR
+.br
+.B rover
+\fB\-v\fR|\fB\-\-version\fR
+.SH DESCRIPTION
+Browse current working directory or the ones specified.
+.SH OPTIONS
+.TP
+\fB\-d\fR, \fB\-\-save\-cwd\fR
+write last visited path to \fIFILE\fR before exiting
+.TP
+\fB\-m\fR, \fB\-\-save\-marks\fR
+append path of marked entries to \fIFILE\fR before exiting;
+if \fIFILE\fR doesn't exist, it'll be created
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+print help message and exit
+.TP
+\fB\-v\fR, \fB\-\-version\fR
+print program version and exit
+.SH CONCEPTS
+.SS TABS
+.PP
+Rover is operated via a tabbed interface. There are always ten tabs in total,
+numbered from 0 to 9. At any given moment, one (and only one) tab is visible on
+the screen. The number of the tab currently visible is shown on the top right
+corner of the screen. To view a different tab, one just needs to press its
+corresponding number. Each tab has a \fBcurrent working directory\fR (\fBCWD\fR)
+associated to it, also shown on the top of the screen. The \fBCWD\fR of the
+visible tab is changed as the user navigates through the file system.
+.PP
+It's also possible to set the \fBCWD\fR of tabs at Rover start-up by passing the
+desired paths as command-line arguments. The tab number 0 always starts at
+\fB$HOME\fR. If more than nine directories are specified, only the first nine
+are used for tabs 1\-9 and the rest are ignored. Tabs for which an invalid path
+was assigned will also start at \fB$HOME\fR. Remaining tabs not specified on
+the command line start at the current working directory of the parent process.
+.SS ENTRIES
+.PP
+An \fBentry\fR is anything that may be found inside a directory in some file
+system. It may be a file, a directory, a link, a socket, a FIFO, etc. In its
+most basic usage, Rover will gather a list of entries inside the \fBCWD\fR
+and present it on the screen.
+.PP
+There is always one (and only one) highlighted entry visible. This is both the
+\fBcursor\fR location and the current \fBselection\fR. The highlighting moves as
+the user navigates the entry list with cursor commands. Simple operations, like
+entering a subdirectory or renaming a file, are done by first selecting the
+relevant entry and then issuing the appropriate command (see the \fBCOMMANDS\fR
+section). For commands that operate on more than one entry at once (batch
+commands), selection is not sufficient, since it's not possible to select more
+than one entry. Batch commands are performed on marked entries.
+.SS MARKS
+.PP
+For some file operations, it is convenient to first \fBmark\fR all entries that
+are to be processed, so that the appropriate command only needs to be issued
+once. For such purpose, Rover allows an arbitrary number of entries to be
+marked, with the limitation that they must be all in the same directory.
+.PP
+Unlike simple selection, the effect of marks is not constrained by visibility.
+This is so one can mark a number of entries in some directory, navigate to another
+directory, and then finally issue a copy or move command. Moreover, marks are
+shared over all tabs. This allows one to mark some entries in one tab that is
+pointed to the "source" directory of the operation and then issue the command on
+another tab that is pointed to the "destination" directory.
+.SH COMMANDS
+.TP
+.B q
+Quit rover.
+.TP
+.B j/k
+Move cursor down/up.
+.TP
+.B J/K
+Move cursor down/up 10 lines.
+.TP
+.B g/G
+Move cursor to top/bottom of listing.
+.TP
+.B l
+Enter selected directory.
+.TP
+.B h
+Go to parent directory.
+.TP
+.B H
+Go to \fB$HOME\fR directory.
+.TP
+.B t
+Go to the target of the selected link.
+.TP
+.B y
+Copy location to clipboard.
+.TP
+.B p
+Go to location in clipboard.
+.TP
+.B r
+Refresh directory listing.
+.TP
+.B <RETURN>
+Open \fB$SHELL\fR on the current directory.
+.TP
+.B <SPACE>
+Open \fB$PAGER\fR with the selected file.
+.TP
+.B e
+Open \fB$VISUAL\fR or \fB$EDITOR\fR with the selected file.
+.TP
+.B o
+Open \fB$OPEN\fR with the selected file.
+.TP
+.B /
+Start incremental search.
+.TP
+.B f/d/s
+Toggle file/directory/hidden listing.
+.TP
+.B n/N
+Create new file/directory.
+.TP
+.B R
+Rename selected file or directory.
+.TP
+.B E
+Toggle execute permission of the selected file.
+.TP
+.B D
+Delete selected file or (empty) directory.
+.TP
+.B m
+Toggle mark on the selected entry.
+.TP
+.B M
+Toggle mark on all visible entries.
+.TP
+.B a
+Mark all visible entries.
+.TP
+.B X/C/V
+Delete/copy/move all marked entries.
+.TP
+.B 0-9
+Change tab.
+.SH LINE EDITING
+.PP
+Some commands will prompt for an input string. For example, in order to rename a
+file, the user must supply the new name. This string will appear at the bottom
+of the screen and must be edited interactively using the keyboard. Printable
+keys will insert characters at the cursor position. The following shortcuts are
+available for line editing:
+.TP
+.B <TAB>
+Finish editing and \fBcancel\fR command.
+.TP
+.B <RETURN>
+Finish editing and \fBconfirm\fR command.
+.TP
+.B <LEFT>/<RIGHT>
+Move insertion cursor left/right.
+.TP
+.B <UP>/<DOWN>
+Move insertion cursor to beginning/end of string.
+.TP
+.B <BACKSPACE>
+Remove one character before cursor.
+.TP
+.B <DELETE>
+Remove one character after cursor.
+.TP
+.B <CONTROL>+u
+Clear line (remove all characters).
+.SH ENVIRONMENT VARIABLES
+.TP
+.B HOME
+Full path of the home directory.
+.TP
+.B PATH
+Colon\-separated path list for program directories.
+.TP
+.B SHELL
+Name of shell program (e.g. \fI/bin/sh\fP).
+.TP
+.B PAGER
+Name of pager program (e.g. \fIless\fP).
+.TP
+.B VISUAL
+Name of visual editor program (e.g. \fIvim\fP or \fIemacs\fP).
+.TP
+.B EDITOR
+Name of line editor program (e.g. \fIed\fP or \fIex\fP).
+.TP
+.B CLIP
+Path of clipboard file (e.g. \fI/tmp/clipboard\fP).
+The user must have read and write permissions on this path.
+If this variable is not defined, Rover will use an internal, in-memory, clipboard.
+.TP
+.B RVSEL
+Rover writes the name of the selected entry to this variable before running a
+subprocess. This allows one to use the selection as part of an arbitrary command
+by first invoking a shell from Rover (see the \fBCOMMANDS\fR section) and then
+typing something like \fBgrep abc "$RVSEL"\fR.
+.TP
+.B OPEN
+This variable can be set to a command accepting a single argument: a filename.
+The command is supposed to open the given file with an appropriate program.
+.TP
+.B ROVER_SHELL, ROVER_PAGER, ROVER_VISUAL, ROVER_EDITOR, ROVER_OPEN
+If any of these variables are set, they override \fBSHELL\fR, \fBPAGER\fR,
+\fBVISUAL\fR, \fBEDITOR\fR and \fBOPEN\fR, respectively.
+.SH CONFIGURATION
+.PP
+If you want to change Rover key bindings or colors, you can edit the
+\fIconfig.h\fP file in the source distribution and recompile the program. Rover
+will not use or create any external file during its execution, except when asked
+to do so by user commands or command-line options.
+.SH NOTES
+.PP
+\fBImportant\fR: Currently, Rover never asks for confirmation before overwriting
+existing files while copying/moving marked entries. Please be careful to not
+accidentally lose your data.
+.SH LINKS
+Rover homepage: <http://lecram.github.io/p/rover/>.
+.SH SEE ALSO
+\fBnoice(1)\fR, \fBmc(1)\fR, \fBvifm(1)\fR, \fBranger(1)\fR
blob - eca6aebc7cbb18cacda010b7d6ee4799b3212035 (mode 644)
blob + /dev/null
--- rover.c
+++ /dev/null
@@ -1,1500 +0,0 @@
-#define _XOPEN_SOURCE       700
-#define _XOPEN_SOURCE_EXTENDED
-#define _FILE_OFFSET_BITS   64
-
-#include <stdlib.h>
-#include <stdint.h>
-#include <ctype.h>
-#include <wchar.h>
-#include <wctype.h>
-#include <string.h>
-#include <sys/types.h>  /* pid_t, ... */
-#include <stdio.h>
-#include <limits.h>     /* PATH_MAX */
-#include <locale.h>     /* setlocale(), LC_ALL */
-#include <unistd.h>     /* chdir(), getcwd(), read(), close(), ... */
-#include <dirent.h>     /* DIR, struct dirent, opendir(), ... */
-#include <libgen.h>
-#include <sys/stat.h>
-#include <fcntl.h>      /* open() */
-#include <sys/wait.h>   /* waitpid() */
-#include <signal.h>     /* struct sigaction, sigaction() */
-#include <errno.h>
-#include <stdarg.h>
-#include <curses.h>
-
-#include "config.h"
-
-/*  This signal is not defined by POSIX, but should be
-   present on all systems that have resizable terminals. */
-#ifndef SIGWINCH
-#define SIGWINCH  28
-#endif
-
-/* String buffers. */
-#define BUFLEN  PATH_MAX
-static char BUF1[BUFLEN];
-static char BUF2[BUFLEN];
-static char INPUT[BUFLEN];
-static char CLIPBOARD[BUFLEN];
-static wchar_t WBUF[BUFLEN];
-
-/* Paths to external programs. */
-static char *user_shell;
-static char *user_pager;
-static char *user_editor;
-static char *user_open;
-
-/* Listing view parameters. */
-#define HEIGHT      (LINES-4)
-#define STATUSPOS   (COLS-16)
-
-/* Listing view flags. */
-#define SHOW_FILES      0x01u
-#define SHOW_DIRS       0x02u
-#define SHOW_HIDDEN     0x04u
-
-/* Marks parameters. */
-#define BULK_INIT   5
-#define BULK_THRESH 256
-
-/* Information associated to each entry in listing. */
-typedef struct Row {
-    char *name;
-    off_t size;
-    mode_t mode;
-    int islink;
-    int marked;
-} Row;
-
-/* Dynamic array of marked entries. */
-typedef struct Marks {
-    char dirpath[PATH_MAX];
-    int bulk;
-    int nentries;
-    char **entries;
-} Marks;
-
-/* Line editing state. */
-typedef struct Edit {
-    wchar_t buffer[BUFLEN+1];
-    int left, right;
-} Edit;
-
-/* Each tab only stores the following information. */
-typedef struct Tab {
-    int scroll;
-    int esel;
-    uint8_t flags;
-    char cwd[PATH_MAX];
-} Tab;
-
-typedef struct Prog {
-    off_t partial;
-    off_t total;
-    const char *msg;
-} Prog;
-
-/* Global state. */
-static struct Rover {
-    int tab;
-    int nfiles;
-    Row *rows;
-    WINDOW *window;
-    Marks marks;
-    Edit edit;
-    int edit_scroll;
-    volatile sig_atomic_t pending_usr1;
-    volatile sig_atomic_t pending_winch;
-    Prog prog;
-    Tab tabs[10];
-} rover;
-
-/* Macros for accessing global state. */
-#define ENAME(I)    rover.rows[I].name
-#define ESIZE(I)    rover.rows[I].size
-#define EMODE(I)    rover.rows[I].mode
-#define ISLINK(I)   rover.rows[I].islink
-#define MARKED(I)   rover.rows[I].marked
-#define SCROLL      rover.tabs[rover.tab].scroll
-#define ESEL        rover.tabs[rover.tab].esel
-#define FLAGS       rover.tabs[rover.tab].flags
-#define CWD         rover.tabs[rover.tab].cwd
-
-/* Helpers. */
-#define MIN(A, B)   ((A) < (B) ? (A) : (B))
-#define MAX(A, B)   ((A) > (B) ? (A) : (B))
-#define ISDIR(E)    (strchr((E), '/') != NULL)
-
-/* Line Editing Macros. */
-#define EDIT_FULL(E)       ((E).left == (E).right)
-#define EDIT_CAN_LEFT(E)   ((E).left)
-#define EDIT_CAN_RIGHT(E)  ((E).right < BUFLEN-1)
-#define EDIT_LEFT(E)       (E).buffer[(E).right--] = (E).buffer[--(E).left]
-#define EDIT_RIGHT(E)      (E).buffer[(E).left++] = (E).buffer[++(E).right]
-#define EDIT_INSERT(E, C)  (E).buffer[(E).left++] = (C)
-#define EDIT_BACKSPACE(E)  (E).left--
-#define EDIT_DELETE(E)     (E).right++
-#define EDIT_CLEAR(E)      do { (E).left = 0; (E).right = BUFLEN-1; } while(0)
-
-typedef enum EditStat {CONTINUE, CONFIRM, CANCEL} EditStat;
-typedef enum Color {DEFAULT, RED, GREEN, YELLOW, BLUE, CYAN, MAGENTA, WHITE, BLACK} Color;
-typedef int (*PROCESS)(const char *path);
-
-static void
-init_marks(Marks *marks)
-{
-    strcpy(marks->dirpath, "");
-    marks->bulk = BULK_INIT;
-    marks->nentries = 0;
-    marks->entries = calloc(marks->bulk, sizeof *marks->entries);
-}
-
-/* Unmark all entries. */
-static void
-mark_none(Marks *marks)
-{
-    int i;
-
-    strcpy(marks->dirpath, "");
-    for (i = 0; i < marks->bulk && marks->nentries; i++)
-        if (marks->entries[i]) {
-            free(marks->entries[i]);
-            marks->entries[i] = NULL;
-            marks->nentries--;
-        }
-    if (marks->bulk > BULK_THRESH) {
-        /* Reset bulk to free some memory. */
-        free(marks->entries);
-        marks->bulk = BULK_INIT;
-        marks->entries = calloc(marks->bulk, sizeof *marks->entries);
-    }
-}
-
-static void
-add_mark(Marks *marks, char *dirpath, char *entry)
-{
-    int i;
-
-    if (!strcmp(marks->dirpath, dirpath)) {
-        /* Append mark to directory. */
-        if (marks->nentries == marks->bulk) {
-            /* Expand bulk to accomodate new entry. */
-            int extra = marks->bulk / 2;
-            marks->bulk += extra; /* bulk *= 1.5; */
-            marks->entries = realloc(marks->entries,
-                                     marks->bulk * sizeof *marks->entries);
-            memset(&marks->entries[marks->nentries], 0,
-                   extra * sizeof *marks->entries);
-            i = marks->nentries;
-        } else {
-            /* Search for empty slot (there must be one). */
-            for (i = 0; i < marks->bulk; i++)
-                if (!marks->entries[i])
-                    break;
-        }
-    } else {
-        /* Directory changed. Discard old marks. */
-        mark_none(marks);
-        strcpy(marks->dirpath, dirpath);
-        i = 0;
-    }
-    marks->entries[i] = malloc(strlen(entry) + 1);
-    strcpy(marks->entries[i], entry);
-    marks->nentries++;
-}
-
-static void
-del_mark(Marks *marks, char *entry)
-{
-    int i;
-
-    if (marks->nentries > 1) {
-        for (i = 0; i < marks->bulk; i++)
-            if (marks->entries[i] && !strcmp(marks->entries[i], entry))
-                break;
-        free(marks->entries[i]);
-        marks->entries[i] = NULL;
-        marks->nentries--;
-    } else
-        mark_none(marks);
-}
-
-static void
-free_marks(Marks *marks)
-{
-    int i;
-
-    for (i = 0; i < marks->bulk && marks->nentries; i++)
-        if (marks->entries[i]) {
-            free(marks->entries[i]);
-            marks->nentries--;
-        }
-    free(marks->entries);
-}
-
-static void
-handle_usr1(int sig)
-{
-    rover.pending_usr1 = 1;
-}
-
-static void
-handle_winch(int sig)
-{
-    rover.pending_winch = 1;
-}
-
-static void
-enable_handlers()
-{
-    struct sigaction sa;
-
-    memset(&sa, 0, sizeof (struct sigaction));
-    sa.sa_handler = handle_usr1;
-    sigaction(SIGUSR1, &sa, NULL);
-    sa.sa_handler = handle_winch;
-    sigaction(SIGWINCH, &sa, NULL);
-}
-
-static void
-disable_handlers()
-{
-    struct sigaction sa;
-
-    memset(&sa, 0, sizeof (struct sigaction));
-    sa.sa_handler = SIG_DFL;
-    sigaction(SIGUSR1, &sa, NULL);
-    sigaction(SIGWINCH, &sa, NULL);
-}
-
-static void reload();
-static void update_view();
-
-/* Handle any signals received since last call. */
-static void
-sync_signals()
-{
-    if (rover.pending_usr1) {
-        /* SIGUSR1 received: refresh directory listing. */
-        reload();
-        rover.pending_usr1 = 0;
-    }
-    if (rover.pending_winch) {
-        /* SIGWINCH received: resize application accordingly. */
-        delwin(rover.window);
-        endwin();
-        refresh();
-        clear();
-        rover.window = subwin(stdscr, LINES - 2, COLS, 1, 0);
-        if (HEIGHT < rover.nfiles && SCROLL + HEIGHT > rover.nfiles)
-            SCROLL = ESEL - HEIGHT;
-        update_view();
-        rover.pending_winch = 0;
-    }
-}
-
-/* This function must be used in place of getch().
-   It handles signals while waiting for user input. */
-static int
-rover_getch()
-{
-    int ch;
-
-    while ((ch = getch()) == ERR)
-        sync_signals();
-    return ch;
-}
-
-/* This function must be used in place of get_wch().
-   It handles signals while waiting for user input. */
-static int
-rover_get_wch(wint_t *wch)
-{
-    wint_t ret;
-
-    while ((ret = get_wch(wch)) == (wint_t) ERR)
-        sync_signals();
-    return ret;
-}
-
-/* Get user programs from the environment. */
-
-#define ROVER_ENV(dst, src) if ((dst = getenv("ROVER_" #src)) == NULL) \
-                                dst = getenv(#src);
-
-static void
-get_user_programs()
-{
-    ROVER_ENV(user_shell, SHELL)
-    ROVER_ENV(user_pager, PAGER)
-    ROVER_ENV(user_editor, VISUAL)
-    if (!user_editor)
-        ROVER_ENV(user_editor, EDITOR)
-    ROVER_ENV(user_open, OPEN)
-}
-
-/* Do a fork-exec to external program (e.g. $EDITOR). */
-static void
-spawn(char **args)
-{
-    pid_t pid;
-    int status;
-
-    setenv("RVSEL", rover.nfiles ? ENAME(ESEL) : "", 1);
-    pid = fork();
-    if (pid > 0) {
-        /* fork() succeeded. */
-        disable_handlers();
-        endwin();
-        waitpid(pid, &status, 0);
-        enable_handlers();
-        kill(getpid(), SIGWINCH);
-    } else if (pid == 0) {
-        /* Child process. */
-        execvp(args[0], args);
-    }
-}
-
-static void
-shell_escaped_cat(char *buf, char *str, size_t n)
-{
-    char *p = buf + strlen(buf);
-    *p++ = '\'';
-    for (n--; n; n--, str++) {
-        switch (*str) {
-        case '\'':
-            if (n < 4)
-                goto done;
-            strcpy(p, "'\\''");
-            n -= 4;
-            p += 4;
-            break;
-        case '\0':
-            goto done;
-        default:
-            *p = *str;
-            p++;
-        }
-    }
-done:
-    strncat(p, "'", n);
-}
-
-static int
-open_with_env(char *program, char *path)
-{
-    if (program) {
-#ifdef RV_SHELL
-        strncpy(BUF1, program, BUFLEN - 1);
-        strncat(BUF1, " ", BUFLEN - strlen(program) - 1);
-        shell_escaped_cat(BUF1, path, BUFLEN - strlen(program) - 2);
-        spawn((char *[]) {RV_SHELL, "-c", BUF1, NULL});
-#else
-        spawn((char *[]) {program, path, NULL});
-#endif
-        return 1;
-    }
-    return 0;
-}
-
-/* Curses setup. */
-static void
-init_term()
-{
-    setlocale(LC_ALL, "");
-    initscr();
-    cbreak(); /* Get one character at a time. */
-    timeout(100); /* For getch(). */
-    noecho();
-    nonl(); /* No NL->CR/NL on output. */
-    intrflush(stdscr, FALSE);
-    keypad(stdscr, TRUE);
-    curs_set(FALSE); /* Hide blinking cursor. */
-    if (has_colors()) {
-        short bg;
-        start_color();
-#ifdef NCURSES_EXT_FUNCS
-        use_default_colors();
-        bg = -1;
-#else
-        bg = COLOR_BLACK;
-#endif
-        init_pair(RED, COLOR_RED, bg);
-        init_pair(GREEN, COLOR_GREEN, bg);
-        init_pair(YELLOW, COLOR_YELLOW, bg);
-        init_pair(BLUE, COLOR_BLUE, bg);
-        init_pair(CYAN, COLOR_CYAN, bg);
-        init_pair(MAGENTA, COLOR_MAGENTA, bg);
-        init_pair(WHITE, COLOR_WHITE, bg);
-        init_pair(BLACK, COLOR_BLACK, bg);
-    }
-    atexit((void (*)(void)) endwin);
-    enable_handlers();
-}
-
-/* Update the listing view. */
-static void
-update_view()
-{
-    int i, j;
-    int numsize;
-    int ishidden;
-    int marking;
-
-    mvhline(0, 0, ' ', COLS);
-    attr_on(A_BOLD, NULL);
-    color_set(RVC_TABNUM, NULL);
-    mvaddch(0, COLS - 2, rover.tab + '0');
-    attr_off(A_BOLD, NULL);
-    if (rover.marks.nentries) {
-        numsize = snprintf(BUF1, BUFLEN, "%d", rover.marks.nentries);
-        color_set(RVC_MARKS, NULL);
-        mvaddstr(0, COLS - 3 - numsize, BUF1);
-    } else
-        numsize = -1;
-    color_set(RVC_CWD, NULL);
-    mbstowcs(WBUF, CWD, PATH_MAX);
-    mvaddnwstr(0, 0, WBUF, COLS - 4 - numsize);
-    wcolor_set(rover.window, RVC_BORDER, NULL);
-    wborder(rover.window, 0, 0, 0, 0, 0, 0, 0, 0);
-    ESEL = MAX(MIN(ESEL, rover.nfiles - 1), 0);
-    /* Selection might not be visible, due to cursor wrapping or window
-       shrinking. In that case, the scroll must be moved to make it visible. */
-    if (rover.nfiles > HEIGHT) {
-        SCROLL = MAX(MIN(SCROLL, ESEL), ESEL - HEIGHT + 1);
-        SCROLL = MIN(MAX(SCROLL, 0), rover.nfiles - HEIGHT);
-    } else
-        SCROLL = 0;
-    marking = !strcmp(CWD, rover.marks.dirpath);
-    for (i = 0, j = SCROLL; i < HEIGHT && j < rover.nfiles; i++, j++) {
-        ishidden = ENAME(j)[0] == '.';
-        if (j == ESEL)
-            wattr_on(rover.window, A_REVERSE, NULL);
-        if (ISLINK(j))
-            wcolor_set(rover.window, RVC_LINK, NULL);
-        else if (ishidden)
-            wcolor_set(rover.window, RVC_HIDDEN, NULL);
-        else if (S_ISREG(EMODE(j))) {
-            if (EMODE(j) & (S_IXUSR | S_IXGRP | S_IXOTH))
-                wcolor_set(rover.window, RVC_EXEC, NULL);
-            else
-                wcolor_set(rover.window, RVC_REG, NULL);
-        } else if (S_ISDIR(EMODE(j)))
-            wcolor_set(rover.window, RVC_DIR, NULL);
-        else if (S_ISCHR(EMODE(j)))
-            wcolor_set(rover.window, RVC_CHR, NULL);
-        else if (S_ISBLK(EMODE(j)))
-            wcolor_set(rover.window, RVC_BLK, NULL);
-        else if (S_ISFIFO(EMODE(j)))
-            wcolor_set(rover.window, RVC_FIFO, NULL);
-        else if (S_ISSOCK(EMODE(j)))
-            wcolor_set(rover.window, RVC_SOCK, NULL);
-        if (S_ISDIR(EMODE(j))) {
-            mbstowcs(WBUF, ENAME(j), PATH_MAX);
-            if (ISLINK(j))
-                wcscat(WBUF, L"/");
-        } else {
-            char *suffix, *suffixes = "BKMGTPEZY";
-            off_t human_size = ESIZE(j) * 10;
-            int length = mbstowcs(WBUF, ENAME(j), PATH_MAX);
-            int namecols = wcswidth(WBUF, length);
-            for (suffix = suffixes; human_size >= 10240; suffix++)
-                human_size = (human_size + 512) / 1024;
-            if (*suffix == 'B')
-                swprintf(WBUF + length, PATH_MAX - length, L"%*d %c",
-                         (int) (COLS - namecols - 6),
-                         (int) human_size / 10, *suffix);
-            else
-                swprintf(WBUF + length, PATH_MAX - length, L"%*d.%d %c",
-                         (int) (COLS - namecols - 8),
-                         (int) human_size / 10, (int) human_size % 10, *suffix);
-        }
-        mvwhline(rover.window, i + 1, 1, ' ', COLS - 2);
-        mvwaddnwstr(rover.window, i + 1, 2, WBUF, COLS - 4);
-        if (marking && MARKED(j)) {
-            wcolor_set(rover.window, RVC_MARKS, NULL);
-            mvwaddch(rover.window, i + 1, 1, RVS_MARK);
-        } else
-            mvwaddch(rover.window, i + 1, 1, ' ');
-        if (j == ESEL)
-            wattr_off(rover.window, A_REVERSE, NULL);
-    }
-    for (; i < HEIGHT; i++)
-        mvwhline(rover.window, i + 1, 1, ' ', COLS - 2);
-    if (rover.nfiles > HEIGHT) {
-        int center, height;
-        center = (SCROLL + HEIGHT / 2) * HEIGHT / rover.nfiles;
-        height = (HEIGHT-1) * HEIGHT / rover.nfiles;
-        if (!height) height = 1;
-        wcolor_set(rover.window, RVC_SCROLLBAR, NULL);
-        mvwvline(rover.window, center-height/2+1, COLS-1, RVS_SCROLLBAR, height);
-    }
-    BUF1[0] = FLAGS & SHOW_FILES  ? 'F' : ' ';
-    BUF1[1] = FLAGS & SHOW_DIRS   ? 'D' : ' ';
-    BUF1[2] = FLAGS & SHOW_HIDDEN ? 'H' : ' ';
-    if (!rover.nfiles)
-        strcpy(BUF2, "0/0");
-    else
-        snprintf(BUF2, BUFLEN, "%d/%d", ESEL + 1, rover.nfiles);
-    snprintf(BUF1+3, BUFLEN-3, "%12s", BUF2);
-    color_set(RVC_STATUS, NULL);
-    mvaddstr(LINES - 1, STATUSPOS, BUF1);
-    wrefresh(rover.window);
-}
-
-/* Show a message on the status bar. */
-static void
-message(Color color, char *fmt, ...)
-{
-    int len, pos;
-    va_list args;
-
-    va_start(args, fmt);
-    vsnprintf(BUF1, MIN(BUFLEN, STATUSPOS), fmt, args);
-    va_end(args);
-    len = strlen(BUF1);
-    pos = (STATUSPOS - len) / 2;
-    attr_on(A_BOLD, NULL);
-    color_set(color, NULL);
-    mvaddstr(LINES - 1, pos, BUF1);
-    color_set(DEFAULT, NULL);
-    attr_off(A_BOLD, NULL);
-}
-
-/* Clear message area, leaving only status info. */
-static void
-clear_message()
-{
-    mvhline(LINES - 1, 0, ' ', STATUSPOS);
-}
-
-/* Comparison used to sort listing entries. */
-static int
-rowcmp(const void *a, const void *b)
-{
-    int isdir1, isdir2, cmpdir;
-    const Row *r1 = a;
-    const Row *r2 = b;
-    isdir1 = S_ISDIR(r1->mode);
-    isdir2 = S_ISDIR(r2->mode);
-    cmpdir = isdir2 - isdir1;
-    return cmpdir ? cmpdir : strcoll(r1->name, r2->name);
-}
-
-/* Get all entries in current working directory. */
-static int
-ls(Row **rowsp, uint8_t flags)
-{
-    DIR *dp;
-    struct dirent *ep;
-    struct stat statbuf;
-    Row *rows;
-    int i, n;
-
-    if(!(dp = opendir("."))) return -1;
-    n = -2; /* We don't want the entries "." and "..". */
-    while (readdir(dp)) n++;
-    if (n == 0) {
-        closedir(dp);
-        return 0;
-    }
-    rewinddir(dp);
-    rows = malloc(n * sizeof *rows);
-    i = 0;
-    while ((ep = readdir(dp))) {
-        if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, ".."))
-            continue;
-        if (!(flags & SHOW_HIDDEN) && ep->d_name[0] == '.')
-            continue;
-        lstat(ep->d_name, &statbuf);
-        rows[i].islink = S_ISLNK(statbuf.st_mode);
-        stat(ep->d_name, &statbuf);
-        if (S_ISDIR(statbuf.st_mode)) {
-            if (flags & SHOW_DIRS) {
-                rows[i].name = malloc(strlen(ep->d_name) + 2);
-                strcpy(rows[i].name, ep->d_name);
-                if (!rows[i].islink)
-                    strcat(rows[i].name, "/");
-                rows[i].mode = statbuf.st_mode;
-                i++;
-            }
-        } else if (flags & SHOW_FILES) {
-            rows[i].name = malloc(strlen(ep->d_name) + 1);
-            strcpy(rows[i].name, ep->d_name);
-            rows[i].size = statbuf.st_size;
-            rows[i].mode = statbuf.st_mode;
-            i++;
-        }
-    }
-    n = i; /* Ignore unused space in array caused by filters. */
-    qsort(rows, n, sizeof (*rows), rowcmp);
-    closedir(dp);
-    *rowsp = rows;
-    return n;
-}
-
-static void
-free_rows(Row **rowsp, int nfiles)
-{
-    int i;
-
-    for (i = 0; i < nfiles; i++)
-        free((*rowsp)[i].name);
-    free(*rowsp);
-    *rowsp = NULL;
-}
-
-/* Change working directory to the path in CWD. */
-static void
-cd(int reset)
-{
-    int i, j;
-
-    message(CYAN, "Loading \"%s\"...", CWD);
-    refresh();
-    if (chdir(CWD) == -1) {
-        getcwd(CWD, PATH_MAX-1);
-        if (CWD[strlen(CWD)-1] != '/')
-            strcat(CWD, "/");
-        goto done;
-    }
-    if (reset) ESEL = SCROLL = 0;
-    if (rover.nfiles)
-        free_rows(&rover.rows, rover.nfiles);
-    rover.nfiles = ls(&rover.rows, FLAGS);
-    if (!strcmp(CWD, rover.marks.dirpath)) {
-        for (i = 0; i < rover.nfiles; i++) {
-            for (j = 0; j < rover.marks.bulk; j++)
-                if (
-                    rover.marks.entries[j] &&
-                    !strcmp(rover.marks.entries[j], ENAME(i))
-                )
-                    break;
-            MARKED(i) = j < rover.marks.bulk;
-        }
-    } else
-        for (i = 0; i < rover.nfiles; i++)
-            MARKED(i) = 0;
-done:
-    clear_message();
-    update_view();
-}
-
-/* Select a target entry, if it is present. */
-static void
-try_to_sel(const char *target)
-{
-    ESEL = 0;
-    if (!ISDIR(target))
-        while ((ESEL+1) < rover.nfiles && S_ISDIR(EMODE(ESEL)))
-            ESEL++;
-    while ((ESEL+1) < rover.nfiles && strcoll(ENAME(ESEL), target) < 0)
-        ESEL++;
-}
-
-/* Reload CWD, but try to keep selection. */
-static void
-reload()
-{
-    if (rover.nfiles) {
-        strcpy(INPUT, ENAME(ESEL));
-        cd(0);
-        try_to_sel(INPUT);
-        update_view();
-    } else
-        cd(1);
-}
-
-static off_t
-count_dir(const char *path)
-{
-    DIR *dp;
-    struct dirent *ep;
-    struct stat statbuf;
-    char subpath[PATH_MAX];
-    off_t total;
-
-    if(!(dp = opendir(path))) return 0;
-    total = 0;
-    while ((ep = readdir(dp))) {
-        if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, ".."))
-            continue;
-        snprintf(subpath, PATH_MAX, "%s%s", path, ep->d_name);
-        lstat(subpath, &statbuf);
-        if (S_ISDIR(statbuf.st_mode)) {
-            strcat(subpath, "/");
-            total += count_dir(subpath);
-        } else
-            total += statbuf.st_size;
-    }
-    closedir(dp);
-    return total;
-}
-
-static off_t
-count_marked()
-{
-    int i;
-    char *entry;
-    off_t total;
-    struct stat statbuf;
-
-    total = 0;
-    chdir(rover.marks.dirpath);
-    for (i = 0; i < rover.marks.bulk; i++) {
-        entry = rover.marks.entries[i];
-        if (entry) {
-            if (ISDIR(entry)) {
-                total += count_dir(entry);
-            } else {
-                lstat(entry, &statbuf);
-                total += statbuf.st_size;
-            }
-        }
-    }
-    chdir(CWD);
-    return total;
-}
-
-/* Recursively process a source directory using CWD as destination root.
-   For each node (i.e. directory), do the following:
-    1. call pre(destination);
-    2. call proc() on every child leaf (i.e. files);
-    3. recurse into every child node;
-    4. call pos(source).
-   E.g. to move directory /src/ (and all its contents) inside /dst/:
-    strcpy(CWD, "/dst/");
-    process_dir(adddir, movfile, deldir, "/src/"); */
-static int
-process_dir(PROCESS pre, PROCESS proc, PROCESS pos, const char *path)
-{
-    int ret;
-    DIR *dp;
-    struct dirent *ep;
-    struct stat statbuf;
-    char subpath[PATH_MAX];
-
-    ret = 0;
-    if (pre) {
-        char dstpath[PATH_MAX];
-        strcpy(dstpath, CWD);
-        strcat(dstpath, path + strlen(rover.marks.dirpath));
-        ret |= pre(dstpath);
-    }
-    if(!(dp = opendir(path))) return -1;
-    while ((ep = readdir(dp))) {
-        if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, ".."))
-            continue;
-        snprintf(subpath, PATH_MAX, "%s%s", path, ep->d_name);
-        lstat(subpath, &statbuf);
-        if (S_ISDIR(statbuf.st_mode)) {
-            strcat(subpath, "/");
-            ret |= process_dir(pre, proc, pos, subpath);
-        } else
-            ret |= proc(subpath);
-    }
-    closedir(dp);
-    if (pos) ret |= pos(path);
-    return ret;
-}
-
-/* Process all marked entries using CWD as destination root.
-   All marked entries that are directories will be recursively processed.
-   See process_dir() for details on the parameters. */
-static void
-process_marked(PROCESS pre, PROCESS proc, PROCESS pos,
-               const char *msg_doing, const char *msg_done)
-{
-    int i, ret;
-    char *entry;
-    char path[PATH_MAX];
-
-    clear_message();
-    message(CYAN, "%s...", msg_doing);
-    refresh();
-    rover.prog = (Prog) {0, count_marked(), msg_doing};
-    for (i = 0; i < rover.marks.bulk; i++) {
-        entry = rover.marks.entries[i];
-        if (entry) {
-            ret = 0;
-            snprintf(path, PATH_MAX, "%s%s", rover.marks.dirpath, entry);
-            if (ISDIR(entry)) {
-                if (!strncmp(path, CWD, strlen(path)))
-                    ret = -1;
-                else
-                    ret = process_dir(pre, proc, pos, path);
-            } else
-                ret = proc(path);
-            if (!ret) {
-                del_mark(&rover.marks, entry);
-                reload();
-            }
-        }
-    }
-    rover.prog.total = 0;
-    reload();
-    if (!rover.marks.nentries)
-        message(GREEN, "%s all marked entries.", msg_done);
-    else
-        message(RED, "Some errors occured while %s.", msg_doing);
-    RV_ALERT();
-}
-
-static void
-update_progress(off_t delta)
-{
-    int percent;
-
-    if (!rover.prog.total) return;
-    rover.prog.partial += delta;
-    percent = (int) (rover.prog.partial * 100 / rover.prog.total);
-    message(CYAN, "%s...%d%%", rover.prog.msg, percent);
-    refresh();
-}
-
-/* Wrappers for file operations. */
-static int delfile(const char *path) {
-    int ret;
-    struct stat st;
-
-    ret = lstat(path, &st);
-    if (ret < 0) return ret;
-    update_progress(st.st_size);
-    return unlink(path);
-}
-static PROCESS deldir = rmdir;
-static int addfile(const char *path) {
-    /* Using creat(2) because mknod(2) doesn't seem to be portable. */
-    int ret;
-
-    ret = creat(path, 0644);
-    if (ret < 0) return ret;
-    return close(ret);
-}
-static int cpyfile(const char *srcpath) {
-    int src, dst, ret;
-    size_t size;
-    struct stat st;
-    char buf[BUFSIZ];
-    char dstpath[PATH_MAX];
-
-    strcpy(dstpath, CWD);
-    strcat(dstpath, srcpath + strlen(rover.marks.dirpath));
-    ret = lstat(srcpath, &st);
-    if (ret < 0) return ret;
-    if (S_ISLNK(st.st_mode)) {
-        ret = readlink(srcpath, BUF1, BUFLEN-1);
-        if (ret < 0) return ret;
-        BUF1[ret] = '\0';
-        ret = symlink(BUF1, dstpath);
-    } else {
-        ret = src = open(srcpath, O_RDONLY);
-        if (ret < 0) return ret;
-        ret = dst = creat(dstpath, st.st_mode);
-        if (ret < 0) return ret;
-        while ((size = read(src, buf, BUFSIZ)) > 0) {
-            write(dst, buf, size);
-            update_progress(size);
-            sync_signals();
-        }
-        close(src);
-        close(dst);
-        ret = 0;
-    }
-    return ret;
-}
-static int adddir(const char *path) {
-    int ret;
-    struct stat st;
-
-    ret = stat(CWD, &st);
-    if (ret < 0) return ret;
-    return mkdir(path, st.st_mode);
-}
-static int movfile(const char *srcpath) {
-    int ret;
-    struct stat st;
-    char dstpath[PATH_MAX];
-
-    strcpy(dstpath, CWD);
-    strcat(dstpath, srcpath + strlen(rover.marks.dirpath));
-    ret = rename(srcpath, dstpath);
-    if (ret == 0) {
-        ret = lstat(dstpath, &st);
-        if (ret < 0) return ret;
-        update_progress(st.st_size);
-    } else if (errno == EXDEV) {
-        ret = cpyfile(srcpath);
-        if (ret < 0) return ret;
-        ret = unlink(srcpath);
-    }
-    return ret;
-}
-
-static void
-start_line_edit(const char *init_input)
-{
-    curs_set(TRUE);
-    strncpy(INPUT, init_input, BUFLEN);
-    rover.edit.left = mbstowcs(rover.edit.buffer, init_input, BUFLEN);
-    rover.edit.right = BUFLEN - 1;
-    rover.edit.buffer[BUFLEN] = L'\0';
-    rover.edit_scroll = 0;
-}
-
-/* Read input and change editing state accordingly. */
-static EditStat
-get_line_edit()
-{
-    wchar_t eraser, killer, wch;
-    int ret, length;
-
-    ret = rover_get_wch((wint_t *) &wch);
-    erasewchar(&eraser);
-    killwchar(&killer);
-    if (ret == KEY_CODE_YES) {
-        if (wch == KEY_ENTER) {
-            curs_set(FALSE);
-            return CONFIRM;
-        } else if (wch == KEY_LEFT) {
-            if (EDIT_CAN_LEFT(rover.edit)) EDIT_LEFT(rover.edit);
-        } else if (wch == KEY_RIGHT) {
-            if (EDIT_CAN_RIGHT(rover.edit)) EDIT_RIGHT(rover.edit);
-        } else if (wch == KEY_UP) {
-            while (EDIT_CAN_LEFT(rover.edit)) EDIT_LEFT(rover.edit);
-        } else if (wch == KEY_DOWN) {
-            while (EDIT_CAN_RIGHT(rover.edit)) EDIT_RIGHT(rover.edit);
-        } else if (wch == KEY_BACKSPACE) {
-            if (EDIT_CAN_LEFT(rover.edit)) EDIT_BACKSPACE(rover.edit);
-        } else if (wch == KEY_DC) {
-            if (EDIT_CAN_RIGHT(rover.edit)) EDIT_DELETE(rover.edit);
-        }
-    } else {
-        if (wch == L'\r' || wch == L'\n') {
-            curs_set(FALSE);
-            return CONFIRM;
-        } else if (wch == L'\t') {
-            curs_set(FALSE);
-            return CANCEL;
-        } else if (wch == eraser) {
-            if (EDIT_CAN_LEFT(rover.edit)) EDIT_BACKSPACE(rover.edit);
-        } else if (wch == killer) {
-            EDIT_CLEAR(rover.edit);
-            clear_message();
-        } else if (iswprint(wch)) {
-            if (!EDIT_FULL(rover.edit)) EDIT_INSERT(rover.edit, wch);
-        }
-    }
-    /* Encode edit contents in INPUT. */
-    rover.edit.buffer[rover.edit.left] = L'\0';
-    length = wcstombs(INPUT, rover.edit.buffer, BUFLEN);
-    wcstombs(&INPUT[length], &rover.edit.buffer[rover.edit.right+1],
-             BUFLEN-length);
-    return CONTINUE;
-}
-
-/* Update line input on the screen. */
-static void
-update_input(const char *prompt, Color color)
-{
-    int plen, ilen, maxlen;
-
-    plen = strlen(prompt);
-    ilen = mbstowcs(NULL, INPUT, 0);
-    maxlen = STATUSPOS - plen - 2;
-    if (ilen - rover.edit_scroll < maxlen)
-        rover.edit_scroll = MAX(ilen - maxlen, 0);
-    else if (rover.edit.left > rover.edit_scroll + maxlen - 1)
-        rover.edit_scroll = rover.edit.left - maxlen;
-    else if (rover.edit.left < rover.edit_scroll)
-        rover.edit_scroll = MAX(rover.edit.left - maxlen, 0);
-    color_set(RVC_PROMPT, NULL);
-    mvaddstr(LINES - 1, 0, prompt);
-    color_set(color, NULL);
-    mbstowcs(WBUF, INPUT, COLS);
-    mvaddnwstr(LINES - 1, plen, &WBUF[rover.edit_scroll], maxlen);
-    mvaddch(LINES - 1, plen + MIN(ilen - rover.edit_scroll, maxlen + 1), ' ');
-    color_set(DEFAULT, NULL);
-    if (rover.edit_scroll)
-        mvaddch(LINES - 1, plen - 1, '<');
-    if (ilen > rover.edit_scroll + maxlen)
-        mvaddch(LINES - 1, plen + maxlen, '>');
-    move(LINES - 1, plen + rover.edit.left - rover.edit_scroll);
-}
-
-int
-main(int argc, char *argv[])
-{
-    int i, ch;
-    char *program;
-    char *entry;
-    const char *key;
-    const char *clip_path;
-    DIR *d;
-    EditStat edit_stat;
-    FILE *save_cwd_file = NULL;
-    FILE *save_marks_file = NULL;
-    FILE *clip_file;
-
-    if (argc >= 2) {
-        if (!strcmp(argv[1], "-v") || !strcmp(argv[1], "--version")) {
-            printf("rover %s\n", RV_VERSION);
-            return 0;
-        } else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
-            printf(
-                "Usage: rover [OPTIONS] [DIR [DIR [...]]]\n"
-                "       Browse current directory or the ones specified.\n\n"
-                "  or:  rover -h|--help\n"
-                "       Print this help message and exit.\n\n"
-                "  or:  rover -v|--version\n"
-                "       Print program version and exit.\n\n"
-                "See rover(1) for more information.\n"
-                "Rover homepage: <https://github.com/lecram/rover>.\n"
-            );
-            return 0;
-        } else if (!strcmp(argv[1], "-d") || !strcmp(argv[1], "--save-cwd")) {
-            if (argc > 2) {
-                save_cwd_file = fopen(argv[2], "w");
-                argc -= 2; argv += 2;
-            } else {
-                fprintf(stderr, "error: missing argument to %s\n", argv[1]);
-                return 1;
-            }
-        } else if (!strcmp(argv[1], "-m") || !strcmp(argv[1], "--save-marks")) {
-            if (argc > 2) {
-                save_marks_file = fopen(argv[2], "a");
-                argc -= 2; argv += 2;
-            } else {
-                fprintf(stderr, "error: missing argument to %s\n", argv[1]);
-                return 1;
-            }
-        }
-    }
-    get_user_programs();
-    init_term();
-    rover.nfiles = 0;
-    for (i = 0; i < 10; i++) {
-        rover.tabs[i].esel = rover.tabs[i].scroll = 0;
-        rover.tabs[i].flags = RV_FLAGS;
-    }
-    strcpy(rover.tabs[0].cwd, getenv("HOME"));
-    for (i = 1; i < argc && i < 10; i++) {
-        if ((d = opendir(argv[i]))) {
-            realpath(argv[i], rover.tabs[i].cwd);
-            closedir(d);
-        } else
-            strcpy(rover.tabs[i].cwd, rover.tabs[0].cwd);
-    }
-    getcwd(rover.tabs[i].cwd, PATH_MAX);
-    for (i++; i < 10; i++)
-        strcpy(rover.tabs[i].cwd, rover.tabs[i-1].cwd);
-    for (i = 0; i < 10; i++)
-        if (rover.tabs[i].cwd[strlen(rover.tabs[i].cwd) - 1] != '/')
-            strcat(rover.tabs[i].cwd, "/");
-    rover.tab = 1;
-    rover.window = subwin(stdscr, LINES - 2, COLS, 1, 0);
-    init_marks(&rover.marks);
-    cd(1);
-    strcpy(CLIPBOARD, CWD);
-    if (rover.nfiles > 0)
-        strcat(CLIPBOARD, ENAME(ESEL));
-    while (1) {
-        ch = rover_getch();
-        key = keyname(ch);
-        clear_message();
-        if (!strcmp(key, RVK_QUIT)) break;
-        else if (ch >= '0' && ch <= '9') {
-            rover.tab = ch - '0';
-            cd(0);
-        } else if (!strcmp(key, RVK_HELP)) {
-            spawn((char *[]) {"man", "rover", NULL});
-        } else if (!strcmp(key, RVK_DOWN)) {
-            if (!rover.nfiles) continue;
-            ESEL = MIN(ESEL + 1, rover.nfiles - 1);
-            update_view();
-        } else if (!strcmp(key, RVK_UP)) {
-            if (!rover.nfiles) continue;
-            ESEL = MAX(ESEL - 1, 0);
-            update_view();
-        } else if (!strcmp(key, RVK_JUMP_DOWN)) {
-            if (!rover.nfiles) continue;
-            ESEL = MIN(ESEL + RV_JUMP, rover.nfiles - 1);
-            if (rover.nfiles > HEIGHT)
-                SCROLL = MIN(SCROLL + RV_JUMP, rover.nfiles - HEIGHT);
-            update_view();
-        } else if (!strcmp(key, RVK_JUMP_UP)) {
-            if (!rover.nfiles) continue;
-            ESEL = MAX(ESEL - RV_JUMP, 0);
-            SCROLL = MAX(SCROLL - RV_JUMP, 0);
-            update_view();
-        } else if (!strcmp(key, RVK_JUMP_TOP)) {
-            if (!rover.nfiles) continue;
-            ESEL = 0;
-            update_view();
-        } else if (!strcmp(key, RVK_JUMP_BOTTOM)) {
-            if (!rover.nfiles) continue;
-            ESEL = rover.nfiles - 1;
-            update_view();
-        } else if (!strcmp(key, RVK_CD_DOWN)) {
-            if (!rover.nfiles || !S_ISDIR(EMODE(ESEL))) continue;
-            if (chdir(ENAME(ESEL)) == -1) {
-                message(RED, "Cannot access \"%s\".", ENAME(ESEL));
-                continue;
-            }
-            strcat(CWD, ENAME(ESEL));
-            cd(1);
-        } else if (!strcmp(key, RVK_CD_UP)) {
-            char *dirname, first;
-            if (!strcmp(CWD, "/")) continue;
-            CWD[strlen(CWD) - 1] = '\0';
-            dirname = strrchr(CWD, '/') + 1;
-            first = dirname[0];
-            dirname[0] = '\0';
-            cd(1);
-            dirname[0] = first;
-            dirname[strlen(dirname)] = '/';
-            try_to_sel(dirname);
-            dirname[0] = '\0';
-            if (rover.nfiles > HEIGHT)
-                SCROLL = ESEL - HEIGHT / 2;
-            update_view();
-        } else if (!strcmp(key, RVK_HOME)) {
-            strcpy(CWD, getenv("HOME"));
-            if (CWD[strlen(CWD) - 1] != '/')
-                strcat(CWD, "/");
-            cd(1);
-        } else if (!strcmp(key, RVK_TARGET)) {
-            char *bname, first;
-            int is_dir = S_ISDIR(EMODE(ESEL));
-            ssize_t len = readlink(ENAME(ESEL), BUF1, BUFLEN-1);
-            if (len == -1) continue;
-            BUF1[len] = '\0';
-            if (access(BUF1, F_OK) == -1) {
-                char *msg;
-                switch (errno) {
-                case EACCES:
-                    msg = "Cannot access \"%s\".";
-                    break;
-                case ENOENT:
-                    msg = "\"%s\" does not exist.";
-                    break;
-                default:
-                    msg = "Cannot navigate to \"%s\".";
-                }
-                strcpy(BUF2, BUF1); /* message() uses BUF1. */
-                message(RED, msg, BUF2);
-                continue;
-            }
-            realpath(BUF1, CWD);
-            len = strlen(CWD);
-            if (CWD[len - 1] == '/')
-                CWD[len - 1] = '\0';
-            bname = strrchr(CWD, '/') + 1;
-            first = *bname;
-            *bname = '\0';
-            cd(1);
-            *bname = first;
-            if (is_dir)
-                strcat(CWD, "/");
-            try_to_sel(bname);
-            *bname = '\0';
-            update_view();
-        } else if (!strcmp(key, RVK_COPY_PATH)) {
-            clip_path = getenv("CLIP");
-            if (!clip_path) goto copy_path_fail;
-            clip_file = fopen(clip_path, "w");
-            if (!clip_file) goto copy_path_fail;
-            fprintf(clip_file, "%s%s\n", CWD, ENAME(ESEL));
-            fclose(clip_file);
-            goto copy_path_done;
-copy_path_fail:
-            strcpy(CLIPBOARD, CWD);
-            strcat(CLIPBOARD, ENAME(ESEL));
-copy_path_done:
-            ;
-        } else if (!strcmp(key, RVK_PASTE_PATH)) {
-            clip_path = getenv("CLIP");
-            if (!clip_path) goto paste_path_fail;
-            clip_file = fopen(clip_path, "r");
-            if (!clip_file) goto paste_path_fail;
-            fscanf(clip_file, "%s\n", CLIPBOARD);
-            fclose(clip_file);
-paste_path_fail:
-            strcpy(BUF1, CLIPBOARD);
-            strcpy(CWD, dirname(BUF1));
-            if (strcmp(CWD, "/"))
-                strcat(CWD, "/");
-            cd(1);
-            strcpy(BUF1, CLIPBOARD);
-            try_to_sel(strstr(CLIPBOARD, basename(BUF1)));
-            update_view();
-        } else if (!strcmp(key, RVK_REFRESH)) {
-            reload();
-        } else if (!strcmp(key, RVK_SHELL)) {
-            program = user_shell;
-            if (program) {
-#ifdef RV_SHELL
-                spawn((char *[]) {RV_SHELL, "-c", program, NULL});
-#else
-                spawn((char *[]) {program, NULL});
-#endif
-                reload();
-            }
-        } else if (!strcmp(key, RVK_VIEW)) {
-            if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) continue;
-            if (open_with_env(user_pager, ENAME(ESEL)))
-                cd(0);
-        } else if (!strcmp(key, RVK_EDIT)) {
-            if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) continue;
-            if (open_with_env(user_editor, ENAME(ESEL)))
-                cd(0);
-        } else if (!strcmp(key, RVK_OPEN)) {
-            if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) continue;
-            if (open_with_env(user_open, ENAME(ESEL)))
-                cd(0);
-        } else if (!strcmp(key, RVK_SEARCH)) {
-            int oldsel, oldscroll, length;
-            if (!rover.nfiles) continue;
-            oldsel = ESEL;
-            oldscroll = SCROLL;
-            start_line_edit("");
-            update_input(RVP_SEARCH, RED);
-            while ((edit_stat = get_line_edit()) == CONTINUE) {
-                int sel;
-                Color color = RED;
-                length = strlen(INPUT);
-                if (length) {
-                    for (sel = 0; sel < rover.nfiles; sel++)
-                        if (!strncmp(ENAME(sel), INPUT, length))
-                            break;
-                    if (sel < rover.nfiles) {
-                        color = GREEN;
-                        ESEL = sel;
-                        if (rover.nfiles > HEIGHT) {
-                            if (sel < 3)
-                                SCROLL = 0;
-                            else if (sel - 3 > rover.nfiles - HEIGHT)
-                                SCROLL = rover.nfiles - HEIGHT;
-                            else
-                                SCROLL = sel - 3;
-                        }
-                    }
-                } else {
-                    ESEL = oldsel;
-                    SCROLL = oldscroll;
-                }
-                update_view();
-                update_input(RVP_SEARCH, color);
-            }
-            if (edit_stat == CANCEL) {
-                ESEL = oldsel;
-                SCROLL = oldscroll;
-            }
-            clear_message();
-            update_view();
-        } else if (!strcmp(key, RVK_TG_FILES)) {
-            FLAGS ^= SHOW_FILES;
-            reload();
-        } else if (!strcmp(key, RVK_TG_DIRS)) {
-            FLAGS ^= SHOW_DIRS;
-            reload();
-        } else if (!strcmp(key, RVK_TG_HIDDEN)) {
-            FLAGS ^= SHOW_HIDDEN;
-            reload();
-        } else if (!strcmp(key, RVK_NEW_FILE)) {
-            int ok = 0;
-            start_line_edit("");
-            update_input(RVP_NEW_FILE, RED);
-            while ((edit_stat = get_line_edit()) == CONTINUE) {
-                int length = strlen(INPUT);
-                ok = length;
-                for (i = 0; i < rover.nfiles; i++) {
-                    if (
-                        !strncmp(ENAME(i), INPUT, length) &&
-                        (!strcmp(ENAME(i) + length, "") ||
-                         !strcmp(ENAME(i) + length, "/"))
-                    ) {
-                        ok = 0;
-                        break;
-                    }
-                }
-                update_input(RVP_NEW_FILE, ok ? GREEN : RED);
-            }
-            clear_message();
-            if (edit_stat == CONFIRM) {
-                if (ok) {
-                    if (addfile(INPUT) == 0) {
-                        cd(1);
-                        try_to_sel(INPUT);
-                        update_view();
-                    } else
-                        message(RED, "Could not create \"%s\".", INPUT);
-                } else
-                    message(RED, "\"%s\" already exists.", INPUT);
-            }
-        } else if (!strcmp(key, RVK_NEW_DIR)) {
-            int ok = 0;
-            start_line_edit("");
-            update_input(RVP_NEW_DIR, RED);
-            while ((edit_stat = get_line_edit()) == CONTINUE) {
-                int length = strlen(INPUT);
-                ok = length;
-                for (i = 0; i < rover.nfiles; i++) {
-                    if (
-                        !strncmp(ENAME(i), INPUT, length) &&
-                        (!strcmp(ENAME(i) + length, "") ||
-                         !strcmp(ENAME(i) + length, "/"))
-                    ) {
-                        ok = 0;
-                        break;
-                    }
-                }
-                update_input(RVP_NEW_DIR, ok ? GREEN : RED);
-            }
-            clear_message();
-            if (edit_stat == CONFIRM) {
-                if (ok) {
-                    if (adddir(INPUT) == 0) {
-                        cd(1);
-                        strcat(INPUT, "/");
-                        try_to_sel(INPUT);
-                        update_view();
-                    } else
-                        message(RED, "Could not create \"%s/\".", INPUT);
-                } else
-                    message(RED, "\"%s\" already exists.", INPUT);
-            }
-        } else if (!strcmp(key, RVK_RENAME)) {
-            int ok = 0;
-            char *last;
-            int isdir;
-            strcpy(INPUT, ENAME(ESEL));
-            last = INPUT + strlen(INPUT) - 1;
-            if ((isdir = *last == '/'))
-                *last = '\0';
-            start_line_edit(INPUT);
-            update_input(RVP_RENAME, RED);
-            while ((edit_stat = get_line_edit()) == CONTINUE) {
-                int length = strlen(INPUT);
-                ok = length;
-                for (i = 0; i < rover.nfiles; i++)
-                    if (
-                        !strncmp(ENAME(i), INPUT, length) &&
-                        (!strcmp(ENAME(i) + length, "") ||
-                         !strcmp(ENAME(i) + length, "/"))
-                    ) {
-                        ok = 0;
-                        break;
-                    }
-                update_input(RVP_RENAME, ok ? GREEN : RED);
-            }
-            clear_message();
-            if (edit_stat == CONFIRM) {
-                if (isdir)
-                    strcat(INPUT, "/");
-                if (ok) {
-                    if (!rename(ENAME(ESEL), INPUT) && MARKED(ESEL)) {
-                        del_mark(&rover.marks, ENAME(ESEL));
-                        add_mark(&rover.marks, CWD, INPUT);
-                    }
-                    cd(1);
-                    try_to_sel(INPUT);
-                    update_view();
-                } else
-                    message(RED, "\"%s\" already exists.", INPUT);
-            }
-        } else if (!strcmp(key, RVK_TG_EXEC)) {
-            if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) continue;
-            if (S_IXUSR & EMODE(ESEL))
-                EMODE(ESEL) &= ~(S_IXUSR | S_IXGRP | S_IXOTH);
-            else
-                EMODE(ESEL) |=   S_IXUSR | S_IXGRP | S_IXOTH ;
-            if (chmod(ENAME(ESEL), EMODE(ESEL))) {
-                message(RED, "Failed to change mode of \"%s\".", ENAME(ESEL));
-            } else {
-                message(GREEN, "Changed mode of \"%s\".", ENAME(ESEL));
-                update_view();
-            }
-        } else if (!strcmp(key, RVK_DELETE)) {
-            if (rover.nfiles) {
-                message(YELLOW, "Delete \"%s\"? (Y/n)", ENAME(ESEL));
-                if (rover_getch() == 'Y') {
-                    const char *name = ENAME(ESEL);
-                    int ret = ISDIR(ENAME(ESEL)) ? deldir(name) : delfile(name);
-                    reload();
-                    if (ret)
-                        message(RED, "Could not delete \"%s\".", ENAME(ESEL));
-                } else
-                    clear_message();
-            } else
-                  message(RED, "No entry selected for deletion.");
-        } else if (!strcmp(key, RVK_TG_MARK)) {
-            if (MARKED(ESEL))
-                del_mark(&rover.marks, ENAME(ESEL));
-            else
-                add_mark(&rover.marks, CWD, ENAME(ESEL));
-            MARKED(ESEL) = !MARKED(ESEL);
-            ESEL = (ESEL + 1) % rover.nfiles;
-            update_view();
-        } else if (!strcmp(key, RVK_INVMARK)) {
-            for (i = 0; i < rover.nfiles; i++) {
-                if (MARKED(i))
-                    del_mark(&rover.marks, ENAME(i));
-                else
-                    add_mark(&rover.marks, CWD, ENAME(i));
-                MARKED(i) = !MARKED(i);
-            }
-            update_view();
-        } else if (!strcmp(key, RVK_MARKALL)) {
-            for (i = 0; i < rover.nfiles; i++)
-                if (!MARKED(i)) {
-                    add_mark(&rover.marks, CWD, ENAME(i));
-                    MARKED(i) = 1;
-                }
-            update_view();
-        } else if (!strcmp(key, RVK_MARK_DELETE)) {
-            if (rover.marks.nentries) {
-                message(YELLOW, "Delete all marked entries? (Y/n)");
-                if (rover_getch() == 'Y')
-                    process_marked(NULL, delfile, deldir, "Deleting", "Deleted");
-                else
-                    clear_message();
-            } else
-                message(RED, "No entries marked for deletion.");
-        } else if (!strcmp(key, RVK_MARK_COPY)) {
-            if (rover.marks.nentries) {
-                if (strcmp(CWD, rover.marks.dirpath))
-                    process_marked(adddir, cpyfile, NULL, "Copying", "Copied");
-                else
-                    message(RED, "Cannot copy to the same path.");
-            } else
-                message(RED, "No entries marked for copying.");
-        } else if (!strcmp(key, RVK_MARK_MOVE)) {
-            if (rover.marks.nentries) {
-                if (strcmp(CWD, rover.marks.dirpath))
-                    process_marked(adddir, movfile, deldir, "Moving", "Moved");
-                else
-                    message(RED, "Cannot move to the same path.");
-            } else
-                message(RED, "No entries marked for moving.");
-        }
-    }
-    if (rover.nfiles)
-        free_rows(&rover.rows, rover.nfiles);
-    delwin(rover.window);
-    if (save_cwd_file != NULL) {
-        fputs(CWD, save_cwd_file);
-        fclose(save_cwd_file);
-    }
-    if (save_marks_file != NULL) {
-        for (i = 0; i < rover.marks.bulk; i++) {
-            entry = rover.marks.entries[i];
-            if (entry)
-                fprintf(save_marks_file, "%s%s\n", rover.marks.dirpath, entry);
-        }
-        fclose(save_marks_file);
-    }
-    free_marks(&rover.marks);
-    return 0;
-}
blob - /dev/null
blob + eca6aebc7cbb18cacda010b7d6ee4799b3212035 (mode 644)
--- /dev/null
+++ fm.c
@@ -0,0 +1,1500 @@
+#define _XOPEN_SOURCE       700
+#define _XOPEN_SOURCE_EXTENDED
+#define _FILE_OFFSET_BITS   64
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <ctype.h>
+#include <wchar.h>
+#include <wctype.h>
+#include <string.h>
+#include <sys/types.h>  /* pid_t, ... */
+#include <stdio.h>
+#include <limits.h>     /* PATH_MAX */
+#include <locale.h>     /* setlocale(), LC_ALL */
+#include <unistd.h>     /* chdir(), getcwd(), read(), close(), ... */
+#include <dirent.h>     /* DIR, struct dirent, opendir(), ... */
+#include <libgen.h>
+#include <sys/stat.h>
+#include <fcntl.h>      /* open() */
+#include <sys/wait.h>   /* waitpid() */
+#include <signal.h>     /* struct sigaction, sigaction() */
+#include <errno.h>
+#include <stdarg.h>
+#include <curses.h>
+
+#include "config.h"
+
+/*  This signal is not defined by POSIX, but should be
+   present on all systems that have resizable terminals. */
+#ifndef SIGWINCH
+#define SIGWINCH  28
+#endif
+
+/* String buffers. */
+#define BUFLEN  PATH_MAX
+static char BUF1[BUFLEN];
+static char BUF2[BUFLEN];
+static char INPUT[BUFLEN];
+static char CLIPBOARD[BUFLEN];
+static wchar_t WBUF[BUFLEN];
+
+/* Paths to external programs. */
+static char *user_shell;
+static char *user_pager;
+static char *user_editor;
+static char *user_open;
+
+/* Listing view parameters. */
+#define HEIGHT      (LINES-4)
+#define STATUSPOS   (COLS-16)
+
+/* Listing view flags. */
+#define SHOW_FILES      0x01u
+#define SHOW_DIRS       0x02u
+#define SHOW_HIDDEN     0x04u
+
+/* Marks parameters. */
+#define BULK_INIT   5
+#define BULK_THRESH 256
+
+/* Information associated to each entry in listing. */
+typedef struct Row {
+    char *name;
+    off_t size;
+    mode_t mode;
+    int islink;
+    int marked;
+} Row;
+
+/* Dynamic array of marked entries. */
+typedef struct Marks {
+    char dirpath[PATH_MAX];
+    int bulk;
+    int nentries;
+    char **entries;
+} Marks;
+
+/* Line editing state. */
+typedef struct Edit {
+    wchar_t buffer[BUFLEN+1];
+    int left, right;
+} Edit;
+
+/* Each tab only stores the following information. */
+typedef struct Tab {
+    int scroll;
+    int esel;
+    uint8_t flags;
+    char cwd[PATH_MAX];
+} Tab;
+
+typedef struct Prog {
+    off_t partial;
+    off_t total;
+    const char *msg;
+} Prog;
+
+/* Global state. */
+static struct Rover {
+    int tab;
+    int nfiles;
+    Row *rows;
+    WINDOW *window;
+    Marks marks;
+    Edit edit;
+    int edit_scroll;
+    volatile sig_atomic_t pending_usr1;
+    volatile sig_atomic_t pending_winch;
+    Prog prog;
+    Tab tabs[10];
+} rover;
+
+/* Macros for accessing global state. */
+#define ENAME(I)    rover.rows[I].name
+#define ESIZE(I)    rover.rows[I].size
+#define EMODE(I)    rover.rows[I].mode
+#define ISLINK(I)   rover.rows[I].islink
+#define MARKED(I)   rover.rows[I].marked
+#define SCROLL      rover.tabs[rover.tab].scroll
+#define ESEL        rover.tabs[rover.tab].esel
+#define FLAGS       rover.tabs[rover.tab].flags
+#define CWD         rover.tabs[rover.tab].cwd
+
+/* Helpers. */
+#define MIN(A, B)   ((A) < (B) ? (A) : (B))
+#define MAX(A, B)   ((A) > (B) ? (A) : (B))
+#define ISDIR(E)    (strchr((E), '/') != NULL)
+
+/* Line Editing Macros. */
+#define EDIT_FULL(E)       ((E).left == (E).right)
+#define EDIT_CAN_LEFT(E)   ((E).left)
+#define EDIT_CAN_RIGHT(E)  ((E).right < BUFLEN-1)
+#define EDIT_LEFT(E)       (E).buffer[(E).right--] = (E).buffer[--(E).left]
+#define EDIT_RIGHT(E)      (E).buffer[(E).left++] = (E).buffer[++(E).right]
+#define EDIT_INSERT(E, C)  (E).buffer[(E).left++] = (C)
+#define EDIT_BACKSPACE(E)  (E).left--
+#define EDIT_DELETE(E)     (E).right++
+#define EDIT_CLEAR(E)      do { (E).left = 0; (E).right = BUFLEN-1; } while(0)
+
+typedef enum EditStat {CONTINUE, CONFIRM, CANCEL} EditStat;
+typedef enum Color {DEFAULT, RED, GREEN, YELLOW, BLUE, CYAN, MAGENTA, WHITE, BLACK} Color;
+typedef int (*PROCESS)(const char *path);
+
+static void
+init_marks(Marks *marks)
+{
+    strcpy(marks->dirpath, "");
+    marks->bulk = BULK_INIT;
+    marks->nentries = 0;
+    marks->entries = calloc(marks->bulk, sizeof *marks->entries);
+}
+
+/* Unmark all entries. */
+static void
+mark_none(Marks *marks)
+{
+    int i;
+
+    strcpy(marks->dirpath, "");
+    for (i = 0; i < marks->bulk && marks->nentries; i++)
+        if (marks->entries[i]) {
+            free(marks->entries[i]);
+            marks->entries[i] = NULL;
+            marks->nentries--;
+        }
+    if (marks->bulk > BULK_THRESH) {
+        /* Reset bulk to free some memory. */
+        free(marks->entries);
+        marks->bulk = BULK_INIT;
+        marks->entries = calloc(marks->bulk, sizeof *marks->entries);
+    }
+}
+
+static void
+add_mark(Marks *marks, char *dirpath, char *entry)
+{
+    int i;
+
+    if (!strcmp(marks->dirpath, dirpath)) {
+        /* Append mark to directory. */
+        if (marks->nentries == marks->bulk) {
+            /* Expand bulk to accomodate new entry. */
+            int extra = marks->bulk / 2;
+            marks->bulk += extra; /* bulk *= 1.5; */
+            marks->entries = realloc(marks->entries,
+                                     marks->bulk * sizeof *marks->entries);
+            memset(&marks->entries[marks->nentries], 0,
+                   extra * sizeof *marks->entries);
+            i = marks->nentries;
+        } else {
+            /* Search for empty slot (there must be one). */
+            for (i = 0; i < marks->bulk; i++)
+                if (!marks->entries[i])
+                    break;
+        }
+    } else {
+        /* Directory changed. Discard old marks. */
+        mark_none(marks);
+        strcpy(marks->dirpath, dirpath);
+        i = 0;
+    }
+    marks->entries[i] = malloc(strlen(entry) + 1);
+    strcpy(marks->entries[i], entry);
+    marks->nentries++;
+}
+
+static void
+del_mark(Marks *marks, char *entry)
+{
+    int i;
+
+    if (marks->nentries > 1) {
+        for (i = 0; i < marks->bulk; i++)
+            if (marks->entries[i] && !strcmp(marks->entries[i], entry))
+                break;
+        free(marks->entries[i]);
+        marks->entries[i] = NULL;
+        marks->nentries--;
+    } else
+        mark_none(marks);
+}
+
+static void
+free_marks(Marks *marks)
+{
+    int i;
+
+    for (i = 0; i < marks->bulk && marks->nentries; i++)
+        if (marks->entries[i]) {
+            free(marks->entries[i]);
+            marks->nentries--;
+        }
+    free(marks->entries);
+}
+
+static void
+handle_usr1(int sig)
+{
+    rover.pending_usr1 = 1;
+}
+
+static void
+handle_winch(int sig)
+{
+    rover.pending_winch = 1;
+}
+
+static void
+enable_handlers()
+{
+    struct sigaction sa;
+
+    memset(&sa, 0, sizeof (struct sigaction));
+    sa.sa_handler = handle_usr1;
+    sigaction(SIGUSR1, &sa, NULL);
+    sa.sa_handler = handle_winch;
+    sigaction(SIGWINCH, &sa, NULL);
+}
+
+static void
+disable_handlers()
+{
+    struct sigaction sa;
+
+    memset(&sa, 0, sizeof (struct sigaction));
+    sa.sa_handler = SIG_DFL;
+    sigaction(SIGUSR1, &sa, NULL);
+    sigaction(SIGWINCH, &sa, NULL);
+}
+
+static void reload();
+static void update_view();
+
+/* Handle any signals received since last call. */
+static void
+sync_signals()
+{
+    if (rover.pending_usr1) {
+        /* SIGUSR1 received: refresh directory listing. */
+        reload();
+        rover.pending_usr1 = 0;
+    }
+    if (rover.pending_winch) {
+        /* SIGWINCH received: resize application accordingly. */
+        delwin(rover.window);
+        endwin();
+        refresh();
+        clear();
+        rover.window = subwin(stdscr, LINES - 2, COLS, 1, 0);
+        if (HEIGHT < rover.nfiles && SCROLL + HEIGHT > rover.nfiles)
+            SCROLL = ESEL - HEIGHT;
+        update_view();
+        rover.pending_winch = 0;
+    }
+}
+
+/* This function must be used in place of getch().
+   It handles signals while waiting for user input. */
+static int
+rover_getch()
+{
+    int ch;
+
+    while ((ch = getch()) == ERR)
+        sync_signals();
+    return ch;
+}
+
+/* This function must be used in place of get_wch().
+   It handles signals while waiting for user input. */
+static int
+rover_get_wch(wint_t *wch)
+{
+    wint_t ret;
+
+    while ((ret = get_wch(wch)) == (wint_t) ERR)
+        sync_signals();
+    return ret;
+}
+
+/* Get user programs from the environment. */
+
+#define ROVER_ENV(dst, src) if ((dst = getenv("ROVER_" #src)) == NULL) \
+                                dst = getenv(#src);
+
+static void
+get_user_programs()
+{
+    ROVER_ENV(user_shell, SHELL)
+    ROVER_ENV(user_pager, PAGER)
+    ROVER_ENV(user_editor, VISUAL)
+    if (!user_editor)
+        ROVER_ENV(user_editor, EDITOR)
+    ROVER_ENV(user_open, OPEN)
+}
+
+/* Do a fork-exec to external program (e.g. $EDITOR). */
+static void
+spawn(char **args)
+{
+    pid_t pid;
+    int status;
+
+    setenv("RVSEL", rover.nfiles ? ENAME(ESEL) : "", 1);
+    pid = fork();
+    if (pid > 0) {
+        /* fork() succeeded. */
+        disable_handlers();
+        endwin();
+        waitpid(pid, &status, 0);
+        enable_handlers();
+        kill(getpid(), SIGWINCH);
+    } else if (pid == 0) {
+        /* Child process. */
+        execvp(args[0], args);
+    }
+}
+
+static void
+shell_escaped_cat(char *buf, char *str, size_t n)
+{
+    char *p = buf + strlen(buf);
+    *p++ = '\'';
+    for (n--; n; n--, str++) {
+        switch (*str) {
+        case '\'':
+            if (n < 4)
+                goto done;
+            strcpy(p, "'\\''");
+            n -= 4;
+            p += 4;
+            break;
+        case '\0':
+            goto done;
+        default:
+            *p = *str;
+            p++;
+        }
+    }
+done:
+    strncat(p, "'", n);
+}
+
+static int
+open_with_env(char *program, char *path)
+{
+    if (program) {
+#ifdef RV_SHELL
+        strncpy(BUF1, program, BUFLEN - 1);
+        strncat(BUF1, " ", BUFLEN - strlen(program) - 1);
+        shell_escaped_cat(BUF1, path, BUFLEN - strlen(program) - 2);
+        spawn((char *[]) {RV_SHELL, "-c", BUF1, NULL});
+#else
+        spawn((char *[]) {program, path, NULL});
+#endif
+        return 1;
+    }
+    return 0;
+}
+
+/* Curses setup. */
+static void
+init_term()
+{
+    setlocale(LC_ALL, "");
+    initscr();
+    cbreak(); /* Get one character at a time. */
+    timeout(100); /* For getch(). */
+    noecho();
+    nonl(); /* No NL->CR/NL on output. */
+    intrflush(stdscr, FALSE);
+    keypad(stdscr, TRUE);
+    curs_set(FALSE); /* Hide blinking cursor. */
+    if (has_colors()) {
+        short bg;
+        start_color();
+#ifdef NCURSES_EXT_FUNCS
+        use_default_colors();
+        bg = -1;
+#else
+        bg = COLOR_BLACK;
+#endif
+        init_pair(RED, COLOR_RED, bg);
+        init_pair(GREEN, COLOR_GREEN, bg);
+        init_pair(YELLOW, COLOR_YELLOW, bg);
+        init_pair(BLUE, COLOR_BLUE, bg);
+        init_pair(CYAN, COLOR_CYAN, bg);
+        init_pair(MAGENTA, COLOR_MAGENTA, bg);
+        init_pair(WHITE, COLOR_WHITE, bg);
+        init_pair(BLACK, COLOR_BLACK, bg);
+    }
+    atexit((void (*)(void)) endwin);
+    enable_handlers();
+}
+
+/* Update the listing view. */
+static void
+update_view()
+{
+    int i, j;
+    int numsize;
+    int ishidden;
+    int marking;
+
+    mvhline(0, 0, ' ', COLS);
+    attr_on(A_BOLD, NULL);
+    color_set(RVC_TABNUM, NULL);
+    mvaddch(0, COLS - 2, rover.tab + '0');
+    attr_off(A_BOLD, NULL);
+    if (rover.marks.nentries) {
+        numsize = snprintf(BUF1, BUFLEN, "%d", rover.marks.nentries);
+        color_set(RVC_MARKS, NULL);
+        mvaddstr(0, COLS - 3 - numsize, BUF1);
+    } else
+        numsize = -1;
+    color_set(RVC_CWD, NULL);
+    mbstowcs(WBUF, CWD, PATH_MAX);
+    mvaddnwstr(0, 0, WBUF, COLS - 4 - numsize);
+    wcolor_set(rover.window, RVC_BORDER, NULL);
+    wborder(rover.window, 0, 0, 0, 0, 0, 0, 0, 0);
+    ESEL = MAX(MIN(ESEL, rover.nfiles - 1), 0);
+    /* Selection might not be visible, due to cursor wrapping or window
+       shrinking. In that case, the scroll must be moved to make it visible. */
+    if (rover.nfiles > HEIGHT) {
+        SCROLL = MAX(MIN(SCROLL, ESEL), ESEL - HEIGHT + 1);
+        SCROLL = MIN(MAX(SCROLL, 0), rover.nfiles - HEIGHT);
+    } else
+        SCROLL = 0;
+    marking = !strcmp(CWD, rover.marks.dirpath);
+    for (i = 0, j = SCROLL; i < HEIGHT && j < rover.nfiles; i++, j++) {
+        ishidden = ENAME(j)[0] == '.';
+        if (j == ESEL)
+            wattr_on(rover.window, A_REVERSE, NULL);
+        if (ISLINK(j))
+            wcolor_set(rover.window, RVC_LINK, NULL);
+        else if (ishidden)
+            wcolor_set(rover.window, RVC_HIDDEN, NULL);
+        else if (S_ISREG(EMODE(j))) {
+            if (EMODE(j) & (S_IXUSR | S_IXGRP | S_IXOTH))
+                wcolor_set(rover.window, RVC_EXEC, NULL);
+            else
+                wcolor_set(rover.window, RVC_REG, NULL);
+        } else if (S_ISDIR(EMODE(j)))
+            wcolor_set(rover.window, RVC_DIR, NULL);
+        else if (S_ISCHR(EMODE(j)))
+            wcolor_set(rover.window, RVC_CHR, NULL);
+        else if (S_ISBLK(EMODE(j)))
+            wcolor_set(rover.window, RVC_BLK, NULL);
+        else if (S_ISFIFO(EMODE(j)))
+            wcolor_set(rover.window, RVC_FIFO, NULL);
+        else if (S_ISSOCK(EMODE(j)))
+            wcolor_set(rover.window, RVC_SOCK, NULL);
+        if (S_ISDIR(EMODE(j))) {
+            mbstowcs(WBUF, ENAME(j), PATH_MAX);
+            if (ISLINK(j))
+                wcscat(WBUF, L"/");
+        } else {
+            char *suffix, *suffixes = "BKMGTPEZY";
+            off_t human_size = ESIZE(j) * 10;
+            int length = mbstowcs(WBUF, ENAME(j), PATH_MAX);
+            int namecols = wcswidth(WBUF, length);
+            for (suffix = suffixes; human_size >= 10240; suffix++)
+                human_size = (human_size + 512) / 1024;
+            if (*suffix == 'B')
+                swprintf(WBUF + length, PATH_MAX - length, L"%*d %c",
+                         (int) (COLS - namecols - 6),
+                         (int) human_size / 10, *suffix);
+            else
+                swprintf(WBUF + length, PATH_MAX - length, L"%*d.%d %c",
+                         (int) (COLS - namecols - 8),
+                         (int) human_size / 10, (int) human_size % 10, *suffix);
+        }
+        mvwhline(rover.window, i + 1, 1, ' ', COLS - 2);
+        mvwaddnwstr(rover.window, i + 1, 2, WBUF, COLS - 4);
+        if (marking && MARKED(j)) {
+            wcolor_set(rover.window, RVC_MARKS, NULL);
+            mvwaddch(rover.window, i + 1, 1, RVS_MARK);
+        } else
+            mvwaddch(rover.window, i + 1, 1, ' ');
+        if (j == ESEL)
+            wattr_off(rover.window, A_REVERSE, NULL);
+    }
+    for (; i < HEIGHT; i++)
+        mvwhline(rover.window, i + 1, 1, ' ', COLS - 2);
+    if (rover.nfiles > HEIGHT) {
+        int center, height;
+        center = (SCROLL + HEIGHT / 2) * HEIGHT / rover.nfiles;
+        height = (HEIGHT-1) * HEIGHT / rover.nfiles;
+        if (!height) height = 1;
+        wcolor_set(rover.window, RVC_SCROLLBAR, NULL);
+        mvwvline(rover.window, center-height/2+1, COLS-1, RVS_SCROLLBAR, height);
+    }
+    BUF1[0] = FLAGS & SHOW_FILES  ? 'F' : ' ';
+    BUF1[1] = FLAGS & SHOW_DIRS   ? 'D' : ' ';
+    BUF1[2] = FLAGS & SHOW_HIDDEN ? 'H' : ' ';
+    if (!rover.nfiles)
+        strcpy(BUF2, "0/0");
+    else
+        snprintf(BUF2, BUFLEN, "%d/%d", ESEL + 1, rover.nfiles);
+    snprintf(BUF1+3, BUFLEN-3, "%12s", BUF2);
+    color_set(RVC_STATUS, NULL);
+    mvaddstr(LINES - 1, STATUSPOS, BUF1);
+    wrefresh(rover.window);
+}
+
+/* Show a message on the status bar. */
+static void
+message(Color color, char *fmt, ...)
+{
+    int len, pos;
+    va_list args;
+
+    va_start(args, fmt);
+    vsnprintf(BUF1, MIN(BUFLEN, STATUSPOS), fmt, args);
+    va_end(args);
+    len = strlen(BUF1);
+    pos = (STATUSPOS - len) / 2;
+    attr_on(A_BOLD, NULL);
+    color_set(color, NULL);
+    mvaddstr(LINES - 1, pos, BUF1);
+    color_set(DEFAULT, NULL);
+    attr_off(A_BOLD, NULL);
+}
+
+/* Clear message area, leaving only status info. */
+static void
+clear_message()
+{
+    mvhline(LINES - 1, 0, ' ', STATUSPOS);
+}
+
+/* Comparison used to sort listing entries. */
+static int
+rowcmp(const void *a, const void *b)
+{
+    int isdir1, isdir2, cmpdir;
+    const Row *r1 = a;
+    const Row *r2 = b;
+    isdir1 = S_ISDIR(r1->mode);
+    isdir2 = S_ISDIR(r2->mode);
+    cmpdir = isdir2 - isdir1;
+    return cmpdir ? cmpdir : strcoll(r1->name, r2->name);
+}
+
+/* Get all entries in current working directory. */
+static int
+ls(Row **rowsp, uint8_t flags)
+{
+    DIR *dp;
+    struct dirent *ep;
+    struct stat statbuf;
+    Row *rows;
+    int i, n;
+
+    if(!(dp = opendir("."))) return -1;
+    n = -2; /* We don't want the entries "." and "..". */
+    while (readdir(dp)) n++;
+    if (n == 0) {
+        closedir(dp);
+        return 0;
+    }
+    rewinddir(dp);
+    rows = malloc(n * sizeof *rows);
+    i = 0;
+    while ((ep = readdir(dp))) {
+        if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, ".."))
+            continue;
+        if (!(flags & SHOW_HIDDEN) && ep->d_name[0] == '.')
+            continue;
+        lstat(ep->d_name, &statbuf);
+        rows[i].islink = S_ISLNK(statbuf.st_mode);
+        stat(ep->d_name, &statbuf);
+        if (S_ISDIR(statbuf.st_mode)) {
+            if (flags & SHOW_DIRS) {
+                rows[i].name = malloc(strlen(ep->d_name) + 2);
+                strcpy(rows[i].name, ep->d_name);
+                if (!rows[i].islink)
+                    strcat(rows[i].name, "/");
+                rows[i].mode = statbuf.st_mode;
+                i++;
+            }
+        } else if (flags & SHOW_FILES) {
+            rows[i].name = malloc(strlen(ep->d_name) + 1);
+            strcpy(rows[i].name, ep->d_name);
+            rows[i].size = statbuf.st_size;
+            rows[i].mode = statbuf.st_mode;
+            i++;
+        }
+    }
+    n = i; /* Ignore unused space in array caused by filters. */
+    qsort(rows, n, sizeof (*rows), rowcmp);
+    closedir(dp);
+    *rowsp = rows;
+    return n;
+}
+
+static void
+free_rows(Row **rowsp, int nfiles)
+{
+    int i;
+
+    for (i = 0; i < nfiles; i++)
+        free((*rowsp)[i].name);
+    free(*rowsp);
+    *rowsp = NULL;
+}
+
+/* Change working directory to the path in CWD. */
+static void
+cd(int reset)
+{
+    int i, j;
+
+    message(CYAN, "Loading \"%s\"...", CWD);
+    refresh();
+    if (chdir(CWD) == -1) {
+        getcwd(CWD, PATH_MAX-1);
+        if (CWD[strlen(CWD)-1] != '/')
+            strcat(CWD, "/");
+        goto done;
+    }
+    if (reset) ESEL = SCROLL = 0;
+    if (rover.nfiles)
+        free_rows(&rover.rows, rover.nfiles);
+    rover.nfiles = ls(&rover.rows, FLAGS);
+    if (!strcmp(CWD, rover.marks.dirpath)) {
+        for (i = 0; i < rover.nfiles; i++) {
+            for (j = 0; j < rover.marks.bulk; j++)
+                if (
+                    rover.marks.entries[j] &&
+                    !strcmp(rover.marks.entries[j], ENAME(i))
+                )
+                    break;
+            MARKED(i) = j < rover.marks.bulk;
+        }
+    } else
+        for (i = 0; i < rover.nfiles; i++)
+            MARKED(i) = 0;
+done:
+    clear_message();
+    update_view();
+}
+
+/* Select a target entry, if it is present. */
+static void
+try_to_sel(const char *target)
+{
+    ESEL = 0;
+    if (!ISDIR(target))
+        while ((ESEL+1) < rover.nfiles && S_ISDIR(EMODE(ESEL)))
+            ESEL++;
+    while ((ESEL+1) < rover.nfiles && strcoll(ENAME(ESEL), target) < 0)
+        ESEL++;
+}
+
+/* Reload CWD, but try to keep selection. */
+static void
+reload()
+{
+    if (rover.nfiles) {
+        strcpy(INPUT, ENAME(ESEL));
+        cd(0);
+        try_to_sel(INPUT);
+        update_view();
+    } else
+        cd(1);
+}
+
+static off_t
+count_dir(const char *path)
+{
+    DIR *dp;
+    struct dirent *ep;
+    struct stat statbuf;
+    char subpath[PATH_MAX];
+    off_t total;
+
+    if(!(dp = opendir(path))) return 0;
+    total = 0;
+    while ((ep = readdir(dp))) {
+        if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, ".."))
+            continue;
+        snprintf(subpath, PATH_MAX, "%s%s", path, ep->d_name);
+        lstat(subpath, &statbuf);
+        if (S_ISDIR(statbuf.st_mode)) {
+            strcat(subpath, "/");
+            total += count_dir(subpath);
+        } else
+            total += statbuf.st_size;
+    }
+    closedir(dp);
+    return total;
+}
+
+static off_t
+count_marked()
+{
+    int i;
+    char *entry;
+    off_t total;
+    struct stat statbuf;
+
+    total = 0;
+    chdir(rover.marks.dirpath);
+    for (i = 0; i < rover.marks.bulk; i++) {
+        entry = rover.marks.entries[i];
+        if (entry) {
+            if (ISDIR(entry)) {
+                total += count_dir(entry);
+            } else {
+                lstat(entry, &statbuf);
+                total += statbuf.st_size;
+            }
+        }
+    }
+    chdir(CWD);
+    return total;
+}
+
+/* Recursively process a source directory using CWD as destination root.
+   For each node (i.e. directory), do the following:
+    1. call pre(destination);
+    2. call proc() on every child leaf (i.e. files);
+    3. recurse into every child node;
+    4. call pos(source).
+   E.g. to move directory /src/ (and all its contents) inside /dst/:
+    strcpy(CWD, "/dst/");
+    process_dir(adddir, movfile, deldir, "/src/"); */
+static int
+process_dir(PROCESS pre, PROCESS proc, PROCESS pos, const char *path)
+{
+    int ret;
+    DIR *dp;
+    struct dirent *ep;
+    struct stat statbuf;
+    char subpath[PATH_MAX];
+
+    ret = 0;
+    if (pre) {
+        char dstpath[PATH_MAX];
+        strcpy(dstpath, CWD);
+        strcat(dstpath, path + strlen(rover.marks.dirpath));
+        ret |= pre(dstpath);
+    }
+    if(!(dp = opendir(path))) return -1;
+    while ((ep = readdir(dp))) {
+        if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, ".."))
+            continue;
+        snprintf(subpath, PATH_MAX, "%s%s", path, ep->d_name);
+        lstat(subpath, &statbuf);
+        if (S_ISDIR(statbuf.st_mode)) {
+            strcat(subpath, "/");
+            ret |= process_dir(pre, proc, pos, subpath);
+        } else
+            ret |= proc(subpath);
+    }
+    closedir(dp);
+    if (pos) ret |= pos(path);
+    return ret;
+}
+
+/* Process all marked entries using CWD as destination root.
+   All marked entries that are directories will be recursively processed.
+   See process_dir() for details on the parameters. */
+static void
+process_marked(PROCESS pre, PROCESS proc, PROCESS pos,
+               const char *msg_doing, const char *msg_done)
+{
+    int i, ret;
+    char *entry;
+    char path[PATH_MAX];
+
+    clear_message();
+    message(CYAN, "%s...", msg_doing);
+    refresh();
+    rover.prog = (Prog) {0, count_marked(), msg_doing};
+    for (i = 0; i < rover.marks.bulk; i++) {
+        entry = rover.marks.entries[i];
+        if (entry) {
+            ret = 0;
+            snprintf(path, PATH_MAX, "%s%s", rover.marks.dirpath, entry);
+            if (ISDIR(entry)) {
+                if (!strncmp(path, CWD, strlen(path)))
+                    ret = -1;
+                else
+                    ret = process_dir(pre, proc, pos, path);
+            } else
+                ret = proc(path);
+            if (!ret) {
+                del_mark(&rover.marks, entry);
+                reload();
+            }
+        }
+    }
+    rover.prog.total = 0;
+    reload();
+    if (!rover.marks.nentries)
+        message(GREEN, "%s all marked entries.", msg_done);
+    else
+        message(RED, "Some errors occured while %s.", msg_doing);
+    RV_ALERT();
+}
+
+static void
+update_progress(off_t delta)
+{
+    int percent;
+
+    if (!rover.prog.total) return;
+    rover.prog.partial += delta;
+    percent = (int) (rover.prog.partial * 100 / rover.prog.total);
+    message(CYAN, "%s...%d%%", rover.prog.msg, percent);
+    refresh();
+}
+
+/* Wrappers for file operations. */
+static int delfile(const char *path) {
+    int ret;
+    struct stat st;
+
+    ret = lstat(path, &st);
+    if (ret < 0) return ret;
+    update_progress(st.st_size);
+    return unlink(path);
+}
+static PROCESS deldir = rmdir;
+static int addfile(const char *path) {
+    /* Using creat(2) because mknod(2) doesn't seem to be portable. */
+    int ret;
+
+    ret = creat(path, 0644);
+    if (ret < 0) return ret;
+    return close(ret);
+}
+static int cpyfile(const char *srcpath) {
+    int src, dst, ret;
+    size_t size;
+    struct stat st;
+    char buf[BUFSIZ];
+    char dstpath[PATH_MAX];
+
+    strcpy(dstpath, CWD);
+    strcat(dstpath, srcpath + strlen(rover.marks.dirpath));
+    ret = lstat(srcpath, &st);
+    if (ret < 0) return ret;
+    if (S_ISLNK(st.st_mode)) {
+        ret = readlink(srcpath, BUF1, BUFLEN-1);
+        if (ret < 0) return ret;
+        BUF1[ret] = '\0';
+        ret = symlink(BUF1, dstpath);
+    } else {
+        ret = src = open(srcpath, O_RDONLY);
+        if (ret < 0) return ret;
+        ret = dst = creat(dstpath, st.st_mode);
+        if (ret < 0) return ret;
+        while ((size = read(src, buf, BUFSIZ)) > 0) {
+            write(dst, buf, size);
+            update_progress(size);
+            sync_signals();
+        }
+        close(src);
+        close(dst);
+        ret = 0;
+    }
+    return ret;
+}
+static int adddir(const char *path) {
+    int ret;
+    struct stat st;
+
+    ret = stat(CWD, &st);
+    if (ret < 0) return ret;
+    return mkdir(path, st.st_mode);
+}
+static int movfile(const char *srcpath) {
+    int ret;
+    struct stat st;
+    char dstpath[PATH_MAX];
+
+    strcpy(dstpath, CWD);
+    strcat(dstpath, srcpath + strlen(rover.marks.dirpath));
+    ret = rename(srcpath, dstpath);
+    if (ret == 0) {
+        ret = lstat(dstpath, &st);
+        if (ret < 0) return ret;
+        update_progress(st.st_size);
+    } else if (errno == EXDEV) {
+        ret = cpyfile(srcpath);
+        if (ret < 0) return ret;
+        ret = unlink(srcpath);
+    }
+    return ret;
+}
+
+static void
+start_line_edit(const char *init_input)
+{
+    curs_set(TRUE);
+    strncpy(INPUT, init_input, BUFLEN);
+    rover.edit.left = mbstowcs(rover.edit.buffer, init_input, BUFLEN);
+    rover.edit.right = BUFLEN - 1;
+    rover.edit.buffer[BUFLEN] = L'\0';
+    rover.edit_scroll = 0;
+}
+
+/* Read input and change editing state accordingly. */
+static EditStat
+get_line_edit()
+{
+    wchar_t eraser, killer, wch;
+    int ret, length;
+
+    ret = rover_get_wch((wint_t *) &wch);
+    erasewchar(&eraser);
+    killwchar(&killer);
+    if (ret == KEY_CODE_YES) {
+        if (wch == KEY_ENTER) {
+            curs_set(FALSE);
+            return CONFIRM;
+        } else if (wch == KEY_LEFT) {
+            if (EDIT_CAN_LEFT(rover.edit)) EDIT_LEFT(rover.edit);
+        } else if (wch == KEY_RIGHT) {
+            if (EDIT_CAN_RIGHT(rover.edit)) EDIT_RIGHT(rover.edit);
+        } else if (wch == KEY_UP) {
+            while (EDIT_CAN_LEFT(rover.edit)) EDIT_LEFT(rover.edit);
+        } else if (wch == KEY_DOWN) {
+            while (EDIT_CAN_RIGHT(rover.edit)) EDIT_RIGHT(rover.edit);
+        } else if (wch == KEY_BACKSPACE) {
+            if (EDIT_CAN_LEFT(rover.edit)) EDIT_BACKSPACE(rover.edit);
+        } else if (wch == KEY_DC) {
+            if (EDIT_CAN_RIGHT(rover.edit)) EDIT_DELETE(rover.edit);
+        }
+    } else {
+        if (wch == L'\r' || wch == L'\n') {
+            curs_set(FALSE);
+            return CONFIRM;
+        } else if (wch == L'\t') {
+            curs_set(FALSE);
+            return CANCEL;
+        } else if (wch == eraser) {
+            if (EDIT_CAN_LEFT(rover.edit)) EDIT_BACKSPACE(rover.edit);
+        } else if (wch == killer) {
+            EDIT_CLEAR(rover.edit);
+            clear_message();
+        } else if (iswprint(wch)) {
+            if (!EDIT_FULL(rover.edit)) EDIT_INSERT(rover.edit, wch);
+        }
+    }
+    /* Encode edit contents in INPUT. */
+    rover.edit.buffer[rover.edit.left] = L'\0';
+    length = wcstombs(INPUT, rover.edit.buffer, BUFLEN);
+    wcstombs(&INPUT[length], &rover.edit.buffer[rover.edit.right+1],
+             BUFLEN-length);
+    return CONTINUE;
+}
+
+/* Update line input on the screen. */
+static void
+update_input(const char *prompt, Color color)
+{
+    int plen, ilen, maxlen;
+
+    plen = strlen(prompt);
+    ilen = mbstowcs(NULL, INPUT, 0);
+    maxlen = STATUSPOS - plen - 2;
+    if (ilen - rover.edit_scroll < maxlen)
+        rover.edit_scroll = MAX(ilen - maxlen, 0);
+    else if (rover.edit.left > rover.edit_scroll + maxlen - 1)
+        rover.edit_scroll = rover.edit.left - maxlen;
+    else if (rover.edit.left < rover.edit_scroll)
+        rover.edit_scroll = MAX(rover.edit.left - maxlen, 0);
+    color_set(RVC_PROMPT, NULL);
+    mvaddstr(LINES - 1, 0, prompt);
+    color_set(color, NULL);
+    mbstowcs(WBUF, INPUT, COLS);
+    mvaddnwstr(LINES - 1, plen, &WBUF[rover.edit_scroll], maxlen);
+    mvaddch(LINES - 1, plen + MIN(ilen - rover.edit_scroll, maxlen + 1), ' ');
+    color_set(DEFAULT, NULL);
+    if (rover.edit_scroll)
+        mvaddch(LINES - 1, plen - 1, '<');
+    if (ilen > rover.edit_scroll + maxlen)
+        mvaddch(LINES - 1, plen + maxlen, '>');
+    move(LINES - 1, plen + rover.edit.left - rover.edit_scroll);
+}
+
+int
+main(int argc, char *argv[])
+{
+    int i, ch;
+    char *program;
+    char *entry;
+    const char *key;
+    const char *clip_path;
+    DIR *d;
+    EditStat edit_stat;
+    FILE *save_cwd_file = NULL;
+    FILE *save_marks_file = NULL;
+    FILE *clip_file;
+
+    if (argc >= 2) {
+        if (!strcmp(argv[1], "-v") || !strcmp(argv[1], "--version")) {
+            printf("rover %s\n", RV_VERSION);
+            return 0;
+        } else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
+            printf(
+                "Usage: rover [OPTIONS] [DIR [DIR [...]]]\n"
+                "       Browse current directory or the ones specified.\n\n"
+                "  or:  rover -h|--help\n"
+                "       Print this help message and exit.\n\n"
+                "  or:  rover -v|--version\n"
+                "       Print program version and exit.\n\n"
+                "See rover(1) for more information.\n"
+                "Rover homepage: <https://github.com/lecram/rover>.\n"
+            );
+            return 0;
+        } else if (!strcmp(argv[1], "-d") || !strcmp(argv[1], "--save-cwd")) {
+            if (argc > 2) {
+                save_cwd_file = fopen(argv[2], "w");
+                argc -= 2; argv += 2;
+            } else {
+                fprintf(stderr, "error: missing argument to %s\n", argv[1]);
+                return 1;
+            }
+        } else if (!strcmp(argv[1], "-m") || !strcmp(argv[1], "--save-marks")) {
+            if (argc > 2) {
+                save_marks_file = fopen(argv[2], "a");
+                argc -= 2; argv += 2;
+            } else {
+                fprintf(stderr, "error: missing argument to %s\n", argv[1]);
+                return 1;
+            }
+        }
+    }
+    get_user_programs();
+    init_term();
+    rover.nfiles = 0;
+    for (i = 0; i < 10; i++) {
+        rover.tabs[i].esel = rover.tabs[i].scroll = 0;
+        rover.tabs[i].flags = RV_FLAGS;
+    }
+    strcpy(rover.tabs[0].cwd, getenv("HOME"));
+    for (i = 1; i < argc && i < 10; i++) {
+        if ((d = opendir(argv[i]))) {
+            realpath(argv[i], rover.tabs[i].cwd);
+            closedir(d);
+        } else
+            strcpy(rover.tabs[i].cwd, rover.tabs[0].cwd);
+    }
+    getcwd(rover.tabs[i].cwd, PATH_MAX);
+    for (i++; i < 10; i++)
+        strcpy(rover.tabs[i].cwd, rover.tabs[i-1].cwd);
+    for (i = 0; i < 10; i++)
+        if (rover.tabs[i].cwd[strlen(rover.tabs[i].cwd) - 1] != '/')
+            strcat(rover.tabs[i].cwd, "/");
+    rover.tab = 1;
+    rover.window = subwin(stdscr, LINES - 2, COLS, 1, 0);
+    init_marks(&rover.marks);
+    cd(1);
+    strcpy(CLIPBOARD, CWD);
+    if (rover.nfiles > 0)
+        strcat(CLIPBOARD, ENAME(ESEL));
+    while (1) {
+        ch = rover_getch();
+        key = keyname(ch);
+        clear_message();
+        if (!strcmp(key, RVK_QUIT)) break;
+        else if (ch >= '0' && ch <= '9') {
+            rover.tab = ch - '0';
+            cd(0);
+        } else if (!strcmp(key, RVK_HELP)) {
+            spawn((char *[]) {"man", "rover", NULL});
+        } else if (!strcmp(key, RVK_DOWN)) {
+            if (!rover.nfiles) continue;
+            ESEL = MIN(ESEL + 1, rover.nfiles - 1);
+            update_view();
+        } else if (!strcmp(key, RVK_UP)) {
+            if (!rover.nfiles) continue;
+            ESEL = MAX(ESEL - 1, 0);
+            update_view();
+        } else if (!strcmp(key, RVK_JUMP_DOWN)) {
+            if (!rover.nfiles) continue;
+            ESEL = MIN(ESEL + RV_JUMP, rover.nfiles - 1);
+            if (rover.nfiles > HEIGHT)
+                SCROLL = MIN(SCROLL + RV_JUMP, rover.nfiles - HEIGHT);
+            update_view();
+        } else if (!strcmp(key, RVK_JUMP_UP)) {
+            if (!rover.nfiles) continue;
+            ESEL = MAX(ESEL - RV_JUMP, 0);
+            SCROLL = MAX(SCROLL - RV_JUMP, 0);
+            update_view();
+        } else if (!strcmp(key, RVK_JUMP_TOP)) {
+            if (!rover.nfiles) continue;
+            ESEL = 0;
+            update_view();
+        } else if (!strcmp(key, RVK_JUMP_BOTTOM)) {
+            if (!rover.nfiles) continue;
+            ESEL = rover.nfiles - 1;
+            update_view();
+        } else if (!strcmp(key, RVK_CD_DOWN)) {
+            if (!rover.nfiles || !S_ISDIR(EMODE(ESEL))) continue;
+            if (chdir(ENAME(ESEL)) == -1) {
+                message(RED, "Cannot access \"%s\".", ENAME(ESEL));
+                continue;
+            }
+            strcat(CWD, ENAME(ESEL));
+            cd(1);
+        } else if (!strcmp(key, RVK_CD_UP)) {
+            char *dirname, first;
+            if (!strcmp(CWD, "/")) continue;
+            CWD[strlen(CWD) - 1] = '\0';
+            dirname = strrchr(CWD, '/') + 1;
+            first = dirname[0];
+            dirname[0] = '\0';
+            cd(1);
+            dirname[0] = first;
+            dirname[strlen(dirname)] = '/';
+            try_to_sel(dirname);
+            dirname[0] = '\0';
+            if (rover.nfiles > HEIGHT)
+                SCROLL = ESEL - HEIGHT / 2;
+            update_view();
+        } else if (!strcmp(key, RVK_HOME)) {
+            strcpy(CWD, getenv("HOME"));
+            if (CWD[strlen(CWD) - 1] != '/')
+                strcat(CWD, "/");
+            cd(1);
+        } else if (!strcmp(key, RVK_TARGET)) {
+            char *bname, first;
+            int is_dir = S_ISDIR(EMODE(ESEL));
+            ssize_t len = readlink(ENAME(ESEL), BUF1, BUFLEN-1);
+            if (len == -1) continue;
+            BUF1[len] = '\0';
+            if (access(BUF1, F_OK) == -1) {
+                char *msg;
+                switch (errno) {
+                case EACCES:
+                    msg = "Cannot access \"%s\".";
+                    break;
+                case ENOENT:
+                    msg = "\"%s\" does not exist.";
+                    break;
+                default:
+                    msg = "Cannot navigate to \"%s\".";
+                }
+                strcpy(BUF2, BUF1); /* message() uses BUF1. */
+                message(RED, msg, BUF2);
+                continue;
+            }
+            realpath(BUF1, CWD);
+            len = strlen(CWD);
+            if (CWD[len - 1] == '/')
+                CWD[len - 1] = '\0';
+            bname = strrchr(CWD, '/') + 1;
+            first = *bname;
+            *bname = '\0';
+            cd(1);
+            *bname = first;
+            if (is_dir)
+                strcat(CWD, "/");
+            try_to_sel(bname);
+            *bname = '\0';
+            update_view();
+        } else if (!strcmp(key, RVK_COPY_PATH)) {
+            clip_path = getenv("CLIP");
+            if (!clip_path) goto copy_path_fail;
+            clip_file = fopen(clip_path, "w");
+            if (!clip_file) goto copy_path_fail;
+            fprintf(clip_file, "%s%s\n", CWD, ENAME(ESEL));
+            fclose(clip_file);
+            goto copy_path_done;
+copy_path_fail:
+            strcpy(CLIPBOARD, CWD);
+            strcat(CLIPBOARD, ENAME(ESEL));
+copy_path_done:
+            ;
+        } else if (!strcmp(key, RVK_PASTE_PATH)) {
+            clip_path = getenv("CLIP");
+            if (!clip_path) goto paste_path_fail;
+            clip_file = fopen(clip_path, "r");
+            if (!clip_file) goto paste_path_fail;
+            fscanf(clip_file, "%s\n", CLIPBOARD);
+            fclose(clip_file);
+paste_path_fail:
+            strcpy(BUF1, CLIPBOARD);
+            strcpy(CWD, dirname(BUF1));
+            if (strcmp(CWD, "/"))
+                strcat(CWD, "/");
+            cd(1);
+            strcpy(BUF1, CLIPBOARD);
+            try_to_sel(strstr(CLIPBOARD, basename(BUF1)));
+            update_view();
+        } else if (!strcmp(key, RVK_REFRESH)) {
+            reload();
+        } else if (!strcmp(key, RVK_SHELL)) {
+            program = user_shell;
+            if (program) {
+#ifdef RV_SHELL
+                spawn((char *[]) {RV_SHELL, "-c", program, NULL});
+#else
+                spawn((char *[]) {program, NULL});
+#endif
+                reload();
+            }
+        } else if (!strcmp(key, RVK_VIEW)) {
+            if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) continue;
+            if (open_with_env(user_pager, ENAME(ESEL)))
+                cd(0);
+        } else if (!strcmp(key, RVK_EDIT)) {
+            if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) continue;
+            if (open_with_env(user_editor, ENAME(ESEL)))
+                cd(0);
+        } else if (!strcmp(key, RVK_OPEN)) {
+            if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) continue;
+            if (open_with_env(user_open, ENAME(ESEL)))
+                cd(0);
+        } else if (!strcmp(key, RVK_SEARCH)) {
+            int oldsel, oldscroll, length;
+            if (!rover.nfiles) continue;
+            oldsel = ESEL;
+            oldscroll = SCROLL;
+            start_line_edit("");
+            update_input(RVP_SEARCH, RED);
+            while ((edit_stat = get_line_edit()) == CONTINUE) {
+                int sel;
+                Color color = RED;
+                length = strlen(INPUT);
+                if (length) {
+                    for (sel = 0; sel < rover.nfiles; sel++)
+                        if (!strncmp(ENAME(sel), INPUT, length))
+                            break;
+                    if (sel < rover.nfiles) {
+                        color = GREEN;
+                        ESEL = sel;
+                        if (rover.nfiles > HEIGHT) {
+                            if (sel < 3)
+                                SCROLL = 0;
+                            else if (sel - 3 > rover.nfiles - HEIGHT)
+                                SCROLL = rover.nfiles - HEIGHT;
+                            else
+                                SCROLL = sel - 3;
+                        }
+                    }
+                } else {
+                    ESEL = oldsel;
+                    SCROLL = oldscroll;
+                }
+                update_view();
+                update_input(RVP_SEARCH, color);
+            }
+            if (edit_stat == CANCEL) {
+                ESEL = oldsel;
+                SCROLL = oldscroll;
+            }
+            clear_message();
+            update_view();
+        } else if (!strcmp(key, RVK_TG_FILES)) {
+            FLAGS ^= SHOW_FILES;
+            reload();
+        } else if (!strcmp(key, RVK_TG_DIRS)) {
+            FLAGS ^= SHOW_DIRS;
+            reload();
+        } else if (!strcmp(key, RVK_TG_HIDDEN)) {
+            FLAGS ^= SHOW_HIDDEN;
+            reload();
+        } else if (!strcmp(key, RVK_NEW_FILE)) {
+            int ok = 0;
+            start_line_edit("");
+            update_input(RVP_NEW_FILE, RED);
+            while ((edit_stat = get_line_edit()) == CONTINUE) {
+                int length = strlen(INPUT);
+                ok = length;
+                for (i = 0; i < rover.nfiles; i++) {
+                    if (
+                        !strncmp(ENAME(i), INPUT, length) &&
+                        (!strcmp(ENAME(i) + length, "") ||
+                         !strcmp(ENAME(i) + length, "/"))
+                    ) {
+                        ok = 0;
+                        break;
+                    }
+                }
+                update_input(RVP_NEW_FILE, ok ? GREEN : RED);
+            }
+            clear_message();
+            if (edit_stat == CONFIRM) {
+                if (ok) {
+                    if (addfile(INPUT) == 0) {
+                        cd(1);
+                        try_to_sel(INPUT);
+                        update_view();
+                    } else
+                        message(RED, "Could not create \"%s\".", INPUT);
+                } else
+                    message(RED, "\"%s\" already exists.", INPUT);
+            }
+        } else if (!strcmp(key, RVK_NEW_DIR)) {
+            int ok = 0;
+            start_line_edit("");
+            update_input(RVP_NEW_DIR, RED);
+            while ((edit_stat = get_line_edit()) == CONTINUE) {
+                int length = strlen(INPUT);
+                ok = length;
+                for (i = 0; i < rover.nfiles; i++) {
+                    if (
+                        !strncmp(ENAME(i), INPUT, length) &&
+                        (!strcmp(ENAME(i) + length, "") ||
+                         !strcmp(ENAME(i) + length, "/"))
+                    ) {
+                        ok = 0;
+                        break;
+                    }
+                }
+                update_input(RVP_NEW_DIR, ok ? GREEN : RED);
+            }
+            clear_message();
+            if (edit_stat == CONFIRM) {
+                if (ok) {
+                    if (adddir(INPUT) == 0) {
+                        cd(1);
+                        strcat(INPUT, "/");
+                        try_to_sel(INPUT);
+                        update_view();
+                    } else
+                        message(RED, "Could not create \"%s/\".", INPUT);
+                } else
+                    message(RED, "\"%s\" already exists.", INPUT);
+            }
+        } else if (!strcmp(key, RVK_RENAME)) {
+            int ok = 0;
+            char *last;
+            int isdir;
+            strcpy(INPUT, ENAME(ESEL));
+            last = INPUT + strlen(INPUT) - 1;
+            if ((isdir = *last == '/'))
+                *last = '\0';
+            start_line_edit(INPUT);
+            update_input(RVP_RENAME, RED);
+            while ((edit_stat = get_line_edit()) == CONTINUE) {
+                int length = strlen(INPUT);
+                ok = length;
+                for (i = 0; i < rover.nfiles; i++)
+                    if (
+                        !strncmp(ENAME(i), INPUT, length) &&
+                        (!strcmp(ENAME(i) + length, "") ||
+                         !strcmp(ENAME(i) + length, "/"))
+                    ) {
+                        ok = 0;
+                        break;
+                    }
+                update_input(RVP_RENAME, ok ? GREEN : RED);
+            }
+            clear_message();
+            if (edit_stat == CONFIRM) {
+                if (isdir)
+                    strcat(INPUT, "/");
+                if (ok) {
+                    if (!rename(ENAME(ESEL), INPUT) && MARKED(ESEL)) {
+                        del_mark(&rover.marks, ENAME(ESEL));
+                        add_mark(&rover.marks, CWD, INPUT);
+                    }
+                    cd(1);
+                    try_to_sel(INPUT);
+                    update_view();
+                } else
+                    message(RED, "\"%s\" already exists.", INPUT);
+            }
+        } else if (!strcmp(key, RVK_TG_EXEC)) {
+            if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) continue;
+            if (S_IXUSR & EMODE(ESEL))
+                EMODE(ESEL) &= ~(S_IXUSR | S_IXGRP | S_IXOTH);
+            else
+                EMODE(ESEL) |=   S_IXUSR | S_IXGRP | S_IXOTH ;
+            if (chmod(ENAME(ESEL), EMODE(ESEL))) {
+                message(RED, "Failed to change mode of \"%s\".", ENAME(ESEL));
+            } else {
+                message(GREEN, "Changed mode of \"%s\".", ENAME(ESEL));
+                update_view();
+            }
+        } else if (!strcmp(key, RVK_DELETE)) {
+            if (rover.nfiles) {
+                message(YELLOW, "Delete \"%s\"? (Y/n)", ENAME(ESEL));
+                if (rover_getch() == 'Y') {
+                    const char *name = ENAME(ESEL);
+                    int ret = ISDIR(ENAME(ESEL)) ? deldir(name) : delfile(name);
+                    reload();
+                    if (ret)
+                        message(RED, "Could not delete \"%s\".", ENAME(ESEL));
+                } else
+                    clear_message();
+            } else
+                  message(RED, "No entry selected for deletion.");
+        } else if (!strcmp(key, RVK_TG_MARK)) {
+            if (MARKED(ESEL))
+                del_mark(&rover.marks, ENAME(ESEL));
+            else
+                add_mark(&rover.marks, CWD, ENAME(ESEL));
+            MARKED(ESEL) = !MARKED(ESEL);
+            ESEL = (ESEL + 1) % rover.nfiles;
+            update_view();
+        } else if (!strcmp(key, RVK_INVMARK)) {
+            for (i = 0; i < rover.nfiles; i++) {
+                if (MARKED(i))
+                    del_mark(&rover.marks, ENAME(i));
+                else
+                    add_mark(&rover.marks, CWD, ENAME(i));
+                MARKED(i) = !MARKED(i);
+            }
+            update_view();
+        } else if (!strcmp(key, RVK_MARKALL)) {
+            for (i = 0; i < rover.nfiles; i++)
+                if (!MARKED(i)) {
+                    add_mark(&rover.marks, CWD, ENAME(i));
+                    MARKED(i) = 1;
+                }
+            update_view();
+        } else if (!strcmp(key, RVK_MARK_DELETE)) {
+            if (rover.marks.nentries) {
+                message(YELLOW, "Delete all marked entries? (Y/n)");
+                if (rover_getch() == 'Y')
+                    process_marked(NULL, delfile, deldir, "Deleting", "Deleted");
+                else
+                    clear_message();
+            } else
+                message(RED, "No entries marked for deletion.");
+        } else if (!strcmp(key, RVK_MARK_COPY)) {
+            if (rover.marks.nentries) {
+                if (strcmp(CWD, rover.marks.dirpath))
+                    process_marked(adddir, cpyfile, NULL, "Copying", "Copied");
+                else
+                    message(RED, "Cannot copy to the same path.");
+            } else
+                message(RED, "No entries marked for copying.");
+        } else if (!strcmp(key, RVK_MARK_MOVE)) {
+            if (rover.marks.nentries) {
+                if (strcmp(CWD, rover.marks.dirpath))
+                    process_marked(adddir, movfile, deldir, "Moving", "Moved");
+                else
+                    message(RED, "Cannot move to the same path.");
+            } else
+                message(RED, "No entries marked for moving.");
+        }
+    }
+    if (rover.nfiles)
+        free_rows(&rover.rows, rover.nfiles);
+    delwin(rover.window);
+    if (save_cwd_file != NULL) {
+        fputs(CWD, save_cwd_file);
+        fclose(save_cwd_file);
+    }
+    if (save_marks_file != NULL) {
+        for (i = 0; i < rover.marks.bulk; i++) {
+            entry = rover.marks.entries[i];
+            if (entry)
+                fprintf(save_marks_file, "%s%s\n", rover.marks.dirpath, entry);
+        }
+        fclose(save_marks_file);
+    }
+    free_marks(&rover.marks);
+    return 0;
+}