From 1fc565984244bdaf21e736bb9a129fff0de7cab1 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Thu, 29 Sep 2016 00:54:27 +0900 Subject: [PATCH] Add support for more ANSI color attributes (#674) Dim, underline, blink, reverse --- src/ansi.go | 24 +++++++++++++++++------- src/curses/curses.go | 35 +++++++++++++++++++++-------------- src/result.go | 10 +++++----- src/terminal.go | 38 +++++++++++++++++++------------------- 4 files changed, 62 insertions(+), 45 deletions(-) diff --git a/src/ansi.go b/src/ansi.go index 2bf4c62..e5ac81c 100644 --- a/src/ansi.go +++ b/src/ansi.go @@ -6,6 +6,8 @@ import ( "strconv" "strings" "unicode/utf8" + + "github.com/junegunn/fzf/src/curses" ) type ansiOffset struct { @@ -16,18 +18,18 @@ 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 @@ -94,9 +96,9 @@ 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[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: diff --git a/src/curses/curses.go b/src/curses/curses.go index 7a9ccd4..700e667 100644 --- a/src/curses/curses.go +++ b/src/curses/curses.go @@ -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) diff --git a/src/result.go b/src/result.go index 87478ab..9152f67 100644 --- a/src/result.go +++ b/src/result.go @@ -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}) } } } diff --git a/src/terminal.go b/src/terminal.go index 2d7f4c6..14dcd28 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -526,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) } @@ -562,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() { @@ -586,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) } } @@ -620,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) } } @@ -690,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 @@ -715,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) { @@ -764,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 @@ -778,7 +778,7 @@ 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) } } @@ -812,7 +812,7 @@ func (t *Terminal) printPreview() { } } 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) })