2015-03-18 16:59:14 +00:00
|
|
|
package fzf
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"regexp"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
2015-03-22 08:19:30 +00:00
|
|
|
"unicode/utf8"
|
2016-09-28 15:54:27 +00:00
|
|
|
|
2016-10-24 00:44:56 +00:00
|
|
|
"github.com/junegunn/fzf/src/tui"
|
2015-03-18 16:59:14 +00:00
|
|
|
)
|
|
|
|
|
2015-03-22 07:05:54 +00:00
|
|
|
type ansiOffset struct {
|
2015-03-18 16:59:14 +00:00
|
|
|
offset [2]int32
|
|
|
|
color ansiState
|
|
|
|
}
|
|
|
|
|
|
|
|
type ansiState struct {
|
2016-10-24 00:44:56 +00:00
|
|
|
fg tui.Color
|
|
|
|
bg tui.Color
|
|
|
|
attr tui.Attr
|
2015-03-18 16:59:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *ansiState) colored() bool {
|
2016-09-28 15:54:27 +00:00
|
|
|
return s.fg != -1 || s.bg != -1 || s.attr > 0
|
2015-03-18 16:59:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *ansiState) equals(t *ansiState) bool {
|
|
|
|
if t == nil {
|
|
|
|
return !s.colored()
|
|
|
|
}
|
2016-09-28 15:54:27 +00:00
|
|
|
return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr
|
2015-03-18 16:59:14 +00:00
|
|
|
}
|
|
|
|
|
2019-03-06 10:05:05 +00:00
|
|
|
func (s *ansiState) ToString() string {
|
|
|
|
if !s.colored() {
|
2019-03-07 01:47:09 +00:00
|
|
|
return ""
|
2019-03-06 10:05:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ret := ""
|
|
|
|
if s.attr&tui.Bold > 0 {
|
|
|
|
ret += "1;"
|
|
|
|
}
|
|
|
|
if s.attr&tui.Dim > 0 {
|
|
|
|
ret += "2;"
|
|
|
|
}
|
|
|
|
if s.attr&tui.Italic > 0 {
|
|
|
|
ret += "3;"
|
|
|
|
}
|
|
|
|
if s.attr&tui.Underline > 0 {
|
|
|
|
ret += "4;"
|
|
|
|
}
|
|
|
|
if s.attr&tui.Blink > 0 {
|
|
|
|
ret += "5;"
|
|
|
|
}
|
|
|
|
if s.attr&tui.Reverse > 0 {
|
|
|
|
ret += "7;"
|
|
|
|
}
|
|
|
|
ret += toAnsiString(s.fg, 30) + toAnsiString(s.bg, 40)
|
|
|
|
|
|
|
|
return "\x1b[" + strings.TrimSuffix(ret, ";") + "m"
|
|
|
|
}
|
|
|
|
|
|
|
|
func toAnsiString(color tui.Color, offset int) string {
|
|
|
|
col := int(color)
|
|
|
|
ret := ""
|
|
|
|
if col == -1 {
|
|
|
|
ret += strconv.Itoa(offset + 9)
|
|
|
|
} else if col < 8 {
|
|
|
|
ret += strconv.Itoa(offset + col)
|
|
|
|
} else if col < 16 {
|
|
|
|
ret += strconv.Itoa(offset - 30 + 90 + col - 8)
|
|
|
|
} else if col < 256 {
|
2019-03-06 17:00:31 +00:00
|
|
|
ret += strconv.Itoa(offset+8) + ";5;" + strconv.Itoa(col)
|
2019-03-06 10:05:05 +00:00
|
|
|
} else if col >= (1 << 24) {
|
2019-03-06 17:00:31 +00:00
|
|
|
r := strconv.Itoa((col >> 16) & 0xff)
|
|
|
|
g := strconv.Itoa((col >> 8) & 0xff)
|
|
|
|
b := strconv.Itoa(col & 0xff)
|
|
|
|
ret += strconv.Itoa(offset+8) + ";2;" + r + ";" + g + ";" + b
|
2019-03-06 10:05:05 +00:00
|
|
|
}
|
|
|
|
return ret + ";"
|
|
|
|
}
|
|
|
|
|
2015-03-18 16:59:14 +00:00
|
|
|
var ansiRegex *regexp.Regexp
|
|
|
|
|
|
|
|
func init() {
|
2016-11-13 17:13:16 +00:00
|
|
|
/*
|
|
|
|
References:
|
|
|
|
- https://github.com/gnachman/iTerm2
|
|
|
|
- http://ascii-table.com/ansi-escape-sequences.php
|
|
|
|
- http://ascii-table.com/ansi-escape-sequences-vt-100.php
|
|
|
|
- http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
|
|
|
|
*/
|
|
|
|
// The following regular expression will include not all but most of the
|
|
|
|
// frequently used ANSI sequences
|
2017-07-19 12:49:41 +00:00
|
|
|
ansiRegex = regexp.MustCompile("(?:\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b.|[\x0e\x0f]|.\x08)")
|
|
|
|
}
|
|
|
|
|
|
|
|
func findAnsiStart(str string) int {
|
|
|
|
idx := 0
|
|
|
|
for ; idx < len(str); idx++ {
|
|
|
|
b := str[idx]
|
|
|
|
if b == 0x1b || b == 0x0e || b == 0x0f {
|
|
|
|
return idx
|
|
|
|
}
|
|
|
|
if b == 0x08 && idx > 0 {
|
|
|
|
return idx - 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return idx
|
2015-03-18 16:59:14 +00:00
|
|
|
}
|
|
|
|
|
2016-08-18 17:39:32 +00:00
|
|
|
func extractColor(str string, state *ansiState, proc func(string, *ansiState) bool) (string, *[]ansiOffset, *ansiState) {
|
2015-03-22 07:05:54 +00:00
|
|
|
var offsets []ansiOffset
|
2015-03-18 16:59:14 +00:00
|
|
|
var output bytes.Buffer
|
2015-07-22 05:19:45 +00:00
|
|
|
|
|
|
|
if state != nil {
|
|
|
|
offsets = append(offsets, ansiOffset{[2]int32{0, 0}, *state})
|
|
|
|
}
|
2015-03-18 16:59:14 +00:00
|
|
|
|
2017-07-19 12:49:41 +00:00
|
|
|
prevIdx := 0
|
|
|
|
runeCount := 0
|
|
|
|
for idx := 0; idx < len(str); {
|
|
|
|
idx += findAnsiStart(str[idx:])
|
|
|
|
if idx == len(str) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure that we found an ANSI code
|
|
|
|
offset := ansiRegex.FindStringIndex(str[idx:])
|
2017-08-25 18:24:42 +00:00
|
|
|
if len(offset) < 2 {
|
2017-07-19 12:49:41 +00:00
|
|
|
idx++
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
offset[0] += idx
|
|
|
|
offset[1] += idx
|
|
|
|
idx = offset[1]
|
|
|
|
|
|
|
|
// Check if we should continue
|
|
|
|
prev := str[prevIdx:offset[0]]
|
2016-06-14 12:52:47 +00:00
|
|
|
if proc != nil && !proc(prev, state) {
|
2016-07-15 14:24:14 +00:00
|
|
|
return "", nil, nil
|
2016-06-14 12:52:47 +00:00
|
|
|
}
|
2015-03-18 16:59:14 +00:00
|
|
|
|
2017-07-19 12:49:41 +00:00
|
|
|
prevIdx = offset[1]
|
|
|
|
runeCount += utf8.RuneCountInString(prev)
|
|
|
|
output.WriteString(prev)
|
|
|
|
|
|
|
|
newState := interpretCode(str[offset[0]:offset[1]], state)
|
2015-03-18 16:59:14 +00:00
|
|
|
if !newState.equals(state) {
|
|
|
|
if state != nil {
|
|
|
|
// Update last offset
|
2017-07-19 12:49:41 +00:00
|
|
|
(&offsets[len(offsets)-1]).offset[1] = int32(runeCount)
|
2015-03-18 16:59:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if newState.colored() {
|
|
|
|
// Append new offset
|
|
|
|
state = newState
|
2017-07-19 12:49:41 +00:00
|
|
|
offsets = append(offsets, ansiOffset{[2]int32{int32(runeCount), int32(runeCount)}, *state})
|
2015-03-18 16:59:14 +00:00
|
|
|
} else {
|
|
|
|
// Discard state
|
|
|
|
state = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-17 18:04:11 +00:00
|
|
|
var rest string
|
|
|
|
var trimmed string
|
|
|
|
|
|
|
|
if prevIdx == 0 {
|
|
|
|
// No ANSI code found
|
|
|
|
rest = str
|
|
|
|
trimmed = str
|
|
|
|
} else {
|
|
|
|
rest = str[prevIdx:]
|
2015-03-18 16:59:14 +00:00
|
|
|
output.WriteString(rest)
|
2017-08-17 18:04:11 +00:00
|
|
|
trimmed = output.String()
|
|
|
|
}
|
|
|
|
if len(rest) > 0 && state != nil {
|
|
|
|
// Update last offset
|
|
|
|
runeCount += utf8.RuneCountInString(rest)
|
|
|
|
(&offsets[len(offsets)-1]).offset[1] = int32(runeCount)
|
2015-03-18 16:59:14 +00:00
|
|
|
}
|
2016-06-14 12:52:47 +00:00
|
|
|
if proc != nil {
|
|
|
|
proc(rest, state)
|
|
|
|
}
|
2016-08-18 17:39:32 +00:00
|
|
|
if len(offsets) == 0 {
|
2017-08-17 18:04:11 +00:00
|
|
|
return trimmed, nil, state
|
2016-08-18 17:39:32 +00:00
|
|
|
}
|
2017-08-17 18:04:11 +00:00
|
|
|
return trimmed, &offsets, state
|
2015-03-18 16:59:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
|
|
|
// State
|
|
|
|
var state *ansiState
|
|
|
|
if prevState == nil {
|
2016-09-28 15:54:27 +00:00
|
|
|
state = &ansiState{-1, -1, 0}
|
2015-03-18 16:59:14 +00:00
|
|
|
} else {
|
2016-09-28 15:54:27 +00:00
|
|
|
state = &ansiState{prevState.fg, prevState.bg, prevState.attr}
|
2015-03-18 16:59:14 +00:00
|
|
|
}
|
2016-11-13 17:13:16 +00:00
|
|
|
if ansiCode[0] != '\x1b' || ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' {
|
2015-03-27 03:35:06 +00:00
|
|
|
return state
|
|
|
|
}
|
2015-03-18 16:59:14 +00:00
|
|
|
|
|
|
|
ptr := &state.fg
|
|
|
|
state256 := 0
|
|
|
|
|
|
|
|
init := func() {
|
|
|
|
state.fg = -1
|
|
|
|
state.bg = -1
|
2016-09-28 15:54:27 +00:00
|
|
|
state.attr = 0
|
2015-03-18 16:59:14 +00:00
|
|
|
state256 = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
ansiCode = ansiCode[2 : len(ansiCode)-1]
|
2015-03-22 16:24:31 +00:00
|
|
|
if len(ansiCode) == 0 {
|
|
|
|
init()
|
|
|
|
}
|
2015-03-18 16:59:14 +00:00
|
|
|
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:
|
2016-11-23 15:13:10 +00:00
|
|
|
state.attr = state.attr | tui.Bold
|
2016-09-28 15:54:27 +00:00
|
|
|
case 2:
|
2016-11-23 15:13:10 +00:00
|
|
|
state.attr = state.attr | tui.Dim
|
|
|
|
case 3:
|
|
|
|
state.attr = state.attr | tui.Italic
|
2016-09-28 15:54:27 +00:00
|
|
|
case 4:
|
2016-11-23 15:13:10 +00:00
|
|
|
state.attr = state.attr | tui.Underline
|
2016-09-28 15:54:27 +00:00
|
|
|
case 5:
|
2016-11-23 15:13:10 +00:00
|
|
|
state.attr = state.attr | tui.Blink
|
2016-09-28 15:54:27 +00:00
|
|
|
case 7:
|
2016-11-23 15:13:10 +00:00
|
|
|
state.attr = state.attr | tui.Reverse
|
2015-03-18 16:59:14 +00:00
|
|
|
case 0:
|
|
|
|
init()
|
|
|
|
default:
|
|
|
|
if num >= 30 && num <= 37 {
|
2016-10-24 00:44:56 +00:00
|
|
|
state.fg = tui.Color(num - 30)
|
2015-03-18 16:59:14 +00:00
|
|
|
} else if num >= 40 && num <= 47 {
|
2016-10-24 00:44:56 +00:00
|
|
|
state.bg = tui.Color(num - 40)
|
2016-09-25 09:11:35 +00:00
|
|
|
} else if num >= 90 && num <= 97 {
|
2016-10-24 00:44:56 +00:00
|
|
|
state.fg = tui.Color(num - 90 + 8)
|
2016-09-25 09:11:35 +00:00
|
|
|
} else if num >= 100 && num <= 107 {
|
2016-10-24 00:44:56 +00:00
|
|
|
state.bg = tui.Color(num - 100 + 8)
|
2015-03-18 16:59:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
case 1:
|
|
|
|
switch num {
|
2016-11-25 15:36:38 +00:00
|
|
|
case 2:
|
|
|
|
state256 = 10 // MAGIC
|
2015-03-18 16:59:14 +00:00
|
|
|
case 5:
|
|
|
|
state256++
|
|
|
|
default:
|
|
|
|
state256 = 0
|
|
|
|
}
|
|
|
|
case 2:
|
2016-10-24 00:44:56 +00:00
|
|
|
*ptr = tui.Color(num)
|
2015-03-18 16:59:14 +00:00
|
|
|
state256 = 0
|
2016-11-25 15:36:38 +00:00
|
|
|
case 10:
|
|
|
|
*ptr = tui.Color(1<<24) | tui.Color(num<<16)
|
|
|
|
state256++
|
|
|
|
case 11:
|
|
|
|
*ptr = *ptr | tui.Color(num<<8)
|
|
|
|
state256++
|
|
|
|
case 12:
|
|
|
|
*ptr = *ptr | tui.Color(num)
|
|
|
|
state256 = 0
|
2015-03-18 16:59:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-11-25 15:36:38 +00:00
|
|
|
if state256 > 0 {
|
|
|
|
*ptr = -1
|
|
|
|
}
|
2015-03-18 16:59:14 +00:00
|
|
|
return state
|
|
|
|
}
|