* refactor selection handling to buf.c to prepare for multiple selections.
* right click to fetch file or line
* Make Fn keys execute nth command in the tags buffers
-* check for file changes on save
* check for file changes when window regains focus
* 100% coverage with unit and unit-integration tests
* refactor x11.c and win.c
Toggle the autoindent feature on or off\.
.
.TP
+\fBOverwrite\fR
+Save the file to disk even if the file has been modified externally\.
+.
+.TP
\fBPaste\fR
Paste the contents of the X11 CLIPBOARD selection into the buffer\.
.
Redo the last undone change\.
.
.TP
+\fBReload\fR
+Reload the buffer contents from the on\-disk file\.
+.
+.TP
\fBSave\fR
Save the contents of the buffer to disk\.
.
### Scrolling
The scroll bar is located to the left of the content region. It's operation is
-similar to that of acme(1) or 9term(1). A left click on the scroll bar will
-scroll the adjacent line in the content region to the bottom of the window. A
-right click will move the adjacent line in the content region to the top of the
-window. A middle click will jump to an approximate location in the file
-determined by calculating the vertical distance from the top of the scrollbar
+similar to that of acme(1) or 9term(1). A left click on the scroll bar will
+scroll the adjacent line in the content region to the bottom of the window. A
+right click will move the adjacent line in the content region to the top of the
+window. A middle click will jump to an approximate location in the file
+determined by calculating the vertical distance from the top of the scrollbar
and applying that as a percentage to the offset in the file.
### Typing and Editing
Typed characters in `xedit` are delivered to the currently active region. Which
-region is active is determined by the placement of the mouse or by keyboard
+region is active is determined by the placement of the mouse or by keyboard
shortcut. That is to say, the focus follows the mouse much as it does in acme(1)
-but there is a keyboard shortcut that allows users to toggle focus between the
+but there is a keyboard shortcut that allows users to toggle focus between the
content region and the tag region without using the mouse.
-The mechanics of editing text in the tag and content region is identical with
-the exception of searching and saving. Edited content in the tag region is not
-saved to disk unlike the content region. This region is considered a scratch
-buffer for commands, notes, and other bits of text that are placed there
-temporarily. The content region displays the current view of the file being
+The mechanics of editing text in the tag and content region is identical with
+the exception of searching and saving. Edited content in the tag region is not
+saved to disk unlike the content region. This region is considered a scratch
+buffer for commands, notes, and other bits of text that are placed there
+temporarily. The content region displays the current view of the file being
edited and can be flushed to disk when requested.
-Searching with a term selected in the tags region will search for the term in
-the content region rather than the tags region. In this way a user can edit the
-search term incrementally and perform repeated searches through the content
+Searching with a term selected in the tags region will search for the term in
+the content region rather than the tags region. In this way a user can edit the
+search term incrementally and perform repeated searches through the content
region. Searching for a term in the content region *will* search for the term in
the content region however.
## TEXT SELECTION
-`xedit` uses a series of rules to determine how much text to select when the
-user executes a context sensitive selection, a search, or a context sensitive
+`xedit` uses a series of rules to determine how much text to select when the
+user executes a context sensitive selection, a search, or a context sensitive
execution. The following rules are applied in order until a match is found.
1. `Cursor over '(' or ')'`:
3. `Cursor over '{' or '}'`:
Highlight text between the braces including nested braces.
-
+
4. `Cursor at beginning or end of line`:
Highlight the entire line (including the newline)
-
+
5. `Cursor over alphanumeric character or underscore`:
- Highlight the word under the cursor consisting of only alphanumeric and
+ Highlight the word under the cursor consisting of only alphanumeric and
underscore characters.
-
-If none of the above rules match, `xedit` will simply highlight the block of
+
+If none of the above rules match, `xedit` will simply highlight the block of
non-whitespace characters under the cursor.
## MOUSE HANDLING
* `Left Button`:
The left mouse button is used for selecting text or moving the cursor. A
single-click will move the mose to the clicked location. A double-click will
- will select the object under the clicked location based on context as
+ will select the object under the clicked location based on context as
described in `TEXT SELECTION`. A triple-click will select the largest
contiguous chunk of non-whitespace characters at the clicked location.
* `Middle Button`:
The middle mouse button is used for executing text at the clicked location.
The command to be executed is determined by the context rules defined in
- the `TEXT SELECTION` section. The cursor position is not changed on a middle
+ the `TEXT SELECTION` section. The cursor position is not changed on a middle
click.
* `Right Button`:
- The right button is used to search for the next occurrence of the clicked
- text. The search term is determined by the context rules defined in the
+ The right button is used to search for the next occurrence of the clicked
+ text. The search term is determined by the context rules defined in the
`TEXT SELECTION` section. The search direction follows the direction of the
previous search operation. The `Shift` key can be held in combination with a
click of the right mosue button in order to reverse the search direction.
## COMMAND EXECUTION
-`xedit` allows for the execution of any arbitrary text as a command. The input
+`xedit` allows for the execution of any arbitrary text as a command. The input
and output to/from each command executed can be controlled by prepending one of
-a set of sigils defined below. These sigils instruct `xedit` from where the
-command will receive its input and where it will place its output (both standard
+a set of sigils defined below. These sigils instruct `xedit` from where the
+command will receive its input and where it will place its output (both standard
and errors).
* `'!' - Run command detached from editor`:
- The command will be executed in the background and all of its input and
+ The command will be executed in the background and all of its input and
output file descriptors will be closed.
* `'<' - Input from command`:
- The command will be executed in the background and its standard output will
- be placed in the content region. Its error output will be placed in the tags
+ The command will be executed in the background and its standard output will
+ be placed in the content region. Its error output will be placed in the tags
region.
* `'>' - Output to command`:
The command will be executed in the background. The currently selected text
- will be written to the command's standard input. The command's standard
+ will be written to the command's standard input. The command's standard
output and standard error content will be written to the tags region.
* `'|' - Pipe through command`:
The command will be executed in the background. The currently selected text
- will be written to the command's standard input. The command's standard
+ will be written to the command's standard input. The command's standard
output will replace the currently selected text. Any error output will be
placed in the tags region.
-
+
* `':' - Pipe through sed(1)`:
- Identical to '|' except that the command is always sed(1). This is a
- convenience shortcut to allow quick and easy access to sed for editing
+ Identical to '|' except that the command is always sed(1). This is a
+ convenience shortcut to allow quick and easy access to sed for editing
blocks of text.
* `Commands with no sigil`:
- Commands with none of the aforementioned sigils will be executed in the
- background and have their standard output placed in the content region and
+ Commands with none of the aforementioned sigils will be executed in the
+ background and have their standard output placed in the content region and
their error output placed in the tags region.
## KEYBOARD SHORTCUTS
* `Down`:
Move the cursor to the next line.
-
+
* `Ctrl+Up`:
Move the current line or selection up a line.
* `Indent`:
Toggle the autoindent feature on or off.
+* `Overwrite`:
+ Save the file to disk even if the file has been modified externally.
+
* `Paste`:
Paste the contents of the X11 CLIPBOARD selection into the buffer.
* `Redo`:
Redo the last undone change.
+* `Reload`:
+ Reload the buffer contents from the on-disk file.
+
* `Save`:
Save the contents of the buffer to disk.
* `SaveAs [path]`:
- Save the contents of the buffer to disk. If a path argument is provided, the
+ Save the contents of the buffer to disk. If a path argument is provided, the
buffer will be saved to the new path.
* `Tabs`:
char* stringdup(const char* str);
char* fdgets(int fd);
char* chomp(char* in);
+uint64_t modtime(char* path);
/* Buffer management functions
*****************************************************************************/
/* gap buffer main data structure */
typedef struct buf {
char* path; /* the path to the open file */
+ uint64_t modtime; /* modification time of the opened file */
int charset; /* the character set of the buffer */
int crlf; /* tracks whether the file uses dos style line endings */
size_t bufsize; /* size of the buffer in runes */
} Sel;
void buf_init(Buf* buf, void (*errfn)(char*));
+void buf_reload(Buf* buf);
unsigned buf_load(Buf* buf, char* path);
void buf_save(Buf* buf);
Rune buf_get(Buf* buf, unsigned pos);
enum {
BINARY = 0, /* binary encoded file */
UTF_8, /* UTF-8 encoded file */
-
+
/* these arent used but are reserved for later */
UTF_16BE, /* UTF-16 encoding, big-endian */
UTF_16LE, /* UTF-16 encoding, little-endian */
};
void view_init(View* view, char* file, void (*errfn)(char*));
+void view_reload(View* view);
size_t view_limitrows(View* view, size_t maxrows, size_t ncols);
void view_resize(View* view, size_t nrows, size_t ncols);
void view_update(View* view, size_t* csrx, size_t* csry);
/* reset buffer state */
buf->modified = false;
+ buf->modtime = modtime(buf->path);
buf_logclear(buf);
return off;
}
+void buf_reload(Buf* buf) {
+ void (*errfn)(char*) = buf->errfn;
+ char* path = buf->path;
+ buf_init(buf, errfn);
+ buf_load(buf, path);
+}
+
void buf_save(Buf* buf) {
if (0 == buf_end(buf)) return;
-
- /* text files should always end in a new line. If we detected a
+
+ /* text files should always end in a new line. If we detected a
binary file or at least a non-utf8 file, skip this part. */
if (!buf_iseol(buf, buf_end(buf)-1) && (buf->charset != BINARY))
buf_insert(buf, false, buf_end(buf), '\n');
-
+
size_t wrlen = 0;
if (buf->path) {
FMap file = mmap_readwrite(buf->path, buf_end(buf) * UTF_MAX);
mmap_close(file);
truncate(buf->path, wrlen);
buf->modified = false;
+ buf->modtime = modtime(buf->path);
} else {
buf->errfn("Failed to open file for writing");
}
} else {
off = buf_byrune(buf, off, move);
}
-
+
return off;
}
unsigned opbeg = *end, opend = *end;
if (log && log->insert)
opbeg = log->data.ins.end, opend = log->data.ins.end;
-
+
unsigned delsize = 0;
for (; log; log = log->next) {
if (log->insert) {
return in;
}
+uint64_t modtime(char* path) {
+ struct stat status;
+ if (stat(path, &status) < 0)
+ return 0u;
+ return (uint64_t)status.st_mtime;
+}
view->selection.end = buf_load(&(view->buffer), file);
view->selection.beg = view->selection.end;
view->sync_needed = true;
+ view->sync_center = true;
+ }
+}
+
+void view_reload(View* view) {
+ if (view->buffer.path) {
+ buf_reload(&(view->buffer));
+ view->selection = (Sel){ 0 };
+ view->sync_needed = true;
view->sync_center = true;
}
}
view_append(win_view(TAGS), msg);
}
-static void quit(void) {
- static uint64_t before = 0;
- uint64_t now = getmillis();
- if (!win_buf(EDIT)->modified || (now-before) <= 250) {
- #ifndef TEST
- x11_deinit();
- #else
- exit(0);
- #endif
- } else {
- view_append(win_view(TAGS),
- "File is modified. Repeat action twice in < 250ms to quit.");
- }
- before = now;
-}
-
-static void save(void) {
+static void trim_whitespace(void) {
Buf* buf = win_buf(EDIT);
if (TrimOnSave && buf_end(buf) > 0) {
View* view = win_view(EDIT);
off = buf_byline(buf, off, +1);
}
}
- buf_save(buf);
+}
+
+static void quit(void) {
+ static uint64_t before = 0;
+ uint64_t now = getmillis();
+ if (!win_buf(EDIT)->modified || (now-before) <= 250) {
+ #ifndef TEST
+ x11_deinit();
+ #else
+ exit(0);
+ #endif
+ } else {
+ view_append(win_view(TAGS),
+ "File is modified. Repeat action twice in < 250ms to quit.");
+ }
+ before = now;
+}
+
+static void save(void) {
+ Buf* buf = win_buf(EDIT);
+ if (buf->modtime != modtime(buf->path)) {
+ view_append(win_view(TAGS),
+ "File modified externally: Reload or Overwrite");
+ } else {
+ trim_whitespace();
+ buf_save(win_buf(EDIT));
+ }
+}
+
+static void overwrite(void) {
+ trim_whitespace();
+ buf_save(win_buf(EDIT));
+}
+
+static void reload(void) {
+ view_reload(win_view(EDIT));
}
/* Mouse Handling
/* Main Routine
******************************************************************************/
static Tag Builtins[] = {
- { .tag = "Quit", .action.noarg = quit },
- { .tag = "Save", .action.noarg = save },
- { .tag = "SaveAs", .action.arg = saveas },
- { .tag = "Cut", .action.noarg = cut },
- { .tag = "Copy", .action.noarg = copy },
- { .tag = "Paste", .action.noarg = paste },
- { .tag = "Undo", .action.noarg = tag_undo },
- { .tag = "Redo", .action.noarg = tag_redo },
- { .tag = "Find", .action.arg = find },
- { .tag = "GoTo", .action.arg = jump_to },
- { .tag = "Tabs", .action.noarg = tabs },
- { .tag = "Indent", .action.noarg = indent },
- { .tag = "Eol", .action.noarg = eol_mode },
- { .tag = NULL, .action.noarg = NULL }
+ { .tag = "Cut", .action.noarg = cut },
+ { .tag = "Copy", .action.noarg = copy },
+ { .tag = "Eol", .action.noarg = eol_mode },
+ { .tag = "Find", .action.arg = find },
+ { .tag = "GoTo", .action.arg = jump_to },
+ { .tag = "Indent", .action.noarg = indent },
+ { .tag = "Overwrite", .action.noarg = overwrite },
+ { .tag = "Paste", .action.noarg = paste },
+ { .tag = "Quit", .action.noarg = quit },
+ { .tag = "Redo", .action.noarg = tag_redo },
+ { .tag = "Reload", .action.noarg = reload },
+ { .tag = "Save", .action.noarg = save },
+ { .tag = "SaveAs", .action.arg = saveas },
+ { .tag = "Tabs", .action.noarg = tabs },
+ { .tag = "Undo", .action.noarg = tag_undo },
+ { .tag = NULL, .action.noarg = NULL }
};
static KeyBinding Bindings[] = {