mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-14 14:23:47 -05:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b0d0342d4 | ||
|
|
957c12e7d7 | ||
|
|
3b5ae0f8a2 | ||
|
|
1fc5659842 | ||
|
|
1acd2adce2 | ||
|
|
1bc223d4b3 | ||
|
|
bef405bfa5 | ||
|
|
0612074abe | ||
|
|
3bf51d8362 | ||
|
|
2c8479a7c5 | ||
|
|
8c8b5b313e | ||
|
|
66d55fd893 | ||
|
|
7fa5e6c861 | ||
|
|
00f96aae76 | ||
|
|
a749e6bd16 | ||
|
|
791076d366 |
21
CHANGELOG.md
21
CHANGELOG.md
@@ -1,6 +1,27 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
0.15.3
|
||||
------
|
||||
- Added support for more ANSI attributes: dim, underline, blink, and reverse
|
||||
- Fixed race condition in `toggle-preview`
|
||||
|
||||
0.15.2
|
||||
------
|
||||
- Preview window is now scrollable
|
||||
- With mouse scroll or with bindable actions
|
||||
- `preview-up`
|
||||
- `preview-down`
|
||||
- `preview-page-up`
|
||||
- `preview-page-down`
|
||||
- Updated ANSI processor to support high intensity colors and ignore
|
||||
some VT100-related escape sequences
|
||||
|
||||
0.15.1
|
||||
------
|
||||
- Fixed panic when the pattern occurs after 2^15-th column
|
||||
- Fixed rendering delay when displaying extremely long lines
|
||||
|
||||
0.15.0
|
||||
------
|
||||
- Improved fuzzy search algorithm
|
||||
|
||||
4
install
4
install
@@ -2,8 +2,8 @@
|
||||
|
||||
set -u
|
||||
|
||||
[[ "$@" =~ --pre ]] && version=0.15.0 pre=1 ||
|
||||
version=0.15.0 pre=0
|
||||
[[ "$@" =~ --pre ]] && version=0.15.3 pre=1 ||
|
||||
version=0.15.3 pre=0
|
||||
|
||||
auto_completion=
|
||||
key_bindings=
|
||||
|
||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
..
|
||||
.TH fzf-tmux 1 "Sep 2016" "fzf 0.15.0" "fzf-tmux - open fzf in tmux split pane"
|
||||
.TH fzf-tmux 1 "Sep 2016" "fzf 0.15.3" "fzf-tmux - open fzf in tmux split pane"
|
||||
|
||||
.SH NAME
|
||||
fzf-tmux - open fzf in tmux split pane
|
||||
|
||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
..
|
||||
.TH fzf 1 "Sep 2016" "fzf 0.15.0" "fzf - a command-line fuzzy finder"
|
||||
.TH fzf 1 "Sep 2016" "fzf 0.15.3" "fzf - a command-line fuzzy finder"
|
||||
|
||||
.SH NAME
|
||||
fzf - a command-line fuzzy finder
|
||||
@@ -434,6 +434,10 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
|
||||
\fBnext-history\fR (\fIctrl-n\fR on \fB--history\fR)
|
||||
\fBpage-down\fR \fIpgdn\fR
|
||||
\fBpage-up\fR \fIpgup\fR
|
||||
\fBpreview-down\fR
|
||||
\fBpreview-up\fR
|
||||
\fBpreview-page-down\fR
|
||||
\fBpreview-page-up\fR
|
||||
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
||||
\fBprint-query\fR (print query and exit)
|
||||
\fBselect-all\fR
|
||||
|
||||
@@ -32,7 +32,7 @@ fi
|
||||
|
||||
_fzf_orig_completion_filter() {
|
||||
sed 's/^\(.*-F\) *\([^ ]*\).* \([^ ]*\)$/export _fzf_orig_completion_\3="\1 %s \3 #\2";/' |
|
||||
awk -F= '{gsub(/[^a-z0-9_= ;]/, "_", $1); print $1"="$2}'
|
||||
awk -F= '{gsub(/[^A-Za-z0-9_= ;]/, "_", $1); print $1"="$2}'
|
||||
}
|
||||
|
||||
_fzf_opts_completion() {
|
||||
@@ -117,7 +117,7 @@ _fzf_handle_dynamic_completion() {
|
||||
__fzf_generic_path_completion() {
|
||||
local cur base dir leftover matches trigger cmd fzf
|
||||
[ "${FZF_TMUX:-1}" != 0 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
|
||||
cmd=$(echo "${COMP_WORDS[0]}" | sed 's/[^a-z0-9_=]/_/g')
|
||||
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
|
||||
COMPREPLY=()
|
||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
@@ -162,7 +162,7 @@ _fzf_complete() {
|
||||
type -t "$post" > /dev/null 2>&1 || post=cat
|
||||
[ "${FZF_TMUX:-1}" != 0 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
|
||||
|
||||
cmd=$(echo "${COMP_WORDS[0]}" | sed 's/[^a-z0-9_=]/_/g')
|
||||
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
|
||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
if [[ "$cur" == *"$trigger" ]]; then
|
||||
@@ -277,7 +277,7 @@ _fzf_defc() {
|
||||
cmd="$1"
|
||||
func="$2"
|
||||
opts="$3"
|
||||
orig_var="_fzf_orig_completion_$cmd"
|
||||
orig_var="_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}"
|
||||
orig="${!orig_var}"
|
||||
if [ -n "$orig" ]; then
|
||||
printf -v def "$orig" "$func"
|
||||
|
||||
@@ -257,13 +257,14 @@ func FuzzyMatchV2(caseSensitive bool, forward bool, input util.Chars, pattern []
|
||||
}
|
||||
|
||||
// Reuse pre-allocated integer slice to avoid unnecessary sweeping of garbages
|
||||
offset := 0
|
||||
offset16 := 0
|
||||
offset32 := 0
|
||||
// Bonus point for each position
|
||||
offset, B := alloc16(offset, slab, N, false)
|
||||
offset16, B := alloc16(offset16, slab, N, false)
|
||||
// The first occurrence of each character in the pattern
|
||||
offset, F := alloc16(offset, slab, M, false)
|
||||
offset32, F := alloc32(offset32, slab, M, false)
|
||||
// Rune array
|
||||
_, T := alloc32(0, slab, N, false)
|
||||
offset32, T := alloc32(offset32, slab, N, false)
|
||||
|
||||
// Phase 1. Check if there's a match and calculate bonus for each point
|
||||
pidx, lastIdx, prevClass := 0, 0, charNonWord
|
||||
@@ -291,7 +292,7 @@ func FuzzyMatchV2(caseSensitive bool, forward bool, input util.Chars, pattern []
|
||||
if pidx < M {
|
||||
if char == pattern[pidx] {
|
||||
lastIdx = idx
|
||||
F[pidx] = int16(idx)
|
||||
F[pidx] = int32(idx)
|
||||
pidx++
|
||||
}
|
||||
} else {
|
||||
@@ -307,10 +308,10 @@ func FuzzyMatchV2(caseSensitive bool, forward bool, input util.Chars, pattern []
|
||||
// Phase 2. Fill in score matrix (H)
|
||||
// Unlike the original algorithm, we do not allow omission.
|
||||
width := lastIdx - int(F[0]) + 1
|
||||
offset, H := alloc16(offset, slab, width*M, false)
|
||||
offset16, H := alloc16(offset16, slab, width*M, false)
|
||||
|
||||
// Possible length of consecutive chunk at each position.
|
||||
offset, C := alloc16(offset, slab, width*M, false)
|
||||
offset16, C := alloc16(offset16, slab, width*M, false)
|
||||
|
||||
maxScore, maxScorePos := int16(0), 0
|
||||
for i := 0; i < M; i++ {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package algo
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -154,3 +155,12 @@ func TestEmptyPattern(t *testing.T) {
|
||||
assertMatch(t, SuffixMatch, true, dir, "foobar", "", 6, 6, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLongString(t *testing.T) {
|
||||
bytes := make([]byte, math.MaxUint16*2)
|
||||
for i := range bytes {
|
||||
bytes[i] = 'x'
|
||||
}
|
||||
bytes[math.MaxUint16] = 'z'
|
||||
assertMatch(t, FuzzyMatchV2, true, true, string(bytes), "zx", math.MaxUint16, math.MaxUint16+2, scoreMatch*2+bonusConsecutive)
|
||||
}
|
||||
|
||||
32
src/ansi.go
32
src/ansi.go
@@ -6,6 +6,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/junegunn/fzf/src/curses"
|
||||
)
|
||||
|
||||
type ansiOffset struct {
|
||||
@@ -16,24 +18,24 @@ type ansiOffset struct {
|
||||
type ansiState struct {
|
||||
fg int
|
||||
bg int
|
||||
bold bool
|
||||
attr curses.Attr
|
||||
}
|
||||
|
||||
func (s *ansiState) colored() bool {
|
||||
return s.fg != -1 || s.bg != -1 || s.bold
|
||||
return s.fg != -1 || s.bg != -1 || s.attr > 0
|
||||
}
|
||||
|
||||
func (s *ansiState) equals(t *ansiState) bool {
|
||||
if t == nil {
|
||||
return !s.colored()
|
||||
}
|
||||
return s.fg == t.fg && s.bg == t.bg && s.bold == t.bold
|
||||
return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr
|
||||
}
|
||||
|
||||
var ansiRegex *regexp.Regexp
|
||||
|
||||
func init() {
|
||||
ansiRegex = regexp.MustCompile("\x1b\\[[0-9;]*[mK]")
|
||||
ansiRegex = regexp.MustCompile("\x1b.[0-9;]*.")
|
||||
}
|
||||
|
||||
func extractColor(str string, state *ansiState, proc func(string, *ansiState) bool) (string, *[]ansiOffset, *ansiState) {
|
||||
@@ -94,11 +96,11 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
||||
// State
|
||||
var state *ansiState
|
||||
if prevState == nil {
|
||||
state = &ansiState{-1, -1, false}
|
||||
state = &ansiState{-1, -1, 0}
|
||||
} else {
|
||||
state = &ansiState{prevState.fg, prevState.bg, prevState.bold}
|
||||
state = &ansiState{prevState.fg, prevState.bg, prevState.attr}
|
||||
}
|
||||
if ansiCode[len(ansiCode)-1] == 'K' {
|
||||
if ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' {
|
||||
return state
|
||||
}
|
||||
|
||||
@@ -108,7 +110,7 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
||||
init := func() {
|
||||
state.fg = -1
|
||||
state.bg = -1
|
||||
state.bold = false
|
||||
state.attr = 0
|
||||
state256 = 0
|
||||
}
|
||||
|
||||
@@ -132,7 +134,15 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
||||
case 49:
|
||||
state.bg = -1
|
||||
case 1:
|
||||
state.bold = true
|
||||
state.attr = curses.Bold
|
||||
case 2:
|
||||
state.attr = curses.Dim
|
||||
case 4:
|
||||
state.attr = curses.Underline
|
||||
case 5:
|
||||
state.attr = curses.Blink
|
||||
case 7:
|
||||
state.attr = curses.Reverse
|
||||
case 0:
|
||||
init()
|
||||
default:
|
||||
@@ -140,6 +150,10 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
||||
state.fg = num - 30
|
||||
} else if num >= 40 && num <= 47 {
|
||||
state.bg = num - 40
|
||||
} else if num >= 90 && num <= 97 {
|
||||
state.fg = num - 90 + 8
|
||||
} else if num >= 100 && num <= 107 {
|
||||
state.bg = num - 100 + 8
|
||||
}
|
||||
}
|
||||
case 1:
|
||||
|
||||
@@ -3,13 +3,19 @@ package fzf
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/junegunn/fzf/src/curses"
|
||||
)
|
||||
|
||||
func TestExtractColor(t *testing.T) {
|
||||
assert := func(offset ansiOffset, b int32, e int32, fg int, bg int, bold bool) {
|
||||
var attr curses.Attr
|
||||
if bold {
|
||||
attr = curses.Bold
|
||||
}
|
||||
if offset.offset[0] != b || offset.offset[1] != e ||
|
||||
offset.color.fg != fg || offset.color.bg != bg || offset.color.bold != bold {
|
||||
t.Error(offset, b, e, fg, bg, bold)
|
||||
offset.color.fg != fg || offset.color.bg != bg || offset.color.attr != attr {
|
||||
t.Error(offset, b, e, fg, bg, attr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +127,7 @@ func TestExtractColor(t *testing.T) {
|
||||
if len(*offsets) != 1 {
|
||||
t.Fail()
|
||||
}
|
||||
if state.fg != 2 || state.bg != -1 || !state.bold {
|
||||
if state.fg != 2 || state.bg != -1 || state.attr == 0 {
|
||||
t.Fail()
|
||||
}
|
||||
assert((*offsets)[0], 6, 11, 2, -1, true)
|
||||
@@ -132,7 +138,7 @@ func TestExtractColor(t *testing.T) {
|
||||
if len(*offsets) != 1 {
|
||||
t.Fail()
|
||||
}
|
||||
if state.fg != 2 || state.bg != -1 || !state.bold {
|
||||
if state.fg != 2 || state.bg != -1 || state.attr == 0 {
|
||||
t.Fail()
|
||||
}
|
||||
assert((*offsets)[0], 0, 11, 2, -1, true)
|
||||
@@ -143,7 +149,7 @@ func TestExtractColor(t *testing.T) {
|
||||
if len(*offsets) != 2 {
|
||||
t.Fail()
|
||||
}
|
||||
if state.fg != 200 || state.bg != 100 || state.bold {
|
||||
if state.fg != 200 || state.bg != 100 || state.attr > 0 {
|
||||
t.Fail()
|
||||
}
|
||||
assert((*offsets)[0], 0, 6, 2, -1, true)
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
const (
|
||||
// Current version
|
||||
version = "0.15.0"
|
||||
version = "0.15.3"
|
||||
|
||||
// Core
|
||||
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
||||
|
||||
@@ -23,6 +23,16 @@ import (
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const (
|
||||
Bold = C.A_BOLD
|
||||
Dim = C.A_DIM
|
||||
Blink = C.A_BLINK
|
||||
Reverse = C.A_REVERSE
|
||||
Underline = C.A_UNDERLINE
|
||||
)
|
||||
|
||||
type Attr C.int
|
||||
|
||||
// Types of user action
|
||||
const (
|
||||
Rune = iota
|
||||
@@ -158,7 +168,7 @@ type MouseEvent struct {
|
||||
var (
|
||||
_buf []byte
|
||||
_in *os.File
|
||||
_color func(int, bool) C.int
|
||||
_color func(int, Attr) C.int
|
||||
_colorMap map[int]int
|
||||
_prevDownTime time.Time
|
||||
_clickY []int
|
||||
@@ -183,7 +193,7 @@ type Window struct {
|
||||
func NewWindow(top int, left int, width int, height int, border bool) *Window {
|
||||
win := C.newwin(C.int(height), C.int(width), C.int(top), C.int(left))
|
||||
if border {
|
||||
attr := _color(ColBorder, false)
|
||||
attr := _color(ColBorder, 0)
|
||||
C.wattron(win, attr)
|
||||
C.box(win, 0, 0)
|
||||
C.wattroff(win, attr)
|
||||
@@ -266,22 +276,19 @@ func init() {
|
||||
Border: 145}
|
||||
}
|
||||
|
||||
func attrColored(pair int, bold bool) C.int {
|
||||
func attrColored(pair int, a Attr) C.int {
|
||||
var attr C.int
|
||||
if pair > ColNormal {
|
||||
attr = C.COLOR_PAIR(C.int(pair))
|
||||
}
|
||||
if bold {
|
||||
attr = attr | C.A_BOLD
|
||||
}
|
||||
return attr
|
||||
return attr | C.int(a)
|
||||
}
|
||||
|
||||
func attrMono(pair int, bold bool) C.int {
|
||||
func attrMono(pair int, a Attr) C.int {
|
||||
var attr C.int
|
||||
switch pair {
|
||||
case ColCurrent:
|
||||
if bold {
|
||||
if a&C.A_BOLD == C.A_BOLD {
|
||||
attr = C.A_REVERSE
|
||||
}
|
||||
case ColMatch:
|
||||
@@ -289,7 +296,7 @@ func attrMono(pair int, bold bool) C.int {
|
||||
case ColCurrentMatch:
|
||||
attr = C.A_UNDERLINE | C.A_REVERSE
|
||||
}
|
||||
if bold {
|
||||
if a&C.A_BOLD == C.A_BOLD {
|
||||
attr = attr | C.A_BOLD
|
||||
}
|
||||
return attr
|
||||
@@ -648,8 +655,8 @@ func (w *Window) Print(text string) {
|
||||
}, text)))
|
||||
}
|
||||
|
||||
func (w *Window) CPrint(pair int, bold bool, text string) {
|
||||
attr := _color(pair, bold)
|
||||
func (w *Window) CPrint(pair int, a Attr, text string) {
|
||||
attr := _color(pair, a)
|
||||
C.wattron(w.win, attr)
|
||||
w.Print(text)
|
||||
C.wattroff(w.win, attr)
|
||||
@@ -675,8 +682,8 @@ func (w *Window) Fill(str string) bool {
|
||||
return C.waddstr(w.win, C.CString(str)) == C.OK
|
||||
}
|
||||
|
||||
func (w *Window) CFill(str string, fg int, bg int, bold bool) bool {
|
||||
attr := _color(PairFor(fg, bg), bold)
|
||||
func (w *Window) CFill(str string, fg int, bg int, a Attr) bool {
|
||||
attr := _color(PairFor(fg, bg), a)
|
||||
C.wattron(w.win, attr)
|
||||
ret := w.Fill(str)
|
||||
C.wattroff(w.win, attr)
|
||||
|
||||
@@ -663,6 +663,14 @@ func parseKeymap(keymap map[int]actionType, execmap map[int]string, str string)
|
||||
keymap[key] = actTogglePreview
|
||||
case "toggle-sort":
|
||||
keymap[key] = actToggleSort
|
||||
case "preview-up":
|
||||
keymap[key] = actPreviewUp
|
||||
case "preview-down":
|
||||
keymap[key] = actPreviewDown
|
||||
case "preview-page-up":
|
||||
keymap[key] = actPreviewPageUp
|
||||
case "preview-page-down":
|
||||
keymap[key] = actPreviewPageDown
|
||||
default:
|
||||
if isExecuteAction(actLower) {
|
||||
var offset int
|
||||
|
||||
@@ -342,7 +342,7 @@ func TestDefaultCtrlNP(t *testing.T) {
|
||||
check([]string{"--bind=ctrl-n:accept"}, curses.CtrlN, actAccept)
|
||||
check([]string{"--bind=ctrl-p:accept"}, curses.CtrlP, actAccept)
|
||||
|
||||
hist := "--history=/tmp/foo"
|
||||
hist := "--history=/tmp/fzf-history"
|
||||
check([]string{hist}, curses.CtrlN, actNextHistory)
|
||||
check([]string{hist}, curses.CtrlP, actPreviousHistory)
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ type Offset [2]int32
|
||||
type colorOffset struct {
|
||||
offset [2]int32
|
||||
color int
|
||||
bold bool
|
||||
attr curses.Attr
|
||||
index int32
|
||||
}
|
||||
|
||||
@@ -91,14 +91,14 @@ func minRank() rank {
|
||||
return rank{index: 0, points: [4]uint16{math.MaxUint16, 0, 0, 0}}
|
||||
}
|
||||
|
||||
func (result *Result) colorOffsets(matchOffsets []Offset, color int, bold bool, current bool) []colorOffset {
|
||||
func (result *Result) colorOffsets(matchOffsets []Offset, color int, attr curses.Attr, current bool) []colorOffset {
|
||||
itemColors := result.item.Colors()
|
||||
|
||||
if len(itemColors) == 0 {
|
||||
var offsets []colorOffset
|
||||
for _, off := range matchOffsets {
|
||||
|
||||
offsets = append(offsets, colorOffset{offset: [2]int32{off[0], off[1]}, color: color, bold: bold})
|
||||
offsets = append(offsets, colorOffset{offset: [2]int32{off[0], off[1]}, color: color, attr: attr})
|
||||
}
|
||||
return offsets
|
||||
}
|
||||
@@ -142,7 +142,7 @@ func (result *Result) colorOffsets(matchOffsets []Offset, color int, bold bool,
|
||||
if curr != 0 && idx > start {
|
||||
if curr == -1 {
|
||||
colors = append(colors, colorOffset{
|
||||
offset: [2]int32{int32(start), int32(idx)}, color: color, bold: bold})
|
||||
offset: [2]int32{int32(start), int32(idx)}, color: color, attr: attr})
|
||||
} else {
|
||||
ansi := itemColors[curr-1]
|
||||
fg := ansi.color.fg
|
||||
@@ -164,7 +164,7 @@ func (result *Result) colorOffsets(matchOffsets []Offset, color int, bold bool,
|
||||
colors = append(colors, colorOffset{
|
||||
offset: [2]int32{int32(start), int32(idx)},
|
||||
color: curses.PairFor(fg, bg),
|
||||
bold: ansi.color.bold || bold})
|
||||
attr: ansi.color.attr | attr})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,16 +97,20 @@ func TestColorOffset(t *testing.T) {
|
||||
item := Result{
|
||||
item: &Item{
|
||||
colors: &[]ansiOffset{
|
||||
ansiOffset{[2]int32{0, 20}, ansiState{1, 5, false}},
|
||||
ansiOffset{[2]int32{22, 27}, ansiState{2, 6, true}},
|
||||
ansiOffset{[2]int32{30, 32}, ansiState{3, 7, false}},
|
||||
ansiOffset{[2]int32{33, 40}, ansiState{4, 8, true}}}}}
|
||||
ansiOffset{[2]int32{0, 20}, ansiState{1, 5, 0}},
|
||||
ansiOffset{[2]int32{22, 27}, ansiState{2, 6, curses.Bold}},
|
||||
ansiOffset{[2]int32{30, 32}, ansiState{3, 7, 0}},
|
||||
ansiOffset{[2]int32{33, 40}, ansiState{4, 8, curses.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}]
|
||||
|
||||
colors := item.colorOffsets(offsets, 99, false, true)
|
||||
colors := item.colorOffsets(offsets, 99, 0, true)
|
||||
assert := func(idx int, b int32, e int32, c int, bold bool) {
|
||||
var attr curses.Attr
|
||||
if bold {
|
||||
attr = curses.Bold
|
||||
}
|
||||
o := colors[idx]
|
||||
if o.offset[0] != b || o.offset[1] != e || o.color != c || o.bold != bold {
|
||||
if o.offset[0] != b || o.offset[1] != e || o.color != c || o.attr != attr {
|
||||
t.Error(o)
|
||||
}
|
||||
}
|
||||
|
||||
133
src/terminal.go
133
src/terminal.go
@@ -28,6 +28,13 @@ const (
|
||||
jumpAcceptEnabled
|
||||
)
|
||||
|
||||
type previewer struct {
|
||||
text string
|
||||
lines int
|
||||
offset int
|
||||
enabled bool
|
||||
}
|
||||
|
||||
// Terminal represents terminal input/output
|
||||
type Terminal struct {
|
||||
initDelay time.Duration
|
||||
@@ -68,8 +75,7 @@ type Terminal struct {
|
||||
selected map[int32]selectedItem
|
||||
reqBox *util.EventBox
|
||||
preview previewOpts
|
||||
previewing bool
|
||||
previewTxt string
|
||||
previewer previewer
|
||||
previewBox *util.EventBox
|
||||
eventBox *util.EventBox
|
||||
mutex sync.Mutex
|
||||
@@ -119,6 +125,7 @@ const (
|
||||
reqPrintQuery
|
||||
reqPreviewEnqueue
|
||||
reqPreviewDisplay
|
||||
reqPreviewRefresh
|
||||
reqQuit
|
||||
)
|
||||
|
||||
@@ -165,6 +172,10 @@ const (
|
||||
actPrintQuery
|
||||
actToggleSort
|
||||
actTogglePreview
|
||||
actPreviewUp
|
||||
actPreviewDown
|
||||
actPreviewPageUp
|
||||
actPreviewPageDown
|
||||
actPreviousHistory
|
||||
actNextHistory
|
||||
actExecute
|
||||
@@ -275,8 +286,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||
selected: make(map[int32]selectedItem),
|
||||
reqBox: util.NewEventBox(),
|
||||
preview: opts.Preview,
|
||||
previewing: previewBox != nil && !opts.Preview.hidden,
|
||||
previewTxt: "",
|
||||
previewer: previewer{"", 0, 0, previewBox != nil && !opts.Preview.hidden},
|
||||
previewBox: previewBox,
|
||||
eventBox: eventBox,
|
||||
mutex: sync.Mutex{},
|
||||
@@ -401,6 +411,8 @@ func displayWidth(runes []rune) int {
|
||||
const (
|
||||
minWidth = 16
|
||||
minHeight = 4
|
||||
|
||||
maxDisplayWidthCalc = 1024
|
||||
)
|
||||
|
||||
func calculateSize(base int, size sizeSpec, margin int, minSize int) int {
|
||||
@@ -514,24 +526,24 @@ func (t *Terminal) placeCursor() {
|
||||
|
||||
func (t *Terminal) printPrompt() {
|
||||
t.move(0, 0, true)
|
||||
t.window.CPrint(C.ColPrompt, true, t.prompt)
|
||||
t.window.CPrint(C.ColNormal, true, string(t.input))
|
||||
t.window.CPrint(C.ColPrompt, C.Bold, t.prompt)
|
||||
t.window.CPrint(C.ColNormal, C.Bold, string(t.input))
|
||||
}
|
||||
|
||||
func (t *Terminal) printInfo() {
|
||||
if t.inlineInfo {
|
||||
t.move(0, displayWidth([]rune(t.prompt))+displayWidth(t.input)+1, true)
|
||||
if t.reading {
|
||||
t.window.CPrint(C.ColSpinner, true, " < ")
|
||||
t.window.CPrint(C.ColSpinner, C.Bold, " < ")
|
||||
} else {
|
||||
t.window.CPrint(C.ColPrompt, true, " < ")
|
||||
t.window.CPrint(C.ColPrompt, C.Bold, " < ")
|
||||
}
|
||||
} else {
|
||||
t.move(1, 0, true)
|
||||
if t.reading {
|
||||
duration := int64(spinnerDuration)
|
||||
idx := (time.Now().UnixNano() % (duration * int64(len(_spinner)))) / duration
|
||||
t.window.CPrint(C.ColSpinner, true, _spinner[idx])
|
||||
t.window.CPrint(C.ColSpinner, C.Bold, _spinner[idx])
|
||||
}
|
||||
t.move(1, 2, false)
|
||||
}
|
||||
@@ -550,7 +562,7 @@ func (t *Terminal) printInfo() {
|
||||
if t.progress > 0 && t.progress < 100 {
|
||||
output += fmt.Sprintf(" (%d%%)", t.progress)
|
||||
}
|
||||
t.window.CPrint(C.ColInfo, false, output)
|
||||
t.window.CPrint(C.ColInfo, 0, output)
|
||||
}
|
||||
|
||||
func (t *Terminal) printHeader() {
|
||||
@@ -574,7 +586,7 @@ func (t *Terminal) printHeader() {
|
||||
colors: colors}
|
||||
|
||||
t.move(line, 2, true)
|
||||
t.printHighlighted(&Result{item: item}, false, C.ColHeader, 0, false)
|
||||
t.printHighlighted(&Result{item: item}, 0, C.ColHeader, 0, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -608,21 +620,21 @@ func (t *Terminal) printItem(result *Result, i int, current bool) {
|
||||
} else if current {
|
||||
label = ">"
|
||||
}
|
||||
t.window.CPrint(C.ColCursor, true, label)
|
||||
t.window.CPrint(C.ColCursor, C.Bold, label)
|
||||
if current {
|
||||
if selected {
|
||||
t.window.CPrint(C.ColSelected, true, ">")
|
||||
t.window.CPrint(C.ColSelected, C.Bold, ">")
|
||||
} else {
|
||||
t.window.CPrint(C.ColCurrent, true, " ")
|
||||
t.window.CPrint(C.ColCurrent, C.Bold, " ")
|
||||
}
|
||||
t.printHighlighted(result, true, C.ColCurrent, C.ColCurrentMatch, true)
|
||||
t.printHighlighted(result, C.Bold, C.ColCurrent, C.ColCurrentMatch, true)
|
||||
} else {
|
||||
if selected {
|
||||
t.window.CPrint(C.ColSelected, true, ">")
|
||||
t.window.CPrint(C.ColSelected, C.Bold, ">")
|
||||
} else {
|
||||
t.window.Print(" ")
|
||||
}
|
||||
t.printHighlighted(result, false, 0, C.ColMatch, false)
|
||||
t.printHighlighted(result, 0, 0, C.ColMatch, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -651,6 +663,11 @@ func displayWidthWithLimit(runes []rune, prefixWidth int, limit int) int {
|
||||
}
|
||||
|
||||
func trimLeft(runes []rune, width int) ([]rune, int32) {
|
||||
if len(runes) > maxDisplayWidthCalc && len(runes) > width {
|
||||
trimmed := len(runes) - width
|
||||
return runes[trimmed:], int32(trimmed)
|
||||
}
|
||||
|
||||
currentWidth := displayWidth(runes)
|
||||
var trimmed int32
|
||||
|
||||
@@ -673,7 +690,7 @@ func overflow(runes []rune, max int) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *Terminal) printHighlighted(result *Result, bold bool, col1 int, col2 int, current bool) {
|
||||
func (t *Terminal) printHighlighted(result *Result, attr C.Attr, col1 int, col2 int, current bool) {
|
||||
item := result.item
|
||||
|
||||
// Overflow
|
||||
@@ -698,7 +715,7 @@ func (t *Terminal) printHighlighted(result *Result, bold bool, col1 int, col2 in
|
||||
maxe = util.Max(maxe, int(offset[1]))
|
||||
}
|
||||
|
||||
offsets := result.colorOffsets(charOffsets, col2, bold, current)
|
||||
offsets := result.colorOffsets(charOffsets, col2, attr, current)
|
||||
maxWidth := t.window.Width - 3
|
||||
maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text))
|
||||
if overflow(text, maxWidth) {
|
||||
@@ -747,11 +764,11 @@ func (t *Terminal) printHighlighted(result *Result, bold bool, col1 int, col2 in
|
||||
e := util.Constrain32(offset.offset[1], index, maxOffset)
|
||||
|
||||
substr, prefixWidth = processTabs(text[index:b], prefixWidth)
|
||||
t.window.CPrint(col1, bold, substr)
|
||||
t.window.CPrint(col1, attr, substr)
|
||||
|
||||
if b < e {
|
||||
substr, prefixWidth = processTabs(text[b:e], prefixWidth)
|
||||
t.window.CPrint(offset.color, offset.bold, substr)
|
||||
t.window.CPrint(offset.color, offset.attr, substr)
|
||||
}
|
||||
|
||||
index = e
|
||||
@@ -761,15 +778,44 @@ func (t *Terminal) printHighlighted(result *Result, bold bool, col1 int, col2 in
|
||||
}
|
||||
if index < maxOffset {
|
||||
substr, _ = processTabs(text[index:], prefixWidth)
|
||||
t.window.CPrint(col1, bold, substr)
|
||||
t.window.CPrint(col1, attr, substr)
|
||||
}
|
||||
}
|
||||
|
||||
func numLinesMax(str string, max int) int {
|
||||
lines := 0
|
||||
for lines < max {
|
||||
idx := strings.Index(str, "\n")
|
||||
if idx < 0 {
|
||||
break
|
||||
}
|
||||
str = str[idx+1:]
|
||||
lines++
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
func (t *Terminal) printPreview() {
|
||||
if !t.isPreviewEnabled() {
|
||||
return
|
||||
}
|
||||
t.pwindow.Erase()
|
||||
extractColor(t.previewTxt, nil, func(str string, ansi *ansiState) bool {
|
||||
skip := t.previewer.offset
|
||||
extractColor(t.previewer.text, nil, func(str string, ansi *ansiState) bool {
|
||||
if skip > 0 {
|
||||
newlines := numLinesMax(str, skip)
|
||||
if skip <= newlines {
|
||||
for i := 0; i < skip; i++ {
|
||||
str = str[strings.Index(str, "\n")+1:]
|
||||
}
|
||||
skip = 0
|
||||
} else {
|
||||
skip -= newlines
|
||||
return true
|
||||
}
|
||||
}
|
||||
if ansi != nil && ansi.colored() {
|
||||
return t.pwindow.CFill(str, ansi.fg, ansi.bg, ansi.bold)
|
||||
return t.pwindow.CFill(str, ansi.fg, ansi.bg, ansi.attr)
|
||||
}
|
||||
return t.pwindow.Fill(str)
|
||||
})
|
||||
@@ -796,9 +842,7 @@ func (t *Terminal) printAll() {
|
||||
t.printPrompt()
|
||||
t.printInfo()
|
||||
t.printHeader()
|
||||
if t.isPreviewEnabled() {
|
||||
t.printPreview()
|
||||
}
|
||||
t.printPreview()
|
||||
}
|
||||
|
||||
func (t *Terminal) refresh() {
|
||||
@@ -884,7 +928,7 @@ func (t *Terminal) hasPreviewWindow() bool {
|
||||
}
|
||||
|
||||
func (t *Terminal) isPreviewEnabled() bool {
|
||||
return t.previewBox != nil && t.previewing
|
||||
return t.previewBox != nil && t.previewer.enabled
|
||||
}
|
||||
|
||||
func (t *Terminal) current() string {
|
||||
@@ -1026,7 +1070,11 @@ func (t *Terminal) Loop() {
|
||||
}
|
||||
exit(exitNoMatch)
|
||||
case reqPreviewDisplay:
|
||||
t.previewTxt = value.(string)
|
||||
t.previewer.text = value.(string)
|
||||
t.previewer.lines = strings.Count(t.previewer.text, "\n")
|
||||
t.previewer.offset = 0
|
||||
t.printPreview()
|
||||
case reqPreviewRefresh:
|
||||
t.printPreview()
|
||||
case reqPrintQuery:
|
||||
C.Close()
|
||||
@@ -1078,6 +1126,11 @@ func (t *Terminal) Loop() {
|
||||
req(reqInfo)
|
||||
}
|
||||
}
|
||||
scrollPreview := func(amount int) {
|
||||
t.previewer.offset = util.Constrain(
|
||||
t.previewer.offset+amount, 0, t.previewer.lines-t.pwindow.Height)
|
||||
req(reqPreviewRefresh)
|
||||
}
|
||||
for key, ret := range t.expect {
|
||||
if keyMatch(key, event) {
|
||||
t.pressed = ret
|
||||
@@ -1111,10 +1164,10 @@ func (t *Terminal) Loop() {
|
||||
return false
|
||||
case actTogglePreview:
|
||||
if t.hasPreviewWindow() {
|
||||
t.previewing = !t.previewing
|
||||
t.previewer.enabled = !t.previewer.enabled
|
||||
t.resizeWindows()
|
||||
cnt := t.merger.Length()
|
||||
if t.previewing && cnt > 0 && cnt > t.cy {
|
||||
if t.previewer.enabled && cnt > 0 && cnt > t.cy {
|
||||
t.previewBox.Set(reqPreviewEnqueue, previewRequest{true, t.current()})
|
||||
}
|
||||
req(reqList, reqInfo)
|
||||
@@ -1124,6 +1177,22 @@ func (t *Terminal) Loop() {
|
||||
t.eventBox.Set(EvtSearchNew, t.sort)
|
||||
t.mutex.Unlock()
|
||||
return false
|
||||
case actPreviewUp:
|
||||
if t.isPreviewEnabled() {
|
||||
scrollPreview(-1)
|
||||
}
|
||||
case actPreviewDown:
|
||||
if t.isPreviewEnabled() {
|
||||
scrollPreview(1)
|
||||
}
|
||||
case actPreviewPageUp:
|
||||
if t.isPreviewEnabled() {
|
||||
scrollPreview(-t.pwindow.Height)
|
||||
}
|
||||
case actPreviewPageDown:
|
||||
if t.isPreviewEnabled() {
|
||||
scrollPreview(t.pwindow.Height)
|
||||
}
|
||||
case actBeginningOfLine:
|
||||
t.cx = 0
|
||||
case actBackwardChar:
|
||||
@@ -1292,6 +1361,8 @@ func (t *Terminal) Loop() {
|
||||
}
|
||||
t.vmove(me.S)
|
||||
req(reqList)
|
||||
} else if t.isPreviewEnabled() && t.pwindow.Enclose(my, mx) {
|
||||
scrollPreview(-me.S)
|
||||
}
|
||||
} else if t.window.Enclose(my, mx) {
|
||||
mx -= t.window.Left
|
||||
|
||||
Reference in New Issue
Block a user