mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2024-11-11 08:10:54 +00:00
commit
05ed57a9f0
@ -2,7 +2,7 @@ language: ruby
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- env: TAGS=
|
- env: TAGS=
|
||||||
rvm: 2.2.0
|
rvm: 2.3.3
|
||||||
# - env: TAGS=tcell
|
# - env: TAGS=tcell
|
||||||
# rvm: 2.2.0
|
# rvm: 2.2.0
|
||||||
|
|
||||||
|
12
CHANGELOG.md
12
CHANGELOG.md
@ -1,6 +1,18 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.16.0
|
||||||
|
------
|
||||||
|
- *Added `--height HEIGHT[%]` option*
|
||||||
|
- fzf can now display finder without occupying the full screen
|
||||||
|
- Preview window will truncate long lines by default. Line wrap can be enabled
|
||||||
|
by `:wrap` flag in `--preview-window`.
|
||||||
|
- Latin script letters will be normalized before matching so that it's easier
|
||||||
|
to match against accented letters. e.g. `sodanco` can match `Só Danço Samba`.
|
||||||
|
- Normalization can be disabled via `--literal`
|
||||||
|
- Added `--filepath-word` to make word-wise movements/actions (`alt-b`,
|
||||||
|
`alt-f`, `alt-bs`, `alt-d`) respect path separators
|
||||||
|
|
||||||
0.15.9
|
0.15.9
|
||||||
------
|
------
|
||||||
- Fixed rendering glitches introduced in 0.15.8
|
- Fixed rendering glitches introduced in 0.15.8
|
||||||
|
47
README.md
47
README.md
@ -123,6 +123,29 @@ vim $(fzf)
|
|||||||
- Mouse: scroll, click, double-click; shift-click and shift-scroll on
|
- Mouse: scroll, click, double-click; shift-click and shift-scroll on
|
||||||
multi-select mode
|
multi-select mode
|
||||||
|
|
||||||
|
#### Layout
|
||||||
|
|
||||||
|
fzf by default starts in fullscreen mode, but you can make it start below the
|
||||||
|
cursor with `--height` option.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
vim $(fzf --height 40%)
|
||||||
|
```
|
||||||
|
|
||||||
|
Also check out `--reverse` option if you prefer "top-down" layout instead of
|
||||||
|
the default "bottom-up" layout.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
vim $(fzf --height 40% --reverse)
|
||||||
|
```
|
||||||
|
|
||||||
|
You can add these options to `$FZF_DEFAULT_OPTS` so that they're applied by
|
||||||
|
default.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export FZF_DEFAULT_OPTS='--height 40% --reverse'
|
||||||
|
```
|
||||||
|
|
||||||
#### Search syntax
|
#### Search syntax
|
||||||
|
|
||||||
Unless otherwise specified, fzf starts in "extended-search mode" where you can
|
Unless otherwise specified, fzf starts in "extended-search mode" where you can
|
||||||
@ -189,6 +212,13 @@ cat /usr/share/dict/words | fzf-tmux -l 20% --multi --reverse
|
|||||||
It will still work even when you're not on tmux, silently ignoring `-[udlr]`
|
It will still work even when you're not on tmux, silently ignoring `-[udlr]`
|
||||||
options, so you can invariably use `fzf-tmux` in your scripts.
|
options, so you can invariably use `fzf-tmux` in your scripts.
|
||||||
|
|
||||||
|
Alternatively, you can use `--height HEIGHT[%]` option not to start fzf in
|
||||||
|
fullscreen mode.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
fzf --height 40%
|
||||||
|
```
|
||||||
|
|
||||||
Key bindings for command line
|
Key bindings for command line
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
@ -206,9 +236,9 @@ fish.
|
|||||||
- Set `FZF_ALT_C_COMMAND` to override the default command
|
- Set `FZF_ALT_C_COMMAND` to override the default command
|
||||||
- Set `FZF_ALT_C_OPTS` to pass additional options
|
- Set `FZF_ALT_C_OPTS` to pass additional options
|
||||||
|
|
||||||
If you're on a tmux session, fzf will start in a split pane. You may disable
|
If you're on a tmux session, you can start fzf in a split pane by setting
|
||||||
this tmux integration by setting `FZF_TMUX` to 0, or change the height of the
|
`FZF_TMUX` to 1, and change the height of the pane with `FZF_TMUX_HEIGHT`
|
||||||
pane with `FZF_TMUX_HEIGHT` (e.g. `20`, `50%`).
|
(e.g. `20`, `50%`).
|
||||||
|
|
||||||
If you use vi mode on bash, you need to add `set -o vi` *before* `source
|
If you use vi mode on bash, you need to add `set -o vi` *before* `source
|
||||||
~/.fzf.bash` in your .bashrc, so that it correctly sets up key bindings for vi
|
~/.fzf.bash` in your .bashrc, so that it correctly sets up key bindings for vi
|
||||||
@ -466,12 +496,9 @@ valid directory. Example:
|
|||||||
set -l FZF_CTRL_T_COMMAND "command find -L \$dir -type f 2> /dev/null | sed '1d; s#^\./##'"
|
set -l FZF_CTRL_T_COMMAND "command find -L \$dir -type f 2> /dev/null | sed '1d; s#^\./##'"
|
||||||
```
|
```
|
||||||
|
|
||||||
License
|
[License](LICENSE)
|
||||||
-------
|
------------------
|
||||||
|
|
||||||
[MIT](LICENSE)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Author
|
Copyright (c) 2017 Junegunn Choi
|
||||||
------
|
|
||||||
|
|
||||||
Junegunn Choi
|
|
||||||
|
@ -114,6 +114,9 @@ if [[ -z "$TMUX" || "$opt" =~ ^-h && "$columns" -le 40 || ! "$opt" =~ ^-h && "$l
|
|||||||
exit $?
|
exit $?
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# --height option is not allowed
|
||||||
|
args+=("--no-height")
|
||||||
|
|
||||||
# Handle zoomed tmux pane by moving it to a temp window
|
# Handle zoomed tmux pane by moving it to a temp window
|
||||||
if tmux list-panes -F '#F' | grep -q Z; then
|
if tmux list-panes -F '#F' | grep -q Z; then
|
||||||
zoomed=1
|
zoomed=1
|
||||||
|
13
install
13
install
@ -2,9 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
[[ "$@" =~ --pre ]] && version=0.15.9 pre=1 ||
|
version=0.16.0
|
||||||
version=0.15.9 pre=0
|
|
||||||
|
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
@ -48,7 +46,7 @@ for opt in "$@"; do
|
|||||||
--no-update-rc) update_config=0 ;;
|
--no-update-rc) update_config=0 ;;
|
||||||
--32) binary_arch=386 ;;
|
--32) binary_arch=386 ;;
|
||||||
--64) binary_arch=amd64 ;;
|
--64) binary_arch=amd64 ;;
|
||||||
--bin|--pre) ;;
|
--bin) ;;
|
||||||
*)
|
*)
|
||||||
echo "unknown option: $opt"
|
echo "unknown option: $opt"
|
||||||
help
|
help
|
||||||
@ -121,7 +119,7 @@ try_wget() {
|
|||||||
|
|
||||||
download() {
|
download() {
|
||||||
echo "Downloading bin/fzf ..."
|
echo "Downloading bin/fzf ..."
|
||||||
if [ $pre = 0 ]; then
|
if [[ ! "$version" =~ alpha ]]; then
|
||||||
if [ -x "$fzf_base"/bin/fzf ]; then
|
if [ -x "$fzf_base"/bin/fzf ]; then
|
||||||
echo " - Already exists"
|
echo " - Already exists"
|
||||||
check_binary && return
|
check_binary && return
|
||||||
@ -137,7 +135,10 @@ download() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local url=https://github.com/junegunn/fzf-bin/releases/download/$version/${1}.tgz
|
local url
|
||||||
|
[[ "$version" =~ alpha ]] &&
|
||||||
|
url=https://github.com/junegunn/fzf-bin/releases/download/alpha/${1}.tgz ||
|
||||||
|
url=https://github.com/junegunn/fzf-bin/releases/download/$version/${1}.tgz
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
if ! (try_curl $url || try_wget $url); then
|
if ! (try_curl $url || try_wget $url); then
|
||||||
set +o pipefail
|
set +o pipefail
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
.ig
|
.ig
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2016 Junegunn Choi
|
Copyright (c) 2017 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@ -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 "Nov 2016" "fzf 0.15.9" "fzf-tmux - open fzf in tmux split pane"
|
.TH fzf-tmux 1 "Jan 2017" "fzf 0.16.0" "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
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
.ig
|
.ig
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2016 Junegunn Choi
|
Copyright (c) 2017 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@ -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 "Nov 2016" "fzf 0.15.9" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Jan 2017" "fzf 0.16.0" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@ -48,6 +48,9 @@ Case-insensitive match (default: smart-case match)
|
|||||||
.B "+i"
|
.B "+i"
|
||||||
Case-sensitive match
|
Case-sensitive match
|
||||||
.TP
|
.TP
|
||||||
|
.B "--literal"
|
||||||
|
Do not normalize latin script letters for matching.
|
||||||
|
.TP
|
||||||
.BI "--algo=" TYPE
|
.BI "--algo=" TYPE
|
||||||
Fuzzy matching algorithm (default: v2)
|
Fuzzy matching algorithm (default: v2)
|
||||||
|
|
||||||
@ -126,10 +129,30 @@ Number of screen columns to keep to the right of the highlighted substring
|
|||||||
(default: 10). Setting it to a large value will cause the text to be positioned
|
(default: 10). Setting it to a large value will cause the text to be positioned
|
||||||
on the center of the screen.
|
on the center of the screen.
|
||||||
.TP
|
.TP
|
||||||
|
.B "--filepath-word"
|
||||||
|
Make word-wise movements and actions respect path separators. The following
|
||||||
|
actions are affected:
|
||||||
|
|
||||||
|
\fBbackward-kill-word\fR
|
||||||
|
.br
|
||||||
|
\fBbackward-word\fR
|
||||||
|
.br
|
||||||
|
\fBforward-word\fR
|
||||||
|
.br
|
||||||
|
\fBkill-word\fR
|
||||||
|
.TP
|
||||||
.BI "--jump-labels=" "CHARS"
|
.BI "--jump-labels=" "CHARS"
|
||||||
Label characters for \fBjump\fR and \fBjump-accept\fR
|
Label characters for \fBjump\fR and \fBjump-accept\fR
|
||||||
.SS Layout
|
.SS Layout
|
||||||
.TP
|
.TP
|
||||||
|
.BI "--height=" "HEIGHT[%]"
|
||||||
|
Display fzf window below the cursor with the given height instead of using
|
||||||
|
the full screen.
|
||||||
|
.TP
|
||||||
|
.BI "--min-height=" "HEIGHT"
|
||||||
|
Minimum height when \fB--height\fR is given in percent (default: 10).
|
||||||
|
Ignored when \fB--height\fR is not specified.
|
||||||
|
.TP
|
||||||
.B "--reverse"
|
.B "--reverse"
|
||||||
Reverse orientation
|
Reverse orientation
|
||||||
.TP
|
.TP
|
||||||
@ -185,7 +208,9 @@ Number of spaces for a tab character (default: 8)
|
|||||||
.BI "--color=" "[BASE_SCHEME][,COLOR:ANSI]"
|
.BI "--color=" "[BASE_SCHEME][,COLOR:ANSI]"
|
||||||
Color configuration. The name of the base color scheme is followed by custom
|
Color configuration. The name of the base color scheme is followed by custom
|
||||||
color mappings. Ansi color code of -1 denotes terminal default
|
color mappings. Ansi color code of -1 denotes terminal default
|
||||||
foreground/background color.
|
foreground/background color. You can also specify 24-bit color in \fB#rrggbb\fR
|
||||||
|
format, but the support for 24-bit colors is experimental and only works when
|
||||||
|
\fB--height\fR option is used.
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
e.g. \fBfzf --color=bg+:24\fR
|
e.g. \fBfzf --color=bg+:24\fR
|
||||||
@ -248,10 +273,11 @@ e.g. \fBfzf --preview="head -$LINES {}"\fR
|
|||||||
Note that you can escape a placeholder pattern by prepending a backslash.
|
Note that you can escape a placeholder pattern by prepending a backslash.
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.BI "--preview-window=" "[POSITION][:SIZE[%]][:hidden]"
|
.BI "--preview-window=" "[POSITION][:SIZE[%]][:wrap][:hidden]"
|
||||||
Determine the layout of the preview window. If the argument ends with
|
Determine the layout of the preview window. If the argument ends with
|
||||||
\fB:hidden\fR, the preview window will be hidden by default until
|
\fB:hidden\fR, the preview window will be hidden by default until
|
||||||
\fBtoggle-preview\fR action is triggered.
|
\fBtoggle-preview\fR action is triggered. Long lines are truncated by default.
|
||||||
|
Line wrap can be enabled with \fB:wrap\fR flag.
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
.B POSITION: (default: right)
|
.B POSITION: (default: right)
|
||||||
|
@ -297,14 +297,25 @@ try
|
|||||||
else
|
else
|
||||||
let prefix = ''
|
let prefix = ''
|
||||||
endif
|
endif
|
||||||
let tmux = (!has('nvim') || get(g:, 'fzf_prefer_tmux', 0)) && s:tmux_enabled() && s:splittable(dict)
|
|
||||||
|
let use_height = has_key(dict, 'down') &&
|
||||||
|
\ !(has('nvim') || has('win32') || has('win64') || s:present(dict, 'up', 'left', 'right')) &&
|
||||||
|
\ executable('tput') && filereadable('/dev/tty')
|
||||||
|
let tmux = !use_height && (!has('nvim') || get(g:, 'fzf_prefer_tmux', 0)) && s:tmux_enabled() && s:splittable(dict)
|
||||||
|
let term = has('nvim') && !tmux
|
||||||
|
if use_height
|
||||||
|
let optstr .= ' --height='.s:calc_size(&lines, dict.down, dict)
|
||||||
|
elseif term
|
||||||
|
let optstr .= ' --no-height'
|
||||||
|
endif
|
||||||
let command = prefix.(tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
|
let command = prefix.(tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
|
||||||
|
|
||||||
if has('nvim') && !tmux
|
if term
|
||||||
return s:execute_term(dict, command, temps)
|
return s:execute_term(dict, command, temps)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
let lines = tmux ? s:execute_tmux(dict, command, temps) : s:execute(dict, command, temps)
|
let lines = tmux ? s:execute_tmux(dict, command, temps)
|
||||||
|
\ : s:execute(dict, command, use_height, temps)
|
||||||
call s:callback(dict, lines)
|
call s:callback(dict, lines)
|
||||||
return lines
|
return lines
|
||||||
finally
|
finally
|
||||||
@ -401,9 +412,9 @@ function! s:exit_handler(code, command, ...)
|
|||||||
return 1
|
return 1
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:execute(dict, command, temps) abort
|
function! s:execute(dict, command, use_height, temps) abort
|
||||||
call s:pushd(a:dict)
|
call s:pushd(a:dict)
|
||||||
if has('unix')
|
if has('unix') && !a:use_height
|
||||||
silent! !clear 2> /dev/null
|
silent! !clear 2> /dev/null
|
||||||
endif
|
endif
|
||||||
let escaped = escape(substitute(a:command, '\n', '\\n', 'g'), '%#')
|
let escaped = escape(substitute(a:command, '\n', '\\n', 'g'), '%#')
|
||||||
@ -417,7 +428,12 @@ function! s:execute(dict, command, temps) abort
|
|||||||
else
|
else
|
||||||
let command = escaped
|
let command = escaped
|
||||||
endif
|
endif
|
||||||
execute 'silent !'.command
|
if a:use_height
|
||||||
|
let stdin = has_key(a:dict, 'source') ? '' : '< /dev/tty'
|
||||||
|
call system(printf('tput cup %d > /dev/tty; tput cnorm > /dev/tty; %s %s 2> /dev/tty', &lines, command, stdin))
|
||||||
|
else
|
||||||
|
execute 'silent !'.command
|
||||||
|
endif
|
||||||
let exit_status = v:shell_error
|
let exit_status = v:shell_error
|
||||||
redraw!
|
redraw!
|
||||||
return s:exit_handler(exit_status, command) ? s:collect(a:temps) : []
|
return s:exit_handler(exit_status, command) ? s:collect(a:temps) : []
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
# / __/ / /_/ __/
|
# / __/ / /_/ __/
|
||||||
# /_/ /___/_/-completion.bash
|
# /_/ /___/_/-completion.bash
|
||||||
#
|
#
|
||||||
# - $FZF_TMUX (default: 1)
|
# - $FZF_TMUX (default: 0)
|
||||||
# - $FZF_TMUX_HEIGHT (default: '40%')
|
# - $FZF_TMUX_HEIGHT (default: '40%')
|
||||||
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||||
@ -30,6 +30,14 @@ fi
|
|||||||
|
|
||||||
###########################################################
|
###########################################################
|
||||||
|
|
||||||
|
# To redraw line after fzf closes (printf '\e[5n')
|
||||||
|
bind '"\e[0n": redraw-current-line'
|
||||||
|
|
||||||
|
__fzfcmd_complete() {
|
||||||
|
[ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ] &&
|
||||||
|
echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
|
||||||
|
}
|
||||||
|
|
||||||
_fzf_orig_completion_filter() {
|
_fzf_orig_completion_filter() {
|
||||||
sed 's/^\(.*-F\) *\([^ ]*\).* \([^ ]*\)$/export _fzf_orig_completion_\3="\1 %s \3 #\2";/' |
|
sed 's/^\(.*-F\) *\([^ ]*\).* \([^ ]*\)$/export _fzf_orig_completion_\3="\1 %s \3 #\2";/' |
|
||||||
awk -F= '{gsub(/[^A-Za-z0-9_= ;]/, "_", $1); print $1"="$2}'
|
awk -F= '{gsub(/[^A-Za-z0-9_= ;]/, "_", $1); print $1"="$2}'
|
||||||
@ -43,35 +51,43 @@ _fzf_opts_completion() {
|
|||||||
opts="
|
opts="
|
||||||
-x --extended
|
-x --extended
|
||||||
-e --exact
|
-e --exact
|
||||||
|
--algo
|
||||||
-i +i
|
-i +i
|
||||||
-n --nth
|
-n --nth
|
||||||
|
--with-nth
|
||||||
-d --delimiter
|
-d --delimiter
|
||||||
+s --no-sort
|
+s --no-sort
|
||||||
--tac
|
--tac
|
||||||
--tiebreak
|
--tiebreak
|
||||||
--bind
|
|
||||||
-m --multi
|
-m --multi
|
||||||
--no-mouse
|
--no-mouse
|
||||||
--color
|
--bind
|
||||||
--black
|
--cycle
|
||||||
--reverse
|
|
||||||
--no-hscroll
|
--no-hscroll
|
||||||
|
--jump-labels
|
||||||
|
--height
|
||||||
|
--literal
|
||||||
|
--reverse
|
||||||
|
--margin
|
||||||
--inline-info
|
--inline-info
|
||||||
--prompt
|
--prompt
|
||||||
|
--header
|
||||||
|
--header-lines
|
||||||
|
--ansi
|
||||||
|
--tabstop
|
||||||
|
--color
|
||||||
|
--no-bold
|
||||||
|
--history
|
||||||
|
--history-size
|
||||||
|
--preview
|
||||||
|
--preview-window
|
||||||
-q --query
|
-q --query
|
||||||
-1 --select-1
|
-1 --select-1
|
||||||
-0 --exit-0
|
-0 --exit-0
|
||||||
-f --filter
|
-f --filter
|
||||||
--print-query
|
--print-query
|
||||||
--expect
|
--expect
|
||||||
--toggle-sort
|
--sync"
|
||||||
--sync
|
|
||||||
--cycle
|
|
||||||
--history
|
|
||||||
--history-size
|
|
||||||
--header
|
|
||||||
--header-lines
|
|
||||||
--margin"
|
|
||||||
|
|
||||||
case "${prev}" in
|
case "${prev}" in
|
||||||
--tiebreak)
|
--tiebreak)
|
||||||
@ -116,7 +132,7 @@ _fzf_handle_dynamic_completion() {
|
|||||||
|
|
||||||
__fzf_generic_path_completion() {
|
__fzf_generic_path_completion() {
|
||||||
local cur base dir leftover matches trigger cmd fzf
|
local cur base dir leftover matches trigger cmd fzf
|
||||||
[ "${FZF_TMUX:-1}" != 0 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
|
fzf="$(__fzfcmd_complete)"
|
||||||
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
|
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
|
||||||
COMPREPLY=()
|
COMPREPLY=()
|
||||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||||
@ -132,8 +148,7 @@ __fzf_generic_path_completion() {
|
|||||||
leftover=${leftover/#\/}
|
leftover=${leftover/#\/}
|
||||||
[ -z "$dir" ] && dir='.'
|
[ -z "$dir" ] && dir='.'
|
||||||
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
||||||
tput sc
|
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" $fzf $2 -q "$leftover" | while read -r item; do
|
||||||
matches=$(eval "$1 $(printf %q "$dir")" | $fzf $FZF_COMPLETION_OPTS $2 -q "$leftover" | while read -r item; do
|
|
||||||
printf "%q$3 " "$item"
|
printf "%q$3 " "$item"
|
||||||
done)
|
done)
|
||||||
matches=${matches% }
|
matches=${matches% }
|
||||||
@ -142,7 +157,7 @@ __fzf_generic_path_completion() {
|
|||||||
else
|
else
|
||||||
COMPREPLY=( "$cur" )
|
COMPREPLY=( "$cur" )
|
||||||
fi
|
fi
|
||||||
tput rc
|
printf '\e[5n'
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
dir=$(dirname "$dir")
|
dir=$(dirname "$dir")
|
||||||
@ -160,7 +175,7 @@ _fzf_complete() {
|
|||||||
local cur selected trigger cmd fzf post
|
local cur selected trigger cmd fzf post
|
||||||
post="$(caller 0 | awk '{print $2}')_post"
|
post="$(caller 0 | awk '{print $2}')_post"
|
||||||
type -t "$post" > /dev/null 2>&1 || post=cat
|
type -t "$post" > /dev/null 2>&1 || post=cat
|
||||||
[ "${FZF_TMUX:-1}" != 0 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
|
fzf="$(__fzfcmd_complete)"
|
||||||
|
|
||||||
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
|
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
|
||||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||||
@ -168,10 +183,9 @@ _fzf_complete() {
|
|||||||
if [[ "$cur" == *"$trigger" ]]; then
|
if [[ "$cur" == *"$trigger" ]]; then
|
||||||
cur=${cur:0:${#cur}-${#trigger}}
|
cur=${cur:0:${#cur}-${#trigger}}
|
||||||
|
|
||||||
tput sc
|
selected=$(cat | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" $fzf $1 -q "$cur" | $post | tr '\n' ' ')
|
||||||
selected=$(cat | $fzf $FZF_COMPLETION_OPTS $1 -q "$cur" | $post | tr '\n' ' ')
|
|
||||||
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
|
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
|
||||||
tput rc
|
printf '\e[5n'
|
||||||
|
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
COMPREPLY=("$selected")
|
COMPREPLY=("$selected")
|
||||||
@ -200,10 +214,9 @@ _fzf_complete_kill() {
|
|||||||
[ -n "${COMP_WORDS[COMP_CWORD]}" ] && return 1
|
[ -n "${COMP_WORDS[COMP_CWORD]}" ] && return 1
|
||||||
|
|
||||||
local selected fzf
|
local selected fzf
|
||||||
[ "${FZF_TMUX:-1}" != 0 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
|
fzf="$(__fzfcmd_complete)"
|
||||||
tput sc
|
selected=$(ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS --preview 'echo {}' --preview-window down:3:wrap $FZF_COMPLETION_OPTS" $fzf -m | awk '{print $2}' | tr '\n' ' ')
|
||||||
selected=$(ps -ef | sed 1d | $fzf -m $FZF_COMPLETION_OPTS | awk '{print $2}' | tr '\n' ' ')
|
printf '\e[5n'
|
||||||
tput rc
|
|
||||||
|
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
COMPREPLY=( "$selected" )
|
COMPREPLY=( "$selected" )
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
# / __/ / /_/ __/
|
# / __/ / /_/ __/
|
||||||
# /_/ /___/_/-completion.zsh
|
# /_/ /___/_/-completion.zsh
|
||||||
#
|
#
|
||||||
# - $FZF_TMUX (default: 1)
|
# - $FZF_TMUX (default: 0)
|
||||||
# - $FZF_TMUX_HEIGHT (default: '40%')
|
# - $FZF_TMUX_HEIGHT (default: '40%')
|
||||||
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||||
@ -30,6 +30,11 @@ fi
|
|||||||
|
|
||||||
###########################################################
|
###########################################################
|
||||||
|
|
||||||
|
__fzfcmd_complete() {
|
||||||
|
[ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ] &&
|
||||||
|
echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
|
||||||
|
}
|
||||||
|
|
||||||
__fzf_generic_path_completion() {
|
__fzf_generic_path_completion() {
|
||||||
local base lbuf compgen fzf_opts suffix tail fzf dir leftover matches
|
local base lbuf compgen fzf_opts suffix tail fzf dir leftover matches
|
||||||
# (Q) flag removes a quoting level: "foo\ bar" => "foo bar"
|
# (Q) flag removes a quoting level: "foo\ bar" => "foo bar"
|
||||||
@ -39,7 +44,7 @@ __fzf_generic_path_completion() {
|
|||||||
fzf_opts=$4
|
fzf_opts=$4
|
||||||
suffix=$5
|
suffix=$5
|
||||||
tail=$6
|
tail=$6
|
||||||
[ ${FZF_TMUX:-1} -eq 1 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
|
fzf="$(__fzfcmd_complete)"
|
||||||
|
|
||||||
setopt localoptions nonomatch
|
setopt localoptions nonomatch
|
||||||
dir="$base"
|
dir="$base"
|
||||||
@ -50,7 +55,7 @@ __fzf_generic_path_completion() {
|
|||||||
[ -z "$dir" ] && dir='.'
|
[ -z "$dir" ] && dir='.'
|
||||||
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
||||||
dir=${~dir}
|
dir=${~dir}
|
||||||
matches=$(eval "$compgen $(printf %q "$dir")" | ${=fzf} ${=FZF_COMPLETION_OPTS} ${=fzf_opts} -q "$leftover" | while read item; do
|
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" ${=fzf} ${=fzf_opts} -q "$leftover" | while read item; do
|
||||||
echo -n "${(q)item}$suffix "
|
echo -n "${(q)item}$suffix "
|
||||||
done)
|
done)
|
||||||
matches=${matches% }
|
matches=${matches% }
|
||||||
@ -90,10 +95,10 @@ _fzf_complete() {
|
|||||||
post="${funcstack[2]}_post"
|
post="${funcstack[2]}_post"
|
||||||
type $post > /dev/null 2>&1 || post=cat
|
type $post > /dev/null 2>&1 || post=cat
|
||||||
|
|
||||||
[ ${FZF_TMUX:-1} -eq 1 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
|
fzf="$(__fzfcmd_complete)"
|
||||||
|
|
||||||
_fzf_feed_fifo "$fifo"
|
_fzf_feed_fifo "$fifo"
|
||||||
matches=$(cat "$fifo" | ${=fzf} ${=FZF_COMPLETION_OPTS} ${=fzf_opts} -q "${(Q)prefix}" | $post | tr '\n' ' ')
|
matches=$(cat "$fifo" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" ${=fzf} ${=fzf_opts} -q "${(Q)prefix}" | $post | tr '\n' ' ')
|
||||||
if [ -n "$matches" ]; then
|
if [ -n "$matches" ]; then
|
||||||
LBUFFER="$lbuf$matches"
|
LBUFFER="$lbuf$matches"
|
||||||
fi
|
fi
|
||||||
@ -157,8 +162,8 @@ fzf-completion() {
|
|||||||
tail=${LBUFFER:$(( ${#LBUFFER} - ${#trigger} ))}
|
tail=${LBUFFER:$(( ${#LBUFFER} - ${#trigger} ))}
|
||||||
# Kill completion (do not require trigger sequence)
|
# Kill completion (do not require trigger sequence)
|
||||||
if [ $cmd = kill -a ${LBUFFER[-1]} = ' ' ]; then
|
if [ $cmd = kill -a ${LBUFFER[-1]} = ' ' ]; then
|
||||||
[ ${FZF_TMUX:-1} -eq 1 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
|
fzf="$(__fzfcmd_complete)"
|
||||||
matches=$(ps -ef | sed 1d | ${=fzf} ${=FZF_COMPLETION_OPTS} -m | awk '{print $2}' | tr '\n' ' ')
|
matches=$(ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS --preview 'echo {}' --preview-window down:3:wrap $FZF_COMPLETION_OPTS" ${=fzf} -m | awk '{print $2}' | tr '\n' ' ')
|
||||||
if [ -n "$matches" ]; then
|
if [ -n "$matches" ]; then
|
||||||
LBUFFER="$LBUFFER$matches"
|
LBUFFER="$LBUFFER$matches"
|
||||||
fi
|
fi
|
||||||
|
@ -5,7 +5,7 @@ __fzf_select__() {
|
|||||||
-o -type f -print \
|
-o -type f -print \
|
||||||
-o -type d -print \
|
-o -type d -print \
|
||||||
-o -type l -print 2> /dev/null | cut -b3-"}"
|
-o -type l -print 2> /dev/null | cut -b3-"}"
|
||||||
eval "$cmd | fzf -m $FZF_CTRL_T_OPTS" | while read -r item; do
|
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" fzf -m "$@" | while read -r item; do
|
||||||
printf '%q ' "$item"
|
printf '%q ' "$item"
|
||||||
done
|
done
|
||||||
echo
|
echo
|
||||||
@ -13,8 +13,13 @@ __fzf_select__() {
|
|||||||
|
|
||||||
if [[ $- =~ i ]]; then
|
if [[ $- =~ i ]]; then
|
||||||
|
|
||||||
|
__fzf_use_tmux__() {
|
||||||
|
[ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ]
|
||||||
|
}
|
||||||
|
|
||||||
__fzfcmd() {
|
__fzfcmd() {
|
||||||
[ "${FZF_TMUX:-1}" != 0 ] && echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
|
__fzf_use_tmux__ &&
|
||||||
|
echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
|
||||||
}
|
}
|
||||||
|
|
||||||
__fzf_select_tmux__() {
|
__fzf_select_tmux__() {
|
||||||
@ -26,7 +31,7 @@ __fzf_select_tmux__() {
|
|||||||
height="-l $height"
|
height="-l $height"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
tmux split-window $height "cd $(printf %q "$PWD"); FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS") PATH=$(printf %q "$PATH") FZF_CTRL_T_COMMAND=$(printf %q "$FZF_CTRL_T_COMMAND") FZF_CTRL_T_OPTS=$(printf %q "$FZF_CTRL_T_OPTS") bash -c 'source \"${BASH_SOURCE[0]}\"; RESULT=\"\$(__fzf_select__)\"; tmux setb -b fzf \"\$RESULT\" \\; pasteb -b fzf -t $TMUX_PANE \\; deleteb -b fzf || tmux send-keys -t $TMUX_PANE \"\$RESULT\"'"
|
tmux split-window $height "cd $(printf %q "$PWD"); FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS") PATH=$(printf %q "$PATH") FZF_CTRL_T_COMMAND=$(printf %q "$FZF_CTRL_T_COMMAND") FZF_CTRL_T_OPTS=$(printf %q "$FZF_CTRL_T_OPTS") bash -c 'source \"${BASH_SOURCE[0]}\"; RESULT=\"\$(__fzf_select__ --no-height)\"; tmux setb -b fzf \"\$RESULT\" \\; pasteb -b fzf -t $TMUX_PANE \\; deleteb -b fzf || tmux send-keys -t $TMUX_PANE \"\$RESULT\"'"
|
||||||
}
|
}
|
||||||
|
|
||||||
fzf-file-widget() {
|
fzf-file-widget() {
|
||||||
@ -43,7 +48,7 @@ __fzf_cd__() {
|
|||||||
local cmd dir
|
local cmd dir
|
||||||
cmd="${FZF_ALT_C_COMMAND:-"command find -L . \\( -path '*/\\.*' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
cmd="${FZF_ALT_C_COMMAND:-"command find -L . \\( -path '*/\\.*' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||||
-o -type d -print 2> /dev/null | sed 1d | cut -b3-"}"
|
-o -type d -print 2> /dev/null | sed 1d | cut -b3-"}"
|
||||||
dir=$(eval "$cmd | $(__fzfcmd) +m $FZF_ALT_C_OPTS") && printf 'cd %q' "$dir"
|
dir=$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m) && printf 'cd %q' "$dir"
|
||||||
}
|
}
|
||||||
|
|
||||||
__fzf_history__() (
|
__fzf_history__() (
|
||||||
@ -51,7 +56,7 @@ __fzf_history__() (
|
|||||||
shopt -u nocaseglob nocasematch
|
shopt -u nocaseglob nocasematch
|
||||||
line=$(
|
line=$(
|
||||||
HISTTIMEFORMAT= history |
|
HISTTIMEFORMAT= history |
|
||||||
eval "$(__fzfcmd) +s --tac +m -n2..,.. --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS" |
|
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS +s --tac --no-reverse -n2..,.. --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS +m" $(__fzfcmd) |
|
||||||
command grep '^ *[0-9]') &&
|
command grep '^ *[0-9]') &&
|
||||||
if [[ $- =~ H ]]; then
|
if [[ $- =~ H ]]; then
|
||||||
sed 's/^ *\([0-9]*\)\** .*/!\1/' <<< "$line"
|
sed 's/^ *\([0-9]*\)\** .*/!\1/' <<< "$line"
|
||||||
@ -60,22 +65,15 @@ __fzf_history__() (
|
|||||||
fi
|
fi
|
||||||
)
|
)
|
||||||
|
|
||||||
__fzf_use_tmux__() {
|
|
||||||
[ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-1}" != 0 ] && [ ${LINES:-40} -gt 15 ]
|
|
||||||
}
|
|
||||||
|
|
||||||
[ $BASH_VERSINFO -gt 3 ] && __use_bind_x=1 || __use_bind_x=0
|
|
||||||
__fzf_use_tmux__ && __use_tmux=1 || __use_tmux=0
|
|
||||||
|
|
||||||
if [[ ! -o vi ]]; then
|
if [[ ! -o vi ]]; then
|
||||||
# Required to refresh the prompt after fzf
|
# Required to refresh the prompt after fzf
|
||||||
bind '"\er": redraw-current-line'
|
bind '"\er": redraw-current-line'
|
||||||
bind '"\e^": history-expand-line'
|
bind '"\e^": history-expand-line'
|
||||||
|
|
||||||
# CTRL-T - Paste the selected file path into the command line
|
# CTRL-T - Paste the selected file path into the command line
|
||||||
if [ $__use_bind_x -eq 1 ]; then
|
if [ $BASH_VERSINFO -gt 3 ]; then
|
||||||
bind -x '"\C-t": "fzf-file-widget"'
|
bind -x '"\C-t": "fzf-file-widget"'
|
||||||
elif [ $__use_tmux -eq 1 ]; then
|
elif __fzf_use_tmux__; then
|
||||||
bind '"\C-t": " \C-u \C-a\C-k`__fzf_select_tmux__`\e\C-e\C-y\C-a\C-d\C-y\ey\C-h"'
|
bind '"\C-t": " \C-u \C-a\C-k`__fzf_select_tmux__`\e\C-e\C-y\C-a\C-d\C-y\ey\C-h"'
|
||||||
else
|
else
|
||||||
bind '"\C-t": " \C-u \C-a\C-k`__fzf_select__`\e\C-e\C-y\C-a\C-y\ey\C-h\C-e\er \C-h"'
|
bind '"\C-t": " \C-u \C-a\C-k`__fzf_select__`\e\C-e\C-y\C-a\C-y\ey\C-h\C-e\er \C-h"'
|
||||||
@ -102,9 +100,9 @@ else
|
|||||||
|
|
||||||
# CTRL-T - Paste the selected file path into the command line
|
# CTRL-T - Paste the selected file path into the command line
|
||||||
# - FIXME: Selected items are attached to the end regardless of cursor position
|
# - FIXME: Selected items are attached to the end regardless of cursor position
|
||||||
if [ $__use_bind_x -eq 1 ]; then
|
if [ $BASH_VERSINFO -gt 3 ]; then
|
||||||
bind -x '"\C-t": "fzf-file-widget"'
|
bind -x '"\C-t": "fzf-file-widget"'
|
||||||
elif [ $__use_tmux -eq 1 ]; then
|
elif __fzf_use_tmux__; then
|
||||||
bind '"\C-t": "\C-x\C-a$a \C-x\C-addi`__fzf_select_tmux__`\C-x\C-e\C-x\C-a0P$xa"'
|
bind '"\C-t": "\C-x\C-a$a \C-x\C-addi`__fzf_select_tmux__`\C-x\C-e\C-x\C-a0P$xa"'
|
||||||
else
|
else
|
||||||
bind '"\C-t": "\C-x\C-a$a \C-x\C-addi`__fzf_select__`\C-x\C-e\C-x\C-a0Px$a \C-x\C-r\C-x\C-axa "'
|
bind '"\C-t": "\C-x\C-a$a \C-x\C-addi`__fzf_select__`\C-x\C-e\C-x\C-a0Px$a \C-x\C-r\C-x\C-axa "'
|
||||||
@ -120,6 +118,4 @@ else
|
|||||||
bind -m vi-command '"\ec": "ddi`__fzf_cd__`\C-x\C-e\C-x\C-r\C-m"'
|
bind -m vi-command '"\ec": "ddi`__fzf_cd__`\C-x\C-e\C-x\C-r\C-m"'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
unset -v __use_tmux __use_bind_x
|
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
@ -21,7 +21,11 @@ function fzf_key_bindings
|
|||||||
-o -type d -print \
|
-o -type d -print \
|
||||||
-o -type l -print 2> /dev/null | sed 's#^\./##'"
|
-o -type l -print 2> /dev/null | sed 's#^\./##'"
|
||||||
|
|
||||||
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)" -m $FZF_CTRL_T_OPTS" | while read -l r; set result $result $r; end
|
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
|
||||||
|
begin
|
||||||
|
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
|
||||||
|
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)" -m" | while read -l r; set result $result $r; end
|
||||||
|
end
|
||||||
if [ -z "$result" ]
|
if [ -z "$result" ]
|
||||||
commandline -f repaint
|
commandline -f repaint
|
||||||
return
|
return
|
||||||
@ -39,8 +43,12 @@ function fzf_key_bindings
|
|||||||
end
|
end
|
||||||
|
|
||||||
function fzf-history-widget -d "Show command history"
|
function fzf-history-widget -d "Show command history"
|
||||||
history | eval (__fzfcmd) +s +m --tiebreak=index $FZF_CTRL_R_OPTS -q '(commandline)' | read -l result
|
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
|
||||||
and commandline -- $result
|
begin
|
||||||
|
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS +s --no-reverse --tiebreak=index $FZF_CTRL_R_OPTS +m"
|
||||||
|
history | eval (__fzfcmd) -q '(commandline)' | read -l result
|
||||||
|
and commandline -- $result
|
||||||
|
end
|
||||||
commandline -f repaint
|
commandline -f repaint
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -48,19 +56,20 @@ function fzf_key_bindings
|
|||||||
set -q FZF_ALT_C_COMMAND; or set -l FZF_ALT_C_COMMAND "
|
set -q FZF_ALT_C_COMMAND; or set -l FZF_ALT_C_COMMAND "
|
||||||
command find -L . \\( -path '*/\\.*' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
command find -L . \\( -path '*/\\.*' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
||||||
-o -type d -print 2> /dev/null | sed 1d | cut -b3-"
|
-o -type d -print 2> /dev/null | sed 1d | cut -b3-"
|
||||||
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)" +m $FZF_ALT_C_OPTS" | read -l result
|
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
|
||||||
[ "$result" ]; and cd $result
|
begin
|
||||||
|
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
|
||||||
|
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)" +m" | read -l result
|
||||||
|
[ "$result" ]; and cd $result
|
||||||
|
end
|
||||||
commandline -f repaint
|
commandline -f repaint
|
||||||
end
|
end
|
||||||
|
|
||||||
function __fzfcmd
|
function __fzfcmd
|
||||||
set -q FZF_TMUX; or set FZF_TMUX 1
|
set -q FZF_TMUX; or set FZF_TMUX 0
|
||||||
|
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
|
||||||
if [ $FZF_TMUX -eq 1 ]
|
if [ $FZF_TMUX -eq 1 ]
|
||||||
if set -q FZF_TMUX_HEIGHT
|
echo "fzf-tmux -d$FZF_TMUX_HEIGHT"
|
||||||
echo "fzf-tmux -d$FZF_TMUX_HEIGHT"
|
|
||||||
else
|
|
||||||
echo "fzf-tmux -d40%"
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
echo "fzf"
|
echo "fzf"
|
||||||
end
|
end
|
||||||
|
@ -9,7 +9,7 @@ __fsel() {
|
|||||||
-o -type d -print \
|
-o -type d -print \
|
||||||
-o -type l -print 2> /dev/null | cut -b3-"}"
|
-o -type l -print 2> /dev/null | cut -b3-"}"
|
||||||
setopt localoptions pipefail 2> /dev/null
|
setopt localoptions pipefail 2> /dev/null
|
||||||
eval "$cmd | $(__fzfcmd) -m $FZF_CTRL_T_OPTS" | while read item; do
|
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read item; do
|
||||||
echo -n "${(q)item} "
|
echo -n "${(q)item} "
|
||||||
done
|
done
|
||||||
local ret=$?
|
local ret=$?
|
||||||
@ -17,8 +17,13 @@ __fsel() {
|
|||||||
return $ret
|
return $ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
__fzf_use_tmux__() {
|
||||||
|
[ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ]
|
||||||
|
}
|
||||||
|
|
||||||
__fzfcmd() {
|
__fzfcmd() {
|
||||||
[ ${FZF_TMUX:-1} -eq 1 ] && echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
|
__fzf_use_tmux__ &&
|
||||||
|
echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
|
||||||
}
|
}
|
||||||
|
|
||||||
fzf-file-widget() {
|
fzf-file-widget() {
|
||||||
@ -36,7 +41,7 @@ fzf-cd-widget() {
|
|||||||
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . \\( -path '*/\\.*' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . \\( -path '*/\\.*' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||||
-o -type d -print 2> /dev/null | sed 1d | cut -b3-"}"
|
-o -type d -print 2> /dev/null | sed 1d | cut -b3-"}"
|
||||||
setopt localoptions pipefail 2> /dev/null
|
setopt localoptions pipefail 2> /dev/null
|
||||||
cd "${$(eval "$cmd | $(__fzfcmd) +m $FZF_ALT_C_OPTS"):-.}"
|
cd "${$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m):-.}"
|
||||||
local ret=$?
|
local ret=$?
|
||||||
zle reset-prompt
|
zle reset-prompt
|
||||||
typeset -f zle-line-init >/dev/null && zle zle-line-init
|
typeset -f zle-line-init >/dev/null && zle zle-line-init
|
||||||
@ -49,7 +54,8 @@ bindkey '\ec' fzf-cd-widget
|
|||||||
fzf-history-widget() {
|
fzf-history-widget() {
|
||||||
local selected num
|
local selected num
|
||||||
setopt localoptions noglobsubst pipefail 2> /dev/null
|
setopt localoptions noglobsubst pipefail 2> /dev/null
|
||||||
selected=( $(fc -l 1 | eval "$(__fzfcmd) +s --tac +m -n2..,.. --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS -q ${(q)LBUFFER}") )
|
selected=( $(fc -l 1 |
|
||||||
|
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS +s --tac --no-reverse -n2..,.. --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS +m --query=${(q)LBUFFER}" $(__fzfcmd)) )
|
||||||
local ret=$?
|
local ret=$?
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
num=$selected[1]
|
num=$selected[1]
|
||||||
|
@ -234,9 +234,24 @@ func bonusAt(input util.Chars, idx int) int16 {
|
|||||||
return bonusFor(charClassOf(input.Get(idx-1)), charClassOf(input.Get(idx)))
|
return bonusFor(charClassOf(input.Get(idx-1)), charClassOf(input.Get(idx)))
|
||||||
}
|
}
|
||||||
|
|
||||||
type Algo func(caseSensitive bool, forward bool, input util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int)
|
func normalizeRune(r rune) rune {
|
||||||
|
if r < 0x00C0 || r > 0x2184 {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
func FuzzyMatchV2(caseSensitive bool, forward bool, input util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
n := normalized[r]
|
||||||
|
if n > 0 {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Algo functions make two assumptions
|
||||||
|
// 1. "pattern" is given in lowercase if "caseSensitive" is false
|
||||||
|
// 2. "pattern" is already normalized if "normalize" is true
|
||||||
|
type Algo func(caseSensitive bool, normalize bool, forward bool, input util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int)
|
||||||
|
|
||||||
|
func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
// Assume that pattern is given in lowercase if case-insensitive.
|
// Assume that pattern is given in lowercase if case-insensitive.
|
||||||
// First check if there's a match and calculate bonus for each position.
|
// First check if there's a match and calculate bonus for each position.
|
||||||
// If the input string is too long, consider finding the matching chars in
|
// If the input string is too long, consider finding the matching chars in
|
||||||
@ -247,13 +262,13 @@ func FuzzyMatchV2(caseSensitive bool, forward bool, input util.Chars, pattern []
|
|||||||
case 0:
|
case 0:
|
||||||
return Result{0, 0, 0}, posArray(withPos, M)
|
return Result{0, 0, 0}, posArray(withPos, M)
|
||||||
case 1:
|
case 1:
|
||||||
return ExactMatchNaive(caseSensitive, forward, input, pattern[0:1], withPos, slab)
|
return ExactMatchNaive(caseSensitive, normalize, forward, input, pattern[0:1], withPos, slab)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since O(nm) algorithm can be prohibitively expensive for large input,
|
// Since O(nm) algorithm can be prohibitively expensive for large input,
|
||||||
// we fall back to the greedy algorithm.
|
// we fall back to the greedy algorithm.
|
||||||
if slab != nil && N*M > cap(slab.I16) {
|
if slab != nil && N*M > cap(slab.I16) {
|
||||||
return FuzzyMatchV1(caseSensitive, forward, input, pattern, withPos, slab)
|
return FuzzyMatchV1(caseSensitive, normalize, forward, input, pattern, withPos, slab)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reuse pre-allocated integer slice to avoid unnecessary sweeping of garbages
|
// Reuse pre-allocated integer slice to avoid unnecessary sweeping of garbages
|
||||||
@ -285,6 +300,10 @@ func FuzzyMatchV2(caseSensitive bool, forward bool, input util.Chars, pattern []
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if normalize {
|
||||||
|
char = normalizeRune(char)
|
||||||
|
}
|
||||||
|
|
||||||
T[idx] = char
|
T[idx] = char
|
||||||
B[idx] = bonusFor(prevClass, class)
|
B[idx] = bonusFor(prevClass, class)
|
||||||
prevClass = class
|
prevClass = class
|
||||||
@ -432,7 +451,7 @@ func FuzzyMatchV2(caseSensitive bool, forward bool, input util.Chars, pattern []
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Implement the same sorting criteria as V2
|
// Implement the same sorting criteria as V2
|
||||||
func calculateScore(caseSensitive bool, text util.Chars, pattern []rune, sidx int, eidx int, withPos bool) (int, *[]int) {
|
func calculateScore(caseSensitive bool, normalize bool, text util.Chars, pattern []rune, sidx int, eidx int, withPos bool) (int, *[]int) {
|
||||||
pidx, score, inGap, consecutive, firstBonus := 0, 0, false, 0, int16(0)
|
pidx, score, inGap, consecutive, firstBonus := 0, 0, false, 0, int16(0)
|
||||||
pos := posArray(withPos, len(pattern))
|
pos := posArray(withPos, len(pattern))
|
||||||
prevClass := charNonWord
|
prevClass := charNonWord
|
||||||
@ -449,6 +468,10 @@ func calculateScore(caseSensitive bool, text util.Chars, pattern []rune, sidx in
|
|||||||
char = unicode.To(unicode.LowerCase, char)
|
char = unicode.To(unicode.LowerCase, char)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// pattern is already normalized
|
||||||
|
if normalize {
|
||||||
|
char = normalizeRune(char)
|
||||||
|
}
|
||||||
if char == pattern[pidx] {
|
if char == pattern[pidx] {
|
||||||
if withPos {
|
if withPos {
|
||||||
*pos = append(*pos, idx)
|
*pos = append(*pos, idx)
|
||||||
@ -488,7 +511,7 @@ func calculateScore(caseSensitive bool, text util.Chars, pattern []rune, sidx in
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FuzzyMatchV1 performs fuzzy-match
|
// FuzzyMatchV1 performs fuzzy-match
|
||||||
func FuzzyMatchV1(caseSensitive bool, forward bool, text util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
func FuzzyMatchV1(caseSensitive bool, normalize bool, forward bool, text util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
if len(pattern) == 0 {
|
if len(pattern) == 0 {
|
||||||
return Result{0, 0, 0}, nil
|
return Result{0, 0, 0}, nil
|
||||||
}
|
}
|
||||||
@ -514,6 +537,9 @@ func FuzzyMatchV1(caseSensitive bool, forward bool, text util.Chars, pattern []r
|
|||||||
char = unicode.To(unicode.LowerCase, char)
|
char = unicode.To(unicode.LowerCase, char)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if normalize {
|
||||||
|
char = normalizeRune(char)
|
||||||
|
}
|
||||||
pchar := pattern[indexAt(pidx, lenPattern, forward)]
|
pchar := pattern[indexAt(pidx, lenPattern, forward)]
|
||||||
if char == pchar {
|
if char == pchar {
|
||||||
if sidx < 0 {
|
if sidx < 0 {
|
||||||
@ -553,7 +579,7 @@ func FuzzyMatchV1(caseSensitive bool, forward bool, text util.Chars, pattern []r
|
|||||||
sidx, eidx = lenRunes-eidx, lenRunes-sidx
|
sidx, eidx = lenRunes-eidx, lenRunes-sidx
|
||||||
}
|
}
|
||||||
|
|
||||||
score, pos := calculateScore(caseSensitive, text, pattern, sidx, eidx, withPos)
|
score, pos := calculateScore(caseSensitive, normalize, text, pattern, sidx, eidx, withPos)
|
||||||
return Result{sidx, eidx, score}, pos
|
return Result{sidx, eidx, score}, pos
|
||||||
}
|
}
|
||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
@ -568,7 +594,7 @@ func FuzzyMatchV1(caseSensitive bool, forward bool, text util.Chars, pattern []r
|
|||||||
// bonus point, instead of stopping immediately after finding the first match.
|
// bonus point, instead of stopping immediately after finding the first match.
|
||||||
// The solution is much cheaper since there is only one possible alignment of
|
// The solution is much cheaper since there is only one possible alignment of
|
||||||
// the pattern.
|
// the pattern.
|
||||||
func ExactMatchNaive(caseSensitive bool, forward bool, text util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
if len(pattern) == 0 {
|
if len(pattern) == 0 {
|
||||||
return Result{0, 0, 0}, nil
|
return Result{0, 0, 0}, nil
|
||||||
}
|
}
|
||||||
@ -593,6 +619,9 @@ func ExactMatchNaive(caseSensitive bool, forward bool, text util.Chars, pattern
|
|||||||
char = unicode.To(unicode.LowerCase, char)
|
char = unicode.To(unicode.LowerCase, char)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if normalize {
|
||||||
|
char = normalizeRune(char)
|
||||||
|
}
|
||||||
pidx_ := indexAt(pidx, lenPattern, forward)
|
pidx_ := indexAt(pidx, lenPattern, forward)
|
||||||
pchar := pattern[pidx_]
|
pchar := pattern[pidx_]
|
||||||
if pchar == char {
|
if pchar == char {
|
||||||
@ -624,14 +653,14 @@ func ExactMatchNaive(caseSensitive bool, forward bool, text util.Chars, pattern
|
|||||||
sidx = lenRunes - (bestPos + 1)
|
sidx = lenRunes - (bestPos + 1)
|
||||||
eidx = lenRunes - (bestPos - lenPattern + 1)
|
eidx = lenRunes - (bestPos - lenPattern + 1)
|
||||||
}
|
}
|
||||||
score, _ := calculateScore(caseSensitive, text, pattern, sidx, eidx, false)
|
score, _ := calculateScore(caseSensitive, normalize, text, pattern, sidx, eidx, false)
|
||||||
return Result{sidx, eidx, score}, nil
|
return Result{sidx, eidx, score}, nil
|
||||||
}
|
}
|
||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrefixMatch performs prefix-match
|
// PrefixMatch performs prefix-match
|
||||||
func PrefixMatch(caseSensitive bool, forward bool, text util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
func PrefixMatch(caseSensitive bool, normalize bool, forward bool, text util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
if len(pattern) == 0 {
|
if len(pattern) == 0 {
|
||||||
return Result{0, 0, 0}, nil
|
return Result{0, 0, 0}, nil
|
||||||
}
|
}
|
||||||
@ -645,17 +674,20 @@ func PrefixMatch(caseSensitive bool, forward bool, text util.Chars, pattern []ru
|
|||||||
if !caseSensitive {
|
if !caseSensitive {
|
||||||
char = unicode.ToLower(char)
|
char = unicode.ToLower(char)
|
||||||
}
|
}
|
||||||
|
if normalize {
|
||||||
|
char = normalizeRune(char)
|
||||||
|
}
|
||||||
if char != r {
|
if char != r {
|
||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lenPattern := len(pattern)
|
lenPattern := len(pattern)
|
||||||
score, _ := calculateScore(caseSensitive, text, pattern, 0, lenPattern, false)
|
score, _ := calculateScore(caseSensitive, normalize, text, pattern, 0, lenPattern, false)
|
||||||
return Result{0, lenPattern, score}, nil
|
return Result{0, lenPattern, score}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SuffixMatch performs suffix-match
|
// SuffixMatch performs suffix-match
|
||||||
func SuffixMatch(caseSensitive bool, forward bool, text util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
func SuffixMatch(caseSensitive bool, normalize bool, forward bool, text util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
lenRunes := text.Length()
|
lenRunes := text.Length()
|
||||||
trimmedLen := lenRunes - text.TrailingWhitespaces()
|
trimmedLen := lenRunes - text.TrailingWhitespaces()
|
||||||
if len(pattern) == 0 {
|
if len(pattern) == 0 {
|
||||||
@ -671,6 +703,9 @@ func SuffixMatch(caseSensitive bool, forward bool, text util.Chars, pattern []ru
|
|||||||
if !caseSensitive {
|
if !caseSensitive {
|
||||||
char = unicode.ToLower(char)
|
char = unicode.ToLower(char)
|
||||||
}
|
}
|
||||||
|
if normalize {
|
||||||
|
char = normalizeRune(char)
|
||||||
|
}
|
||||||
if char != r {
|
if char != r {
|
||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
}
|
}
|
||||||
@ -678,21 +713,37 @@ func SuffixMatch(caseSensitive bool, forward bool, text util.Chars, pattern []ru
|
|||||||
lenPattern := len(pattern)
|
lenPattern := len(pattern)
|
||||||
sidx := trimmedLen - lenPattern
|
sidx := trimmedLen - lenPattern
|
||||||
eidx := trimmedLen
|
eidx := trimmedLen
|
||||||
score, _ := calculateScore(caseSensitive, text, pattern, sidx, eidx, false)
|
score, _ := calculateScore(caseSensitive, normalize, text, pattern, sidx, eidx, false)
|
||||||
return Result{sidx, eidx, score}, nil
|
return Result{sidx, eidx, score}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EqualMatch performs equal-match
|
// EqualMatch performs equal-match
|
||||||
func EqualMatch(caseSensitive bool, forward bool, text util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
func EqualMatch(caseSensitive bool, normalize bool, forward bool, text util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
lenPattern := len(pattern)
|
lenPattern := len(pattern)
|
||||||
if text.Length() != lenPattern {
|
if text.Length() != lenPattern {
|
||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
}
|
}
|
||||||
runesStr := text.ToString()
|
match := true
|
||||||
if !caseSensitive {
|
if normalize {
|
||||||
runesStr = strings.ToLower(runesStr)
|
runes := text.ToRunes()
|
||||||
|
for idx, pchar := range pattern {
|
||||||
|
char := runes[idx]
|
||||||
|
if !caseSensitive {
|
||||||
|
char = unicode.To(unicode.LowerCase, char)
|
||||||
|
}
|
||||||
|
if normalizeRune(pchar) != normalizeRune(char) {
|
||||||
|
match = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
runesStr := text.ToString()
|
||||||
|
if !caseSensitive {
|
||||||
|
runesStr = strings.ToLower(runesStr)
|
||||||
|
}
|
||||||
|
match = runesStr == string(pattern)
|
||||||
}
|
}
|
||||||
if runesStr == string(pattern) {
|
if match {
|
||||||
return Result{0, lenPattern, (scoreMatch+bonusBoundary)*lenPattern +
|
return Result{0, lenPattern, (scoreMatch+bonusBoundary)*lenPattern +
|
||||||
(bonusFirstCharMultiplier-1)*bonusBoundary}, nil
|
(bonusFirstCharMultiplier-1)*bonusBoundary}, nil
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func assertMatch(t *testing.T, fun Algo, caseSensitive, forward bool, input, pattern string, sidx int, eidx int, score int) {
|
func assertMatch(t *testing.T, fun Algo, caseSensitive, forward bool, input, pattern string, sidx int, eidx int, score int) {
|
||||||
|
assertMatch2(t, fun, caseSensitive, false, forward, input, pattern, sidx, eidx, score)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertMatch2(t *testing.T, fun Algo, caseSensitive, normalize, forward bool, input, pattern string, sidx int, eidx int, score int) {
|
||||||
if !caseSensitive {
|
if !caseSensitive {
|
||||||
pattern = strings.ToLower(pattern)
|
pattern = strings.ToLower(pattern)
|
||||||
}
|
}
|
||||||
res, pos := fun(caseSensitive, forward, util.RunesToChars([]rune(input)), []rune(pattern), true, nil)
|
res, pos := fun(caseSensitive, normalize, forward, util.RunesToChars([]rune(input)), []rune(pattern), true, nil)
|
||||||
var start, end int
|
var start, end int
|
||||||
if pos == nil || len(*pos) == 0 {
|
if pos == nil || len(*pos) == 0 {
|
||||||
start = res.Start
|
start = res.Start
|
||||||
@ -156,6 +160,21 @@ func TestEmptyPattern(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNormalize(t *testing.T) {
|
||||||
|
caseSensitive := false
|
||||||
|
normalize := true
|
||||||
|
forward := true
|
||||||
|
test := func(input, pattern string, sidx, eidx, score int, funs ...Algo) {
|
||||||
|
for _, fun := range funs {
|
||||||
|
assertMatch2(t, fun, caseSensitive, normalize, forward,
|
||||||
|
input, pattern, sidx, eidx, score)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
test("Só Danço Samba", "So", 0, 2, 56, FuzzyMatchV1, FuzzyMatchV2, PrefixMatch, ExactMatchNaive)
|
||||||
|
test("Só Danço Samba", "sodc", 0, 7, 89, FuzzyMatchV1, FuzzyMatchV2)
|
||||||
|
test("Danço", "danco", 0, 5, 128, FuzzyMatchV1, FuzzyMatchV2, PrefixMatch, SuffixMatch, ExactMatchNaive, EqualMatch)
|
||||||
|
}
|
||||||
|
|
||||||
func TestLongString(t *testing.T) {
|
func TestLongString(t *testing.T) {
|
||||||
bytes := make([]byte, math.MaxUint16*2)
|
bytes := make([]byte, math.MaxUint16*2)
|
||||||
for i := range bytes {
|
for i := range bytes {
|
||||||
|
424
src/algo/normalize.go
Normal file
424
src/algo/normalize.go
Normal file
@ -0,0 +1,424 @@
|
|||||||
|
// Normalization of latin script letters
|
||||||
|
// Reference: http://www.unicode.org/Public/UCD/latest/ucd/Index.txt
|
||||||
|
|
||||||
|
package algo
|
||||||
|
|
||||||
|
var normalized map[rune]rune = map[rune]rune{
|
||||||
|
0x00E1: 'a', // WITH ACUTE, LATIN SMALL LETTER
|
||||||
|
0x0103: 'a', // WITH BREVE, LATIN SMALL LETTER
|
||||||
|
0x01CE: 'a', // WITH CARON, LATIN SMALL LETTER
|
||||||
|
0x00E2: 'a', // WITH CIRCUMFLEX, LATIN SMALL LETTER
|
||||||
|
0x00E4: 'a', // WITH DIAERESIS, LATIN SMALL LETTER
|
||||||
|
0x0227: 'a', // WITH DOT ABOVE, LATIN SMALL LETTER
|
||||||
|
0x1EA1: 'a', // WITH DOT BELOW, LATIN SMALL LETTER
|
||||||
|
0x0201: 'a', // WITH DOUBLE GRAVE, LATIN SMALL LETTER
|
||||||
|
0x00E0: 'a', // WITH GRAVE, LATIN SMALL LETTER
|
||||||
|
0x1EA3: 'a', // WITH HOOK ABOVE, LATIN SMALL LETTER
|
||||||
|
0x0203: 'a', // WITH INVERTED BREVE, LATIN SMALL LETTER
|
||||||
|
0x0101: 'a', // WITH MACRON, LATIN SMALL LETTER
|
||||||
|
0x0105: 'a', // WITH OGONEK, LATIN SMALL LETTER
|
||||||
|
0x1E9A: 'a', // WITH RIGHT HALF RING, LATIN SMALL LETTER
|
||||||
|
0x00E5: 'a', // WITH RING ABOVE, LATIN SMALL LETTER
|
||||||
|
0x1E01: 'a', // WITH RING BELOW, LATIN SMALL LETTER
|
||||||
|
0x00E3: 'a', // WITH TILDE, LATIN SMALL LETTER
|
||||||
|
0x0363: 'a', // , COMBINING LATIN SMALL LETTER
|
||||||
|
0x0250: 'a', // , LATIN SMALL LETTER TURNED
|
||||||
|
0x1E03: 'b', // WITH DOT ABOVE, LATIN SMALL LETTER
|
||||||
|
0x1E05: 'b', // WITH DOT BELOW, LATIN SMALL LETTER
|
||||||
|
0x0253: 'b', // WITH HOOK, LATIN SMALL LETTER
|
||||||
|
0x1E07: 'b', // WITH LINE BELOW, LATIN SMALL LETTER
|
||||||
|
0x0180: 'b', // WITH STROKE, LATIN SMALL LETTER
|
||||||
|
0x0183: 'b', // WITH TOPBAR, LATIN SMALL LETTER
|
||||||
|
0x0107: 'c', // WITH ACUTE, LATIN SMALL LETTER
|
||||||
|
0x010D: 'c', // WITH CARON, LATIN SMALL LETTER
|
||||||
|
0x00E7: 'c', // WITH CEDILLA, LATIN SMALL LETTER
|
||||||
|
0x0109: 'c', // WITH CIRCUMFLEX, LATIN SMALL LETTER
|
||||||
|
0x0255: 'c', // WITH CURL, LATIN SMALL LETTER
|
||||||
|
0x010B: 'c', // WITH DOT ABOVE, LATIN SMALL LETTER
|
||||||
|
0x0188: 'c', // WITH HOOK, LATIN SMALL LETTER
|
||||||
|
0x023C: 'c', // WITH STROKE, LATIN SMALL LETTER
|
||||||
|
0x0368: 'c', // , COMBINING LATIN SMALL LETTER
|
||||||
|
0x0297: 'c', // , LATIN LETTER STRETCHED
|
||||||
|
0x2184: 'c', // , LATIN SMALL LETTER REVERSED
|
||||||
|
0x010F: 'd', // WITH CARON, LATIN SMALL LETTER
|
||||||
|
0x1E11: 'd', // WITH CEDILLA, LATIN SMALL LETTER
|
||||||
|
0x1E13: 'd', // WITH CIRCUMFLEX BELOW, LATIN SMALL LETTER
|
||||||
|
0x0221: 'd', // WITH CURL, LATIN SMALL LETTER
|
||||||
|
0x1E0B: 'd', // WITH DOT ABOVE, LATIN SMALL LETTER
|
||||||
|
0x1E0D: 'd', // WITH DOT BELOW, LATIN SMALL LETTER
|
||||||
|
0x0257: 'd', // WITH HOOK, LATIN SMALL LETTER
|
||||||
|
0x1E0F: 'd', // WITH LINE BELOW, LATIN SMALL LETTER
|
||||||
|
0x0111: 'd', // WITH STROKE, LATIN SMALL LETTER
|
||||||
|
0x0256: 'd', // WITH TAIL, LATIN SMALL LETTER
|
||||||
|
0x018C: 'd', // WITH TOPBAR, LATIN SMALL LETTER
|
||||||
|
0x0369: 'd', // , COMBINING LATIN SMALL LETTER
|
||||||
|
0x00E9: 'e', // WITH ACUTE, LATIN SMALL LETTER
|
||||||
|
0x0115: 'e', // WITH BREVE, LATIN SMALL LETTER
|
||||||
|
0x011B: 'e', // WITH CARON, LATIN SMALL LETTER
|
||||||
|
0x0229: 'e', // WITH CEDILLA, LATIN SMALL LETTER
|
||||||
|
0x1E19: 'e', // WITH CIRCUMFLEX BELOW, LATIN SMALL LETTER
|
||||||
|
0x00EA: 'e', // WITH CIRCUMFLEX, LATIN SMALL LETTER
|
||||||
|
0x00EB: 'e', // WITH DIAERESIS, LATIN SMALL LETTER
|
||||||
|
0x0117: 'e', // WITH DOT ABOVE, LATIN SMALL LETTER
|
||||||
|
0x1EB9: 'e', // WITH DOT BELOW, LATIN SMALL LETTER
|
||||||
|
0x0205: 'e', // WITH DOUBLE GRAVE, LATIN SMALL LETTER
|
||||||
|
0x00E8: 'e', // WITH GRAVE, LATIN SMALL LETTER
|
||||||
|
0x1EBB: 'e', // WITH HOOK ABOVE, LATIN SMALL LETTER
|
||||||
|
0x025D: 'e', // WITH HOOK, LATIN SMALL LETTER REVERSED OPEN
|
||||||
|
0x0207: 'e', // WITH INVERTED BREVE, LATIN SMALL LETTER
|
||||||
|
0x0113: 'e', // WITH MACRON, LATIN SMALL LETTER
|
||||||
|
0x0119: 'e', // WITH OGONEK, LATIN SMALL LETTER
|
||||||
|
0x0247: 'e', // WITH STROKE, LATIN SMALL LETTER
|
||||||
|
0x1E1B: 'e', // WITH TILDE BELOW, LATIN SMALL LETTER
|
||||||
|
0x1EBD: 'e', // WITH TILDE, LATIN SMALL LETTER
|
||||||
|
0x0364: 'e', // , COMBINING LATIN SMALL LETTER
|
||||||
|
0x029A: 'e', // , LATIN SMALL LETTER CLOSED OPEN
|
||||||
|
0x025E: 'e', // , LATIN SMALL LETTER CLOSED REVERSED OPEN
|
||||||
|
0x025B: 'e', // , LATIN SMALL LETTER OPEN
|
||||||
|
0x0258: 'e', // , LATIN SMALL LETTER REVERSED
|
||||||
|
0x025C: 'e', // , LATIN SMALL LETTER REVERSED OPEN
|
||||||
|
0x01DD: 'e', // , LATIN SMALL LETTER TURNED
|
||||||
|
0x1D08: 'e', // , LATIN SMALL LETTER TURNED OPEN
|
||||||
|
0x1E1F: 'f', // WITH DOT ABOVE, LATIN SMALL LETTER
|
||||||
|
0x0192: 'f', // WITH HOOK, LATIN SMALL LETTER
|
||||||
|
0x01F5: 'g', // WITH ACUTE, LATIN SMALL LETTER
|
||||||
|
0x011F: 'g', // WITH BREVE, LATIN SMALL LETTER
|
||||||
|
0x01E7: 'g', // WITH CARON, LATIN SMALL LETTER
|
||||||
|
0x0123: 'g', // WITH CEDILLA, LATIN SMALL LETTER
|
||||||
|
0x011D: 'g', // WITH CIRCUMFLEX, LATIN SMALL LETTER
|
||||||
|
0x0121: 'g', // WITH DOT ABOVE, LATIN SMALL LETTER
|
||||||
|
0x0260: 'g', // WITH HOOK, LATIN SMALL LETTER
|
||||||
|
0x1E21: 'g', // WITH MACRON, LATIN SMALL LETTER
|
||||||
|
0x01E5: 'g', // WITH STROKE, LATIN SMALL LETTER
|
||||||
|
0x0261: 'g', // , LATIN SMALL LETTER SCRIPT
|
||||||
|
0x1E2B: 'h', // WITH BREVE BELOW, LATIN SMALL LETTER
|
||||||
|
0x021F: 'h', // WITH CARON, LATIN SMALL LETTER
|
||||||
|
0x1E29: 'h', // WITH CEDILLA, LATIN SMALL LETTER
|
||||||
|
0x0125: 'h', // WITH CIRCUMFLEX, LATIN SMALL LETTER
|
||||||
|
0x1E27: 'h', // WITH DIAERESIS, LATIN SMALL LETTER
|
||||||
|
0x1E23: 'h', // WITH DOT ABOVE, LATIN SMALL LETTER
|
||||||
|
0x1E25: 'h', // WITH DOT BELOW, LATIN SMALL LETTER
|
||||||
|
0x02AE: 'h', // WITH FISHHOOK, LATIN SMALL LETTER TURNED
|
||||||
|
0x0266: 'h', // WITH HOOK, LATIN SMALL LETTER
|
||||||
|
0x1E96: 'h', // WITH LINE BELOW, LATIN SMALL LETTER
|
||||||
|
0x0127: 'h', // WITH STROKE, LATIN SMALL LETTER
|
||||||
|
0x036A: 'h', // , COMBINING LATIN SMALL LETTER
|
||||||
|
0x0265: 'h', // , LATIN SMALL LETTER TURNED
|
||||||
|
0x2095: 'h', // , LATIN SUBSCRIPT SMALL LETTER
|
||||||
|
0x00ED: 'i', // WITH ACUTE, LATIN SMALL LETTER
|
||||||
|
0x012D: 'i', // WITH BREVE, LATIN SMALL LETTER
|
||||||
|
0x01D0: 'i', // WITH CARON, LATIN SMALL LETTER
|
||||||
|
0x00EE: 'i', // WITH CIRCUMFLEX, LATIN SMALL LETTER
|
||||||
|
0x00EF: 'i', // WITH DIAERESIS, LATIN SMALL LETTER
|
||||||
|
0x1ECB: 'i', // WITH DOT BELOW, LATIN SMALL LETTER
|
||||||
|
0x0209: 'i', // WITH DOUBLE GRAVE, LATIN SMALL LETTER
|
||||||
|
0x00EC: 'i', // WITH GRAVE, LATIN SMALL LETTER
|
||||||
|
0x1EC9: 'i', // WITH HOOK ABOVE, LATIN SMALL LETTER
|
||||||
|
0x020B: 'i', // WITH INVERTED BREVE, LATIN SMALL LETTER
|
||||||
|
0x012B: 'i', // WITH MACRON, LATIN SMALL LETTER
|
||||||
|
0x012F: 'i', // WITH OGONEK, LATIN SMALL LETTER
|
||||||
|
0x0268: 'i', // WITH STROKE, LATIN SMALL LETTER
|
||||||
|
0x1E2D: 'i', // WITH TILDE BELOW, LATIN SMALL LETTER
|
||||||
|
0x0129: 'i', // WITH TILDE, LATIN SMALL LETTER
|
||||||
|
0x0365: 'i', // , COMBINING LATIN SMALL LETTER
|
||||||
|
0x0131: 'i', // , LATIN SMALL LETTER DOTLESS
|
||||||
|
0x1D09: 'i', // , LATIN SMALL LETTER TURNED
|
||||||
|
0x1D62: 'i', // , LATIN SUBSCRIPT SMALL LETTER
|
||||||
|
0x2071: 'i', // , SUPERSCRIPT LATIN SMALL LETTER
|
||||||
|
0x01F0: 'j', // WITH CARON, LATIN SMALL LETTER
|
||||||
|
0x0135: 'j', // WITH CIRCUMFLEX, LATIN SMALL LETTER
|
||||||
|
0x029D: 'j', // WITH CROSSED-TAIL, LATIN SMALL LETTER
|
||||||
|
0x0249: 'j', // WITH STROKE, LATIN SMALL LETTER
|
||||||
|
0x025F: 'j', // WITH STROKE, LATIN SMALL LETTER DOTLESS
|
||||||
|
0x0237: 'j', // , LATIN SMALL LETTER DOTLESS
|
||||||
|
0x1E31: 'k', // WITH ACUTE, LATIN SMALL LETTER
|
||||||
|
0x01E9: 'k', // WITH CARON, LATIN SMALL LETTER
|
||||||
|
0x0137: 'k', // WITH CEDILLA, LATIN SMALL LETTER
|
||||||
|
0x1E33: 'k', // WITH DOT BELOW, LATIN SMALL LETTER
|
||||||
|
0x0199: 'k', // WITH HOOK, LATIN SMALL LETTER
|
||||||
|
0x1E35: 'k', // WITH LINE BELOW, LATIN SMALL LETTER
|
||||||
|
0x029E: 'k', // , LATIN SMALL LETTER TURNED
|
||||||
|
0x2096: 'k', // , LATIN SUBSCRIPT SMALL LETTER
|
||||||
|
0x013A: 'l', // WITH ACUTE, LATIN SMALL LETTER
|
||||||
|
0x019A: 'l', // WITH BAR, LATIN SMALL LETTER
|
||||||
|
0x026C: 'l', // WITH BELT, LATIN SMALL LETTER
|
||||||
|
0x013E: 'l', // WITH CARON, LATIN SMALL LETTER
|
||||||
|
0x013C: 'l', // WITH CEDILLA, LATIN SMALL LETTER
|
||||||
|
0x1E3D: 'l', // WITH CIRCUMFLEX BELOW, LATIN SMALL LETTER
|
||||||
|
0x0234: 'l', // WITH CURL, LATIN SMALL LETTER
|
||||||
|
0x1E37: 'l', // WITH DOT BELOW, LATIN SMALL LETTER
|
||||||
|
0x1E3B: 'l', // WITH LINE BELOW, LATIN SMALL LETTER
|
||||||
|
0x0140: 'l', // WITH MIDDLE DOT, LATIN SMALL LETTER
|
||||||
|
0x026B: 'l', // WITH MIDDLE TILDE, LATIN SMALL LETTER
|
||||||
|
0x026D: 'l', // WITH RETROFLEX HOOK, LATIN SMALL LETTER
|
||||||
|
0x0142: 'l', // WITH STROKE, LATIN SMALL LETTER
|
||||||
|
0x2097: 'l', // , LATIN SUBSCRIPT SMALL LETTER
|
||||||
|
0x1E3F: 'm', // WITH ACUTE, LATIN SMALL LETTER
|
||||||
|
0x1E41: 'm', // WITH DOT ABOVE, LATIN SMALL LETTER
|
||||||
|
0x1E43: 'm', // WITH DOT BELOW, LATIN SMALL LETTER
|
||||||
|
0x0271: 'm', // WITH HOOK, LATIN SMALL LETTER
|
||||||
|
0x0270: 'm', // WITH LONG LEG, LATIN SMALL LETTER TURNED
|
||||||
|
0x036B: 'm', // , COMBINING LATIN SMALL LETTER
|
||||||
|
0x1D1F: 'm', // , LATIN SMALL LETTER SIDEWAYS TURNED
|
||||||
|
0x026F: 'm', // , LATIN SMALL LETTER TURNED
|
||||||
|
0x2098: 'm', // , LATIN SUBSCRIPT SMALL LETTER
|
||||||
|
0x0144: 'n', // WITH ACUTE, LATIN SMALL LETTER
|
||||||
|
0x0148: 'n', // WITH CARON, LATIN SMALL LETTER
|
||||||
|
0x0146: 'n', // WITH CEDILLA, LATIN SMALL LETTER
|
||||||
|
0x1E4B: 'n', // WITH CIRCUMFLEX BELOW, LATIN SMALL LETTER
|
||||||
|
0x0235: 'n', // WITH CURL, LATIN SMALL LETTER
|
||||||
|
0x1E45: 'n', // WITH DOT ABOVE, LATIN SMALL LETTER
|
||||||
|
0x1E47: 'n', // WITH DOT BELOW, LATIN SMALL LETTER
|
||||||
|
0x01F9: 'n', // WITH GRAVE, LATIN SMALL LETTER
|
||||||
|
0x0272: 'n', // WITH LEFT HOOK, LATIN SMALL LETTER
|
||||||
|
0x1E49: 'n', // WITH LINE BELOW, LATIN SMALL LETTER
|
||||||
|
0x019E: 'n', // WITH LONG RIGHT LEG, LATIN SMALL LETTER
|
||||||
|
0x0273: 'n', // WITH RETROFLEX HOOK, LATIN SMALL LETTER
|
||||||
|
0x00F1: 'n', // WITH TILDE, LATIN SMALL LETTER
|
||||||
|
0x2099: 'n', // , LATIN SUBSCRIPT SMALL LETTER
|
||||||
|
0x00F3: 'o', // WITH ACUTE, LATIN SMALL LETTER
|
||||||
|
0x014F: 'o', // WITH BREVE, LATIN SMALL LETTER
|
||||||
|
0x01D2: 'o', // WITH CARON, LATIN SMALL LETTER
|
||||||
|
0x00F4: 'o', // WITH CIRCUMFLEX, LATIN SMALL LETTER
|
||||||
|
0x00F6: 'o', // WITH DIAERESIS, LATIN SMALL LETTER
|
||||||
|
0x022F: 'o', // WITH DOT ABOVE, LATIN SMALL LETTER
|
||||||
|
0x1ECD: 'o', // WITH DOT BELOW, LATIN SMALL LETTER
|
||||||
|
0x0151: 'o', // WITH DOUBLE ACUTE, LATIN SMALL LETTER
|
||||||
|
0x020D: 'o', // WITH DOUBLE GRAVE, LATIN SMALL LETTER
|
||||||
|
0x00F2: 'o', // WITH GRAVE, LATIN SMALL LETTER
|
||||||
|
0x1ECF: 'o', // WITH HOOK ABOVE, LATIN SMALL LETTER
|
||||||
|
0x01A1: 'o', // WITH HORN, LATIN SMALL LETTER
|
||||||
|
0x020F: 'o', // WITH INVERTED BREVE, LATIN SMALL LETTER
|
||||||
|
0x014D: 'o', // WITH MACRON, LATIN SMALL LETTER
|
||||||
|
0x01EB: 'o', // WITH OGONEK, LATIN SMALL LETTER
|
||||||
|
0x00F8: 'o', // WITH STROKE, LATIN SMALL LETTER
|
||||||
|
0x1D13: 'o', // WITH STROKE, LATIN SMALL LETTER SIDEWAYS
|
||||||
|
0x00F5: 'o', // WITH TILDE, LATIN SMALL LETTER
|
||||||
|
0x0366: 'o', // , COMBINING LATIN SMALL LETTER
|
||||||
|
0x0275: 'o', // , LATIN SMALL LETTER BARRED
|
||||||
|
0x1D17: 'o', // , LATIN SMALL LETTER BOTTOM HALF
|
||||||
|
0x0254: 'o', // , LATIN SMALL LETTER OPEN
|
||||||
|
0x1D11: 'o', // , LATIN SMALL LETTER SIDEWAYS
|
||||||
|
0x1D12: 'o', // , LATIN SMALL LETTER SIDEWAYS OPEN
|
||||||
|
0x1D16: 'o', // , LATIN SMALL LETTER TOP HALF
|
||||||
|
0x1E55: 'p', // WITH ACUTE, LATIN SMALL LETTER
|
||||||
|
0x1E57: 'p', // WITH DOT ABOVE, LATIN SMALL LETTER
|
||||||
|
0x01A5: 'p', // WITH HOOK, LATIN SMALL LETTER
|
||||||
|
0x209A: 'p', // , LATIN SUBSCRIPT SMALL LETTER
|
||||||
|
0x024B: 'q', // WITH HOOK TAIL, LATIN SMALL LETTER
|
||||||
|
0x02A0: 'q', // WITH HOOK, LATIN SMALL LETTER
|
||||||
|
0x0155: 'r', // WITH ACUTE, LATIN SMALL LETTER
|
||||||
|
0x0159: 'r', // WITH CARON, LATIN SMALL LETTER
|
||||||
|
0x0157: 'r', // WITH CEDILLA, LATIN SMALL LETTER
|
||||||
|
0x1E59: 'r', // WITH DOT ABOVE, LATIN SMALL LETTER
|
||||||
|
0x1E5B: 'r', // WITH DOT BELOW, LATIN SMALL LETTER
|
||||||
|
0x0211: 'r', // WITH DOUBLE GRAVE, LATIN SMALL LETTER
|
||||||
|
0x027E: 'r', // WITH FISHHOOK, LATIN SMALL LETTER
|
||||||
|
0x027F: 'r', // WITH FISHHOOK, LATIN SMALL LETTER REVERSED
|
||||||
|
0x027B: 'r', // WITH HOOK, LATIN SMALL LETTER TURNED
|
||||||
|
0x0213: 'r', // WITH INVERTED BREVE, LATIN SMALL LETTER
|
||||||
|
0x1E5F: 'r', // WITH LINE BELOW, LATIN SMALL LETTER
|
||||||
|
0x027C: 'r', // WITH LONG LEG, LATIN SMALL LETTER
|
||||||
|
0x027A: 'r', // WITH LONG LEG, LATIN SMALL LETTER TURNED
|
||||||
|
0x024D: 'r', // WITH STROKE, LATIN SMALL LETTER
|
||||||
|
0x027D: 'r', // WITH TAIL, LATIN SMALL LETTER
|
||||||
|
0x036C: 'r', // , COMBINING LATIN SMALL LETTER
|
||||||
|
0x0279: 'r', // , LATIN SMALL LETTER TURNED
|
||||||
|
0x1D63: 'r', // , LATIN SUBSCRIPT SMALL LETTER
|
||||||
|
0x015B: 's', // WITH ACUTE, LATIN SMALL LETTER
|
||||||
|
0x0161: 's', // WITH CARON, LATIN SMALL LETTER
|
||||||
|
0x015F: 's', // WITH CEDILLA, LATIN SMALL LETTER
|
||||||
|
0x015D: 's', // WITH CIRCUMFLEX, LATIN SMALL LETTER
|
||||||
|
0x0219: 's', // WITH COMMA BELOW, LATIN SMALL LETTER
|
||||||
|
0x1E61: 's', // WITH DOT ABOVE, LATIN SMALL LETTER
|
||||||
|
0x1E9B: 's', // WITH DOT ABOVE, LATIN SMALL LETTER LONG
|
||||||
|
0x1E63: 's', // WITH DOT BELOW, LATIN SMALL LETTER
|
||||||
|
0x0282: 's', // WITH HOOK, LATIN SMALL LETTER
|
||||||
|
0x023F: 's', // WITH SWASH TAIL, LATIN SMALL LETTER
|
||||||
|
0x017F: 's', // , LATIN SMALL LETTER LONG
|
||||||
|
0x00DF: 's', // , LATIN SMALL LETTER SHARP
|
||||||
|
0x209B: 's', // , LATIN SUBSCRIPT SMALL LETTER
|
||||||
|
0x0165: 't', // WITH CARON, LATIN SMALL LETTER
|
||||||
|
0x0163: 't', // WITH CEDILLA, LATIN SMALL LETTER
|
||||||
|
0x1E71: 't', // WITH CIRCUMFLEX BELOW, LATIN SMALL LETTER
|
||||||
|
0x021B: 't', // WITH COMMA BELOW, LATIN SMALL LETTER
|
||||||
|
0x0236: 't', // WITH CURL, LATIN SMALL LETTER
|
||||||
|
0x1E97: 't', // WITH DIAERESIS, LATIN SMALL LETTER
|
||||||
|
0x1E6B: 't', // WITH DOT ABOVE, LATIN SMALL LETTER
|
||||||
|
0x1E6D: 't', // WITH DOT BELOW, LATIN SMALL LETTER
|
||||||
|
0x01AD: 't', // WITH HOOK, LATIN SMALL LETTER
|
||||||
|
0x1E6F: 't', // WITH LINE BELOW, LATIN SMALL LETTER
|
||||||
|
0x01AB: 't', // WITH PALATAL HOOK, LATIN SMALL LETTER
|
||||||
|
0x0288: 't', // WITH RETROFLEX HOOK, LATIN SMALL LETTER
|
||||||
|
0x0167: 't', // WITH STROKE, LATIN SMALL LETTER
|
||||||
|
0x036D: 't', // , COMBINING LATIN SMALL LETTER
|
||||||
|
0x0287: 't', // , LATIN SMALL LETTER TURNED
|
||||||
|
0x209C: 't', // , LATIN SUBSCRIPT SMALL LETTER
|
||||||
|
0x0289: 'u', // BAR, LATIN SMALL LETTER
|
||||||
|
0x00FA: 'u', // WITH ACUTE, LATIN SMALL LETTER
|
||||||
|
0x016D: 'u', // WITH BREVE, LATIN SMALL LETTER
|
||||||
|
0x01D4: 'u', // WITH CARON, LATIN SMALL LETTER
|
||||||
|
0x1E77: 'u', // WITH CIRCUMFLEX BELOW, LATIN SMALL LETTER
|
||||||
|
0x00FB: 'u', // WITH CIRCUMFLEX, LATIN SMALL LETTER
|
||||||
|
0x1E73: 'u', // WITH DIAERESIS BELOW, LATIN SMALL LETTER
|
||||||
|
0x00FC: 'u', // WITH DIAERESIS, LATIN SMALL LETTER
|
||||||
|
0x1EE5: 'u', // WITH DOT BELOW, LATIN SMALL LETTER
|
||||||
|
0x0171: 'u', // WITH DOUBLE ACUTE, LATIN SMALL LETTER
|
||||||
|
0x0215: 'u', // WITH DOUBLE GRAVE, LATIN SMALL LETTER
|
||||||
|
0x00F9: 'u', // WITH GRAVE, LATIN SMALL LETTER
|
||||||
|
0x1EE7: 'u', // WITH HOOK ABOVE, LATIN SMALL LETTER
|
||||||
|
0x01B0: 'u', // WITH HORN, LATIN SMALL LETTER
|
||||||
|
0x0217: 'u', // WITH INVERTED BREVE, LATIN SMALL LETTER
|
||||||
|
0x016B: 'u', // WITH MACRON, LATIN SMALL LETTER
|
||||||
|
0x0173: 'u', // WITH OGONEK, LATIN SMALL LETTER
|
||||||
|
0x016F: 'u', // WITH RING ABOVE, LATIN SMALL LETTER
|
||||||
|
0x1E75: 'u', // WITH TILDE BELOW, LATIN SMALL LETTER
|
||||||
|
0x0169: 'u', // WITH TILDE, LATIN SMALL LETTER
|
||||||
|
0x0367: 'u', // , COMBINING LATIN SMALL LETTER
|
||||||
|
0x1D1D: 'u', // , LATIN SMALL LETTER SIDEWAYS
|
||||||
|
0x1D1E: 'u', // , LATIN SMALL LETTER SIDEWAYS DIAERESIZED
|
||||||
|
0x1D64: 'u', // , LATIN SUBSCRIPT SMALL LETTER
|
||||||
|
0x1E7F: 'v', // WITH DOT BELOW, LATIN SMALL LETTER
|
||||||
|
0x028B: 'v', // WITH HOOK, LATIN SMALL LETTER
|
||||||
|
0x1E7D: 'v', // WITH TILDE, LATIN SMALL LETTER
|
||||||
|
0x036E: 'v', // , COMBINING LATIN SMALL LETTER
|
||||||
|
0x028C: 'v', // , LATIN SMALL LETTER TURNED
|
||||||
|
0x1D65: 'v', // , LATIN SUBSCRIPT SMALL LETTER
|
||||||
|
0x1E83: 'w', // WITH ACUTE, LATIN SMALL LETTER
|
||||||
|
0x0175: 'w', // WITH CIRCUMFLEX, LATIN SMALL LETTER
|
||||||
|
0x1E85: 'w', // WITH DIAERESIS, LATIN SMALL LETTER
|
||||||
|
0x1E87: 'w', // WITH DOT ABOVE, LATIN SMALL LETTER
|
||||||
|
0x1E89: 'w', // WITH DOT BELOW, LATIN SMALL LETTER
|
||||||
|
0x1E81: 'w', // WITH GRAVE, LATIN SMALL LETTER
|
||||||
|
0x1E98: 'w', // WITH RING ABOVE, LATIN SMALL LETTER
|
||||||
|
0x028D: 'w', // , LATIN SMALL LETTER TURNED
|
||||||
|
0x1E8D: 'x', // WITH DIAERESIS, LATIN SMALL LETTER
|
||||||
|
0x1E8B: 'x', // WITH DOT ABOVE, LATIN SMALL LETTER
|
||||||
|
0x036F: 'x', // , COMBINING LATIN SMALL LETTER
|
||||||
|
0x00FD: 'y', // WITH ACUTE, LATIN SMALL LETTER
|
||||||
|
0x0177: 'y', // WITH CIRCUMFLEX, LATIN SMALL LETTER
|
||||||
|
0x00FF: 'y', // WITH DIAERESIS, LATIN SMALL LETTER
|
||||||
|
0x1E8F: 'y', // WITH DOT ABOVE, LATIN SMALL LETTER
|
||||||
|
0x1EF5: 'y', // WITH DOT BELOW, LATIN SMALL LETTER
|
||||||
|
0x1EF3: 'y', // WITH GRAVE, LATIN SMALL LETTER
|
||||||
|
0x1EF7: 'y', // WITH HOOK ABOVE, LATIN SMALL LETTER
|
||||||
|
0x01B4: 'y', // WITH HOOK, LATIN SMALL LETTER
|
||||||
|
0x0233: 'y', // WITH MACRON, LATIN SMALL LETTER
|
||||||
|
0x1E99: 'y', // WITH RING ABOVE, LATIN SMALL LETTER
|
||||||
|
0x024F: 'y', // WITH STROKE, LATIN SMALL LETTER
|
||||||
|
0x1EF9: 'y', // WITH TILDE, LATIN SMALL LETTER
|
||||||
|
0x028E: 'y', // , LATIN SMALL LETTER TURNED
|
||||||
|
0x017A: 'z', // WITH ACUTE, LATIN SMALL LETTER
|
||||||
|
0x017E: 'z', // WITH CARON, LATIN SMALL LETTER
|
||||||
|
0x1E91: 'z', // WITH CIRCUMFLEX, LATIN SMALL LETTER
|
||||||
|
0x0291: 'z', // WITH CURL, LATIN SMALL LETTER
|
||||||
|
0x017C: 'z', // WITH DOT ABOVE, LATIN SMALL LETTER
|
||||||
|
0x1E93: 'z', // WITH DOT BELOW, LATIN SMALL LETTER
|
||||||
|
0x0225: 'z', // WITH HOOK, LATIN SMALL LETTER
|
||||||
|
0x1E95: 'z', // WITH LINE BELOW, LATIN SMALL LETTER
|
||||||
|
0x0290: 'z', // WITH RETROFLEX HOOK, LATIN SMALL LETTER
|
||||||
|
0x01B6: 'z', // WITH STROKE, LATIN SMALL LETTER
|
||||||
|
0x0240: 'z', // WITH SWASH TAIL, LATIN SMALL LETTER
|
||||||
|
0x0251: 'a', // , latin small letter script
|
||||||
|
0x00C1: 'A', // WITH ACUTE, LATIN CAPITAL LETTER
|
||||||
|
0x00C2: 'A', // WITH CIRCUMFLEX, LATIN CAPITAL LETTER
|
||||||
|
0x00C4: 'A', // WITH DIAERESIS, LATIN CAPITAL LETTER
|
||||||
|
0x00C0: 'A', // WITH GRAVE, LATIN CAPITAL LETTER
|
||||||
|
0x00C5: 'A', // WITH RING ABOVE, LATIN CAPITAL LETTER
|
||||||
|
0x023A: 'A', // WITH STROKE, LATIN CAPITAL LETTER
|
||||||
|
0x00C3: 'A', // WITH TILDE, LATIN CAPITAL LETTER
|
||||||
|
0x1D00: 'A', // , LATIN LETTER SMALL CAPITAL
|
||||||
|
0x0181: 'B', // WITH HOOK, LATIN CAPITAL LETTER
|
||||||
|
0x0243: 'B', // WITH STROKE, LATIN CAPITAL LETTER
|
||||||
|
0x0299: 'B', // , LATIN LETTER SMALL CAPITAL
|
||||||
|
0x1D03: 'B', // , LATIN LETTER SMALL CAPITAL BARRED
|
||||||
|
0x00C7: 'C', // WITH CEDILLA, LATIN CAPITAL LETTER
|
||||||
|
0x023B: 'C', // WITH STROKE, LATIN CAPITAL LETTER
|
||||||
|
0x1D04: 'C', // , LATIN LETTER SMALL CAPITAL
|
||||||
|
0x018A: 'D', // WITH HOOK, LATIN CAPITAL LETTER
|
||||||
|
0x0189: 'D', // , LATIN CAPITAL LETTER AFRICAN
|
||||||
|
0x1D05: 'D', // , LATIN LETTER SMALL CAPITAL
|
||||||
|
0x00C9: 'E', // WITH ACUTE, LATIN CAPITAL LETTER
|
||||||
|
0x00CA: 'E', // WITH CIRCUMFLEX, LATIN CAPITAL LETTER
|
||||||
|
0x00CB: 'E', // WITH DIAERESIS, LATIN CAPITAL LETTER
|
||||||
|
0x00C8: 'E', // WITH GRAVE, LATIN CAPITAL LETTER
|
||||||
|
0x0246: 'E', // WITH STROKE, LATIN CAPITAL LETTER
|
||||||
|
0x0190: 'E', // , LATIN CAPITAL LETTER OPEN
|
||||||
|
0x018E: 'E', // , LATIN CAPITAL LETTER REVERSED
|
||||||
|
0x1D07: 'E', // , LATIN LETTER SMALL CAPITAL
|
||||||
|
0x0193: 'G', // WITH HOOK, LATIN CAPITAL LETTER
|
||||||
|
0x029B: 'G', // WITH HOOK, LATIN LETTER SMALL CAPITAL
|
||||||
|
0x0262: 'G', // , LATIN LETTER SMALL CAPITAL
|
||||||
|
0x029C: 'H', // , LATIN LETTER SMALL CAPITAL
|
||||||
|
0x00CD: 'I', // WITH ACUTE, LATIN CAPITAL LETTER
|
||||||
|
0x00CE: 'I', // WITH CIRCUMFLEX, LATIN CAPITAL LETTER
|
||||||
|
0x00CF: 'I', // WITH DIAERESIS, LATIN CAPITAL LETTER
|
||||||
|
0x0130: 'I', // WITH DOT ABOVE, LATIN CAPITAL LETTER
|
||||||
|
0x00CC: 'I', // WITH GRAVE, LATIN CAPITAL LETTER
|
||||||
|
0x0197: 'I', // WITH STROKE, LATIN CAPITAL LETTER
|
||||||
|
0x026A: 'I', // , LATIN LETTER SMALL CAPITAL
|
||||||
|
0x0248: 'J', // WITH STROKE, LATIN CAPITAL LETTER
|
||||||
|
0x1D0A: 'J', // , LATIN LETTER SMALL CAPITAL
|
||||||
|
0x1D0B: 'K', // , LATIN LETTER SMALL CAPITAL
|
||||||
|
0x023D: 'L', // WITH BAR, LATIN CAPITAL LETTER
|
||||||
|
0x1D0C: 'L', // WITH STROKE, LATIN LETTER SMALL CAPITAL
|
||||||
|
0x029F: 'L', // , LATIN LETTER SMALL CAPITAL
|
||||||
|
0x019C: 'M', // , LATIN CAPITAL LETTER TURNED
|
||||||
|
0x1D0D: 'M', // , LATIN LETTER SMALL CAPITAL
|
||||||
|
0x019D: 'N', // WITH LEFT HOOK, LATIN CAPITAL LETTER
|
||||||
|
0x0220: 'N', // WITH LONG RIGHT LEG, LATIN CAPITAL LETTER
|
||||||
|
0x00D1: 'N', // WITH TILDE, LATIN CAPITAL LETTER
|
||||||
|
0x0274: 'N', // , LATIN LETTER SMALL CAPITAL
|
||||||
|
0x1D0E: 'N', // , LATIN LETTER SMALL CAPITAL REVERSED
|
||||||
|
0x00D3: 'O', // WITH ACUTE, LATIN CAPITAL LETTER
|
||||||
|
0x00D4: 'O', // WITH CIRCUMFLEX, LATIN CAPITAL LETTER
|
||||||
|
0x00D6: 'O', // WITH DIAERESIS, LATIN CAPITAL LETTER
|
||||||
|
0x00D2: 'O', // WITH GRAVE, LATIN CAPITAL LETTER
|
||||||
|
0x019F: 'O', // WITH MIDDLE TILDE, LATIN CAPITAL LETTER
|
||||||
|
0x00D8: 'O', // WITH STROKE, LATIN CAPITAL LETTER
|
||||||
|
0x00D5: 'O', // WITH TILDE, LATIN CAPITAL LETTER
|
||||||
|
0x0186: 'O', // , LATIN CAPITAL LETTER OPEN
|
||||||
|
0x1D0F: 'O', // , LATIN LETTER SMALL CAPITAL
|
||||||
|
0x1D10: 'O', // , LATIN LETTER SMALL CAPITAL OPEN
|
||||||
|
0x1D18: 'P', // , LATIN LETTER SMALL CAPITAL
|
||||||
|
0x024A: 'Q', // WITH HOOK TAIL, LATIN CAPITAL LETTER SMALL
|
||||||
|
0x024C: 'R', // WITH STROKE, LATIN CAPITAL LETTER
|
||||||
|
0x0280: 'R', // , LATIN LETTER SMALL CAPITAL
|
||||||
|
0x0281: 'R', // , LATIN LETTER SMALL CAPITAL INVERTED
|
||||||
|
0x1D19: 'R', // , LATIN LETTER SMALL CAPITAL REVERSED
|
||||||
|
0x1D1A: 'R', // , LATIN LETTER SMALL CAPITAL TURNED
|
||||||
|
0x023E: 'T', // WITH DIAGONAL STROKE, LATIN CAPITAL LETTER
|
||||||
|
0x01AE: 'T', // WITH RETROFLEX HOOK, LATIN CAPITAL LETTER
|
||||||
|
0x1D1B: 'T', // , LATIN LETTER SMALL CAPITAL
|
||||||
|
0x0244: 'U', // BAR, LATIN CAPITAL LETTER
|
||||||
|
0x00DA: 'U', // WITH ACUTE, LATIN CAPITAL LETTER
|
||||||
|
0x00DB: 'U', // WITH CIRCUMFLEX, LATIN CAPITAL LETTER
|
||||||
|
0x00DC: 'U', // WITH DIAERESIS, LATIN CAPITAL LETTER
|
||||||
|
0x00D9: 'U', // WITH GRAVE, LATIN CAPITAL LETTER
|
||||||
|
0x1D1C: 'U', // , LATIN LETTER SMALL CAPITAL
|
||||||
|
0x01B2: 'V', // WITH HOOK, LATIN CAPITAL LETTER
|
||||||
|
0x0245: 'V', // , LATIN CAPITAL LETTER TURNED
|
||||||
|
0x1D20: 'V', // , LATIN LETTER SMALL CAPITAL
|
||||||
|
0x1D21: 'W', // , LATIN LETTER SMALL CAPITAL
|
||||||
|
0x00DD: 'Y', // WITH ACUTE, LATIN CAPITAL LETTER
|
||||||
|
0x0178: 'Y', // WITH DIAERESIS, LATIN CAPITAL LETTER
|
||||||
|
0x024E: 'Y', // WITH STROKE, LATIN CAPITAL LETTER
|
||||||
|
0x028F: 'Y', // , LATIN LETTER SMALL CAPITAL
|
||||||
|
0x1D22: 'Z', // , LATIN LETTER SMALL CAPITAL
|
||||||
|
}
|
||||||
|
|
||||||
|
// NormalizeRunes normalizes latin script letters
|
||||||
|
func NormalizeRunes(runes []rune) []rune {
|
||||||
|
ret := make([]rune, len(runes))
|
||||||
|
copy(ret, runes)
|
||||||
|
for idx, r := range runes {
|
||||||
|
if r < 0x00C0 || r > 0x2184 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
n := normalized[r]
|
||||||
|
if n > 0 {
|
||||||
|
ret[idx] = normalized[r]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// Current version
|
// Current version
|
||||||
version = "0.15.9"
|
version = "0.16.0"
|
||||||
|
|
||||||
// Core
|
// Core
|
||||||
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
||||||
|
@ -143,7 +143,7 @@ func Run(opts *Options) {
|
|||||||
}
|
}
|
||||||
patternBuilder := func(runes []rune) *Pattern {
|
patternBuilder := func(runes []rune) *Pattern {
|
||||||
return BuildPattern(
|
return BuildPattern(
|
||||||
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, forward,
|
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward,
|
||||||
opts.Filter == nil, opts.Nth, opts.Delimiter, runes)
|
opts.Filter == nil, opts.Nth, opts.Delimiter, runes)
|
||||||
}
|
}
|
||||||
matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox)
|
matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox)
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/junegunn/fzf/src/algo"
|
"github.com/junegunn/fzf/src/algo"
|
||||||
"github.com/junegunn/fzf/src/tui"
|
"github.com/junegunn/fzf/src/tui"
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
|
||||||
"github.com/junegunn/go-shellwords"
|
"github.com/junegunn/go-shellwords"
|
||||||
)
|
)
|
||||||
@ -23,6 +24,7 @@ const usage = `usage: fzf [options]
|
|||||||
--algo=TYPE Fuzzy matching algorithm: [v1|v2] (default: v2)
|
--algo=TYPE Fuzzy matching algorithm: [v1|v2] (default: v2)
|
||||||
-i Case-insensitive match (default: smart-case match)
|
-i Case-insensitive match (default: smart-case match)
|
||||||
+i Case-sensitive match
|
+i Case-sensitive match
|
||||||
|
--literal Do not normalize latin script letters before matching
|
||||||
-n, --nth=N[,..] Comma-separated list of field index expressions
|
-n, --nth=N[,..] Comma-separated list of field index expressions
|
||||||
for limiting search scope. Each can be a non-zero
|
for limiting search scope. Each can be a non-zero
|
||||||
integer or a range expression ([BEGIN]..[END]).
|
integer or a range expression ([BEGIN]..[END]).
|
||||||
@ -43,9 +45,14 @@ const usage = `usage: fzf [options]
|
|||||||
--no-hscroll Disable horizontal scroll
|
--no-hscroll Disable horizontal scroll
|
||||||
--hscroll-off=COL Number of screen columns to keep to the right of the
|
--hscroll-off=COL Number of screen columns to keep to the right of the
|
||||||
highlighted substring (default: 10)
|
highlighted substring (default: 10)
|
||||||
|
--filepath-word Make word-wise movements respect path separators
|
||||||
--jump-labels=CHARS Label characters for jump and jump-accept
|
--jump-labels=CHARS Label characters for jump and jump-accept
|
||||||
|
|
||||||
Layout
|
Layout
|
||||||
|
--height=HEIGHT[%] Display fzf window below the cursor with the given
|
||||||
|
height instead of using fullscreen
|
||||||
|
--min-height=HEIGHT Minimum height when --height is given in percent
|
||||||
|
(default: 10)
|
||||||
--reverse Reverse orientation
|
--reverse Reverse orientation
|
||||||
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L)
|
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L)
|
||||||
--inline-info Display finder info inline with the query
|
--inline-info Display finder info inline with the query
|
||||||
@ -135,6 +142,7 @@ type Options struct {
|
|||||||
FuzzyAlgo algo.Algo
|
FuzzyAlgo algo.Algo
|
||||||
Extended bool
|
Extended bool
|
||||||
Case Case
|
Case Case
|
||||||
|
Normalize bool
|
||||||
Nth []Range
|
Nth []Range
|
||||||
WithNth []Range
|
WithNth []Range
|
||||||
Delimiter Delimiter
|
Delimiter Delimiter
|
||||||
@ -147,10 +155,13 @@ type Options struct {
|
|||||||
Theme *tui.ColorTheme
|
Theme *tui.ColorTheme
|
||||||
Black bool
|
Black bool
|
||||||
Bold bool
|
Bold bool
|
||||||
|
Height sizeSpec
|
||||||
|
MinHeight int
|
||||||
Reverse bool
|
Reverse bool
|
||||||
Cycle bool
|
Cycle bool
|
||||||
Hscroll bool
|
Hscroll bool
|
||||||
HscrollOff int
|
HscrollOff int
|
||||||
|
FileWord bool
|
||||||
InlineInfo bool
|
InlineInfo bool
|
||||||
JumpLabels string
|
JumpLabels string
|
||||||
Prompt string
|
Prompt string
|
||||||
@ -181,6 +192,7 @@ func defaultOptions() *Options {
|
|||||||
FuzzyAlgo: algo.FuzzyMatchV2,
|
FuzzyAlgo: algo.FuzzyMatchV2,
|
||||||
Extended: true,
|
Extended: true,
|
||||||
Case: CaseSmart,
|
Case: CaseSmart,
|
||||||
|
Normalize: true,
|
||||||
Nth: make([]Range, 0),
|
Nth: make([]Range, 0),
|
||||||
WithNth: make([]Range, 0),
|
WithNth: make([]Range, 0),
|
||||||
Delimiter: Delimiter{},
|
Delimiter: Delimiter{},
|
||||||
@ -193,10 +205,12 @@ func defaultOptions() *Options {
|
|||||||
Theme: tui.EmptyTheme(),
|
Theme: tui.EmptyTheme(),
|
||||||
Black: false,
|
Black: false,
|
||||||
Bold: true,
|
Bold: true,
|
||||||
|
MinHeight: 10,
|
||||||
Reverse: false,
|
Reverse: false,
|
||||||
Cycle: false,
|
Cycle: false,
|
||||||
Hscroll: true,
|
Hscroll: true,
|
||||||
HscrollOff: 10,
|
HscrollOff: 10,
|
||||||
|
FileWord: false,
|
||||||
InlineInfo: false,
|
InlineInfo: false,
|
||||||
JumpLabels: defaultJumpLabels,
|
JumpLabels: defaultJumpLabels,
|
||||||
Prompt: "> ",
|
Prompt: "> ",
|
||||||
@ -482,6 +496,7 @@ func dupeTheme(theme *tui.ColorTheme) *tui.ColorTheme {
|
|||||||
|
|
||||||
func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
|
func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
|
||||||
theme := dupeTheme(defaultTheme)
|
theme := dupeTheme(defaultTheme)
|
||||||
|
rrggbb := regexp.MustCompile("^#[0-9a-fA-F]{6}$")
|
||||||
for _, str := range strings.Split(strings.ToLower(str), ",") {
|
for _, str := range strings.Split(strings.ToLower(str), ",") {
|
||||||
switch str {
|
switch str {
|
||||||
case "dark":
|
case "dark":
|
||||||
@ -505,11 +520,17 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
|
|||||||
if len(pair) != 2 {
|
if len(pair) != 2 {
|
||||||
fail()
|
fail()
|
||||||
}
|
}
|
||||||
ansi32, err := strconv.Atoi(pair[1])
|
|
||||||
if err != nil || ansi32 < -1 || ansi32 > 255 {
|
var ansi tui.Color
|
||||||
fail()
|
if rrggbb.MatchString(pair[1]) {
|
||||||
|
ansi = tui.HexToColor(pair[1])
|
||||||
|
} else {
|
||||||
|
ansi32, err := strconv.Atoi(pair[1])
|
||||||
|
if err != nil || ansi32 < -1 || ansi32 > 255 {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
ansi = tui.Color(ansi32)
|
||||||
}
|
}
|
||||||
ansi := tui.Color(ansi32)
|
|
||||||
switch pair[0] {
|
switch pair[0] {
|
||||||
case "fg":
|
case "fg":
|
||||||
theme.Fg = ansi
|
theme.Fg = ansi
|
||||||
@ -760,6 +781,14 @@ func parseSize(str string, maxPercent float64, label string) sizeSpec {
|
|||||||
return sizeSpec{val, percent}
|
return sizeSpec{val, percent}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseHeight(str string) sizeSpec {
|
||||||
|
if util.IsWindows() {
|
||||||
|
errorExit("--height options is currently not supported on Windows")
|
||||||
|
}
|
||||||
|
size := parseSize(str, 100, "height")
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
|
||||||
func parsePreviewWindow(opts *previewOpts, input string) {
|
func parsePreviewWindow(opts *previewOpts, input string) {
|
||||||
// Default
|
// Default
|
||||||
opts.position = posRight
|
opts.position = posRight
|
||||||
@ -875,6 +904,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
case "-f", "--filter":
|
case "-f", "--filter":
|
||||||
filter := nextString(allArgs, &i, "query string required")
|
filter := nextString(allArgs, &i, "query string required")
|
||||||
opts.Filter = &filter
|
opts.Filter = &filter
|
||||||
|
case "--literal":
|
||||||
|
opts.Normalize = false
|
||||||
|
case "--no-literal":
|
||||||
|
opts.Normalize = true
|
||||||
case "--algo":
|
case "--algo":
|
||||||
opts.FuzzyAlgo = parseAlgo(nextString(allArgs, &i, "algorithm required (v1|v2)"))
|
opts.FuzzyAlgo = parseAlgo(nextString(allArgs, &i, "algorithm required (v1|v2)"))
|
||||||
case "--expect":
|
case "--expect":
|
||||||
@ -946,6 +979,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Hscroll = false
|
opts.Hscroll = false
|
||||||
case "--hscroll-off":
|
case "--hscroll-off":
|
||||||
opts.HscrollOff = nextInt(allArgs, &i, "hscroll offset required")
|
opts.HscrollOff = nextInt(allArgs, &i, "hscroll offset required")
|
||||||
|
case "--filepath-word":
|
||||||
|
opts.FileWord = true
|
||||||
|
case "--no-filepath-word":
|
||||||
|
opts.FileWord = false
|
||||||
case "--inline-info":
|
case "--inline-info":
|
||||||
opts.InlineInfo = true
|
opts.InlineInfo = true
|
||||||
case "--no-inline-info":
|
case "--no-inline-info":
|
||||||
@ -1003,6 +1040,12 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
case "--preview-window":
|
case "--preview-window":
|
||||||
parsePreviewWindow(&opts.Preview,
|
parsePreviewWindow(&opts.Preview,
|
||||||
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][:SIZE[%]][:wrap][:hidden]"))
|
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][:SIZE[%]][:wrap][:hidden]"))
|
||||||
|
case "--height":
|
||||||
|
opts.Height = parseHeight(nextString(allArgs, &i, "height required: HEIGHT[%]"))
|
||||||
|
case "--min-height":
|
||||||
|
opts.MinHeight = nextInt(allArgs, &i, "height required: HEIGHT")
|
||||||
|
case "--no-height":
|
||||||
|
opts.Height = sizeSpec{}
|
||||||
case "--no-margin":
|
case "--no-margin":
|
||||||
opts.Margin = defaultMargin()
|
opts.Margin = defaultMargin()
|
||||||
case "--margin":
|
case "--margin":
|
||||||
@ -1029,6 +1072,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.WithNth = splitNth(value)
|
opts.WithNth = splitNth(value)
|
||||||
} else if match, _ := optString(arg, "-s", "--sort="); match {
|
} else if match, _ := optString(arg, "-s", "--sort="); match {
|
||||||
opts.Sort = 1 // Don't care
|
opts.Sort = 1 // Don't care
|
||||||
|
} else if match, value := optString(arg, "--height="); match {
|
||||||
|
opts.Height = parseHeight(value)
|
||||||
|
} else if match, value := optString(arg, "--min-height="); match {
|
||||||
|
opts.MinHeight = atoi(value)
|
||||||
} else if match, value := optString(arg, "--toggle-sort="); match {
|
} else if match, value := optString(arg, "--toggle-sort="); match {
|
||||||
parseToggleSort(opts.Keymap, value)
|
parseToggleSort(opts.Keymap, value)
|
||||||
} else if match, value := optString(arg, "--expect="); match {
|
} else if match, value := optString(arg, "--expect="); match {
|
||||||
|
@ -43,6 +43,7 @@ type Pattern struct {
|
|||||||
fuzzyAlgo algo.Algo
|
fuzzyAlgo algo.Algo
|
||||||
extended bool
|
extended bool
|
||||||
caseSensitive bool
|
caseSensitive bool
|
||||||
|
normalize bool
|
||||||
forward bool
|
forward bool
|
||||||
text []rune
|
text []rune
|
||||||
termSets []termSet
|
termSets []termSet
|
||||||
@ -75,7 +76,7 @@ func clearChunkCache() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BuildPattern builds Pattern object from the given arguments
|
// BuildPattern builds Pattern object from the given arguments
|
||||||
func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, forward bool,
|
func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool,
|
||||||
cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern {
|
cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern {
|
||||||
|
|
||||||
var asString string
|
var asString string
|
||||||
@ -94,7 +95,7 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
|
|||||||
termSets := []termSet{}
|
termSets := []termSet{}
|
||||||
|
|
||||||
if extended {
|
if extended {
|
||||||
termSets = parseTerms(fuzzy, caseMode, asString)
|
termSets = parseTerms(fuzzy, caseMode, normalize, asString)
|
||||||
Loop:
|
Loop:
|
||||||
for _, termSet := range termSets {
|
for _, termSet := range termSets {
|
||||||
for idx, term := range termSet {
|
for idx, term := range termSet {
|
||||||
@ -120,6 +121,7 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
|
|||||||
fuzzyAlgo: fuzzyAlgo,
|
fuzzyAlgo: fuzzyAlgo,
|
||||||
extended: extended,
|
extended: extended,
|
||||||
caseSensitive: caseSensitive,
|
caseSensitive: caseSensitive,
|
||||||
|
normalize: normalize,
|
||||||
forward: forward,
|
forward: forward,
|
||||||
text: []rune(asString),
|
text: []rune(asString),
|
||||||
termSets: termSets,
|
termSets: termSets,
|
||||||
@ -138,7 +140,7 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
|
|||||||
return ptr
|
return ptr
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseTerms(fuzzy bool, caseMode Case, str string) []termSet {
|
func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet {
|
||||||
tokens := _splitRegex.Split(str, -1)
|
tokens := _splitRegex.Split(str, -1)
|
||||||
sets := []termSet{}
|
sets := []termSet{}
|
||||||
set := termSet{}
|
set := termSet{}
|
||||||
@ -194,10 +196,14 @@ func parseTerms(fuzzy bool, caseMode Case, str string) []termSet {
|
|||||||
sets = append(sets, set)
|
sets = append(sets, set)
|
||||||
set = termSet{}
|
set = termSet{}
|
||||||
}
|
}
|
||||||
|
textRunes := []rune(text)
|
||||||
|
if normalize {
|
||||||
|
textRunes = algo.NormalizeRunes(textRunes)
|
||||||
|
}
|
||||||
set = append(set, term{
|
set = append(set, term{
|
||||||
typ: typ,
|
typ: typ,
|
||||||
inv: inv,
|
inv: inv,
|
||||||
text: []rune(text),
|
text: textRunes,
|
||||||
caseSensitive: caseSensitive,
|
caseSensitive: caseSensitive,
|
||||||
origText: origText})
|
origText: origText})
|
||||||
switchSet = true
|
switchSet = true
|
||||||
@ -309,9 +315,9 @@ func (p *Pattern) MatchItem(item *Item, withPos bool, slab *util.Slab) (*Result,
|
|||||||
func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset, int, int, *[]int) {
|
func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset, int, int, *[]int) {
|
||||||
input := p.prepareInput(item)
|
input := p.prepareInput(item)
|
||||||
if p.fuzzy {
|
if p.fuzzy {
|
||||||
return p.iter(p.fuzzyAlgo, input, p.caseSensitive, p.forward, p.text, withPos, slab)
|
return p.iter(p.fuzzyAlgo, input, p.caseSensitive, p.normalize, p.forward, p.text, withPos, slab)
|
||||||
}
|
}
|
||||||
return p.iter(algo.ExactMatchNaive, input, p.caseSensitive, p.forward, p.text, withPos, slab)
|
return p.iter(algo.ExactMatchNaive, input, p.caseSensitive, p.normalize, p.forward, p.text, withPos, slab)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Offset, int, int, *[]int) {
|
func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Offset, int, int, *[]int) {
|
||||||
@ -330,7 +336,7 @@ func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Of
|
|||||||
matched := false
|
matched := false
|
||||||
for _, term := range termSet {
|
for _, term := range termSet {
|
||||||
pfun := p.procFun[term.typ]
|
pfun := p.procFun[term.typ]
|
||||||
off, score, tLen, pos := p.iter(pfun, input, term.caseSensitive, p.forward, term.text, withPos, slab)
|
off, score, tLen, pos := p.iter(pfun, input, term.caseSensitive, p.normalize, p.forward, term.text, withPos, slab)
|
||||||
if sidx := off[0]; sidx >= 0 {
|
if sidx := off[0]; sidx >= 0 {
|
||||||
if term.inv {
|
if term.inv {
|
||||||
continue
|
continue
|
||||||
@ -378,9 +384,9 @@ func (p *Pattern) prepareInput(item *Item) []Token {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pattern) iter(pfun algo.Algo, tokens []Token, caseSensitive bool, forward bool, pattern []rune, withPos bool, slab *util.Slab) (Offset, int, int, *[]int) {
|
func (p *Pattern) iter(pfun algo.Algo, tokens []Token, caseSensitive bool, normalize bool, forward bool, pattern []rune, withPos bool, slab *util.Slab) (Offset, int, int, *[]int) {
|
||||||
for _, part := range tokens {
|
for _, part := range tokens {
|
||||||
if res, pos := pfun(caseSensitive, forward, *part.text, pattern, withPos, slab); res.Start >= 0 {
|
if res, pos := pfun(caseSensitive, normalize, forward, *part.text, pattern, withPos, slab); res.Start >= 0 {
|
||||||
sidx := int32(res.Start) + part.prefixLength
|
sidx := int32(res.Start) + part.prefixLength
|
||||||
eidx := int32(res.End) + part.prefixLength
|
eidx := int32(res.End) + part.prefixLength
|
||||||
if pos != nil {
|
if pos != nil {
|
||||||
|
@ -15,7 +15,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParseTermsExtended(t *testing.T) {
|
func TestParseTermsExtended(t *testing.T) {
|
||||||
terms := parseTerms(true, CaseSmart,
|
terms := parseTerms(true, CaseSmart, false,
|
||||||
"| aaa 'bbb ^ccc ddd$ !eee !'fff !^ggg !hhh$ | ^iii$ ^xxx | 'yyy | | zzz$ | !ZZZ |")
|
"| aaa 'bbb ^ccc ddd$ !eee !'fff !^ggg !hhh$ | ^iii$ ^xxx | 'yyy | | zzz$ | !ZZZ |")
|
||||||
if len(terms) != 9 ||
|
if len(terms) != 9 ||
|
||||||
terms[0][0].typ != termFuzzy || terms[0][0].inv ||
|
terms[0][0].typ != termFuzzy || terms[0][0].inv ||
|
||||||
@ -50,7 +50,7 @@ func TestParseTermsExtended(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParseTermsExtendedExact(t *testing.T) {
|
func TestParseTermsExtendedExact(t *testing.T) {
|
||||||
terms := parseTerms(false, CaseSmart,
|
terms := parseTerms(false, CaseSmart, false,
|
||||||
"aaa 'bbb ^ccc ddd$ !eee !'fff !^ggg !hhh$")
|
"aaa 'bbb ^ccc ddd$ !eee !'fff !^ggg !hhh$")
|
||||||
if len(terms) != 8 ||
|
if len(terms) != 8 ||
|
||||||
terms[0][0].typ != termExact || terms[0][0].inv || len(terms[0][0].text) != 3 ||
|
terms[0][0].typ != termExact || terms[0][0].inv || len(terms[0][0].text) != 3 ||
|
||||||
@ -66,7 +66,7 @@ func TestParseTermsExtendedExact(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParseTermsEmpty(t *testing.T) {
|
func TestParseTermsEmpty(t *testing.T) {
|
||||||
terms := parseTerms(true, CaseSmart, "' $ ^ !' !^ !$")
|
terms := parseTerms(true, CaseSmart, false, "' $ ^ !' !^ !$")
|
||||||
if len(terms) != 0 {
|
if len(terms) != 0 {
|
||||||
t.Errorf("%s", terms)
|
t.Errorf("%s", terms)
|
||||||
}
|
}
|
||||||
@ -75,10 +75,10 @@ func TestParseTermsEmpty(t *testing.T) {
|
|||||||
func TestExact(t *testing.T) {
|
func TestExact(t *testing.T) {
|
||||||
defer clearPatternCache()
|
defer clearPatternCache()
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, true, true,
|
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true,
|
||||||
[]Range{}, Delimiter{}, []rune("'abc"))
|
[]Range{}, Delimiter{}, []rune("'abc"))
|
||||||
res, pos := algo.ExactMatchNaive(
|
res, pos := algo.ExactMatchNaive(
|
||||||
pattern.caseSensitive, pattern.forward, util.RunesToChars([]rune("aabbcc abc")), pattern.termSets[0][0].text, true, nil)
|
pattern.caseSensitive, pattern.normalize, pattern.forward, util.RunesToChars([]rune("aabbcc abc")), pattern.termSets[0][0].text, true, nil)
|
||||||
if res.Start != 7 || res.End != 10 {
|
if res.Start != 7 || res.End != 10 {
|
||||||
t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End)
|
t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End)
|
||||||
}
|
}
|
||||||
@ -90,11 +90,11 @@ func TestExact(t *testing.T) {
|
|||||||
func TestEqual(t *testing.T) {
|
func TestEqual(t *testing.T) {
|
||||||
defer clearPatternCache()
|
defer clearPatternCache()
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, true, true, []Range{}, Delimiter{}, []rune("^AbC$"))
|
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("^AbC$"))
|
||||||
|
|
||||||
match := func(str string, sidxExpected int, eidxExpected int) {
|
match := func(str string, sidxExpected int, eidxExpected int) {
|
||||||
res, pos := algo.EqualMatch(
|
res, pos := algo.EqualMatch(
|
||||||
pattern.caseSensitive, pattern.forward, util.RunesToChars([]rune(str)), pattern.termSets[0][0].text, true, nil)
|
pattern.caseSensitive, pattern.normalize, pattern.forward, util.RunesToChars([]rune(str)), pattern.termSets[0][0].text, true, nil)
|
||||||
if res.Start != sidxExpected || res.End != eidxExpected {
|
if res.Start != sidxExpected || res.End != eidxExpected {
|
||||||
t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End)
|
t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End)
|
||||||
}
|
}
|
||||||
@ -109,17 +109,17 @@ func TestEqual(t *testing.T) {
|
|||||||
func TestCaseSensitivity(t *testing.T) {
|
func TestCaseSensitivity(t *testing.T) {
|
||||||
defer clearPatternCache()
|
defer clearPatternCache()
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pat1 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, true, true, []Range{}, Delimiter{}, []rune("abc"))
|
pat1 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("abc"))
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pat2 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, true, true, []Range{}, Delimiter{}, []rune("Abc"))
|
pat2 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("Abc"))
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pat3 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, true, true, []Range{}, Delimiter{}, []rune("abc"))
|
pat3 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, true, []Range{}, Delimiter{}, []rune("abc"))
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pat4 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, true, true, []Range{}, Delimiter{}, []rune("Abc"))
|
pat4 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, true, []Range{}, Delimiter{}, []rune("Abc"))
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pat5 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, true, true, []Range{}, Delimiter{}, []rune("abc"))
|
pat5 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, true, []Range{}, Delimiter{}, []rune("abc"))
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pat6 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, true, true, []Range{}, Delimiter{}, []rune("Abc"))
|
pat6 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, true, []Range{}, Delimiter{}, []rune("Abc"))
|
||||||
|
|
||||||
if string(pat1.text) != "abc" || pat1.caseSensitive != false ||
|
if string(pat1.text) != "abc" || pat1.caseSensitive != false ||
|
||||||
string(pat2.text) != "Abc" || pat2.caseSensitive != true ||
|
string(pat2.text) != "Abc" || pat2.caseSensitive != true ||
|
||||||
@ -132,7 +132,7 @@ func TestCaseSensitivity(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestOrigTextAndTransformed(t *testing.T) {
|
func TestOrigTextAndTransformed(t *testing.T) {
|
||||||
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, true, true, []Range{}, Delimiter{}, []rune("jg"))
|
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("jg"))
|
||||||
tokens := Tokenize(util.RunesToChars([]rune("junegunn")), Delimiter{})
|
tokens := Tokenize(util.RunesToChars([]rune("junegunn")), Delimiter{})
|
||||||
trans := Transform(tokens, []Range{Range{1, 1}})
|
trans := Transform(tokens, []Range{Range{1, 1}})
|
||||||
|
|
||||||
@ -167,7 +167,7 @@ func TestOrigTextAndTransformed(t *testing.T) {
|
|||||||
|
|
||||||
func TestCacheKey(t *testing.T) {
|
func TestCacheKey(t *testing.T) {
|
||||||
test := func(extended bool, patStr string, expected string, cacheable bool) {
|
test := func(extended bool, patStr string, expected string, cacheable bool) {
|
||||||
pat := BuildPattern(true, algo.FuzzyMatchV2, extended, CaseSmart, true, true, []Range{}, Delimiter{}, []rune(patStr))
|
pat := BuildPattern(true, algo.FuzzyMatchV2, extended, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune(patStr))
|
||||||
if pat.CacheKey() != expected {
|
if pat.CacheKey() != expected {
|
||||||
t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey())
|
t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey())
|
||||||
}
|
}
|
||||||
|
@ -166,7 +166,7 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
|
|||||||
}
|
}
|
||||||
colors = append(colors, colorOffset{
|
colors = append(colors, colorOffset{
|
||||||
offset: [2]int32{int32(start), int32(idx)},
|
offset: [2]int32{int32(start), int32(idx)},
|
||||||
color: tui.PairFor(fg, bg),
|
color: tui.NewColorPair(fg, bg),
|
||||||
attr: ansi.color.attr.Merge(attr)})
|
attr: ansi.color.attr.Merge(attr)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,7 +105,8 @@ func TestColorOffset(t *testing.T) {
|
|||||||
ansiOffset{[2]int32{33, 40}, ansiState{4, 8, tui.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, tui.Dark256, 99, 0, true)
|
pair := tui.NewColorPair(99, 199)
|
||||||
|
colors := item.colorOffsets(offsets, tui.Dark256, pair, tui.AttrRegular, true)
|
||||||
assert := func(idx int, b int32, e int32, c tui.ColorPair, bold bool) {
|
assert := func(idx int, b int32, e int32, c tui.ColorPair, bold bool) {
|
||||||
var attr tui.Attr
|
var attr tui.Attr
|
||||||
if bold {
|
if bold {
|
||||||
@ -116,10 +117,10 @@ func TestColorOffset(t *testing.T) {
|
|||||||
t.Error(o)
|
t.Error(o)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert(0, 0, 5, tui.ColUser, false)
|
assert(0, 0, 5, tui.NewColorPair(1, 5), false)
|
||||||
assert(1, 5, 15, 99, false)
|
assert(1, 5, 15, pair, false)
|
||||||
assert(2, 15, 20, tui.ColUser, false)
|
assert(2, 15, 20, tui.NewColorPair(1, 5), false)
|
||||||
assert(3, 22, 25, tui.ColUser+1, true)
|
assert(3, 22, 25, tui.NewColorPair(2, 6), true)
|
||||||
assert(4, 25, 35, 99, false)
|
assert(4, 25, 35, pair, false)
|
||||||
assert(5, 35, 40, tui.ColUser+2, true)
|
assert(5, 35, 40, tui.NewColorPair(4, 8), true)
|
||||||
}
|
}
|
||||||
|
321
src/terminal.go
321
src/terminal.go
@ -1,8 +1,10 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"regexp"
|
"regexp"
|
||||||
@ -15,8 +17,6 @@ import (
|
|||||||
|
|
||||||
"github.com/junegunn/fzf/src/tui"
|
"github.com/junegunn/fzf/src/tui"
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
|
||||||
"github.com/junegunn/go-runewidth"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// import "github.com/pkg/profile"
|
// import "github.com/pkg/profile"
|
||||||
@ -42,6 +42,16 @@ type previewer struct {
|
|||||||
enabled bool
|
enabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type itemLine struct {
|
||||||
|
current bool
|
||||||
|
selected bool
|
||||||
|
label string
|
||||||
|
width int
|
||||||
|
result Result
|
||||||
|
}
|
||||||
|
|
||||||
|
var emptyLine = itemLine{}
|
||||||
|
|
||||||
// Terminal represents terminal input/output
|
// Terminal represents terminal input/output
|
||||||
type Terminal struct {
|
type Terminal struct {
|
||||||
initDelay time.Duration
|
initDelay time.Duration
|
||||||
@ -50,6 +60,8 @@ type Terminal struct {
|
|||||||
reverse bool
|
reverse bool
|
||||||
hscroll bool
|
hscroll bool
|
||||||
hscrollOff int
|
hscrollOff int
|
||||||
|
wordRubout string
|
||||||
|
wordNext string
|
||||||
cx int
|
cx int
|
||||||
cy int
|
cy int
|
||||||
offset int
|
offset int
|
||||||
@ -69,11 +81,12 @@ type Terminal struct {
|
|||||||
header []string
|
header []string
|
||||||
header0 []string
|
header0 []string
|
||||||
ansi bool
|
ansi bool
|
||||||
|
tabstop int
|
||||||
margin [4]sizeSpec
|
margin [4]sizeSpec
|
||||||
strong tui.Attr
|
strong tui.Attr
|
||||||
window *tui.Window
|
window tui.Window
|
||||||
bwindow *tui.Window
|
bwindow tui.Window
|
||||||
pwindow *tui.Window
|
pwindow tui.Window
|
||||||
count int
|
count int
|
||||||
progress int
|
progress int
|
||||||
reading bool
|
reading bool
|
||||||
@ -89,10 +102,12 @@ type Terminal struct {
|
|||||||
eventBox *util.EventBox
|
eventBox *util.EventBox
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
initFunc func()
|
initFunc func()
|
||||||
|
prevLines []itemLine
|
||||||
suppress bool
|
suppress bool
|
||||||
startChan chan bool
|
startChan chan bool
|
||||||
slab *util.Slab
|
slab *util.Slab
|
||||||
theme *tui.ColorTheme
|
theme *tui.ColorTheme
|
||||||
|
tui tui.Renderer
|
||||||
}
|
}
|
||||||
|
|
||||||
type selectedItem struct {
|
type selectedItem struct {
|
||||||
@ -115,7 +130,6 @@ func (a byTimeOrder) Less(i, j int) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var _spinner = []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
|
var _spinner = []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
|
||||||
var _runeWidths = make(map[rune]int)
|
|
||||||
var _tabStop int
|
var _tabStop int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -247,7 +261,6 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
} else {
|
} else {
|
||||||
header = reverseStringArray(opts.Header)
|
header = reverseStringArray(opts.Header)
|
||||||
}
|
}
|
||||||
_tabStop = opts.Tabstop
|
|
||||||
var delay time.Duration
|
var delay time.Duration
|
||||||
if opts.Tac {
|
if opts.Tac {
|
||||||
delay = initialDelayTac
|
delay = initialDelayTac
|
||||||
@ -262,6 +275,32 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
if !opts.Bold {
|
if !opts.Bold {
|
||||||
strongAttr = tui.AttrRegular
|
strongAttr = tui.AttrRegular
|
||||||
}
|
}
|
||||||
|
var renderer tui.Renderer
|
||||||
|
if opts.Height.size > 0 {
|
||||||
|
maxHeightFunc := func(termHeight int) int {
|
||||||
|
var maxHeight int
|
||||||
|
if opts.Height.percent {
|
||||||
|
maxHeight = util.Min(termHeight,
|
||||||
|
util.Max(int(opts.Height.size*float64(termHeight)/100.0), opts.MinHeight))
|
||||||
|
} else {
|
||||||
|
maxHeight = util.Min(termHeight, int(opts.Height.size))
|
||||||
|
}
|
||||||
|
if opts.InlineInfo {
|
||||||
|
return util.Max(maxHeight, minHeight-1)
|
||||||
|
}
|
||||||
|
return util.Max(maxHeight, minHeight)
|
||||||
|
}
|
||||||
|
renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, maxHeightFunc)
|
||||||
|
} else {
|
||||||
|
renderer = tui.NewFullscreenRenderer(opts.Theme, opts.Black, opts.Mouse)
|
||||||
|
}
|
||||||
|
wordRubout := "[^[:alnum:]][[:alnum:]]"
|
||||||
|
wordNext := "[[:alnum:]][^[:alnum:]]|(.$)"
|
||||||
|
if opts.FileWord {
|
||||||
|
sep := regexp.QuoteMeta(string(os.PathSeparator))
|
||||||
|
wordRubout = fmt.Sprintf("%s[^%s]", sep, sep)
|
||||||
|
wordNext = fmt.Sprintf("[^%s]%s|(.$)", sep, sep)
|
||||||
|
}
|
||||||
return &Terminal{
|
return &Terminal{
|
||||||
initDelay: delay,
|
initDelay: delay,
|
||||||
inlineInfo: opts.InlineInfo,
|
inlineInfo: opts.InlineInfo,
|
||||||
@ -269,6 +308,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
reverse: opts.Reverse,
|
reverse: opts.Reverse,
|
||||||
hscroll: opts.Hscroll,
|
hscroll: opts.Hscroll,
|
||||||
hscrollOff: opts.HscrollOff,
|
hscrollOff: opts.HscrollOff,
|
||||||
|
wordRubout: wordRubout,
|
||||||
|
wordNext: wordNext,
|
||||||
cx: len(input),
|
cx: len(input),
|
||||||
cy: 0,
|
cy: 0,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
@ -290,6 +331,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
header: header,
|
header: header,
|
||||||
header0: header,
|
header0: header,
|
||||||
ansi: opts.Ansi,
|
ansi: opts.Ansi,
|
||||||
|
tabstop: opts.Tabstop,
|
||||||
reading: true,
|
reading: true,
|
||||||
jumping: jumpDisabled,
|
jumping: jumpDisabled,
|
||||||
jumpLabels: opts.JumpLabels,
|
jumpLabels: opts.JumpLabels,
|
||||||
@ -306,9 +348,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
slab: util.MakeSlab(slab16Size, slab32Size),
|
slab: util.MakeSlab(slab16Size, slab32Size),
|
||||||
theme: opts.Theme,
|
theme: opts.Theme,
|
||||||
startChan: make(chan bool, 1),
|
startChan: make(chan bool, 1),
|
||||||
initFunc: func() {
|
tui: renderer,
|
||||||
tui.Init(opts.Theme, opts.Black, opts.Mouse)
|
initFunc: func() { renderer.Init() }}
|
||||||
}}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Input returns current query string
|
// Input returns current query string
|
||||||
@ -401,22 +442,10 @@ func (t *Terminal) sortSelected() []selectedItem {
|
|||||||
return sels
|
return sels
|
||||||
}
|
}
|
||||||
|
|
||||||
func runeWidth(r rune, prefixWidth int) int {
|
func (t *Terminal) displayWidth(runes []rune) int {
|
||||||
if r == '\t' {
|
|
||||||
return _tabStop - prefixWidth%_tabStop
|
|
||||||
} else if w, found := _runeWidths[r]; found {
|
|
||||||
return w
|
|
||||||
} else {
|
|
||||||
w := runewidth.RuneWidth(r)
|
|
||||||
_runeWidths[r] = w
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func displayWidth(runes []rune) int {
|
|
||||||
l := 0
|
l := 0
|
||||||
for _, r := range runes {
|
for _, r := range runes {
|
||||||
l += runeWidth(r, l)
|
l += util.RuneWidth(r, l, t.tabstop)
|
||||||
}
|
}
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
@ -437,9 +466,10 @@ func calculateSize(base int, size sizeSpec, margin int, minSize int) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) resizeWindows() {
|
func (t *Terminal) resizeWindows() {
|
||||||
screenWidth := tui.MaxX()
|
screenWidth := t.tui.MaxX()
|
||||||
screenHeight := tui.MaxY()
|
screenHeight := t.tui.MaxY()
|
||||||
marginInt := [4]int{}
|
marginInt := [4]int{}
|
||||||
|
t.prevLines = make([]itemLine, screenHeight)
|
||||||
for idx, sizeSpec := range t.margin {
|
for idx, sizeSpec := range t.margin {
|
||||||
if sizeSpec.percent {
|
if sizeSpec.percent {
|
||||||
var max float64
|
var max float64
|
||||||
@ -487,40 +517,40 @@ 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 = tui.NewWindow(y, x, w, h, true)
|
t.bwindow = t.tui.NewWindow(y, x, w, h, true)
|
||||||
pwidth := w - 4
|
pwidth := w - 4
|
||||||
// ncurses auto-wraps the line when the cursor reaches the right-end of
|
// ncurses auto-wraps the line when the cursor reaches the right-end of
|
||||||
// the window. To prevent unintended line-wraps, we use the width one
|
// the window. To prevent unintended line-wraps, we use the width one
|
||||||
// column larger than the desired value.
|
// column larger than the desired value.
|
||||||
if !t.preview.wrap && tui.DoesAutoWrap() {
|
if !t.preview.wrap && t.tui.DoesAutoWrap() {
|
||||||
pwidth += 1
|
pwidth += 1
|
||||||
}
|
}
|
||||||
t.pwindow = tui.NewWindow(y+1, x+2, pwidth, h-2, false)
|
t.pwindow = t.tui.NewWindow(y+1, x+2, pwidth, 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 = tui.NewWindow(
|
t.window = t.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 = tui.NewWindow(
|
t.window = t.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 = tui.NewWindow(
|
t.window = t.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 = tui.NewWindow(
|
t.window = t.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 = tui.NewWindow(
|
t.window = t.tui.NewWindow(
|
||||||
marginInt[0],
|
marginInt[0],
|
||||||
marginInt[3],
|
marginInt[3],
|
||||||
width,
|
width,
|
||||||
@ -530,7 +560,7 @@ func (t *Terminal) resizeWindows() {
|
|||||||
|
|
||||||
func (t *Terminal) move(y int, x int, clear bool) {
|
func (t *Terminal) move(y int, x int, clear bool) {
|
||||||
if !t.reverse {
|
if !t.reverse {
|
||||||
y = t.window.Height - y - 1
|
y = t.window.Height() - y - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if clear {
|
if clear {
|
||||||
@ -541,7 +571,7 @@ func (t *Terminal) move(y int, x int, clear bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) placeCursor() {
|
func (t *Terminal) placeCursor() {
|
||||||
t.move(0, displayWidth([]rune(t.prompt))+displayWidth(t.input[:t.cx]), false)
|
t.move(0, t.displayWidth([]rune(t.prompt))+t.displayWidth(t.input[:t.cx]), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) printPrompt() {
|
func (t *Terminal) printPrompt() {
|
||||||
@ -552,7 +582,7 @@ func (t *Terminal) printPrompt() {
|
|||||||
|
|
||||||
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, t.displayWidth([]rune(t.prompt))+t.displayWidth(t.input)+1, true)
|
||||||
if t.reading {
|
if t.reading {
|
||||||
t.window.CPrint(tui.ColSpinner, t.strong, " < ")
|
t.window.CPrint(tui.ColSpinner, t.strong, " < ")
|
||||||
} else {
|
} else {
|
||||||
@ -589,7 +619,7 @@ func (t *Terminal) printHeader() {
|
|||||||
if len(t.header) == 0 {
|
if len(t.header) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
max := t.window.Height
|
max := t.window.Height()
|
||||||
var state *ansiState
|
var state *ansiState
|
||||||
for idx, lineStr := range t.header {
|
for idx, lineStr := range t.header {
|
||||||
line := idx + 2
|
line := idx + 2
|
||||||
@ -607,7 +637,7 @@ func (t *Terminal) printHeader() {
|
|||||||
|
|
||||||
t.move(line, 2, true)
|
t.move(line, 2, true)
|
||||||
t.printHighlighted(&Result{item: item},
|
t.printHighlighted(&Result{item: item},
|
||||||
tui.AttrRegular, tui.ColHeader, tui.ColDefault, false)
|
tui.AttrRegular, tui.ColHeader, tui.ColDefault, false, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -616,19 +646,25 @@ func (t *Terminal) printList() {
|
|||||||
|
|
||||||
maxy := t.maxItems()
|
maxy := t.maxItems()
|
||||||
count := t.merger.Length() - t.offset
|
count := t.merger.Length() - t.offset
|
||||||
for i := 0; i < maxy; i++ {
|
for j := 0; j < maxy; j++ {
|
||||||
|
i := j
|
||||||
|
if !t.reverse {
|
||||||
|
i = maxy - 1 - j
|
||||||
|
}
|
||||||
line := i + 2 + len(t.header)
|
line := i + 2 + len(t.header)
|
||||||
if t.inlineInfo {
|
if t.inlineInfo {
|
||||||
line--
|
line--
|
||||||
}
|
}
|
||||||
t.move(line, 0, true)
|
|
||||||
if i < count {
|
if i < count {
|
||||||
t.printItem(t.merger.Get(i+t.offset), i, i == t.cy-t.offset)
|
t.printItem(t.merger.Get(i+t.offset), line, i, i == t.cy-t.offset)
|
||||||
|
} else if t.prevLines[i] != emptyLine {
|
||||||
|
t.prevLines[i] = emptyLine
|
||||||
|
t.move(line, 0, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) printItem(result *Result, i int, current bool) {
|
func (t *Terminal) printItem(result *Result, line int, i int, current bool) {
|
||||||
item := result.item
|
item := result.item
|
||||||
_, selected := t.selected[item.Index()]
|
_, selected := t.selected[item.Index()]
|
||||||
label := " "
|
label := " "
|
||||||
@ -641,6 +677,19 @@ func (t *Terminal) printItem(result *Result, i int, current bool) {
|
|||||||
} else if current {
|
} else if current {
|
||||||
label = ">"
|
label = ">"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Avoid unnecessary redraw
|
||||||
|
newLine := itemLine{current: current, selected: selected, label: label, result: *result, width: 0}
|
||||||
|
prevLine := t.prevLines[i]
|
||||||
|
if prevLine.current == newLine.current &&
|
||||||
|
prevLine.selected == newLine.selected &&
|
||||||
|
prevLine.label == newLine.label &&
|
||||||
|
prevLine.result == newLine.result {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optimized renderer can simply erase to the end of the window
|
||||||
|
t.move(line, 0, t.tui.IsOptimized())
|
||||||
t.window.CPrint(tui.ColCursor, t.strong, label)
|
t.window.CPrint(tui.ColCursor, t.strong, label)
|
||||||
if current {
|
if current {
|
||||||
if selected {
|
if selected {
|
||||||
@ -648,22 +697,29 @@ func (t *Terminal) printItem(result *Result, i int, current bool) {
|
|||||||
} else {
|
} else {
|
||||||
t.window.CPrint(tui.ColCurrent, t.strong, " ")
|
t.window.CPrint(tui.ColCurrent, t.strong, " ")
|
||||||
}
|
}
|
||||||
t.printHighlighted(result, t.strong, tui.ColCurrent, tui.ColCurrentMatch, true)
|
newLine.width = t.printHighlighted(result, t.strong, tui.ColCurrent, tui.ColCurrentMatch, true, true)
|
||||||
} else {
|
} else {
|
||||||
if selected {
|
if selected {
|
||||||
t.window.CPrint(tui.ColSelected, t.strong, ">")
|
t.window.CPrint(tui.ColSelected, t.strong, ">")
|
||||||
} else {
|
} else {
|
||||||
t.window.Print(" ")
|
t.window.Print(" ")
|
||||||
}
|
}
|
||||||
t.printHighlighted(result, 0, tui.ColNormal, tui.ColMatch, false)
|
newLine.width = t.printHighlighted(result, 0, tui.ColNormal, tui.ColMatch, false, true)
|
||||||
}
|
}
|
||||||
|
if !t.tui.IsOptimized() {
|
||||||
|
fillSpaces := prevLine.width - newLine.width
|
||||||
|
if fillSpaces > 0 {
|
||||||
|
t.window.Print(strings.Repeat(" ", fillSpaces))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.prevLines[i] = newLine
|
||||||
}
|
}
|
||||||
|
|
||||||
func trimRight(runes []rune, width int) ([]rune, int) {
|
func (t *Terminal) trimRight(runes []rune, width int) ([]rune, int) {
|
||||||
// We start from the beginning to handle tab characters
|
// We start from the beginning to handle tab characters
|
||||||
l := 0
|
l := 0
|
||||||
for idx, r := range runes {
|
for idx, r := range runes {
|
||||||
l += runeWidth(r, l)
|
l += util.RuneWidth(r, l, t.tabstop)
|
||||||
if l > width {
|
if l > width {
|
||||||
return runes[:idx], len(runes) - idx
|
return runes[:idx], len(runes) - idx
|
||||||
}
|
}
|
||||||
@ -671,10 +727,10 @@ func trimRight(runes []rune, width int) ([]rune, int) {
|
|||||||
return runes, 0
|
return runes, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func displayWidthWithLimit(runes []rune, prefixWidth int, limit int) int {
|
func (t *Terminal) displayWidthWithLimit(runes []rune, prefixWidth int, limit int) int {
|
||||||
l := 0
|
l := 0
|
||||||
for _, r := range runes {
|
for _, r := range runes {
|
||||||
l += runeWidth(r, l+prefixWidth)
|
l += util.RuneWidth(r, l+prefixWidth, t.tabstop)
|
||||||
if l > limit {
|
if l > limit {
|
||||||
// Early exit
|
// Early exit
|
||||||
return l
|
return l
|
||||||
@ -683,35 +739,28 @@ func displayWidthWithLimit(runes []rune, prefixWidth int, limit int) int {
|
|||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
func trimLeft(runes []rune, width int) ([]rune, int32) {
|
func (t *Terminal) trimLeft(runes []rune, width int) ([]rune, int32) {
|
||||||
if len(runes) > maxDisplayWidthCalc && len(runes) > width {
|
if len(runes) > maxDisplayWidthCalc && len(runes) > width {
|
||||||
trimmed := len(runes) - width
|
trimmed := len(runes) - width
|
||||||
return runes[trimmed:], int32(trimmed)
|
return runes[trimmed:], int32(trimmed)
|
||||||
}
|
}
|
||||||
|
|
||||||
currentWidth := displayWidth(runes)
|
currentWidth := t.displayWidth(runes)
|
||||||
var trimmed int32
|
var trimmed int32
|
||||||
|
|
||||||
for currentWidth > width && len(runes) > 0 {
|
for currentWidth > width && len(runes) > 0 {
|
||||||
runes = runes[1:]
|
runes = runes[1:]
|
||||||
trimmed++
|
trimmed++
|
||||||
currentWidth = displayWidthWithLimit(runes, 2, width)
|
currentWidth = t.displayWidthWithLimit(runes, 2, width)
|
||||||
}
|
}
|
||||||
return runes, trimmed
|
return runes, trimmed
|
||||||
}
|
}
|
||||||
|
|
||||||
func overflow(runes []rune, max int) bool {
|
func (t *Terminal) overflow(runes []rune, max int) bool {
|
||||||
l := 0
|
return t.displayWidthWithLimit(runes, 0, max) > max
|
||||||
for _, r := range runes {
|
|
||||||
l += runeWidth(r, l)
|
|
||||||
if l > max {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) printHighlighted(result *Result, attr tui.Attr, col1 tui.ColorPair, col2 tui.ColorPair, current bool) {
|
func (t *Terminal) printHighlighted(result *Result, attr tui.Attr, col1 tui.ColorPair, col2 tui.ColorPair, current bool, match bool) int {
|
||||||
item := result.item
|
item := result.item
|
||||||
|
|
||||||
// Overflow
|
// Overflow
|
||||||
@ -719,7 +768,7 @@ func (t *Terminal) printHighlighted(result *Result, attr tui.Attr, col1 tui.Colo
|
|||||||
copy(text, item.text.ToRunes())
|
copy(text, item.text.ToRunes())
|
||||||
matchOffsets := []Offset{}
|
matchOffsets := []Offset{}
|
||||||
var pos *[]int
|
var pos *[]int
|
||||||
if t.merger.pattern != nil {
|
if match && t.merger.pattern != nil {
|
||||||
_, matchOffsets, pos = t.merger.pattern.MatchItem(item, true, t.slab)
|
_, matchOffsets, pos = t.merger.pattern.MatchItem(item, true, t.slab)
|
||||||
}
|
}
|
||||||
charOffsets := matchOffsets
|
charOffsets := matchOffsets
|
||||||
@ -737,22 +786,23 @@ func (t *Terminal) printHighlighted(result *Result, attr tui.Attr, col1 tui.Colo
|
|||||||
}
|
}
|
||||||
|
|
||||||
offsets := result.colorOffsets(charOffsets, t.theme, 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) {
|
displayWidth := t.displayWidthWithLimit(text, 0, maxWidth)
|
||||||
|
if displayWidth > maxWidth {
|
||||||
if t.hscroll {
|
if t.hscroll {
|
||||||
// Stri..
|
// Stri..
|
||||||
if !overflow(text[:maxe], maxWidth-2) {
|
if !t.overflow(text[:maxe], maxWidth-2) {
|
||||||
text, _ = trimRight(text, maxWidth-2)
|
text, _ = t.trimRight(text, maxWidth-2)
|
||||||
text = append(text, []rune("..")...)
|
text = append(text, []rune("..")...)
|
||||||
} else {
|
} else {
|
||||||
// Stri..
|
// Stri..
|
||||||
if overflow(text[maxe:], 2) {
|
if t.overflow(text[maxe:], 2) {
|
||||||
text = append(text[:maxe], []rune("..")...)
|
text = append(text[:maxe], []rune("..")...)
|
||||||
}
|
}
|
||||||
// ..ri..
|
// ..ri..
|
||||||
var diff int32
|
var diff int32
|
||||||
text, diff = trimLeft(text, maxWidth-2)
|
text, diff = t.trimLeft(text, maxWidth-2)
|
||||||
|
|
||||||
// Transform offsets
|
// Transform offsets
|
||||||
for idx, offset := range offsets {
|
for idx, offset := range offsets {
|
||||||
@ -766,7 +816,7 @@ func (t *Terminal) printHighlighted(result *Result, attr tui.Attr, col1 tui.Colo
|
|||||||
text = append([]rune(".."), text...)
|
text = append([]rune(".."), text...)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
text, _ = trimRight(text, maxWidth-2)
|
text, _ = t.trimRight(text, maxWidth-2)
|
||||||
text = append(text, []rune("..")...)
|
text = append(text, []rune("..")...)
|
||||||
|
|
||||||
for idx, offset := range offsets {
|
for idx, offset := range offsets {
|
||||||
@ -784,11 +834,11 @@ func (t *Terminal) printHighlighted(result *Result, attr tui.Attr, col1 tui.Colo
|
|||||||
b := util.Constrain32(offset.offset[0], index, maxOffset)
|
b := util.Constrain32(offset.offset[0], index, maxOffset)
|
||||||
e := util.Constrain32(offset.offset[1], index, maxOffset)
|
e := util.Constrain32(offset.offset[1], index, maxOffset)
|
||||||
|
|
||||||
substr, prefixWidth = processTabs(text[index:b], prefixWidth)
|
substr, prefixWidth = t.processTabs(text[index:b], prefixWidth)
|
||||||
t.window.CPrint(col1, attr, substr)
|
t.window.CPrint(col1, attr, substr)
|
||||||
|
|
||||||
if b < e {
|
if b < e {
|
||||||
substr, prefixWidth = processTabs(text[b:e], prefixWidth)
|
substr, prefixWidth = t.processTabs(text[b:e], prefixWidth)
|
||||||
t.window.CPrint(offset.color, offset.attr, substr)
|
t.window.CPrint(offset.color, offset.attr, substr)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -798,9 +848,10 @@ func (t *Terminal) printHighlighted(result *Result, attr tui.Attr, col1 tui.Colo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if index < maxOffset {
|
if index < maxOffset {
|
||||||
substr, _ = processTabs(text[index:], prefixWidth)
|
substr, _ = t.processTabs(text[index:], prefixWidth)
|
||||||
t.window.CPrint(col1, attr, substr)
|
t.window.CPrint(col1, attr, substr)
|
||||||
}
|
}
|
||||||
|
return displayWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
func numLinesMax(str string, max int) int {
|
func numLinesMax(str string, max int) int {
|
||||||
@ -821,52 +872,66 @@ func (t *Terminal) printPreview() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
t.pwindow.Erase()
|
t.pwindow.Erase()
|
||||||
skip := t.previewer.offset
|
|
||||||
extractColor(t.previewer.text, nil, func(str string, ansi *ansiState) bool {
|
maxWidth := t.pwindow.Width()
|
||||||
if skip > 0 {
|
if t.tui.DoesAutoWrap() {
|
||||||
newlines := numLinesMax(str, skip)
|
maxWidth -= 1
|
||||||
if skip <= newlines {
|
}
|
||||||
for i := 0; i < skip; i++ {
|
reader := bufio.NewReader(strings.NewReader(t.previewer.text))
|
||||||
str = str[strings.Index(str, "\n")+1:]
|
lineNo := -t.previewer.offset
|
||||||
|
for {
|
||||||
|
line, err := reader.ReadString('\n')
|
||||||
|
eof := err == io.EOF
|
||||||
|
if !eof {
|
||||||
|
line = line[:len(line)-1]
|
||||||
|
}
|
||||||
|
lineNo++
|
||||||
|
if lineNo > t.pwindow.Height() {
|
||||||
|
break
|
||||||
|
} else if lineNo > 0 {
|
||||||
|
var fillRet tui.FillReturn
|
||||||
|
extractColor(line, nil, func(str string, ansi *ansiState) bool {
|
||||||
|
trimmed := []rune(str)
|
||||||
|
if !t.preview.wrap {
|
||||||
|
trimmed, _ = t.trimRight(trimmed, maxWidth-t.pwindow.X())
|
||||||
}
|
}
|
||||||
skip = 0
|
str, _ = t.processTabs(trimmed, 0)
|
||||||
} else {
|
if ansi != nil && ansi.colored() {
|
||||||
skip -= newlines
|
fillRet = t.pwindow.CFill(ansi.fg, ansi.bg, ansi.attr, str)
|
||||||
return true
|
} else {
|
||||||
|
fillRet = t.pwindow.Fill(str)
|
||||||
|
}
|
||||||
|
return fillRet == tui.FillContinue
|
||||||
|
})
|
||||||
|
switch fillRet {
|
||||||
|
case tui.FillNextLine:
|
||||||
|
continue
|
||||||
|
case tui.FillSuspend:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
t.pwindow.Fill("\n")
|
||||||
}
|
}
|
||||||
if !t.preview.wrap {
|
if eof {
|
||||||
lines := strings.Split(str, "\n")
|
break
|
||||||
for i, line := range lines {
|
|
||||||
limit := t.pwindow.Width
|
|
||||||
if tui.DoesAutoWrap() {
|
|
||||||
limit -= 1
|
|
||||||
}
|
|
||||||
if i == 0 {
|
|
||||||
limit -= t.pwindow.X()
|
|
||||||
}
|
|
||||||
trimmed, _ := trimRight([]rune(line), limit)
|
|
||||||
lines[i], _ = processTabs(trimmed, 0)
|
|
||||||
}
|
|
||||||
str = strings.Join(lines, "\n")
|
|
||||||
}
|
}
|
||||||
if ansi != nil && ansi.colored() {
|
}
|
||||||
return t.pwindow.CFill(str, ansi.fg, ansi.bg, ansi.attr)
|
t.pwindow.FinishFill()
|
||||||
}
|
if t.previewer.lines > t.pwindow.Height() {
|
||||||
return t.pwindow.Fill(str)
|
|
||||||
})
|
|
||||||
if t.previewer.lines > t.pwindow.Height {
|
|
||||||
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))
|
pos := t.pwindow.Width() - len(offset)
|
||||||
|
if t.tui.DoesAutoWrap() {
|
||||||
|
pos -= 1
|
||||||
|
}
|
||||||
|
t.pwindow.Move(0, pos)
|
||||||
t.pwindow.CPrint(tui.ColInfo, tui.Reverse, offset)
|
t.pwindow.CPrint(tui.ColInfo, tui.Reverse, offset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func processTabs(runes []rune, prefixWidth int) (string, int) {
|
func (t *Terminal) processTabs(runes []rune, prefixWidth int) (string, int) {
|
||||||
var strbuf bytes.Buffer
|
var strbuf bytes.Buffer
|
||||||
l := prefixWidth
|
l := prefixWidth
|
||||||
for _, r := range runes {
|
for _, r := range runes {
|
||||||
w := runeWidth(r, l)
|
w := util.RuneWidth(r, l, t.tabstop)
|
||||||
l += w
|
l += w
|
||||||
if r == '\t' {
|
if r == '\t' {
|
||||||
strbuf.WriteString(strings.Repeat(" ", w))
|
strbuf.WriteString(strings.Repeat(" ", w))
|
||||||
@ -889,9 +954,9 @@ func (t *Terminal) printAll() {
|
|||||||
func (t *Terminal) refresh() {
|
func (t *Terminal) refresh() {
|
||||||
if !t.suppress {
|
if !t.suppress {
|
||||||
if t.isPreviewEnabled() {
|
if t.isPreviewEnabled() {
|
||||||
tui.RefreshWindows([]*tui.Window{t.bwindow, t.pwindow, t.window})
|
t.tui.RefreshWindows([]tui.Window{t.bwindow, t.pwindow, t.window})
|
||||||
} else {
|
} else {
|
||||||
tui.RefreshWindows([]*tui.Window{t.window})
|
t.tui.RefreshWindows([]tui.Window{t.window})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1013,9 +1078,9 @@ 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
|
||||||
tui.Pause()
|
t.tui.Pause()
|
||||||
cmd.Run()
|
cmd.Run()
|
||||||
if tui.Resume() {
|
if t.tui.Resume() {
|
||||||
t.printAll()
|
t.printAll()
|
||||||
}
|
}
|
||||||
t.refresh()
|
t.refresh()
|
||||||
@ -1162,11 +1227,11 @@ func (t *Terminal) Loop() {
|
|||||||
case reqRefresh:
|
case reqRefresh:
|
||||||
t.suppress = false
|
t.suppress = false
|
||||||
case reqRedraw:
|
case reqRedraw:
|
||||||
tui.Clear()
|
t.tui.Clear()
|
||||||
tui.Refresh()
|
t.tui.Refresh()
|
||||||
t.printAll()
|
t.printAll()
|
||||||
case reqClose:
|
case reqClose:
|
||||||
tui.Close()
|
t.tui.Close()
|
||||||
if t.output() {
|
if t.output() {
|
||||||
exit(exitOk)
|
exit(exitOk)
|
||||||
}
|
}
|
||||||
@ -1179,11 +1244,11 @@ func (t *Terminal) Loop() {
|
|||||||
case reqPreviewRefresh:
|
case reqPreviewRefresh:
|
||||||
t.printPreview()
|
t.printPreview()
|
||||||
case reqPrintQuery:
|
case reqPrintQuery:
|
||||||
tui.Close()
|
t.tui.Close()
|
||||||
t.printer(string(t.input))
|
t.printer(string(t.input))
|
||||||
exit(exitOk)
|
exit(exitOk)
|
||||||
case reqQuit:
|
case reqQuit:
|
||||||
tui.Close()
|
t.tui.Close()
|
||||||
exit(exitInterrupt)
|
exit(exitInterrupt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1196,7 +1261,7 @@ func (t *Terminal) Loop() {
|
|||||||
|
|
||||||
looping := true
|
looping := true
|
||||||
for looping {
|
for looping {
|
||||||
event := tui.GetChar()
|
event := t.tui.GetChar()
|
||||||
|
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
previousInput := t.input
|
previousInput := t.input
|
||||||
@ -1288,11 +1353,11 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
case actPreviewPageUp:
|
case actPreviewPageUp:
|
||||||
if t.isPreviewEnabled() {
|
if t.isPreviewEnabled() {
|
||||||
scrollPreview(-t.pwindow.Height)
|
scrollPreview(-t.pwindow.Height())
|
||||||
}
|
}
|
||||||
case actPreviewPageDown:
|
case actPreviewPageDown:
|
||||||
if t.isPreviewEnabled() {
|
if t.isPreviewEnabled() {
|
||||||
scrollPreview(t.pwindow.Height)
|
scrollPreview(t.pwindow.Height())
|
||||||
}
|
}
|
||||||
case actBeginningOfLine:
|
case actBeginningOfLine:
|
||||||
t.cx = 0
|
t.cx = 0
|
||||||
@ -1401,7 +1466,7 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
case actBackwardKillWord:
|
case actBackwardKillWord:
|
||||||
if t.cx > 0 {
|
if t.cx > 0 {
|
||||||
t.rubout("[^[:alnum:]][[:alnum:]]")
|
t.rubout(t.wordRubout)
|
||||||
}
|
}
|
||||||
case actYank:
|
case actYank:
|
||||||
suffix := copySlice(t.input[t.cx:])
|
suffix := copySlice(t.input[t.cx:])
|
||||||
@ -1420,12 +1485,12 @@ func (t *Terminal) Loop() {
|
|||||||
t.jumping = jumpAcceptEnabled
|
t.jumping = jumpAcceptEnabled
|
||||||
req(reqJump)
|
req(reqJump)
|
||||||
case actBackwardWord:
|
case actBackwardWord:
|
||||||
t.cx = findLastMatch("[^[:alnum:]][[:alnum:]]", string(t.input[:t.cx])) + 1
|
t.cx = findLastMatch(t.wordRubout, string(t.input[:t.cx])) + 1
|
||||||
case actForwardWord:
|
case actForwardWord:
|
||||||
t.cx += findFirstMatch("[[:alnum:]][^[:alnum:]]|(.$)", string(t.input[t.cx:])) + 1
|
t.cx += findFirstMatch(t.wordNext, string(t.input[t.cx:])) + 1
|
||||||
case actKillWord:
|
case actKillWord:
|
||||||
ncx := t.cx +
|
ncx := t.cx +
|
||||||
findFirstMatch("[[:alnum:]][^[:alnum:]]|(.$)", string(t.input[t.cx:])) + 1
|
findFirstMatch(t.wordNext, string(t.input[t.cx:])) + 1
|
||||||
if ncx > t.cx {
|
if ncx > t.cx {
|
||||||
t.yanked = copySlice(t.input[t.cx:ncx])
|
t.yanked = copySlice(t.input[t.cx:ncx])
|
||||||
t.input = append(t.input[:t.cx], t.input[ncx:]...)
|
t.input = append(t.input[:t.cx], t.input[ncx:]...)
|
||||||
@ -1466,11 +1531,11 @@ func (t *Terminal) Loop() {
|
|||||||
scrollPreview(-me.S)
|
scrollPreview(-me.S)
|
||||||
}
|
}
|
||||||
} else if t.window.Enclose(my, mx) {
|
} else if t.window.Enclose(my, mx) {
|
||||||
mx -= t.window.Left
|
mx -= t.window.Left()
|
||||||
my -= t.window.Top
|
my -= t.window.Top()
|
||||||
mx = util.Constrain(mx-displayWidth([]rune(t.prompt)), 0, len(t.input))
|
mx = util.Constrain(mx-t.displayWidth([]rune(t.prompt)), 0, len(t.input))
|
||||||
if !t.reverse {
|
if !t.reverse {
|
||||||
my = t.window.Height - my - 1
|
my = t.window.Height() - my - 1
|
||||||
}
|
}
|
||||||
min := 2 + len(t.header)
|
min := 2 + len(t.header)
|
||||||
if t.inlineInfo {
|
if t.inlineInfo {
|
||||||
@ -1582,7 +1647,7 @@ func (t *Terminal) vset(o int) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) maxItems() int {
|
func (t *Terminal) maxItems() int {
|
||||||
max := t.window.Height - 2 - len(t.header)
|
max := t.window.Height() - 2 - len(t.header)
|
||||||
if t.inlineInfo {
|
if t.inlineInfo {
|
||||||
max++
|
max++
|
||||||
}
|
}
|
||||||
|
820
src/tui/light.go
Normal file
820
src/tui/light.go
Normal file
@ -0,0 +1,820 @@
|
|||||||
|
package tui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultWidth = 80
|
||||||
|
defaultHeight = 24
|
||||||
|
|
||||||
|
escPollInterval = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
const consoleDevice string = "/dev/tty"
|
||||||
|
|
||||||
|
func openTtyIn() *os.File {
|
||||||
|
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
panic("Failed to open " + consoleDevice)
|
||||||
|
}
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) stderr(str string) {
|
||||||
|
r.stderrInternal(str, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Need better handling of non-displayable characters
|
||||||
|
func (r *LightRenderer) stderrInternal(str string, allowNLCR bool) {
|
||||||
|
bytes := []byte(str)
|
||||||
|
runes := []rune{}
|
||||||
|
for len(bytes) > 0 {
|
||||||
|
r, sz := utf8.DecodeRune(bytes)
|
||||||
|
if r == utf8.RuneError || r < 32 &&
|
||||||
|
r != '\x1b' && (!allowNLCR || r != '\n' && r != '\r') {
|
||||||
|
runes = append(runes, '?')
|
||||||
|
} else {
|
||||||
|
runes = append(runes, r)
|
||||||
|
}
|
||||||
|
bytes = bytes[sz:]
|
||||||
|
}
|
||||||
|
r.queued += string(runes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) csi(code string) {
|
||||||
|
r.stderr("\x1b[" + code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) flush() {
|
||||||
|
if len(r.queued) > 0 {
|
||||||
|
fmt.Fprint(os.Stderr, r.queued)
|
||||||
|
r.queued = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Light renderer
|
||||||
|
type LightRenderer struct {
|
||||||
|
theme *ColorTheme
|
||||||
|
mouse bool
|
||||||
|
forceBlack bool
|
||||||
|
prevDownTime time.Time
|
||||||
|
clickY []int
|
||||||
|
ttyin *os.File
|
||||||
|
buffer []byte
|
||||||
|
origState *terminal.State
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
yoffset int
|
||||||
|
tabstop int
|
||||||
|
escDelay int
|
||||||
|
upOneLine bool
|
||||||
|
queued string
|
||||||
|
y int
|
||||||
|
x int
|
||||||
|
maxHeightFunc func(int) int
|
||||||
|
}
|
||||||
|
|
||||||
|
type LightWindow struct {
|
||||||
|
renderer *LightRenderer
|
||||||
|
colored bool
|
||||||
|
border bool
|
||||||
|
top int
|
||||||
|
left int
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
posx int
|
||||||
|
posy int
|
||||||
|
tabstop int
|
||||||
|
bg Color
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLightRenderer(theme *ColorTheme, forceBlack bool, mouse bool, tabstop int, maxHeightFunc func(int) int) Renderer {
|
||||||
|
r := LightRenderer{
|
||||||
|
theme: theme,
|
||||||
|
forceBlack: forceBlack,
|
||||||
|
mouse: mouse,
|
||||||
|
ttyin: openTtyIn(),
|
||||||
|
yoffset: -1,
|
||||||
|
tabstop: tabstop,
|
||||||
|
upOneLine: false,
|
||||||
|
maxHeightFunc: maxHeightFunc}
|
||||||
|
return &r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) fd() int {
|
||||||
|
return int(r.ttyin.Fd())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) defaultTheme() *ColorTheme {
|
||||||
|
if strings.Contains(os.Getenv("TERM"), "256") {
|
||||||
|
return Dark256
|
||||||
|
}
|
||||||
|
colors, err := exec.Command("tput", "colors").Output()
|
||||||
|
if err == nil && atoi(strings.TrimSpace(string(colors)), 16) > 16 {
|
||||||
|
return Dark256
|
||||||
|
}
|
||||||
|
return Default16
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) findOffset() (row int, col int) {
|
||||||
|
r.csi("6n")
|
||||||
|
r.flush()
|
||||||
|
bytes := r.getBytesInternal([]byte{})
|
||||||
|
|
||||||
|
// ^[[*;*R
|
||||||
|
if len(bytes) > 5 && bytes[0] == 27 && bytes[1] == 91 && bytes[len(bytes)-1] == 'R' {
|
||||||
|
nums := strings.Split(string(bytes[2:len(bytes)-1]), ";")
|
||||||
|
if len(nums) == 2 {
|
||||||
|
return atoi(nums[0], 0) - 1, atoi(nums[1], 0) - 1
|
||||||
|
}
|
||||||
|
return -1, -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// No idea
|
||||||
|
return -1, -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func repeat(s string, times int) string {
|
||||||
|
if times > 0 {
|
||||||
|
return strings.Repeat(s, times)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func atoi(s string, defaultValue int) int {
|
||||||
|
value, err := strconv.Atoi(s)
|
||||||
|
if err != nil {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) Init() {
|
||||||
|
delay := 100
|
||||||
|
delayEnv := os.Getenv("ESCDELAY")
|
||||||
|
if len(delayEnv) > 0 {
|
||||||
|
num, err := strconv.Atoi(delayEnv)
|
||||||
|
if err == nil && num >= 0 {
|
||||||
|
delay = num
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.escDelay = delay
|
||||||
|
|
||||||
|
fd := r.fd()
|
||||||
|
origState, err := terminal.GetState(fd)
|
||||||
|
if err != nil {
|
||||||
|
errorExit(err.Error())
|
||||||
|
}
|
||||||
|
r.origState = origState
|
||||||
|
terminal.MakeRaw(fd)
|
||||||
|
r.updateTerminalSize()
|
||||||
|
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
||||||
|
|
||||||
|
_, x := r.findOffset()
|
||||||
|
if x > 0 {
|
||||||
|
r.upOneLine = true
|
||||||
|
r.stderr("\n")
|
||||||
|
}
|
||||||
|
for i := 1; i < r.MaxY(); i++ {
|
||||||
|
r.stderr("\n")
|
||||||
|
r.csi("G")
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.mouse {
|
||||||
|
r.csi("?1000h")
|
||||||
|
}
|
||||||
|
r.csi(fmt.Sprintf("%dA", r.MaxY()-1))
|
||||||
|
r.csi("G")
|
||||||
|
// r.csi("s")
|
||||||
|
if r.mouse {
|
||||||
|
r.yoffset, _ = r.findOffset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) move(y int, x int) {
|
||||||
|
// w.csi("u")
|
||||||
|
if r.y < y {
|
||||||
|
r.csi(fmt.Sprintf("%dB", y-r.y))
|
||||||
|
} else if r.y > y {
|
||||||
|
r.csi(fmt.Sprintf("%dA", r.y-y))
|
||||||
|
}
|
||||||
|
r.stderr("\r")
|
||||||
|
if x > 0 {
|
||||||
|
r.csi(fmt.Sprintf("%dC", x))
|
||||||
|
}
|
||||||
|
r.y = y
|
||||||
|
r.x = x
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) origin() {
|
||||||
|
r.move(0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEnv(name string, defaultValue int) int {
|
||||||
|
env := os.Getenv(name)
|
||||||
|
if len(env) == 0 {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
return atoi(env, defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) updateTerminalSize() {
|
||||||
|
width, height, err := terminal.GetSize(r.fd())
|
||||||
|
if err == nil {
|
||||||
|
r.width = width
|
||||||
|
r.height = r.maxHeightFunc(height)
|
||||||
|
} else {
|
||||||
|
r.width = getEnv("COLUMNS", defaultWidth)
|
||||||
|
r.height = r.maxHeightFunc(getEnv("LINES", defaultHeight))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) getch(nonblock bool) (int, bool) {
|
||||||
|
b := make([]byte, 1)
|
||||||
|
util.SetNonblock(r.ttyin, nonblock)
|
||||||
|
_, err := r.ttyin.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return int(b[0]), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) getBytes() []byte {
|
||||||
|
return r.getBytesInternal(r.buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) getBytesInternal(buffer []byte) []byte {
|
||||||
|
c, ok := r.getch(false)
|
||||||
|
if !ok {
|
||||||
|
r.Close()
|
||||||
|
errorExit("Failed to read " + consoleDevice)
|
||||||
|
}
|
||||||
|
|
||||||
|
retries := 0
|
||||||
|
if c == ESC {
|
||||||
|
retries = r.escDelay / escPollInterval
|
||||||
|
}
|
||||||
|
buffer = append(buffer, byte(c))
|
||||||
|
|
||||||
|
for {
|
||||||
|
c, ok = r.getch(true)
|
||||||
|
if !ok {
|
||||||
|
if retries > 0 {
|
||||||
|
retries--
|
||||||
|
time.Sleep(escPollInterval * time.Millisecond)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
retries = 0
|
||||||
|
buffer = append(buffer, byte(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) GetChar() Event {
|
||||||
|
if len(r.buffer) == 0 {
|
||||||
|
r.buffer = r.getBytes()
|
||||||
|
}
|
||||||
|
if len(r.buffer) == 0 {
|
||||||
|
panic("Empty buffer")
|
||||||
|
}
|
||||||
|
|
||||||
|
sz := 1
|
||||||
|
defer func() {
|
||||||
|
r.buffer = r.buffer[sz:]
|
||||||
|
}()
|
||||||
|
|
||||||
|
switch r.buffer[0] {
|
||||||
|
case CtrlC:
|
||||||
|
return Event{CtrlC, 0, nil}
|
||||||
|
case CtrlG:
|
||||||
|
return Event{CtrlG, 0, nil}
|
||||||
|
case CtrlQ:
|
||||||
|
return Event{CtrlQ, 0, nil}
|
||||||
|
case 127:
|
||||||
|
return Event{BSpace, 0, nil}
|
||||||
|
case ESC:
|
||||||
|
ev := r.escSequence(&sz)
|
||||||
|
// Second chance
|
||||||
|
if ev.Type == Invalid {
|
||||||
|
r.buffer = r.getBytes()
|
||||||
|
ev = r.escSequence(&sz)
|
||||||
|
}
|
||||||
|
return ev
|
||||||
|
}
|
||||||
|
|
||||||
|
// CTRL-A ~ CTRL-Z
|
||||||
|
if r.buffer[0] <= CtrlZ {
|
||||||
|
return Event{int(r.buffer[0]), 0, nil}
|
||||||
|
}
|
||||||
|
char, rsz := utf8.DecodeRune(r.buffer)
|
||||||
|
if char == utf8.RuneError {
|
||||||
|
return Event{ESC, 0, nil}
|
||||||
|
}
|
||||||
|
sz = rsz
|
||||||
|
return Event{Rune, char, nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) escSequence(sz *int) Event {
|
||||||
|
if len(r.buffer) < 2 {
|
||||||
|
return Event{ESC, 0, nil}
|
||||||
|
}
|
||||||
|
*sz = 2
|
||||||
|
switch r.buffer[1] {
|
||||||
|
case 13:
|
||||||
|
return Event{AltEnter, 0, nil}
|
||||||
|
case 32:
|
||||||
|
return Event{AltSpace, 0, nil}
|
||||||
|
case 47:
|
||||||
|
return Event{AltSlash, 0, nil}
|
||||||
|
case 98:
|
||||||
|
return Event{AltB, 0, nil}
|
||||||
|
case 100:
|
||||||
|
return Event{AltD, 0, nil}
|
||||||
|
case 102:
|
||||||
|
return Event{AltF, 0, nil}
|
||||||
|
case 127:
|
||||||
|
return Event{AltBS, 0, nil}
|
||||||
|
case 91, 79:
|
||||||
|
if len(r.buffer) < 3 {
|
||||||
|
return Event{Invalid, 0, nil}
|
||||||
|
}
|
||||||
|
*sz = 3
|
||||||
|
switch r.buffer[2] {
|
||||||
|
case 68:
|
||||||
|
return Event{Left, 0, nil}
|
||||||
|
case 67:
|
||||||
|
return Event{Right, 0, nil}
|
||||||
|
case 66:
|
||||||
|
return Event{Down, 0, nil}
|
||||||
|
case 65:
|
||||||
|
return Event{Up, 0, nil}
|
||||||
|
case 90:
|
||||||
|
return Event{BTab, 0, nil}
|
||||||
|
case 72:
|
||||||
|
return Event{Home, 0, nil}
|
||||||
|
case 70:
|
||||||
|
return Event{End, 0, nil}
|
||||||
|
case 77:
|
||||||
|
return r.mouseSequence(sz)
|
||||||
|
case 80:
|
||||||
|
return Event{F1, 0, nil}
|
||||||
|
case 81:
|
||||||
|
return Event{F2, 0, nil}
|
||||||
|
case 82:
|
||||||
|
return Event{F3, 0, nil}
|
||||||
|
case 83:
|
||||||
|
return Event{F4, 0, nil}
|
||||||
|
case 49, 50, 51, 52, 53, 54:
|
||||||
|
if len(r.buffer) < 4 {
|
||||||
|
return Event{Invalid, 0, nil}
|
||||||
|
}
|
||||||
|
*sz = 4
|
||||||
|
switch r.buffer[2] {
|
||||||
|
case 50:
|
||||||
|
if len(r.buffer) == 5 && r.buffer[4] == 126 {
|
||||||
|
*sz = 5
|
||||||
|
switch r.buffer[3] {
|
||||||
|
case 48:
|
||||||
|
return Event{F9, 0, nil}
|
||||||
|
case 49:
|
||||||
|
return Event{F10, 0, nil}
|
||||||
|
case 51:
|
||||||
|
return Event{F11, 0, nil}
|
||||||
|
case 52:
|
||||||
|
return Event{F12, 0, nil}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Bracketed paste mode \e[200~ / \e[201
|
||||||
|
if r.buffer[3] == 48 && (r.buffer[4] == 48 || r.buffer[4] == 49) && r.buffer[5] == 126 {
|
||||||
|
*sz = 6
|
||||||
|
return Event{Invalid, 0, nil}
|
||||||
|
}
|
||||||
|
return Event{Invalid, 0, nil} // INS
|
||||||
|
case 51:
|
||||||
|
return Event{Del, 0, nil}
|
||||||
|
case 52:
|
||||||
|
return Event{End, 0, nil}
|
||||||
|
case 53:
|
||||||
|
return Event{PgUp, 0, nil}
|
||||||
|
case 54:
|
||||||
|
return Event{PgDn, 0, nil}
|
||||||
|
case 49:
|
||||||
|
switch r.buffer[3] {
|
||||||
|
case 126:
|
||||||
|
return Event{Home, 0, nil}
|
||||||
|
case 53, 55, 56, 57:
|
||||||
|
if len(r.buffer) == 5 && r.buffer[4] == 126 {
|
||||||
|
*sz = 5
|
||||||
|
switch r.buffer[3] {
|
||||||
|
case 53:
|
||||||
|
return Event{F5, 0, nil}
|
||||||
|
case 55:
|
||||||
|
return Event{F6, 0, nil}
|
||||||
|
case 56:
|
||||||
|
return Event{F7, 0, nil}
|
||||||
|
case 57:
|
||||||
|
return Event{F8, 0, nil}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Event{Invalid, 0, nil}
|
||||||
|
case 59:
|
||||||
|
if len(r.buffer) != 6 {
|
||||||
|
return Event{Invalid, 0, nil}
|
||||||
|
}
|
||||||
|
*sz = 6
|
||||||
|
switch r.buffer[4] {
|
||||||
|
case 50:
|
||||||
|
switch r.buffer[5] {
|
||||||
|
case 68:
|
||||||
|
return Event{Home, 0, nil}
|
||||||
|
case 67:
|
||||||
|
return Event{End, 0, nil}
|
||||||
|
}
|
||||||
|
case 53:
|
||||||
|
switch r.buffer[5] {
|
||||||
|
case 68:
|
||||||
|
return Event{SLeft, 0, nil}
|
||||||
|
case 67:
|
||||||
|
return Event{SRight, 0, nil}
|
||||||
|
}
|
||||||
|
} // r.buffer[4]
|
||||||
|
} // r.buffer[3]
|
||||||
|
} // r.buffer[2]
|
||||||
|
} // r.buffer[2]
|
||||||
|
} // r.buffer[1]
|
||||||
|
if r.buffer[1] >= 'a' && r.buffer[1] <= 'z' {
|
||||||
|
return Event{AltA + int(r.buffer[1]) - 'a', 0, nil}
|
||||||
|
}
|
||||||
|
return Event{Invalid, 0, nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) mouseSequence(sz *int) Event {
|
||||||
|
if len(r.buffer) < 6 || r.yoffset < 0 {
|
||||||
|
return Event{Invalid, 0, nil}
|
||||||
|
}
|
||||||
|
*sz = 6
|
||||||
|
switch r.buffer[3] {
|
||||||
|
case 32, 36, 40, 48, // mouse-down / shift / cmd / ctrl
|
||||||
|
35, 39, 43, 51: // mouse-up / shift / cmd / ctrl
|
||||||
|
mod := r.buffer[3] >= 36
|
||||||
|
down := r.buffer[3]%2 == 0
|
||||||
|
x := int(r.buffer[4] - 33)
|
||||||
|
y := int(r.buffer[5]-33) - r.yoffset
|
||||||
|
double := false
|
||||||
|
if down {
|
||||||
|
now := time.Now()
|
||||||
|
if now.Sub(r.prevDownTime) < doubleClickDuration {
|
||||||
|
r.clickY = append(r.clickY, y)
|
||||||
|
} else {
|
||||||
|
r.clickY = []int{y}
|
||||||
|
}
|
||||||
|
r.prevDownTime = now
|
||||||
|
} else {
|
||||||
|
if len(r.clickY) > 1 && r.clickY[0] == r.clickY[1] &&
|
||||||
|
time.Now().Sub(r.prevDownTime) < doubleClickDuration {
|
||||||
|
double = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Event{Mouse, 0, &MouseEvent{y, x, 0, down, double, mod}}
|
||||||
|
case 96, 100, 104, 112, // scroll-up / shift / cmd / ctrl
|
||||||
|
97, 101, 105, 113: // scroll-down / shift / cmd / ctrl
|
||||||
|
mod := r.buffer[3] >= 100
|
||||||
|
s := 1 - int(r.buffer[3]%2)*2
|
||||||
|
x := int(r.buffer[4] - 33)
|
||||||
|
y := int(r.buffer[5]-33) - r.yoffset
|
||||||
|
return Event{Mouse, 0, &MouseEvent{y, x, s, false, false, mod}}
|
||||||
|
}
|
||||||
|
return Event{Invalid, 0, nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) Pause() {
|
||||||
|
terminal.Restore(r.fd(), r.origState)
|
||||||
|
r.csi("?1049h")
|
||||||
|
r.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) Resume() bool {
|
||||||
|
terminal.MakeRaw(r.fd())
|
||||||
|
r.csi("?1049l")
|
||||||
|
r.flush()
|
||||||
|
// Should redraw
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) Clear() {
|
||||||
|
// r.csi("u")
|
||||||
|
r.origin()
|
||||||
|
r.csi("J")
|
||||||
|
r.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) RefreshWindows(windows []Window) {
|
||||||
|
r.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) Refresh() {
|
||||||
|
r.updateTerminalSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) Close() {
|
||||||
|
// r.csi("u")
|
||||||
|
r.origin()
|
||||||
|
r.csi("J")
|
||||||
|
if r.mouse {
|
||||||
|
r.csi("?1000l")
|
||||||
|
}
|
||||||
|
if r.upOneLine {
|
||||||
|
r.csi("A")
|
||||||
|
}
|
||||||
|
r.flush()
|
||||||
|
terminal.Restore(r.fd(), r.origState)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) MaxX() int {
|
||||||
|
return r.width
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) MaxY() int {
|
||||||
|
return r.height
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) DoesAutoWrap() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) IsOptimized() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) NewWindow(top int, left int, width int, height int, border bool) Window {
|
||||||
|
w := &LightWindow{
|
||||||
|
renderer: r,
|
||||||
|
colored: r.theme != nil,
|
||||||
|
border: border,
|
||||||
|
top: top,
|
||||||
|
left: left,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
tabstop: r.tabstop,
|
||||||
|
bg: colDefault}
|
||||||
|
if r.theme != nil {
|
||||||
|
w.bg = r.theme.Bg
|
||||||
|
}
|
||||||
|
if w.border {
|
||||||
|
w.drawBorder()
|
||||||
|
}
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) drawBorder() {
|
||||||
|
w.Move(0, 0)
|
||||||
|
w.CPrint(ColBorder, AttrRegular, "┌"+repeat("─", w.width-2)+"┐")
|
||||||
|
for y := 1; y < w.height-1; y++ {
|
||||||
|
w.Move(y, 0)
|
||||||
|
w.CPrint(ColBorder, AttrRegular, "│")
|
||||||
|
w.cprint2(colDefault, w.bg, AttrRegular, repeat(" ", w.width-2))
|
||||||
|
w.CPrint(ColBorder, AttrRegular, "│")
|
||||||
|
}
|
||||||
|
w.Move(w.height-1, 0)
|
||||||
|
w.CPrint(ColBorder, AttrRegular, "└"+repeat("─", w.width-2)+"┘")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) csi(code string) {
|
||||||
|
w.renderer.csi(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) stderr(str string) {
|
||||||
|
w.renderer.stderr(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) stderrInternal(str string, allowNLCR bool) {
|
||||||
|
w.renderer.stderrInternal(str, allowNLCR)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) Top() int {
|
||||||
|
return w.top
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) Left() int {
|
||||||
|
return w.left
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) Width() int {
|
||||||
|
return w.width
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) Height() int {
|
||||||
|
return w.height
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) Refresh() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) Close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) X() int {
|
||||||
|
return w.posx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) 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 *LightWindow) Move(y int, x int) {
|
||||||
|
w.posx = x
|
||||||
|
w.posy = y
|
||||||
|
|
||||||
|
w.renderer.move(w.Top()+y, w.Left()+x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) MoveAndClear(y int, x int) {
|
||||||
|
w.Move(y, x)
|
||||||
|
// We should not delete preview window on the right
|
||||||
|
// csi("K")
|
||||||
|
w.Print(repeat(" ", w.width-x))
|
||||||
|
w.Move(y, x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func attrCodes(attr Attr) []string {
|
||||||
|
codes := []string{}
|
||||||
|
if (attr & Bold) > 0 {
|
||||||
|
codes = append(codes, "1")
|
||||||
|
}
|
||||||
|
if (attr & Dim) > 0 {
|
||||||
|
codes = append(codes, "2")
|
||||||
|
}
|
||||||
|
if (attr & Italic) > 0 {
|
||||||
|
codes = append(codes, "3")
|
||||||
|
}
|
||||||
|
if (attr & Underline) > 0 {
|
||||||
|
codes = append(codes, "4")
|
||||||
|
}
|
||||||
|
if (attr & Blink) > 0 {
|
||||||
|
codes = append(codes, "5")
|
||||||
|
}
|
||||||
|
if (attr & Reverse) > 0 {
|
||||||
|
codes = append(codes, "7")
|
||||||
|
}
|
||||||
|
return codes
|
||||||
|
}
|
||||||
|
|
||||||
|
func colorCodes(fg Color, bg Color) []string {
|
||||||
|
codes := []string{}
|
||||||
|
appendCode := func(c Color, offset int) {
|
||||||
|
if c == colDefault {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c.is24() {
|
||||||
|
r := (c >> 16) & 0xff
|
||||||
|
g := (c >> 8) & 0xff
|
||||||
|
b := (c) & 0xff
|
||||||
|
codes = append(codes, fmt.Sprintf("%d;2;%d;%d;%d", 38+offset, r, g, b))
|
||||||
|
} else if c >= colBlack && c <= colWhite {
|
||||||
|
codes = append(codes, fmt.Sprintf("%d", int(c)+30+offset))
|
||||||
|
} else if c > colWhite && c < 16 {
|
||||||
|
codes = append(codes, fmt.Sprintf("%d", int(c)+90+offset-8))
|
||||||
|
} else if c >= 16 && c < 256 {
|
||||||
|
codes = append(codes, fmt.Sprintf("%d;5;%d", 38+offset, c))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
appendCode(fg, 0)
|
||||||
|
appendCode(bg, 10)
|
||||||
|
return codes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) csiColor(fg Color, bg Color, attr Attr) bool {
|
||||||
|
codes := append(attrCodes(attr), colorCodes(fg, bg)...)
|
||||||
|
w.csi(";" + strings.Join(codes, ";") + "m")
|
||||||
|
return len(codes) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) Print(text string) {
|
||||||
|
w.cprint2(colDefault, w.bg, AttrRegular, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) CPrint(pair ColorPair, attr Attr, text string) {
|
||||||
|
if !w.colored {
|
||||||
|
w.csiColor(colDefault, colDefault, attrFor(pair, attr))
|
||||||
|
} else {
|
||||||
|
w.csiColor(pair.Fg(), pair.Bg(), attr)
|
||||||
|
}
|
||||||
|
w.stderrInternal(text, false)
|
||||||
|
w.csi("m")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) cprint2(fg Color, bg Color, attr Attr, text string) {
|
||||||
|
if w.csiColor(fg, bg, attr) {
|
||||||
|
defer w.csi("m")
|
||||||
|
}
|
||||||
|
w.stderrInternal(text, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
type wrappedLine struct {
|
||||||
|
text string
|
||||||
|
displayWidth int
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapLine(input string, prefixLength int, max int, tabstop int) []wrappedLine {
|
||||||
|
lines := []wrappedLine{}
|
||||||
|
width := 0
|
||||||
|
line := ""
|
||||||
|
for _, r := range input {
|
||||||
|
w := util.Max(util.RuneWidth(r, prefixLength+width, 8), 1)
|
||||||
|
width += w
|
||||||
|
str := string(r)
|
||||||
|
if r == '\t' {
|
||||||
|
str = repeat(" ", w)
|
||||||
|
}
|
||||||
|
if prefixLength+width <= max {
|
||||||
|
line += str
|
||||||
|
} else {
|
||||||
|
lines = append(lines, wrappedLine{string(line), width - w})
|
||||||
|
line = str
|
||||||
|
prefixLength = 0
|
||||||
|
width = util.RuneWidth(r, prefixLength, 8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lines = append(lines, wrappedLine{string(line), width})
|
||||||
|
return lines
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) fill(str string, onMove func()) FillReturn {
|
||||||
|
allLines := strings.Split(str, "\n")
|
||||||
|
for i, line := range allLines {
|
||||||
|
lines := wrapLine(line, w.posx, w.width, w.tabstop)
|
||||||
|
for j, wl := range lines {
|
||||||
|
if w.posx >= w.Width()-1 && wl.displayWidth == 0 {
|
||||||
|
if w.posy < w.height-1 {
|
||||||
|
w.MoveAndClear(w.posy+1, 0)
|
||||||
|
}
|
||||||
|
return FillNextLine
|
||||||
|
}
|
||||||
|
w.stderrInternal(wl.text, false)
|
||||||
|
w.posx += wl.displayWidth
|
||||||
|
if j < len(lines)-1 || i < len(allLines)-1 {
|
||||||
|
if w.posy+1 >= w.height {
|
||||||
|
return FillSuspend
|
||||||
|
}
|
||||||
|
w.MoveAndClear(w.posy+1, 0)
|
||||||
|
onMove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FillContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) setBg() {
|
||||||
|
if w.bg != colDefault {
|
||||||
|
w.csiColor(colDefault, w.bg, AttrRegular)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) Fill(text string) FillReturn {
|
||||||
|
w.MoveAndClear(w.posy, w.posx)
|
||||||
|
w.setBg()
|
||||||
|
return w.fill(text, w.setBg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) CFill(fg Color, bg Color, attr Attr, text string) FillReturn {
|
||||||
|
w.MoveAndClear(w.posy, w.posx)
|
||||||
|
if bg == colDefault {
|
||||||
|
bg = w.bg
|
||||||
|
}
|
||||||
|
if w.csiColor(fg, bg, attr) {
|
||||||
|
return w.fill(text, func() { w.csiColor(fg, bg, attr) })
|
||||||
|
defer w.csi("m")
|
||||||
|
}
|
||||||
|
return w.fill(text, w.setBg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) FinishFill() {
|
||||||
|
for y := w.posy + 1; y < w.height; y++ {
|
||||||
|
w.MoveAndClear(y, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) Erase() {
|
||||||
|
if w.border {
|
||||||
|
w.drawBorder()
|
||||||
|
}
|
||||||
|
// We don't erase the window here to avoid flickering during scroll
|
||||||
|
w.Move(0, 0)
|
||||||
|
}
|
@ -25,7 +25,6 @@ int c_getcurx(WINDOW* win) {
|
|||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -33,9 +32,39 @@ import (
|
|||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ColorPair int16
|
|
||||||
type Attr C.uint
|
type Attr C.uint
|
||||||
type WindowImpl C.WINDOW
|
|
||||||
|
type CursesWindow struct {
|
||||||
|
impl *C.WINDOW
|
||||||
|
top int
|
||||||
|
left int
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *CursesWindow) Top() int {
|
||||||
|
return w.top
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *CursesWindow) Left() int {
|
||||||
|
return w.left
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *CursesWindow) Width() int {
|
||||||
|
return w.width
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *CursesWindow) Height() int {
|
||||||
|
return w.height
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *CursesWindow) Refresh() {
|
||||||
|
C.wnoutrefresh(w.impl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *CursesWindow) FinishFill() {
|
||||||
|
// NO-OP
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Bold Attr = C.A_BOLD
|
Bold Attr = C.A_BOLD
|
||||||
@ -51,31 +80,14 @@ const (
|
|||||||
AttrRegular Attr = 0
|
AttrRegular Attr = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
// Pallete
|
|
||||||
const (
|
|
||||||
ColDefault ColorPair = iota
|
|
||||||
ColNormal
|
|
||||||
ColPrompt
|
|
||||||
ColMatch
|
|
||||||
ColCurrent
|
|
||||||
ColCurrentMatch
|
|
||||||
ColSpinner
|
|
||||||
ColInfo
|
|
||||||
ColCursor
|
|
||||||
ColSelected
|
|
||||||
ColHeader
|
|
||||||
ColBorder
|
|
||||||
ColUser // Should be the last entry
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_screen *C.SCREEN
|
_screen *C.SCREEN
|
||||||
_colorMap map[int]ColorPair
|
_colorMap map[int]int16
|
||||||
_colorFn func(ColorPair, Attr) (C.short, C.int)
|
_colorFn func(ColorPair, Attr) (C.short, C.int)
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
_colorMap = make(map[int]ColorPair)
|
_colorMap = make(map[int]int16)
|
||||||
if strings.HasPrefix(C.GoString(C.curses_version()), "ncurses 5") {
|
if strings.HasPrefix(C.GoString(C.curses_version()), "ncurses 5") {
|
||||||
Italic = C.A_NORMAL
|
Italic = C.A_NORMAL
|
||||||
}
|
}
|
||||||
@ -85,27 +97,25 @@ func (a Attr) Merge(b Attr) Attr {
|
|||||||
return a | b
|
return a | b
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultTheme() *ColorTheme {
|
func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
|
||||||
if C.tigetnum(C.CString("colors")) >= 256 {
|
if C.tigetnum(C.CString("colors")) >= 256 {
|
||||||
return Dark256
|
return Dark256
|
||||||
}
|
}
|
||||||
return Default16
|
return Default16
|
||||||
}
|
}
|
||||||
|
|
||||||
func Init(theme *ColorTheme, black bool, mouse bool) {
|
func (r *FullscreenRenderer) Init() {
|
||||||
C.setlocale(C.LC_ALL, C.CString(""))
|
C.setlocale(C.LC_ALL, C.CString(""))
|
||||||
tty := C.c_tty()
|
tty := C.c_tty()
|
||||||
if tty == nil {
|
if tty == nil {
|
||||||
fmt.Println("Failed to open /dev/tty")
|
errorExit("Failed to open /dev/tty")
|
||||||
os.Exit(2)
|
|
||||||
}
|
}
|
||||||
_screen = C.c_newterm(tty)
|
_screen = C.c_newterm(tty)
|
||||||
if _screen == nil {
|
if _screen == nil {
|
||||||
fmt.Println("Invalid $TERM: " + os.Getenv("TERM"))
|
errorExit("Invalid $TERM: " + os.Getenv("TERM"))
|
||||||
os.Exit(2)
|
|
||||||
}
|
}
|
||||||
C.set_term(_screen)
|
C.set_term(_screen)
|
||||||
if mouse {
|
if r.mouse {
|
||||||
C.mousemask(C.ALL_MOUSE_EVENTS, nil)
|
C.mousemask(C.ALL_MOUSE_EVENTS, nil)
|
||||||
C.mouseinterval(0)
|
C.mouseinterval(0)
|
||||||
}
|
}
|
||||||
@ -124,14 +134,14 @@ func Init(theme *ColorTheme, black bool, mouse bool) {
|
|||||||
}
|
}
|
||||||
C.set_escdelay(C.int(delay))
|
C.set_escdelay(C.int(delay))
|
||||||
|
|
||||||
_color = theme != nil
|
if r.theme != nil {
|
||||||
if _color {
|
|
||||||
C.start_color()
|
C.start_color()
|
||||||
InitTheme(theme, black)
|
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
||||||
initPairs(theme)
|
initPairs(r.theme)
|
||||||
C.bkgd(C.chtype(C.COLOR_PAIR(C.int(ColNormal))))
|
C.bkgd(C.chtype(C.COLOR_PAIR(C.int(ColNormal.index()))))
|
||||||
_colorFn = attrColored
|
_colorFn = attrColored
|
||||||
} else {
|
} else {
|
||||||
|
initTheme(r.theme, nil, r.forceBlack)
|
||||||
_colorFn = attrMono
|
_colorFn = attrMono
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,39 +155,39 @@ func Init(theme *ColorTheme, black bool, mouse bool) {
|
|||||||
|
|
||||||
func initPairs(theme *ColorTheme) {
|
func initPairs(theme *ColorTheme) {
|
||||||
C.assume_default_colors(C.int(theme.Fg), C.int(theme.Bg))
|
C.assume_default_colors(C.int(theme.Fg), C.int(theme.Bg))
|
||||||
initPair := func(group ColorPair, fg Color, bg Color) {
|
for _, pair := range []ColorPair{
|
||||||
C.init_pair(C.short(group), C.short(fg), C.short(bg))
|
ColNormal,
|
||||||
|
ColPrompt,
|
||||||
|
ColMatch,
|
||||||
|
ColCurrent,
|
||||||
|
ColCurrentMatch,
|
||||||
|
ColSpinner,
|
||||||
|
ColInfo,
|
||||||
|
ColCursor,
|
||||||
|
ColSelected,
|
||||||
|
ColHeader,
|
||||||
|
ColBorder} {
|
||||||
|
C.init_pair(C.short(pair.index()), C.short(pair.Fg()), C.short(pair.Bg()))
|
||||||
}
|
}
|
||||||
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 Pause() {
|
func (r *FullscreenRenderer) Pause() {
|
||||||
C.endwin()
|
C.endwin()
|
||||||
}
|
}
|
||||||
|
|
||||||
func Resume() bool {
|
func (r *FullscreenRenderer) Resume() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func Close() {
|
func (r *FullscreenRenderer) Close() {
|
||||||
C.endwin()
|
C.endwin()
|
||||||
C.delscreen(_screen)
|
C.delscreen(_screen)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWindow(top int, left int, width int, height int, border bool) *Window {
|
func (r *FullscreenRenderer) 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))
|
win := C.newwin(C.int(height), C.int(width), C.int(top), C.int(left))
|
||||||
if _color {
|
if r.theme != nil {
|
||||||
C.wbkgd(win, C.chtype(C.COLOR_PAIR(C.int(ColNormal))))
|
C.wbkgd(win, C.chtype(C.COLOR_PAIR(C.int(ColNormal.index()))))
|
||||||
}
|
}
|
||||||
if border {
|
if border {
|
||||||
pair, attr := _colorFn(ColBorder, 0)
|
pair, attr := _colorFn(ColBorder, 0)
|
||||||
@ -188,66 +198,50 @@ func NewWindow(top int, left int, width int, height int, border bool) *Window {
|
|||||||
C.wcolor_set(win, 0, nil)
|
C.wcolor_set(win, 0, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Window{
|
return &CursesWindow{
|
||||||
impl: (*WindowImpl)(win),
|
impl: win,
|
||||||
Top: top,
|
top: top,
|
||||||
Left: left,
|
left: left,
|
||||||
Width: width,
|
width: width,
|
||||||
Height: height,
|
height: height,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func attrColored(pair ColorPair, a Attr) (C.short, C.int) {
|
func attrColored(color ColorPair, a Attr) (C.short, C.int) {
|
||||||
return C.short(pair), C.int(a)
|
return C.short(color.index()), C.int(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
func attrMono(pair ColorPair, a Attr) (C.short, C.int) {
|
func attrMono(color ColorPair, a Attr) (C.short, C.int) {
|
||||||
var attr C.int
|
return 0, C.int(attrFor(color, a))
|
||||||
switch pair {
|
|
||||||
case ColCurrent:
|
|
||||||
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 0, attr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func MaxX() int {
|
func (r *FullscreenRenderer) MaxX() int {
|
||||||
return int(C.COLS)
|
return int(C.COLS)
|
||||||
}
|
}
|
||||||
|
|
||||||
func MaxY() int {
|
func (r *FullscreenRenderer) MaxY() int {
|
||||||
return int(C.LINES)
|
return int(C.LINES)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) win() *C.WINDOW {
|
func (w *CursesWindow) Close() {
|
||||||
return (*C.WINDOW)(w.impl)
|
C.delwin(w.impl)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) Close() {
|
func (w *CursesWindow) Enclose(y int, x int) bool {
|
||||||
C.delwin(w.win())
|
return bool(C.wenclose(w.impl, C.int(y), C.int(x)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) Enclose(y int, x int) bool {
|
func (w *CursesWindow) Move(y int, x int) {
|
||||||
return bool(C.wenclose(w.win(), C.int(y), C.int(x)))
|
C.wmove(w.impl, C.int(y), C.int(x))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) Move(y int, x int) {
|
func (w *CursesWindow) MoveAndClear(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)
|
w.Move(y, x)
|
||||||
C.wclrtoeol(w.win())
|
C.wclrtoeol(w.impl)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) Print(text string) {
|
func (w *CursesWindow) Print(text string) {
|
||||||
C.waddstr(w.win(), C.CString(strings.Map(func(r rune) rune {
|
C.waddstr(w.impl, C.CString(strings.Map(func(r rune) rune {
|
||||||
if r < 32 {
|
if r < 32 {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
@ -255,69 +249,81 @@ func (w *Window) Print(text string) {
|
|||||||
}, text)))
|
}, text)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) CPrint(pair ColorPair, attr Attr, text string) {
|
func (w *CursesWindow) CPrint(color ColorPair, attr Attr, text string) {
|
||||||
p, a := _colorFn(pair, attr)
|
p, a := _colorFn(color, attr)
|
||||||
C.wcolor_set(w.win(), p, nil)
|
C.wcolor_set(w.impl, p, nil)
|
||||||
C.wattron(w.win(), a)
|
C.wattron(w.impl, a)
|
||||||
w.Print(text)
|
w.Print(text)
|
||||||
C.wattroff(w.win(), a)
|
C.wattroff(w.impl, a)
|
||||||
C.wcolor_set(w.win(), 0, nil)
|
C.wcolor_set(w.impl, 0, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Clear() {
|
func (r *FullscreenRenderer) Clear() {
|
||||||
C.clear()
|
C.clear()
|
||||||
C.endwin()
|
C.endwin()
|
||||||
}
|
}
|
||||||
|
|
||||||
func Refresh() {
|
func (r *FullscreenRenderer) Refresh() {
|
||||||
C.refresh()
|
C.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) Erase() {
|
func (w *CursesWindow) Erase() {
|
||||||
C.werase(w.win())
|
C.werase(w.impl)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) X() int {
|
func (w *CursesWindow) X() int {
|
||||||
return int(C.c_getcurx(w.win()))
|
return int(C.c_getcurx(w.impl))
|
||||||
}
|
}
|
||||||
|
|
||||||
func DoesAutoWrap() bool {
|
func (r *FullscreenRenderer) DoesAutoWrap() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) Fill(str string) bool {
|
func (r *FullscreenRenderer) IsOptimized() bool {
|
||||||
return C.waddstr(w.win(), C.CString(str)) == C.OK
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) CFill(str string, fg Color, bg Color, attr Attr) bool {
|
func (w *CursesWindow) Fill(str string) FillReturn {
|
||||||
pair := PairFor(fg, bg)
|
if C.waddstr(w.impl, C.CString(str)) == C.OK {
|
||||||
C.wcolor_set(w.win(), C.short(pair), nil)
|
return FillContinue
|
||||||
C.wattron(w.win(), C.int(attr))
|
}
|
||||||
|
return FillSuspend
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *CursesWindow) CFill(fg Color, bg Color, attr Attr, str string) FillReturn {
|
||||||
|
index := ColorPair{fg, bg, -1}.index()
|
||||||
|
C.wcolor_set(w.impl, C.short(index), nil)
|
||||||
|
C.wattron(w.impl, C.int(attr))
|
||||||
ret := w.Fill(str)
|
ret := w.Fill(str)
|
||||||
C.wattroff(w.win(), C.int(attr))
|
C.wattroff(w.impl, C.int(attr))
|
||||||
C.wcolor_set(w.win(), 0, nil)
|
C.wcolor_set(w.impl, 0, nil)
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func RefreshWindows(windows []*Window) {
|
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {
|
||||||
for _, w := range windows {
|
for _, w := range windows {
|
||||||
C.wnoutrefresh(w.win())
|
w.Refresh()
|
||||||
}
|
}
|
||||||
C.doupdate()
|
C.doupdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
func PairFor(fg Color, bg Color) ColorPair {
|
func (p ColorPair) index() int16 {
|
||||||
// ncurses does not support 24-bit colors
|
if p.id >= 0 {
|
||||||
if fg.is24() || bg.is24() {
|
return p.id
|
||||||
return ColDefault
|
|
||||||
}
|
}
|
||||||
key := (int(fg) << 8) + int(bg)
|
|
||||||
|
// ncurses does not support 24-bit colors
|
||||||
|
if p.is24() {
|
||||||
|
return ColDefault.index()
|
||||||
|
}
|
||||||
|
|
||||||
|
key := p.key()
|
||||||
if found, prs := _colorMap[key]; prs {
|
if found, prs := _colorMap[key]; prs {
|
||||||
return found
|
return found
|
||||||
}
|
}
|
||||||
|
|
||||||
id := ColorPair(len(_colorMap) + int(ColUser))
|
id := int16(len(_colorMap)) + ColUser.id
|
||||||
C.init_pair(C.short(id), C.short(fg), C.short(bg))
|
C.init_pair(C.short(id), C.short(p.Fg()), C.short(p.Bg()))
|
||||||
_colorMap[key] = id
|
_colorMap[key] = id
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
@ -369,11 +375,13 @@ func escSequence() Event {
|
|||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetChar() Event {
|
func (r *FullscreenRenderer) GetChar() Event {
|
||||||
c := C.getch()
|
c := C.getch()
|
||||||
switch c {
|
switch c {
|
||||||
case C.ERR:
|
case C.ERR:
|
||||||
return Event{Invalid, 0, nil}
|
// Unexpected error from blocking read
|
||||||
|
r.Close()
|
||||||
|
errorExit("Failed to read /dev/tty")
|
||||||
case C.KEY_UP:
|
case C.KEY_UP:
|
||||||
return Event{Up, 0, nil}
|
return Event{Up, 0, nil}
|
||||||
case C.KEY_DOWN:
|
case C.KEY_DOWN:
|
||||||
@ -435,17 +443,17 @@ func GetChar() Event {
|
|||||||
/* Cannot use BUTTON1_DOUBLE_CLICKED due to mouseinterval(0) */
|
/* Cannot use BUTTON1_DOUBLE_CLICKED due to mouseinterval(0) */
|
||||||
if (me.bstate & C.BUTTON1_PRESSED) > 0 {
|
if (me.bstate & C.BUTTON1_PRESSED) > 0 {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
if now.Sub(_prevDownTime) < doubleClickDuration {
|
if now.Sub(r.prevDownTime) < doubleClickDuration {
|
||||||
_clickY = append(_clickY, y)
|
r.clickY = append(r.clickY, y)
|
||||||
} else {
|
} else {
|
||||||
_clickY = []int{y}
|
r.clickY = []int{y}
|
||||||
_prevDownTime = now
|
r.prevDownTime = now
|
||||||
}
|
}
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, true, false, mod}}
|
return Event{Mouse, 0, &MouseEvent{y, x, 0, true, false, mod}}
|
||||||
} else if (me.bstate & C.BUTTON1_RELEASED) > 0 {
|
} else if (me.bstate & C.BUTTON1_RELEASED) > 0 {
|
||||||
double := false
|
double := false
|
||||||
if len(_clickY) > 1 && _clickY[0] == _clickY[1] &&
|
if len(r.clickY) > 1 && r.clickY[0] == r.clickY[1] &&
|
||||||
time.Now().Sub(_prevDownTime) < doubleClickDuration {
|
time.Now().Sub(r.prevDownTime) < doubleClickDuration {
|
||||||
double = true
|
double = true
|
||||||
}
|
}
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, false, double, mod}}
|
return Event{Mouse, 0, &MouseEvent{y, x, 0, false, double, mod}}
|
||||||
|
292
src/tui/tcell.go
292
src/tui/tcell.go
@ -6,9 +6,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
// https://github.com/gdamore/tcell/pull/135
|
// https://github.com/gdamore/tcell/pull/135
|
||||||
@ -18,30 +15,56 @@ import (
|
|||||||
"github.com/junegunn/go-runewidth"
|
"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 {
|
func (p ColorPair) style() tcell.Style {
|
||||||
style := tcell.StyleDefault
|
style := tcell.StyleDefault
|
||||||
return style.Foreground(tcell.Color(p.fg())).Background(tcell.Color(p.bg()))
|
return style.Foreground(tcell.Color(p.Fg())).Background(tcell.Color(p.Bg()))
|
||||||
}
|
}
|
||||||
|
|
||||||
type Attr tcell.Style
|
type Attr tcell.Style
|
||||||
|
|
||||||
type WindowTcell struct {
|
type TcellWindow struct {
|
||||||
LastX int
|
color bool
|
||||||
LastY int
|
top int
|
||||||
MoveCursor bool
|
left int
|
||||||
Border bool
|
width int
|
||||||
|
height int
|
||||||
|
lastX int
|
||||||
|
lastY int
|
||||||
|
moveCursor bool
|
||||||
|
border bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *TcellWindow) Top() int {
|
||||||
|
return w.top
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *TcellWindow) Left() int {
|
||||||
|
return w.left
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *TcellWindow) Width() int {
|
||||||
|
return w.width
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *TcellWindow) Height() int {
|
||||||
|
return w.height
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *TcellWindow) Refresh() {
|
||||||
|
if w.moveCursor {
|
||||||
|
_screen.ShowCursor(w.left+w.lastX, w.top+w.lastY)
|
||||||
|
w.moveCursor = false
|
||||||
|
}
|
||||||
|
w.lastX = 0
|
||||||
|
w.lastY = 0
|
||||||
|
if w.border {
|
||||||
|
w.drawBorder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *TcellWindow) FinishFill() {
|
||||||
|
// NO-OP
|
||||||
}
|
}
|
||||||
type WindowImpl WindowTcell
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Bold Attr = Attr(tcell.AttrBold)
|
Bold Attr = Attr(tcell.AttrBold)
|
||||||
@ -56,33 +79,13 @@ const (
|
|||||||
AttrRegular Attr = 0
|
AttrRegular Attr = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
|
||||||
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 {
|
if _screen.Colors() >= 256 {
|
||||||
return Dark256
|
return Dark256
|
||||||
}
|
}
|
||||||
return Default16
|
return Default16
|
||||||
}
|
}
|
||||||
|
|
||||||
func PairFor(fg Color, bg Color) ColorPair {
|
|
||||||
return [2]Color{fg, bg}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_colorToAttribute = []tcell.Color{
|
_colorToAttribute = []tcell.Color{
|
||||||
tcell.ColorBlack,
|
tcell.ColorBlack,
|
||||||
@ -112,20 +115,17 @@ func (a Attr) Merge(b Attr) Attr {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
_screen tcell.Screen
|
_screen tcell.Screen
|
||||||
_mouse bool
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func initScreen() {
|
func (r *FullscreenRenderer) initScreen() {
|
||||||
s, e := tcell.NewScreen()
|
s, e := tcell.NewScreen()
|
||||||
if e != nil {
|
if e != nil {
|
||||||
fmt.Fprintf(os.Stderr, "%v\n", e)
|
errorExit(e.Error())
|
||||||
os.Exit(2)
|
|
||||||
}
|
}
|
||||||
if e = s.Init(); e != nil {
|
if e = s.Init(); e != nil {
|
||||||
fmt.Fprintf(os.Stderr, "%v\n", e)
|
errorExit(e.Error())
|
||||||
os.Exit(2)
|
|
||||||
}
|
}
|
||||||
if _mouse {
|
if r.mouse {
|
||||||
s.EnableMouse()
|
s.EnableMouse()
|
||||||
} else {
|
} else {
|
||||||
s.DisableMouse()
|
s.DisableMouse()
|
||||||
@ -133,63 +133,45 @@ func initScreen() {
|
|||||||
_screen = s
|
_screen = s
|
||||||
}
|
}
|
||||||
|
|
||||||
func Init(theme *ColorTheme, black bool, mouse bool) {
|
func (r *FullscreenRenderer) Init() {
|
||||||
encoding.Register()
|
encoding.Register()
|
||||||
|
|
||||||
_mouse = mouse
|
r.initScreen()
|
||||||
initScreen()
|
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
||||||
|
|
||||||
_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 {
|
func (r *FullscreenRenderer) MaxX() int {
|
||||||
ncols, _ := _screen.Size()
|
ncols, _ := _screen.Size()
|
||||||
return int(ncols)
|
return int(ncols)
|
||||||
}
|
}
|
||||||
|
|
||||||
func MaxY() int {
|
func (r *FullscreenRenderer) MaxY() int {
|
||||||
_, nlines := _screen.Size()
|
_, nlines := _screen.Size()
|
||||||
return int(nlines)
|
return int(nlines)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) win() *WindowTcell {
|
func (w *TcellWindow) X() int {
|
||||||
return (*WindowTcell)(w.impl)
|
return w.lastX
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) X() int {
|
func (r *FullscreenRenderer) DoesAutoWrap() bool {
|
||||||
return w.impl.LastX
|
|
||||||
}
|
|
||||||
|
|
||||||
func DoesAutoWrap() bool {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func Clear() {
|
func (r *FullscreenRenderer) IsOptimized() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FullscreenRenderer) Clear() {
|
||||||
_screen.Sync()
|
_screen.Sync()
|
||||||
_screen.Clear()
|
_screen.Clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
func Refresh() {
|
func (r *FullscreenRenderer) Refresh() {
|
||||||
// noop
|
// noop
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetChar() Event {
|
func (r *FullscreenRenderer) GetChar() Event {
|
||||||
ev := _screen.PollEvent()
|
ev := _screen.PollEvent()
|
||||||
switch ev := ev.(type) {
|
switch ev := ev.(type) {
|
||||||
case *tcell.EventResize:
|
case *tcell.EventResize:
|
||||||
@ -213,15 +195,15 @@ func GetChar() Event {
|
|||||||
double := false
|
double := false
|
||||||
if down {
|
if down {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
if now.Sub(_prevDownTime) < doubleClickDuration {
|
if now.Sub(r.prevDownTime) < doubleClickDuration {
|
||||||
_clickY = append(_clickY, x)
|
r.clickY = append(r.clickY, x)
|
||||||
} else {
|
} else {
|
||||||
_clickY = []int{x}
|
r.clickY = []int{x}
|
||||||
_prevDownTime = now
|
r.prevDownTime = now
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if len(_clickY) > 1 && _clickY[0] == _clickY[1] &&
|
if len(r.clickY) > 1 && r.clickY[0] == r.clickY[1] &&
|
||||||
time.Now().Sub(_prevDownTime) < doubleClickDuration {
|
time.Now().Sub(r.prevDownTime) < doubleClickDuration {
|
||||||
double = true
|
double = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -368,49 +350,39 @@ func GetChar() Event {
|
|||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Pause() {
|
func (r *FullscreenRenderer) Pause() {
|
||||||
_screen.Fini()
|
_screen.Fini()
|
||||||
}
|
}
|
||||||
|
|
||||||
func Resume() bool {
|
func (r *FullscreenRenderer) Resume() bool {
|
||||||
initScreen()
|
r.initScreen()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func Close() {
|
func (r *FullscreenRenderer) Close() {
|
||||||
_screen.Fini()
|
_screen.Fini()
|
||||||
}
|
}
|
||||||
|
|
||||||
func RefreshWindows(windows []*Window) {
|
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {
|
||||||
// TODO
|
// TODO
|
||||||
for _, w := range windows {
|
for _, w := range windows {
|
||||||
if w.win().MoveCursor {
|
w.Refresh()
|
||||||
_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()
|
_screen.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWindow(top int, left int, width int, height int, border bool) *Window {
|
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, border bool) Window {
|
||||||
// TODO
|
// TODO
|
||||||
win := new(WindowTcell)
|
return &TcellWindow{
|
||||||
win.Border = border
|
color: r.theme != nil,
|
||||||
return &Window{
|
top: top,
|
||||||
impl: (*WindowImpl)(win),
|
left: left,
|
||||||
Top: top,
|
width: width,
|
||||||
Left: left,
|
height: height,
|
||||||
Width: width,
|
border: border}
|
||||||
Height: height,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) Close() {
|
func (w *TcellWindow) Close() {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -422,40 +394,40 @@ func fill(x, y, w, h int, r rune) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) Erase() {
|
func (w *TcellWindow) Erase() {
|
||||||
// TODO
|
// TODO
|
||||||
fill(w.Left, w.Top, w.Width, w.Height, ' ')
|
fill(w.left, w.top, w.width, w.height, ' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) Enclose(y int, x int) bool {
|
func (w *TcellWindow) Enclose(y int, x int) bool {
|
||||||
return x >= w.Left && x <= (w.Left+w.Width) &&
|
return x >= w.left && x < (w.left+w.width) &&
|
||||||
y >= w.Top && y <= (w.Top+w.Height)
|
y >= w.top && y < (w.top+w.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) Move(y int, x int) {
|
func (w *TcellWindow) Move(y int, x int) {
|
||||||
w.win().LastX = x
|
w.lastX = x
|
||||||
w.win().LastY = y
|
w.lastY = y
|
||||||
w.win().MoveCursor = true
|
w.moveCursor = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) MoveAndClear(y int, x int) {
|
func (w *TcellWindow) MoveAndClear(y int, x int) {
|
||||||
w.Move(y, x)
|
w.Move(y, x)
|
||||||
for i := w.win().LastX; i < w.Width; i++ {
|
for i := w.lastX; i < w.width; i++ {
|
||||||
_screen.SetContent(i+w.Left, w.win().LastY+w.Top, rune(' '), nil, ColDefault.style())
|
_screen.SetContent(i+w.left, w.lastY+w.top, rune(' '), nil, ColDefault.style())
|
||||||
}
|
}
|
||||||
w.win().LastX = x
|
w.lastX = x
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) Print(text string) {
|
func (w *TcellWindow) Print(text string) {
|
||||||
w.PrintString(text, ColDefault, 0)
|
w.printString(text, ColDefault, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) PrintString(text string, pair ColorPair, a Attr) {
|
func (w *TcellWindow) printString(text string, pair ColorPair, a Attr) {
|
||||||
t := text
|
t := text
|
||||||
lx := 0
|
lx := 0
|
||||||
|
|
||||||
var style tcell.Style
|
var style tcell.Style
|
||||||
if _color {
|
if w.color {
|
||||||
style = pair.style().
|
style = pair.style().
|
||||||
Reverse(a&Attr(tcell.AttrReverse) != 0).
|
Reverse(a&Attr(tcell.AttrReverse) != 0).
|
||||||
Underline(a&Attr(tcell.AttrUnderline) != 0)
|
Underline(a&Attr(tcell.AttrUnderline) != 0)
|
||||||
@ -481,7 +453,7 @@ func (w *Window) PrintString(text string, pair ColorPair, a Attr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if r == '\n' {
|
if r == '\n' {
|
||||||
w.win().LastY++
|
w.lastY++
|
||||||
lx = 0
|
lx = 0
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
@ -489,26 +461,26 @@ func (w *Window) PrintString(text string, pair ColorPair, a Attr) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var xPos = w.Left + w.win().LastX + lx
|
var xPos = w.left + w.lastX + lx
|
||||||
var yPos = w.Top + w.win().LastY
|
var yPos = w.top + w.lastY
|
||||||
if xPos < (w.Left+w.Width) && yPos < (w.Top+w.Height) {
|
if xPos < (w.left+w.width) && yPos < (w.top+w.height) {
|
||||||
_screen.SetContent(xPos, yPos, r, nil, style)
|
_screen.SetContent(xPos, yPos, r, nil, style)
|
||||||
}
|
}
|
||||||
lx += runewidth.RuneWidth(r)
|
lx += runewidth.RuneWidth(r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
w.win().LastX += lx
|
w.lastX += lx
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) CPrint(pair ColorPair, a Attr, text string) {
|
func (w *TcellWindow) CPrint(pair ColorPair, attr Attr, text string) {
|
||||||
w.PrintString(text, pair, a)
|
w.printString(text, pair, attr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) FillString(text string, pair ColorPair, a Attr) bool {
|
func (w *TcellWindow) fillString(text string, pair ColorPair, a Attr) FillReturn {
|
||||||
lx := 0
|
lx := 0
|
||||||
|
|
||||||
var style tcell.Style
|
var style tcell.Style
|
||||||
if _color {
|
if w.color {
|
||||||
style = pair.style()
|
style = pair.style()
|
||||||
} else {
|
} else {
|
||||||
style = ColDefault.style()
|
style = ColDefault.style()
|
||||||
@ -522,50 +494,50 @@ func (w *Window) FillString(text string, pair ColorPair, a Attr) bool {
|
|||||||
|
|
||||||
for _, r := range text {
|
for _, r := range text {
|
||||||
if r == '\n' {
|
if r == '\n' {
|
||||||
w.win().LastY++
|
w.lastY++
|
||||||
w.win().LastX = 0
|
w.lastX = 0
|
||||||
lx = 0
|
lx = 0
|
||||||
} else {
|
} else {
|
||||||
var xPos = w.Left + w.win().LastX + lx
|
var xPos = w.left + w.lastX + lx
|
||||||
|
|
||||||
// word wrap:
|
// word wrap:
|
||||||
if xPos >= (w.Left + w.Width) {
|
if xPos >= (w.left + w.width) {
|
||||||
w.win().LastY++
|
w.lastY++
|
||||||
w.win().LastX = 0
|
w.lastX = 0
|
||||||
lx = 0
|
lx = 0
|
||||||
xPos = w.Left
|
xPos = w.left
|
||||||
}
|
}
|
||||||
var yPos = w.Top + w.win().LastY
|
var yPos = w.top + w.lastY
|
||||||
|
|
||||||
if yPos >= (w.Top + w.Height) {
|
if yPos >= (w.top + w.height) {
|
||||||
return false
|
return FillSuspend
|
||||||
}
|
}
|
||||||
|
|
||||||
_screen.SetContent(xPos, yPos, r, nil, style)
|
_screen.SetContent(xPos, yPos, r, nil, style)
|
||||||
lx += runewidth.RuneWidth(r)
|
lx += runewidth.RuneWidth(r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
w.win().LastX += lx
|
w.lastX += lx
|
||||||
|
|
||||||
return true
|
return FillContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) Fill(str string) bool {
|
func (w *TcellWindow) Fill(str string) FillReturn {
|
||||||
return w.FillString(str, ColDefault, 0)
|
return w.fillString(str, ColDefault, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) CFill(str string, fg Color, bg Color, a Attr) bool {
|
func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
|
||||||
return w.FillString(str, ColorPair{fg, bg}, a)
|
return w.fillString(str, ColorPair{fg, bg, -1}, a)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) DrawBorder() {
|
func (w *TcellWindow) drawBorder() {
|
||||||
left := w.Left
|
left := w.left
|
||||||
right := left + w.Width
|
right := left + w.width
|
||||||
top := w.Top
|
top := w.top
|
||||||
bot := top + w.Height
|
bot := top + w.height
|
||||||
|
|
||||||
var style tcell.Style
|
var style tcell.Style
|
||||||
if _color {
|
if w.color {
|
||||||
style = ColBorder.style()
|
style = ColBorder.style()
|
||||||
} else {
|
} else {
|
||||||
style = ColDefault.style()
|
style = ColDefault.style()
|
||||||
|
194
src/tui/tui.go
194
src/tui/tui.go
@ -1,6 +1,9 @@
|
|||||||
package tui
|
package tui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -115,6 +118,47 @@ const (
|
|||||||
colWhite
|
colWhite
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type FillReturn int
|
||||||
|
|
||||||
|
const (
|
||||||
|
FillContinue FillReturn = iota
|
||||||
|
FillNextLine
|
||||||
|
FillSuspend
|
||||||
|
)
|
||||||
|
|
||||||
|
type ColorPair struct {
|
||||||
|
fg Color
|
||||||
|
bg Color
|
||||||
|
id int16
|
||||||
|
}
|
||||||
|
|
||||||
|
func HexToColor(rrggbb string) Color {
|
||||||
|
r, _ := strconv.ParseInt(rrggbb[1:3], 16, 0)
|
||||||
|
g, _ := strconv.ParseInt(rrggbb[3:5], 16, 0)
|
||||||
|
b, _ := strconv.ParseInt(rrggbb[5:7], 16, 0)
|
||||||
|
return Color((1 << 24) + (r << 16) + (g << 8) + b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewColorPair(fg Color, bg Color) ColorPair {
|
||||||
|
return ColorPair{fg, bg, -1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ColorPair) Fg() Color {
|
||||||
|
return p.fg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ColorPair) Bg() Color {
|
||||||
|
return p.bg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ColorPair) key() int {
|
||||||
|
return (int(p.Fg()) << 8) + int(p.Bg())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ColorPair) is24() bool {
|
||||||
|
return p.Fg().is24() || p.Bg().is24()
|
||||||
|
}
|
||||||
|
|
||||||
type ColorTheme struct {
|
type ColorTheme struct {
|
||||||
Fg Color
|
Fg Color
|
||||||
Bg Color
|
Bg Color
|
||||||
@ -146,23 +190,85 @@ type MouseEvent struct {
|
|||||||
Mod bool
|
Mod bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
type Renderer interface {
|
||||||
_color bool
|
Init()
|
||||||
_prevDownTime time.Time
|
Pause()
|
||||||
_clickY []int
|
Resume() bool
|
||||||
Default16 *ColorTheme
|
Clear()
|
||||||
Dark256 *ColorTheme
|
RefreshWindows(windows []Window)
|
||||||
Light256 *ColorTheme
|
Refresh()
|
||||||
)
|
Close()
|
||||||
|
|
||||||
type Window struct {
|
GetChar() Event
|
||||||
impl *WindowImpl
|
|
||||||
Top int
|
MaxX() int
|
||||||
Left int
|
MaxY() int
|
||||||
Width int
|
DoesAutoWrap() bool
|
||||||
Height int
|
IsOptimized() bool
|
||||||
|
|
||||||
|
NewWindow(top int, left int, width int, height int, border bool) Window
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Window interface {
|
||||||
|
Top() int
|
||||||
|
Left() int
|
||||||
|
Width() int
|
||||||
|
Height() int
|
||||||
|
|
||||||
|
Refresh()
|
||||||
|
FinishFill()
|
||||||
|
Close()
|
||||||
|
|
||||||
|
X() int
|
||||||
|
Enclose(y int, x int) bool
|
||||||
|
|
||||||
|
Move(y int, x int)
|
||||||
|
MoveAndClear(y int, x int)
|
||||||
|
Print(text string)
|
||||||
|
CPrint(color ColorPair, attr Attr, text string)
|
||||||
|
Fill(text string) FillReturn
|
||||||
|
CFill(fg Color, bg Color, attr Attr, text string) FillReturn
|
||||||
|
Erase()
|
||||||
|
}
|
||||||
|
|
||||||
|
type FullscreenRenderer struct {
|
||||||
|
theme *ColorTheme
|
||||||
|
mouse bool
|
||||||
|
forceBlack bool
|
||||||
|
prevDownTime time.Time
|
||||||
|
clickY []int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFullscreenRenderer(theme *ColorTheme, forceBlack bool, mouse bool) Renderer {
|
||||||
|
r := &FullscreenRenderer{
|
||||||
|
theme: theme,
|
||||||
|
mouse: mouse,
|
||||||
|
forceBlack: forceBlack,
|
||||||
|
prevDownTime: time.Unix(0, 0),
|
||||||
|
clickY: []int{}}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
Default16 *ColorTheme
|
||||||
|
Dark256 *ColorTheme
|
||||||
|
Light256 *ColorTheme
|
||||||
|
|
||||||
|
ColDefault ColorPair
|
||||||
|
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 EmptyTheme() *ColorTheme {
|
func EmptyTheme() *ColorTheme {
|
||||||
return &ColorTheme{
|
return &ColorTheme{
|
||||||
Fg: colUndefined,
|
Fg: colUndefined,
|
||||||
@ -180,9 +286,12 @@ func EmptyTheme() *ColorTheme {
|
|||||||
Border: colUndefined}
|
Border: colUndefined}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func errorExit(message string) {
|
||||||
|
fmt.Fprintln(os.Stderr, message)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
_prevDownTime = time.Unix(0, 0)
|
|
||||||
_clickY = []int{}
|
|
||||||
Default16 = &ColorTheme{
|
Default16 = &ColorTheme{
|
||||||
Fg: colDefault,
|
Fg: colDefault,
|
||||||
Bg: colDefault,
|
Bg: colDefault,
|
||||||
@ -227,14 +336,13 @@ func init() {
|
|||||||
Border: 145}
|
Border: 145}
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitTheme(theme *ColorTheme, black bool) {
|
func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
|
||||||
_color = theme != nil
|
if theme == nil {
|
||||||
if !_color {
|
initPalette(theme)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
baseTheme := DefaultTheme()
|
if forceBlack {
|
||||||
if black {
|
|
||||||
theme.Bg = colBlack
|
theme.Bg = colBlack
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,4 +365,48 @@ func InitTheme(theme *ColorTheme, black bool) {
|
|||||||
theme.Selected = o(baseTheme.Selected, theme.Selected)
|
theme.Selected = o(baseTheme.Selected, theme.Selected)
|
||||||
theme.Header = o(baseTheme.Header, theme.Header)
|
theme.Header = o(baseTheme.Header, theme.Header)
|
||||||
theme.Border = o(baseTheme.Border, theme.Border)
|
theme.Border = o(baseTheme.Border, theme.Border)
|
||||||
|
|
||||||
|
initPalette(theme)
|
||||||
|
}
|
||||||
|
|
||||||
|
func initPalette(theme *ColorTheme) {
|
||||||
|
ColDefault = ColorPair{colDefault, colDefault, 0}
|
||||||
|
if theme != nil {
|
||||||
|
ColNormal = ColorPair{theme.Fg, theme.Bg, 1}
|
||||||
|
ColPrompt = ColorPair{theme.Prompt, theme.Bg, 2}
|
||||||
|
ColMatch = ColorPair{theme.Match, theme.Bg, 3}
|
||||||
|
ColCurrent = ColorPair{theme.Current, theme.DarkBg, 4}
|
||||||
|
ColCurrentMatch = ColorPair{theme.CurrentMatch, theme.DarkBg, 5}
|
||||||
|
ColSpinner = ColorPair{theme.Spinner, theme.Bg, 6}
|
||||||
|
ColInfo = ColorPair{theme.Info, theme.Bg, 7}
|
||||||
|
ColCursor = ColorPair{theme.Cursor, theme.DarkBg, 8}
|
||||||
|
ColSelected = ColorPair{theme.Selected, theme.DarkBg, 9}
|
||||||
|
ColHeader = ColorPair{theme.Header, theme.Bg, 10}
|
||||||
|
ColBorder = ColorPair{theme.Border, theme.Bg, 11}
|
||||||
|
} else {
|
||||||
|
ColNormal = ColorPair{colDefault, colDefault, 1}
|
||||||
|
ColPrompt = ColorPair{colDefault, colDefault, 2}
|
||||||
|
ColMatch = ColorPair{colDefault, colDefault, 3}
|
||||||
|
ColCurrent = ColorPair{colDefault, colDefault, 4}
|
||||||
|
ColCurrentMatch = ColorPair{colDefault, colDefault, 5}
|
||||||
|
ColSpinner = ColorPair{colDefault, colDefault, 6}
|
||||||
|
ColInfo = ColorPair{colDefault, colDefault, 7}
|
||||||
|
ColCursor = ColorPair{colDefault, colDefault, 8}
|
||||||
|
ColSelected = ColorPair{colDefault, colDefault, 9}
|
||||||
|
ColHeader = ColorPair{colDefault, colDefault, 10}
|
||||||
|
ColBorder = ColorPair{colDefault, colDefault, 11}
|
||||||
|
}
|
||||||
|
ColUser = ColorPair{colDefault, colDefault, 12}
|
||||||
|
}
|
||||||
|
|
||||||
|
func attrFor(color ColorPair, attr Attr) Attr {
|
||||||
|
switch color {
|
||||||
|
case ColCurrent:
|
||||||
|
return attr | Reverse
|
||||||
|
case ColMatch:
|
||||||
|
return attr | Underline
|
||||||
|
case ColCurrentMatch:
|
||||||
|
return attr | Underline | Reverse
|
||||||
|
}
|
||||||
|
return attr
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,20 @@
|
|||||||
package tui
|
package tui
|
||||||
|
|
||||||
import (
|
import "testing"
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPairFor(t *testing.T) {
|
func TestHexToColor(t *testing.T) {
|
||||||
if PairFor(30, 50) != PairFor(30, 50) {
|
assert := func(expr string, r, g, b int) {
|
||||||
t.Fail()
|
color := HexToColor(expr)
|
||||||
}
|
if !color.is24() ||
|
||||||
if PairFor(-1, 10) != PairFor(-1, 10) {
|
int((color>>16)&0xff) != r ||
|
||||||
t.Fail()
|
int((color>>8)&0xff) != g ||
|
||||||
|
int((color)&0xff) != b {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert("#ff0000", 255, 0, 0)
|
||||||
|
assert("#010203", 1, 2, 3)
|
||||||
|
assert("#102030", 16, 32, 48)
|
||||||
|
assert("#ffffff", 255, 255, 255)
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,24 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/junegunn/go-isatty"
|
"github.com/junegunn/go-isatty"
|
||||||
|
"github.com/junegunn/go-runewidth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var _runeWidths = make(map[rune]int)
|
||||||
|
|
||||||
|
// RuneWidth returns rune width
|
||||||
|
func RuneWidth(r rune, prefixWidth int, tabstop int) int {
|
||||||
|
if r == '\t' {
|
||||||
|
return tabstop - prefixWidth%tabstop
|
||||||
|
} else if w, found := _runeWidths[r]; found {
|
||||||
|
return w
|
||||||
|
} else {
|
||||||
|
w := Max(runewidth.RuneWidth(r), 1)
|
||||||
|
_runeWidths[r] = w
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Max returns the largest integer
|
// Max returns the largest integer
|
||||||
func Max(first int, second int) int {
|
func Max(first int, second int) int {
|
||||||
if first >= second {
|
if first >= second {
|
||||||
|
@ -5,6 +5,7 @@ package util
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExecCommand executes the given command with $SHELL
|
// ExecCommand executes the given command with $SHELL
|
||||||
@ -20,3 +21,8 @@ func ExecCommand(command string) *exec.Cmd {
|
|||||||
func IsWindows() bool {
|
func IsWindows() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetNonBlock executes syscall.SetNonblock on file descriptor
|
||||||
|
func SetNonblock(file *os.File, nonblock bool) {
|
||||||
|
syscall.SetNonblock(int(file.Fd()), nonblock)
|
||||||
|
}
|
||||||
|
@ -5,6 +5,7 @@ package util
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/junegunn/go-shellwords"
|
"github.com/junegunn/go-shellwords"
|
||||||
)
|
)
|
||||||
@ -26,3 +27,8 @@ func ExecCommand(command string) *exec.Cmd {
|
|||||||
func IsWindows() bool {
|
func IsWindows() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetNonBlock executes syscall.SetNonblock on file descriptor
|
||||||
|
func SetNonblock(file *os.File, nonblock bool) {
|
||||||
|
syscall.SetNonblock(syscall.Handle(file.Fd()), nonblock)
|
||||||
|
}
|
||||||
|
312
test/test_go.rb
312
test/test_go.rb
@ -117,8 +117,28 @@ class Tmux
|
|||||||
wait do
|
wait do
|
||||||
lines = capture(pane)
|
lines = capture(pane)
|
||||||
class << lines
|
class << lines
|
||||||
|
def counts
|
||||||
|
self.lazy
|
||||||
|
.map { |l| l.scan /^. ([0-9]+)\/([0-9]+)( \(([0-9]+)\))?/ }
|
||||||
|
.reject(&:empty?)
|
||||||
|
.first&.first&.map(&:to_i)&.values_at(0, 1, 3) || [0, 0, 0]
|
||||||
|
end
|
||||||
|
|
||||||
|
def match_count
|
||||||
|
counts[0]
|
||||||
|
end
|
||||||
|
|
||||||
def item_count
|
def item_count
|
||||||
self[-2] ? self[-2].strip.split('/').last.to_i : 0
|
counts[1]
|
||||||
|
end
|
||||||
|
|
||||||
|
def select_count
|
||||||
|
counts[2]
|
||||||
|
end
|
||||||
|
|
||||||
|
def any_include? val
|
||||||
|
method = val.is_a?(Regexp) ? :match : :include?
|
||||||
|
self.select { |line| line.send method, val }.first
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
yield lines
|
yield lines
|
||||||
@ -163,6 +183,11 @@ class TestBase < Minitest::Test
|
|||||||
@temp_suffix].join '-'
|
@temp_suffix].join '-'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def writelines path, lines
|
||||||
|
File.unlink path while File.exists? path
|
||||||
|
File.open(path, 'w') { |f| f << lines.join($/) + $/ }
|
||||||
|
end
|
||||||
|
|
||||||
def readonce
|
def readonce
|
||||||
wait { File.exists?(tempname) }
|
wait { File.exists?(tempname) }
|
||||||
File.read(tempname)
|
File.read(tempname)
|
||||||
@ -297,6 +322,19 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| lines.last !~ /^>/ }
|
tmux.until { |lines| lines.last !~ /^>/ }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_file_word
|
||||||
|
tmux.send_keys "#{FZF} -q '--/foo bar/foo-bar/baz' --filepath-word", :Enter
|
||||||
|
tmux.until { |lines| lines.last =~ /^>/ }
|
||||||
|
|
||||||
|
tmux.send_keys :Escape, :b
|
||||||
|
tmux.send_keys :Escape, :b
|
||||||
|
tmux.send_keys :Escape, :b
|
||||||
|
tmux.send_keys :Escape, :d
|
||||||
|
tmux.send_keys :Escape, :f
|
||||||
|
tmux.send_keys :Escape, :BSpace
|
||||||
|
tmux.until { |lines| lines.last == '> --///baz' }
|
||||||
|
end
|
||||||
|
|
||||||
def test_multi_order
|
def test_multi_order
|
||||||
tmux.send_keys "seq 1 10 | #{fzf :multi}", :Enter
|
tmux.send_keys "seq 1 10 | #{fzf :multi}", :Enter
|
||||||
tmux.until { |lines| lines.last =~ /^>/ }
|
tmux.until { |lines| lines.last =~ /^>/ }
|
||||||
@ -1056,7 +1094,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_invalid_term
|
def test_invalid_term
|
||||||
lines = `TERM=xxx #{FZF}`
|
lines = `TERM=xxx #{FZF} 2>&1`
|
||||||
assert_equal 2, $?.exitstatus
|
assert_equal 2, $?.exitstatus
|
||||||
assert lines.include?('Invalid $TERM: xxx') || lines.include?('terminal entry not found')
|
assert lines.include?('Invalid $TERM: xxx') || lines.include?('terminal entry not found')
|
||||||
end
|
end
|
||||||
@ -1190,12 +1228,6 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys '?'
|
tmux.send_keys '?'
|
||||||
tmux.until { |lines| lines[-1] == '> 555' }
|
tmux.until { |lines| lines[-1] == '> 555' }
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
def writelines path, lines
|
|
||||||
File.unlink path while File.exists? path
|
|
||||||
File.open(path, 'w') { |f| f << lines.join($/) + $/ }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
module TestShell
|
module TestShell
|
||||||
@ -1213,79 +1245,66 @@ module TestShell
|
|||||||
tmux.prepare
|
tmux.prepare
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_ctrl_t
|
def unset_var name
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys "unset #{name}", :Enter
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'C-t', pane: 0
|
|
||||||
lines = tmux.until(1) { |lines| lines.item_count > 1 }
|
|
||||||
expected = lines.values_at(-3, -4).map { |line| line[2..-1] }.join(' ')
|
|
||||||
tmux.send_keys :BTab, :BTab, pane: 1
|
|
||||||
tmux.until(1) { |lines| lines[-2].include?('(2)') }
|
|
||||||
tmux.send_keys :Enter, pane: 1
|
|
||||||
tmux.until(0) { |lines| lines[-1].include? expected }
|
|
||||||
tmux.send_keys 'C-c'
|
|
||||||
|
|
||||||
# FZF_TMUX=0
|
|
||||||
new_shell
|
|
||||||
tmux.send_keys 'C-t', pane: 0
|
|
||||||
lines = tmux.until(0) { |lines| lines.item_count > 1 }
|
|
||||||
expected = lines.values_at(-3, -4).map { |line| line[2..-1] }.join(' ')
|
|
||||||
tmux.send_keys :BTab, :BTab, pane: 0
|
|
||||||
tmux.until(0) { |lines| lines[-2].include?('(2)') }
|
|
||||||
tmux.send_keys :Enter, pane: 0
|
|
||||||
tmux.until(0) { |lines| lines[-1].include? expected }
|
|
||||||
tmux.send_keys 'C-c', 'C-d'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_ctrl_t_command
|
def test_ctrl_t
|
||||||
set_var "FZF_CTRL_T_COMMAND", "seq 100"
|
set_var "FZF_CTRL_T_COMMAND", "seq 100"
|
||||||
tmux.send_keys 'C-t', pane: 0
|
|
||||||
lines = tmux.until(1) { |lines| lines.item_count == 100 }
|
lines = retries do
|
||||||
tmux.send_keys :BTab, :BTab, :BTab, pane: 1
|
tmux.prepare
|
||||||
tmux.until(1) { |lines| lines[-2].include?('(3)') }
|
tmux.send_keys 'C-t'
|
||||||
tmux.send_keys :Enter, pane: 1
|
tmux.until { |lines| lines.item_count == 100 }
|
||||||
tmux.until(0) { |lines| lines[-1].include? '1 2 3' }
|
end
|
||||||
|
tmux.send_keys :Tab, :Tab, :Tab
|
||||||
|
tmux.until { |lines| lines.any_include? ' (3)' }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| lines.any_include? '1 2 3' }
|
||||||
|
tmux.send_keys 'C-c'
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_ctrl_t_unicode
|
def test_ctrl_t_unicode
|
||||||
FileUtils.mkdir_p '/tmp/fzf-test'
|
writelines tempname, ['fzf-unicode 테스트1', 'fzf-unicode 테스트2']
|
||||||
tmux.paste 'cd /tmp/fzf-test; echo -n test1 > "fzf-unicode 테스트1"; echo -n test2 > "fzf-unicode 테스트2"'
|
set_var "FZF_CTRL_T_COMMAND", "cat #{tempname}"
|
||||||
tmux.prepare
|
|
||||||
tmux.send_keys 'cat ', 'C-t', pane: 0
|
|
||||||
tmux.until(1) { |lines| lines.item_count >= 1 }
|
|
||||||
tmux.send_keys 'fzf-unicode', pane: 1
|
|
||||||
redraw = ->() { tmux.send_keys 'C-l', pane: 1 }
|
|
||||||
tmux.until(1) { |lines| redraw.(); lines[-2] =~ %r(^ *2/) }
|
|
||||||
|
|
||||||
tmux.send_keys '1', pane: 1
|
retries do
|
||||||
tmux.until(1) { |lines| redraw.(); lines[-2] =~ %r(^ *1/) }
|
tmux.prepare
|
||||||
tmux.send_keys :BTab, pane: 1
|
tmux.send_keys 'echo ', 'C-t'
|
||||||
tmux.until(1) { |lines| redraw.(); lines[-2].include? '(1)' }
|
tmux.until { |lines| lines.item_count == 2 }
|
||||||
|
|
||||||
tmux.send_keys :BSpace, pane: 1
|
|
||||||
tmux.until(1) { |lines| redraw.(); lines[-2] =~ %r(^ *2/) }
|
|
||||||
|
|
||||||
tmux.send_keys '2', pane: 1
|
|
||||||
tmux.until(1) { |lines| redraw.(); lines[-2] =~ %r(^ *1/) }
|
|
||||||
tmux.send_keys :BTab, pane: 1
|
|
||||||
tmux.until(1) { |lines| redraw.(); lines[-2].include? '(2)' }
|
|
||||||
|
|
||||||
tmux.send_keys :Enter, pane: 1
|
|
||||||
tmux.until do |lines|
|
|
||||||
tmux.send_keys 'C-l'
|
|
||||||
[-1, -2].map { |offset| lines[offset] }.any? do |line|
|
|
||||||
line.start_with?('cat') && line.include?('fzf-unicode')
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
tmux.send_keys 'fzf-unicode'
|
||||||
|
tmux.until { |lines| lines.match_count == 2 }
|
||||||
|
|
||||||
|
tmux.send_keys '1'
|
||||||
|
tmux.until { |lines| lines.match_count == 1 }
|
||||||
|
tmux.send_keys :Tab
|
||||||
|
tmux.until { |lines| lines.select_count == 1 }
|
||||||
|
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until { |lines| lines.match_count == 2 }
|
||||||
|
|
||||||
|
tmux.send_keys '2'
|
||||||
|
tmux.until { |lines| lines.match_count == 1 }
|
||||||
|
tmux.send_keys :Tab
|
||||||
|
tmux.until { |lines| lines.select_count == 2 }
|
||||||
|
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until { |lines| lines[-1].include? 'test1test2' }
|
tmux.until { |lines| lines.any_include? /echo.*fzf-unicode.*1.*fzf-unicode.*2/ }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| lines.any_include? /^fzf-unicode.*1.*fzf-unicode.*2/ }
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_alt_c
|
def test_alt_c
|
||||||
tmux.prepare
|
lines = retries do
|
||||||
tmux.send_keys :Escape, :c, pane: 0
|
tmux.prepare
|
||||||
lines = tmux.until(1) { |lines| lines.item_count > 0 && lines[-3][2..-1] }
|
tmux.send_keys :Escape, :c
|
||||||
expected = lines[-3][2..-1]
|
tmux.until { |lines| lines.item_count > 0 }
|
||||||
tmux.send_keys :Enter, pane: 1
|
end
|
||||||
|
expected = lines.reverse.select { |l| l.start_with? '>' }.first[2..-1]
|
||||||
|
tmux.send_keys :Enter
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys :pwd, :Enter
|
tmux.send_keys :pwd, :Enter
|
||||||
tmux.until { |lines| lines[-1].end_with?(expected) }
|
tmux.until { |lines| lines[-1].end_with?(expected) }
|
||||||
@ -1297,10 +1316,12 @@ module TestShell
|
|||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'cd /', :Enter
|
tmux.send_keys 'cd /', :Enter
|
||||||
|
|
||||||
tmux.prepare
|
retries do
|
||||||
tmux.send_keys :Escape, :c, pane: 0
|
tmux.prepare
|
||||||
lines = tmux.until(1) { |lines| lines.item_count == 1 }
|
tmux.send_keys :Escape, :c
|
||||||
tmux.send_keys :Enter, pane: 1
|
lines = tmux.until { |lines| lines.item_count == 1 }
|
||||||
|
end
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys :pwd, :Enter
|
tmux.send_keys :pwd, :Enter
|
||||||
@ -1313,16 +1334,29 @@ module TestShell
|
|||||||
tmux.send_keys 'echo 2nd', :Enter; tmux.prepare
|
tmux.send_keys 'echo 2nd', :Enter; tmux.prepare
|
||||||
tmux.send_keys 'echo 3d', :Enter; tmux.prepare
|
tmux.send_keys 'echo 3d', :Enter; tmux.prepare
|
||||||
tmux.send_keys 'echo 3rd', :Enter; tmux.prepare
|
tmux.send_keys 'echo 3rd', :Enter; tmux.prepare
|
||||||
tmux.send_keys 'echo 4th', :Enter; tmux.prepare
|
tmux.send_keys 'echo 4th', :Enter
|
||||||
tmux.send_keys 'C-r', pane: 0
|
retries do
|
||||||
tmux.until(1) { |lines| lines.item_count > 0 }
|
tmux.prepare
|
||||||
tmux.send_keys '3d', pane: 1
|
tmux.send_keys 'C-r'
|
||||||
tmux.until(1) { |lines| lines[-3].end_with? 'echo 3rd' } # --no-sort
|
tmux.until { |lines| lines.item_count > 0 }
|
||||||
tmux.send_keys :Enter, pane: 1
|
end
|
||||||
|
tmux.send_keys '3d'
|
||||||
|
tmux.until { |lines| lines[-3].end_with? 'echo 3rd' }
|
||||||
|
tmux.send_keys :Enter
|
||||||
tmux.until { |lines| lines[-1] == 'echo 3rd' }
|
tmux.until { |lines| lines[-1] == 'echo 3rd' }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until { |lines| lines[-1] == '3rd' }
|
tmux.until { |lines| lines[-1] == '3rd' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def retries times = 3, &block
|
||||||
|
(times - 1).times do |t|
|
||||||
|
begin
|
||||||
|
return block.call
|
||||||
|
rescue RuntimeError
|
||||||
|
end
|
||||||
|
end
|
||||||
|
block.call
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module CompletionTest
|
module CompletionTest
|
||||||
@ -1334,12 +1368,12 @@ module CompletionTest
|
|||||||
FileUtils.touch File.expand_path(f)
|
FileUtils.touch File.expand_path(f)
|
||||||
end
|
end
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'cat /tmp/fzf-test/10**', :Tab, pane: 0
|
tmux.send_keys 'cat /tmp/fzf-test/10**', :Tab
|
||||||
tmux.until(1) { |lines| lines.item_count > 0 }
|
tmux.until { |lines| lines.item_count > 0 }
|
||||||
tmux.send_keys ' !d'
|
tmux.send_keys ' !d'
|
||||||
tmux.until(1) { |lines| lines[-2].include?(' 2/') }
|
tmux.until { |lines| lines.match_count == 2 }
|
||||||
tmux.send_keys :BTab, :BTab
|
tmux.send_keys :Tab, :Tab
|
||||||
tmux.until(1) { |lines| lines[-2].include?('(2)') }
|
tmux.until { |lines| lines.select_count == 2 }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
tmux.send_keys 'C-L'
|
tmux.send_keys 'C-L'
|
||||||
@ -1349,10 +1383,10 @@ module CompletionTest
|
|||||||
|
|
||||||
# ~USERNAME**<TAB>
|
# ~USERNAME**<TAB>
|
||||||
tmux.send_keys 'C-u'
|
tmux.send_keys 'C-u'
|
||||||
tmux.send_keys "cat ~#{ENV['USER']}**", :Tab, pane: 0
|
tmux.send_keys "cat ~#{ENV['USER']}**", :Tab
|
||||||
tmux.until(1) { |lines| lines.item_count > 0 }
|
tmux.until { |lines| lines.item_count > 0 }
|
||||||
tmux.send_keys '.fzf-home'
|
tmux.send_keys "'.fzf-home"
|
||||||
tmux.until(1) { |lines| lines[-3].end_with? '.fzf-home' }
|
tmux.until { |lines| lines.select { |l| l.include? '.fzf-home' }.count > 1 }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
tmux.send_keys 'C-L'
|
tmux.send_keys 'C-L'
|
||||||
@ -1361,8 +1395,8 @@ module CompletionTest
|
|||||||
|
|
||||||
# ~INVALID_USERNAME**<TAB>
|
# ~INVALID_USERNAME**<TAB>
|
||||||
tmux.send_keys 'C-u'
|
tmux.send_keys 'C-u'
|
||||||
tmux.send_keys "cat ~such**", :Tab, pane: 0
|
tmux.send_keys "cat ~such**", :Tab
|
||||||
tmux.until(1) { |lines| lines[-3].end_with? 'no~such~user' }
|
tmux.until { |lines| lines.any_include? 'no~such~user' }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
tmux.send_keys 'C-L'
|
tmux.send_keys 'C-L'
|
||||||
@ -1371,9 +1405,11 @@ module CompletionTest
|
|||||||
|
|
||||||
# /tmp/fzf\ test**<TAB>
|
# /tmp/fzf\ test**<TAB>
|
||||||
tmux.send_keys 'C-u'
|
tmux.send_keys 'C-u'
|
||||||
tmux.send_keys 'cat /tmp/fzf\ test/**', :Tab, pane: 0
|
tmux.send_keys 'cat /tmp/fzf\ test/**', :Tab
|
||||||
tmux.until(1) { |lines| lines.item_count > 0 }
|
tmux.until { |lines| lines.item_count > 0 }
|
||||||
tmux.send_keys 'C-K', :Enter
|
tmux.send_keys 'foobar$'
|
||||||
|
tmux.until { |lines| lines.match_count == 1 }
|
||||||
|
tmux.send_keys :Enter
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
tmux.send_keys 'C-L'
|
tmux.send_keys 'C-L'
|
||||||
lines[-1].end_with?('/tmp/fzf\ test/foobar')
|
lines[-1].end_with?('/tmp/fzf\ test/foobar')
|
||||||
@ -1382,11 +1418,10 @@ module CompletionTest
|
|||||||
# Should include hidden files
|
# Should include hidden files
|
||||||
(1..100).each { |i| FileUtils.touch "/tmp/fzf-test/.hidden-#{i}" }
|
(1..100).each { |i| FileUtils.touch "/tmp/fzf-test/.hidden-#{i}" }
|
||||||
tmux.send_keys 'C-u'
|
tmux.send_keys 'C-u'
|
||||||
tmux.send_keys 'cat /tmp/fzf-test/hidden**', :Tab, pane: 0
|
tmux.send_keys 'cat /tmp/fzf-test/hidden**', :Tab
|
||||||
tmux.until(1) do |lines|
|
tmux.until do |lines|
|
||||||
tmux.send_keys 'C-L'
|
tmux.send_keys 'C-L'
|
||||||
lines[-2].include?('100/') &&
|
lines.match_count == 100 && lines.any_include?('/tmp/fzf-test/.hidden-')
|
||||||
lines[-3].include?('/tmp/fzf-test/.hidden-')
|
|
||||||
end
|
end
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
ensure
|
ensure
|
||||||
@ -1396,19 +1431,22 @@ module CompletionTest
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_file_completion_root
|
def test_file_completion_root
|
||||||
tmux.send_keys 'ls /**', :Tab, pane: 0
|
tmux.send_keys 'ls /**', :Tab
|
||||||
tmux.until(1) { |lines| lines.item_count > 0 }
|
tmux.until { |lines| lines.item_count > 0 }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_dir_completion
|
def test_dir_completion
|
||||||
tmux.send_keys 'mkdir -p /tmp/fzf-test/d{1..100}; touch /tmp/fzf-test/d55/xxx', :Enter
|
(1..100).each do |idx|
|
||||||
|
FileUtils.mkdir_p "/tmp/fzf-test/d#{idx}"
|
||||||
|
end
|
||||||
|
FileUtils.touch '/tmp/fzf-test/d55/xxx'
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'cd /tmp/fzf-test/**', :Tab, pane: 0
|
tmux.send_keys 'cd /tmp/fzf-test/**', :Tab
|
||||||
tmux.until(1) { |lines| lines.item_count > 0 }
|
tmux.until { |lines| lines.item_count > 0 }
|
||||||
tmux.send_keys :BTab, :BTab # BTab does not work here
|
tmux.send_keys :Tab, :Tab # Tab does not work here
|
||||||
tmux.send_keys 55
|
tmux.send_keys 55
|
||||||
tmux.until(1) { |lines| lines[-2].start_with? ' 1/' }
|
tmux.until { |lines| lines.match_count == 1 }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
tmux.send_keys 'C-L'
|
tmux.send_keys 'C-L'
|
||||||
@ -1435,14 +1473,15 @@ module CompletionTest
|
|||||||
lines = tmux.until { |lines| lines[-1].start_with? '[1]' }
|
lines = tmux.until { |lines| lines[-1].start_with? '[1]' }
|
||||||
pid = lines[-1].split.last
|
pid = lines[-1].split.last
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'kill ', :Tab, pane: 0
|
tmux.send_keys 'C-L'
|
||||||
tmux.until(1) { |lines| lines.item_count > 0 }
|
tmux.send_keys 'kill ', :Tab
|
||||||
|
tmux.until { |lines| lines.item_count > 0 }
|
||||||
tmux.send_keys 'sleep12345'
|
tmux.send_keys 'sleep12345'
|
||||||
tmux.until(1) { |lines| lines[-3].include? 'sleep 12345' }
|
tmux.until { |lines| lines.any_include? 'sleep 12345' }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
tmux.send_keys 'C-L'
|
tmux.send_keys 'C-L'
|
||||||
lines[-1] == "kill #{pid}"
|
lines[-1].include? "kill #{pid}"
|
||||||
end
|
end
|
||||||
ensure
|
ensure
|
||||||
Process.kill 'KILL', pid.to_i rescue nil if pid
|
Process.kill 'KILL', pid.to_i rescue nil if pid
|
||||||
@ -1451,10 +1490,10 @@ module CompletionTest
|
|||||||
def test_custom_completion
|
def test_custom_completion
|
||||||
tmux.send_keys '_fzf_compgen_path() { echo "\$1"; seq 10; }', :Enter
|
tmux.send_keys '_fzf_compgen_path() { echo "\$1"; seq 10; }', :Enter
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'ls /tmp/**', :Tab, pane: 0
|
tmux.send_keys 'ls /tmp/**', :Tab
|
||||||
tmux.until(1) { |lines| lines.item_count == 11 }
|
tmux.until { |lines| lines.item_count == 11 }
|
||||||
tmux.send_keys :BTab, :BTab, :BTab
|
tmux.send_keys :Tab, :Tab, :Tab
|
||||||
tmux.until(1) { |lines| lines[-2].include? '(3)' }
|
tmux.until { |lines| lines.select_count == 3 }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
tmux.send_keys 'C-L'
|
tmux.send_keys 'C-L'
|
||||||
@ -1463,49 +1502,48 @@ module CompletionTest
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_unset_completion
|
def test_unset_completion
|
||||||
tmux.send_keys 'export FOO=BAR', :Enter
|
tmux.send_keys 'export FZFFOOBAR=BAZ', :Enter
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
|
|
||||||
# Using tmux
|
# Using tmux
|
||||||
tmux.send_keys 'unset FOO**', :Tab, pane: 0
|
tmux.send_keys 'unset FZFFOO**', :Tab
|
||||||
tmux.until(1) { |lines| lines[-2].include? ' 1/' }
|
tmux.until { |lines| lines.match_count == 1 }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until { |lines| lines[-1] == 'unset FOO' }
|
tmux.until { |lines| lines[-1].include? 'unset FZFFOOBAR' }
|
||||||
tmux.send_keys 'C-c'
|
tmux.send_keys 'C-c'
|
||||||
|
|
||||||
# FZF_TMUX=0
|
# FZF_TMUX=1
|
||||||
new_shell
|
new_shell
|
||||||
tmux.send_keys 'unset FOO**', :Tab
|
tmux.send_keys 'unset FZFFO**', :Tab, pane: 0
|
||||||
tmux.until { |lines| lines[-2].include? ' 1/' }
|
tmux.until(1) { |lines| lines.match_count == 1 }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until { |lines| lines[-1] == 'unset FOO' }
|
tmux.until { |lines| lines[-1].include? 'unset FZFFOOBAR' }
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_file_completion_unicode
|
def test_file_completion_unicode
|
||||||
FileUtils.mkdir_p '/tmp/fzf-test'
|
FileUtils.mkdir_p '/tmp/fzf-test'
|
||||||
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
|
||||||
redraw = ->() { tmux.send_keys 'C-l', pane: 1 }
|
tmux.until { |lines| lines.match_count == 2 }
|
||||||
tmux.until(1) { |lines| redraw.(); lines[-2] =~ %r(^ *2/) }
|
|
||||||
|
|
||||||
tmux.send_keys '1', pane: 1
|
tmux.send_keys '1'
|
||||||
tmux.until(1) { |lines| redraw.(); lines[-2] =~ %r(^ *1/) }
|
tmux.until { |lines| lines.match_count == 1 }
|
||||||
tmux.send_keys :BTab, pane: 1
|
tmux.send_keys :Tab
|
||||||
tmux.until(1) { |lines| redraw.(); lines[-2].include? '(1)' }
|
tmux.until { |lines| lines.select_count == 1 }
|
||||||
|
|
||||||
tmux.send_keys :BSpace, pane: 1
|
tmux.send_keys :BSpace
|
||||||
tmux.until(1) { |lines| redraw.(); lines[-2] =~ %r(^ *2/) }
|
tmux.until { |lines| lines.match_count == 2 }
|
||||||
|
|
||||||
tmux.send_keys '2', pane: 1
|
tmux.send_keys '2'
|
||||||
tmux.until(1) { |lines| redraw.(); lines[-2] =~ %r(^ *1/) }
|
tmux.until { |lines| lines.select_count == 1 }
|
||||||
tmux.send_keys :BTab, pane: 1
|
tmux.send_keys :Tab
|
||||||
tmux.until(1) { |lines| redraw.(); lines[-2].include? '(2)' }
|
tmux.until { |lines| lines.select_count == 2 }
|
||||||
|
|
||||||
tmux.send_keys :Enter, pane: 1
|
tmux.send_keys :Enter
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
tmux.send_keys 'C-l'
|
tmux.send_keys 'C-l'
|
||||||
lines[-1].include?('cat') || lines[-2].include?('cat')
|
lines.any_include? 'cat'
|
||||||
end
|
end
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until { |lines| lines[-1].include? 'test3test4' }
|
tmux.until { |lines| lines[-1].include? 'test3test4' }
|
||||||
@ -1518,7 +1556,7 @@ class TestBash < TestBase
|
|||||||
|
|
||||||
def new_shell
|
def new_shell
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys "FZF_TMUX=0 #{Shell.bash}", :Enter
|
tmux.send_keys "FZF_TMUX=1 #{Shell.bash}", :Enter
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -1533,7 +1571,7 @@ class TestZsh < TestBase
|
|||||||
include CompletionTest
|
include CompletionTest
|
||||||
|
|
||||||
def new_shell
|
def new_shell
|
||||||
tmux.send_keys "FZF_TMUX=0 #{Shell.zsh}", :Enter
|
tmux.send_keys "FZF_TMUX=1 #{Shell.zsh}", :Enter
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -1547,7 +1585,7 @@ class TestFish < TestBase
|
|||||||
include TestShell
|
include TestShell
|
||||||
|
|
||||||
def new_shell
|
def new_shell
|
||||||
tmux.send_keys 'env FZF_TMUX=0 fish', :Enter
|
tmux.send_keys 'env FZF_TMUX=1 fish', :Enter
|
||||||
tmux.send_keys 'function fish_prompt; end; clear', :Enter
|
tmux.send_keys 'function fish_prompt; end; clear', :Enter
|
||||||
tmux.until { |lines| lines.empty? }
|
tmux.until { |lines| lines.empty? }
|
||||||
end
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user