Repository: saulpw/visidata

Issue:
[save-selected] default save filename is not valid
**Small description**
When doing a `save-selected` the default sheet name is invalid, see the screenshot below

**Expected result**
For the suggested file name to be valid and useable. Maybe something like '`sheet1_sheet2_.csv`'.

**Actual result with screenshot**
![image](https://user-images.githubusercontent.com/11225502/140711890-afa742c8-0927-4d79-ab51-c06a54dcdb89.png)

**Steps to reproduce with sample data and a .vd**
Open a number of sheets, go to `sheets-stack` select a few and do `save-selected`. This gives a stringified list rather than a proper name.

**Additional context**
Commit: https://github.com/saulpw/visidata/commit/9ce048d0378de0211e53f1591d79c8fbed39dff1

This seems to work:
`IndexSheet.addCommand('g^S', 'save-selected', 'vd.saveSheets(inputPath("save %d sheets to: " % nSelectedRows, value="_".join(vs.name or "blank" for vs in source)), *selectedRows, confirm_overwrite=options.confirm_overwrite)', 'save all selected sheets to given file or directory')`



Discussion:
While looking at this, I was interested to see there is some ambiguity in the saver functionality when it comes to saving multiple files, for example:

- For savers that are not capable of embedded sheets (e.g. csv, as opposed to xlsx), there are no indications to the user they should provide a directory, rather than a single file. Only a full trace exception.
- When saving to a directory, you cannot specific a filetype? Unless by `option.save_filetype`?

Question: Should all `save_foo` methods be capable of taking multiple sheets? If the path is a dir, they save each sheet within it, if it is a full path, it should `vd.fail("cannot save multiple sheets to a single file")`

Resolving patch:
diff --git a/visidata/save.py b/visidata/save.py
--- a/visidata/save.py
+++ b/visidata/save.py
@@ -107,11 +107,16 @@ def saveSheets(vd, givenpath, *vsheets, confirm_overwrite=False):
 
     savefunc = getattr(vsheets[0], 'save_' + filetype, None) or getattr(vd, 'save_' + filetype, None)
 
+    using_save_filetype = False
+
     if savefunc is None:
         savefunc = getattr(vd, 'save_' + options.save_filetype, None) or vd.fail('no function to save as type %s, set options.save_filetype' % filetype)
         vd.warning(f'save for {filetype} unavailable, using {options.save_filetype}')
+        using_save_filetype = True
 
-    if givenpath.exists() and confirm_overwrite:
+    if using_save_filetype and givenpath.exists() and confirm_overwrite:
+        vd.confirm("%s already exists. overwrite with %s? " % (givenpath.given, options.save_filetype))
+    elif givenpath.exists() and confirm_overwrite:
         vd.confirm("%s already exists. overwrite? " % givenpath.given)
 
     vd.status('saving %s sheets to %s as %s' % (len(vsheets), givenpath.given, filetype))
@@ -119,9 +124,13 @@ def saveSheets(vd, givenpath, *vsheets, confirm_overwrite=False):
     if not givenpath.given.endswith('/'):  # forcibly specify save individual files into directory by ending path with /
         for vs in vsheets:
             vs.hasBeenModified = False
+        # savefuncs(vd, p, *vsheets) will have 2 argcount (*vsheets does not get counted as an arg)
+        # savefuncs(vd, p, vs) will have 3 argcount (vs counts as an arg, along with vd, path)
+        if savefunc.__code__.co_argcount == 3 and len(vsheets) > 1:
+            vd.fail(f'cannot save multiple {filetype} sheets to non-dir')
         return vd.execAsync(savefunc, givenpath, *vsheets)
 
-    # more than one sheet; either no specific multisave for save filetype, or path ends with /
+    # path is a dir
 
     # save as individual files in the givenpath directory
     try:
diff --git a/visidata/textsheet.py b/visidata/textsheet.py
--- a/visidata/textsheet.py
+++ b/visidata/textsheet.py
@@ -7,7 +7,7 @@
 
 
 vd.option('wrap', False, 'wrap text to fit window width on TextSheet')
-vd.option('save_filetype', '', 'specify default file type to save as', replay=True)
+vd.option('save_filetype', 'tsv', 'specify default file type to save as', replay=True)
 
 
 ## text viewer


Test patch: