pass fzf query or cmd match to editor environment

pull/361/head
khimaros 6 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
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.
type Editor struct {
editor string
query string
}
// NewEditor creates a new Editor from the given editor user setting or the
// matching environment variables.
func NewEditor(editor opt.String) (*Editor, error) {
func NewEditor(editor opt.String, query string) (*Editor, error) {
editor = osutil.GetOptEnv("ZK_EDITOR").
Or(editor).
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 &Editor{editor.Unwrap()}, nil
return &Editor{editor.Unwrap(), query}, nil
}
// 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
// properly in this case.
// 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.Stdin = os.Stdin
cmd.Stdout = os.Stdout

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

@ -62,6 +62,8 @@ type Fzf struct {
cmd *exec.Cmd
pipe io.WriteCloser
closeOnce sync.Once
Query string
}
// New runs a fzf instance.
@ -162,7 +164,8 @@ func New(opts Opts) (*Fzf, error) {
func (f *Fzf) parseSelection(output []byte) {
f.selection = make([][]string, 0)
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)
// Trim padding
for i, field := range fields {

@ -19,6 +19,7 @@ type NoteFilter struct {
fs core.FileStorage
terminal *term.Terminal
templateLoader core.TemplateLoader
Query string
}
// 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
}
f.Query = fzf.Query
for _, s := range selection {
path := s[len(s)-1]
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
"--layout reverse", // Display the input field at the top
"--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
"--preview-window wrap", // Enable line wrapping in the preview window
}, " ")

@ -243,6 +243,7 @@ func TestFormatDateHelper(t *testing.T) {
func TestFormatDateHelperElapsedYear(t *testing.T) {
year := time.Now().UTC().Year() - 14
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")
}

@ -65,7 +65,12 @@ func (cmd *Edit) Run(container *cli.Container) error {
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 {
return err
}

@ -96,7 +96,7 @@ func (cmd *New) Run(container *cli.Container) error {
fmt.Printf("%+v\n", path)
return nil
} else {
editor, err := container.NewNoteEditor(notebook)
editor, err := container.NewNoteEditor(notebook, "")
if err != nil {
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)
}
func (c *Container) NewNoteEditor(notebook *core.Notebook) (*editor.Editor, error) {
return editor.NewEditor(notebook.Config.Tool.Editor)
func (c *Container) NewNoteEditor(notebook *core.Notebook, query string) (*editor.Editor, error) {
return editor.NewEditor(notebook.Config.Tool.Editor, query)
}
// Paginate creates an auto-closing io.Writer which will be automatically

Loading…
Cancel
Save