pass fzf query or cmd match to editor environment

pull/361/head
khimaros 7 months ago
parent 0973f9929d
commit 9108f6347e

@ -12,3 +12,37 @@ You can customize which editor to use either from the [configuration file](confi
``` ```
3. `VISUAL` environment variable 3. `VISUAL` environment variable
4. `EDITOR` environment variable 4. `EDITOR` environment variable
When invoking the editor, `zk` will provide the following environment to the editor:
`ZK_QUERY`: the query interactively provided to `fzf` or otherwise the value passed with the `-m` flag
## Advanced editor usage
It is also possible to create a custom script for your editor, for example to jump to a specific instance of a search term.
Consider the following script:
```bash
#!/bin/bash
set -eou pipefail
grep -nEv '^[[:space:]]*$' "$1" \
| fzf \
--tiebreak=begin \
--exact \
--tabstop=4 \
--height=100% \
--layout=reverse \
--no-hscroll \
--color=hl:-1,hl+:-1 \
--preview-window=wrap \
--delimiter=':' \
--with-nth=2.. \
--query="${ZK_QUERY}" \
| sed 's/:.*$//' \
| xargs -o -I {} vim +{} "$1"
```
This script could then be configured as the `editor` in `.zk/config.yaml` to open a second `fzf` instance prepopulated with the query which was previously entered into `zk`.

@ -15,11 +15,12 @@ import (
// Editor represents an external editor able to edit the notes. // Editor represents an external editor able to edit the notes.
type Editor struct { type Editor struct {
editor string editor string
query string
} }
// NewEditor creates a new Editor from the given editor user setting or the // NewEditor creates a new Editor from the given editor user setting or the
// matching environment variables. // matching environment variables.
func NewEditor(editor opt.String) (*Editor, error) { func NewEditor(editor opt.String, query string) (*Editor, error) {
editor = osutil.GetOptEnv("ZK_EDITOR"). editor = osutil.GetOptEnv("ZK_EDITOR").
Or(editor). Or(editor).
Or(osutil.GetOptEnv("VISUAL")). Or(osutil.GetOptEnv("VISUAL")).
@ -29,7 +30,7 @@ func NewEditor(editor opt.String) (*Editor, error) {
return nil, fmt.Errorf("no editor set in config") return nil, fmt.Errorf("no editor set in config")
} }
return &Editor{editor.Unwrap()}, nil return &Editor{editor.Unwrap(), query}, nil
} }
// Open launches the editor with the notes at given paths. // Open launches the editor with the notes at given paths.
@ -38,6 +39,7 @@ func (e *Editor) Open(paths ...string) error {
// initial note content to `zk new`. Without this, Vim doesn't work // initial note content to `zk new`. Without this, Vim doesn't work
// properly in this case. // properly in this case.
// See https://github.com/zk-org/zk/issues/4 // See https://github.com/zk-org/zk/issues/4
os.Setenv("ZK_QUERY", e.query)
cmd := executil.CommandFromString(e.editor + " " + shellquote.Join(paths...) + " </dev/tty") cmd := executil.CommandFromString(e.editor + " " + shellquote.Join(paths...) + " </dev/tty")
cmd.Stdin = os.Stdin cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout

@ -13,7 +13,7 @@ func TestEditorUsesZkEditorFirst(t *testing.T) {
os.Setenv("VISUAL", "visual") os.Setenv("VISUAL", "visual")
os.Setenv("EDITOR", "editor") os.Setenv("EDITOR", "editor")
editor, err := NewEditor(opt.NewString("custom-editor")) editor, err := NewEditor(opt.NewString("custom-editor"), "")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, editor.editor, "zk-editor") assert.Equal(t, editor.editor, "zk-editor")
} }
@ -23,7 +23,7 @@ func TestEditorFallsbackOnUserConfig(t *testing.T) {
os.Setenv("VISUAL", "visual") os.Setenv("VISUAL", "visual")
os.Setenv("EDITOR", "editor") os.Setenv("EDITOR", "editor")
editor, err := NewEditor(opt.NewString("custom-editor")) editor, err := NewEditor(opt.NewString("custom-editor"), "")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, editor.editor, "custom-editor") assert.Equal(t, editor.editor, "custom-editor")
} }
@ -33,7 +33,7 @@ func TestEditorFallsbackOnVisual(t *testing.T) {
os.Setenv("VISUAL", "visual") os.Setenv("VISUAL", "visual")
os.Setenv("EDITOR", "editor") os.Setenv("EDITOR", "editor")
editor, err := NewEditor(opt.NullString) editor, err := NewEditor(opt.NullString, "")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, editor.editor, "visual") assert.Equal(t, editor.editor, "visual")
} }
@ -43,7 +43,7 @@ func TestEditorFallsbackOnEditor(t *testing.T) {
os.Unsetenv("VISUAL") os.Unsetenv("VISUAL")
os.Setenv("EDITOR", "editor") os.Setenv("EDITOR", "editor")
editor, err := NewEditor(opt.NullString) editor, err := NewEditor(opt.NullString, "")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, editor.editor, "editor") assert.Equal(t, editor.editor, "editor")
} }
@ -53,7 +53,7 @@ func TestEditorFailsWhenUnset(t *testing.T) {
os.Unsetenv("VISUAL") os.Unsetenv("VISUAL")
os.Unsetenv("EDITOR") os.Unsetenv("EDITOR")
editor, err := NewEditor(opt.NullString) editor, err := NewEditor(opt.NullString, "")
assert.Err(t, err, "no editor set in config") assert.Err(t, err, "no editor set in config")
assert.Nil(t, editor) assert.Nil(t, editor)
} }

@ -62,6 +62,8 @@ type Fzf struct {
cmd *exec.Cmd cmd *exec.Cmd
pipe io.WriteCloser pipe io.WriteCloser
closeOnce sync.Once closeOnce sync.Once
Query string
} }
// New runs a fzf instance. // New runs a fzf instance.
@ -162,7 +164,8 @@ func New(opts Opts) (*Fzf, error) {
func (f *Fzf) parseSelection(output []byte) { func (f *Fzf) parseSelection(output []byte) {
f.selection = make([][]string, 0) f.selection = make([][]string, 0)
lines := stringsutil.SplitLines(string(output)) lines := stringsutil.SplitLines(string(output))
for _, line := range lines { f.Query = lines[0]
for _, line := range lines[1:] {
fields := strings.Split(line, f.opts.Delimiter) fields := strings.Split(line, f.opts.Delimiter)
// Trim padding // Trim padding
for i, field := range fields { for i, field := range fields {

@ -19,6 +19,7 @@ type NoteFilter struct {
fs core.FileStorage fs core.FileStorage
terminal *term.Terminal terminal *term.Terminal
templateLoader core.TemplateLoader templateLoader core.TemplateLoader
Query string
} }
// NoteFilterOpts holds the configuration for the fzf notes filtering. // NoteFilterOpts holds the configuration for the fzf notes filtering.
@ -153,6 +154,8 @@ func (f *NoteFilter) Apply(notes []core.ContextualNote) ([]core.ContextualNote,
return selectedNotes, err return selectedNotes, err
} }
f.Query = fzf.Query
for _, s := range selection { for _, s := range selection {
path := s[len(s)-1] path := s[len(s)-1]
for i, m := range notes { for i, m := range notes {
@ -175,6 +178,7 @@ var defaultOptions = strings.Join([]string{
"--height 100%", // Height of the list relative to the terminal window "--height 100%", // Height of the list relative to the terminal window
"--layout reverse", // Display the input field at the top "--layout reverse", // Display the input field at the top
"--no-hscroll", // Make sure the path and titles are always visible "--no-hscroll", // Make sure the path and titles are always visible
"--print-query", // Print the query as the first line
"--color hl:-1,hl+:-1", // Don't highlight search terms "--color hl:-1,hl+:-1", // Don't highlight search terms
"--preview-window wrap", // Enable line wrapping in the preview window "--preview-window wrap", // Enable line wrapping in the preview window
}, " ") }, " ")

@ -243,6 +243,7 @@ func TestFormatDateHelper(t *testing.T) {
func TestFormatDateHelperElapsedYear(t *testing.T) { func TestFormatDateHelperElapsedYear(t *testing.T) {
year := time.Now().UTC().Year() - 14 year := time.Now().UTC().Year() - 14
context := map[string]interface{}{"now": time.Date(year, 11, 17, 20, 34, 58, 651387237, time.UTC)} context := map[string]interface{}{"now": time.Date(year, 11, 17, 20, 34, 58, 651387237, time.UTC)}
// FIXME: this test will break once per year
testString(t, "{{format-date now 'elapsed'}}", context, "14 years ago") testString(t, "{{format-date now 'elapsed'}}", context, "14 years ago")
} }

@ -65,7 +65,12 @@ func (cmd *Edit) Run(container *cli.Container) error {
paths = append(paths, absPath) paths = append(paths, absPath)
} }
editor, err := container.NewNoteEditor(notebook) query := filter.Query
if query == "" && len(cmd.Match) > 0 {
query = cmd.Match[0]
}
editor, err := container.NewNoteEditor(notebook, query)
if err != nil { if err != nil {
return err return err
} }

@ -96,7 +96,7 @@ func (cmd *New) Run(container *cli.Container) error {
fmt.Printf("%+v\n", path) fmt.Printf("%+v\n", path)
return nil return nil
} else { } else {
editor, err := container.NewNoteEditor(notebook) editor, err := container.NewNoteEditor(notebook, "")
if err != nil { if err != nil {
return err return err
} }

@ -236,8 +236,8 @@ func (c *Container) NewNoteFilter(opts fzf.NoteFilterOpts) *fzf.NoteFilter {
return fzf.NewNoteFilter(opts, c.FS, c.Terminal, c.TemplateLoader) return fzf.NewNoteFilter(opts, c.FS, c.Terminal, c.TemplateLoader)
} }
func (c *Container) NewNoteEditor(notebook *core.Notebook) (*editor.Editor, error) { func (c *Container) NewNoteEditor(notebook *core.Notebook, query string) (*editor.Editor, error) {
return editor.NewEditor(notebook.Config.Tool.Editor) return editor.NewEditor(notebook.Config.Tool.Editor, query)
} }
// Paginate creates an auto-closing io.Writer which will be automatically // Paginate creates an auto-closing io.Writer which will be automatically

Loading…
Cancel
Save