Save as, Open and Download's prompts and the command line now share saveAsFile, openFile and downloadFile, so files are resolved, checked and replaced one way whichever reached them.
- commit
- 6f25f2ba07950e4efe8e89fc1c1ef2deda6e887d
- parent
- af4eda8871d0d584342d0bfd8e11584dfdbe0608
- author
- Dami <dami.etoile@gmail.com>
| M | docs/ | 2 | +- |
| M | internal/ | 46 | ++++++++++++------------------ |
| M | internal/ | 34 | +++++++++++++--------- |
| M | internal/ | 25 | ++++++++++------ |
@@ -97,7 +97,7 @@ The `:` line takes:
9797 |---|---|9898 | `:B12`, `:Sheet2!A1`, `:C3:D9`, `:Sales` | Go to a cell, range or named range |9999 | `:40` | Go to row 40 |100-| `:w`, `:w name`, `:w out.csv` | Save; save as; download as another format (`:w!` replaces without asking) |100+| `:w`, `:w name`, `:w out.csv` | Save, Save as, or Download as another format, just as the File menu does |101101 | `:q`, `:q!` | Quit (asking about unsaved changes), quit discarding them |102102 | `:wq`, `:x` | Save and quit; `:x` saves only if something changed |103103 | `:e name`, `:e!` | Open a sheet or import a file; refused with unsaved changes unless `:e!` |@@ -2,7 +2,6 @@ package ui
22 33 import (44 "cmp"5- "path/filepath"65 "slices"76 "strconv"87 "strings"@@ -181,7 +180,7 @@ func (m *Model) runCmdLine(text string) (tea.Cmd, bool) {
181180 bang := strings.HasSuffix(word, "!")182181 switch strings.TrimSuffix(word, "!") {183182 case "w":184- return m.writeTo(arg, bang), true183+ return m.writeTo(arg), true185184 case "q":186185 if bang {187186 return m.exit(), true@@ -192,7 +191,7 @@ func (m *Model) runCmdLine(text string) (tea.Cmd, bool) {
192191 return m.exit(), true193192 }194193 m.quitAfterSave = true195- return m.writeTo(arg, bang), true194+ return m.writeTo(arg), true196195 case "e":197196 return m.editFile(arg, bang), true198197 }@@ -231,31 +230,25 @@ func commandNamed(text string) (string, bool) {
231230 return "", false232231 }233232 234-// writeTo is :w. Without a name it saves; with one it saves under that235-// name, or downloads when the name is another format's (:w out.csv).236-// An existing file is replaced only after asking, or with :w!.237-func (m *Model) writeTo(name string, force bool) tea.Cmd {233+// writeTo is :w. Without a name it saves, as File > Save; with one it234+// saves as that name, as File > Save as, or downloads when the name is235+// another format's (:w out.csv), as File > Download. It goes the menus'236+// ways, so files are named, checked and replaced the same.237+func (m *Model) writeTo(name string) tea.Cmd {238238 if name == "" {239239 return m.runCommand("file.save")240240 }241- if k, ok := fileio.KindOf(name); ok {242- if !k.CanExport() {243- m.quitAfterSave = false244- m.fail("012 can't write " + k.String() + " files")245- return nil246- }247- if k.HasTables() {248- m.openTableName(name, k, sheet.Rect{})249- return nil250- }251- return m.confirmReplace(filepath.Base(name)+" exists.", func(m *Model) tea.Cmd {252- return m.download(name, k, sheet.Rect{}, "")253- }, exists(name) && !force)241+ k, ok := fileio.KindOf(name)242+ switch {243+ case !ok:244+ return m.saveAsFile(name)245+ case !k.CanExport():246+ m.quitAfterSave = false247+ m.fail("012 can't write " + k.String() + " files")248+ return nil254249 }255- name = withExt(name)256- return m.confirmReplace(filepath.Base(name)+" exists.", func(m *Model) tea.Cmd {257- return saveCmd(m.sheet, name)258- }, exists(name) && !force && name != m.filename)250+ m.quitAfterSave = false // a download isn't the sheet's file251+ return m.downloadFile(k, sheet.Rect{}, name)259252 }260253 261254 // editFile is :e name: open a sheet or import a file, refusing to drop@@ -268,10 +261,7 @@ func (m *Model) editFile(name string, force bool) tea.Cmd {
268261 m.fail("Unsaved changes: :w saves them, :e! " + name + " discards them")269262 return nil270263 }271- if _, ok := fileio.KindOf(name); ok {272- return m.confirmImport(name, fileio.Options{})273- }274- return loadCmd(withExt(name))264+ return m.openFile(name)275265 }276266 277267 func (c *cmdLine) contextLine(m *Model) (string, string) {@@ -78,24 +78,30 @@ func (m *Model) openDownload(k fileio.Kind) tea.Cmd {
7878 label = "Download " + r.String() + " as " + k.String() + ":"7979 }8080 m.openText(label, m.displayBase()+k.Ext(), func(m *Model, text string) tea.Cmd {81- if text == "" {82- return nil83- }84- name := text85- if filepath.Ext(name) == "" {86- name += k.Ext()87- }88- if k.HasTables() {89- m.openTableName(name, k, r)90- return nil91- }92- return m.confirmReplace(filepath.Base(name)+" exists.", func(m *Model) tea.Cmd {93- return m.download(name, k, r, "")94- }, exists(name))81+ return m.downloadFile(k, r, text)9582 })9683 return nil9784 }9885 86+// downloadFile downloads r (the whole sheet when zero) as k to a file87+// named for Download or :w, asking before replacing a file or table.88+func (m *Model) downloadFile(k fileio.Kind, r sheet.Rect, text string) tea.Cmd {89+ if text == "" {90+ return nil91+ }92+ name := text93+ if filepath.Ext(name) == "" {94+ name += k.Ext()95+ }96+ if k.HasTables() {97+ m.openTableName(name, k, r)98+ return nil99+ }100+ return m.confirmReplace(filepath.Base(name)+" exists.", func(m *Model) tea.Cmd {101+ return m.download(name, k, r, "")102+ }, exists(name))103+}104+99105 func exists(name string) bool {100106 _, err := os.Stat(name)101107 return err == nil@@ -23,22 +23,29 @@ func (m *Model) openSave() tea.Cmd {
2323 if name == "" {2424 name = m.displayBase() + sheet.FileExt2525 }26- m.openText("Save as:", name, func(m *Model, text string) tea.Cmd {27- return saveCmd(m.sheet, withExt(text))28- })26+ m.openText("Save as:", name, (*Model).saveAsFile)2927 return nil3028 }3129 30+// saveAsFile saves the sheet under a name typed for Save as or :w.31+func (m *Model) saveAsFile(text string) tea.Cmd {32+ return saveCmd(m.sheet, withExt(text))33+}34+3235 func (m *Model) openRetrieve() tea.Cmd {33- m.openText("Open file:", "", func(m *Model, text string) tea.Cmd {34- if _, ok := fileio.KindOf(text); ok {35- return m.confirmImport(text, fileio.Options{})36- }37- return loadCmd(withExt(text))38- })36+ m.openText("Open file:", "", (*Model).openFile)3937 return listFilesCmd4038 }4139 40+// openFile opens a sheet, or imports a file of another format, named for41+// Open or :e.42+func (m *Model) openFile(text string) tea.Cmd {43+ if _, ok := fileio.KindOf(text); ok {44+ return m.confirmImport(text, fileio.Options{})45+ }46+ return loadCmd(withExt(text))47+}48+4249 // reset starts over on s, as File > New and Open do. What belongs to the4350 // session rather than the sheet carries over: the window, theme, terminal4451 // state and the JEV connection.