feat: e on the status pane edits the config #55

Merged
keb merged 2 commits from edit-config into main 2026-10-13 13:03:43 +02:00
7 changed files with 26 additions and 16 deletions
Showing only changes of commit afaf7ac567 - Show all commits

No files matched your search

+3 -3
View File
@@ -2,9 +2,9 @@
Optional, at `~/.config/lazysecrets/config.yaml` (or Optional, at `~/.config/lazysecrets/config.yaml` (or
`$XDG_CONFIG_HOME/lazysecrets/config.yaml`, or `--config`). Flags override `$XDG_CONFIG_HOME/lazysecrets/config.yaml`, or `--config`). Flags override
it. Unknown keys are an error, so typos don't go unnoticed. `E` in it. Unknown keys are an error, so typos don't go unnoticed. `e` on the
lazysecrets opens it in your editor (making it if it isn't there yet); status pane (`0`) opens it in your editor, as in lazygit (making it if
changes apply when lazysecrets starts again. it isn't there yet); changes apply when lazysecrets starts again.
```yaml ```yaml
icons: true # Nerd Font icons (v3+); also --icons=false or LAZYSECRETS_ICONS=0 icons: true # Nerd Font icons (v3+); also --icons=false or LAZYSECRETS_ICONS=0
+2 -2
View File
@@ -59,7 +59,6 @@ match case exactly.
| `x` | [custom commands](custom-commands.md) | | `x` | [custom commands](custom-commands.md) |
| `L` | sign in again (shown when a login has expired) | | `L` | sign in again (shown when a login has expired) |
| `U` | a newer lazysecrets: its changelog, and update in place (restart to use it); or look for one | | `U` | a newer lazysecrets: its changelog, and update in place (restart to use it); or look for one |
| `E` | edit your [config](config.md), like `e` opens a file (made if it isn't there yet; restart to use it) |
A revealed value is shown as it reads best: JSON indented, and PEM A revealed value is shown as it reads best: JSON indented, and PEM
certificates as their subject, issuer, dates, names and thumbprint, with certificates as their subject, issuer, dates, names and thumbprint, with
@@ -123,7 +122,8 @@ Secrets. Going into it (`l`, or `4`) keeps it on that; there `j`/`k`,
it's hidden. it's hidden.
On top, the status pane (`0`) shows the version, a newer release and how On top, the status pane (`0`) shows the version, a newer release and how
your accounts are doing. Focused, the right pane lists more under the your accounts are doing; `e` there opens your [config](config.md) in your
editor, as in lazygit. Focused, the right pane lists more under the
logo (when there's room): the version and updates (`U`), each account logo (when there's room): the version and updates (`U`), each account
(`L` on one that needs it signs in), what's expiring (`enter` goes to (`L` on one that needs it signs in), what's expiring (`enter` goes to
it), this session's errors, newest first (what a pane showed, or the it), this session's errors, newest first (what a pane showed, or the
-2
View File
@@ -965,8 +965,6 @@ func (m *Model) handleKey(msg tea.KeyPressMsg) (cmd tea.Cmd, quit bool) {
return m.refresh(), false return m.refresh(), false
case key.Matches(msg, k.Update): case key.Matches(msg, k.Update):
return m.update(), false return m.update(), false
case key.Matches(msg, k.EditConfig):
return m.editConfig(), false
} }
return nil, false return nil, false
} }
+4 -2
View File
@@ -17,7 +17,7 @@ import (
"git.kebcloud.no/keb/lazysecrets/internal/update" "git.kebcloud.no/keb/lazysecrets/internal/update"
) )
// Opening a repo's file (e): SOPS files with sops edit, which decrypts // Opening a repo's file (e), or on the status pane the config: SOPS files with sops edit, which decrypts
// them for the editor and encrypts them again after. Other files, inside // them for the editor and encrypts them again after. Other files, inside
// nvim: in that nvim, once lazysecrets has quit; else in $VISUAL or // nvim: in that nvim, once lazysecrets has quit; else in $VISUAL or
// $EDITOR, coming back after; else in the system's default app. On a // $EDITOR, coming back after; else in the system's default app. On a
@@ -47,6 +47,8 @@ func (m *Model) edit() tea.Cmd {
var c provider.Container var c provider.Container
var o *provider.Object var o *provider.Object
switch m.focus { switch m.focus {
case paneStatus: // like lazygit's status panel
return m.editConfig()
case paneSources: case paneSources:
m.flash = warning("select a file to open it") m.flash = warning("select a file to open it")
return nil return nil
@@ -100,7 +102,7 @@ func (m *Model) edit() tea.Cmd {
const configHeader = "# lazysecrets config: " + update.DefaultRepo + "/src/branch/main/docs/config.md\n" const configHeader = "# lazysecrets config: " + update.DefaultRepo + "/src/branch/main/docs/config.md\n"
// editConfig opens the user's config file (the first of them), as e // editConfig opens the user's config file (the first of them), as e
// opens a file, making it if it isn't there yet. // opens a file, making it if it isn't there yet: e on the status pane.
func (m *Model) editConfig() tea.Cmd { func (m *Model) editConfig() tea.Cmd {
if len(m.configFiles) == 0 { if len(m.configFiles) == 0 {
m.flash = warning("no config file") m.flash = warning("no config file")
+2 -1
View File
@@ -54,7 +54,8 @@ func (c contextHelp) ShortHelp() []key.Binding {
switch { switch {
case c.focus == paneStatus: case c.focus == paneStatus:
details := key.NewBinding(key.WithKeys(k.Enter.Keys()...), key.WithHelp(k.Enter.Help().Key, "details")) details := key.NewBinding(key.WithKeys(k.Enter.Keys()...), key.WithHelp(k.Enter.Help().Key, "details"))
return append(b, details, k.Update, k.Help, k.Quit) config := key.NewBinding(key.WithKeys(k.Edit.Keys()...), key.WithHelp(k.Edit.Help().Key, "edit config"))
return append(b, details, config, k.Update, k.Help, k.Quit)
case c.focus == paneDetail && c.account && c.of == paneStatus: case c.focus == paneDetail && c.account && c.of == paneStatus:
open := key.NewBinding(key.WithKeys(k.Open.Keys()...), key.WithHelp(k.Open.Help().Key, "open link")) open := key.NewBinding(key.WithKeys(k.Open.Keys()...), key.WithHelp(k.Open.Help().Key, "open link"))
return append(b, k.Up, k.Down, copyField, open, k.Edit, k.Filter, k.Help, k.Quit) return append(b, k.Up, k.Down, copyField, open, k.Edit, k.Filter, k.Help, k.Quit)
+12 -2
View File
@@ -350,8 +350,9 @@ func TestEditConfig(t *testing.T) {
d := newDriverOpts(t, Options{ConfigFiles: []string{path}, OpenURL: func(u string) error { opened = append(opened, u); return nil }}) d := newDriverOpts(t, Options{ConfigFiles: []string{path}, OpenURL: func(u string) error { opened = append(opened, u); return nil }})
d.m.getenv = func(string) string { return "" } d.m.getenv = func(string) string { return "" }
// No editor: the system's default app, on a config made for it. // e on the status pane. No editor: the system's default app, on a
d.keys("E") // config made for it.
d.keys("0", "e")
if !slices.Equal(opened, []string{path}) { if !slices.Equal(opened, []string{path}) {
t.Errorf("opened %q", opened) t.Errorf("opened %q", opened)
} }
@@ -382,3 +383,12 @@ func TestEditConfig(t *testing.T) {
t.Errorf("flash %q", d.m.flash.text) t.Errorf("flash %q", d.m.flash.text)
} }
} }
func TestStatusFooterEditsConfig(t *testing.T) {
t.Parallel()
d := newDriver(t)
d.keys("0")
if !strings.Contains(d.screen(), "e edit config") {
t.Errorf("no edit config in the footer:\n%s", d.screen())
}
}
+3 -4
View File
@@ -48,7 +48,7 @@ type Map struct {
SplitLeft, SplitRight, SplitReset key.Binding SplitLeft, SplitRight, SplitReset key.Binding
Taller, Shorter, ZoomIn, ZoomOut key.Binding Taller, Shorter, ZoomIn, ZoomOut key.Binding
Login, Refresh, Help, Quit key.Binding Login, Refresh, Help, Quit key.Binding
Update, EditConfig key.Binding Update key.Binding
// filter // filter
AcceptFilter, ClearFilter key.Binding AcceptFilter, ClearFilter key.Binding
FilterUp, FilterDown key.Binding FilterUp, FilterDown key.Binding
@@ -114,7 +114,7 @@ func (m *Map) Actions() []Action {
a(Lists, "copyReference", &m.CopyRef, "copy reference", "secrets", "Copy the reference to use in config, e.g. @Microsoft.KeyVault(SecretUri=…) or {{secrets/scope/key}}."), a(Lists, "copyReference", &m.CopyRef, "copy reference", "secrets", "Copy the reference to use in config, e.g. @Microsoft.KeyVault(SecretUri=…) or {{secrets/scope/key}}."),
a(Lists, "versions", &m.Versions, "versions", "secrets", "List the versions of the selected item (Azure). Reveal, copy and share then act on the selected version."), a(Lists, "versions", &m.Versions, "versions", "secrets", "List the versions of the selected item (Azure). Reveal, copy and share then act on the selected version."),
a(Lists, "openInBrowser", &m.Open, "open in browser", "secrets", "Open the selected subscription, vault or item in the browser (Azure portal or Databricks workspace)."), a(Lists, "openInBrowser", &m.Open, "open in browser", "secrets", "Open the selected subscription, vault or item in the browser (Azure portal or Databricks workspace)."),
a(Lists, "edit", &m.Edit, "open file", "secrets", "Open the selected SOPS or .env file, or the file of the selected secret at its line. SOPS files open with sops edit. Others open in the nvim lazysecrets runs in (quitting lazysecrets), else in $VISUAL or $EDITOR, else in the system's default app."), a(Lists, "edit", &m.Edit, "open file", "secrets", "Open the selected SOPS or .env file, or the file of the selected secret at its line; on the status pane, your config (made if it isn't there yet). SOPS files open with sops edit. Others open in the nvim lazysecrets runs in (quitting lazysecrets), else in $VISUAL or $EDITOR, else in the system's default app."),
a(Lists, "share", &m.Share, "copy link", "share", "Copy a lazysecrets:// link to the selected subscription, workspace, vault, scope or secret, for sharing. From the versions view the link is pinned to that version."), a(Lists, "share", &m.Share, "copy link", "share", "Copy a lazysecrets:// link to the selected subscription, workspace, vault, scope or secret, for sharing. From the versions view the link is pinned to that version."),
a(Lists, "shareMenu", &m.ShareMenu, "share", "share", "Choose what to share: the lazysecrets link, the plain Key Vault URL, the config reference, or the web page."), a(Lists, "shareMenu", &m.ShareMenu, "share", "share", "Choose what to share: the lazysecrets link, the plain Key Vault URL, the config reference, or the web page."),
@@ -131,7 +131,6 @@ func (m *Map) Actions() []Action {
a(Lists, "refresh", &m.Refresh, "refresh", "other", "Reload the focused pane, bypassing the cache."), a(Lists, "refresh", &m.Refresh, "refresh", "other", "Reload the focused pane, bypassing the cache."),
a(Lists, "login", &m.Login, "sign in", "other", "Sign in again (az login or databricks auth login) when a login has expired."), a(Lists, "login", &m.Login, "sign in", "other", "Sign in again (az login or databricks auth login) when a login has expired."),
a(Lists, "update", &m.Update, "update", "other", "Check for a newer lazysecrets: its changelog, and update to it."), a(Lists, "update", &m.Update, "update", "other", "Check for a newer lazysecrets: its changelog, and update to it."),
a(Lists, "editConfig", &m.EditConfig, "edit config", "other", "Open your config file like e opens a file: in the nvim lazysecrets runs in, else in $VISUAL or $EDITOR, else in the system's default app. Made if it doesn't exist yet. Changes apply when lazysecrets starts again."),
a(Lists, "help", &m.Help, "help", "other", "Show this list. Also works in Go to while browsing, and in menus, showing their keys first."), a(Lists, "help", &m.Help, "help", "other", "Show this list. Also works in Go to while browsing, and in menus, showing their keys first."),
a(Lists, "quit", &m.Quit, "quit", "other", "Quit lazysecrets. ctrl+c always quits too."), a(Lists, "quit", &m.Quit, "quit", "other", "Quit lazysecrets. ctrl+c always quits too."),
@@ -195,7 +194,7 @@ var defaults = map[string][]string{
"lists.dividerLeft": {"ctrl+left", "<"}, "lists.dividerRight": {"ctrl+right", ">"}, "lists.dividerLeft": {"ctrl+left", "<"}, "lists.dividerRight": {"ctrl+right", ">"},
"lists.taller": {"ctrl+down", "}"}, "lists.shorter": {"ctrl+up", "{"}, "lists.taller": {"ctrl+down", "}"}, "lists.shorter": {"ctrl+up", "{"},
"lists.zoomIn": {"+"}, "lists.zoomOut": {"_"}, "lists.resetLayout": {"="}, "lists.zoomIn": {"+"}, "lists.zoomOut": {"_"}, "lists.resetLayout": {"="},
"lists.refresh": {"r"}, "lists.login": {"L"}, "lists.update": {"U"}, "lists.editConfig": {"E"}, "lists.help": {"?"}, "lists.quit": {"q"}, "lists.refresh": {"r"}, "lists.login": {"L"}, "lists.update": {"U"}, "lists.help": {"?"}, "lists.quit": {"q"},
"filter.down": {"ctrl+j", "ctrl+n", "down"}, "filter.up": {"ctrl+k", "ctrl+p", "up"}, "filter.down": {"ctrl+j", "ctrl+n", "down"}, "filter.up": {"ctrl+k", "ctrl+p", "up"},
"filter.copyValue": {"ctrl+y"}, "filter.reveal": {"ctrl+r"}, "filter.star": {"ctrl+f"}, "filter.copyValue": {"ctrl+y"}, "filter.reveal": {"ctrl+r"}, "filter.star": {"ctrl+f"},