Add support for ANSI color codes
This commit is contained in:
parent
d80a41bb6d
commit
e70a2a5817
141
src/ansi.go
Normal file
141
src/ansi.go
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
package fzf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AnsiOffset struct {
|
||||||
|
offset [2]int32
|
||||||
|
color ansiState
|
||||||
|
}
|
||||||
|
|
||||||
|
type ansiState struct {
|
||||||
|
fg int
|
||||||
|
bg int
|
||||||
|
bold bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ansiState) colored() bool {
|
||||||
|
return s.fg != -1 || s.bg != -1 || s.bold
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
var ansiRegex *regexp.Regexp
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ansiRegex = regexp.MustCompile("\x1b\\[[0-9;]*m")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExtractColor(str *string) (*string, []AnsiOffset) {
|
||||||
|
offsets := make([]AnsiOffset, 0)
|
||||||
|
|
||||||
|
var output bytes.Buffer
|
||||||
|
var state *ansiState
|
||||||
|
|
||||||
|
idx := 0
|
||||||
|
for _, offset := range ansiRegex.FindAllStringIndex(*str, -1) {
|
||||||
|
output.WriteString((*str)[idx:offset[0]])
|
||||||
|
newLen := int32(output.Len())
|
||||||
|
newState := interpretCode((*str)[offset[0]:offset[1]], state)
|
||||||
|
|
||||||
|
if !newState.equals(state) {
|
||||||
|
if state != nil {
|
||||||
|
// Update last offset
|
||||||
|
(&offsets[len(offsets)-1]).offset[1] = int32(output.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
if newState.colored() {
|
||||||
|
// Append new offset
|
||||||
|
state = newState
|
||||||
|
offsets = append(offsets, AnsiOffset{[2]int32{newLen, newLen}, *state})
|
||||||
|
} else {
|
||||||
|
// Discard state
|
||||||
|
state = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
idx = offset[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
rest := (*str)[idx:]
|
||||||
|
if len(rest) > 0 {
|
||||||
|
output.WriteString(rest)
|
||||||
|
if state != nil {
|
||||||
|
// Update last offset
|
||||||
|
(&offsets[len(offsets)-1]).offset[1] = int32(output.Len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outputStr := output.String()
|
||||||
|
return &outputStr, offsets
|
||||||
|
}
|
||||||
|
|
||||||
|
func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
||||||
|
// State
|
||||||
|
var state *ansiState
|
||||||
|
if prevState == nil {
|
||||||
|
state = &ansiState{-1, -1, false}
|
||||||
|
} else {
|
||||||
|
state = &ansiState{prevState.fg, prevState.bg, prevState.bold}
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr := &state.fg
|
||||||
|
state256 := 0
|
||||||
|
|
||||||
|
init := func() {
|
||||||
|
state.fg = -1
|
||||||
|
state.bg = -1
|
||||||
|
state.bold = false
|
||||||
|
state256 = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
ansiCode = ansiCode[2 : len(ansiCode)-1]
|
||||||
|
for _, code := range strings.Split(ansiCode, ";") {
|
||||||
|
if num, err := strconv.Atoi(code); err == nil {
|
||||||
|
switch state256 {
|
||||||
|
case 0:
|
||||||
|
switch num {
|
||||||
|
case 38:
|
||||||
|
ptr = &state.fg
|
||||||
|
state256++
|
||||||
|
case 48:
|
||||||
|
ptr = &state.bg
|
||||||
|
state256++
|
||||||
|
case 39:
|
||||||
|
state.fg = -1
|
||||||
|
case 49:
|
||||||
|
state.bg = -1
|
||||||
|
case 1:
|
||||||
|
state.bold = true
|
||||||
|
case 0:
|
||||||
|
init()
|
||||||
|
default:
|
||||||
|
if num >= 30 && num <= 37 {
|
||||||
|
state.fg = num - 30
|
||||||
|
} else if num >= 40 && num <= 47 {
|
||||||
|
state.bg = num - 40
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
|
switch num {
|
||||||
|
case 5:
|
||||||
|
state256++
|
||||||
|
default:
|
||||||
|
state256 = 0
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
*ptr = num
|
||||||
|
state256 = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
91
src/ansi_test.go
Normal file
91
src/ansi_test.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package fzf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExtractColor(t *testing.T) {
|
||||||
|
assert := func(offset AnsiOffset, b int32, e int32, fg int, bg int, bold bool) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
src := "hello world"
|
||||||
|
clean := "\x1b[0m"
|
||||||
|
check := func(assertion func(ansiOffsets []AnsiOffset)) {
|
||||||
|
output, ansiOffsets := ExtractColor(&src)
|
||||||
|
if *output != "hello world" {
|
||||||
|
t.Errorf("Invalid output: {}", output)
|
||||||
|
}
|
||||||
|
fmt.Println(src, ansiOffsets, clean)
|
||||||
|
assertion(ansiOffsets)
|
||||||
|
}
|
||||||
|
|
||||||
|
check(func(offsets []AnsiOffset) {
|
||||||
|
if len(offsets) > 0 {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
src = "\x1b[0mhello world"
|
||||||
|
check(func(offsets []AnsiOffset) {
|
||||||
|
if len(offsets) > 0 {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
src = "\x1b[1mhello world"
|
||||||
|
check(func(offsets []AnsiOffset) {
|
||||||
|
if len(offsets) != 1 {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
assert(offsets[0], 0, 11, -1, -1, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
src = "hello \x1b[34;45;1mworld"
|
||||||
|
check(func(offsets []AnsiOffset) {
|
||||||
|
if len(offsets) != 1 {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
assert(offsets[0], 6, 11, 4, 5, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
src = "hello \x1b[34;45;1mwor\x1b[34;45;1mld"
|
||||||
|
check(func(offsets []AnsiOffset) {
|
||||||
|
if len(offsets) != 1 {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
assert(offsets[0], 6, 11, 4, 5, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
src = "hello \x1b[34;45;1mwor\x1b[0mld"
|
||||||
|
check(func(offsets []AnsiOffset) {
|
||||||
|
if len(offsets) != 1 {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
assert(offsets[0], 6, 9, 4, 5, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
src = "hello \x1b[34;48;5;233;1mwo\x1b[38;5;161mr\x1b[0ml\x1b[38;5;161md"
|
||||||
|
check(func(offsets []AnsiOffset) {
|
||||||
|
if len(offsets) != 3 {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
assert(offsets[0], 6, 8, 4, 233, true)
|
||||||
|
assert(offsets[1], 8, 9, 161, 233, true)
|
||||||
|
assert(offsets[2], 10, 11, 161, -1, false)
|
||||||
|
})
|
||||||
|
|
||||||
|
// {38,48};5;{38,48}
|
||||||
|
src = "hello \x1b[38;5;38;48;5;48;1mwor\x1b[38;5;48;48;5;38ml\x1b[0md"
|
||||||
|
check(func(offsets []AnsiOffset) {
|
||||||
|
if len(offsets) != 2 {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
assert(offsets[0], 6, 9, 38, 48, true)
|
||||||
|
assert(offsets[1], 9, 10, 48, 38, true)
|
||||||
|
})
|
||||||
|
}
|
33
src/core.go
33
src/core.go
@ -63,14 +63,36 @@ func Run(options *Options) {
|
|||||||
// Event channel
|
// Event channel
|
||||||
eventBox := util.NewEventBox()
|
eventBox := util.NewEventBox()
|
||||||
|
|
||||||
|
// ANSI code processor
|
||||||
|
extractColors := func(data *string) (*string, []AnsiOffset) {
|
||||||
|
// By default, we do nothing
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
if opts.Ansi {
|
||||||
|
if opts.Color {
|
||||||
|
extractColors = func(data *string) (*string, []AnsiOffset) {
|
||||||
|
return ExtractColor(data)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// When color is disabled but ansi option is given,
|
||||||
|
// we simply strip out ANSI codes from the input
|
||||||
|
extractColors = func(data *string) (*string, []AnsiOffset) {
|
||||||
|
trimmed, _ := ExtractColor(data)
|
||||||
|
return trimmed, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Chunk list
|
// Chunk list
|
||||||
var chunkList *ChunkList
|
var chunkList *ChunkList
|
||||||
if len(opts.WithNth) == 0 {
|
if len(opts.WithNth) == 0 {
|
||||||
chunkList = NewChunkList(func(data *string, index int) *Item {
|
chunkList = NewChunkList(func(data *string, index int) *Item {
|
||||||
|
data, colors := extractColors(data)
|
||||||
return &Item{
|
return &Item{
|
||||||
text: data,
|
text: data,
|
||||||
index: uint32(index),
|
index: uint32(index),
|
||||||
rank: Rank{0, 0, uint32(index)}}
|
colors: colors,
|
||||||
|
rank: Rank{0, 0, uint32(index)}}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
chunkList = NewChunkList(func(data *string, index int) *Item {
|
chunkList = NewChunkList(func(data *string, index int) *Item {
|
||||||
@ -79,7 +101,12 @@ func Run(options *Options) {
|
|||||||
text: Transform(tokens, opts.WithNth).whole,
|
text: Transform(tokens, opts.WithNth).whole,
|
||||||
origText: data,
|
origText: data,
|
||||||
index: uint32(index),
|
index: uint32(index),
|
||||||
|
colors: nil,
|
||||||
rank: Rank{0, 0, uint32(index)}}
|
rank: Rank{0, 0, uint32(index)}}
|
||||||
|
|
||||||
|
trimmed, colors := extractColors(item.text)
|
||||||
|
item.text = trimmed
|
||||||
|
item.colors = colors
|
||||||
return &item
|
return &item
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -78,6 +78,7 @@ const (
|
|||||||
ColInfo
|
ColInfo
|
||||||
ColCursor
|
ColCursor
|
||||||
ColSelected
|
ColSelected
|
||||||
|
ColUser
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -103,14 +104,17 @@ var (
|
|||||||
_buf []byte
|
_buf []byte
|
||||||
_in *os.File
|
_in *os.File
|
||||||
_color func(int, bool) C.int
|
_color func(int, bool) C.int
|
||||||
|
_colorMap map[int]int
|
||||||
_prevDownTime time.Time
|
_prevDownTime time.Time
|
||||||
_prevDownY int
|
_prevDownY int
|
||||||
_clickY []int
|
_clickY []int
|
||||||
|
DarkBG C.short
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
_prevDownTime = time.Unix(0, 0)
|
_prevDownTime = time.Unix(0, 0)
|
||||||
_clickY = []int{}
|
_clickY = []int{}
|
||||||
|
_colorMap = make(map[int]int)
|
||||||
}
|
}
|
||||||
|
|
||||||
func attrColored(pair int, bold bool) C.int {
|
func attrColored(pair int, bold bool) C.int {
|
||||||
@ -200,23 +204,25 @@ func Init(color bool, color256 bool, black bool, mouse bool) {
|
|||||||
bg = -1
|
bg = -1
|
||||||
}
|
}
|
||||||
if color256 {
|
if color256 {
|
||||||
|
DarkBG = 236
|
||||||
C.init_pair(ColPrompt, 110, bg)
|
C.init_pair(ColPrompt, 110, bg)
|
||||||
C.init_pair(ColMatch, 108, bg)
|
C.init_pair(ColMatch, 108, bg)
|
||||||
C.init_pair(ColCurrent, 254, 236)
|
C.init_pair(ColCurrent, 254, DarkBG)
|
||||||
C.init_pair(ColCurrentMatch, 151, 236)
|
C.init_pair(ColCurrentMatch, 151, DarkBG)
|
||||||
C.init_pair(ColSpinner, 148, bg)
|
C.init_pair(ColSpinner, 148, bg)
|
||||||
C.init_pair(ColInfo, 144, bg)
|
C.init_pair(ColInfo, 144, bg)
|
||||||
C.init_pair(ColCursor, 161, 236)
|
C.init_pair(ColCursor, 161, DarkBG)
|
||||||
C.init_pair(ColSelected, 168, 236)
|
C.init_pair(ColSelected, 168, DarkBG)
|
||||||
} else {
|
} else {
|
||||||
|
DarkBG = C.COLOR_BLACK
|
||||||
C.init_pair(ColPrompt, C.COLOR_BLUE, bg)
|
C.init_pair(ColPrompt, C.COLOR_BLUE, bg)
|
||||||
C.init_pair(ColMatch, C.COLOR_GREEN, bg)
|
C.init_pair(ColMatch, C.COLOR_GREEN, bg)
|
||||||
C.init_pair(ColCurrent, C.COLOR_YELLOW, C.COLOR_BLACK)
|
C.init_pair(ColCurrent, C.COLOR_YELLOW, DarkBG)
|
||||||
C.init_pair(ColCurrentMatch, C.COLOR_GREEN, C.COLOR_BLACK)
|
C.init_pair(ColCurrentMatch, C.COLOR_GREEN, DarkBG)
|
||||||
C.init_pair(ColSpinner, C.COLOR_GREEN, bg)
|
C.init_pair(ColSpinner, C.COLOR_GREEN, bg)
|
||||||
C.init_pair(ColInfo, C.COLOR_WHITE, bg)
|
C.init_pair(ColInfo, C.COLOR_WHITE, bg)
|
||||||
C.init_pair(ColCursor, C.COLOR_RED, C.COLOR_BLACK)
|
C.init_pair(ColCursor, C.COLOR_RED, DarkBG)
|
||||||
C.init_pair(ColSelected, C.COLOR_MAGENTA, C.COLOR_BLACK)
|
C.init_pair(ColSelected, C.COLOR_MAGENTA, DarkBG)
|
||||||
}
|
}
|
||||||
_color = attrColored
|
_color = attrColored
|
||||||
} else {
|
} else {
|
||||||
@ -428,3 +434,15 @@ func Endwin() {
|
|||||||
func Refresh() {
|
func Refresh() {
|
||||||
C.refresh()
|
C.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func PairFor(fg int, bg int) int {
|
||||||
|
key := (fg << 8) + bg
|
||||||
|
if found, prs := _colorMap[key]; prs {
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
id := len(_colorMap) + ColUser
|
||||||
|
C.init_pair(C.short(id), C.short(fg), C.short(bg))
|
||||||
|
_colorMap[key] = id
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
14
src/curses/curses_test.go
Normal file
14
src/curses/curses_test.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package curses
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPairFor(t *testing.T) {
|
||||||
|
if PairFor(30, 50) != PairFor(30, 50) {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
if PairFor(-1, 10) != PairFor(-1, 10) {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
84
src/item.go
84
src/item.go
@ -1,8 +1,18 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/junegunn/fzf/src/curses"
|
||||||
|
)
|
||||||
|
|
||||||
// Offset holds two 32-bit integers denoting the offsets of a matched substring
|
// Offset holds two 32-bit integers denoting the offsets of a matched substring
|
||||||
type Offset [2]int32
|
type Offset [2]int32
|
||||||
|
|
||||||
|
type ColorOffset struct {
|
||||||
|
offset [2]int32
|
||||||
|
color int
|
||||||
|
bold bool
|
||||||
|
}
|
||||||
|
|
||||||
// Item represents each input line
|
// Item represents each input line
|
||||||
type Item struct {
|
type Item struct {
|
||||||
text *string
|
text *string
|
||||||
@ -10,6 +20,7 @@ type Item struct {
|
|||||||
transformed *Transformed
|
transformed *Transformed
|
||||||
index uint32
|
index uint32
|
||||||
offsets []Offset
|
offsets []Offset
|
||||||
|
colors []AnsiOffset
|
||||||
rank Rank
|
rank Rank
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,6 +66,79 @@ func (i *Item) AsString() string {
|
|||||||
return *i.text
|
return *i.text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (item *Item) ColorOffsets(color int, bold bool, current bool) []ColorOffset {
|
||||||
|
if len(item.colors) == 0 {
|
||||||
|
offsets := make([]ColorOffset, 0)
|
||||||
|
for _, off := range item.offsets {
|
||||||
|
offsets = append(offsets, ColorOffset{offset: off, color: color, bold: bold})
|
||||||
|
}
|
||||||
|
return offsets
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find max column
|
||||||
|
var maxCol int32 = 0
|
||||||
|
for _, off := range item.offsets {
|
||||||
|
if off[1] > maxCol {
|
||||||
|
maxCol = off[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, ansi := range item.colors {
|
||||||
|
if ansi.offset[1] > maxCol {
|
||||||
|
maxCol = ansi.offset[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cols := make([]int, maxCol)
|
||||||
|
|
||||||
|
for colorIndex, ansi := range item.colors {
|
||||||
|
for i := ansi.offset[0]; i < ansi.offset[1]; i++ {
|
||||||
|
cols[i] = colorIndex + 1 // XXX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, off := range item.offsets {
|
||||||
|
for i := off[0]; i < off[1]; i++ {
|
||||||
|
cols[i] = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort.Sort(ByOrder(offsets))
|
||||||
|
|
||||||
|
// Merge offsets
|
||||||
|
// ------------ ---- -- ----
|
||||||
|
// ++++++++ ++++++++++
|
||||||
|
// --++++++++-- --++++++++++---
|
||||||
|
curr := 0
|
||||||
|
start := 0
|
||||||
|
offsets := make([]ColorOffset, 0)
|
||||||
|
add := func(idx int) {
|
||||||
|
if curr != 0 && idx > start {
|
||||||
|
if curr == -1 {
|
||||||
|
offsets = append(offsets, ColorOffset{
|
||||||
|
offset: Offset{int32(start), int32(idx)}, color: color, bold: bold})
|
||||||
|
} else {
|
||||||
|
ansi := item.colors[curr-1]
|
||||||
|
bg := ansi.color.bg
|
||||||
|
if current {
|
||||||
|
bg = int(curses.DarkBG)
|
||||||
|
}
|
||||||
|
offsets = append(offsets, ColorOffset{
|
||||||
|
offset: Offset{int32(start), int32(idx)},
|
||||||
|
color: curses.PairFor(ansi.color.fg, bg),
|
||||||
|
bold: ansi.color.bold || bold})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for idx, col := range cols {
|
||||||
|
if col != curr {
|
||||||
|
add(idx)
|
||||||
|
start = idx
|
||||||
|
curr = col
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add(int(maxCol))
|
||||||
|
return offsets
|
||||||
|
}
|
||||||
|
|
||||||
// ByOrder is for sorting substring offsets
|
// ByOrder is for sorting substring offsets
|
||||||
type ByOrder []Offset
|
type ByOrder []Offset
|
||||||
|
|
||||||
|
@ -3,6 +3,8 @@ package fzf
|
|||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/curses"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOffsetSort(t *testing.T) {
|
func TestOffsetSort(t *testing.T) {
|
||||||
@ -72,3 +74,31 @@ func TestItemRank(t *testing.T) {
|
|||||||
t.Error(items)
|
t.Error(items)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestColorOffset(t *testing.T) {
|
||||||
|
// ------------ 20 ---- -- ----
|
||||||
|
// ++++++++ ++++++++++
|
||||||
|
// --++++++++-- --++++++++++---
|
||||||
|
item := Item{
|
||||||
|
offsets: []Offset{Offset{5, 15}, Offset{25, 35}},
|
||||||
|
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}}}}
|
||||||
|
// [{[0 5] 9 false} {[5 15] 99 false} {[15 20] 9 false} {[22 25] 10 true} {[25 35] 99 false} {[35 40] 11 true}]
|
||||||
|
|
||||||
|
offsets := item.ColorOffsets(99, false, true)
|
||||||
|
assert := func(idx int, b int32, e int32, c int, bold bool) {
|
||||||
|
o := offsets[idx]
|
||||||
|
if o.offset[0] != b || o.offset[1] != e || o.color != c || o.bold != bold {
|
||||||
|
t.Error(o)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(0, 0, 5, curses.ColUser, false)
|
||||||
|
assert(1, 5, 15, 99, false)
|
||||||
|
assert(2, 15, 20, curses.ColUser, false)
|
||||||
|
assert(3, 22, 25, curses.ColUser+1, true)
|
||||||
|
assert(4, 25, 35, 99, false)
|
||||||
|
assert(5, 35, 40, curses.ColUser+2, true)
|
||||||
|
}
|
||||||
|
@ -29,6 +29,7 @@ const usage = `usage: fzf [options]
|
|||||||
|
|
||||||
Interface
|
Interface
|
||||||
-m, --multi Enable multi-select with tab/shift-tab
|
-m, --multi Enable multi-select with tab/shift-tab
|
||||||
|
--ansi Interpret ANSI color codes and remove from output
|
||||||
--no-mouse Disable mouse
|
--no-mouse Disable mouse
|
||||||
+c, --no-color Disable colors
|
+c, --no-color Disable colors
|
||||||
+2, --no-256 Disable 256-color
|
+2, --no-256 Disable 256-color
|
||||||
@ -81,6 +82,7 @@ type Options struct {
|
|||||||
Sort int
|
Sort int
|
||||||
Tac bool
|
Tac bool
|
||||||
Multi bool
|
Multi bool
|
||||||
|
Ansi bool
|
||||||
Mouse bool
|
Mouse bool
|
||||||
Color bool
|
Color bool
|
||||||
Color256 bool
|
Color256 bool
|
||||||
@ -106,6 +108,7 @@ func defaultOptions() *Options {
|
|||||||
Sort: 1000,
|
Sort: 1000,
|
||||||
Tac: false,
|
Tac: false,
|
||||||
Multi: false,
|
Multi: false,
|
||||||
|
Ansi: false,
|
||||||
Mouse: true,
|
Mouse: true,
|
||||||
Color: true,
|
Color: true,
|
||||||
Color256: strings.Contains(os.Getenv("TERM"), "256"),
|
Color256: strings.Contains(os.Getenv("TERM"), "256"),
|
||||||
@ -227,6 +230,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Multi = true
|
opts.Multi = true
|
||||||
case "+m", "--no-multi":
|
case "+m", "--no-multi":
|
||||||
opts.Multi = false
|
opts.Multi = false
|
||||||
|
case "--ansi":
|
||||||
|
opts.Ansi = true
|
||||||
|
case "--no-ansi":
|
||||||
|
opts.Ansi = false
|
||||||
case "--no-mouse":
|
case "--no-mouse":
|
||||||
opts.Mouse = false
|
opts.Mouse = false
|
||||||
case "+c", "--no-color":
|
case "+c", "--no-color":
|
||||||
|
@ -264,6 +264,7 @@ func dupItem(item *Item, offsets []Offset) *Item {
|
|||||||
transformed: item.transformed,
|
transformed: item.transformed,
|
||||||
index: item.index,
|
index: item.index,
|
||||||
offsets: offsets,
|
offsets: offsets,
|
||||||
|
colors: item.colors,
|
||||||
rank: Rank{0, 0, item.index}}
|
rank: Rank{0, 0, item.index}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,7 +251,7 @@ func (t *Terminal) printItem(item *Item, current bool) {
|
|||||||
} else {
|
} else {
|
||||||
C.CPrint(C.ColCurrent, true, " ")
|
C.CPrint(C.ColCurrent, true, " ")
|
||||||
}
|
}
|
||||||
t.printHighlighted(item, true, C.ColCurrent, C.ColCurrentMatch)
|
t.printHighlighted(item, true, C.ColCurrent, C.ColCurrentMatch, true)
|
||||||
} else {
|
} else {
|
||||||
C.CPrint(C.ColCursor, true, " ")
|
C.CPrint(C.ColCursor, true, " ")
|
||||||
if selected {
|
if selected {
|
||||||
@ -259,7 +259,7 @@ func (t *Terminal) printItem(item *Item, current bool) {
|
|||||||
} else {
|
} else {
|
||||||
C.Print(" ")
|
C.Print(" ")
|
||||||
}
|
}
|
||||||
t.printHighlighted(item, false, 0, C.ColMatch)
|
t.printHighlighted(item, false, 0, C.ColMatch, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,7 +299,7 @@ func trimLeft(runes []rune, width int) ([]rune, int32) {
|
|||||||
return runes, trimmed
|
return runes, trimmed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*Terminal) printHighlighted(item *Item, bold bool, col1 int, col2 int) {
|
func (*Terminal) printHighlighted(item *Item, bold bool, col1 int, col2 int, current bool) {
|
||||||
var maxe int32
|
var maxe int32
|
||||||
for _, offset := range item.offsets {
|
for _, offset := range item.offsets {
|
||||||
if offset[1] > maxe {
|
if offset[1] > maxe {
|
||||||
@ -309,7 +309,7 @@ func (*Terminal) printHighlighted(item *Item, bold bool, col1 int, col2 int) {
|
|||||||
|
|
||||||
// Overflow
|
// Overflow
|
||||||
text := []rune(*item.text)
|
text := []rune(*item.text)
|
||||||
offsets := item.offsets
|
offsets := item.ColorOffsets(col2, bold, current)
|
||||||
maxWidth := C.MaxX() - 3
|
maxWidth := C.MaxX() - 3
|
||||||
fullWidth := displayWidth(text)
|
fullWidth := displayWidth(text)
|
||||||
if fullWidth > maxWidth {
|
if fullWidth > maxWidth {
|
||||||
@ -328,37 +328,40 @@ func (*Terminal) printHighlighted(item *Item, bold bool, col1 int, col2 int) {
|
|||||||
text, diff = trimLeft(text, maxWidth-2)
|
text, diff = trimLeft(text, maxWidth-2)
|
||||||
|
|
||||||
// Transform offsets
|
// Transform offsets
|
||||||
offsets = make([]Offset, len(item.offsets))
|
for idx, offset := range offsets {
|
||||||
for idx, offset := range item.offsets {
|
b, e := offset.offset[0], offset.offset[1]
|
||||||
b, e := offset[0], offset[1]
|
|
||||||
b += 2 - diff
|
b += 2 - diff
|
||||||
e += 2 - diff
|
e += 2 - diff
|
||||||
b = util.Max32(b, 2)
|
b = util.Max32(b, 2)
|
||||||
if b < e {
|
if b < e {
|
||||||
offsets[idx] = Offset{b, e}
|
offsets[idx].offset[0] = b
|
||||||
|
offsets[idx].offset[1] = e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
text = append([]rune(".."), text...)
|
text = append([]rune(".."), text...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Sort(ByOrder(offsets))
|
|
||||||
var index int32
|
var index int32
|
||||||
var substr string
|
var substr string
|
||||||
var prefixWidth int
|
var prefixWidth int
|
||||||
|
maxOffset := int32(len(text))
|
||||||
for _, offset := range offsets {
|
for _, offset := range offsets {
|
||||||
b := util.Max32(index, offset[0])
|
b := util.Constrain32(offset.offset[0], index, maxOffset)
|
||||||
e := util.Max32(index, offset[1])
|
e := util.Constrain32(offset.offset[1], index, maxOffset)
|
||||||
|
|
||||||
substr, prefixWidth = processTabs(text[index:b], prefixWidth)
|
substr, prefixWidth = processTabs(text[index:b], prefixWidth)
|
||||||
C.CPrint(col1, bold, substr)
|
C.CPrint(col1, bold, substr)
|
||||||
|
|
||||||
substr, prefixWidth = processTabs(text[b:e], prefixWidth)
|
substr, prefixWidth = processTabs(text[b:e], prefixWidth)
|
||||||
C.CPrint(col2, bold, substr)
|
C.CPrint(offset.color, bold, substr)
|
||||||
|
|
||||||
index = e
|
index = e
|
||||||
|
if index >= maxOffset {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if index < int32(len(text)) {
|
if index < maxOffset {
|
||||||
substr, _ = processTabs(text[index:], prefixWidth)
|
substr, _ = processTabs(text[index:], prefixWidth)
|
||||||
C.CPrint(col1, bold, substr)
|
C.CPrint(col1, bold, substr)
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,17 @@ func Max32(first int32, second int32) int32 {
|
|||||||
return second
|
return second
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Constrain32 limits the given 32-bit integer with the upper and lower bounds
|
||||||
|
func Constrain32(val int32, min int32, max int32) int32 {
|
||||||
|
if val < min {
|
||||||
|
return min
|
||||||
|
}
|
||||||
|
if val > max {
|
||||||
|
return max
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
// Constrain limits the given integer with the upper and lower bounds
|
// Constrain limits the given integer with the upper and lower bounds
|
||||||
func Constrain(val int, min int, max int) int {
|
func Constrain(val int, min int, max int) int {
|
||||||
if val < min {
|
if val < min {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user