feat: start sops edit on the key's line
CI / Checks (pull_request) Successful in 25s
PR title / Conventional title (pull_request) Successful in 1s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kebandClaude Opus 5.5 committed 2026-10-11 00:25:01 +02:00
1 parent c29319b821
commit 10c2362482
3 files changed
+49 -5

No files matched your search

+3 -1
View File
@@ -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. without either, in the system's default app for the file.
Editors that take a line (vim, nvim, nano, emacs, micro, helix, VS Code, 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 ## Links
+27 -4
View File
@@ -1,6 +1,7 @@
package app package app
import ( import (
"cmp"
"errors" "errors"
"os" "os"
"os/exec" "os/exec"
@@ -71,6 +72,12 @@ func (m *Model) edit() tea.Cmd {
if f.Sops != "" { if f.Sops != "" {
cmd := exec.Command(f.Sops, "edit", f.Path) cmd := exec.Command(f.Sops, "edit", f.Path)
cmd.Dir = filepath.Dir(f.Path) // sops looks for .sops.yaml from here 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{} m.flash = flash{}
return tea.ExecProcess(cmd, finished) return tea.ExecProcess(cmd, finished)
} }
@@ -100,6 +107,22 @@ func (m *Model) edit() tea.Cmd {
return nil 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. // editorArgs opens f at its line, for editors known to take one.
func editorArgs(editor string, f provider.File) []string { func editorArgs(editor string, f provider.File) []string {
fields := strings.Fields(editor) fields := strings.Fields(editor)
@@ -107,12 +130,12 @@ func editorArgs(editor string, f provider.File) []string {
return []string{f.Path} return []string{f.Path}
} }
at := f.Path + ":" + strconv.Itoa(f.Line) at := f.Path + ":" + strconv.Itoa(f.Line)
switch filepath.Base(fields[0]) { switch base := filepath.Base(fields[0]); {
case "vi", "vim", "nvim", "nano", "emacs", "emacsclient", "micro", "kak", "mg", "ne", "joe", "vis": case plusLine[base]:
return []string{"+" + strconv.Itoa(f.Line), f.Path} 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} return []string{at}
case "code", "codium", "cursor", "windsurf": case base == "code" || base == "codium" || base == "cursor" || base == "windsurf":
return []string{"-g", at} return []string{"-g", at}
} }
return []string{f.Path} return []string{f.Path}
+19
View File
@@ -323,3 +323,22 @@ func TestEditorArgs(t *testing.T) {
t.Errorf("no line: %q", got) 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)
}
}
}