doing 2.0.18 → 2.0.22
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/Gemfile.lock +15 -5
- data/README.md +1 -1
- data/bin/doing +2 -18
- data/doing.gemspec +5 -4
- data/doing.rdoc +2 -2
- data/generate_completions.sh +3 -3
- data/lib/doing/cli_status.rb +6 -2
- data/lib/doing/completion/bash_completion.rb +185 -0
- data/lib/doing/completion/fish_completion.rb +175 -0
- data/lib/doing/completion/string.rb +17 -0
- data/lib/doing/completion/zsh_completion.rb +140 -0
- data/lib/doing/completion.rb +39 -0
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +18 -6
- data/lib/doing.rb +1 -1
- data/lib/helpers/fzf/.goreleaser.yml +119 -0
- data/lib/helpers/fzf/.rubocop.yml +28 -0
- data/lib/helpers/fzf/ADVANCED.md +565 -0
- data/lib/helpers/fzf/BUILD.md +49 -0
- data/lib/helpers/fzf/CHANGELOG.md +1193 -0
- data/lib/helpers/fzf/Dockerfile +11 -0
- data/lib/helpers/fzf/LICENSE +21 -0
- data/lib/helpers/fzf/Makefile +166 -0
- data/lib/helpers/fzf/README-VIM.md +486 -0
- data/lib/helpers/fzf/README.md +712 -0
- data/lib/helpers/fzf/bin/fzf-tmux +233 -0
- data/lib/helpers/fzf/doc/fzf.txt +512 -0
- data/lib/helpers/fzf/go.mod +17 -0
- data/lib/helpers/fzf/go.sum +31 -0
- data/lib/helpers/fzf/install +382 -0
- data/lib/helpers/fzf/install.ps1 +65 -0
- data/lib/helpers/fzf/main.go +14 -0
- data/lib/helpers/fzf/man/man1/fzf-tmux.1 +68 -0
- data/lib/helpers/fzf/man/man1/fzf.1 +1001 -0
- data/lib/helpers/fzf/plugin/fzf.vim +1048 -0
- data/lib/helpers/fzf/shell/completion.bash +381 -0
- data/lib/helpers/fzf/shell/completion.zsh +329 -0
- data/lib/helpers/fzf/shell/key-bindings.bash +96 -0
- data/lib/helpers/fzf/shell/key-bindings.fish +172 -0
- data/lib/helpers/fzf/shell/key-bindings.zsh +114 -0
- data/lib/helpers/fzf/src/LICENSE +21 -0
- data/lib/helpers/fzf/src/algo/algo.go +884 -0
- data/lib/helpers/fzf/src/algo/algo_test.go +197 -0
- data/lib/helpers/fzf/src/algo/normalize.go +492 -0
- data/lib/helpers/fzf/src/ansi.go +409 -0
- data/lib/helpers/fzf/src/ansi_test.go +427 -0
- data/lib/helpers/fzf/src/cache.go +81 -0
- data/lib/helpers/fzf/src/cache_test.go +39 -0
- data/lib/helpers/fzf/src/chunklist.go +89 -0
- data/lib/helpers/fzf/src/chunklist_test.go +80 -0
- data/lib/helpers/fzf/src/constants.go +85 -0
- data/lib/helpers/fzf/src/core.go +351 -0
- data/lib/helpers/fzf/src/history.go +96 -0
- data/lib/helpers/fzf/src/history_test.go +68 -0
- data/lib/helpers/fzf/src/item.go +44 -0
- data/lib/helpers/fzf/src/item_test.go +23 -0
- data/lib/helpers/fzf/src/matcher.go +235 -0
- data/lib/helpers/fzf/src/merger.go +120 -0
- data/lib/helpers/fzf/src/merger_test.go +88 -0
- data/lib/helpers/fzf/src/options.go +1691 -0
- data/lib/helpers/fzf/src/options_test.go +457 -0
- data/lib/helpers/fzf/src/pattern.go +425 -0
- data/lib/helpers/fzf/src/pattern_test.go +209 -0
- data/lib/helpers/fzf/src/protector/protector.go +8 -0
- data/lib/helpers/fzf/src/protector/protector_openbsd.go +10 -0
- data/lib/helpers/fzf/src/reader.go +201 -0
- data/lib/helpers/fzf/src/reader_test.go +63 -0
- data/lib/helpers/fzf/src/result.go +243 -0
- data/lib/helpers/fzf/src/result_others.go +16 -0
- data/lib/helpers/fzf/src/result_test.go +159 -0
- data/lib/helpers/fzf/src/result_x86.go +16 -0
- data/lib/helpers/fzf/src/terminal.go +2832 -0
- data/lib/helpers/fzf/src/terminal_test.go +638 -0
- data/lib/helpers/fzf/src/terminal_unix.go +26 -0
- data/lib/helpers/fzf/src/terminal_windows.go +45 -0
- data/lib/helpers/fzf/src/tokenizer.go +253 -0
- data/lib/helpers/fzf/src/tokenizer_test.go +112 -0
- data/lib/helpers/fzf/src/tui/dummy.go +46 -0
- data/lib/helpers/fzf/src/tui/light.go +987 -0
- data/lib/helpers/fzf/src/tui/light_unix.go +110 -0
- data/lib/helpers/fzf/src/tui/light_windows.go +145 -0
- data/lib/helpers/fzf/src/tui/tcell.go +721 -0
- data/lib/helpers/fzf/src/tui/tcell_test.go +392 -0
- data/lib/helpers/fzf/src/tui/ttyname_unix.go +47 -0
- data/lib/helpers/fzf/src/tui/ttyname_windows.go +14 -0
- data/lib/helpers/fzf/src/tui/tui.go +625 -0
- data/lib/helpers/fzf/src/tui/tui_test.go +20 -0
- data/lib/helpers/fzf/src/util/atomicbool.go +34 -0
- data/lib/helpers/fzf/src/util/atomicbool_test.go +17 -0
- data/lib/helpers/fzf/src/util/chars.go +198 -0
- data/lib/helpers/fzf/src/util/chars_test.go +46 -0
- data/lib/helpers/fzf/src/util/eventbox.go +96 -0
- data/lib/helpers/fzf/src/util/eventbox_test.go +61 -0
- data/lib/helpers/fzf/src/util/slab.go +12 -0
- data/lib/helpers/fzf/src/util/util.go +138 -0
- data/lib/helpers/fzf/src/util/util_test.go +40 -0
- data/lib/helpers/fzf/src/util/util_unix.go +47 -0
- data/lib/helpers/fzf/src/util/util_windows.go +83 -0
- data/lib/helpers/fzf/test/fzf.vader +175 -0
- data/lib/helpers/fzf/test/test_go.rb +2626 -0
- data/lib/helpers/fzf/uninstall +117 -0
- data/scripts/generate_bash_completions.rb +6 -12
- data/scripts/generate_fish_completions.rb +7 -16
- data/scripts/generate_zsh_completions.rb +6 -15
- metadata +144 -9
@@ -0,0 +1,638 @@
|
|
1
|
+
package fzf
|
2
|
+
|
3
|
+
import (
|
4
|
+
"bytes"
|
5
|
+
"io"
|
6
|
+
"os"
|
7
|
+
"regexp"
|
8
|
+
"strings"
|
9
|
+
"testing"
|
10
|
+
"text/template"
|
11
|
+
|
12
|
+
"github.com/junegunn/fzf/src/util"
|
13
|
+
)
|
14
|
+
|
15
|
+
func TestReplacePlaceholder(t *testing.T) {
|
16
|
+
item1 := newItem(" foo'bar \x1b[31mbaz\x1b[m")
|
17
|
+
items1 := []*Item{item1, item1}
|
18
|
+
items2 := []*Item{
|
19
|
+
newItem("foo'bar \x1b[31mbaz\x1b[m"),
|
20
|
+
newItem("foo'bar \x1b[31mbaz\x1b[m"),
|
21
|
+
newItem("FOO'BAR \x1b[31mBAZ\x1b[m")}
|
22
|
+
|
23
|
+
delim := "'"
|
24
|
+
var regex *regexp.Regexp
|
25
|
+
|
26
|
+
var result string
|
27
|
+
check := func(expected string) {
|
28
|
+
if result != expected {
|
29
|
+
t.Errorf("expected: %s, actual: %s", expected, result)
|
30
|
+
}
|
31
|
+
}
|
32
|
+
// helper function that converts template format into string and carries out the check()
|
33
|
+
checkFormat := func(format string) {
|
34
|
+
type quotes struct{ O, I, S string } // outer, inner quotes, print separator
|
35
|
+
unixStyle := quotes{`'`, `'\''`, "\n"}
|
36
|
+
windowsStyle := quotes{`^"`, `'`, "\n"}
|
37
|
+
var effectiveStyle quotes
|
38
|
+
|
39
|
+
if util.IsWindows() {
|
40
|
+
effectiveStyle = windowsStyle
|
41
|
+
} else {
|
42
|
+
effectiveStyle = unixStyle
|
43
|
+
}
|
44
|
+
|
45
|
+
expected := templateToString(format, effectiveStyle)
|
46
|
+
check(expected)
|
47
|
+
}
|
48
|
+
printsep := "\n"
|
49
|
+
|
50
|
+
/*
|
51
|
+
Test multiple placeholders and the function parameters.
|
52
|
+
*/
|
53
|
+
|
54
|
+
// {}, preserve ansi
|
55
|
+
result = replacePlaceholder("echo {}", false, Delimiter{}, printsep, false, "query", items1)
|
56
|
+
checkFormat("echo {{.O}} foo{{.I}}bar \x1b[31mbaz\x1b[m{{.O}}")
|
57
|
+
|
58
|
+
// {}, strip ansi
|
59
|
+
result = replacePlaceholder("echo {}", true, Delimiter{}, printsep, false, "query", items1)
|
60
|
+
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}")
|
61
|
+
|
62
|
+
// {}, with multiple items
|
63
|
+
result = replacePlaceholder("echo {}", true, Delimiter{}, printsep, false, "query", items2)
|
64
|
+
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}}")
|
65
|
+
|
66
|
+
// {..}, strip leading whitespaces, preserve ansi
|
67
|
+
result = replacePlaceholder("echo {..}", false, Delimiter{}, printsep, false, "query", items1)
|
68
|
+
checkFormat("echo {{.O}}foo{{.I}}bar \x1b[31mbaz\x1b[m{{.O}}")
|
69
|
+
|
70
|
+
// {..}, strip leading whitespaces, strip ansi
|
71
|
+
result = replacePlaceholder("echo {..}", true, Delimiter{}, printsep, false, "query", items1)
|
72
|
+
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}}")
|
73
|
+
|
74
|
+
// {q}
|
75
|
+
result = replacePlaceholder("echo {} {q}", true, Delimiter{}, printsep, false, "query", items1)
|
76
|
+
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}} {{.O}}query{{.O}}")
|
77
|
+
|
78
|
+
// {q}, multiple items
|
79
|
+
result = replacePlaceholder("echo {+}{q}{+}", true, Delimiter{}, printsep, false, "query 'string'", items2)
|
80
|
+
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}{{.O}}query {{.I}}string{{.I}}{{.O}}{{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}")
|
81
|
+
|
82
|
+
result = replacePlaceholder("echo {}{q}{}", true, Delimiter{}, printsep, false, "query 'string'", items2)
|
83
|
+
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}}{{.O}}query {{.I}}string{{.I}}{{.O}}{{.O}}foo{{.I}}bar baz{{.O}}")
|
84
|
+
|
85
|
+
result = replacePlaceholder("echo {1}/{2}/{2,1}/{-1}/{-2}/{}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, false, "query", items1)
|
86
|
+
checkFormat("echo {{.O}}foo{{.I}}bar{{.O}}/{{.O}}baz{{.O}}/{{.O}}bazfoo{{.I}}bar{{.O}}/{{.O}}baz{{.O}}/{{.O}}foo{{.I}}bar{{.O}}/{{.O}} foo{{.I}}bar baz{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}}")
|
87
|
+
|
88
|
+
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, false, "query", items2)
|
89
|
+
checkFormat("echo {{.O}}foo{{.I}}bar{{.O}}/{{.O}}baz{{.O}}/{{.O}}baz{{.O}}/{{.O}}foo{{.I}}bar{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}}")
|
90
|
+
|
91
|
+
result = replacePlaceholder("echo {+1}/{+2}/{+-1}/{+-2}/{+..}/{n.t}/\\{}/\\{1}/\\{q}/{+3}", true, Delimiter{}, printsep, false, "query", items2)
|
92
|
+
checkFormat("echo {{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}} {{.O}}{{.O}}")
|
93
|
+
|
94
|
+
// forcePlus
|
95
|
+
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, true, "query", items2)
|
96
|
+
checkFormat("echo {{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}} {{.O}}{{.O}}")
|
97
|
+
|
98
|
+
// Whitespace preserving flag with "'" delimiter
|
99
|
+
result = replacePlaceholder("echo {s1}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
100
|
+
checkFormat("echo {{.O}} foo{{.O}}")
|
101
|
+
|
102
|
+
result = replacePlaceholder("echo {s2}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
103
|
+
checkFormat("echo {{.O}}bar baz{{.O}}")
|
104
|
+
|
105
|
+
result = replacePlaceholder("echo {s}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
106
|
+
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}")
|
107
|
+
|
108
|
+
result = replacePlaceholder("echo {s..}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
109
|
+
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}")
|
110
|
+
|
111
|
+
// Whitespace preserving flag with regex delimiter
|
112
|
+
regex = regexp.MustCompile(`\w+`)
|
113
|
+
|
114
|
+
result = replacePlaceholder("echo {s1}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
|
115
|
+
checkFormat("echo {{.O}} {{.O}}")
|
116
|
+
|
117
|
+
result = replacePlaceholder("echo {s2}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
|
118
|
+
checkFormat("echo {{.O}}{{.I}}{{.O}}")
|
119
|
+
|
120
|
+
result = replacePlaceholder("echo {s3}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
|
121
|
+
checkFormat("echo {{.O}} {{.O}}")
|
122
|
+
|
123
|
+
// No match
|
124
|
+
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, nil})
|
125
|
+
check("echo /")
|
126
|
+
|
127
|
+
// No match, but with selections
|
128
|
+
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, item1})
|
129
|
+
checkFormat("echo /{{.O}} foo{{.I}}bar baz{{.O}}")
|
130
|
+
|
131
|
+
// String delimiter
|
132
|
+
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
133
|
+
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}/{{.O}}foo{{.O}}/{{.O}}bar baz{{.O}}")
|
134
|
+
|
135
|
+
// Regex delimiter
|
136
|
+
regex = regexp.MustCompile("[oa]+")
|
137
|
+
// foo'bar baz
|
138
|
+
result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
|
139
|
+
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}/{{.O}}f{{.O}}/{{.O}}r b{{.O}}/{{.O}}{{.I}}bar b{{.O}}")
|
140
|
+
|
141
|
+
/*
|
142
|
+
Test single placeholders, but focus on the placeholders' parameters (e.g. flags).
|
143
|
+
see: TestParsePlaceholder
|
144
|
+
*/
|
145
|
+
items3 := []*Item{
|
146
|
+
// single line
|
147
|
+
newItem("1a 1b 1c 1d 1e 1f"),
|
148
|
+
// multi line
|
149
|
+
newItem("1a 1b 1c 1d 1e 1f"),
|
150
|
+
newItem("2a 2b 2c 2d 2e 2f"),
|
151
|
+
newItem("3a 3b 3c 3d 3e 3f"),
|
152
|
+
newItem("4a 4b 4c 4d 4e 4f"),
|
153
|
+
newItem("5a 5b 5c 5d 5e 5f"),
|
154
|
+
newItem("6a 6b 6c 6d 6e 6f"),
|
155
|
+
newItem("7a 7b 7c 7d 7e 7f"),
|
156
|
+
}
|
157
|
+
stripAnsi := false
|
158
|
+
printsep = "\n"
|
159
|
+
forcePlus := false
|
160
|
+
query := "sample query"
|
161
|
+
|
162
|
+
templateToOutput := make(map[string]string)
|
163
|
+
templateToFile := make(map[string]string) // same as above, but the file contents will be matched
|
164
|
+
// I. item type placeholder
|
165
|
+
templateToOutput[`{}`] = `{{.O}}1a 1b 1c 1d 1e 1f{{.O}}`
|
166
|
+
templateToOutput[`{+}`] = `{{.O}}1a 1b 1c 1d 1e 1f{{.O}} {{.O}}2a 2b 2c 2d 2e 2f{{.O}} {{.O}}3a 3b 3c 3d 3e 3f{{.O}} {{.O}}4a 4b 4c 4d 4e 4f{{.O}} {{.O}}5a 5b 5c 5d 5e 5f{{.O}} {{.O}}6a 6b 6c 6d 6e 6f{{.O}} {{.O}}7a 7b 7c 7d 7e 7f{{.O}}`
|
167
|
+
templateToOutput[`{n}`] = `0`
|
168
|
+
templateToOutput[`{+n}`] = `0 0 0 0 0 0 0`
|
169
|
+
templateToFile[`{f}`] = `1a 1b 1c 1d 1e 1f{{.S}}`
|
170
|
+
templateToFile[`{+f}`] = `1a 1b 1c 1d 1e 1f{{.S}}2a 2b 2c 2d 2e 2f{{.S}}3a 3b 3c 3d 3e 3f{{.S}}4a 4b 4c 4d 4e 4f{{.S}}5a 5b 5c 5d 5e 5f{{.S}}6a 6b 6c 6d 6e 6f{{.S}}7a 7b 7c 7d 7e 7f{{.S}}`
|
171
|
+
templateToFile[`{nf}`] = `0{{.S}}`
|
172
|
+
templateToFile[`{+nf}`] = `0{{.S}}0{{.S}}0{{.S}}0{{.S}}0{{.S}}0{{.S}}0{{.S}}`
|
173
|
+
|
174
|
+
// II. token type placeholders
|
175
|
+
templateToOutput[`{..}`] = templateToOutput[`{}`]
|
176
|
+
templateToOutput[`{1..}`] = templateToOutput[`{}`]
|
177
|
+
templateToOutput[`{..2}`] = `{{.O}}1a 1b{{.O}}`
|
178
|
+
templateToOutput[`{1..2}`] = templateToOutput[`{..2}`]
|
179
|
+
templateToOutput[`{-2..-1}`] = `{{.O}}1e 1f{{.O}}`
|
180
|
+
// shorthand for x..x range
|
181
|
+
templateToOutput[`{1}`] = `{{.O}}1a{{.O}}`
|
182
|
+
templateToOutput[`{1..1}`] = templateToOutput[`{1}`]
|
183
|
+
templateToOutput[`{-6}`] = templateToOutput[`{1}`]
|
184
|
+
// multiple ranges
|
185
|
+
templateToOutput[`{1,2}`] = templateToOutput[`{1..2}`]
|
186
|
+
templateToOutput[`{1,2,4}`] = `{{.O}}1a 1b 1d{{.O}}`
|
187
|
+
templateToOutput[`{1,2..4}`] = `{{.O}}1a 1b 1c 1d{{.O}}`
|
188
|
+
templateToOutput[`{1..2,-4..-3}`] = `{{.O}}1a 1b 1c 1d{{.O}}`
|
189
|
+
// flags
|
190
|
+
templateToOutput[`{+1}`] = `{{.O}}1a{{.O}} {{.O}}2a{{.O}} {{.O}}3a{{.O}} {{.O}}4a{{.O}} {{.O}}5a{{.O}} {{.O}}6a{{.O}} {{.O}}7a{{.O}}`
|
191
|
+
templateToOutput[`{+-1}`] = `{{.O}}1f{{.O}} {{.O}}2f{{.O}} {{.O}}3f{{.O}} {{.O}}4f{{.O}} {{.O}}5f{{.O}} {{.O}}6f{{.O}} {{.O}}7f{{.O}}`
|
192
|
+
templateToOutput[`{s1}`] = `{{.O}}1a {{.O}}`
|
193
|
+
templateToFile[`{f1}`] = `1a{{.S}}`
|
194
|
+
templateToOutput[`{+s1..2}`] = `{{.O}}1a 1b {{.O}} {{.O}}2a 2b {{.O}} {{.O}}3a 3b {{.O}} {{.O}}4a 4b {{.O}} {{.O}}5a 5b {{.O}} {{.O}}6a 6b {{.O}} {{.O}}7a 7b {{.O}}`
|
195
|
+
templateToFile[`{+sf1..2}`] = `1a 1b {{.S}}2a 2b {{.S}}3a 3b {{.S}}4a 4b {{.S}}5a 5b {{.S}}6a 6b {{.S}}7a 7b {{.S}}`
|
196
|
+
|
197
|
+
// III. query type placeholder
|
198
|
+
// query flag is not removed after parsing, so it gets doubled
|
199
|
+
// while the double q is invalid, it is useful here for testing purposes
|
200
|
+
templateToOutput[`{q}`] = "{{.O}}" + query + "{{.O}}"
|
201
|
+
|
202
|
+
// IV. escaping placeholder
|
203
|
+
templateToOutput[`\{}`] = `{}`
|
204
|
+
templateToOutput[`\{++}`] = `{++}`
|
205
|
+
templateToOutput[`{++}`] = templateToOutput[`{+}`]
|
206
|
+
|
207
|
+
for giveTemplate, wantOutput := range templateToOutput {
|
208
|
+
result = replacePlaceholder(giveTemplate, stripAnsi, Delimiter{}, printsep, forcePlus, query, items3)
|
209
|
+
checkFormat(wantOutput)
|
210
|
+
}
|
211
|
+
for giveTemplate, wantOutput := range templateToFile {
|
212
|
+
path := replacePlaceholder(giveTemplate, stripAnsi, Delimiter{}, printsep, forcePlus, query, items3)
|
213
|
+
|
214
|
+
data, err := readFile(path)
|
215
|
+
if err != nil {
|
216
|
+
t.Errorf("Cannot read the content of the temp file %s.", path)
|
217
|
+
}
|
218
|
+
result = string(data)
|
219
|
+
|
220
|
+
checkFormat(wantOutput)
|
221
|
+
}
|
222
|
+
}
|
223
|
+
|
224
|
+
func TestQuoteEntry(t *testing.T) {
|
225
|
+
type quotes struct{ E, O, SQ, DQ, BS string } // standalone escape, outer, single and double quotes, backslash
|
226
|
+
unixStyle := quotes{``, `'`, `'\''`, `"`, `\`}
|
227
|
+
windowsStyle := quotes{`^`, `^"`, `'`, `\^"`, `\\`}
|
228
|
+
var effectiveStyle quotes
|
229
|
+
|
230
|
+
if util.IsWindows() {
|
231
|
+
effectiveStyle = windowsStyle
|
232
|
+
} else {
|
233
|
+
effectiveStyle = unixStyle
|
234
|
+
}
|
235
|
+
|
236
|
+
tests := map[string]string{
|
237
|
+
`'`: `{{.O}}{{.SQ}}{{.O}}`,
|
238
|
+
`"`: `{{.O}}{{.DQ}}{{.O}}`,
|
239
|
+
`\`: `{{.O}}{{.BS}}{{.O}}`,
|
240
|
+
`\"`: `{{.O}}{{.BS}}{{.DQ}}{{.O}}`,
|
241
|
+
`"\\\"`: `{{.O}}{{.DQ}}{{.BS}}{{.BS}}{{.BS}}{{.DQ}}{{.O}}`,
|
242
|
+
|
243
|
+
`$`: `{{.O}}${{.O}}`,
|
244
|
+
`$HOME`: `{{.O}}$HOME{{.O}}`,
|
245
|
+
`'$HOME'`: `{{.O}}{{.SQ}}$HOME{{.SQ}}{{.O}}`,
|
246
|
+
|
247
|
+
`&`: `{{.O}}{{.E}}&{{.O}}`,
|
248
|
+
`|`: `{{.O}}{{.E}}|{{.O}}`,
|
249
|
+
`<`: `{{.O}}{{.E}}<{{.O}}`,
|
250
|
+
`>`: `{{.O}}{{.E}}>{{.O}}`,
|
251
|
+
`(`: `{{.O}}{{.E}}({{.O}}`,
|
252
|
+
`)`: `{{.O}}{{.E}}){{.O}}`,
|
253
|
+
`@`: `{{.O}}{{.E}}@{{.O}}`,
|
254
|
+
`^`: `{{.O}}{{.E}}^{{.O}}`,
|
255
|
+
`%`: `{{.O}}{{.E}}%{{.O}}`,
|
256
|
+
`!`: `{{.O}}{{.E}}!{{.O}}`,
|
257
|
+
`%USERPROFILE%`: `{{.O}}{{.E}}%USERPROFILE{{.E}}%{{.O}}`,
|
258
|
+
`C:\Program Files (x86)\`: `{{.O}}C:{{.BS}}Program Files {{.E}}(x86{{.E}}){{.BS}}{{.O}}`,
|
259
|
+
`"C:\Program Files"`: `{{.O}}{{.DQ}}C:{{.BS}}Program Files{{.DQ}}{{.O}}`,
|
260
|
+
}
|
261
|
+
|
262
|
+
for input, expected := range tests {
|
263
|
+
escaped := quoteEntry(input)
|
264
|
+
expected = templateToString(expected, effectiveStyle)
|
265
|
+
if escaped != expected {
|
266
|
+
t.Errorf("Input: %s, expected: %s, actual %s", input, expected, escaped)
|
267
|
+
}
|
268
|
+
}
|
269
|
+
}
|
270
|
+
|
271
|
+
// purpose of this test is to demonstrate some shortcomings of fzf's templating system on Unix
|
272
|
+
func TestUnixCommands(t *testing.T) {
|
273
|
+
if util.IsWindows() {
|
274
|
+
t.SkipNow()
|
275
|
+
}
|
276
|
+
tests := []testCase{
|
277
|
+
// reference: give{template, query, items}, want{output OR match}
|
278
|
+
|
279
|
+
// 1) working examples
|
280
|
+
|
281
|
+
// paths that does not have to evaluated will work fine, when quoted
|
282
|
+
{give{`grep foo {}`, ``, newItems(`test`)}, want{output: `grep foo 'test'`}},
|
283
|
+
{give{`grep foo {}`, ``, newItems(`/home/user/test`)}, want{output: `grep foo '/home/user/test'`}},
|
284
|
+
{give{`grep foo {}`, ``, newItems(`./test`)}, want{output: `grep foo './test'`}},
|
285
|
+
|
286
|
+
// only placeholders are escaped as data, this will lookup tilde character in a test file in your home directory
|
287
|
+
// quoting the tilde is required (to be treated as string)
|
288
|
+
{give{`grep {} ~/test`, ``, newItems(`~`)}, want{output: `grep '~' ~/test`}},
|
289
|
+
|
290
|
+
// 2) problematic examples
|
291
|
+
// (not necessarily unexpected)
|
292
|
+
|
293
|
+
// paths that need to expand some part of it won't work (special characters and variables)
|
294
|
+
{give{`cat {}`, ``, newItems(`~/test`)}, want{output: `cat '~/test'`}},
|
295
|
+
{give{`cat {}`, ``, newItems(`$HOME/test`)}, want{output: `cat '$HOME/test'`}},
|
296
|
+
}
|
297
|
+
testCommands(t, tests)
|
298
|
+
}
|
299
|
+
|
300
|
+
// purpose of this test is to demonstrate some shortcomings of fzf's templating system on Windows
|
301
|
+
func TestWindowsCommands(t *testing.T) {
|
302
|
+
if !util.IsWindows() {
|
303
|
+
t.SkipNow()
|
304
|
+
}
|
305
|
+
tests := []testCase{
|
306
|
+
// reference: give{template, query, items}, want{output OR match}
|
307
|
+
|
308
|
+
// 1) working examples
|
309
|
+
|
310
|
+
// example of redundantly escaped backslash in the output, besides looking bit ugly, it won't cause any issue
|
311
|
+
{give{`type {}`, ``, newItems(`C:\test.txt`)}, want{output: `type ^"C:\\test.txt^"`}},
|
312
|
+
{give{`rg -- "package" {}`, ``, newItems(`.\test.go`)}, want{output: `rg -- "package" ^".\\test.go^"`}},
|
313
|
+
// example of mandatorily escaped backslash in the output, otherwise `rg -- "C:\test.txt"` is matching for tabulator
|
314
|
+
{give{`rg -- {}`, ``, newItems(`C:\test.txt`)}, want{output: `rg -- ^"C:\\test.txt^"`}},
|
315
|
+
// example of mandatorily escaped double quote in the output, otherwise `rg -- ""C:\\test.txt""` is not matching for the double quotes around the path
|
316
|
+
{give{`rg -- {}`, ``, newItems(`"C:\test.txt"`)}, want{output: `rg -- ^"\^"C:\\test.txt\^"^"`}},
|
317
|
+
|
318
|
+
// 2) problematic examples
|
319
|
+
// (not necessarily unexpected)
|
320
|
+
|
321
|
+
// notepad++'s parser can't handle `-n"12"` generate by fzf, expects `-n12`
|
322
|
+
{give{`notepad++ -n{1} {2}`, ``, newItems(`12 C:\Work\Test Folder\File.txt`)}, want{output: `notepad++ -n^"12^" ^"C:\\Work\\Test Folder\\File.txt^"`}},
|
323
|
+
|
324
|
+
// cat is parsing `\"` as a part of the file path, double quote is illegal character for paths on Windows
|
325
|
+
// cat: "C:\\test.txt: Invalid argument
|
326
|
+
{give{`cat {}`, ``, newItems(`"C:\test.txt"`)}, want{output: `cat ^"\^"C:\\test.txt\^"^"`}},
|
327
|
+
// cat: "C:\\test.txt": Invalid argument
|
328
|
+
{give{`cmd /c {}`, ``, newItems(`cat "C:\test.txt"`)}, want{output: `cmd /c ^"cat \^"C:\\test.txt\^"^"`}},
|
329
|
+
|
330
|
+
// the "file" flag in the pattern won't create *.bat or *.cmd file so the command in the output tries to edit the file, instead of executing it
|
331
|
+
// the temp file contains: `cat "C:\test.txt"`
|
332
|
+
// TODO this should actually work
|
333
|
+
{give{`cmd /c {f}`, ``, newItems(`cat "C:\test.txt"`)}, want{match: `^cmd /c .*\fzf-preview-[0-9]{9}$`}},
|
334
|
+
}
|
335
|
+
testCommands(t, tests)
|
336
|
+
}
|
337
|
+
|
338
|
+
// purpose of this test is to demonstrate some shortcomings of fzf's templating system on Windows in Powershell
|
339
|
+
func TestPowershellCommands(t *testing.T) {
|
340
|
+
if !util.IsWindows() {
|
341
|
+
t.SkipNow()
|
342
|
+
}
|
343
|
+
|
344
|
+
tests := []testCase{
|
345
|
+
// reference: give{template, query, items}, want{output OR match}
|
346
|
+
|
347
|
+
/*
|
348
|
+
You can read each line in the following table as a pipeline that
|
349
|
+
consist of series of parsers that act upon your input (col. 1) and
|
350
|
+
each cell represents the output value.
|
351
|
+
|
352
|
+
For example:
|
353
|
+
- exec.Command("program.exe", `\''`)
|
354
|
+
- goes to win32 api which will process it transparently as it contains no special characters, see [CommandLineToArgvW][].
|
355
|
+
- powershell command will receive it as is, that is two arguments: a literal backslash and empty string in single quotes
|
356
|
+
- native command run via/from powershell will receive only one argument: a literal backslash. Because extra parsing rules apply, see [NativeCallsFromPowershell][].
|
357
|
+
- some¹ apps have internal parser, that requires one more level of escaping (yes, this is completely application-specific, but see terminal_test.go#TestWindowsCommands)
|
358
|
+
|
359
|
+
Character⁰ CommandLineToArgvW Powershell commands Native commands from Powershell Apps requiring escapes¹ | Being tested below
|
360
|
+
---------- ------------------ ------------------------------ ------------------------------- -------------------------- | ------------------
|
361
|
+
" empty string² missing argument error ... ... |
|
362
|
+
\" literal " unbalanced quote error ... ... |
|
363
|
+
'\"' literal '"' literal " empty string empty string (match all) | yes
|
364
|
+
'\\\"' literal '\"' literal \" literal " literal " |
|
365
|
+
---------- ------------------ ------------------------------ ------------------------------- -------------------------- | ------------------
|
366
|
+
\ transparent transparent transparent regex error |
|
367
|
+
'\' transparent literal \ literal \ regex error | yes
|
368
|
+
\\ transparent transparent transparent literal \ |
|
369
|
+
'\\' transparent literal \\ literal \\ literal \ |
|
370
|
+
---------- ------------------ ------------------------------ ------------------------------- -------------------------- | ------------------
|
371
|
+
' transparent unbalanced quote error ... ... |
|
372
|
+
\' transparent literal \ and unb. quote error ... ... |
|
373
|
+
\'' transparent literal \ and empty string literal \ regex error | no, but given as example above
|
374
|
+
''' transparent unbalanced quote error ... ... |
|
375
|
+
'''' transparent literal ' literal ' literal ' | yes
|
376
|
+
---------- ------------------ ------------------------------ ------------------------------- -------------------------- | ------------------
|
377
|
+
|
378
|
+
⁰: charatecter or characters 'x' as an argument to a program in go's call: exec.Command("program.exe", `x`)
|
379
|
+
¹: native commands like grep, git grep, ripgrep
|
380
|
+
²: interpreted as a grouping quote, affects argument parser and gets removed from the result
|
381
|
+
|
382
|
+
[CommandLineToArgvW]: https://docs.microsoft.com/en-gb/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw#remarks
|
383
|
+
[NativeCallsFromPowershell]: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_parsing?view=powershell-7.1#passing-arguments-that-contain-quote-characters
|
384
|
+
*/
|
385
|
+
|
386
|
+
// 1) working examples
|
387
|
+
|
388
|
+
{give{`Get-Content {}`, ``, newItems(`C:\test.txt`)}, want{output: `Get-Content 'C:\test.txt'`}},
|
389
|
+
{give{`rg -- "package" {}`, ``, newItems(`.\test.go`)}, want{output: `rg -- "package" '.\test.go'`}},
|
390
|
+
|
391
|
+
// example of escaping single quotes
|
392
|
+
{give{`rg -- {}`, ``, newItems(`'foobar'`)}, want{output: `rg -- '''foobar'''`}},
|
393
|
+
|
394
|
+
// chaining powershells
|
395
|
+
{give{`powershell -NoProfile -Command {}`, ``, newItems(`cat "C:\test.txt"`)}, want{output: `powershell -NoProfile -Command 'cat \"C:\test.txt\"'`}},
|
396
|
+
|
397
|
+
// 2) problematic examples
|
398
|
+
// (not necessarily unexpected)
|
399
|
+
|
400
|
+
// looking for a path string will only work with escaped backslashes
|
401
|
+
{give{`rg -- {}`, ``, newItems(`C:\test.txt`)}, want{output: `rg -- 'C:\test.txt'`}},
|
402
|
+
// looking for a literal double quote will only work with triple escaped double quotes
|
403
|
+
{give{`rg -- {}`, ``, newItems(`"C:\test.txt"`)}, want{output: `rg -- '\"C:\test.txt\"'`}},
|
404
|
+
|
405
|
+
// Get-Content (i.e. cat alias) is parsing `"` as a part of the file path, returns an error:
|
406
|
+
// Get-Content : Cannot find drive. A drive with the name '"C:' does not exist.
|
407
|
+
{give{`cat {}`, ``, newItems(`"C:\test.txt"`)}, want{output: `cat '\"C:\test.txt\"'`}},
|
408
|
+
|
409
|
+
// the "file" flag in the pattern won't create *.ps1 file so the powershell will offload this "unknown" filetype
|
410
|
+
// to explorer, which will prompt user to pick editing program for the fzf-preview file
|
411
|
+
// the temp file contains: `cat "C:\test.txt"`
|
412
|
+
// TODO this should actually work
|
413
|
+
{give{`powershell -NoProfile -Command {f}`, ``, newItems(`cat "C:\test.txt"`)}, want{match: `^powershell -NoProfile -Command .*\fzf-preview-[0-9]{9}$`}},
|
414
|
+
}
|
415
|
+
|
416
|
+
// to force powershell-style escaping we temporarily set environment variable that fzf honors
|
417
|
+
shellBackup := os.Getenv("SHELL")
|
418
|
+
os.Setenv("SHELL", "powershell")
|
419
|
+
testCommands(t, tests)
|
420
|
+
os.Setenv("SHELL", shellBackup)
|
421
|
+
}
|
422
|
+
|
423
|
+
/*
|
424
|
+
Test typical valid placeholders and parsing of them.
|
425
|
+
|
426
|
+
Also since the parser assumes the input is matched with `placeholder` regex,
|
427
|
+
the regex is tested here as well.
|
428
|
+
*/
|
429
|
+
func TestParsePlaceholder(t *testing.T) {
|
430
|
+
// give, want pairs
|
431
|
+
templates := map[string]string{
|
432
|
+
// I. item type placeholder
|
433
|
+
`{}`: `{}`,
|
434
|
+
`{+}`: `{+}`,
|
435
|
+
`{n}`: `{n}`,
|
436
|
+
`{+n}`: `{+n}`,
|
437
|
+
`{f}`: `{f}`,
|
438
|
+
`{+nf}`: `{+nf}`,
|
439
|
+
|
440
|
+
// II. token type placeholders
|
441
|
+
`{..}`: `{..}`,
|
442
|
+
`{1..}`: `{1..}`,
|
443
|
+
`{..2}`: `{..2}`,
|
444
|
+
`{1..2}`: `{1..2}`,
|
445
|
+
`{-2..-1}`: `{-2..-1}`,
|
446
|
+
// shorthand for x..x range
|
447
|
+
`{1}`: `{1}`,
|
448
|
+
`{1..1}`: `{1..1}`,
|
449
|
+
`{-6}`: `{-6}`,
|
450
|
+
// multiple ranges
|
451
|
+
`{1,2}`: `{1,2}`,
|
452
|
+
`{1,2,4}`: `{1,2,4}`,
|
453
|
+
`{1,2..4}`: `{1,2..4}`,
|
454
|
+
`{1..2,-4..-3}`: `{1..2,-4..-3}`,
|
455
|
+
// flags
|
456
|
+
`{+1}`: `{+1}`,
|
457
|
+
`{+-1}`: `{+-1}`,
|
458
|
+
`{s1}`: `{s1}`,
|
459
|
+
`{f1}`: `{f1}`,
|
460
|
+
`{+s1..2}`: `{+s1..2}`,
|
461
|
+
`{+sf1..2}`: `{+sf1..2}`,
|
462
|
+
|
463
|
+
// III. query type placeholder
|
464
|
+
// query flag is not removed after parsing, so it gets doubled
|
465
|
+
// while the double q is invalid, it is useful here for testing purposes
|
466
|
+
`{q}`: `{qq}`,
|
467
|
+
|
468
|
+
// IV. escaping placeholder
|
469
|
+
`\{}`: `{}`,
|
470
|
+
`\{++}`: `{++}`,
|
471
|
+
`{++}`: `{+}`,
|
472
|
+
}
|
473
|
+
|
474
|
+
for giveTemplate, wantTemplate := range templates {
|
475
|
+
if !placeholder.MatchString(giveTemplate) {
|
476
|
+
t.Errorf(`given placeholder %s does not match placeholder regex, so attempt to parse it is unexpected`, giveTemplate)
|
477
|
+
continue
|
478
|
+
}
|
479
|
+
|
480
|
+
_, placeholderWithoutFlags, flags := parsePlaceholder(giveTemplate)
|
481
|
+
gotTemplate := placeholderWithoutFlags[:1] + flags.encodePlaceholder() + placeholderWithoutFlags[1:]
|
482
|
+
|
483
|
+
if gotTemplate != wantTemplate {
|
484
|
+
t.Errorf(`parsed placeholder "%s" into "%s", but want "%s"`, giveTemplate, gotTemplate, wantTemplate)
|
485
|
+
}
|
486
|
+
}
|
487
|
+
}
|
488
|
+
|
489
|
+
/* utilities section */
|
490
|
+
|
491
|
+
// Item represents one line in fzf UI. Usually it is relative path to files and folders.
|
492
|
+
func newItem(str string) *Item {
|
493
|
+
bytes := []byte(str)
|
494
|
+
trimmed, _, _ := extractColor(str, nil, nil)
|
495
|
+
return &Item{origText: &bytes, text: util.ToChars([]byte(trimmed))}
|
496
|
+
}
|
497
|
+
|
498
|
+
// Functions tested in this file require array of items (allItems). The array needs
|
499
|
+
// to consist of at least two nils. This is helper function.
|
500
|
+
func newItems(str ...string) []*Item {
|
501
|
+
result := make([]*Item, util.Max(len(str), 2))
|
502
|
+
for i, s := range str {
|
503
|
+
result[i] = newItem(s)
|
504
|
+
}
|
505
|
+
return result
|
506
|
+
}
|
507
|
+
|
508
|
+
// (for logging purposes)
|
509
|
+
func (item *Item) String() string {
|
510
|
+
return item.AsString(true)
|
511
|
+
}
|
512
|
+
|
513
|
+
// Helper function to parse, execute and convert "text/template" to string. Panics on error.
|
514
|
+
func templateToString(format string, data interface{}) string {
|
515
|
+
bb := &bytes.Buffer{}
|
516
|
+
|
517
|
+
err := template.Must(template.New("").Parse(format)).Execute(bb, data)
|
518
|
+
if err != nil {
|
519
|
+
panic(err)
|
520
|
+
}
|
521
|
+
|
522
|
+
return bb.String()
|
523
|
+
}
|
524
|
+
|
525
|
+
// ad hoc types for test cases
|
526
|
+
type give struct {
|
527
|
+
template string
|
528
|
+
query string
|
529
|
+
allItems []*Item
|
530
|
+
}
|
531
|
+
type want struct {
|
532
|
+
/*
|
533
|
+
Unix:
|
534
|
+
The `want.output` string is supposed to be formatted for evaluation by
|
535
|
+
`sh -c command` system call.
|
536
|
+
|
537
|
+
Windows:
|
538
|
+
The `want.output` string is supposed to be formatted for evaluation by
|
539
|
+
`cmd.exe /s /c "command"` system call. The `/s` switch enables so called old
|
540
|
+
behaviour, which is more favourable for nesting (possibly escaped)
|
541
|
+
special characters. This is the relevant section of `help cmd`:
|
542
|
+
|
543
|
+
...old behavior is to see if the first character is
|
544
|
+
a quote character and if so, strip the leading character and
|
545
|
+
remove the last quote character on the command line, preserving
|
546
|
+
any text after the last quote character.
|
547
|
+
*/
|
548
|
+
output string // literal output
|
549
|
+
match string // output is matched against this regex (when output is empty string)
|
550
|
+
}
|
551
|
+
type testCase struct {
|
552
|
+
give
|
553
|
+
want
|
554
|
+
}
|
555
|
+
|
556
|
+
func testCommands(t *testing.T, tests []testCase) {
|
557
|
+
// common test parameters
|
558
|
+
delim := "\t"
|
559
|
+
delimiter := Delimiter{str: &delim}
|
560
|
+
printsep := ""
|
561
|
+
stripAnsi := false
|
562
|
+
forcePlus := false
|
563
|
+
|
564
|
+
// evaluate the test cases
|
565
|
+
for idx, test := range tests {
|
566
|
+
gotOutput := replacePlaceholder(
|
567
|
+
test.give.template, stripAnsi, delimiter, printsep, forcePlus,
|
568
|
+
test.give.query,
|
569
|
+
test.give.allItems)
|
570
|
+
switch {
|
571
|
+
case test.want.output != "":
|
572
|
+
if gotOutput != test.want.output {
|
573
|
+
t.Errorf("tests[%v]:\ngave{\n\ttemplate: '%s',\n\tquery: '%s',\n\tallItems: %s}\nand got '%s',\nbut want '%s'",
|
574
|
+
idx,
|
575
|
+
test.give.template, test.give.query, test.give.allItems,
|
576
|
+
gotOutput, test.want.output)
|
577
|
+
}
|
578
|
+
case test.want.match != "":
|
579
|
+
wantMatch := strings.ReplaceAll(test.want.match, `\`, `\\`)
|
580
|
+
wantRegex := regexp.MustCompile(wantMatch)
|
581
|
+
if !wantRegex.MatchString(gotOutput) {
|
582
|
+
t.Errorf("tests[%v]:\ngave{\n\ttemplate: '%s',\n\tquery: '%s',\n\tallItems: %s}\nand got '%s',\nbut want '%s'",
|
583
|
+
idx,
|
584
|
+
test.give.template, test.give.query, test.give.allItems,
|
585
|
+
gotOutput, test.want.match)
|
586
|
+
}
|
587
|
+
default:
|
588
|
+
t.Errorf("tests[%v]: test case does not describe 'want' property", idx)
|
589
|
+
}
|
590
|
+
}
|
591
|
+
}
|
592
|
+
|
593
|
+
// naive encoder of placeholder flags
|
594
|
+
func (flags placeholderFlags) encodePlaceholder() string {
|
595
|
+
encoded := ""
|
596
|
+
if flags.plus {
|
597
|
+
encoded += "+"
|
598
|
+
}
|
599
|
+
if flags.preserveSpace {
|
600
|
+
encoded += "s"
|
601
|
+
}
|
602
|
+
if flags.number {
|
603
|
+
encoded += "n"
|
604
|
+
}
|
605
|
+
if flags.file {
|
606
|
+
encoded += "f"
|
607
|
+
}
|
608
|
+
if flags.query {
|
609
|
+
encoded += "q"
|
610
|
+
}
|
611
|
+
return encoded
|
612
|
+
}
|
613
|
+
|
614
|
+
// can be replaced with os.ReadFile() in go 1.16+
|
615
|
+
func readFile(path string) ([]byte, error) {
|
616
|
+
file, err := os.Open(path)
|
617
|
+
if err != nil {
|
618
|
+
return nil, err
|
619
|
+
}
|
620
|
+
defer file.Close()
|
621
|
+
|
622
|
+
data := make([]byte, 0, 128)
|
623
|
+
for {
|
624
|
+
if len(data) >= cap(data) {
|
625
|
+
d := append(data[:cap(data)], 0)
|
626
|
+
data = d[:len(data)]
|
627
|
+
}
|
628
|
+
|
629
|
+
n, err := file.Read(data[len(data):cap(data)])
|
630
|
+
data = data[:len(data)+n]
|
631
|
+
if err != nil {
|
632
|
+
if err == io.EOF {
|
633
|
+
err = nil
|
634
|
+
}
|
635
|
+
return data, err
|
636
|
+
}
|
637
|
+
}
|
638
|
+
}
|
@@ -0,0 +1,26 @@
|
|
1
|
+
// +build !windows
|
2
|
+
|
3
|
+
package fzf
|
4
|
+
|
5
|
+
import (
|
6
|
+
"os"
|
7
|
+
"os/signal"
|
8
|
+
"strings"
|
9
|
+
"syscall"
|
10
|
+
)
|
11
|
+
|
12
|
+
func notifyOnResize(resizeChan chan<- os.Signal) {
|
13
|
+
signal.Notify(resizeChan, syscall.SIGWINCH)
|
14
|
+
}
|
15
|
+
|
16
|
+
func notifyStop(p *os.Process) {
|
17
|
+
p.Signal(syscall.SIGSTOP)
|
18
|
+
}
|
19
|
+
|
20
|
+
func notifyOnCont(resizeChan chan<- os.Signal) {
|
21
|
+
signal.Notify(resizeChan, syscall.SIGCONT)
|
22
|
+
}
|
23
|
+
|
24
|
+
func quoteEntry(entry string) string {
|
25
|
+
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
|
26
|
+
}
|