From: Michael D. Lowis Date: Tue, 23 May 2017 01:47:27 +0000 (-0400) Subject: added logic to check for external file changes on save. Added 'Reload' and 'Overwrite... X-Git-Url: https://git.mdlowis.com/?a=commitdiff_plain;h=9883b021c4e851b0e8f2a330ef6ebf4024e00a95;p=projs%2Ftide.git added logic to check for external file changes on save. Added 'Reload' and 'Overwrite' builtins to address them --- diff --git a/TODO.md b/TODO.md index 0709aac..e54929b 100644 --- a/TODO.md +++ b/TODO.md @@ -6,7 +6,6 @@ Up Next: * 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 diff --git a/docs/xedit.1 b/docs/xedit.1 index bd2d0f6..1e47a96 100644 --- a/docs/xedit.1 +++ b/docs/xedit.1 @@ -300,6 +300,10 @@ Jump to a specific line number or symbol\. 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\. . @@ -312,6 +316,10 @@ Quit the editor\. 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\. . diff --git a/docs/xedit.1.md b/docs/xedit.1.md index 1ef369f..f0569c0 100644 --- a/docs/xedit.1.md +++ b/docs/xedit.1.md @@ -29,38 +29,38 @@ of the content region. This region acts as a scrollbar for the content region. ### 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 ')'`: @@ -71,15 +71,15 @@ execution. The following rules are applied in order until a match is found. 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 @@ -87,59 +87,59 @@ non-whitespace characters under the cursor. * `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 @@ -183,7 +183,7 @@ position. * `Down`: Move the cursor to the next line. - + * `Ctrl+Up`: Move the current line or selection up a line. @@ -331,6 +331,9 @@ search operation to be applied in the opposite direction of the previous. * `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. @@ -340,11 +343,14 @@ search operation to be applied in the opposite direction of the previous. * `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`: diff --git a/inc/edit.h b/inc/edit.h index 5670ecf..6c8e295 100644 --- a/inc/edit.h +++ b/inc/edit.h @@ -13,6 +13,7 @@ uint64_t getmillis(void); char* stringdup(const char* str); char* fdgets(int fd); char* chomp(char* in); +uint64_t modtime(char* path); /* Buffer management functions *****************************************************************************/ @@ -37,6 +38,7 @@ typedef struct Log { /* 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 */ @@ -61,6 +63,7 @@ typedef struct { } 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); @@ -96,7 +99,7 @@ void buf_lastins(Buf* buf, size_t* beg, size_t* end); 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 */ @@ -142,6 +145,7 @@ enum { }; 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); diff --git a/lib/buf.c b/lib/buf.c index 9f6e877..649c35a 100644 --- a/lib/buf.c +++ b/lib/buf.c @@ -234,18 +234,26 @@ unsigned buf_load(Buf* buf, char* path) { /* 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); @@ -264,6 +272,7 @@ void buf_save(Buf* buf) { mmap_close(file); truncate(buf->path, wrlen); buf->modified = false; + buf->modtime = modtime(buf->path); } else { buf->errfn("Failed to open file for writing"); } @@ -486,7 +495,7 @@ unsigned buf_byword(Buf* buf, unsigned off, int count) { } else { off = buf_byrune(buf, off, move); } - + return off; } @@ -590,7 +599,7 @@ void buf_lastins(Buf* buf, size_t* beg, size_t* end) { 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) { diff --git a/lib/utils.c b/lib/utils.c index 6f029f8..3d74cbb 100644 --- a/lib/utils.c +++ b/lib/utils.c @@ -98,3 +98,9 @@ char* chomp(char* in) { return in; } +uint64_t modtime(char* path) { + struct stat status; + if (stat(path, &status) < 0) + return 0u; + return (uint64_t)status.st_mtime; +} diff --git a/lib/view.c b/lib/view.c index adfe927..5247803 100644 --- a/lib/view.c +++ b/lib/view.c @@ -187,6 +187,15 @@ void view_init(View* view, char* file, void (*errfn)(char*)) { 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; } } diff --git a/xedit.c b/xedit.c index dfaf740..51e3ae7 100644 --- a/xedit.c +++ b/xedit.c @@ -110,23 +110,7 @@ static void onerror(char* msg) { 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); @@ -147,7 +131,42 @@ static void save(void) { 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 @@ -356,20 +375,22 @@ void highlight(void) { /* 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[] = {