mirror of
https://github.com/junegunn/fzf.git
synced 2025-08-02 13:12:00 -07:00
Compare commits
30 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
a2beb159f1 | ||
|
7ce427ff47 | ||
|
a221c672fb | ||
|
f87d382ec8 | ||
|
3dfc020fac | ||
|
2d87896939 | ||
|
2192d8d816 | ||
|
d206949f62 | ||
|
4accc69022 | ||
|
898d8d94c8 | ||
|
26895da969 | ||
|
0c573b3dff | ||
|
2cff00dce2 | ||
|
06a6ad8bca | ||
|
02c6ad0e59 | ||
|
9f321cbe13 | ||
|
9f30ca2923 | ||
|
37f2d8f795 | ||
|
400e443a0a | ||
|
0a8d2996dc | ||
|
cfdb00b971 | ||
|
9b9ad39143 | ||
|
0541c0dbcf | ||
|
47b11cb8b4 | ||
|
d3da310b92 | ||
|
93e0a6a9de | ||
|
ac549a853a | ||
|
053af9a1c8 | ||
|
60112def02 | ||
|
2134c0c8a9 |
@@ -1,6 +1,10 @@
|
|||||||
language: ruby
|
language: ruby
|
||||||
rvm:
|
matrix:
|
||||||
- 2.2.0
|
include:
|
||||||
|
- env: TAGS=
|
||||||
|
rvm: 2.2.0
|
||||||
|
# - env: TAGS=tcell
|
||||||
|
# rvm: 2.2.0
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- sudo apt-get update
|
- sudo apt-get update
|
||||||
|
19
CHANGELOG.md
19
CHANGELOG.md
@@ -1,6 +1,25 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.15.7
|
||||||
|
------
|
||||||
|
- Fixed panic when color is disabled and header lines contain ANSI colors
|
||||||
|
|
||||||
|
0.15.6
|
||||||
|
------
|
||||||
|
- Windows binaries! (@kelleyma49)
|
||||||
|
- Fixed the bug where header lines are cleared when preview window is toggled
|
||||||
|
- Fixed not to display ^N and ^O on screen
|
||||||
|
- Fixed cursor keys (or any key sequence that starts with ESC) on WSL by
|
||||||
|
making fzf wait for additional keystrokes after ESC for up to 100ms
|
||||||
|
|
||||||
|
0.15.5
|
||||||
|
------
|
||||||
|
- Setting foreground color will no longer set background color to black
|
||||||
|
- e.g. `fzf --color fg:153`
|
||||||
|
- `--tiebreak=end` will consider relative position instead of absolute distance
|
||||||
|
- Updated `fzf#wrap` function to respect `g:fzf_colors`
|
||||||
|
|
||||||
0.15.4
|
0.15.4
|
||||||
------
|
------
|
||||||
- Added support for range expression in preview and execute action
|
- Added support for range expression in preview and execute action
|
||||||
|
20
README.md
20
README.md
@@ -82,6 +82,15 @@ method used.
|
|||||||
- brew: `brew update; brew reinstall fzf`
|
- brew: `brew update; brew reinstall fzf`
|
||||||
- vim-plug: `:PlugUpdate fzf`
|
- vim-plug: `:PlugUpdate fzf`
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
Pre-built binaries for Windows can be downloaded [here][bin]. However, other
|
||||||
|
components of the project may not work on Windows. You might want to consider
|
||||||
|
installing fzf on [Windows Subsystem for Linux][wsl] where everything runs
|
||||||
|
flawlessly.
|
||||||
|
|
||||||
|
[wsl]: https://blogs.msdn.microsoft.com/wsl/
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
-----
|
-----
|
||||||
|
|
||||||
@@ -102,7 +111,7 @@ vim $(fzf)
|
|||||||
|
|
||||||
#### Using the finder
|
#### Using the finder
|
||||||
|
|
||||||
- `CTRL-J` / `CTRL-K` (or `CTRL-N` / `CTRL-P)` to move cursor up and down
|
- `CTRL-J` / `CTRL-K` (or `CTRL-N` / `CTRL-P`) to move cursor up and down
|
||||||
- `Enter` key to select the item, `CTRL-C` / `CTRL-G` / `ESC` to exit
|
- `Enter` key to select the item, `CTRL-C` / `CTRL-G` / `ESC` to exit
|
||||||
- On multi-select mode (`-m`), `TAB` and `Shift-TAB` to mark multiple items
|
- On multi-select mode (`-m`), `TAB` and `Shift-TAB` to mark multiple items
|
||||||
- Emacs style key bindings
|
- Emacs style key bindings
|
||||||
@@ -145,6 +154,10 @@ or `py`.
|
|||||||
- Default options
|
- Default options
|
||||||
- e.g. `export FZF_DEFAULT_OPTS="--reverse --inline-info"`
|
- e.g. `export FZF_DEFAULT_OPTS="--reverse --inline-info"`
|
||||||
|
|
||||||
|
#### Options
|
||||||
|
|
||||||
|
See the man page (`man fzf`) for the full list of options.
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
|
|
||||||
@@ -319,7 +332,7 @@ Note that the environment variables `FZF_DEFAULT_COMMAND` and
|
|||||||
`FZF_DEFAULT_OPTS` also apply here. Refer to [the wiki page][fzf-config] for
|
`FZF_DEFAULT_OPTS` also apply here. Refer to [the wiki page][fzf-config] for
|
||||||
customization.
|
customization.
|
||||||
|
|
||||||
[fzf-config]: https://github.com/junegunn/fzf/wiki/Configuring-FZF-command-(vim)
|
[fzf-config]: https://github.com/junegunn/fzf/wiki/Configuring-Vim-plugin
|
||||||
|
|
||||||
#### `fzf#run`
|
#### `fzf#run`
|
||||||
|
|
||||||
@@ -347,7 +360,8 @@ page](https://github.com/junegunn/fzf/wiki/Examples-(vim)).
|
|||||||
|
|
||||||
`fzf#wrap([name string,] [opts dict,] [fullscreen boolean])` is a helper
|
`fzf#wrap([name string,] [opts dict,] [fullscreen boolean])` is a helper
|
||||||
function that decorates the options dictionary so that it understands
|
function that decorates the options dictionary so that it understands
|
||||||
`g:fzf_layout`, `g:fzf_action`, and `g:fzf_history_dir` like `:FZF`.
|
`g:fzf_layout`, `g:fzf_action`, `g:fzf_colors`, and `g:fzf_history_dir` like
|
||||||
|
`:FZF`.
|
||||||
|
|
||||||
```vim
|
```vim
|
||||||
command! -bang MyStuff
|
command! -bang MyStuff
|
||||||
|
@@ -17,6 +17,7 @@ swap=""
|
|||||||
close=""
|
close=""
|
||||||
term=""
|
term=""
|
||||||
[[ -n "$LINES" ]] && lines=$LINES || lines=$(tput lines)
|
[[ -n "$LINES" ]] && lines=$LINES || lines=$(tput lines)
|
||||||
|
[[ -n "$COLUMNS" ]] && columns=$COLUMNS || columns=$(tput cols)
|
||||||
|
|
||||||
help() {
|
help() {
|
||||||
>&2 echo 'usage: fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS]
|
>&2 echo 'usage: fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS]
|
||||||
@@ -83,7 +84,7 @@ while [[ $# -gt 0 ]]; do
|
|||||||
else
|
else
|
||||||
if [[ -n "$swap" ]]; then
|
if [[ -n "$swap" ]]; then
|
||||||
if [[ "$arg" =~ ^.l ]]; then
|
if [[ "$arg" =~ ^.l ]]; then
|
||||||
[[ -n "$COLUMNS" ]] && max=$COLUMNS || max=$(tput cols)
|
max=$columns
|
||||||
else
|
else
|
||||||
max=$lines
|
max=$lines
|
||||||
fi
|
fi
|
||||||
@@ -108,7 +109,7 @@ while [[ $# -gt 0 ]]; do
|
|||||||
[[ -n "$skip" ]] && args+=("$arg")
|
[[ -n "$skip" ]] && args+=("$arg")
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ -z "$TMUX" ]] || [[ "$lines" -le 15 ]]; then
|
if [[ -z "$TMUX" || "$opt" =~ ^-h && "$columns" -le 40 || ! "$opt" =~ ^-h && "$lines" -le 15 ]]; then
|
||||||
"$fzf" "${args[@]}"
|
"$fzf" "${args[@]}"
|
||||||
exit $?
|
exit $?
|
||||||
fi
|
fi
|
||||||
|
4
install
4
install
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
[[ "$@" =~ --pre ]] && version=0.15.4 pre=1 ||
|
[[ "$@" =~ --pre ]] && version=0.15.7 pre=1 ||
|
||||||
version=0.15.4 pre=0
|
version=0.15.7 pre=0
|
||||||
|
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
|
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf-tmux 1 "Oct 2016" "fzf 0.15.4" "fzf-tmux - open fzf in tmux split pane"
|
.TH fzf-tmux 1 "Nov 2016" "fzf 0.15.7" "fzf-tmux - open fzf in tmux split pane"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf-tmux - open fzf in tmux split pane
|
fzf-tmux - open fzf in tmux split pane
|
||||||
|
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf 1 "Oct 2016" "fzf 0.15.4" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Nov 2016" "fzf 0.15.7" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
|
@@ -21,6 +21,11 @@
|
|||||||
" OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
" OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
if exists('g:loaded_fzf')
|
||||||
|
finish
|
||||||
|
endif
|
||||||
|
let g:loaded_fzf = 1
|
||||||
|
|
||||||
let s:default_layout = { 'down': '~40%' }
|
let s:default_layout = { 'down': '~40%' }
|
||||||
let s:layout_keys = ['window', 'up', 'down', 'left', 'right']
|
let s:layout_keys = ['window', 'up', 'down', 'left', 'right']
|
||||||
let s:fzf_go = expand('<sfile>:h:h').'/bin/fzf'
|
let s:fzf_go = expand('<sfile>:h:h').'/bin/fzf'
|
||||||
@@ -154,6 +159,22 @@ function! s:common_sink(action, lines) abort
|
|||||||
endtry
|
endtry
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
function! s:get_color(attr, ...)
|
||||||
|
for group in a:000
|
||||||
|
let code = synIDattr(synIDtrans(hlID(group)), a:attr, 'cterm')
|
||||||
|
if code =~ '^[0-9]\+$'
|
||||||
|
return code
|
||||||
|
endif
|
||||||
|
endfor
|
||||||
|
return ''
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! s:defaults()
|
||||||
|
let rules = copy(get(g:, 'fzf_colors', {}))
|
||||||
|
let colors = join(map(items(filter(map(rules, 'call("s:get_color", v:val)'), '!empty(v:val)')), 'join(v:val, ":")'), ',')
|
||||||
|
return empty(colors) ? '' : ('--color='.colors)
|
||||||
|
endfunction
|
||||||
|
|
||||||
" [name string,] [opts dict,] [fullscreen boolean]
|
" [name string,] [opts dict,] [fullscreen boolean]
|
||||||
function! fzf#wrap(...)
|
function! fzf#wrap(...)
|
||||||
let args = ['', {}, 0]
|
let args = ['', {}, 0]
|
||||||
@@ -185,8 +206,10 @@ function! fzf#wrap(...)
|
|||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
" Colors: g:fzf_colors
|
||||||
|
let opts.options = s:defaults() .' '. get(opts, 'options', '')
|
||||||
|
|
||||||
" History: g:fzf_history_dir
|
" History: g:fzf_history_dir
|
||||||
let opts.options = get(opts, 'options', '')
|
|
||||||
if len(name) && len(get(g:, 'fzf_history_dir', ''))
|
if len(name) && len(get(g:, 'fzf_history_dir', ''))
|
||||||
let dir = expand(g:fzf_history_dir)
|
let dir = expand(g:fzf_history_dir)
|
||||||
if !isdirectory(dir)
|
if !isdirectory(dir)
|
||||||
@@ -289,7 +312,8 @@ function! s:fzf_tmux(dict)
|
|||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:splittable(dict)
|
function! s:splittable(dict)
|
||||||
return s:present(a:dict, 'up', 'down', 'left', 'right')
|
return s:present(a:dict, 'up', 'down') && &lines > 15 ||
|
||||||
|
\ s:present(a:dict, 'left', 'right') && &columns > 40
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:pushd(dict)
|
function! s:pushd(dict)
|
||||||
@@ -405,6 +429,11 @@ function! s:split(dict)
|
|||||||
\ 'right': ['vertical botright', 'vertical resize', &columns] }
|
\ 'right': ['vertical botright', 'vertical resize', &columns] }
|
||||||
let ppos = s:getpos()
|
let ppos = s:getpos()
|
||||||
try
|
try
|
||||||
|
if s:present(a:dict, 'window')
|
||||||
|
execute a:dict.window
|
||||||
|
elseif !s:splittable(a:dict)
|
||||||
|
execute (tabpagenr()-1).'tabnew'
|
||||||
|
else
|
||||||
for [dir, triple] in items(directions)
|
for [dir, triple] in items(directions)
|
||||||
let val = get(a:dict, dir, '')
|
let val = get(a:dict, dir, '')
|
||||||
if !empty(val)
|
if !empty(val)
|
||||||
@@ -419,10 +448,6 @@ function! s:split(dict)
|
|||||||
return [ppos, {}]
|
return [ppos, {}]
|
||||||
endif
|
endif
|
||||||
endfor
|
endfor
|
||||||
if s:present(a:dict, 'window')
|
|
||||||
execute a:dict.window
|
|
||||||
else
|
|
||||||
execute (tabpagenr()-1).'tabnew'
|
|
||||||
endif
|
endif
|
||||||
return [ppos, { '&l:wfw': &l:wfw, '&l:wfh': &l:wfh }]
|
return [ppos, { '&l:wfw': &l:wfw, '&l:wfh': &l:wfh }]
|
||||||
finally
|
finally
|
||||||
|
@@ -44,7 +44,7 @@ __fzf_generic_path_completion() {
|
|||||||
setopt localoptions nonomatch
|
setopt localoptions nonomatch
|
||||||
dir="$base"
|
dir="$base"
|
||||||
while [ 1 ]; do
|
while [ 1 ]; do
|
||||||
if [ -z "$dir" -o -d ${~dir} ]; then
|
if [[ -z "$dir" || -d ${~dir} ]]; then
|
||||||
leftover=${base/#"$dir"}
|
leftover=${base/#"$dir"}
|
||||||
leftover=${leftover/#\/}
|
leftover=${leftover/#\/}
|
||||||
[ -z "$dir" ] && dir='.'
|
[ -z "$dir" ] && dir='.'
|
||||||
@@ -111,7 +111,7 @@ _fzf_complete_telnet() {
|
|||||||
|
|
||||||
_fzf_complete_ssh() {
|
_fzf_complete_ssh() {
|
||||||
_fzf_complete '+m' "$@" < <(
|
_fzf_complete '+m' "$@" < <(
|
||||||
cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host' | command grep -v '*') \
|
command cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host' | command grep -v '*') \
|
||||||
<(command grep -oE '^[^ ]+' ~/.ssh/known_hosts | tr ',' '\n' | awk '{ print $1 " " $1 }') \
|
<(command grep -oE '^[^ ]+' ~/.ssh/known_hosts | tr ',' '\n' | awk '{ print $1 " " $1 }') \
|
||||||
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
||||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
||||||
|
@@ -26,8 +26,8 @@ function fzf_key_bindings
|
|||||||
end
|
end
|
||||||
|
|
||||||
function fzf-history-widget
|
function fzf-history-widget
|
||||||
history | eval (__fzfcmd) +s +m --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS > $TMPDIR/fzf.result
|
history | eval (__fzfcmd) +s +m --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS -q '(commandline)' > $TMPDIR/fzf.result
|
||||||
and commandline (cat $TMPDIR/fzf.result)
|
and commandline -- (cat $TMPDIR/fzf.result)
|
||||||
commandline -f repaint
|
commandline -f repaint
|
||||||
rm -f $TMPDIR/fzf.result
|
rm -f $TMPDIR/fzf.result
|
||||||
end
|
end
|
||||||
|
11
src/Makefile
11
src/Makefile
@@ -33,17 +33,24 @@ endif
|
|||||||
|
|
||||||
all: fzf/$(BINARY)
|
all: fzf/$(BINARY)
|
||||||
|
|
||||||
|
ifeq ($(GOOS),windows)
|
||||||
|
release: fzf/$(BINARY32) fzf/$(BINARY64)
|
||||||
|
-cd fzf && cp $(BINARY32) $(RELEASE32).exe && zip $(RELEASE32).zip $(RELEASE32).exe
|
||||||
|
cd fzf && cp $(BINARY64) $(RELEASE64).exe && zip $(RELEASE64).zip $(RELEASE64).exe && \
|
||||||
|
rm -f $(RELEASE32).exe $(RELEASE64).exe
|
||||||
|
else
|
||||||
release: test fzf/$(BINARY32) fzf/$(BINARY64)
|
release: test fzf/$(BINARY32) fzf/$(BINARY64)
|
||||||
-cd fzf && cp $(BINARY32) $(RELEASE32) && tar -czf $(RELEASE32).tgz $(RELEASE32)
|
-cd fzf && cp $(BINARY32) $(RELEASE32) && tar -czf $(RELEASE32).tgz $(RELEASE32)
|
||||||
cd fzf && cp $(BINARY64) $(RELEASE64) && tar -czf $(RELEASE64).tgz $(RELEASE64) && \
|
cd fzf && cp $(BINARY64) $(RELEASE64) && tar -czf $(RELEASE64).tgz $(RELEASE64) && \
|
||||||
rm -f $(RELEASE32) $(RELEASE64)
|
rm -f $(RELEASE32) $(RELEASE64)
|
||||||
|
endif
|
||||||
|
|
||||||
$(SRCDIR):
|
$(SRCDIR):
|
||||||
mkdir -p $(shell dirname $(SRCDIR))
|
mkdir -p $(shell dirname $(SRCDIR))
|
||||||
ln -s $(ROOTDIR) $(SRCDIR)
|
ln -s $(ROOTDIR) $(SRCDIR)
|
||||||
|
|
||||||
deps: $(SRCDIR) $(SOURCES)
|
deps: $(SRCDIR) $(SOURCES)
|
||||||
cd $(SRCDIR) && go get
|
cd $(SRCDIR) && go get -tags "$(TAGS)"
|
||||||
|
|
||||||
android-build: $(SRCDIR)
|
android-build: $(SRCDIR)
|
||||||
cd $(SRCDIR) && GOARCH=arm GOARM=7 CGO_ENABLED=1 go get
|
cd $(SRCDIR) && GOARCH=arm GOARM=7 CGO_ENABLED=1 go get
|
||||||
@@ -52,7 +59,7 @@ android-build: $(SRCDIR)
|
|||||||
rm -f $(RELEASEARM7)
|
rm -f $(RELEASEARM7)
|
||||||
|
|
||||||
test: deps
|
test: deps
|
||||||
SHELL=/bin/sh go test -v ./...
|
SHELL=/bin/sh GOOS=$(GOOS) go test -v -tags "$(TAGS)" ./...
|
||||||
|
|
||||||
install: $(BINDIR)/fzf
|
install: $(BINDIR)/fzf
|
||||||
|
|
||||||
|
@@ -83,9 +83,11 @@ Third-party libraries used
|
|||||||
|
|
||||||
- [ncurses][ncurses]
|
- [ncurses][ncurses]
|
||||||
- [mattn/go-runewidth](https://github.com/mattn/go-runewidth)
|
- [mattn/go-runewidth](https://github.com/mattn/go-runewidth)
|
||||||
- Licensed under [MIT](http://mattn.mit-license.org/2013)
|
- Licensed under [MIT](http://mattn.mit-license.org)
|
||||||
- [mattn/go-shellwords](https://github.com/mattn/go-shellwords)
|
- [mattn/go-shellwords](https://github.com/mattn/go-shellwords)
|
||||||
- Licensed under [MIT](http://mattn.mit-license.org/2014)
|
- Licensed under [MIT](http://mattn.mit-license.org)
|
||||||
|
- [mattn/go-isatty](https://github.com/mattn/go-isatty)
|
||||||
|
- Licensed under [MIT](http://mattn.mit-license.org)
|
||||||
|
|
||||||
License
|
License
|
||||||
-------
|
-------
|
||||||
|
32
src/ansi.go
32
src/ansi.go
@@ -7,7 +7,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/curses"
|
"github.com/junegunn/fzf/src/tui"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ansiOffset struct {
|
type ansiOffset struct {
|
||||||
@@ -16,9 +16,9 @@ type ansiOffset struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ansiState struct {
|
type ansiState struct {
|
||||||
fg int
|
fg tui.Color
|
||||||
bg int
|
bg tui.Color
|
||||||
attr curses.Attr
|
attr tui.Attr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ansiState) colored() bool {
|
func (s *ansiState) colored() bool {
|
||||||
@@ -35,7 +35,7 @@ func (s *ansiState) equals(t *ansiState) bool {
|
|||||||
var ansiRegex *regexp.Regexp
|
var ansiRegex *regexp.Regexp
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
ansiRegex = regexp.MustCompile("\x1b.[0-9;]*.")
|
ansiRegex = regexp.MustCompile("\x1b\\[[0-9;]*.|[\x0e\x0f]")
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractColor(str string, state *ansiState, proc func(string, *ansiState) bool) (string, *[]ansiOffset, *ansiState) {
|
func extractColor(str string, state *ansiState, proc func(string, *ansiState) bool) (string, *[]ansiOffset, *ansiState) {
|
||||||
@@ -100,7 +100,7 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
|||||||
} else {
|
} else {
|
||||||
state = &ansiState{prevState.fg, prevState.bg, prevState.attr}
|
state = &ansiState{prevState.fg, prevState.bg, prevState.attr}
|
||||||
}
|
}
|
||||||
if ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' {
|
if ansiCode[0] != '\x1b' || ansiCode[len(ansiCode)-1] != 'm' {
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,26 +134,26 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
|||||||
case 49:
|
case 49:
|
||||||
state.bg = -1
|
state.bg = -1
|
||||||
case 1:
|
case 1:
|
||||||
state.attr = curses.Bold
|
state.attr = tui.Bold
|
||||||
case 2:
|
case 2:
|
||||||
state.attr = curses.Dim
|
state.attr = tui.Dim
|
||||||
case 4:
|
case 4:
|
||||||
state.attr = curses.Underline
|
state.attr = tui.Underline
|
||||||
case 5:
|
case 5:
|
||||||
state.attr = curses.Blink
|
state.attr = tui.Blink
|
||||||
case 7:
|
case 7:
|
||||||
state.attr = curses.Reverse
|
state.attr = tui.Reverse
|
||||||
case 0:
|
case 0:
|
||||||
init()
|
init()
|
||||||
default:
|
default:
|
||||||
if num >= 30 && num <= 37 {
|
if num >= 30 && num <= 37 {
|
||||||
state.fg = num - 30
|
state.fg = tui.Color(num - 30)
|
||||||
} else if num >= 40 && num <= 47 {
|
} else if num >= 40 && num <= 47 {
|
||||||
state.bg = num - 40
|
state.bg = tui.Color(num - 40)
|
||||||
} else if num >= 90 && num <= 97 {
|
} else if num >= 90 && num <= 97 {
|
||||||
state.fg = num - 90 + 8
|
state.fg = tui.Color(num - 90 + 8)
|
||||||
} else if num >= 100 && num <= 107 {
|
} else if num >= 100 && num <= 107 {
|
||||||
state.bg = num - 100 + 8
|
state.bg = tui.Color(num - 100 + 8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 1:
|
case 1:
|
||||||
@@ -164,7 +164,7 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
|||||||
state256 = 0
|
state256 = 0
|
||||||
}
|
}
|
||||||
case 2:
|
case 2:
|
||||||
*ptr = num
|
*ptr = tui.Color(num)
|
||||||
state256 = 0
|
state256 = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,14 +4,14 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/curses"
|
"github.com/junegunn/fzf/src/tui"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestExtractColor(t *testing.T) {
|
func TestExtractColor(t *testing.T) {
|
||||||
assert := func(offset ansiOffset, b int32, e int32, fg int, bg int, bold bool) {
|
assert := func(offset ansiOffset, b int32, e int32, fg tui.Color, bg tui.Color, bold bool) {
|
||||||
var attr curses.Attr
|
var attr tui.Attr
|
||||||
if bold {
|
if bold {
|
||||||
attr = curses.Bold
|
attr = tui.Bold
|
||||||
}
|
}
|
||||||
if offset.offset[0] != b || offset.offset[1] != e ||
|
if offset.offset[0] != b || offset.offset[1] != e ||
|
||||||
offset.color.fg != fg || offset.color.bg != bg || offset.color.attr != attr {
|
offset.color.fg != fg || offset.color.bg != bg || offset.color.attr != attr {
|
||||||
|
@@ -8,14 +8,13 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// Current version
|
// Current version
|
||||||
version = "0.15.4"
|
version = "0.15.7"
|
||||||
|
|
||||||
// Core
|
// Core
|
||||||
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
||||||
coordinatorDelayStep time.Duration = 10 * time.Millisecond
|
coordinatorDelayStep time.Duration = 10 * time.Millisecond
|
||||||
|
|
||||||
// Reader
|
// Reader
|
||||||
defaultCommand = `find . -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | sed s/^..//`
|
|
||||||
readerBufferSize = 64 * 1024
|
readerBufferSize = 64 * 1024
|
||||||
|
|
||||||
// Terminal
|
// Terminal
|
||||||
|
8
src/constants_unix.go
Normal file
8
src/constants_unix.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package fzf
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Reader
|
||||||
|
defaultCommand = `find . -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | sed s/^..//`
|
||||||
|
)
|
8
src/constants_windows.go
Normal file
8
src/constants_windows.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package fzf
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Reader
|
||||||
|
defaultCommand = `dir /s/b`
|
||||||
|
)
|
@@ -1,7 +1,10 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -10,23 +13,34 @@ func TestHistory(t *testing.T) {
|
|||||||
|
|
||||||
// Invalid arguments
|
// Invalid arguments
|
||||||
user, _ := user.Current()
|
user, _ := user.Current()
|
||||||
paths := []string{"/etc", "/proc"}
|
var paths []string
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
// GOPATH should exist, so we shouldn't be able to override it
|
||||||
|
paths = []string{os.Getenv("GOPATH")}
|
||||||
|
} else {
|
||||||
|
paths = []string{"/etc", "/proc"}
|
||||||
if user.Name != "root" {
|
if user.Name != "root" {
|
||||||
paths = append(paths, "/etc/sudoers")
|
paths = append(paths, "/etc/sudoers")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
if _, e := NewHistory(path, maxHistory); e == nil {
|
if _, e := NewHistory(path, maxHistory); e == nil {
|
||||||
t.Error("Error expected for: " + path)
|
t.Error("Error expected for: " + path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
f, _ := ioutil.TempFile("", "fzf-history")
|
||||||
|
f.Close()
|
||||||
|
|
||||||
{ // Append lines
|
{ // Append lines
|
||||||
h, _ := NewHistory("/tmp/fzf-history", maxHistory)
|
h, _ := NewHistory(f.Name(), maxHistory)
|
||||||
for i := 0; i < maxHistory+10; i++ {
|
for i := 0; i < maxHistory+10; i++ {
|
||||||
h.append("foobar")
|
h.append("foobar")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{ // Read lines
|
{ // Read lines
|
||||||
h, _ := NewHistory("/tmp/fzf-history", maxHistory)
|
h, _ := NewHistory(f.Name(), maxHistory)
|
||||||
if len(h.lines) != maxHistory+1 {
|
if len(h.lines) != maxHistory+1 {
|
||||||
t.Errorf("Expected: %d, actual: %d\n", maxHistory+1, len(h.lines))
|
t.Errorf("Expected: %d, actual: %d\n", maxHistory+1, len(h.lines))
|
||||||
}
|
}
|
||||||
@@ -37,13 +51,13 @@ func TestHistory(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
{ // Append lines
|
{ // Append lines
|
||||||
h, _ := NewHistory("/tmp/fzf-history", maxHistory)
|
h, _ := NewHistory(f.Name(), maxHistory)
|
||||||
h.append("barfoo")
|
h.append("barfoo")
|
||||||
h.append("")
|
h.append("")
|
||||||
h.append("foobarbaz")
|
h.append("foobarbaz")
|
||||||
}
|
}
|
||||||
{ // Read lines again
|
{ // Read lines again
|
||||||
h, _ := NewHistory("/tmp/fzf-history", maxHistory)
|
h, _ := NewHistory(f.Name(), maxHistory)
|
||||||
if len(h.lines) != maxHistory+1 {
|
if len(h.lines) != maxHistory+1 {
|
||||||
t.Errorf("Expected: %d, actual: %d\n", maxHistory+1, len(h.lines))
|
t.Errorf("Expected: %d, actual: %d\n", maxHistory+1, len(h.lines))
|
||||||
}
|
}
|
||||||
|
@@ -9,7 +9,7 @@ import (
|
|||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/algo"
|
"github.com/junegunn/fzf/src/algo"
|
||||||
"github.com/junegunn/fzf/src/curses"
|
"github.com/junegunn/fzf/src/tui"
|
||||||
|
|
||||||
"github.com/junegunn/go-shellwords"
|
"github.com/junegunn/go-shellwords"
|
||||||
)
|
)
|
||||||
@@ -142,7 +142,7 @@ type Options struct {
|
|||||||
Multi bool
|
Multi bool
|
||||||
Ansi bool
|
Ansi bool
|
||||||
Mouse bool
|
Mouse bool
|
||||||
Theme *curses.ColorTheme
|
Theme *tui.ColorTheme
|
||||||
Black bool
|
Black bool
|
||||||
Reverse bool
|
Reverse bool
|
||||||
Cycle bool
|
Cycle bool
|
||||||
@@ -187,7 +187,7 @@ func defaultOptions() *Options {
|
|||||||
Multi: false,
|
Multi: false,
|
||||||
Ansi: false,
|
Ansi: false,
|
||||||
Mouse: true,
|
Mouse: true,
|
||||||
Theme: curses.EmptyTheme(),
|
Theme: tui.EmptyTheme(),
|
||||||
Black: false,
|
Black: false,
|
||||||
Reverse: false,
|
Reverse: false,
|
||||||
Cycle: false,
|
Cycle: false,
|
||||||
@@ -246,7 +246,7 @@ func nextString(args []string, i *int, message string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func optionalNextString(args []string, i *int) string {
|
func optionalNextString(args []string, i *int) string {
|
||||||
if len(args) > *i+1 {
|
if len(args) > *i+1 && !strings.HasPrefix(args[*i+1], "-") {
|
||||||
*i++
|
*i++
|
||||||
return args[*i]
|
return args[*i]
|
||||||
}
|
}
|
||||||
@@ -358,60 +358,60 @@ func parseKeyChords(str string, message string) map[int]string {
|
|||||||
chord := 0
|
chord := 0
|
||||||
switch lkey {
|
switch lkey {
|
||||||
case "up":
|
case "up":
|
||||||
chord = curses.Up
|
chord = tui.Up
|
||||||
case "down":
|
case "down":
|
||||||
chord = curses.Down
|
chord = tui.Down
|
||||||
case "left":
|
case "left":
|
||||||
chord = curses.Left
|
chord = tui.Left
|
||||||
case "right":
|
case "right":
|
||||||
chord = curses.Right
|
chord = tui.Right
|
||||||
case "enter", "return":
|
case "enter", "return":
|
||||||
chord = curses.CtrlM
|
chord = tui.CtrlM
|
||||||
case "space":
|
case "space":
|
||||||
chord = curses.AltZ + int(' ')
|
chord = tui.AltZ + int(' ')
|
||||||
case "bspace", "bs":
|
case "bspace", "bs":
|
||||||
chord = curses.BSpace
|
chord = tui.BSpace
|
||||||
case "alt-enter", "alt-return":
|
case "alt-enter", "alt-return":
|
||||||
chord = curses.AltEnter
|
chord = tui.AltEnter
|
||||||
case "alt-space":
|
case "alt-space":
|
||||||
chord = curses.AltSpace
|
chord = tui.AltSpace
|
||||||
case "alt-/":
|
case "alt-/":
|
||||||
chord = curses.AltSlash
|
chord = tui.AltSlash
|
||||||
case "alt-bs", "alt-bspace":
|
case "alt-bs", "alt-bspace":
|
||||||
chord = curses.AltBS
|
chord = tui.AltBS
|
||||||
case "tab":
|
case "tab":
|
||||||
chord = curses.Tab
|
chord = tui.Tab
|
||||||
case "btab", "shift-tab":
|
case "btab", "shift-tab":
|
||||||
chord = curses.BTab
|
chord = tui.BTab
|
||||||
case "esc":
|
case "esc":
|
||||||
chord = curses.ESC
|
chord = tui.ESC
|
||||||
case "del":
|
case "del":
|
||||||
chord = curses.Del
|
chord = tui.Del
|
||||||
case "home":
|
case "home":
|
||||||
chord = curses.Home
|
chord = tui.Home
|
||||||
case "end":
|
case "end":
|
||||||
chord = curses.End
|
chord = tui.End
|
||||||
case "pgup", "page-up":
|
case "pgup", "page-up":
|
||||||
chord = curses.PgUp
|
chord = tui.PgUp
|
||||||
case "pgdn", "page-down":
|
case "pgdn", "page-down":
|
||||||
chord = curses.PgDn
|
chord = tui.PgDn
|
||||||
case "shift-left":
|
case "shift-left":
|
||||||
chord = curses.SLeft
|
chord = tui.SLeft
|
||||||
case "shift-right":
|
case "shift-right":
|
||||||
chord = curses.SRight
|
chord = tui.SRight
|
||||||
case "double-click":
|
case "double-click":
|
||||||
chord = curses.DoubleClick
|
chord = tui.DoubleClick
|
||||||
case "f10":
|
case "f10":
|
||||||
chord = curses.F10
|
chord = tui.F10
|
||||||
default:
|
default:
|
||||||
if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
|
if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
|
||||||
chord = curses.CtrlA + int(lkey[5]) - 'a'
|
chord = tui.CtrlA + int(lkey[5]) - 'a'
|
||||||
} else if len(key) == 5 && strings.HasPrefix(lkey, "alt-") && isAlphabet(lkey[4]) {
|
} else if len(key) == 5 && strings.HasPrefix(lkey, "alt-") && isAlphabet(lkey[4]) {
|
||||||
chord = curses.AltA + int(lkey[4]) - 'a'
|
chord = tui.AltA + int(lkey[4]) - 'a'
|
||||||
} else if len(key) == 2 && strings.HasPrefix(lkey, "f") && key[1] >= '1' && key[1] <= '9' {
|
} else if len(key) == 2 && strings.HasPrefix(lkey, "f") && key[1] >= '1' && key[1] <= '9' {
|
||||||
chord = curses.F1 + int(key[1]) - '1'
|
chord = tui.F1 + int(key[1]) - '1'
|
||||||
} else if utf8.RuneCountInString(key) == 1 {
|
} else if utf8.RuneCountInString(key) == 1 {
|
||||||
chord = curses.AltZ + int([]rune(key)[0])
|
chord = tui.AltZ + int([]rune(key)[0])
|
||||||
} else {
|
} else {
|
||||||
errorExit("unsupported key: " + key)
|
errorExit("unsupported key: " + key)
|
||||||
}
|
}
|
||||||
@@ -458,7 +458,7 @@ func parseTiebreak(str string) []criterion {
|
|||||||
return criteria
|
return criteria
|
||||||
}
|
}
|
||||||
|
|
||||||
func dupeTheme(theme *curses.ColorTheme) *curses.ColorTheme {
|
func dupeTheme(theme *tui.ColorTheme) *tui.ColorTheme {
|
||||||
if theme != nil {
|
if theme != nil {
|
||||||
dupe := *theme
|
dupe := *theme
|
||||||
return &dupe
|
return &dupe
|
||||||
@@ -466,16 +466,16 @@ func dupeTheme(theme *curses.ColorTheme) *curses.ColorTheme {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseTheme(defaultTheme *curses.ColorTheme, str string) *curses.ColorTheme {
|
func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
|
||||||
theme := dupeTheme(defaultTheme)
|
theme := dupeTheme(defaultTheme)
|
||||||
for _, str := range strings.Split(strings.ToLower(str), ",") {
|
for _, str := range strings.Split(strings.ToLower(str), ",") {
|
||||||
switch str {
|
switch str {
|
||||||
case "dark":
|
case "dark":
|
||||||
theme = dupeTheme(curses.Dark256)
|
theme = dupeTheme(tui.Dark256)
|
||||||
case "light":
|
case "light":
|
||||||
theme = dupeTheme(curses.Light256)
|
theme = dupeTheme(tui.Light256)
|
||||||
case "16":
|
case "16":
|
||||||
theme = dupeTheme(curses.Default16)
|
theme = dupeTheme(tui.Default16)
|
||||||
case "bw", "no":
|
case "bw", "no":
|
||||||
theme = nil
|
theme = nil
|
||||||
default:
|
default:
|
||||||
@@ -495,14 +495,12 @@ func parseTheme(defaultTheme *curses.ColorTheme, str string) *curses.ColorTheme
|
|||||||
if err != nil || ansi32 < -1 || ansi32 > 255 {
|
if err != nil || ansi32 < -1 || ansi32 > 255 {
|
||||||
fail()
|
fail()
|
||||||
}
|
}
|
||||||
ansi := int16(ansi32)
|
ansi := tui.Color(ansi32)
|
||||||
switch pair[0] {
|
switch pair[0] {
|
||||||
case "fg":
|
case "fg":
|
||||||
theme.Fg = ansi
|
theme.Fg = ansi
|
||||||
theme.UseDefault = theme.UseDefault && ansi < 0
|
|
||||||
case "bg":
|
case "bg":
|
||||||
theme.Bg = ansi
|
theme.Bg = ansi
|
||||||
theme.UseDefault = theme.UseDefault && ansi < 0
|
|
||||||
case "fg+":
|
case "fg+":
|
||||||
theme.Current = ansi
|
theme.Current = ansi
|
||||||
case "bg+":
|
case "bg+":
|
||||||
@@ -574,9 +572,9 @@ func parseKeymap(keymap map[int]actionType, execmap map[int]string, str string)
|
|||||||
}
|
}
|
||||||
var key int
|
var key int
|
||||||
if len(pair[0]) == 1 && pair[0][0] == escapedColon {
|
if len(pair[0]) == 1 && pair[0][0] == escapedColon {
|
||||||
key = ':' + curses.AltZ
|
key = ':' + tui.AltZ
|
||||||
} else if len(pair[0]) == 1 && pair[0][0] == escapedComma {
|
} else if len(pair[0]) == 1 && pair[0][0] == escapedComma {
|
||||||
key = ',' + curses.AltZ
|
key = ',' + tui.AltZ
|
||||||
} else {
|
} else {
|
||||||
keys := parseKeyChords(pair[0], "key name required")
|
keys := parseKeyChords(pair[0], "key name required")
|
||||||
key = firstKey(keys)
|
key = firstKey(keys)
|
||||||
@@ -870,7 +868,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
case "--color":
|
case "--color":
|
||||||
spec := optionalNextString(allArgs, &i)
|
spec := optionalNextString(allArgs, &i)
|
||||||
if len(spec) == 0 {
|
if len(spec) == 0 {
|
||||||
opts.Theme = curses.EmptyTheme()
|
opts.Theme = tui.EmptyTheme()
|
||||||
} else {
|
} else {
|
||||||
opts.Theme = parseTheme(opts.Theme, spec)
|
opts.Theme = parseTheme(opts.Theme, spec)
|
||||||
}
|
}
|
||||||
@@ -907,7 +905,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
case "+c", "--no-color":
|
case "+c", "--no-color":
|
||||||
opts.Theme = nil
|
opts.Theme = nil
|
||||||
case "+2", "--no-256":
|
case "+2", "--no-256":
|
||||||
opts.Theme = curses.Default16
|
opts.Theme = tui.Default16
|
||||||
case "--black":
|
case "--black":
|
||||||
opts.Black = true
|
opts.Black = true
|
||||||
case "--no-black":
|
case "--no-black":
|
||||||
@@ -1073,11 +1071,11 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
func postProcessOptions(opts *Options) {
|
func postProcessOptions(opts *Options) {
|
||||||
// Default actions for CTRL-N / CTRL-P when --history is set
|
// Default actions for CTRL-N / CTRL-P when --history is set
|
||||||
if opts.History != nil {
|
if opts.History != nil {
|
||||||
if _, prs := opts.Keymap[curses.CtrlP]; !prs {
|
if _, prs := opts.Keymap[tui.CtrlP]; !prs {
|
||||||
opts.Keymap[curses.CtrlP] = actPreviousHistory
|
opts.Keymap[tui.CtrlP] = actPreviousHistory
|
||||||
}
|
}
|
||||||
if _, prs := opts.Keymap[curses.CtrlN]; !prs {
|
if _, prs := opts.Keymap[tui.CtrlN]; !prs {
|
||||||
opts.Keymap[curses.CtrlN] = actNextHistory
|
opts.Keymap[tui.CtrlN] = actNextHistory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,9 +2,10 @@ package fzf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/curses"
|
"github.com/junegunn/fzf/src/tui"
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -133,48 +134,48 @@ func TestParseKeys(t *testing.T) {
|
|||||||
if len(pairs) != 11 {
|
if len(pairs) != 11 {
|
||||||
t.Error(11)
|
t.Error(11)
|
||||||
}
|
}
|
||||||
check(curses.CtrlZ, "ctrl-z")
|
check(tui.CtrlZ, "ctrl-z")
|
||||||
check(curses.AltZ, "alt-z")
|
check(tui.AltZ, "alt-z")
|
||||||
check(curses.F2, "f2")
|
check(tui.F2, "f2")
|
||||||
check(curses.AltZ+'@', "@")
|
check(tui.AltZ+'@', "@")
|
||||||
check(curses.AltA, "Alt-a")
|
check(tui.AltA, "Alt-a")
|
||||||
check(curses.AltZ+'!', "!")
|
check(tui.AltZ+'!', "!")
|
||||||
check(curses.CtrlA+'g'-'a', "ctrl-G")
|
check(tui.CtrlA+'g'-'a', "ctrl-G")
|
||||||
check(curses.AltZ+'J', "J")
|
check(tui.AltZ+'J', "J")
|
||||||
check(curses.AltZ+'g', "g")
|
check(tui.AltZ+'g', "g")
|
||||||
check(curses.AltEnter, "ALT-enter")
|
check(tui.AltEnter, "ALT-enter")
|
||||||
check(curses.AltSpace, "alt-SPACE")
|
check(tui.AltSpace, "alt-SPACE")
|
||||||
|
|
||||||
// Synonyms
|
// Synonyms
|
||||||
pairs = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "")
|
pairs = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "")
|
||||||
if len(pairs) != 9 {
|
if len(pairs) != 9 {
|
||||||
t.Error(9)
|
t.Error(9)
|
||||||
}
|
}
|
||||||
check(curses.CtrlM, "Return")
|
check(tui.CtrlM, "Return")
|
||||||
check(curses.AltZ+' ', "space")
|
check(tui.AltZ+' ', "space")
|
||||||
check(curses.Tab, "tab")
|
check(tui.Tab, "tab")
|
||||||
check(curses.BTab, "btab")
|
check(tui.BTab, "btab")
|
||||||
check(curses.ESC, "esc")
|
check(tui.ESC, "esc")
|
||||||
check(curses.Up, "up")
|
check(tui.Up, "up")
|
||||||
check(curses.Down, "down")
|
check(tui.Down, "down")
|
||||||
check(curses.Left, "left")
|
check(tui.Left, "left")
|
||||||
check(curses.Right, "right")
|
check(tui.Right, "right")
|
||||||
|
|
||||||
pairs = parseKeyChords("Tab,Ctrl-I,PgUp,page-up,pgdn,Page-Down,Home,End,Alt-BS,Alt-BSpace,shift-left,shift-right,btab,shift-tab,return,Enter,bspace", "")
|
pairs = parseKeyChords("Tab,Ctrl-I,PgUp,page-up,pgdn,Page-Down,Home,End,Alt-BS,Alt-BSpace,shift-left,shift-right,btab,shift-tab,return,Enter,bspace", "")
|
||||||
if len(pairs) != 11 {
|
if len(pairs) != 11 {
|
||||||
t.Error(11)
|
t.Error(11)
|
||||||
}
|
}
|
||||||
check(curses.Tab, "Ctrl-I")
|
check(tui.Tab, "Ctrl-I")
|
||||||
check(curses.PgUp, "page-up")
|
check(tui.PgUp, "page-up")
|
||||||
check(curses.PgDn, "Page-Down")
|
check(tui.PgDn, "Page-Down")
|
||||||
check(curses.Home, "Home")
|
check(tui.Home, "Home")
|
||||||
check(curses.End, "End")
|
check(tui.End, "End")
|
||||||
check(curses.AltBS, "Alt-BSpace")
|
check(tui.AltBS, "Alt-BSpace")
|
||||||
check(curses.SLeft, "shift-left")
|
check(tui.SLeft, "shift-left")
|
||||||
check(curses.SRight, "shift-right")
|
check(tui.SRight, "shift-right")
|
||||||
check(curses.BTab, "shift-tab")
|
check(tui.BTab, "shift-tab")
|
||||||
check(curses.CtrlM, "Enter")
|
check(tui.CtrlM, "Enter")
|
||||||
check(curses.BSpace, "bspace")
|
check(tui.BSpace, "bspace")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseKeysWithComma(t *testing.T) {
|
func TestParseKeysWithComma(t *testing.T) {
|
||||||
@@ -191,36 +192,36 @@ func TestParseKeysWithComma(t *testing.T) {
|
|||||||
|
|
||||||
pairs := parseKeyChords(",", "")
|
pairs := parseKeyChords(",", "")
|
||||||
checkN(len(pairs), 1)
|
checkN(len(pairs), 1)
|
||||||
check(pairs, curses.AltZ+',', ",")
|
check(pairs, tui.AltZ+',', ",")
|
||||||
|
|
||||||
pairs = parseKeyChords(",,a,b", "")
|
pairs = parseKeyChords(",,a,b", "")
|
||||||
checkN(len(pairs), 3)
|
checkN(len(pairs), 3)
|
||||||
check(pairs, curses.AltZ+'a', "a")
|
check(pairs, tui.AltZ+'a', "a")
|
||||||
check(pairs, curses.AltZ+'b', "b")
|
check(pairs, tui.AltZ+'b', "b")
|
||||||
check(pairs, curses.AltZ+',', ",")
|
check(pairs, tui.AltZ+',', ",")
|
||||||
|
|
||||||
pairs = parseKeyChords("a,b,,", "")
|
pairs = parseKeyChords("a,b,,", "")
|
||||||
checkN(len(pairs), 3)
|
checkN(len(pairs), 3)
|
||||||
check(pairs, curses.AltZ+'a', "a")
|
check(pairs, tui.AltZ+'a', "a")
|
||||||
check(pairs, curses.AltZ+'b', "b")
|
check(pairs, tui.AltZ+'b', "b")
|
||||||
check(pairs, curses.AltZ+',', ",")
|
check(pairs, tui.AltZ+',', ",")
|
||||||
|
|
||||||
pairs = parseKeyChords("a,,,b", "")
|
pairs = parseKeyChords("a,,,b", "")
|
||||||
checkN(len(pairs), 3)
|
checkN(len(pairs), 3)
|
||||||
check(pairs, curses.AltZ+'a', "a")
|
check(pairs, tui.AltZ+'a', "a")
|
||||||
check(pairs, curses.AltZ+'b', "b")
|
check(pairs, tui.AltZ+'b', "b")
|
||||||
check(pairs, curses.AltZ+',', ",")
|
check(pairs, tui.AltZ+',', ",")
|
||||||
|
|
||||||
pairs = parseKeyChords("a,,,b,c", "")
|
pairs = parseKeyChords("a,,,b,c", "")
|
||||||
checkN(len(pairs), 4)
|
checkN(len(pairs), 4)
|
||||||
check(pairs, curses.AltZ+'a', "a")
|
check(pairs, tui.AltZ+'a', "a")
|
||||||
check(pairs, curses.AltZ+'b', "b")
|
check(pairs, tui.AltZ+'b', "b")
|
||||||
check(pairs, curses.AltZ+'c', "c")
|
check(pairs, tui.AltZ+'c', "c")
|
||||||
check(pairs, curses.AltZ+',', ",")
|
check(pairs, tui.AltZ+',', ",")
|
||||||
|
|
||||||
pairs = parseKeyChords(",,,", "")
|
pairs = parseKeyChords(",,,", "")
|
||||||
checkN(len(pairs), 1)
|
checkN(len(pairs), 1)
|
||||||
check(pairs, curses.AltZ+',', ",")
|
check(pairs, tui.AltZ+',', ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBind(t *testing.T) {
|
func TestBind(t *testing.T) {
|
||||||
@@ -236,41 +237,41 @@ func TestBind(t *testing.T) {
|
|||||||
}
|
}
|
||||||
keymap := defaultKeymap()
|
keymap := defaultKeymap()
|
||||||
execmap := make(map[int]string)
|
execmap := make(map[int]string)
|
||||||
check(actBeginningOfLine, keymap[curses.CtrlA])
|
check(actBeginningOfLine, keymap[tui.CtrlA])
|
||||||
parseKeymap(keymap, execmap,
|
parseKeymap(keymap, execmap,
|
||||||
"ctrl-a:kill-line,ctrl-b:toggle-sort,c:page-up,alt-z:page-down,"+
|
"ctrl-a:kill-line,ctrl-b:toggle-sort,c:page-up,alt-z:page-down,"+
|
||||||
"f1:execute(ls {}),f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
|
"f1:execute(ls {}),f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
|
||||||
"alt-a:execute@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};"+
|
"alt-a:execute@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};"+
|
||||||
",,:abort,::accept,X:execute:\nfoobar,Y:execute(baz)")
|
",,:abort,::accept,X:execute:\nfoobar,Y:execute(baz)")
|
||||||
check(actKillLine, keymap[curses.CtrlA])
|
check(actKillLine, keymap[tui.CtrlA])
|
||||||
check(actToggleSort, keymap[curses.CtrlB])
|
check(actToggleSort, keymap[tui.CtrlB])
|
||||||
check(actPageUp, keymap[curses.AltZ+'c'])
|
check(actPageUp, keymap[tui.AltZ+'c'])
|
||||||
check(actAbort, keymap[curses.AltZ+','])
|
check(actAbort, keymap[tui.AltZ+','])
|
||||||
check(actAccept, keymap[curses.AltZ+':'])
|
check(actAccept, keymap[tui.AltZ+':'])
|
||||||
check(actPageDown, keymap[curses.AltZ])
|
check(actPageDown, keymap[tui.AltZ])
|
||||||
check(actExecute, keymap[curses.F1])
|
check(actExecute, keymap[tui.F1])
|
||||||
check(actExecute, keymap[curses.F2])
|
check(actExecute, keymap[tui.F2])
|
||||||
check(actExecute, keymap[curses.F3])
|
check(actExecute, keymap[tui.F3])
|
||||||
check(actExecute, keymap[curses.F4])
|
check(actExecute, keymap[tui.F4])
|
||||||
checkString("ls {}", execmap[curses.F1])
|
checkString("ls {}", execmap[tui.F1])
|
||||||
checkString("echo {}, {}, {}", execmap[curses.F2])
|
checkString("echo {}, {}, {}", execmap[tui.F2])
|
||||||
checkString("echo '({})'", execmap[curses.F3])
|
checkString("echo '({})'", execmap[tui.F3])
|
||||||
checkString("less {}", execmap[curses.F4])
|
checkString("less {}", execmap[tui.F4])
|
||||||
checkString("echo (,),[,],/,:,;,%,{}", execmap[curses.AltA])
|
checkString("echo (,),[,],/,:,;,%,{}", execmap[tui.AltA])
|
||||||
checkString("echo (,),[,],/,:,@,%,{}", execmap[curses.AltB])
|
checkString("echo (,),[,],/,:,@,%,{}", execmap[tui.AltB])
|
||||||
checkString("\nfoobar,Y:execute(baz)", execmap[curses.AltZ+'X'])
|
checkString("\nfoobar,Y:execute(baz)", execmap[tui.AltZ+'X'])
|
||||||
|
|
||||||
for idx, char := range []rune{'~', '!', '@', '#', '$', '%', '^', '&', '*', '|', ';', '/'} {
|
for idx, char := range []rune{'~', '!', '@', '#', '$', '%', '^', '&', '*', '|', ';', '/'} {
|
||||||
parseKeymap(keymap, execmap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char))
|
parseKeymap(keymap, execmap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char))
|
||||||
checkString("foobar", execmap[curses.AltZ+int([]rune(fmt.Sprintf("%d", idx%10))[0])])
|
checkString("foobar", execmap[tui.AltZ+int([]rune(fmt.Sprintf("%d", idx%10))[0])])
|
||||||
}
|
}
|
||||||
|
|
||||||
parseKeymap(keymap, execmap, "f1:abort")
|
parseKeymap(keymap, execmap, "f1:abort")
|
||||||
check(actAbort, keymap[curses.F1])
|
check(actAbort, keymap[tui.F1])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestColorSpec(t *testing.T) {
|
func TestColorSpec(t *testing.T) {
|
||||||
theme := curses.Dark256
|
theme := tui.Dark256
|
||||||
dark := parseTheme(theme, "dark")
|
dark := parseTheme(theme, "dark")
|
||||||
if *dark != *theme {
|
if *dark != *theme {
|
||||||
t.Errorf("colors should be equivalent")
|
t.Errorf("colors should be equivalent")
|
||||||
@@ -283,7 +284,7 @@ func TestColorSpec(t *testing.T) {
|
|||||||
if *light == *theme {
|
if *light == *theme {
|
||||||
t.Errorf("should not be equivalent")
|
t.Errorf("should not be equivalent")
|
||||||
}
|
}
|
||||||
if *light != *curses.Light256 {
|
if *light != *tui.Light256 {
|
||||||
t.Errorf("colors should be equivalent")
|
t.Errorf("colors should be equivalent")
|
||||||
}
|
}
|
||||||
if light == theme {
|
if light == theme {
|
||||||
@@ -294,29 +295,23 @@ func TestColorSpec(t *testing.T) {
|
|||||||
if customized.Fg != 231 || customized.Bg != 232 {
|
if customized.Fg != 231 || customized.Bg != 232 {
|
||||||
t.Errorf("color not customized")
|
t.Errorf("color not customized")
|
||||||
}
|
}
|
||||||
if *curses.Dark256 == *customized {
|
if *tui.Dark256 == *customized {
|
||||||
t.Errorf("colors should not be equivalent")
|
t.Errorf("colors should not be equivalent")
|
||||||
}
|
}
|
||||||
customized.Fg = curses.Dark256.Fg
|
customized.Fg = tui.Dark256.Fg
|
||||||
customized.Bg = curses.Dark256.Bg
|
customized.Bg = tui.Dark256.Bg
|
||||||
if *curses.Dark256 == *customized {
|
if *tui.Dark256 != *customized {
|
||||||
t.Errorf("colors should now be equivalent")
|
t.Errorf("colors should now be equivalent: %v, %v", tui.Dark256, customized)
|
||||||
}
|
}
|
||||||
|
|
||||||
customized = parseTheme(theme, "fg:231,dark,bg:232")
|
customized = parseTheme(theme, "fg:231,dark,bg:232")
|
||||||
if customized.Fg != curses.Dark256.Fg || customized.Bg == curses.Dark256.Bg {
|
if customized.Fg != tui.Dark256.Fg || customized.Bg == tui.Dark256.Bg {
|
||||||
t.Errorf("color not customized")
|
t.Errorf("color not customized")
|
||||||
}
|
}
|
||||||
if customized.UseDefault {
|
|
||||||
t.Errorf("not using default colors")
|
|
||||||
}
|
|
||||||
if !curses.Dark256.UseDefault {
|
|
||||||
t.Errorf("using default colors")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseNilTheme(t *testing.T) {
|
func TestParseNilTheme(t *testing.T) {
|
||||||
var theme *curses.ColorTheme
|
var theme *tui.ColorTheme
|
||||||
newTheme := parseTheme(theme, "prompt:12")
|
newTheme := parseTheme(theme, "prompt:12")
|
||||||
if newTheme != nil {
|
if newTheme != nil {
|
||||||
t.Errorf("color is disabled. keep it that way.")
|
t.Errorf("color is disabled. keep it that way.")
|
||||||
@@ -336,21 +331,23 @@ func TestDefaultCtrlNP(t *testing.T) {
|
|||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
check([]string{}, curses.CtrlN, actDown)
|
check([]string{}, tui.CtrlN, actDown)
|
||||||
check([]string{}, curses.CtrlP, actUp)
|
check([]string{}, tui.CtrlP, actUp)
|
||||||
|
|
||||||
check([]string{"--bind=ctrl-n:accept"}, curses.CtrlN, actAccept)
|
check([]string{"--bind=ctrl-n:accept"}, tui.CtrlN, actAccept)
|
||||||
check([]string{"--bind=ctrl-p:accept"}, curses.CtrlP, actAccept)
|
check([]string{"--bind=ctrl-p:accept"}, tui.CtrlP, actAccept)
|
||||||
|
|
||||||
hist := "--history=/tmp/fzf-history"
|
f, _ := ioutil.TempFile("", "fzf-history")
|
||||||
check([]string{hist}, curses.CtrlN, actNextHistory)
|
f.Close()
|
||||||
check([]string{hist}, curses.CtrlP, actPreviousHistory)
|
hist := "--history=" + f.Name()
|
||||||
|
check([]string{hist}, tui.CtrlN, actNextHistory)
|
||||||
|
check([]string{hist}, tui.CtrlP, actPreviousHistory)
|
||||||
|
|
||||||
check([]string{hist, "--bind=ctrl-n:accept"}, curses.CtrlN, actAccept)
|
check([]string{hist, "--bind=ctrl-n:accept"}, tui.CtrlN, actAccept)
|
||||||
check([]string{hist, "--bind=ctrl-n:accept"}, curses.CtrlP, actPreviousHistory)
|
check([]string{hist, "--bind=ctrl-n:accept"}, tui.CtrlP, actPreviousHistory)
|
||||||
|
|
||||||
check([]string{hist, "--bind=ctrl-p:accept"}, curses.CtrlN, actNextHistory)
|
check([]string{hist, "--bind=ctrl-p:accept"}, tui.CtrlN, actNextHistory)
|
||||||
check([]string{hist, "--bind=ctrl-p:accept"}, curses.CtrlP, actAccept)
|
check([]string{hist, "--bind=ctrl-p:accept"}, tui.CtrlP, actAccept)
|
||||||
}
|
}
|
||||||
|
|
||||||
func optsFor(words ...string) *Options {
|
func optsFor(words ...string) *Options {
|
||||||
|
@@ -39,9 +39,15 @@ func (r *Reader) feed(src io.Reader) {
|
|||||||
// ReadBytes returns err != nil if and only if the returned data does not
|
// ReadBytes returns err != nil if and only if the returned data does not
|
||||||
// end in delim.
|
// end in delim.
|
||||||
bytea, err := reader.ReadBytes(delim)
|
bytea, err := reader.ReadBytes(delim)
|
||||||
|
byteaLen := len(bytea)
|
||||||
if len(bytea) > 0 {
|
if len(bytea) > 0 {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
bytea = bytea[:len(bytea)-1]
|
// get rid of carriage return if under Windows:
|
||||||
|
if util.IsWindows() && byteaLen >= 2 && bytea[byteaLen-2] == byte('\r') {
|
||||||
|
bytea = bytea[:byteaLen-2]
|
||||||
|
} else {
|
||||||
|
bytea = bytea[:byteaLen-1]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if r.pusher(bytea) {
|
if r.pusher(bytea) {
|
||||||
r.eventBox.Set(EvtReadNew, nil)
|
r.eventBox.Set(EvtReadNew, nil)
|
||||||
|
@@ -5,7 +5,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/curses"
|
"github.com/junegunn/fzf/src/tui"
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,8 +14,8 @@ type Offset [2]int32
|
|||||||
|
|
||||||
type colorOffset struct {
|
type colorOffset struct {
|
||||||
offset [2]int32
|
offset [2]int32
|
||||||
color int
|
color tui.ColorPair
|
||||||
attr curses.Attr
|
attr tui.Attr
|
||||||
index int32
|
index int32
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ func buildResult(item *Item, offsets []Offset, score int, trimLen int) *Result {
|
|||||||
case byLength:
|
case byLength:
|
||||||
// If offsets is empty, trimLen will be 0, but we don't care
|
// If offsets is empty, trimLen will be 0, but we don't care
|
||||||
val = util.AsUint16(trimLen)
|
val = util.AsUint16(trimLen)
|
||||||
case byBegin:
|
case byBegin, byEnd:
|
||||||
if validOffsetFound {
|
if validOffsetFound {
|
||||||
whitePrefixLen := 0
|
whitePrefixLen := 0
|
||||||
for idx := 0; idx < numChars; idx++ {
|
for idx := 0; idx < numChars; idx++ {
|
||||||
@@ -67,11 +67,11 @@ func buildResult(item *Item, offsets []Offset, score int, trimLen int) *Result {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if criterion == byBegin {
|
||||||
val = util.AsUint16(minBegin - whitePrefixLen)
|
val = util.AsUint16(minBegin - whitePrefixLen)
|
||||||
|
} else {
|
||||||
|
val = util.AsUint16(math.MaxUint16 - math.MaxUint16*(maxEnd-whitePrefixLen)/trimLen)
|
||||||
}
|
}
|
||||||
case byEnd:
|
|
||||||
if validOffsetFound {
|
|
||||||
val = util.AsUint16(1 + numChars - maxEnd)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result.rank.points[idx] = val
|
result.rank.points[idx] = val
|
||||||
@@ -92,13 +92,13 @@ func minRank() rank {
|
|||||||
return rank{index: 0, points: [4]uint16{math.MaxUint16, 0, 0, 0}}
|
return rank{index: 0, points: [4]uint16{math.MaxUint16, 0, 0, 0}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (result *Result) colorOffsets(matchOffsets []Offset, color int, attr curses.Attr, current bool) []colorOffset {
|
func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme, color tui.ColorPair, attr tui.Attr, current bool) []colorOffset {
|
||||||
itemColors := result.item.Colors()
|
itemColors := result.item.Colors()
|
||||||
|
|
||||||
|
// No ANSI code, or --color=no
|
||||||
if len(itemColors) == 0 {
|
if len(itemColors) == 0 {
|
||||||
var offsets []colorOffset
|
var offsets []colorOffset
|
||||||
for _, off := range matchOffsets {
|
for _, off := range matchOffsets {
|
||||||
|
|
||||||
offsets = append(offsets, colorOffset{offset: [2]int32{off[0], off[1]}, color: color, attr: attr})
|
offsets = append(offsets, colorOffset{offset: [2]int32{off[0], off[1]}, color: color, attr: attr})
|
||||||
}
|
}
|
||||||
return offsets
|
return offsets
|
||||||
@@ -147,25 +147,27 @@ func (result *Result) colorOffsets(matchOffsets []Offset, color int, attr curses
|
|||||||
} else {
|
} else {
|
||||||
ansi := itemColors[curr-1]
|
ansi := itemColors[curr-1]
|
||||||
fg := ansi.color.fg
|
fg := ansi.color.fg
|
||||||
|
bg := ansi.color.bg
|
||||||
|
if theme != nil {
|
||||||
if fg == -1 {
|
if fg == -1 {
|
||||||
if current {
|
if current {
|
||||||
fg = curses.CurrentFG
|
fg = theme.Current
|
||||||
} else {
|
} else {
|
||||||
fg = curses.FG
|
fg = theme.Fg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bg := ansi.color.bg
|
|
||||||
if bg == -1 {
|
if bg == -1 {
|
||||||
if current {
|
if current {
|
||||||
bg = curses.DarkBG
|
bg = theme.DarkBg
|
||||||
} else {
|
} else {
|
||||||
bg = curses.BG
|
bg = theme.Bg
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
colors = append(colors, colorOffset{
|
colors = append(colors, colorOffset{
|
||||||
offset: [2]int32{int32(start), int32(idx)},
|
offset: [2]int32{int32(start), int32(idx)},
|
||||||
color: curses.PairFor(fg, bg),
|
color: tui.PairFor(fg, bg),
|
||||||
attr: ansi.color.attr | attr})
|
attr: ansi.color.attr.Merge(attr)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
// +build !tcell
|
||||||
|
|
||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -5,7 +7,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/curses"
|
"github.com/junegunn/fzf/src/tui"
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -98,26 +100,26 @@ func TestColorOffset(t *testing.T) {
|
|||||||
item: &Item{
|
item: &Item{
|
||||||
colors: &[]ansiOffset{
|
colors: &[]ansiOffset{
|
||||||
ansiOffset{[2]int32{0, 20}, ansiState{1, 5, 0}},
|
ansiOffset{[2]int32{0, 20}, ansiState{1, 5, 0}},
|
||||||
ansiOffset{[2]int32{22, 27}, ansiState{2, 6, curses.Bold}},
|
ansiOffset{[2]int32{22, 27}, ansiState{2, 6, tui.Bold}},
|
||||||
ansiOffset{[2]int32{30, 32}, ansiState{3, 7, 0}},
|
ansiOffset{[2]int32{30, 32}, ansiState{3, 7, 0}},
|
||||||
ansiOffset{[2]int32{33, 40}, ansiState{4, 8, curses.Bold}}}}}
|
ansiOffset{[2]int32{33, 40}, ansiState{4, 8, tui.Bold}}}}}
|
||||||
// [{[0 5] 9 false} {[5 15] 99 false} {[15 20] 9 false} {[22 25] 10 true} {[25 35] 99 false} {[35 40] 11 true}]
|
// [{[0 5] 9 false} {[5 15] 99 false} {[15 20] 9 false} {[22 25] 10 true} {[25 35] 99 false} {[35 40] 11 true}]
|
||||||
|
|
||||||
colors := item.colorOffsets(offsets, 99, 0, true)
|
colors := item.colorOffsets(offsets, tui.Dark256, 99, 0, true)
|
||||||
assert := func(idx int, b int32, e int32, c int, bold bool) {
|
assert := func(idx int, b int32, e int32, c tui.ColorPair, bold bool) {
|
||||||
var attr curses.Attr
|
var attr tui.Attr
|
||||||
if bold {
|
if bold {
|
||||||
attr = curses.Bold
|
attr = tui.Bold
|
||||||
}
|
}
|
||||||
o := colors[idx]
|
o := colors[idx]
|
||||||
if o.offset[0] != b || o.offset[1] != e || o.color != c || o.attr != attr {
|
if o.offset[0] != b || o.offset[1] != e || o.color != c || o.attr != attr {
|
||||||
t.Error(o)
|
t.Error(o)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert(0, 0, 5, curses.ColUser, false)
|
assert(0, 0, 5, tui.ColUser, false)
|
||||||
assert(1, 5, 15, 99, false)
|
assert(1, 5, 15, 99, false)
|
||||||
assert(2, 15, 20, curses.ColUser, false)
|
assert(2, 15, 20, tui.ColUser, false)
|
||||||
assert(3, 22, 25, curses.ColUser+1, true)
|
assert(3, 22, 25, tui.ColUser+1, true)
|
||||||
assert(4, 25, 35, 99, false)
|
assert(4, 25, 35, 99, false)
|
||||||
assert(5, 35, 40, curses.ColUser+2, true)
|
assert(5, 35, 40, tui.ColUser+2, true)
|
||||||
}
|
}
|
||||||
|
192
src/terminal.go
192
src/terminal.go
@@ -7,12 +7,13 @@ import (
|
|||||||
"os/signal"
|
"os/signal"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
C "github.com/junegunn/fzf/src/curses"
|
"github.com/junegunn/fzf/src/tui"
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
|
||||||
"github.com/junegunn/go-runewidth"
|
"github.com/junegunn/go-runewidth"
|
||||||
@@ -69,9 +70,9 @@ type Terminal struct {
|
|||||||
header0 []string
|
header0 []string
|
||||||
ansi bool
|
ansi bool
|
||||||
margin [4]sizeSpec
|
margin [4]sizeSpec
|
||||||
window *C.Window
|
window *tui.Window
|
||||||
bwindow *C.Window
|
bwindow *tui.Window
|
||||||
pwindow *C.Window
|
pwindow *tui.Window
|
||||||
count int
|
count int
|
||||||
progress int
|
progress int
|
||||||
reading bool
|
reading bool
|
||||||
@@ -90,6 +91,7 @@ type Terminal struct {
|
|||||||
suppress bool
|
suppress bool
|
||||||
startChan chan bool
|
startChan chan bool
|
||||||
slab *util.Slab
|
slab *util.Slab
|
||||||
|
theme *tui.ColorTheme
|
||||||
}
|
}
|
||||||
|
|
||||||
type selectedItem struct {
|
type selectedItem struct {
|
||||||
@@ -186,51 +188,51 @@ const (
|
|||||||
|
|
||||||
func defaultKeymap() map[int]actionType {
|
func defaultKeymap() map[int]actionType {
|
||||||
keymap := make(map[int]actionType)
|
keymap := make(map[int]actionType)
|
||||||
keymap[C.Invalid] = actInvalid
|
keymap[tui.Invalid] = actInvalid
|
||||||
keymap[C.CtrlA] = actBeginningOfLine
|
keymap[tui.CtrlA] = actBeginningOfLine
|
||||||
keymap[C.CtrlB] = actBackwardChar
|
keymap[tui.CtrlB] = actBackwardChar
|
||||||
keymap[C.CtrlC] = actAbort
|
keymap[tui.CtrlC] = actAbort
|
||||||
keymap[C.CtrlG] = actAbort
|
keymap[tui.CtrlG] = actAbort
|
||||||
keymap[C.CtrlQ] = actAbort
|
keymap[tui.CtrlQ] = actAbort
|
||||||
keymap[C.ESC] = actAbort
|
keymap[tui.ESC] = actAbort
|
||||||
keymap[C.CtrlD] = actDeleteCharEOF
|
keymap[tui.CtrlD] = actDeleteCharEOF
|
||||||
keymap[C.CtrlE] = actEndOfLine
|
keymap[tui.CtrlE] = actEndOfLine
|
||||||
keymap[C.CtrlF] = actForwardChar
|
keymap[tui.CtrlF] = actForwardChar
|
||||||
keymap[C.CtrlH] = actBackwardDeleteChar
|
keymap[tui.CtrlH] = actBackwardDeleteChar
|
||||||
keymap[C.BSpace] = actBackwardDeleteChar
|
keymap[tui.BSpace] = actBackwardDeleteChar
|
||||||
keymap[C.Tab] = actToggleDown
|
keymap[tui.Tab] = actToggleDown
|
||||||
keymap[C.BTab] = actToggleUp
|
keymap[tui.BTab] = actToggleUp
|
||||||
keymap[C.CtrlJ] = actDown
|
keymap[tui.CtrlJ] = actDown
|
||||||
keymap[C.CtrlK] = actUp
|
keymap[tui.CtrlK] = actUp
|
||||||
keymap[C.CtrlL] = actClearScreen
|
keymap[tui.CtrlL] = actClearScreen
|
||||||
keymap[C.CtrlM] = actAccept
|
keymap[tui.CtrlM] = actAccept
|
||||||
keymap[C.CtrlN] = actDown
|
keymap[tui.CtrlN] = actDown
|
||||||
keymap[C.CtrlP] = actUp
|
keymap[tui.CtrlP] = actUp
|
||||||
keymap[C.CtrlU] = actUnixLineDiscard
|
keymap[tui.CtrlU] = actUnixLineDiscard
|
||||||
keymap[C.CtrlW] = actUnixWordRubout
|
keymap[tui.CtrlW] = actUnixWordRubout
|
||||||
keymap[C.CtrlY] = actYank
|
keymap[tui.CtrlY] = actYank
|
||||||
|
|
||||||
keymap[C.AltB] = actBackwardWord
|
keymap[tui.AltB] = actBackwardWord
|
||||||
keymap[C.SLeft] = actBackwardWord
|
keymap[tui.SLeft] = actBackwardWord
|
||||||
keymap[C.AltF] = actForwardWord
|
keymap[tui.AltF] = actForwardWord
|
||||||
keymap[C.SRight] = actForwardWord
|
keymap[tui.SRight] = actForwardWord
|
||||||
keymap[C.AltD] = actKillWord
|
keymap[tui.AltD] = actKillWord
|
||||||
keymap[C.AltBS] = actBackwardKillWord
|
keymap[tui.AltBS] = actBackwardKillWord
|
||||||
|
|
||||||
keymap[C.Up] = actUp
|
keymap[tui.Up] = actUp
|
||||||
keymap[C.Down] = actDown
|
keymap[tui.Down] = actDown
|
||||||
keymap[C.Left] = actBackwardChar
|
keymap[tui.Left] = actBackwardChar
|
||||||
keymap[C.Right] = actForwardChar
|
keymap[tui.Right] = actForwardChar
|
||||||
|
|
||||||
keymap[C.Home] = actBeginningOfLine
|
keymap[tui.Home] = actBeginningOfLine
|
||||||
keymap[C.End] = actEndOfLine
|
keymap[tui.End] = actEndOfLine
|
||||||
keymap[C.Del] = actDeleteChar
|
keymap[tui.Del] = actDeleteChar
|
||||||
keymap[C.PgUp] = actPageUp
|
keymap[tui.PgUp] = actPageUp
|
||||||
keymap[C.PgDn] = actPageDown
|
keymap[tui.PgDn] = actPageDown
|
||||||
|
|
||||||
keymap[C.Rune] = actRune
|
keymap[tui.Rune] = actRune
|
||||||
keymap[C.Mouse] = actMouse
|
keymap[tui.Mouse] = actMouse
|
||||||
keymap[C.DoubleClick] = actAccept
|
keymap[tui.DoubleClick] = actAccept
|
||||||
return keymap
|
return keymap
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,9 +297,10 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
mutex: sync.Mutex{},
|
mutex: sync.Mutex{},
|
||||||
suppress: true,
|
suppress: true,
|
||||||
slab: util.MakeSlab(slab16Size, slab32Size),
|
slab: util.MakeSlab(slab16Size, slab32Size),
|
||||||
|
theme: opts.Theme,
|
||||||
startChan: make(chan bool, 1),
|
startChan: make(chan bool, 1),
|
||||||
initFunc: func() {
|
initFunc: func() {
|
||||||
C.Init(opts.Theme, opts.Black, opts.Mouse)
|
tui.Init(opts.Theme, opts.Black, opts.Mouse)
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -427,8 +430,8 @@ func calculateSize(base int, size sizeSpec, margin int, minSize int) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) resizeWindows() {
|
func (t *Terminal) resizeWindows() {
|
||||||
screenWidth := C.MaxX()
|
screenWidth := tui.MaxX()
|
||||||
screenHeight := C.MaxY()
|
screenHeight := tui.MaxY()
|
||||||
marginInt := [4]int{}
|
marginInt := [4]int{}
|
||||||
for idx, sizeSpec := range t.margin {
|
for idx, sizeSpec := range t.margin {
|
||||||
if sizeSpec.percent {
|
if sizeSpec.percent {
|
||||||
@@ -477,33 +480,33 @@ func (t *Terminal) resizeWindows() {
|
|||||||
height := screenHeight - marginInt[0] - marginInt[2]
|
height := screenHeight - marginInt[0] - marginInt[2]
|
||||||
if t.isPreviewEnabled() {
|
if t.isPreviewEnabled() {
|
||||||
createPreviewWindow := func(y int, x int, w int, h int) {
|
createPreviewWindow := func(y int, x int, w int, h int) {
|
||||||
t.bwindow = C.NewWindow(y, x, w, h, true)
|
t.bwindow = tui.NewWindow(y, x, w, h, true)
|
||||||
t.pwindow = C.NewWindow(y+1, x+2, w-4, h-2, false)
|
t.pwindow = tui.NewWindow(y+1, x+2, w-4, h-2, false)
|
||||||
}
|
}
|
||||||
switch t.preview.position {
|
switch t.preview.position {
|
||||||
case posUp:
|
case posUp:
|
||||||
pheight := calculateSize(height, t.preview.size, minHeight, 3)
|
pheight := calculateSize(height, t.preview.size, minHeight, 3)
|
||||||
t.window = C.NewWindow(
|
t.window = tui.NewWindow(
|
||||||
marginInt[0]+pheight, marginInt[3], width, height-pheight, false)
|
marginInt[0]+pheight, marginInt[3], width, height-pheight, false)
|
||||||
createPreviewWindow(marginInt[0], marginInt[3], width, pheight)
|
createPreviewWindow(marginInt[0], marginInt[3], width, pheight)
|
||||||
case posDown:
|
case posDown:
|
||||||
pheight := calculateSize(height, t.preview.size, minHeight, 3)
|
pheight := calculateSize(height, t.preview.size, minHeight, 3)
|
||||||
t.window = C.NewWindow(
|
t.window = tui.NewWindow(
|
||||||
marginInt[0], marginInt[3], width, height-pheight, false)
|
marginInt[0], marginInt[3], width, height-pheight, false)
|
||||||
createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)
|
createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)
|
||||||
case posLeft:
|
case posLeft:
|
||||||
pwidth := calculateSize(width, t.preview.size, minWidth, 5)
|
pwidth := calculateSize(width, t.preview.size, minWidth, 5)
|
||||||
t.window = C.NewWindow(
|
t.window = tui.NewWindow(
|
||||||
marginInt[0], marginInt[3]+pwidth, width-pwidth, height, false)
|
marginInt[0], marginInt[3]+pwidth, width-pwidth, height, false)
|
||||||
createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
|
createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
|
||||||
case posRight:
|
case posRight:
|
||||||
pwidth := calculateSize(width, t.preview.size, minWidth, 5)
|
pwidth := calculateSize(width, t.preview.size, minWidth, 5)
|
||||||
t.window = C.NewWindow(
|
t.window = tui.NewWindow(
|
||||||
marginInt[0], marginInt[3], width-pwidth, height, false)
|
marginInt[0], marginInt[3], width-pwidth, height, false)
|
||||||
createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height)
|
createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
t.window = C.NewWindow(
|
t.window = tui.NewWindow(
|
||||||
marginInt[0],
|
marginInt[0],
|
||||||
marginInt[3],
|
marginInt[3],
|
||||||
width,
|
width,
|
||||||
@@ -529,24 +532,24 @@ func (t *Terminal) placeCursor() {
|
|||||||
|
|
||||||
func (t *Terminal) printPrompt() {
|
func (t *Terminal) printPrompt() {
|
||||||
t.move(0, 0, true)
|
t.move(0, 0, true)
|
||||||
t.window.CPrint(C.ColPrompt, C.Bold, t.prompt)
|
t.window.CPrint(tui.ColPrompt, tui.Bold, t.prompt)
|
||||||
t.window.CPrint(C.ColNormal, C.Bold, string(t.input))
|
t.window.CPrint(tui.ColNormal, tui.Bold, string(t.input))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) printInfo() {
|
func (t *Terminal) printInfo() {
|
||||||
if t.inlineInfo {
|
if t.inlineInfo {
|
||||||
t.move(0, displayWidth([]rune(t.prompt))+displayWidth(t.input)+1, true)
|
t.move(0, displayWidth([]rune(t.prompt))+displayWidth(t.input)+1, true)
|
||||||
if t.reading {
|
if t.reading {
|
||||||
t.window.CPrint(C.ColSpinner, C.Bold, " < ")
|
t.window.CPrint(tui.ColSpinner, tui.Bold, " < ")
|
||||||
} else {
|
} else {
|
||||||
t.window.CPrint(C.ColPrompt, C.Bold, " < ")
|
t.window.CPrint(tui.ColPrompt, tui.Bold, " < ")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
t.move(1, 0, true)
|
t.move(1, 0, true)
|
||||||
if t.reading {
|
if t.reading {
|
||||||
duration := int64(spinnerDuration)
|
duration := int64(spinnerDuration)
|
||||||
idx := (time.Now().UnixNano() % (duration * int64(len(_spinner)))) / duration
|
idx := (time.Now().UnixNano() % (duration * int64(len(_spinner)))) / duration
|
||||||
t.window.CPrint(C.ColSpinner, C.Bold, _spinner[idx])
|
t.window.CPrint(tui.ColSpinner, tui.Bold, _spinner[idx])
|
||||||
}
|
}
|
||||||
t.move(1, 2, false)
|
t.move(1, 2, false)
|
||||||
}
|
}
|
||||||
@@ -565,7 +568,7 @@ func (t *Terminal) printInfo() {
|
|||||||
if t.progress > 0 && t.progress < 100 {
|
if t.progress > 0 && t.progress < 100 {
|
||||||
output += fmt.Sprintf(" (%d%%)", t.progress)
|
output += fmt.Sprintf(" (%d%%)", t.progress)
|
||||||
}
|
}
|
||||||
t.window.CPrint(C.ColInfo, 0, output)
|
t.window.CPrint(tui.ColInfo, 0, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) printHeader() {
|
func (t *Terminal) printHeader() {
|
||||||
@@ -589,7 +592,8 @@ func (t *Terminal) printHeader() {
|
|||||||
colors: colors}
|
colors: colors}
|
||||||
|
|
||||||
t.move(line, 2, true)
|
t.move(line, 2, true)
|
||||||
t.printHighlighted(&Result{item: item}, 0, C.ColHeader, 0, false)
|
t.printHighlighted(&Result{item: item},
|
||||||
|
tui.AttrRegular, tui.ColHeader, tui.ColDefault, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -623,21 +627,21 @@ func (t *Terminal) printItem(result *Result, i int, current bool) {
|
|||||||
} else if current {
|
} else if current {
|
||||||
label = ">"
|
label = ">"
|
||||||
}
|
}
|
||||||
t.window.CPrint(C.ColCursor, C.Bold, label)
|
t.window.CPrint(tui.ColCursor, tui.Bold, label)
|
||||||
if current {
|
if current {
|
||||||
if selected {
|
if selected {
|
||||||
t.window.CPrint(C.ColSelected, C.Bold, ">")
|
t.window.CPrint(tui.ColSelected, tui.Bold, ">")
|
||||||
} else {
|
} else {
|
||||||
t.window.CPrint(C.ColCurrent, C.Bold, " ")
|
t.window.CPrint(tui.ColCurrent, tui.Bold, " ")
|
||||||
}
|
}
|
||||||
t.printHighlighted(result, C.Bold, C.ColCurrent, C.ColCurrentMatch, true)
|
t.printHighlighted(result, tui.Bold, tui.ColCurrent, tui.ColCurrentMatch, true)
|
||||||
} else {
|
} else {
|
||||||
if selected {
|
if selected {
|
||||||
t.window.CPrint(C.ColSelected, C.Bold, ">")
|
t.window.CPrint(tui.ColSelected, tui.Bold, ">")
|
||||||
} else {
|
} else {
|
||||||
t.window.Print(" ")
|
t.window.Print(" ")
|
||||||
}
|
}
|
||||||
t.printHighlighted(result, 0, 0, C.ColMatch, false)
|
t.printHighlighted(result, 0, tui.ColNormal, tui.ColMatch, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -693,7 +697,7 @@ func overflow(runes []rune, max int) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) printHighlighted(result *Result, attr C.Attr, col1 int, col2 int, current bool) {
|
func (t *Terminal) printHighlighted(result *Result, attr tui.Attr, col1 tui.ColorPair, col2 tui.ColorPair, current bool) {
|
||||||
item := result.item
|
item := result.item
|
||||||
|
|
||||||
// Overflow
|
// Overflow
|
||||||
@@ -718,7 +722,7 @@ func (t *Terminal) printHighlighted(result *Result, attr C.Attr, col1 int, col2
|
|||||||
maxe = util.Max(maxe, int(offset[1]))
|
maxe = util.Max(maxe, int(offset[1]))
|
||||||
}
|
}
|
||||||
|
|
||||||
offsets := result.colorOffsets(charOffsets, col2, attr, current)
|
offsets := result.colorOffsets(charOffsets, t.theme, col2, attr, current)
|
||||||
maxWidth := t.window.Width - 3
|
maxWidth := t.window.Width - 3
|
||||||
maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text))
|
maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text))
|
||||||
if overflow(text, maxWidth) {
|
if overflow(text, maxWidth) {
|
||||||
@@ -825,7 +829,7 @@ func (t *Terminal) printPreview() {
|
|||||||
if t.previewer.offset > 0 {
|
if t.previewer.offset > 0 {
|
||||||
offset := fmt.Sprintf("%d/%d", t.previewer.offset+1, t.previewer.lines)
|
offset := fmt.Sprintf("%d/%d", t.previewer.offset+1, t.previewer.lines)
|
||||||
t.pwindow.Move(0, t.pwindow.Width-len(offset))
|
t.pwindow.Move(0, t.pwindow.Width-len(offset))
|
||||||
t.pwindow.CPrint(C.ColInfo, C.Reverse, offset)
|
t.pwindow.CPrint(tui.ColInfo, tui.Reverse, offset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -856,11 +860,10 @@ func (t *Terminal) printAll() {
|
|||||||
func (t *Terminal) refresh() {
|
func (t *Terminal) refresh() {
|
||||||
if !t.suppress {
|
if !t.suppress {
|
||||||
if t.isPreviewEnabled() {
|
if t.isPreviewEnabled() {
|
||||||
t.bwindow.Refresh()
|
tui.RefreshWindows([]*tui.Window{t.bwindow, t.pwindow, t.window})
|
||||||
t.pwindow.Refresh()
|
} else {
|
||||||
|
tui.RefreshWindows([]*tui.Window{t.window})
|
||||||
}
|
}
|
||||||
t.window.Refresh()
|
|
||||||
C.DoUpdate()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -910,13 +913,16 @@ func (t *Terminal) rubout(pattern string) {
|
|||||||
t.input = append(t.input[:t.cx], after...)
|
t.input = append(t.input[:t.cx], after...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func keyMatch(key int, event C.Event) bool {
|
func keyMatch(key int, event tui.Event) bool {
|
||||||
return event.Type == key ||
|
return event.Type == key ||
|
||||||
event.Type == C.Rune && int(event.Char) == key-C.AltZ ||
|
event.Type == tui.Rune && int(event.Char) == key-tui.AltZ ||
|
||||||
event.Type == C.Mouse && key == C.DoubleClick && event.MouseEvent.Double
|
event.Type == tui.Mouse && key == tui.DoubleClick && event.MouseEvent.Double
|
||||||
}
|
}
|
||||||
|
|
||||||
func quoteEntry(entry string) string {
|
func quoteEntry(entry string) string {
|
||||||
|
if util.IsWindows() {
|
||||||
|
return strconv.Quote(strings.Replace(entry, "\"", "\\\"", -1))
|
||||||
|
}
|
||||||
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
|
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -978,8 +984,11 @@ func (t *Terminal) executeCommand(template string, items []*Item) {
|
|||||||
cmd.Stdin = os.Stdin
|
cmd.Stdin = os.Stdin
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
C.Endwin()
|
tui.Pause()
|
||||||
cmd.Run()
|
cmd.Run()
|
||||||
|
if tui.Resume() {
|
||||||
|
t.printAll()
|
||||||
|
}
|
||||||
t.refresh()
|
t.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1012,7 +1021,7 @@ func (t *Terminal) Loop() {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
resizeChan := make(chan os.Signal, 1)
|
resizeChan := make(chan os.Signal, 1)
|
||||||
signal.Notify(resizeChan, syscall.SIGWINCH)
|
notifyOnResize(resizeChan) // Non-portable
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
<-resizeChan
|
<-resizeChan
|
||||||
@@ -1124,12 +1133,11 @@ func (t *Terminal) Loop() {
|
|||||||
case reqRefresh:
|
case reqRefresh:
|
||||||
t.suppress = false
|
t.suppress = false
|
||||||
case reqRedraw:
|
case reqRedraw:
|
||||||
C.Clear()
|
tui.Clear()
|
||||||
C.Endwin()
|
tui.Refresh()
|
||||||
C.Refresh()
|
|
||||||
t.printAll()
|
t.printAll()
|
||||||
case reqClose:
|
case reqClose:
|
||||||
C.Close()
|
tui.Close()
|
||||||
if t.output() {
|
if t.output() {
|
||||||
exit(exitOk)
|
exit(exitOk)
|
||||||
}
|
}
|
||||||
@@ -1142,11 +1150,11 @@ func (t *Terminal) Loop() {
|
|||||||
case reqPreviewRefresh:
|
case reqPreviewRefresh:
|
||||||
t.printPreview()
|
t.printPreview()
|
||||||
case reqPrintQuery:
|
case reqPrintQuery:
|
||||||
C.Close()
|
tui.Close()
|
||||||
t.printer(string(t.input))
|
t.printer(string(t.input))
|
||||||
exit(exitOk)
|
exit(exitOk)
|
||||||
case reqQuit:
|
case reqQuit:
|
||||||
C.Close()
|
tui.Close()
|
||||||
exit(exitInterrupt)
|
exit(exitInterrupt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1159,7 +1167,7 @@ func (t *Terminal) Loop() {
|
|||||||
|
|
||||||
looping := true
|
looping := true
|
||||||
for looping {
|
for looping {
|
||||||
event := C.GetChar()
|
event := tui.GetChar()
|
||||||
|
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
previousInput := t.input
|
previousInput := t.input
|
||||||
@@ -1234,7 +1242,7 @@ func (t *Terminal) Loop() {
|
|||||||
if t.previewer.enabled && cnt > 0 && cnt > t.cy {
|
if t.previewer.enabled && cnt > 0 && cnt > t.cy {
|
||||||
t.previewBox.Set(reqPreviewEnqueue, t.currentItem())
|
t.previewBox.Set(reqPreviewEnqueue, t.currentItem())
|
||||||
}
|
}
|
||||||
req(reqList, reqInfo)
|
req(reqList, reqInfo, reqHeader)
|
||||||
}
|
}
|
||||||
case actToggleSort:
|
case actToggleSort:
|
||||||
t.sort = !t.sort
|
t.sort = !t.sort
|
||||||
@@ -1443,7 +1451,7 @@ func (t *Terminal) Loop() {
|
|||||||
// Double-click
|
// Double-click
|
||||||
if my >= min {
|
if my >= min {
|
||||||
if t.vset(t.offset+my-min) && t.cy < t.merger.Length() {
|
if t.vset(t.offset+my-min) && t.cy < t.merger.Length() {
|
||||||
return doAction(t.keymap[C.DoubleClick], C.DoubleClick)
|
return doAction(t.keymap[tui.DoubleClick], tui.DoubleClick)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if me.Down {
|
} else if me.Down {
|
||||||
@@ -1466,8 +1474,8 @@ func (t *Terminal) Loop() {
|
|||||||
mapkey := event.Type
|
mapkey := event.Type
|
||||||
if t.jumping == jumpDisabled {
|
if t.jumping == jumpDisabled {
|
||||||
action := t.keymap[mapkey]
|
action := t.keymap[mapkey]
|
||||||
if mapkey == C.Rune {
|
if mapkey == tui.Rune {
|
||||||
mapkey = int(event.Char) + int(C.AltZ)
|
mapkey = int(event.Char) + int(tui.AltZ)
|
||||||
if act, prs := t.keymap[mapkey]; prs {
|
if act, prs := t.keymap[mapkey]; prs {
|
||||||
action = act
|
action = act
|
||||||
}
|
}
|
||||||
@@ -1482,7 +1490,7 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
changed = string(previousInput) != string(t.input)
|
changed = string(previousInput) != string(t.input)
|
||||||
} else {
|
} else {
|
||||||
if mapkey == C.Rune {
|
if mapkey == tui.Rune {
|
||||||
if idx := strings.IndexRune(t.jumpLabels, event.Char); idx >= 0 && idx < t.maxItems() && idx < t.merger.Length() {
|
if idx := strings.IndexRune(t.jumpLabels, event.Char); idx >= 0 && idx < t.maxItems() && idx < t.merger.Length() {
|
||||||
t.cy = idx + t.offset
|
t.cy = idx + t.offset
|
||||||
if t.jumping == jumpAcceptEnabled {
|
if t.jumping == jumpAcceptEnabled {
|
||||||
|
13
src/terminal_unix.go
Normal file
13
src/terminal_unix.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package fzf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func notifyOnResize(resizeChan chan<- os.Signal) {
|
||||||
|
signal.Notify(resizeChan, syscall.SIGWINCH)
|
||||||
|
}
|
11
src/terminal_windows.go
Normal file
11
src/terminal_windows.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package fzf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func notifyOnResize(resizeChan chan<- os.Signal) {
|
||||||
|
// TODO
|
||||||
|
}
|
@@ -1,4 +1,7 @@
|
|||||||
package curses
|
// +build !windows
|
||||||
|
// +build !tcell
|
||||||
|
|
||||||
|
package tui
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#include <ncurses.h>
|
#include <ncurses.h>
|
||||||
@@ -10,7 +13,6 @@ package curses
|
|||||||
SCREEN *c_newterm () {
|
SCREEN *c_newterm () {
|
||||||
return newterm(NULL, stderr, stdin);
|
return newterm(NULL, stderr, stdin);
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
@@ -23,6 +25,10 @@ import (
|
|||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ColorPair int16
|
||||||
|
type Attr C.int
|
||||||
|
type WindowImpl C.WINDOW
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Bold = C.A_BOLD
|
Bold = C.A_BOLD
|
||||||
Dim = C.A_DIM
|
Dim = C.A_DIM
|
||||||
@@ -31,89 +37,14 @@ const (
|
|||||||
Underline = C.A_UNDERLINE
|
Underline = C.A_UNDERLINE
|
||||||
)
|
)
|
||||||
|
|
||||||
type Attr C.int
|
|
||||||
|
|
||||||
// Types of user action
|
|
||||||
const (
|
const (
|
||||||
Rune = iota
|
AttrRegular Attr = 0
|
||||||
|
|
||||||
CtrlA
|
|
||||||
CtrlB
|
|
||||||
CtrlC
|
|
||||||
CtrlD
|
|
||||||
CtrlE
|
|
||||||
CtrlF
|
|
||||||
CtrlG
|
|
||||||
CtrlH
|
|
||||||
Tab
|
|
||||||
CtrlJ
|
|
||||||
CtrlK
|
|
||||||
CtrlL
|
|
||||||
CtrlM
|
|
||||||
CtrlN
|
|
||||||
CtrlO
|
|
||||||
CtrlP
|
|
||||||
CtrlQ
|
|
||||||
CtrlR
|
|
||||||
CtrlS
|
|
||||||
CtrlT
|
|
||||||
CtrlU
|
|
||||||
CtrlV
|
|
||||||
CtrlW
|
|
||||||
CtrlX
|
|
||||||
CtrlY
|
|
||||||
CtrlZ
|
|
||||||
ESC
|
|
||||||
|
|
||||||
Invalid
|
|
||||||
Mouse
|
|
||||||
DoubleClick
|
|
||||||
|
|
||||||
BTab
|
|
||||||
BSpace
|
|
||||||
|
|
||||||
Del
|
|
||||||
PgUp
|
|
||||||
PgDn
|
|
||||||
|
|
||||||
Up
|
|
||||||
Down
|
|
||||||
Left
|
|
||||||
Right
|
|
||||||
Home
|
|
||||||
End
|
|
||||||
|
|
||||||
SLeft
|
|
||||||
SRight
|
|
||||||
|
|
||||||
F1
|
|
||||||
F2
|
|
||||||
F3
|
|
||||||
F4
|
|
||||||
F5
|
|
||||||
F6
|
|
||||||
F7
|
|
||||||
F8
|
|
||||||
F9
|
|
||||||
F10
|
|
||||||
|
|
||||||
AltEnter
|
|
||||||
AltSpace
|
|
||||||
AltSlash
|
|
||||||
AltBS
|
|
||||||
AltA
|
|
||||||
AltB
|
|
||||||
AltC
|
|
||||||
AltD
|
|
||||||
AltE
|
|
||||||
AltF
|
|
||||||
|
|
||||||
AltZ = AltA + 'z' - 'a'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Pallete
|
// Pallete
|
||||||
const (
|
const (
|
||||||
ColNormal = iota
|
ColDefault ColorPair = iota
|
||||||
|
ColNormal
|
||||||
ColPrompt
|
ColPrompt
|
||||||
ColMatch
|
ColMatch
|
||||||
ColCurrent
|
ColCurrent
|
||||||
@@ -127,197 +58,26 @@ const (
|
|||||||
ColUser // Should be the last entry
|
ColUser // Should be the last entry
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
doubleClickDuration = 500 * time.Millisecond
|
|
||||||
colDefault = -1
|
|
||||||
colUndefined = -2
|
|
||||||
)
|
|
||||||
|
|
||||||
type ColorTheme struct {
|
|
||||||
UseDefault bool
|
|
||||||
Fg int16
|
|
||||||
Bg int16
|
|
||||||
DarkBg int16
|
|
||||||
Prompt int16
|
|
||||||
Match int16
|
|
||||||
Current int16
|
|
||||||
CurrentMatch int16
|
|
||||||
Spinner int16
|
|
||||||
Info int16
|
|
||||||
Cursor int16
|
|
||||||
Selected int16
|
|
||||||
Header int16
|
|
||||||
Border int16
|
|
||||||
}
|
|
||||||
|
|
||||||
type Event struct {
|
|
||||||
Type int
|
|
||||||
Char rune
|
|
||||||
MouseEvent *MouseEvent
|
|
||||||
}
|
|
||||||
|
|
||||||
type MouseEvent struct {
|
|
||||||
Y int
|
|
||||||
X int
|
|
||||||
S int
|
|
||||||
Down bool
|
|
||||||
Double bool
|
|
||||||
Mod bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_buf []byte
|
|
||||||
_in *os.File
|
_in *os.File
|
||||||
_color func(int, Attr) C.int
|
|
||||||
_colorMap map[int]int
|
|
||||||
_prevDownTime time.Time
|
|
||||||
_clickY []int
|
|
||||||
_screen *C.SCREEN
|
_screen *C.SCREEN
|
||||||
Default16 *ColorTheme
|
_colorMap map[int]ColorPair
|
||||||
Dark256 *ColorTheme
|
_colorFn func(ColorPair, Attr) C.int
|
||||||
Light256 *ColorTheme
|
|
||||||
FG int
|
|
||||||
CurrentFG int
|
|
||||||
BG int
|
|
||||||
DarkBG int
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Window struct {
|
|
||||||
win *C.WINDOW
|
|
||||||
Top int
|
|
||||||
Left int
|
|
||||||
Width int
|
|
||||||
Height int
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewWindow(top int, left int, width int, height int, border bool) *Window {
|
|
||||||
win := C.newwin(C.int(height), C.int(width), C.int(top), C.int(left))
|
|
||||||
if border {
|
|
||||||
attr := _color(ColBorder, 0)
|
|
||||||
C.wattron(win, attr)
|
|
||||||
C.box(win, 0, 0)
|
|
||||||
C.wattroff(win, attr)
|
|
||||||
}
|
|
||||||
return &Window{
|
|
||||||
win: win,
|
|
||||||
Top: top,
|
|
||||||
Left: left,
|
|
||||||
Width: width,
|
|
||||||
Height: height,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func EmptyTheme() *ColorTheme {
|
|
||||||
return &ColorTheme{
|
|
||||||
UseDefault: true,
|
|
||||||
Fg: colUndefined,
|
|
||||||
Bg: colUndefined,
|
|
||||||
DarkBg: colUndefined,
|
|
||||||
Prompt: colUndefined,
|
|
||||||
Match: colUndefined,
|
|
||||||
Current: colUndefined,
|
|
||||||
CurrentMatch: colUndefined,
|
|
||||||
Spinner: colUndefined,
|
|
||||||
Info: colUndefined,
|
|
||||||
Cursor: colUndefined,
|
|
||||||
Selected: colUndefined,
|
|
||||||
Header: colUndefined,
|
|
||||||
Border: colUndefined}
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
_prevDownTime = time.Unix(0, 0)
|
_colorMap = make(map[int]ColorPair)
|
||||||
_clickY = []int{}
|
|
||||||
_colorMap = make(map[int]int)
|
|
||||||
Default16 = &ColorTheme{
|
|
||||||
UseDefault: true,
|
|
||||||
Fg: 15,
|
|
||||||
Bg: 0,
|
|
||||||
DarkBg: C.COLOR_BLACK,
|
|
||||||
Prompt: C.COLOR_BLUE,
|
|
||||||
Match: C.COLOR_GREEN,
|
|
||||||
Current: C.COLOR_YELLOW,
|
|
||||||
CurrentMatch: C.COLOR_GREEN,
|
|
||||||
Spinner: C.COLOR_GREEN,
|
|
||||||
Info: C.COLOR_WHITE,
|
|
||||||
Cursor: C.COLOR_RED,
|
|
||||||
Selected: C.COLOR_MAGENTA,
|
|
||||||
Header: C.COLOR_CYAN,
|
|
||||||
Border: C.COLOR_BLACK}
|
|
||||||
Dark256 = &ColorTheme{
|
|
||||||
UseDefault: true,
|
|
||||||
Fg: 15,
|
|
||||||
Bg: 0,
|
|
||||||
DarkBg: 236,
|
|
||||||
Prompt: 110,
|
|
||||||
Match: 108,
|
|
||||||
Current: 254,
|
|
||||||
CurrentMatch: 151,
|
|
||||||
Spinner: 148,
|
|
||||||
Info: 144,
|
|
||||||
Cursor: 161,
|
|
||||||
Selected: 168,
|
|
||||||
Header: 109,
|
|
||||||
Border: 59}
|
|
||||||
Light256 = &ColorTheme{
|
|
||||||
UseDefault: true,
|
|
||||||
Fg: 15,
|
|
||||||
Bg: 0,
|
|
||||||
DarkBg: 251,
|
|
||||||
Prompt: 25,
|
|
||||||
Match: 66,
|
|
||||||
Current: 237,
|
|
||||||
CurrentMatch: 23,
|
|
||||||
Spinner: 65,
|
|
||||||
Info: 101,
|
|
||||||
Cursor: 161,
|
|
||||||
Selected: 168,
|
|
||||||
Header: 31,
|
|
||||||
Border: 145}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func attrColored(pair int, a Attr) C.int {
|
func (a Attr) Merge(b Attr) Attr {
|
||||||
var attr C.int
|
return a | b
|
||||||
if pair > ColNormal {
|
|
||||||
attr = C.COLOR_PAIR(C.int(pair))
|
|
||||||
}
|
|
||||||
return attr | C.int(a)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func attrMono(pair int, a Attr) C.int {
|
func DefaultTheme() *ColorTheme {
|
||||||
var attr C.int
|
if C.tigetnum(C.CString("colors")) >= 256 {
|
||||||
switch pair {
|
return Dark256
|
||||||
case ColCurrent:
|
|
||||||
if a&C.A_BOLD == C.A_BOLD {
|
|
||||||
attr = C.A_REVERSE
|
|
||||||
}
|
}
|
||||||
case ColMatch:
|
return Default16
|
||||||
attr = C.A_UNDERLINE
|
|
||||||
case ColCurrentMatch:
|
|
||||||
attr = C.A_UNDERLINE | C.A_REVERSE
|
|
||||||
}
|
|
||||||
if a&C.A_BOLD == C.A_BOLD {
|
|
||||||
attr = attr | C.A_BOLD
|
|
||||||
}
|
|
||||||
return attr
|
|
||||||
}
|
|
||||||
|
|
||||||
func MaxX() int {
|
|
||||||
return int(C.COLS)
|
|
||||||
}
|
|
||||||
|
|
||||||
func MaxY() int {
|
|
||||||
return int(C.LINES)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getch(nonblock bool) int {
|
|
||||||
b := make([]byte, 1)
|
|
||||||
syscall.SetNonblock(int(_in.Fd()), nonblock)
|
|
||||||
_, err := _in.Read(b)
|
|
||||||
if err != nil {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
return int(b[0])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Init(theme *ColorTheme, black bool, mouse bool) {
|
func Init(theme *ColorTheme, black bool, mouse bool) {
|
||||||
@@ -344,61 +104,42 @@ func Init(theme *ColorTheme, black bool, mouse bool) {
|
|||||||
C.noecho()
|
C.noecho()
|
||||||
C.raw() // stty dsusp undef
|
C.raw() // stty dsusp undef
|
||||||
|
|
||||||
if theme != nil {
|
_color = theme != nil
|
||||||
|
if _color {
|
||||||
C.start_color()
|
C.start_color()
|
||||||
var baseTheme *ColorTheme
|
InitTheme(theme, black)
|
||||||
if C.tigetnum(C.CString("colors")) >= 256 {
|
initPairs(theme)
|
||||||
baseTheme = Dark256
|
C.bkgd(C.chtype(C.COLOR_PAIR(C.int(ColNormal))))
|
||||||
|
_colorFn = attrColored
|
||||||
} else {
|
} else {
|
||||||
baseTheme = Default16
|
_colorFn = attrMono
|
||||||
}
|
|
||||||
initPairs(baseTheme, theme, black)
|
|
||||||
_color = attrColored
|
|
||||||
} else {
|
|
||||||
_color = attrMono
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func override(a int16, b int16) C.short {
|
func initPairs(theme *ColorTheme) {
|
||||||
if b == colUndefined {
|
C.assume_default_colors(C.int(theme.Fg), C.int(theme.Bg))
|
||||||
return C.short(a)
|
initPair := func(group ColorPair, fg Color, bg Color) {
|
||||||
|
C.init_pair(C.short(group), C.short(fg), C.short(bg))
|
||||||
}
|
}
|
||||||
return C.short(b)
|
initPair(ColNormal, theme.Fg, theme.Bg)
|
||||||
|
initPair(ColPrompt, theme.Prompt, theme.Bg)
|
||||||
|
initPair(ColMatch, theme.Match, theme.Bg)
|
||||||
|
initPair(ColCurrent, theme.Current, theme.DarkBg)
|
||||||
|
initPair(ColCurrentMatch, theme.CurrentMatch, theme.DarkBg)
|
||||||
|
initPair(ColSpinner, theme.Spinner, theme.Bg)
|
||||||
|
initPair(ColInfo, theme.Info, theme.Bg)
|
||||||
|
initPair(ColCursor, theme.Cursor, theme.DarkBg)
|
||||||
|
initPair(ColSelected, theme.Selected, theme.DarkBg)
|
||||||
|
initPair(ColHeader, theme.Header, theme.Bg)
|
||||||
|
initPair(ColBorder, theme.Border, theme.Bg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initPairs(baseTheme *ColorTheme, theme *ColorTheme, black bool) {
|
func Pause() {
|
||||||
fg := override(baseTheme.Fg, theme.Fg)
|
C.endwin()
|
||||||
bg := override(baseTheme.Bg, theme.Bg)
|
}
|
||||||
if black {
|
|
||||||
bg = C.COLOR_BLACK
|
|
||||||
} else if theme.UseDefault {
|
|
||||||
fg = colDefault
|
|
||||||
bg = colDefault
|
|
||||||
C.use_default_colors()
|
|
||||||
}
|
|
||||||
if theme.UseDefault {
|
|
||||||
FG = colDefault
|
|
||||||
BG = colDefault
|
|
||||||
} else {
|
|
||||||
FG = int(fg)
|
|
||||||
BG = int(bg)
|
|
||||||
C.assume_default_colors(C.int(override(baseTheme.Fg, theme.Fg)), C.int(bg))
|
|
||||||
}
|
|
||||||
|
|
||||||
currentFG := override(baseTheme.Current, theme.Current)
|
func Resume() bool {
|
||||||
darkBG := override(baseTheme.DarkBg, theme.DarkBg)
|
return false
|
||||||
CurrentFG = int(currentFG)
|
|
||||||
DarkBG = int(darkBG)
|
|
||||||
C.init_pair(ColPrompt, override(baseTheme.Prompt, theme.Prompt), bg)
|
|
||||||
C.init_pair(ColMatch, override(baseTheme.Match, theme.Match), bg)
|
|
||||||
C.init_pair(ColCurrent, currentFG, darkBG)
|
|
||||||
C.init_pair(ColCurrentMatch, override(baseTheme.CurrentMatch, theme.CurrentMatch), darkBG)
|
|
||||||
C.init_pair(ColSpinner, override(baseTheme.Spinner, theme.Spinner), bg)
|
|
||||||
C.init_pair(ColInfo, override(baseTheme.Info, theme.Info), bg)
|
|
||||||
C.init_pair(ColCursor, override(baseTheme.Cursor, theme.Cursor), darkBG)
|
|
||||||
C.init_pair(ColSelected, override(baseTheme.Selected, theme.Selected), darkBG)
|
|
||||||
C.init_pair(ColHeader, override(baseTheme.Header, theme.Header), bg)
|
|
||||||
C.init_pair(ColBorder, override(baseTheme.Border, theme.Border), bg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Close() {
|
func Close() {
|
||||||
@@ -406,15 +147,172 @@ func Close() {
|
|||||||
C.delscreen(_screen)
|
C.delscreen(_screen)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewWindow(top int, left int, width int, height int, border bool) *Window {
|
||||||
|
win := C.newwin(C.int(height), C.int(width), C.int(top), C.int(left))
|
||||||
|
if _color {
|
||||||
|
C.wbkgd(win, C.chtype(C.COLOR_PAIR(C.int(ColNormal))))
|
||||||
|
}
|
||||||
|
if border {
|
||||||
|
attr := _colorFn(ColBorder, 0)
|
||||||
|
C.wattron(win, attr)
|
||||||
|
C.box(win, 0, 0)
|
||||||
|
C.wattroff(win, attr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Window{
|
||||||
|
impl: (*WindowImpl)(win),
|
||||||
|
Top: top,
|
||||||
|
Left: left,
|
||||||
|
Width: width,
|
||||||
|
Height: height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func attrColored(pair ColorPair, a Attr) C.int {
|
||||||
|
var attr C.int
|
||||||
|
if pair > 0 {
|
||||||
|
attr = C.COLOR_PAIR(C.int(pair))
|
||||||
|
}
|
||||||
|
return attr | C.int(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func attrMono(pair ColorPair, a Attr) C.int {
|
||||||
|
var attr C.int
|
||||||
|
switch pair {
|
||||||
|
case ColCurrent:
|
||||||
|
if C.int(a)&C.A_BOLD == C.A_BOLD {
|
||||||
|
attr = C.A_REVERSE
|
||||||
|
}
|
||||||
|
case ColMatch:
|
||||||
|
attr = C.A_UNDERLINE
|
||||||
|
case ColCurrentMatch:
|
||||||
|
attr = C.A_UNDERLINE | C.A_REVERSE
|
||||||
|
}
|
||||||
|
if C.int(a)&C.A_BOLD == C.A_BOLD {
|
||||||
|
attr = attr | C.A_BOLD
|
||||||
|
}
|
||||||
|
return attr
|
||||||
|
}
|
||||||
|
|
||||||
|
func MaxX() int {
|
||||||
|
return int(C.COLS)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MaxY() int {
|
||||||
|
return int(C.LINES)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) win() *C.WINDOW {
|
||||||
|
return (*C.WINDOW)(w.impl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) Close() {
|
||||||
|
C.delwin(w.win())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) Enclose(y int, x int) bool {
|
||||||
|
return bool(C.wenclose(w.win(), C.int(y), C.int(x)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) Move(y int, x int) {
|
||||||
|
C.wmove(w.win(), C.int(y), C.int(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) MoveAndClear(y int, x int) {
|
||||||
|
w.Move(y, x)
|
||||||
|
C.wclrtoeol(w.win())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) Print(text string) {
|
||||||
|
C.waddstr(w.win(), C.CString(strings.Map(func(r rune) rune {
|
||||||
|
if r < 32 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}, text)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) CPrint(pair ColorPair, a Attr, text string) {
|
||||||
|
attr := _colorFn(pair, a)
|
||||||
|
C.wattron(w.win(), attr)
|
||||||
|
w.Print(text)
|
||||||
|
C.wattroff(w.win(), attr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Clear() {
|
||||||
|
C.clear()
|
||||||
|
C.endwin()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Refresh() {
|
||||||
|
C.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) Erase() {
|
||||||
|
C.werase(w.win())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) Fill(str string) bool {
|
||||||
|
return C.waddstr(w.win(), C.CString(str)) == C.OK
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) CFill(str string, fg Color, bg Color, a Attr) bool {
|
||||||
|
attr := _colorFn(PairFor(fg, bg), a)
|
||||||
|
C.wattron(w.win(), attr)
|
||||||
|
ret := w.Fill(str)
|
||||||
|
C.wattroff(w.win(), attr)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func RefreshWindows(windows []*Window) {
|
||||||
|
for _, w := range windows {
|
||||||
|
C.wnoutrefresh(w.win())
|
||||||
|
}
|
||||||
|
C.doupdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func PairFor(fg Color, bg Color) ColorPair {
|
||||||
|
key := (int(fg) << 8) + int(bg)
|
||||||
|
if found, prs := _colorMap[key]; prs {
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
id := ColorPair(len(_colorMap) + int(ColUser))
|
||||||
|
C.init_pair(C.short(id), C.short(fg), C.short(bg))
|
||||||
|
_colorMap[key] = id
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
func getch(nonblock bool) int {
|
||||||
|
b := make([]byte, 1)
|
||||||
|
syscall.SetNonblock(int(_in.Fd()), nonblock)
|
||||||
|
_, err := _in.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return int(b[0])
|
||||||
|
}
|
||||||
|
|
||||||
func GetBytes() []byte {
|
func GetBytes() []byte {
|
||||||
c := getch(false)
|
c := getch(false)
|
||||||
|
retries := 0
|
||||||
|
if c == 27 {
|
||||||
|
// Wait for additional keys after ESC for 100ms (10 * 10ms)
|
||||||
|
retries = 10
|
||||||
|
}
|
||||||
_buf = append(_buf, byte(c))
|
_buf = append(_buf, byte(c))
|
||||||
|
|
||||||
for {
|
for {
|
||||||
c = getch(true)
|
c = getch(true)
|
||||||
if c == -1 {
|
if c == -1 {
|
||||||
|
if retries > 0 {
|
||||||
|
retries--
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
continue
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
retries = 0
|
||||||
_buf = append(_buf, byte(c))
|
_buf = append(_buf, byte(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -628,84 +526,3 @@ func GetChar() Event {
|
|||||||
sz = rsz
|
sz = rsz
|
||||||
return Event{Rune, r, nil}
|
return Event{Rune, r, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) Close() {
|
|
||||||
C.delwin(w.win)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Window) Enclose(y int, x int) bool {
|
|
||||||
return bool(C.wenclose(w.win, C.int(y), C.int(x)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Window) Move(y int, x int) {
|
|
||||||
C.wmove(w.win, C.int(y), C.int(x))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Window) MoveAndClear(y int, x int) {
|
|
||||||
w.Move(y, x)
|
|
||||||
C.wclrtoeol(w.win)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Window) Print(text string) {
|
|
||||||
C.waddstr(w.win, C.CString(strings.Map(func(r rune) rune {
|
|
||||||
if r < 32 {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}, text)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Window) CPrint(pair int, a Attr, text string) {
|
|
||||||
attr := _color(pair, a)
|
|
||||||
C.wattron(w.win, attr)
|
|
||||||
w.Print(text)
|
|
||||||
C.wattroff(w.win, attr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Clear() {
|
|
||||||
C.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
func Endwin() {
|
|
||||||
C.endwin()
|
|
||||||
}
|
|
||||||
|
|
||||||
func Refresh() {
|
|
||||||
C.refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Window) Erase() {
|
|
||||||
C.werase(w.win)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Window) Fill(str string) bool {
|
|
||||||
return C.waddstr(w.win, C.CString(str)) == C.OK
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Window) CFill(str string, fg int, bg int, a Attr) bool {
|
|
||||||
attr := _color(PairFor(fg, bg), a)
|
|
||||||
C.wattron(w.win, attr)
|
|
||||||
ret := w.Fill(str)
|
|
||||||
C.wattroff(w.win, attr)
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Window) Refresh() {
|
|
||||||
C.wnoutrefresh(w.win)
|
|
||||||
}
|
|
||||||
|
|
||||||
func DoUpdate() {
|
|
||||||
C.doupdate()
|
|
||||||
}
|
|
||||||
|
|
||||||
func PairFor(fg int, bg int) int {
|
|
||||||
key := (fg << 8) + bg
|
|
||||||
if found, prs := _colorMap[key]; prs {
|
|
||||||
return found
|
|
||||||
}
|
|
||||||
|
|
||||||
id := len(_colorMap) + ColUser
|
|
||||||
C.init_pair(C.short(id), C.short(fg), C.short(bg))
|
|
||||||
_colorMap[key] = id
|
|
||||||
return id
|
|
||||||
}
|
|
575
src/tui/tcell.go
Normal file
575
src/tui/tcell.go
Normal file
@@ -0,0 +1,575 @@
|
|||||||
|
// +build tcell windows
|
||||||
|
|
||||||
|
package tui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/gdamore/tcell"
|
||||||
|
"github.com/gdamore/tcell/encoding"
|
||||||
|
|
||||||
|
"github.com/junegunn/go-runewidth"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ColorPair [2]Color
|
||||||
|
|
||||||
|
func (p ColorPair) fg() Color {
|
||||||
|
return p[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ColorPair) bg() Color {
|
||||||
|
return p[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ColorPair) style() tcell.Style {
|
||||||
|
style := tcell.StyleDefault
|
||||||
|
return style.Foreground(tcell.Color(p.fg())).Background(tcell.Color(p.bg()))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Attr tcell.Style
|
||||||
|
|
||||||
|
type WindowTcell struct {
|
||||||
|
LastX int
|
||||||
|
LastY int
|
||||||
|
MoveCursor bool
|
||||||
|
Border bool
|
||||||
|
}
|
||||||
|
type WindowImpl WindowTcell
|
||||||
|
|
||||||
|
const (
|
||||||
|
Bold = Attr(tcell.AttrBold)
|
||||||
|
Dim = Attr(tcell.AttrDim)
|
||||||
|
Blink = Attr(tcell.AttrBlink)
|
||||||
|
Reverse = Attr(tcell.AttrReverse)
|
||||||
|
Underline = Attr(tcell.AttrUnderline)
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
AttrRegular Attr = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ColDefault = ColorPair{colDefault, colDefault}
|
||||||
|
ColNormal ColorPair
|
||||||
|
ColPrompt ColorPair
|
||||||
|
ColMatch ColorPair
|
||||||
|
ColCurrent ColorPair
|
||||||
|
ColCurrentMatch ColorPair
|
||||||
|
ColSpinner ColorPair
|
||||||
|
ColInfo ColorPair
|
||||||
|
ColCursor ColorPair
|
||||||
|
ColSelected ColorPair
|
||||||
|
ColHeader ColorPair
|
||||||
|
ColBorder ColorPair
|
||||||
|
ColUser ColorPair
|
||||||
|
)
|
||||||
|
|
||||||
|
func DefaultTheme() *ColorTheme {
|
||||||
|
if _screen.Colors() >= 256 {
|
||||||
|
return Dark256
|
||||||
|
}
|
||||||
|
return Default16
|
||||||
|
}
|
||||||
|
|
||||||
|
func PairFor(fg Color, bg Color) ColorPair {
|
||||||
|
return [2]Color{fg, bg}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_colorToAttribute = []tcell.Color{
|
||||||
|
tcell.ColorBlack,
|
||||||
|
tcell.ColorRed,
|
||||||
|
tcell.ColorGreen,
|
||||||
|
tcell.ColorYellow,
|
||||||
|
tcell.ColorBlue,
|
||||||
|
tcell.ColorDarkMagenta,
|
||||||
|
tcell.ColorLightCyan,
|
||||||
|
tcell.ColorWhite,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c Color) Style() tcell.Color {
|
||||||
|
if c <= colDefault {
|
||||||
|
return tcell.ColorDefault
|
||||||
|
} else if c >= colBlack && c <= colWhite {
|
||||||
|
return _colorToAttribute[int(c)]
|
||||||
|
} else {
|
||||||
|
return tcell.Color(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Attr) Merge(b Attr) Attr {
|
||||||
|
return a | b
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_screen tcell.Screen
|
||||||
|
_mouse bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func initScreen() {
|
||||||
|
s, e := tcell.NewScreen()
|
||||||
|
if e != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%v\n", e)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
if e = s.Init(); e != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%v\n", e)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
if _mouse {
|
||||||
|
s.EnableMouse()
|
||||||
|
} else {
|
||||||
|
s.DisableMouse()
|
||||||
|
}
|
||||||
|
_screen = s
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init(theme *ColorTheme, black bool, mouse bool) {
|
||||||
|
encoding.Register()
|
||||||
|
|
||||||
|
_mouse = mouse
|
||||||
|
initScreen()
|
||||||
|
|
||||||
|
_color = theme != nil
|
||||||
|
if _color {
|
||||||
|
InitTheme(theme, black)
|
||||||
|
} else {
|
||||||
|
theme = DefaultTheme()
|
||||||
|
}
|
||||||
|
ColNormal = ColorPair{theme.Fg, theme.Bg}
|
||||||
|
ColPrompt = ColorPair{theme.Prompt, theme.Bg}
|
||||||
|
ColMatch = ColorPair{theme.Match, theme.Bg}
|
||||||
|
ColCurrent = ColorPair{theme.Current, theme.DarkBg}
|
||||||
|
ColCurrentMatch = ColorPair{theme.CurrentMatch, theme.DarkBg}
|
||||||
|
ColSpinner = ColorPair{theme.Spinner, theme.Bg}
|
||||||
|
ColInfo = ColorPair{theme.Info, theme.Bg}
|
||||||
|
ColCursor = ColorPair{theme.Cursor, theme.DarkBg}
|
||||||
|
ColSelected = ColorPair{theme.Selected, theme.DarkBg}
|
||||||
|
ColHeader = ColorPair{theme.Header, theme.Bg}
|
||||||
|
ColBorder = ColorPair{theme.Border, theme.Bg}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MaxX() int {
|
||||||
|
ncols, _ := _screen.Size()
|
||||||
|
return int(ncols)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MaxY() int {
|
||||||
|
_, nlines := _screen.Size()
|
||||||
|
return int(nlines)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) win() *WindowTcell {
|
||||||
|
return (*WindowTcell)(w.impl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Clear() {
|
||||||
|
_screen.Sync()
|
||||||
|
_screen.Clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Refresh() {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetChar() Event {
|
||||||
|
ev := _screen.PollEvent()
|
||||||
|
switch ev := ev.(type) {
|
||||||
|
case *tcell.EventResize:
|
||||||
|
return Event{Invalid, 0, nil}
|
||||||
|
|
||||||
|
// process mouse events:
|
||||||
|
case *tcell.EventMouse:
|
||||||
|
x, y := ev.Position()
|
||||||
|
button := ev.Buttons()
|
||||||
|
mod := ev.Modifiers() != 0
|
||||||
|
if button&tcell.WheelDown != 0 {
|
||||||
|
return Event{Mouse, 0, &MouseEvent{y, x, -1, false, false, mod}}
|
||||||
|
} else if button&tcell.WheelUp != 0 {
|
||||||
|
return Event{Mouse, 0, &MouseEvent{y, x, +1, false, false, mod}}
|
||||||
|
} else if runtime.GOOS != "windows" {
|
||||||
|
// double and single taps on Windows don't quite work due to
|
||||||
|
// the console acting on the events and not allowing us
|
||||||
|
// to consume them.
|
||||||
|
|
||||||
|
down := button&tcell.Button1 != 0 // left
|
||||||
|
double := false
|
||||||
|
if down {
|
||||||
|
now := time.Now()
|
||||||
|
if now.Sub(_prevDownTime) < doubleClickDuration {
|
||||||
|
_clickY = append(_clickY, x)
|
||||||
|
} else {
|
||||||
|
_clickY = []int{x}
|
||||||
|
}
|
||||||
|
_prevDownTime = now
|
||||||
|
} else {
|
||||||
|
if len(_clickY) > 1 && _clickY[0] == _clickY[1] &&
|
||||||
|
time.Now().Sub(_prevDownTime) < doubleClickDuration {
|
||||||
|
double = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Event{Mouse, 0, &MouseEvent{y, x, 0, down, double, mod}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// process keyboard:
|
||||||
|
case *tcell.EventKey:
|
||||||
|
alt := (ev.Modifiers() & tcell.ModAlt) > 0
|
||||||
|
switch ev.Key() {
|
||||||
|
case tcell.KeyCtrlA:
|
||||||
|
return Event{CtrlA, 0, nil}
|
||||||
|
case tcell.KeyCtrlB:
|
||||||
|
return Event{CtrlB, 0, nil}
|
||||||
|
case tcell.KeyCtrlC:
|
||||||
|
return Event{CtrlC, 0, nil}
|
||||||
|
case tcell.KeyCtrlD:
|
||||||
|
return Event{CtrlD, 0, nil}
|
||||||
|
case tcell.KeyCtrlE:
|
||||||
|
return Event{CtrlE, 0, nil}
|
||||||
|
case tcell.KeyCtrlF:
|
||||||
|
return Event{CtrlF, 0, nil}
|
||||||
|
case tcell.KeyCtrlG:
|
||||||
|
return Event{CtrlG, 0, nil}
|
||||||
|
case tcell.KeyCtrlJ:
|
||||||
|
return Event{CtrlJ, 0, nil}
|
||||||
|
case tcell.KeyCtrlK:
|
||||||
|
return Event{CtrlK, 0, nil}
|
||||||
|
case tcell.KeyCtrlL:
|
||||||
|
return Event{CtrlL, 0, nil}
|
||||||
|
case tcell.KeyCtrlM:
|
||||||
|
if alt {
|
||||||
|
return Event{AltEnter, 0, nil}
|
||||||
|
}
|
||||||
|
return Event{CtrlM, 0, nil}
|
||||||
|
case tcell.KeyCtrlN:
|
||||||
|
return Event{CtrlN, 0, nil}
|
||||||
|
case tcell.KeyCtrlO:
|
||||||
|
return Event{CtrlO, 0, nil}
|
||||||
|
case tcell.KeyCtrlP:
|
||||||
|
return Event{CtrlP, 0, nil}
|
||||||
|
case tcell.KeyCtrlQ:
|
||||||
|
return Event{CtrlQ, 0, nil}
|
||||||
|
case tcell.KeyCtrlR:
|
||||||
|
return Event{CtrlR, 0, nil}
|
||||||
|
case tcell.KeyCtrlS:
|
||||||
|
return Event{CtrlS, 0, nil}
|
||||||
|
case tcell.KeyCtrlT:
|
||||||
|
return Event{CtrlT, 0, nil}
|
||||||
|
case tcell.KeyCtrlU:
|
||||||
|
return Event{CtrlU, 0, nil}
|
||||||
|
case tcell.KeyCtrlV:
|
||||||
|
return Event{CtrlV, 0, nil}
|
||||||
|
case tcell.KeyCtrlW:
|
||||||
|
return Event{CtrlW, 0, nil}
|
||||||
|
case tcell.KeyCtrlX:
|
||||||
|
return Event{CtrlX, 0, nil}
|
||||||
|
case tcell.KeyCtrlY:
|
||||||
|
return Event{CtrlY, 0, nil}
|
||||||
|
case tcell.KeyCtrlZ:
|
||||||
|
return Event{CtrlZ, 0, nil}
|
||||||
|
case tcell.KeyBackspace, tcell.KeyBackspace2:
|
||||||
|
if alt {
|
||||||
|
return Event{AltBS, 0, nil}
|
||||||
|
}
|
||||||
|
return Event{BSpace, 0, nil}
|
||||||
|
|
||||||
|
case tcell.KeyUp:
|
||||||
|
return Event{Up, 0, nil}
|
||||||
|
case tcell.KeyDown:
|
||||||
|
return Event{Down, 0, nil}
|
||||||
|
case tcell.KeyLeft:
|
||||||
|
return Event{Left, 0, nil}
|
||||||
|
case tcell.KeyRight:
|
||||||
|
return Event{Right, 0, nil}
|
||||||
|
|
||||||
|
case tcell.KeyHome:
|
||||||
|
return Event{Home, 0, nil}
|
||||||
|
case tcell.KeyDelete:
|
||||||
|
return Event{Del, 0, nil}
|
||||||
|
case tcell.KeyEnd:
|
||||||
|
return Event{End, 0, nil}
|
||||||
|
case tcell.KeyPgUp:
|
||||||
|
return Event{PgUp, 0, nil}
|
||||||
|
case tcell.KeyPgDn:
|
||||||
|
return Event{PgDn, 0, nil}
|
||||||
|
|
||||||
|
case tcell.KeyTab:
|
||||||
|
return Event{Tab, 0, nil}
|
||||||
|
case tcell.KeyBacktab:
|
||||||
|
return Event{BTab, 0, nil}
|
||||||
|
|
||||||
|
case tcell.KeyF1:
|
||||||
|
return Event{F1, 0, nil}
|
||||||
|
case tcell.KeyF2:
|
||||||
|
return Event{F2, 0, nil}
|
||||||
|
case tcell.KeyF3:
|
||||||
|
return Event{F3, 0, nil}
|
||||||
|
case tcell.KeyF4:
|
||||||
|
return Event{F4, 0, nil}
|
||||||
|
case tcell.KeyF5:
|
||||||
|
return Event{F5, 0, nil}
|
||||||
|
case tcell.KeyF6:
|
||||||
|
return Event{F6, 0, nil}
|
||||||
|
case tcell.KeyF7:
|
||||||
|
return Event{F7, 0, nil}
|
||||||
|
case tcell.KeyF8:
|
||||||
|
return Event{F8, 0, nil}
|
||||||
|
case tcell.KeyF9:
|
||||||
|
return Event{F9, 0, nil}
|
||||||
|
case tcell.KeyF10:
|
||||||
|
return Event{F10, 0, nil}
|
||||||
|
case tcell.KeyF11:
|
||||||
|
return Event{Invalid, 0, nil}
|
||||||
|
case tcell.KeyF12:
|
||||||
|
return Event{Invalid, 0, nil}
|
||||||
|
|
||||||
|
// ev.Ch doesn't work for some reason for space:
|
||||||
|
case tcell.KeyRune:
|
||||||
|
r := ev.Rune()
|
||||||
|
if alt {
|
||||||
|
switch r {
|
||||||
|
case ' ':
|
||||||
|
return Event{AltSpace, 0, nil}
|
||||||
|
case '/':
|
||||||
|
return Event{AltSlash, 0, nil}
|
||||||
|
}
|
||||||
|
if r >= 'a' && r <= 'z' {
|
||||||
|
return Event{AltA + int(r) - 'a', 0, nil}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Event{Rune, r, nil}
|
||||||
|
|
||||||
|
case tcell.KeyEsc:
|
||||||
|
return Event{ESC, 0, nil}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Event{Invalid, 0, nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Pause() {
|
||||||
|
_screen.Fini()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Resume() bool {
|
||||||
|
initScreen()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func Close() {
|
||||||
|
_screen.Fini()
|
||||||
|
}
|
||||||
|
|
||||||
|
func RefreshWindows(windows []*Window) {
|
||||||
|
// TODO
|
||||||
|
for _, w := range windows {
|
||||||
|
if w.win().MoveCursor {
|
||||||
|
_screen.ShowCursor(w.Left+w.win().LastX, w.Top+w.win().LastY)
|
||||||
|
w.win().MoveCursor = false
|
||||||
|
}
|
||||||
|
w.win().LastX = 0
|
||||||
|
w.win().LastY = 0
|
||||||
|
if w.win().Border {
|
||||||
|
w.DrawBorder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_screen.Show()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWindow(top int, left int, width int, height int, border bool) *Window {
|
||||||
|
// TODO
|
||||||
|
win := new(WindowTcell)
|
||||||
|
win.Border = border
|
||||||
|
return &Window{
|
||||||
|
impl: (*WindowImpl)(win),
|
||||||
|
Top: top,
|
||||||
|
Left: left,
|
||||||
|
Width: width,
|
||||||
|
Height: height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) Close() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
func fill(x, y, w, h int, r rune) {
|
||||||
|
for ly := 0; ly <= h; ly++ {
|
||||||
|
for lx := 0; lx <= w; lx++ {
|
||||||
|
_screen.SetContent(x+lx, y+ly, r, nil, ColDefault.style())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) Erase() {
|
||||||
|
// TODO
|
||||||
|
fill(w.Left, w.Top, w.Width, w.Height, ' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) Enclose(y int, x int) bool {
|
||||||
|
return x >= w.Left && x <= (w.Left+w.Width) &&
|
||||||
|
y >= w.Top && y <= (w.Top+w.Height)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) Move(y int, x int) {
|
||||||
|
w.win().LastX = x
|
||||||
|
w.win().LastY = y
|
||||||
|
w.win().MoveCursor = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) MoveAndClear(y int, x int) {
|
||||||
|
w.Move(y, x)
|
||||||
|
for i := w.win().LastX; i < w.Width; i++ {
|
||||||
|
_screen.SetContent(i+w.Left, w.win().LastY+w.Top, rune(' '), nil, ColDefault.style())
|
||||||
|
}
|
||||||
|
w.win().LastX = x
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) Print(text string) {
|
||||||
|
w.PrintString(text, ColDefault, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) PrintString(text string, pair ColorPair, a Attr) {
|
||||||
|
t := text
|
||||||
|
lx := 0
|
||||||
|
|
||||||
|
var style tcell.Style
|
||||||
|
if _color {
|
||||||
|
style = pair.style().
|
||||||
|
Reverse(a&Attr(tcell.AttrReverse) != 0).
|
||||||
|
Underline(a&Attr(tcell.AttrUnderline) != 0)
|
||||||
|
} else {
|
||||||
|
style = ColDefault.style().
|
||||||
|
Reverse(a&Attr(tcell.AttrReverse) != 0 || pair == ColCurrent || pair == ColCurrentMatch).
|
||||||
|
Underline(a&Attr(tcell.AttrUnderline) != 0 || pair == ColMatch || pair == ColCurrentMatch)
|
||||||
|
}
|
||||||
|
style = style.
|
||||||
|
Blink(a&Attr(tcell.AttrBlink) != 0).
|
||||||
|
Bold(a&Attr(tcell.AttrBold) != 0).
|
||||||
|
Dim(a&Attr(tcell.AttrDim) != 0)
|
||||||
|
|
||||||
|
for {
|
||||||
|
if len(t) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
r, size := utf8.DecodeRuneInString(t)
|
||||||
|
t = t[size:]
|
||||||
|
|
||||||
|
if r < rune(' ') { // ignore control characters
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if r == '\n' {
|
||||||
|
w.win().LastY++
|
||||||
|
lx = 0
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if r == '\u000D' { // skip carriage return
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var xPos = w.Left + w.win().LastX + lx
|
||||||
|
var yPos = w.Top + w.win().LastY
|
||||||
|
if xPos < (w.Left+w.Width) && yPos < (w.Top+w.Height) {
|
||||||
|
_screen.SetContent(xPos, yPos, r, nil, style)
|
||||||
|
}
|
||||||
|
lx += runewidth.RuneWidth(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.win().LastX += lx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) CPrint(pair ColorPair, a Attr, text string) {
|
||||||
|
w.PrintString(text, pair, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) FillString(text string, pair ColorPair, a Attr) bool {
|
||||||
|
lx := 0
|
||||||
|
|
||||||
|
var style tcell.Style
|
||||||
|
if _color {
|
||||||
|
style = pair.style()
|
||||||
|
} else {
|
||||||
|
style = ColDefault.style()
|
||||||
|
}
|
||||||
|
style = style.
|
||||||
|
Blink(a&Attr(tcell.AttrBlink) != 0).
|
||||||
|
Bold(a&Attr(tcell.AttrBold) != 0).
|
||||||
|
Dim(a&Attr(tcell.AttrDim) != 0).
|
||||||
|
Reverse(a&Attr(tcell.AttrReverse) != 0).
|
||||||
|
Underline(a&Attr(tcell.AttrUnderline) != 0)
|
||||||
|
|
||||||
|
for _, r := range text {
|
||||||
|
if r == '\n' {
|
||||||
|
w.win().LastY++
|
||||||
|
w.win().LastX = 0
|
||||||
|
lx = 0
|
||||||
|
} else {
|
||||||
|
var xPos = w.Left + w.win().LastX + lx
|
||||||
|
|
||||||
|
// word wrap:
|
||||||
|
if xPos > (w.Left + w.Width) {
|
||||||
|
w.win().LastY++
|
||||||
|
w.win().LastX = 0
|
||||||
|
lx = 0
|
||||||
|
xPos = w.Left
|
||||||
|
}
|
||||||
|
var yPos = w.Top + w.win().LastY
|
||||||
|
|
||||||
|
if yPos >= (w.Top + w.Height) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
_screen.SetContent(xPos, yPos, r, nil, style)
|
||||||
|
lx += runewidth.RuneWidth(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.win().LastX += lx
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) Fill(str string) bool {
|
||||||
|
return w.FillString(str, ColDefault, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) CFill(str string, fg Color, bg Color, a Attr) bool {
|
||||||
|
return w.FillString(str, ColorPair{fg, bg}, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) DrawBorder() {
|
||||||
|
left := w.Left
|
||||||
|
right := left + w.Width
|
||||||
|
top := w.Top
|
||||||
|
bot := top + w.Height
|
||||||
|
|
||||||
|
var style tcell.Style
|
||||||
|
if _color {
|
||||||
|
style = ColBorder.style()
|
||||||
|
} else {
|
||||||
|
style = ColDefault.style()
|
||||||
|
}
|
||||||
|
|
||||||
|
for x := left; x < right; x++ {
|
||||||
|
_screen.SetContent(x, top, tcell.RuneHLine, nil, style)
|
||||||
|
_screen.SetContent(x, bot-1, tcell.RuneHLine, nil, style)
|
||||||
|
}
|
||||||
|
|
||||||
|
for y := top; y < bot; y++ {
|
||||||
|
_screen.SetContent(left, y, tcell.RuneVLine, nil, style)
|
||||||
|
_screen.SetContent(right-1, y, tcell.RuneVLine, nil, style)
|
||||||
|
}
|
||||||
|
|
||||||
|
_screen.SetContent(left, top, tcell.RuneULCorner, nil, style)
|
||||||
|
_screen.SetContent(right-1, top, tcell.RuneURCorner, nil, style)
|
||||||
|
_screen.SetContent(left, bot-1, tcell.RuneLLCorner, nil, style)
|
||||||
|
_screen.SetContent(right-1, bot-1, tcell.RuneLRCorner, nil, style)
|
||||||
|
}
|
250
src/tui/tui.go
Normal file
250
src/tui/tui.go
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
package tui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Types of user action
|
||||||
|
const (
|
||||||
|
Rune = iota
|
||||||
|
|
||||||
|
CtrlA
|
||||||
|
CtrlB
|
||||||
|
CtrlC
|
||||||
|
CtrlD
|
||||||
|
CtrlE
|
||||||
|
CtrlF
|
||||||
|
CtrlG
|
||||||
|
CtrlH
|
||||||
|
Tab
|
||||||
|
CtrlJ
|
||||||
|
CtrlK
|
||||||
|
CtrlL
|
||||||
|
CtrlM
|
||||||
|
CtrlN
|
||||||
|
CtrlO
|
||||||
|
CtrlP
|
||||||
|
CtrlQ
|
||||||
|
CtrlR
|
||||||
|
CtrlS
|
||||||
|
CtrlT
|
||||||
|
CtrlU
|
||||||
|
CtrlV
|
||||||
|
CtrlW
|
||||||
|
CtrlX
|
||||||
|
CtrlY
|
||||||
|
CtrlZ
|
||||||
|
ESC
|
||||||
|
|
||||||
|
Invalid
|
||||||
|
Mouse
|
||||||
|
DoubleClick
|
||||||
|
|
||||||
|
BTab
|
||||||
|
BSpace
|
||||||
|
|
||||||
|
Del
|
||||||
|
PgUp
|
||||||
|
PgDn
|
||||||
|
|
||||||
|
Up
|
||||||
|
Down
|
||||||
|
Left
|
||||||
|
Right
|
||||||
|
Home
|
||||||
|
End
|
||||||
|
|
||||||
|
SLeft
|
||||||
|
SRight
|
||||||
|
|
||||||
|
F1
|
||||||
|
F2
|
||||||
|
F3
|
||||||
|
F4
|
||||||
|
F5
|
||||||
|
F6
|
||||||
|
F7
|
||||||
|
F8
|
||||||
|
F9
|
||||||
|
F10
|
||||||
|
|
||||||
|
AltEnter
|
||||||
|
AltSpace
|
||||||
|
AltSlash
|
||||||
|
AltBS
|
||||||
|
AltA
|
||||||
|
AltB
|
||||||
|
AltC
|
||||||
|
AltD
|
||||||
|
AltE
|
||||||
|
AltF
|
||||||
|
|
||||||
|
AltZ = AltA + 'z' - 'a'
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
doubleClickDuration = 500 * time.Millisecond
|
||||||
|
)
|
||||||
|
|
||||||
|
type Color int16
|
||||||
|
|
||||||
|
const (
|
||||||
|
colUndefined Color = -2
|
||||||
|
colDefault = -1
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
colBlack Color = iota
|
||||||
|
colRed
|
||||||
|
colGreen
|
||||||
|
colYellow
|
||||||
|
colBlue
|
||||||
|
colMagenta
|
||||||
|
colCyan
|
||||||
|
colWhite
|
||||||
|
)
|
||||||
|
|
||||||
|
type ColorTheme struct {
|
||||||
|
Fg Color
|
||||||
|
Bg Color
|
||||||
|
DarkBg Color
|
||||||
|
Prompt Color
|
||||||
|
Match Color
|
||||||
|
Current Color
|
||||||
|
CurrentMatch Color
|
||||||
|
Spinner Color
|
||||||
|
Info Color
|
||||||
|
Cursor Color
|
||||||
|
Selected Color
|
||||||
|
Header Color
|
||||||
|
Border Color
|
||||||
|
}
|
||||||
|
|
||||||
|
type Event struct {
|
||||||
|
Type int
|
||||||
|
Char rune
|
||||||
|
MouseEvent *MouseEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
type MouseEvent struct {
|
||||||
|
Y int
|
||||||
|
X int
|
||||||
|
S int
|
||||||
|
Down bool
|
||||||
|
Double bool
|
||||||
|
Mod bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_buf []byte
|
||||||
|
_color bool
|
||||||
|
_prevDownTime time.Time
|
||||||
|
_clickY []int
|
||||||
|
Default16 *ColorTheme
|
||||||
|
Dark256 *ColorTheme
|
||||||
|
Light256 *ColorTheme
|
||||||
|
)
|
||||||
|
|
||||||
|
type Window struct {
|
||||||
|
impl *WindowImpl
|
||||||
|
Top int
|
||||||
|
Left int
|
||||||
|
Width int
|
||||||
|
Height int
|
||||||
|
}
|
||||||
|
|
||||||
|
func EmptyTheme() *ColorTheme {
|
||||||
|
return &ColorTheme{
|
||||||
|
Fg: colUndefined,
|
||||||
|
Bg: colUndefined,
|
||||||
|
DarkBg: colUndefined,
|
||||||
|
Prompt: colUndefined,
|
||||||
|
Match: colUndefined,
|
||||||
|
Current: colUndefined,
|
||||||
|
CurrentMatch: colUndefined,
|
||||||
|
Spinner: colUndefined,
|
||||||
|
Info: colUndefined,
|
||||||
|
Cursor: colUndefined,
|
||||||
|
Selected: colUndefined,
|
||||||
|
Header: colUndefined,
|
||||||
|
Border: colUndefined}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
_prevDownTime = time.Unix(0, 0)
|
||||||
|
_clickY = []int{}
|
||||||
|
Default16 = &ColorTheme{
|
||||||
|
Fg: colDefault,
|
||||||
|
Bg: colDefault,
|
||||||
|
DarkBg: colBlack,
|
||||||
|
Prompt: colBlue,
|
||||||
|
Match: colGreen,
|
||||||
|
Current: colYellow,
|
||||||
|
CurrentMatch: colGreen,
|
||||||
|
Spinner: colGreen,
|
||||||
|
Info: colWhite,
|
||||||
|
Cursor: colRed,
|
||||||
|
Selected: colMagenta,
|
||||||
|
Header: colCyan,
|
||||||
|
Border: colBlack}
|
||||||
|
Dark256 = &ColorTheme{
|
||||||
|
Fg: colDefault,
|
||||||
|
Bg: colDefault,
|
||||||
|
DarkBg: 236,
|
||||||
|
Prompt: 110,
|
||||||
|
Match: 108,
|
||||||
|
Current: 254,
|
||||||
|
CurrentMatch: 151,
|
||||||
|
Spinner: 148,
|
||||||
|
Info: 144,
|
||||||
|
Cursor: 161,
|
||||||
|
Selected: 168,
|
||||||
|
Header: 109,
|
||||||
|
Border: 59}
|
||||||
|
Light256 = &ColorTheme{
|
||||||
|
Fg: colDefault,
|
||||||
|
Bg: colDefault,
|
||||||
|
DarkBg: 251,
|
||||||
|
Prompt: 25,
|
||||||
|
Match: 66,
|
||||||
|
Current: 237,
|
||||||
|
CurrentMatch: 23,
|
||||||
|
Spinner: 65,
|
||||||
|
Info: 101,
|
||||||
|
Cursor: 161,
|
||||||
|
Selected: 168,
|
||||||
|
Header: 31,
|
||||||
|
Border: 145}
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitTheme(theme *ColorTheme, black bool) {
|
||||||
|
_color = theme != nil
|
||||||
|
if !_color {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
baseTheme := DefaultTheme()
|
||||||
|
if black {
|
||||||
|
theme.Bg = colBlack
|
||||||
|
}
|
||||||
|
|
||||||
|
o := func(a Color, b Color) Color {
|
||||||
|
if b == colUndefined {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
theme.Fg = o(baseTheme.Fg, theme.Fg)
|
||||||
|
theme.Bg = o(baseTheme.Bg, theme.Bg)
|
||||||
|
theme.DarkBg = o(baseTheme.DarkBg, theme.DarkBg)
|
||||||
|
theme.Prompt = o(baseTheme.Prompt, theme.Prompt)
|
||||||
|
theme.Match = o(baseTheme.Match, theme.Match)
|
||||||
|
theme.Current = o(baseTheme.Current, theme.Current)
|
||||||
|
theme.CurrentMatch = o(baseTheme.CurrentMatch, theme.CurrentMatch)
|
||||||
|
theme.Spinner = o(baseTheme.Spinner, theme.Spinner)
|
||||||
|
theme.Info = o(baseTheme.Info, theme.Info)
|
||||||
|
theme.Cursor = o(baseTheme.Cursor, theme.Cursor)
|
||||||
|
theme.Selected = o(baseTheme.Selected, theme.Selected)
|
||||||
|
theme.Header = o(baseTheme.Header, theme.Header)
|
||||||
|
theme.Border = o(baseTheme.Border, theme.Border)
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
package curses
|
package tui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
@@ -1,13 +1,11 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
// #include <unistd.h>
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/junegunn/go-isatty"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Max returns the largest integer
|
// Max returns the largest integer
|
||||||
@@ -95,14 +93,5 @@ func DurWithin(
|
|||||||
|
|
||||||
// IsTty returns true is stdin is a terminal
|
// IsTty returns true is stdin is a terminal
|
||||||
func IsTty() bool {
|
func IsTty() bool {
|
||||||
return int(C.isatty(C.int(os.Stdin.Fd()))) != 0
|
return isatty.IsTerminal(os.Stdin.Fd())
|
||||||
}
|
|
||||||
|
|
||||||
// ExecCommand executes the given command with $SHELL
|
|
||||||
func ExecCommand(command string) *exec.Cmd {
|
|
||||||
shell := os.Getenv("SHELL")
|
|
||||||
if len(shell) == 0 {
|
|
||||||
shell = "sh"
|
|
||||||
}
|
|
||||||
return exec.Command(shell, "-c", command)
|
|
||||||
}
|
}
|
||||||
|
22
src/util/util_unix.go
Normal file
22
src/util/util_unix.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExecCommand executes the given command with $SHELL
|
||||||
|
func ExecCommand(command string) *exec.Cmd {
|
||||||
|
shell := os.Getenv("SHELL")
|
||||||
|
if len(shell) == 0 {
|
||||||
|
shell = "sh"
|
||||||
|
}
|
||||||
|
return exec.Command(shell, "-c", command)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsWindows returns true on Windows
|
||||||
|
func IsWindows() bool {
|
||||||
|
return false
|
||||||
|
}
|
28
src/util/util_windows.go
Normal file
28
src/util/util_windows.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"github.com/junegunn/go-shellwords"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExecCommand executes the given command with $SHELL
|
||||||
|
func ExecCommand(command string) *exec.Cmd {
|
||||||
|
shell := os.Getenv("SHELL")
|
||||||
|
if len(shell) == 0 {
|
||||||
|
shell = "cmd"
|
||||||
|
}
|
||||||
|
args, _ := shellwords.Parse(command)
|
||||||
|
allArgs := make([]string, len(args)+1)
|
||||||
|
allArgs[0] = "/c"
|
||||||
|
copy(allArgs[1:], args)
|
||||||
|
return exec.Command(shell, allArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsWindows returns true on Windows
|
||||||
|
func IsWindows() bool {
|
||||||
|
return true
|
||||||
|
}
|
@@ -143,6 +143,10 @@ Execute (fzf#wrap):
|
|||||||
Assert opts.options =~ '--history /tmp/foobar'
|
Assert opts.options =~ '--history /tmp/foobar'
|
||||||
Assert opts.options =~ '--color light'
|
Assert opts.options =~ '--color light'
|
||||||
|
|
||||||
|
let g:fzf_colors = { 'fg': ['fg', 'Error'] }
|
||||||
|
let opts = fzf#wrap({})
|
||||||
|
Assert opts.options =~ '^--color=fg:'
|
||||||
|
|
||||||
Execute (Cleanup):
|
Execute (Cleanup):
|
||||||
unlet g:dir
|
unlet g:dir
|
||||||
Restore
|
Restore
|
||||||
|
@@ -136,8 +136,10 @@ class Tmux
|
|||||||
def prepare
|
def prepare
|
||||||
tries = 0
|
tries = 0
|
||||||
begin
|
begin
|
||||||
self.send_keys 'C-u', 'hello', 'Right'
|
self.until do |lines|
|
||||||
self.until { |lines| lines[-1].end_with?('hello') }
|
self.send_keys 'C-u', 'hello'
|
||||||
|
lines[-1].end_with?('hello')
|
||||||
|
end
|
||||||
rescue Exception
|
rescue Exception
|
||||||
(tries += 1) < 5 ? retry : raise
|
(tries += 1) < 5 ? retry : raise
|
||||||
end
|
end
|
||||||
@@ -604,8 +606,8 @@ class TestGoFZF < TestBase
|
|||||||
], `#{FZF} -fo --tiebreak=end < #{tempname}`.split($/)
|
], `#{FZF} -fo --tiebreak=end < #{tempname}`.split($/)
|
||||||
|
|
||||||
assert_equal [
|
assert_equal [
|
||||||
' xxxxoxxx',
|
|
||||||
'xxxxxoxxx',
|
'xxxxxoxxx',
|
||||||
|
' xxxxoxxx',
|
||||||
'xxxxoxxxx',
|
'xxxxoxxxx',
|
||||||
'xxxoxxxxxx',
|
'xxxoxxxxxx',
|
||||||
'xxoxxxxxxx',
|
'xxoxxxxxxx',
|
||||||
@@ -1056,7 +1058,7 @@ class TestGoFZF < TestBase
|
|||||||
def test_invalid_term
|
def test_invalid_term
|
||||||
lines = `TERM=xxx #{FZF}`
|
lines = `TERM=xxx #{FZF}`
|
||||||
assert_equal 2, $?.exitstatus
|
assert_equal 2, $?.exitstatus
|
||||||
assert lines.include?('Invalid $TERM: xxx')
|
assert lines.include?('Invalid $TERM: xxx') || lines.include?('terminal entry not found')
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_invalid_option
|
def test_invalid_option
|
||||||
@@ -1251,24 +1253,29 @@ module TestShell
|
|||||||
tmux.send_keys 'cat ', 'C-t', pane: 0
|
tmux.send_keys 'cat ', 'C-t', pane: 0
|
||||||
tmux.until(1) { |lines| lines.item_count >= 1 }
|
tmux.until(1) { |lines| lines.item_count >= 1 }
|
||||||
tmux.send_keys 'fzf-unicode', pane: 1
|
tmux.send_keys 'fzf-unicode', pane: 1
|
||||||
tmux.until(1) { |lines| lines[-2].start_with? ' 2/' }
|
redraw = ->() { tmux.send_keys 'C-l', pane: 1 }
|
||||||
|
tmux.until(1) { |lines| redraw.(); lines[-2] =~ %r(^ *2/) }
|
||||||
|
|
||||||
tmux.send_keys '1', pane: 1
|
tmux.send_keys '1', pane: 1
|
||||||
tmux.until(1) { |lines| lines[-2].start_with? ' 1/' }
|
tmux.until(1) { |lines| redraw.(); lines[-2] =~ %r(^ *1/) }
|
||||||
tmux.send_keys :BTab, pane: 1
|
tmux.send_keys :BTab, pane: 1
|
||||||
tmux.until(1) { |lines| lines[-2].include? '(1)' }
|
tmux.until(1) { |lines| redraw.(); lines[-2].include? '(1)' }
|
||||||
|
|
||||||
tmux.send_keys :BSpace, pane: 1
|
tmux.send_keys :BSpace, pane: 1
|
||||||
tmux.until(1) { |lines| lines[-2].start_with? ' 2/' }
|
tmux.until(1) { |lines| redraw.(); lines[-2] =~ %r(^ *2/) }
|
||||||
|
|
||||||
tmux.send_keys '2', pane: 1
|
tmux.send_keys '2', pane: 1
|
||||||
tmux.until(1) { |lines| lines[-2].start_with? ' 1/' }
|
tmux.until(1) { |lines| redraw.(); lines[-2] =~ %r(^ *1/) }
|
||||||
tmux.send_keys :BTab, pane: 1
|
tmux.send_keys :BTab, pane: 1
|
||||||
tmux.until(1) { |lines| lines[-2].include? '(2)' }
|
tmux.until(1) { |lines| redraw.(); lines[-2].include? '(2)' }
|
||||||
|
|
||||||
tmux.send_keys :Enter, pane: 1
|
tmux.send_keys :Enter, pane: 1
|
||||||
tmux.until { |lines| lines[-1].include?('cat') || lines[-2].include?('cat') }
|
tmux.until do |lines|
|
||||||
tmux.until { |lines| lines[-1].include?('fzf-unicode') || lines[-2].include?('fzf-unicode') }
|
tmux.send_keys 'C-l'
|
||||||
|
[-1, -2].map { |offset| lines[offset] }.any? do |line|
|
||||||
|
line.start_with?('cat') && line.include?('fzf-unicode')
|
||||||
|
end
|
||||||
|
end
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until { |lines| lines[-1].include? 'test1test2' }
|
tmux.until { |lines| lines[-1].include? 'test1test2' }
|
||||||
end
|
end
|
||||||
@@ -1479,23 +1486,27 @@ module CompletionTest
|
|||||||
tmux.paste 'cd /tmp/fzf-test; echo -n test3 > "fzf-unicode 테스트1"; echo -n test4 > "fzf-unicode 테스트2"'
|
tmux.paste 'cd /tmp/fzf-test; echo -n test3 > "fzf-unicode 테스트1"; echo -n test4 > "fzf-unicode 테스트2"'
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'cat fzf-unicode**', :Tab, pane: 0
|
tmux.send_keys 'cat fzf-unicode**', :Tab, pane: 0
|
||||||
tmux.until(1) { |lines| lines[-2].start_with? ' 2/' }
|
redraw = ->() { tmux.send_keys 'C-l', pane: 1 }
|
||||||
|
tmux.until(1) { |lines| redraw.(); lines[-2] =~ %r(^ *2/) }
|
||||||
|
|
||||||
tmux.send_keys '1', pane: 1
|
tmux.send_keys '1', pane: 1
|
||||||
tmux.until(1) { |lines| lines[-2].start_with? ' 1/' }
|
tmux.until(1) { |lines| redraw.(); lines[-2] =~ %r(^ *1/) }
|
||||||
tmux.send_keys :BTab, pane: 1
|
tmux.send_keys :BTab, pane: 1
|
||||||
tmux.until(1) { |lines| lines[-2].include? '(1)' }
|
tmux.until(1) { |lines| redraw.(); lines[-2].include? '(1)' }
|
||||||
|
|
||||||
tmux.send_keys :BSpace, pane: 1
|
tmux.send_keys :BSpace, pane: 1
|
||||||
tmux.until(1) { |lines| lines[-2].start_with? ' 2/' }
|
tmux.until(1) { |lines| redraw.(); lines[-2] =~ %r(^ *2/) }
|
||||||
|
|
||||||
tmux.send_keys '2', pane: 1
|
tmux.send_keys '2', pane: 1
|
||||||
tmux.until(1) { |lines| lines[-2].start_with? ' 1/' }
|
tmux.until(1) { |lines| redraw.(); lines[-2] =~ %r(^ *1/) }
|
||||||
tmux.send_keys :BTab, pane: 1
|
tmux.send_keys :BTab, pane: 1
|
||||||
tmux.until(1) { |lines| lines[-2].include? '(2)' }
|
tmux.until(1) { |lines| redraw.(); lines[-2].include? '(2)' }
|
||||||
|
|
||||||
tmux.send_keys :Enter, pane: 1
|
tmux.send_keys :Enter, pane: 1
|
||||||
tmux.until { |lines| lines[-1].include?('cat') || lines[-2].include?('cat') }
|
tmux.until do |lines|
|
||||||
|
tmux.send_keys 'C-l'
|
||||||
|
lines[-1].include?('cat') || lines[-2].include?('cat')
|
||||||
|
end
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until { |lines| lines[-1].include? 'test3test4' }
|
tmux.until { |lines| lines[-1].include? 'test3test4' }
|
||||||
end
|
end
|
||||||
|
Reference in New Issue
Block a user