diff --git a/adapter/fzf/finder.go b/adapter/fzf/finder.go index 19a2048..f95615f 100644 --- a/adapter/fzf/finder.go +++ b/adapter/fzf/finder.go @@ -28,6 +28,8 @@ type NoteFinder struct { type NoteFinderOpts struct { // Indicates whether fzf is opened for every query, even if empty. AlwaysFilter bool + // Preview command to run when selecting a note. + PreviewCmd opt.String // 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 @@ -84,10 +86,9 @@ func (f *NoteFinder) Find(opts note.FinderOpts) ([]note.Match, error) { } fzf, err := New(Opts{ - // PreviewCmd: opt.NewString("bat -p --theme Nord --color always {1}"), - PreviewCmd: opt.NewString(zkBin + " list -f {{raw-content}} {1}"), + PreviewCmd: f.opts.PreviewCmd.OrString("cat {1}").NonEmpty(), Padding: 2, - Bindings: bindings, + Bindings: bindings, }) if err != nil { return selectedMatches, err diff --git a/cmd/edit.go b/cmd/edit.go index 26ea9e5..651d2ba 100644 --- a/cmd/edit.go +++ b/cmd/edit.go @@ -45,6 +45,7 @@ func (cmd *Edit) Run(container *Container) error { err = db.WithTransaction(func(tx sqlite.Transaction) error { finder := container.NoteFinder(tx, fzf.NoteFinderOpts{ AlwaysFilter: true, + PreviewCmd: zk.Config.Fzf.Preview, NewNoteDir: cmd.newNoteDir(zk), BasePath: zk.Path, CurrentPath: wd, diff --git a/cmd/list.go b/cmd/list.go index 0aff63c..b36b510 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -61,6 +61,7 @@ func (cmd *List) Run(container *Container) error { err = db.WithTransaction(func(tx sqlite.Transaction) error { finder := container.NoteFinder(tx, fzf.NoteFinderOpts{ AlwaysFilter: false, + PreviewCmd: zk.Config.Fzf.Preview, BasePath: zk.Path, CurrentPath: wd, }) diff --git a/core/note/create.go b/core/note/create.go index e7064bf..5c41ff1 100644 --- a/core/note/create.go +++ b/core/note/create.go @@ -100,7 +100,7 @@ func create( deps createDeps, ) (*createdNote, error) { context := renderContext{ - Title: opts.Title.OrDefault(opts.Dir.Config.DefaultTitle), + Title: opts.Title.OrString(opts.Dir.Config.DefaultTitle).Unwrap(), Content: opts.Content.Unwrap(), Dir: opts.Dir.Name, Extra: opts.Dir.Config.Extra, diff --git a/core/note/format.go b/core/note/format.go index 0dffff4..a22c977 100644 --- a/core/note/format.go +++ b/core/note/format.go @@ -46,7 +46,7 @@ func NewFormatter(basePath string, currentPath string, format opt.String, templa } func resolveFormatTemplate(format opt.String) string { - templ, ok := formatTemplates[format.OrDefault("short")] + templ, ok := formatTemplates[format.OrString("short").Unwrap()] if !ok { templ = format.String() // Replace raw \n and \t by actual newlines and tabs in user format. diff --git a/core/zk/config.go b/core/zk/config.go index a3d08b4..7be2518 100644 --- a/core/zk/config.go +++ b/core/zk/config.go @@ -14,6 +14,7 @@ type Config struct { Dirs map[string]DirConfig Editor opt.String Pager opt.String + Fzf FzfConfig Aliases map[string]string } @@ -28,6 +29,11 @@ type DirConfig struct { Extra map[string]string } +// FzfConfig holds the user configuration for running fzf. +type FzfConfig struct { + Preview opt.String +} + // ConfigOverrides holds user configuration overriden values, for example fed // from CLI flags. type ConfigOverrides struct { @@ -128,7 +134,10 @@ func ParseConfig(content []byte, templatesDir string) (*Config, error) { Dirs: dirs, Editor: opt.NewNotEmptyString(tomlConf.Editor), Pager: opt.NewStringWithPtr(tomlConf.Pager), - Aliases: aliases, + Fzf: FzfConfig{ + Preview: opt.NewStringWithPtr(tomlConf.Fzf.Preview), + }, + Aliases: aliases, }, nil } @@ -190,6 +199,7 @@ type tomlConfig struct { Dirs map[string]tomlDirConfig `toml:"dir"` Editor string Pager *string + Fzf tomlFzfConfig Aliases map[string]string `toml:"alias"` } @@ -209,6 +219,10 @@ type tomlIDConfig struct { Case string } +type tomlFzfConfig struct { + Preview *string +} + func charsetFromString(charset string) Charset { switch charset { case "alphanum": diff --git a/core/zk/config_test.go b/core/zk/config_test.go index b28e2fd..32c5f3a 100644 --- a/core/zk/config_test.go +++ b/core/zk/config_test.go @@ -14,8 +14,6 @@ func TestParseDefaultConfig(t *testing.T) { assert.Nil(t, err) assert.Equal(t, conf, &Config{ - Editor: opt.NullString, - Pager: opt.NullString, DirConfig: DirConfig{ FilenameTemplate: "{{id}}", Extension: "md", @@ -29,7 +27,12 @@ func TestParseDefaultConfig(t *testing.T) { Lang: "en", Extra: make(map[string]string), }, - Dirs: make(map[string]DirConfig), + Dirs: make(map[string]DirConfig), + Editor: opt.NullString, + Pager: opt.NullString, + Fzf: FzfConfig{ + Preview: opt.NullString, + }, Aliases: make(map[string]string), }) } @@ -57,6 +60,9 @@ func TestParseComplete(t *testing.T) { length = 4 case = "lower" + [fzf] + preview = "bat {1}" + [extra] hello = "world" salut = "le monde" @@ -139,6 +145,9 @@ func TestParseComplete(t *testing.T) { }, Editor: opt.NewString("vim"), Pager: opt.NewString("less"), + Fzf: FzfConfig{ + Preview: opt.NewString("bat {1}"), + }, Aliases: map[string]string{ "ls": "zk list $@", "ed": "zk edit $@", @@ -236,12 +245,20 @@ func TestParseMergesDirConfig(t *testing.T) { }) } -func TestParsePreserveEmptyPager(t *testing.T) { - conf, err := ParseConfig([]byte(`pager = ""`), "") +// Some properties like `pager` and `fzf.preview` differentiate between not +// being set and an empty string. +func TestParsePreservePropertiesAllowingEmptyValues(t *testing.T) { + conf, err := ParseConfig([]byte(` + pager = "" + [fzf] + preview = "" + `), "") assert.Nil(t, err) assert.Equal(t, conf.Pager.IsNull(), false) assert.Equal(t, conf.Pager, opt.NewString("")) + assert.Equal(t, conf.Fzf.Preview.IsNull(), false) + assert.Equal(t, conf.Fzf.Preview, opt.NewString("")) } func TestParseIDCharset(t *testing.T) { diff --git a/util/opt/opt.go b/util/opt/opt.go index 146d607..92baca8 100644 --- a/util/opt/opt.go +++ b/util/opt/opt.go @@ -41,6 +41,15 @@ func (s String) IsEmpty() bool { return !s.IsNull() && *s.value == "" } +// NonEmpty returns a null String if the String is empty. +func (s String) NonEmpty() String { + if s.IsEmpty() { + return NullString + } else { + return s + } +} + // Or returns the receiver if it is not null, otherwise the given optional // String. func (s String) Or(other String) String { @@ -51,19 +60,23 @@ func (s String) Or(other String) String { } } -// OrDefault returns the optional String value or the given default string if +// OrString returns the optional String value or the given default string if // it is null. -func (s String) OrDefault(def string) string { +func (s String) OrString(alt string) String { if s.IsNull() { - return def + return NewString(alt) } else { - return *s.value + return s } } // Unwrap returns the optional String value or an empty String if none is set. func (s String) Unwrap() string { - return s.OrDefault("") + if s.IsNull() { + return "" + } else { + return *s.value + } } func (s String) Equal(other String) bool { @@ -72,7 +85,7 @@ func (s String) Equal(other String) bool { } func (s String) String() string { - return s.OrDefault("") + return s.Unwrap() } func (s String) MarshalJSON() ([]byte, error) {