m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-15 06:43:47 -05:00

Compare commits

..

5 Commits

Author SHA1 Message Date
Junegunn Choi
8b0d0342d4 0.15.3 2016-09-29 03:05:20 +09:00
Junegunn Choi
957c12e7d7 Fix SEGV when trying to render preview but the window is closed
Close #677
2016-09-29 02:53:05 +09:00
Junegunn Choi
3b5ae0f8a2 Fix failing unit tests on ANSI attributes 2016-09-29 01:06:47 +09:00
Junegunn Choi
1fc5659842 Add support for more ANSI color attributes (#674)
Dim, underline, blink, reverse
2016-09-29 00:54:27 +09:00
Junegunn Choi
1acd2adce2 Update man page: missing actions 2016-09-26 15:33:46 +09:00
11 changed files with 101 additions and 64 deletions

View File

@@ -1,6 +1,11 @@
CHANGELOG CHANGELOG
========= =========
0.15.3
------
- Added support for more ANSI attributes: dim, underline, blink, and reverse
- Fixed race condition in `toggle-preview`
0.15.2 0.15.2
------ ------
- Preview window is now scrollable - Preview window is now scrollable

View File

@@ -2,8 +2,8 @@
set -u set -u
[[ "$@" =~ --pre ]] && version=0.15.2 pre=1 || [[ "$@" =~ --pre ]] && version=0.15.3 pre=1 ||
version=0.15.2 pre=0 version=0.15.3 pre=0
auto_completion= auto_completion=
key_bindings= key_bindings=

View File

@@ -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 "Sep 2016" "fzf 0.15.2" "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 .SH NAME
fzf-tmux - open fzf in tmux split pane fzf-tmux - open fzf in tmux split pane

View File

@@ -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 "Sep 2016" "fzf 0.15.2" "fzf - a command-line fuzzy finder" .TH fzf 1 "Sep 2016" "fzf 0.15.3" "fzf - a command-line fuzzy finder"
.SH NAME .SH NAME
fzf - a command-line fuzzy finder 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) \fBnext-history\fR (\fIctrl-n\fR on \fB--history\fR)
\fBpage-down\fR \fIpgdn\fR \fBpage-down\fR \fIpgdn\fR
\fBpage-up\fR \fIpgup\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) \fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
\fBprint-query\fR (print query and exit) \fBprint-query\fR (print query and exit)
\fBselect-all\fR \fBselect-all\fR

View File

@@ -6,6 +6,8 @@ import (
"strconv" "strconv"
"strings" "strings"
"unicode/utf8" "unicode/utf8"
"github.com/junegunn/fzf/src/curses"
) )
type ansiOffset struct { type ansiOffset struct {
@@ -16,18 +18,18 @@ type ansiOffset struct {
type ansiState struct { type ansiState struct {
fg int fg int
bg int bg int
bold bool attr curses.Attr
} }
func (s *ansiState) colored() bool { 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 { func (s *ansiState) equals(t *ansiState) bool {
if t == nil { if t == nil {
return !s.colored() 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 var ansiRegex *regexp.Regexp
@@ -94,9 +96,9 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
// State // State
var state *ansiState var state *ansiState
if prevState == nil { if prevState == nil {
state = &ansiState{-1, -1, false} state = &ansiState{-1, -1, 0}
} else { } else {
state = &ansiState{prevState.fg, prevState.bg, prevState.bold} state = &ansiState{prevState.fg, prevState.bg, prevState.attr}
} }
if ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' { if ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' {
return state return state
@@ -108,7 +110,7 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
init := func() { init := func() {
state.fg = -1 state.fg = -1
state.bg = -1 state.bg = -1
state.bold = false state.attr = 0
state256 = 0 state256 = 0
} }
@@ -132,7 +134,15 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
case 49: case 49:
state.bg = -1 state.bg = -1
case 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: case 0:
init() init()
default: default:

View File

@@ -3,13 +3,19 @@ package fzf
import ( import (
"fmt" "fmt"
"testing" "testing"
"github.com/junegunn/fzf/src/curses"
) )
func TestExtractColor(t *testing.T) { func TestExtractColor(t *testing.T) {
assert := func(offset ansiOffset, b int32, e int32, fg int, bg int, bold bool) { assert := func(offset ansiOffset, b int32, e int32, fg int, bg int, bold bool) {
var attr curses.Attr
if bold {
attr = curses.Bold
}
if offset.offset[0] != b || offset.offset[1] != e || if offset.offset[0] != b || offset.offset[1] != e ||
offset.color.fg != fg || offset.color.bg != bg || offset.color.bold != bold { offset.color.fg != fg || offset.color.bg != bg || offset.color.attr != attr {
t.Error(offset, b, e, fg, bg, bold) t.Error(offset, b, e, fg, bg, attr)
} }
} }
@@ -121,7 +127,7 @@ func TestExtractColor(t *testing.T) {
if len(*offsets) != 1 { if len(*offsets) != 1 {
t.Fail() t.Fail()
} }
if state.fg != 2 || state.bg != -1 || !state.bold { if state.fg != 2 || state.bg != -1 || state.attr == 0 {
t.Fail() t.Fail()
} }
assert((*offsets)[0], 6, 11, 2, -1, true) assert((*offsets)[0], 6, 11, 2, -1, true)
@@ -132,7 +138,7 @@ func TestExtractColor(t *testing.T) {
if len(*offsets) != 1 { if len(*offsets) != 1 {
t.Fail() t.Fail()
} }
if state.fg != 2 || state.bg != -1 || !state.bold { if state.fg != 2 || state.bg != -1 || state.attr == 0 {
t.Fail() t.Fail()
} }
assert((*offsets)[0], 0, 11, 2, -1, true) assert((*offsets)[0], 0, 11, 2, -1, true)
@@ -143,7 +149,7 @@ func TestExtractColor(t *testing.T) {
if len(*offsets) != 2 { if len(*offsets) != 2 {
t.Fail() t.Fail()
} }
if state.fg != 200 || state.bg != 100 || state.bold { if state.fg != 200 || state.bg != 100 || state.attr > 0 {
t.Fail() t.Fail()
} }
assert((*offsets)[0], 0, 6, 2, -1, true) assert((*offsets)[0], 0, 6, 2, -1, true)

View File

@@ -8,7 +8,7 @@ import (
const ( const (
// Current version // Current version
version = "0.15.2" version = "0.15.3"
// Core // Core
coordinatorDelayMax time.Duration = 100 * time.Millisecond coordinatorDelayMax time.Duration = 100 * time.Millisecond

View File

@@ -23,6 +23,16 @@ import (
"unicode/utf8" "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 // Types of user action
const ( const (
Rune = iota Rune = iota
@@ -158,7 +168,7 @@ type MouseEvent struct {
var ( var (
_buf []byte _buf []byte
_in *os.File _in *os.File
_color func(int, bool) C.int _color func(int, Attr) C.int
_colorMap map[int]int _colorMap map[int]int
_prevDownTime time.Time _prevDownTime time.Time
_clickY []int _clickY []int
@@ -183,7 +193,7 @@ type Window struct {
func NewWindow(top int, left int, width int, height int, border bool) *Window { 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)) win := C.newwin(C.int(height), C.int(width), C.int(top), C.int(left))
if border { if border {
attr := _color(ColBorder, false) attr := _color(ColBorder, 0)
C.wattron(win, attr) C.wattron(win, attr)
C.box(win, 0, 0) C.box(win, 0, 0)
C.wattroff(win, attr) C.wattroff(win, attr)
@@ -266,22 +276,19 @@ func init() {
Border: 145} Border: 145}
} }
func attrColored(pair int, bold bool) C.int { func attrColored(pair int, a Attr) C.int {
var attr C.int var attr C.int
if pair > ColNormal { if pair > ColNormal {
attr = C.COLOR_PAIR(C.int(pair)) attr = C.COLOR_PAIR(C.int(pair))
} }
if bold { return attr | C.int(a)
attr = attr | C.A_BOLD
}
return attr
} }
func attrMono(pair int, bold bool) C.int { func attrMono(pair int, a Attr) C.int {
var attr C.int var attr C.int
switch pair { switch pair {
case ColCurrent: case ColCurrent:
if bold { if a&C.A_BOLD == C.A_BOLD {
attr = C.A_REVERSE attr = C.A_REVERSE
} }
case ColMatch: case ColMatch:
@@ -289,7 +296,7 @@ func attrMono(pair int, bold bool) C.int {
case ColCurrentMatch: case ColCurrentMatch:
attr = C.A_UNDERLINE | C.A_REVERSE attr = C.A_UNDERLINE | C.A_REVERSE
} }
if bold { if a&C.A_BOLD == C.A_BOLD {
attr = attr | C.A_BOLD attr = attr | C.A_BOLD
} }
return attr return attr
@@ -648,8 +655,8 @@ func (w *Window) Print(text string) {
}, text))) }, text)))
} }
func (w *Window) CPrint(pair int, bold bool, text string) { func (w *Window) CPrint(pair int, a Attr, text string) {
attr := _color(pair, bold) attr := _color(pair, a)
C.wattron(w.win, attr) C.wattron(w.win, attr)
w.Print(text) w.Print(text)
C.wattroff(w.win, attr) 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 return C.waddstr(w.win, C.CString(str)) == C.OK
} }
func (w *Window) CFill(str string, fg int, bg int, bold bool) bool { func (w *Window) CFill(str string, fg int, bg int, a Attr) bool {
attr := _color(PairFor(fg, bg), bold) attr := _color(PairFor(fg, bg), a)
C.wattron(w.win, attr) C.wattron(w.win, attr)
ret := w.Fill(str) ret := w.Fill(str)
C.wattroff(w.win, attr) C.wattroff(w.win, attr)

View File

@@ -14,7 +14,7 @@ type Offset [2]int32
type colorOffset struct { type colorOffset struct {
offset [2]int32 offset [2]int32
color int color int
bold bool attr curses.Attr
index int32 index int32
} }
@@ -91,14 +91,14 @@ func minRank() rank {
return rank{index: 0, points: [4]uint16{math.MaxUint16, 0, 0, 0}} return rank{index: 0, points: [4]uint16{math.MaxUint16, 0, 0, 0}}
} }
func (result *Result) colorOffsets(matchOffsets []Offset, color int, bold bool, current bool) []colorOffset { func (result *Result) colorOffsets(matchOffsets []Offset, color int, attr curses.Attr, current bool) []colorOffset {
itemColors := result.item.Colors() itemColors := result.item.Colors()
if len(itemColors) == 0 { if len(itemColors) == 0 {
var offsets []colorOffset var offsets []colorOffset
for _, off := range matchOffsets { for _, off := range matchOffsets {
offsets = append(offsets, colorOffset{offset: [2]int32{off[0], off[1]}, color: color, bold: bold}) offsets = append(offsets, colorOffset{offset: [2]int32{off[0], off[1]}, color: color, attr: attr})
} }
return offsets return offsets
} }
@@ -142,7 +142,7 @@ func (result *Result) colorOffsets(matchOffsets []Offset, color int, bold bool,
if curr != 0 && idx > start { if curr != 0 && idx > start {
if curr == -1 { if curr == -1 {
colors = append(colors, colorOffset{ 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 { } else {
ansi := itemColors[curr-1] ansi := itemColors[curr-1]
fg := ansi.color.fg fg := ansi.color.fg
@@ -164,7 +164,7 @@ func (result *Result) colorOffsets(matchOffsets []Offset, color int, bold bool,
colors = append(colors, colorOffset{ colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)}, offset: [2]int32{int32(start), int32(idx)},
color: curses.PairFor(fg, bg), color: curses.PairFor(fg, bg),
bold: ansi.color.bold || bold}) attr: ansi.color.attr | attr})
} }
} }
} }

View File

@@ -97,16 +97,20 @@ func TestColorOffset(t *testing.T) {
item := Result{ item := Result{
item: &Item{ item: &Item{
colors: &[]ansiOffset{ colors: &[]ansiOffset{
ansiOffset{[2]int32{0, 20}, ansiState{1, 5, false}}, ansiOffset{[2]int32{0, 20}, ansiState{1, 5, 0}},
ansiOffset{[2]int32{22, 27}, ansiState{2, 6, true}}, ansiOffset{[2]int32{22, 27}, ansiState{2, 6, curses.Bold}},
ansiOffset{[2]int32{30, 32}, ansiState{3, 7, false}}, ansiOffset{[2]int32{30, 32}, ansiState{3, 7, 0}},
ansiOffset{[2]int32{33, 40}, ansiState{4, 8, true}}}}} 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}] // [{[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) { assert := func(idx int, b int32, e int32, c int, bold bool) {
var attr curses.Attr
if bold {
attr = curses.Bold
}
o := colors[idx] 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) t.Error(o)
} }
} }

View File

@@ -526,24 +526,24 @@ func (t *Terminal) placeCursor() {
func (t *Terminal) printPrompt() { func (t *Terminal) printPrompt() {
t.move(0, 0, true) t.move(0, 0, true)
t.window.CPrint(C.ColPrompt, true, t.prompt) t.window.CPrint(C.ColPrompt, C.Bold, t.prompt)
t.window.CPrint(C.ColNormal, true, string(t.input)) t.window.CPrint(C.ColNormal, C.Bold, string(t.input))
} }
func (t *Terminal) printInfo() { func (t *Terminal) printInfo() {
if t.inlineInfo { if t.inlineInfo {
t.move(0, displayWidth([]rune(t.prompt))+displayWidth(t.input)+1, true) t.move(0, displayWidth([]rune(t.prompt))+displayWidth(t.input)+1, true)
if t.reading { if t.reading {
t.window.CPrint(C.ColSpinner, true, " < ") t.window.CPrint(C.ColSpinner, C.Bold, " < ")
} else { } else {
t.window.CPrint(C.ColPrompt, true, " < ") t.window.CPrint(C.ColPrompt, C.Bold, " < ")
} }
} else { } else {
t.move(1, 0, true) t.move(1, 0, true)
if t.reading { if t.reading {
duration := int64(spinnerDuration) duration := int64(spinnerDuration)
idx := (time.Now().UnixNano() % (duration * int64(len(_spinner)))) / duration idx := (time.Now().UnixNano() % (duration * int64(len(_spinner)))) / duration
t.window.CPrint(C.ColSpinner, true, _spinner[idx]) t.window.CPrint(C.ColSpinner, C.Bold, _spinner[idx])
} }
t.move(1, 2, false) t.move(1, 2, false)
} }
@@ -562,7 +562,7 @@ func (t *Terminal) printInfo() {
if t.progress > 0 && t.progress < 100 { if t.progress > 0 && t.progress < 100 {
output += fmt.Sprintf(" (%d%%)", t.progress) output += fmt.Sprintf(" (%d%%)", t.progress)
} }
t.window.CPrint(C.ColInfo, false, output) t.window.CPrint(C.ColInfo, 0, output)
} }
func (t *Terminal) printHeader() { func (t *Terminal) printHeader() {
@@ -586,7 +586,7 @@ func (t *Terminal) printHeader() {
colors: colors} colors: colors}
t.move(line, 2, true) 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)
} }
} }
@@ -620,21 +620,21 @@ func (t *Terminal) printItem(result *Result, i int, current bool) {
} else if current { } else if current {
label = ">" label = ">"
} }
t.window.CPrint(C.ColCursor, true, label) t.window.CPrint(C.ColCursor, C.Bold, label)
if current { if current {
if selected { if selected {
t.window.CPrint(C.ColSelected, true, ">") t.window.CPrint(C.ColSelected, C.Bold, ">")
} else { } 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 { } else {
if selected { if selected {
t.window.CPrint(C.ColSelected, true, ">") t.window.CPrint(C.ColSelected, C.Bold, ">")
} else { } else {
t.window.Print(" ") t.window.Print(" ")
} }
t.printHighlighted(result, false, 0, C.ColMatch, false) t.printHighlighted(result, 0, 0, C.ColMatch, false)
} }
} }
@@ -690,7 +690,7 @@ func overflow(runes []rune, max int) bool {
return false 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 item := result.item
// Overflow // Overflow
@@ -715,7 +715,7 @@ func (t *Terminal) printHighlighted(result *Result, bold bool, col1 int, col2 in
maxe = util.Max(maxe, int(offset[1])) 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 maxWidth := t.window.Width - 3
maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text)) maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text))
if overflow(text, maxWidth) { if overflow(text, maxWidth) {
@@ -764,11 +764,11 @@ func (t *Terminal) printHighlighted(result *Result, bold bool, col1 int, col2 in
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 = processTabs(text[index:b], prefixWidth)
t.window.CPrint(col1, bold, substr) t.window.CPrint(col1, attr, substr)
if b < e { if b < e {
substr, prefixWidth = processTabs(text[b:e], prefixWidth) 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 index = e
@@ -778,7 +778,7 @@ func (t *Terminal) printHighlighted(result *Result, bold bool, col1 int, col2 in
} }
if index < maxOffset { if index < maxOffset {
substr, _ = processTabs(text[index:], prefixWidth) substr, _ = processTabs(text[index:], prefixWidth)
t.window.CPrint(col1, bold, substr) t.window.CPrint(col1, attr, substr)
} }
} }
@@ -796,6 +796,9 @@ func numLinesMax(str string, max int) int {
} }
func (t *Terminal) printPreview() { func (t *Terminal) printPreview() {
if !t.isPreviewEnabled() {
return
}
t.pwindow.Erase() t.pwindow.Erase()
skip := t.previewer.offset skip := t.previewer.offset
extractColor(t.previewer.text, nil, func(str string, ansi *ansiState) bool { extractColor(t.previewer.text, nil, func(str string, ansi *ansiState) bool {
@@ -812,7 +815,7 @@ func (t *Terminal) printPreview() {
} }
} }
if ansi != nil && ansi.colored() { 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) return t.pwindow.Fill(str)
}) })
@@ -839,9 +842,7 @@ func (t *Terminal) printAll() {
t.printPrompt() t.printPrompt()
t.printInfo() t.printInfo()
t.printHeader() t.printHeader()
if t.isPreviewEnabled() { t.printPreview()
t.printPreview()
}
} }
func (t *Terminal) refresh() { func (t *Terminal) refresh() {