2021-01-23 12:29:14 +00:00
|
|
|
package fzf
|
|
|
|
|
|
|
|
import (
|
2021-02-07 16:48:23 +00:00
|
|
|
"fmt"
|
2021-01-23 14:24:08 +00:00
|
|
|
"os"
|
2021-02-10 20:30:03 +00:00
|
|
|
"path/filepath"
|
2021-01-23 12:29:14 +00:00
|
|
|
|
2021-02-10 19:53:25 +00:00
|
|
|
"github.com/mickael-menu/zk/adapter/term"
|
2021-01-23 12:29:14 +00:00
|
|
|
"github.com/mickael-menu/zk/core/note"
|
|
|
|
"github.com/mickael-menu/zk/core/style"
|
2021-02-07 16:48:23 +00:00
|
|
|
"github.com/mickael-menu/zk/core/zk"
|
2021-01-23 14:24:08 +00:00
|
|
|
"github.com/mickael-menu/zk/util/opt"
|
2021-01-23 12:29:14 +00:00
|
|
|
stringsutil "github.com/mickael-menu/zk/util/strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
// NoteFinder wraps a note.Finder and filters its result interactively using fzf.
|
|
|
|
type NoteFinder struct {
|
2021-02-10 19:53:25 +00:00
|
|
|
opts NoteFinderOpts
|
|
|
|
finder note.Finder
|
|
|
|
terminal *term.Terminal
|
2021-01-23 12:29:14 +00:00
|
|
|
}
|
|
|
|
|
2021-02-10 20:30:03 +00:00
|
|
|
// NoteFinderOpts holds the configuration for the fzf notes finder.
|
|
|
|
//
|
2021-02-15 21:44:31 +00:00
|
|
|
// The absolute path to the notebook (BasePath) and the working directory
|
2021-02-10 20:30:03 +00:00
|
|
|
// (CurrentPath) are used to make the path of each note relative to the working
|
|
|
|
// directory.
|
2021-02-07 16:48:23 +00:00
|
|
|
type NoteFinderOpts struct {
|
|
|
|
// Indicates whether fzf is opened for every query, even if empty.
|
|
|
|
AlwaysFilter bool
|
2021-02-11 20:31:47 +00:00
|
|
|
// Preview command to run when selecting a note.
|
|
|
|
PreviewCmd opt.String
|
2021-02-07 16:48:23 +00:00
|
|
|
// When non nil, a "create new note from query" binding will be added to
|
|
|
|
// fzf to create a note in this directory.
|
|
|
|
NewNoteDir *zk.Dir
|
2021-02-15 21:44:31 +00:00
|
|
|
// Absolute path to the notebook.
|
2021-02-10 20:30:03 +00:00
|
|
|
BasePath string
|
|
|
|
// Path to the working directory.
|
|
|
|
CurrentPath string
|
2021-02-07 16:48:23 +00:00
|
|
|
}
|
|
|
|
|
2021-02-10 19:53:25 +00:00
|
|
|
func NewNoteFinder(opts NoteFinderOpts, finder note.Finder, terminal *term.Terminal) *NoteFinder {
|
2021-02-07 16:48:23 +00:00
|
|
|
return &NoteFinder{
|
2021-02-10 19:53:25 +00:00
|
|
|
opts: opts,
|
|
|
|
finder: finder,
|
|
|
|
terminal: terminal,
|
2021-02-07 16:48:23 +00:00
|
|
|
}
|
2021-01-23 12:29:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (f *NoteFinder) Find(opts note.FinderOpts) ([]note.Match, error) {
|
2021-02-10 20:30:03 +00:00
|
|
|
selectedMatches := make([]note.Match, 0)
|
2021-01-23 12:29:14 +00:00
|
|
|
matches, err := f.finder.Find(opts)
|
2021-02-10 20:30:03 +00:00
|
|
|
relPaths := []string{}
|
2021-01-23 12:29:14 +00:00
|
|
|
|
2021-03-13 14:31:05 +00:00
|
|
|
if !opts.Interactive || !f.terminal.IsInteractive() || err != nil || (!f.opts.AlwaysFilter && len(matches) == 0) {
|
2021-01-23 12:29:14 +00:00
|
|
|
return matches, err
|
|
|
|
}
|
|
|
|
|
2021-02-10 20:30:03 +00:00
|
|
|
for _, match := range matches {
|
|
|
|
path, err := filepath.Rel(f.opts.CurrentPath, filepath.Join(f.opts.BasePath, match.Path))
|
|
|
|
if err != nil {
|
|
|
|
return selectedMatches, err
|
|
|
|
}
|
|
|
|
relPaths = append(relPaths, path)
|
|
|
|
}
|
2021-01-23 12:29:14 +00:00
|
|
|
|
2021-01-23 14:24:08 +00:00
|
|
|
zkBin, err := os.Executable()
|
|
|
|
if err != nil {
|
|
|
|
return selectedMatches, err
|
|
|
|
}
|
|
|
|
|
2021-02-07 16:48:23 +00:00
|
|
|
bindings := []Binding{}
|
|
|
|
|
|
|
|
if dir := f.opts.NewNoteDir; dir != nil {
|
|
|
|
suffix := ""
|
|
|
|
if dir.Name != "" {
|
|
|
|
suffix = " in " + dir.Name + "/"
|
|
|
|
}
|
|
|
|
|
|
|
|
bindings = append(bindings, Binding{
|
|
|
|
Keys: "Ctrl-N",
|
|
|
|
Description: "create a note with the query as title" + suffix,
|
|
|
|
Action: fmt.Sprintf("abort+execute(%s new %s --title {q} < /dev/tty > /dev/tty)", zkBin, dir.Path),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-01-23 14:24:08 +00:00
|
|
|
fzf, err := New(Opts{
|
2021-02-21 09:38:51 +00:00
|
|
|
PreviewCmd: f.opts.PreviewCmd.OrString("cat {-1}").NonEmpty(),
|
2021-01-23 14:24:08 +00:00
|
|
|
Padding: 2,
|
2021-02-11 20:31:47 +00:00
|
|
|
Bindings: bindings,
|
2021-01-23 12:29:14 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return selectedMatches, err
|
|
|
|
}
|
|
|
|
|
2021-02-10 20:30:03 +00:00
|
|
|
for i, match := range matches {
|
2021-02-21 09:38:51 +00:00
|
|
|
title := match.Title
|
|
|
|
if title == "" {
|
|
|
|
title = relPaths[i]
|
|
|
|
}
|
2021-01-23 14:24:08 +00:00
|
|
|
fzf.Add([]string{
|
2021-02-21 09:38:51 +00:00
|
|
|
f.terminal.MustStyle(title, style.RuleYellow),
|
|
|
|
f.terminal.MustStyle(stringsutil.JoinLines(match.Body), style.RuleUnderstate),
|
|
|
|
f.terminal.MustStyle(relPaths[i], style.RuleUnderstate),
|
2021-01-23 14:24:08 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
selection, err := fzf.Selection()
|
|
|
|
if err != nil {
|
|
|
|
return selectedMatches, err
|
|
|
|
}
|
|
|
|
|
2021-01-23 12:29:14 +00:00
|
|
|
for _, s := range selection {
|
2021-02-21 09:38:51 +00:00
|
|
|
path := s[len(s)-1]
|
2021-02-10 20:30:03 +00:00
|
|
|
for i, m := range matches {
|
|
|
|
if relPaths[i] == path {
|
2021-01-23 12:29:14 +00:00
|
|
|
selectedMatches = append(selectedMatches, m)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return selectedMatches, nil
|
|
|
|
}
|