Add support for more ANSI color attributes (#674)

Dim, underline, blink, reverse
This commit is contained in:
Junegunn Choi 2016-09-29 00:54:27 +09:00
parent 1acd2adce2
commit 1fc5659842
No known key found for this signature in database
GPG Key ID: 254BC280FEF9C627
4 changed files with 62 additions and 45 deletions

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

@ -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

@ -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)
} }
} }
@ -812,7 +812,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)
}) })