diff options
| author | Paul Buetow <paul@buetow.org> | 2026-04-17 08:45:13 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-04-17 08:45:13 +0300 |
| commit | 9e2bf4af8b7b3b4ca2980aa6285482e7db0cd151 (patch) | |
| tree | 96bcd8ad2a64e6dfc9d3bedc9d063eeef1591cf0 /internal/keepass/fzf.go | |
| parent | b53e348d89046ea8de5b82c283fb56980b53cdd8 (diff) | |
feat: add internal/keepass package with full Backend implementation (tasks k4+l4+m4)
Read-write KeePass backend implementing backend.Backend:
- WalkIndexes flattens groups into 'Group/Title' virtual entries
- LoadData returns formatted Password/User/URL/Notes for text entries
or raw bytes for binary attachments
- WriteBack hook enables edit round-trip through the kdbx file
- Add/Import/ImportRecursive/Remove with atomic tmp+rename save
- Binary attachments surface as virtual 'Group/Title/filename' entries;
Add to such a path creates/replaces the attachment on the parent entry
- AtomicSave exported for reuse by cli/kdbx_store.go
- parseContent is the tolerant inverse of formatContent
- Helpers EnsureGroup, UpsertEntryByTitle, SetEntryField, SplitDescriptionPath,
SanitizeRelativePath exported so cli/kdbx_store.go avoids duplication
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/keepass/fzf.go')
| -rw-r--r-- | internal/keepass/fzf.go | 94 |
1 files changed, 94 insertions, 0 deletions
diff --git a/internal/keepass/fzf.go b/internal/keepass/fzf.go new file mode 100644 index 0000000..a30e769 --- /dev/null +++ b/internal/keepass/fzf.go @@ -0,0 +1,94 @@ +package keepass + +import ( + "context" + "sort" + "strings" + + "codeberg.org/snonux/foostore/internal/picker" + "codeberg.org/snonux/foostore/internal/store" +) + +// Fzf launches fzf and returns only the selected description. +func (b *Backend) Fzf(ctx context.Context) (string, error) { + result, err := b.FzfInteractive(ctx) + if err != nil { + return "", err + } + return result.Description, nil +} + +// FzfInteractive launches fzf with action key bindings and returns the +// selected description plus the chosen action. +func (b *Backend) FzfInteractive(ctx context.Context) (store.PickerResult, error) { + var indexes store.IndexSlice + if err := b.WalkIndexes(ctx, "", func(idx *store.Index) error { + indexes = append(indexes, idx) + return nil + }); err != nil { + return store.PickerResult{}, err + } + if len(indexes) == 0 { + return store.PickerResult{}, nil + } + + sort.Sort(indexes) + entries := buildPickerEntries(indexes) + return runFzfInteractive(ctx, entries) +} + +// buildPickerEntries converts a sorted IndexSlice into picker.Entry rows. +func buildPickerEntries(indexes store.IndexSlice) []picker.Entry { + entries := make([]picker.Entry, 0, len(indexes)) + for i, idx := range indexes { + kind := "TEXT" + if idx.IsBinary() { + kind = "BINARY" + } + hashSuffix := "" + if len(idx.Hash) >= 63 { + hashSuffix = idx.Hash[53:63] + } + entries = append(entries, picker.Entry{ + RowID: i + 1, + Description: idx.Description, + Kind: kind, + HashSuffix: hashSuffix, + }) + } + return entries +} + +// runFzfInteractive calls picker.Run and maps the chosen key to a PickerAction. +func runFzfInteractive(ctx context.Context, entries []picker.Entry) (store.PickerResult, error) { + selection, err := picker.Run(ctx, entries) + if err != nil { + return store.PickerResult{}, err + } + action, ok := parsePickerAction(selection.Key) + if !ok || selection.Description == "" { + return store.PickerResult{}, nil + } + return store.PickerResult{ + Description: selection.Description, + Action: action, + }, nil +} + +// parsePickerAction maps an fzf key string to a PickerAction. +func parsePickerAction(keyLine string) (store.PickerAction, bool) { + switch strings.TrimSpace(keyLine) { + case "", "enter": + return store.PickerSelect, true + case "ctrl-t", "alt-t": + return store.PickerCat, true + case "ctrl-y", "alt-y": + return store.PickerPaste, true + case "ctrl-o", "alt-o": + return store.PickerOpen, true + case "ctrl-e", "alt-e": + return store.PickerEdit, true + default: + return "", false + } +} |
