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