]> git.mdlowis.com Git - projs/tide.git/commitdiff
added logic to check for external file changes on save. Added 'Reload' and 'Overwrite...
authorMichael D. Lowis <mike@mdlowis.com>
Tue, 23 May 2017 01:47:27 +0000 (21:47 -0400)
committerMichael D. Lowis <mike@mdlowis.com>
Tue, 23 May 2017 01:47:27 +0000 (21:47 -0400)
TODO.md
docs/xedit.1
docs/xedit.1.md
inc/edit.h
lib/buf.c
lib/utils.c
lib/view.c
xedit.c

diff --git a/TODO.md b/TODO.md
index 0709aac2cf085aa83a2efd8dfa24ba51b6071140..e54929b57937a62f6ba8a7fb135555c9b75661bf 100644 (file)
--- 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
index bd2d0f640dae19dd4981c98236d8f067d505a25e..1e47a966bac368e96e56f64031d5dfc548b3c4d9 100644 (file)
@@ -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\.
 .
index 1ef369f1861f945ec9e688c151c6bd06157e9a30..f0569c08284da0cc826555a94907a44f4686262e 100644 (file)
@@ -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`:
index 5670ecfee671aee45beee565e609cfddc0d0ef4c..6c8e295eeabd3b81a3f650bbac2ca10b74ad2c70 100644 (file)
@@ -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);
index 9f6e877f81731bbbccfc5ee47fc58eb0612fd929..649c35a28c7194b00973c55337cdd57b0a9791e0 100644 (file)
--- 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) {
index 6f029f818c57a092b4360fff07b6fce1d2936e39..3d74cbb80f89f95f690d711f0a2c5eb2f35261e1 100644 (file)
@@ -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;
+}
index adfe9271cfdb52ccd3d0e217539b103bcacce710..52478033fd9187bcdd4a7ca45039fba1c82b4c7b 100644 (file)
@@ -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 dfaf74052eb6db210bf2a1a3abffcc74be1f664f..51e3ae7a39e5b339e3998c717b4918e405e089ec 100644 (file)
--- 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[] = {