mirror of https://github.com/mickael-menu/zk
Filter notes interactively with fzf
parent
5c59d7946b
commit
fe6b062309
@ -0,0 +1,73 @@
|
|||||||
|
package fzf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mickael-menu/zk/core/note"
|
||||||
|
"github.com/mickael-menu/zk/core/style"
|
||||||
|
stringsutil "github.com/mickael-menu/zk/util/strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NoteFinder wraps a note.Finder and filters its result interactively using fzf.
|
||||||
|
type NoteFinder struct {
|
||||||
|
finder note.Finder
|
||||||
|
styler style.Styler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNoteFinder(finder note.Finder, styler style.Styler) *NoteFinder {
|
||||||
|
return &NoteFinder{finder, styler}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *NoteFinder) Find(opts note.FinderOpts) ([]note.Match, error) {
|
||||||
|
isInteractive, opts := popInteractiveFilter(opts)
|
||||||
|
matches, err := f.finder.Find(opts)
|
||||||
|
|
||||||
|
if !isInteractive || err != nil {
|
||||||
|
return matches, err
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedMatches := make([]note.Match, 0)
|
||||||
|
|
||||||
|
selection, err := withFzf(func(fzf io.Writer) error {
|
||||||
|
for _, match := range matches {
|
||||||
|
fmt.Fprintf(fzf, "%v\x01 %v %v\n",
|
||||||
|
match.Path,
|
||||||
|
f.styler.MustStyle(match.Title, style.Rule("yellow")),
|
||||||
|
f.styler.MustStyle(stringsutil.JoinLines(match.Body), style.Rule("faint")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return selectedMatches, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range selection {
|
||||||
|
path := strings.Split(s, "\x01")[0]
|
||||||
|
for _, m := range matches {
|
||||||
|
if m.Path == path {
|
||||||
|
selectedMatches = append(selectedMatches, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectedMatches, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func popInteractiveFilter(opts note.FinderOpts) (bool, note.FinderOpts) {
|
||||||
|
isInteractive := false
|
||||||
|
filters := make([]note.Filter, 0)
|
||||||
|
|
||||||
|
for _, filter := range opts.Filters {
|
||||||
|
if f, ok := filter.(note.InteractiveFilter); ok {
|
||||||
|
isInteractive = bool(f)
|
||||||
|
} else {
|
||||||
|
filters = append(filters, filter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.Filters = filters
|
||||||
|
return isInteractive, opts
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package fzf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"github.com/mickael-menu/zk/util/strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func withFzf(callback func(fzf io.Writer) error) ([]string, error) {
|
||||||
|
zkBin, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return []string{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(
|
||||||
|
"fzf",
|
||||||
|
"--delimiter", "\x01",
|
||||||
|
"--tiebreak", "begin",
|
||||||
|
"--ansi",
|
||||||
|
"--exact",
|
||||||
|
"--height", "100%",
|
||||||
|
// FIXME: Use it to create a new note? Like notational velocity
|
||||||
|
// "--print-query",
|
||||||
|
// Make sure the path and titles are always visible
|
||||||
|
"--no-hscroll",
|
||||||
|
"--tabstop", "4",
|
||||||
|
// Don't highlight search terms
|
||||||
|
"--color", "hl:-1,hl+:-1",
|
||||||
|
// "--preview", `bat -p --theme Nord --color always {1}`,
|
||||||
|
"--preview", zkBin+" list -f {{raw-content}} {1}",
|
||||||
|
"--preview-window", "noborder:wrap",
|
||||||
|
)
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
w, err := cmd.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
return []string{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var callbackErr error
|
||||||
|
go func() {
|
||||||
|
callbackErr = callback(w)
|
||||||
|
w.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if callbackErr != nil {
|
||||||
|
return []string{}, callbackErr
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return []string{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.SplitLines(string(output)), nil
|
||||||
|
}
|
@ -1,46 +0,0 @@
|
|||||||
package note
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/mickael-menu/zk/core/style"
|
|
||||||
"github.com/mickael-menu/zk/core/templ"
|
|
||||||
"github.com/mickael-menu/zk/util/opt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ListOpts struct {
|
|
||||||
Format opt.String
|
|
||||||
FinderOpts
|
|
||||||
}
|
|
||||||
|
|
||||||
type ListDeps struct {
|
|
||||||
BasePath string
|
|
||||||
Finder Finder
|
|
||||||
Templates templ.Loader
|
|
||||||
Styler style.Styler
|
|
||||||
}
|
|
||||||
|
|
||||||
// List finds notes matching given criteria and formats them according to user
|
|
||||||
// preference.
|
|
||||||
func List(opts ListOpts, deps ListDeps, out io.Writer) (int, error) {
|
|
||||||
wd, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
formatter, err := NewFormatter(deps.BasePath, wd, opts.Format, deps.Templates, deps.Styler)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return deps.Finder.Find(opts.FinderOpts, func(note Match) error {
|
|
||||||
ft, err := formatter.Format(note)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = fmt.Fprintln(out, ft)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
|
Loading…
Reference in New Issue