Repository: saulpw/visidata

Issue:
[wishlist] Allow undo-ing a reload?
Command log and data: [undo-reload-log-and-data.zip](https://github.com/saulpw/visidata/files/4532253/undo-reload-log-and-data.zip) 

(Note: To fully reproduce, you'll have to press `U` to run the final step, which  seems not to have been logged.)

In the command log above, I load a minimal CSV, `addcol-expr ="a"`, press reload, and then try to undo. My expectation had been that the reload would have been undone, returning me to the pre-reload state ... but instead the statusline tells me that addcol-expr is "undone":

> 1› test-100-nums| addcol-expr undone

... which is unexpected, since that column does not exist on the (not-undone) reloaded sheet.

Ideally (for me), VisiData would simply be able to undo a reload. That said, I realize there may be other constraints / architectural priorities. So, as a backup, perhaps VisiData could just tell me that "reload commands cannot be undone"? That still leaves the question of how to handle undoing/redoing actions that came before the reload, though...



Discussion:
If possible, it would be useful if reload could be undone.  I really like this idea.
Losing changes due to reload is a pain. I've also reported it: https://github.com/saulpw/visidata/issues/438
I feel a warning prompt would be helpful if undo is not implemented.
Hi!

I agree that undo-ing a reload would be convenient. I am going to give a quick overview of why this has not been implemented yet.

Undo works by keeping track of the previous state. Here is an example of one of the undo functions:

```
 def addUndoSetValues(vd, cols, rows):                                                                                                                                                   
     oldvals = [(c, r, c.getValue(r)) for c,r in itertools.product(cols, vd.Progress(rows, gerund='doing'))]                                                                             
     def _undo():                                                                                                                                                                        
         for c, r, v in oldvals:                                                                                                                                                         
             c.setValue(r, v)                                                                                                                                                            
     vd.addUndo(_undo) 
```

Reload is not a modification. It is not comparing the current rows to source, and making updates where it sees differences. It empties the existing rows and columns, and re-creates the sheet from source. 

What this means is to create an undo function for reload, you would need to save the entire state of the sheet pre-reload. This can easily become a memory issue with large sheets, especially if the user reloads frequently in a session. 

We do not foresee users reloading a whole ton, so I think there is a chance that this will end up getting implemented. It is convenient, and folks want it. =)
Thanks for explaining, @anjakefala! Would a reasonable compromise be to prompt users on reload to ask whether to save the previous sheet in the undo-history? I feel like this might solve two things at once:

- The issue at hand
- Allowing people to cancel an unintentional reload (which in the past I have sometimes done when instead trying to run `random-rows` ... though it looks like that particular command has been demapped from the keyboard shortcuts in 2x)

Resolving patch:
diff --git a/visidata/sheets.py b/visidata/sheets.py
--- a/visidata/sheets.py
+++ b/visidata/sheets.py
@@ -988,7 +988,7 @@ def preloadHook(sheet):
 globalCommand('S', 'sheets-stack', 'vd.push(vd.sheetsSheet)', 'open Sheets Stack: join or jump between the active sheets on the current stack')
 globalCommand('gS', 'sheets-all', 'vd.push(vd.allSheetsSheet)', 'open Sheets Sheet: join or jump between all sheets from current session')
 
-BaseSheet.addCommand('^R', 'reload-sheet', 'preloadHook(); reload(); recalc(); status("reloaded")', 'reload current sheet'),
+BaseSheet.addCommand('^R', 'reload-sheet', 'preloadHook(); vd.addUndoReload(rows, columns); reload(); recalc(); status("reloaded")', 'reload current sheet'),
 Sheet.addCommand('^G', 'show-cursor', 'status(statusLine)', 'show cursor position and bounds of current sheet on status line'),
 
 Sheet.addCommand('!', 'key-col', 'toggleKeys([cursorCol])', 'toggle current column as a key column')
diff --git a/visidata/undo.py b/visidata/undo.py
--- a/visidata/undo.py
+++ b/visidata/undo.py
@@ -95,6 +95,15 @@ def _undo():
             c.name = name
     vd.addUndo(_undo)
 
+@VisiData.api
+def addUndoReload(vd, rows, cols):
+    oldrows = rows
+    oldcolumns = cols
+    def _undo():
+        sheet = oldcolumns[0].sheet
+        sheet.rows = oldrows
+        sheet.columns = oldcolumns
+    vd.addUndo(_undo)
 
 BaseSheet.addCommand('U', 'undo-last', 'vd.undo(sheet)', 'undo the most recent modification (requires enabled options.undo)')
 BaseSheet.addCommand('R', 'redo-last', 'vd.redo(sheet)', 'redo the most recent undo (requires enabled options.undo)')


Test patch: