feat: e opens a repo's SOPS or .env file, in nvim, sops edit or your editor #1
No files matched your search
+3
-1
@@ -77,7 +77,9 @@ KMS, PGP). When sops can't decrypt, its whole error message is shown.
|
||||
without either, in the system's default app for the file.
|
||||
|
||||
Editors that take a line (vim, nvim, nano, emacs, micro, helix, VS Code,
|
||||
Zed, Sublime and others) start on the key.
|
||||
Zed, Sublime and others) start on the key. With `sops edit` that works for
|
||||
editors taking `+N` (vim, nvim, nano, emacs, micro and others), and needs
|
||||
sops 3.8 or later.
|
||||
|
||||
## Links
|
||||
|
||||
|
||||
+27
-4
@@ -1,6 +1,7 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -71,6 +72,12 @@ func (m *Model) edit() tea.Cmd {
|
||||
if f.Sops != "" {
|
||||
cmd := exec.Command(f.Sops, "edit", f.Path)
|
||||
cmd.Dir = filepath.Dir(f.Path) // sops looks for .sops.yaml from here
|
||||
// sops starts SOPS_EDITOR (else EDITOR, else vim) on the decrypted
|
||||
// copy, which keeps the file's lines: start it on the key.
|
||||
editor := cmp.Or(m.getenv("SOPS_EDITOR"), m.getenv("EDITOR"), "vim")
|
||||
if at, ok := editorAtLine(editor, f.Line); ok {
|
||||
cmd.Env = append(os.Environ(), "SOPS_EDITOR="+at)
|
||||
}
|
||||
m.flash = flash{}
|
||||
return tea.ExecProcess(cmd, finished)
|
||||
}
|
||||
@@ -100,6 +107,22 @@ func (m *Model) edit() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
// plusLine are editors that open at a line with +N before the file.
|
||||
var plusLine = map[string]bool{
|
||||
"vi": true, "vim": true, "nvim": true, "nano": true, "emacs": true, "emacsclient": true,
|
||||
"micro": true, "kak": true, "mg": true, "ne": true, "joe": true, "vis": true,
|
||||
}
|
||||
|
||||
// editorAtLine is editor starting at line, for editors that take +N:
|
||||
// sops adds the file after it.
|
||||
func editorAtLine(editor string, line int) (string, bool) {
|
||||
fields := strings.Fields(editor)
|
||||
if line == 0 || len(fields) == 0 || !plusLine[filepath.Base(fields[0])] {
|
||||
return "", false
|
||||
}
|
||||
return editor + " +" + strconv.Itoa(line), true
|
||||
}
|
||||
|
||||
// editorArgs opens f at its line, for editors known to take one.
|
||||
func editorArgs(editor string, f provider.File) []string {
|
||||
fields := strings.Fields(editor)
|
||||
@@ -107,12 +130,12 @@ func editorArgs(editor string, f provider.File) []string {
|
||||
return []string{f.Path}
|
||||
}
|
||||
at := f.Path + ":" + strconv.Itoa(f.Line)
|
||||
switch filepath.Base(fields[0]) {
|
||||
case "vi", "vim", "nvim", "nano", "emacs", "emacsclient", "micro", "kak", "mg", "ne", "joe", "vis":
|
||||
switch base := filepath.Base(fields[0]); {
|
||||
case plusLine[base]:
|
||||
return []string{"+" + strconv.Itoa(f.Line), f.Path}
|
||||
case "hx", "helix", "subl", "zed":
|
||||
case base == "hx" || base == "helix" || base == "subl" || base == "zed":
|
||||
return []string{at}
|
||||
case "code", "codium", "cursor", "windsurf":
|
||||
case base == "code" || base == "codium" || base == "cursor" || base == "windsurf":
|
||||
return []string{"-g", at}
|
||||
}
|
||||
return []string{f.Path}
|
||||
|
||||
@@ -323,3 +323,22 @@ func TestEditorArgs(t *testing.T) {
|
||||
t.Errorf("no line: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEditorAtLine(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, c := range []struct {
|
||||
editor string
|
||||
line int
|
||||
want string
|
||||
ok bool
|
||||
}{
|
||||
{"nvim", 4, "nvim +4", true},
|
||||
{"/usr/bin/vim -p", 4, "/usr/bin/vim -p +4", true},
|
||||
{"code --wait", 4, "", false}, // sops adds the file last: no -g file:N
|
||||
{"nvim", 0, "", false},
|
||||
} {
|
||||
if got, ok := editorAtLine(c.editor, c.line); got != c.want || ok != c.ok {
|
||||
t.Errorf("%q at %d: %q %v", c.editor, c.line, got, ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user