add my own session manager
This commit is contained in:
+404
@@ -0,0 +1,404 @@
|
||||
package fuzzyfinder
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
"github.com/nsf/termbox-go"
|
||||
)
|
||||
|
||||
type cell struct {
|
||||
ch rune
|
||||
bg, fg termbox.Attribute
|
||||
}
|
||||
|
||||
type simScreen tcell.SimulationScreen
|
||||
|
||||
// TerminalMock is a mocked terminal for testing.
|
||||
// Most users should use it by calling UseMockedTerminal.
|
||||
type TerminalMock struct {
|
||||
simScreen
|
||||
sizeMu sync.RWMutex
|
||||
width, height int
|
||||
|
||||
eventsMu sync.Mutex
|
||||
events []termbox.Event
|
||||
|
||||
cellsMu sync.RWMutex
|
||||
cells []*cell
|
||||
|
||||
resultMu sync.RWMutex
|
||||
result string
|
||||
|
||||
sleepDuration time.Duration
|
||||
v2 bool
|
||||
}
|
||||
|
||||
// SetSize changes the pseudo-size of the window.
|
||||
// Note that SetSize resets added cells.
|
||||
func (m *TerminalMock) SetSize(w, h int) {
|
||||
if m.v2 {
|
||||
m.simScreen.SetSize(w, h)
|
||||
return
|
||||
}
|
||||
m.sizeMu.Lock()
|
||||
defer m.sizeMu.Unlock()
|
||||
m.cellsMu.Lock()
|
||||
defer m.cellsMu.Unlock()
|
||||
m.width = w
|
||||
m.height = h
|
||||
m.cells = make([]*cell, w*h)
|
||||
}
|
||||
|
||||
// Deprecated: Use SetEventsV2
|
||||
// SetEvents sets all events, which are fetched by pollEvent.
|
||||
// A user of this must set the EscKey event at the end.
|
||||
func (m *TerminalMock) SetEvents(events ...termbox.Event) {
|
||||
m.eventsMu.Lock()
|
||||
defer m.eventsMu.Unlock()
|
||||
m.events = events
|
||||
}
|
||||
|
||||
// SetEventsV2 sets all events, which are fetched by pollEvent.
|
||||
// A user of this must set the EscKey event at the end.
|
||||
func (m *TerminalMock) SetEventsV2(events ...tcell.Event) {
|
||||
for _, event := range events {
|
||||
switch event := event.(type) {
|
||||
case *tcell.EventKey:
|
||||
ek := event
|
||||
m.simScreen.InjectKey(ek.Key(), ek.Rune(), ek.Modifiers())
|
||||
case *tcell.EventResize:
|
||||
er := event
|
||||
w, h := er.Size()
|
||||
m.simScreen.SetSize(w, h)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetResult returns a flushed string that is displayed to the actual terminal.
|
||||
// It contains all escape sequences such that ANSI escape code.
|
||||
func (m *TerminalMock) GetResult() string {
|
||||
if !m.v2 {
|
||||
m.resultMu.RLock()
|
||||
defer m.resultMu.RUnlock()
|
||||
return m.result
|
||||
}
|
||||
|
||||
var s string
|
||||
|
||||
// set cursor for snapshot test
|
||||
setCursor := func() {
|
||||
cursorX, cursorY, _ := m.simScreen.GetCursor()
|
||||
mainc, _, _, _ := m.simScreen.GetContent(cursorX, cursorY)
|
||||
if mainc == ' ' {
|
||||
m.simScreen.SetContent(cursorX, cursorY, '\u2588', nil, tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.ColorDefault))
|
||||
} else {
|
||||
m.simScreen.SetContent(cursorX, cursorY, mainc, nil, tcell.StyleDefault.Background(tcell.ColorWhite))
|
||||
}
|
||||
m.simScreen.Show()
|
||||
}
|
||||
|
||||
setCursor()
|
||||
|
||||
m.resultMu.Lock()
|
||||
|
||||
cells, width, height := m.simScreen.GetContents()
|
||||
|
||||
for h := 0; h < height; h++ {
|
||||
prevFg, prevBg := tcell.ColorDefault, tcell.ColorDefault
|
||||
|
||||
for w := 0; w < width; w++ {
|
||||
cell := cells[h*width+w]
|
||||
fg, bg, attr := cell.Style.Decompose()
|
||||
if fg != prevFg || bg != prevBg {
|
||||
prevFg, prevBg = fg, bg
|
||||
|
||||
s += "\x1b\x5b\x6d" // Reset previous color.
|
||||
v := parseAttrV2(fg, bg, attr)
|
||||
s += v
|
||||
}
|
||||
|
||||
s += string(cell.Runes)
|
||||
rw := runewidth.RuneWidth(cell.Runes[0])
|
||||
if rw != 0 {
|
||||
w += rw - 1
|
||||
}
|
||||
}
|
||||
s += "\n"
|
||||
}
|
||||
s += "\x1b\x5b\x6d" // Reset previous color.
|
||||
|
||||
m.resultMu.Unlock()
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (m *TerminalMock) init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *TerminalMock) size() (width int, height int) {
|
||||
m.sizeMu.RLock()
|
||||
defer m.sizeMu.RUnlock()
|
||||
return m.width, m.height
|
||||
}
|
||||
|
||||
func (m *TerminalMock) clear(fg termbox.Attribute, bg termbox.Attribute) error {
|
||||
// TODO
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *TerminalMock) setCell(x int, y int, ch rune, fg termbox.Attribute, bg termbox.Attribute) {
|
||||
m.sizeMu.RLock()
|
||||
defer m.sizeMu.RUnlock()
|
||||
m.cellsMu.Lock()
|
||||
defer m.cellsMu.Unlock()
|
||||
|
||||
if x < 0 || x >= m.width {
|
||||
return
|
||||
}
|
||||
if y < 0 || y >= m.height {
|
||||
return
|
||||
}
|
||||
m.cells[y*m.width+x] = &cell{ch: ch, fg: fg, bg: bg}
|
||||
}
|
||||
|
||||
func (m *TerminalMock) setCursor(x int, y int) {
|
||||
m.sizeMu.RLock()
|
||||
defer m.sizeMu.RUnlock()
|
||||
m.cellsMu.Lock()
|
||||
defer m.cellsMu.Unlock()
|
||||
if x < 0 || x >= m.width {
|
||||
return
|
||||
}
|
||||
if y < 0 || y >= m.height {
|
||||
return
|
||||
}
|
||||
i := y*m.width + x
|
||||
if m.cells[i] == nil {
|
||||
m.cells[y*m.width+x] = &cell{ch: '\u2588', fg: termbox.ColorWhite, bg: termbox.ColorDefault}
|
||||
} else {
|
||||
// Cursor on a rune.
|
||||
m.cells[y*m.width+x].bg = termbox.ColorWhite
|
||||
}
|
||||
}
|
||||
|
||||
func (m *TerminalMock) pollEvent() termbox.Event {
|
||||
m.eventsMu.Lock()
|
||||
defer m.eventsMu.Unlock()
|
||||
if len(m.events) == 0 {
|
||||
panic("pollEvent called with empty events. have you set expected events by SetEvents?")
|
||||
}
|
||||
e := m.events[0]
|
||||
m.events = m.events[1:]
|
||||
// Wait a moment for goroutine scheduling.
|
||||
time.Sleep(m.sleepDuration)
|
||||
return e
|
||||
}
|
||||
|
||||
// flush displays all items with formatted layout.
|
||||
func (m *TerminalMock) flush() {
|
||||
m.cellsMu.RLock()
|
||||
|
||||
var s string
|
||||
for j := 0; j < m.height; j++ {
|
||||
prevFg, prevBg := termbox.ColorDefault, termbox.ColorDefault
|
||||
for i := 0; i < m.width; i++ {
|
||||
c := m.cells[j*m.width+i]
|
||||
if c == nil {
|
||||
s += " "
|
||||
prevFg, prevBg = termbox.ColorDefault, termbox.ColorDefault
|
||||
continue
|
||||
} else {
|
||||
var fgReset bool
|
||||
if c.fg != prevFg {
|
||||
s += "\x1b\x5b\x6d" // Reset previous color.
|
||||
s += parseAttr(c.fg, true)
|
||||
prevFg = c.fg
|
||||
prevBg = termbox.ColorDefault
|
||||
fgReset = true
|
||||
}
|
||||
if c.bg != prevBg {
|
||||
if !fgReset {
|
||||
s += "\x1b\x5b\x6d" // Reset previous color.
|
||||
prevFg = termbox.ColorDefault
|
||||
}
|
||||
s += parseAttr(c.bg, false)
|
||||
prevBg = c.bg
|
||||
}
|
||||
s += string(c.ch)
|
||||
rw := runewidth.RuneWidth(c.ch)
|
||||
if rw != 0 {
|
||||
i += rw - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
s += "\n"
|
||||
}
|
||||
s += "\x1b\x5b\x6d" // Reset previous color.
|
||||
|
||||
m.cellsMu.RUnlock()
|
||||
m.cellsMu.Lock()
|
||||
m.cells = make([]*cell, m.width*m.height)
|
||||
m.cellsMu.Unlock()
|
||||
|
||||
m.resultMu.Lock()
|
||||
defer m.resultMu.Unlock()
|
||||
|
||||
m.result = s
|
||||
}
|
||||
|
||||
func (m *TerminalMock) close() {}
|
||||
|
||||
// UseMockedTerminal switches the terminal, which is used from
|
||||
// this package to a mocked one.
|
||||
func UseMockedTerminal() *TerminalMock {
|
||||
f := newFinder()
|
||||
return f.UseMockedTerminal()
|
||||
}
|
||||
|
||||
// UseMockedTerminalV2 switches the terminal, which is used from
|
||||
// this package to a mocked one.
|
||||
func UseMockedTerminalV2() *TerminalMock {
|
||||
f := newFinder()
|
||||
return f.UseMockedTerminalV2()
|
||||
}
|
||||
|
||||
func (f *finder) UseMockedTerminal() *TerminalMock {
|
||||
m := &TerminalMock{}
|
||||
f.term = m
|
||||
return m
|
||||
}
|
||||
|
||||
func (f *finder) UseMockedTerminalV2() *TerminalMock {
|
||||
screen := tcell.NewSimulationScreen("UTF-8")
|
||||
if err := screen.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
m := &TerminalMock{
|
||||
simScreen: screen,
|
||||
v2: true,
|
||||
}
|
||||
f.term = m
|
||||
return m
|
||||
}
|
||||
|
||||
// parseAttr parses an attribute of termbox
|
||||
// as an escape sequence.
|
||||
// parseAttr doesn't support output modes othar than color256 in termbox-go.
|
||||
func parseAttr(attr termbox.Attribute, isFg bool) string {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("\x1b[")
|
||||
if attr >= termbox.AttrReverse {
|
||||
buf.WriteString("7;")
|
||||
attr -= termbox.AttrReverse
|
||||
}
|
||||
if attr >= termbox.AttrUnderline {
|
||||
buf.WriteString("4;")
|
||||
attr -= termbox.AttrUnderline
|
||||
}
|
||||
if attr >= termbox.AttrBold {
|
||||
buf.WriteString("1;")
|
||||
attr -= termbox.AttrBold
|
||||
}
|
||||
|
||||
if attr > termbox.ColorWhite {
|
||||
panic(fmt.Sprintf("invalid color code: %d", attr))
|
||||
}
|
||||
|
||||
if attr == termbox.ColorDefault {
|
||||
if isFg {
|
||||
buf.WriteString("39")
|
||||
} else {
|
||||
buf.WriteString("49")
|
||||
}
|
||||
} else {
|
||||
color := int(attr) - 1
|
||||
if isFg {
|
||||
fmt.Fprintf(&buf, "38;5;%d", color)
|
||||
} else {
|
||||
fmt.Fprintf(&buf, "48;5;%d", color)
|
||||
}
|
||||
}
|
||||
buf.WriteString("m")
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// parseAttrV2 parses color and attribute for testing.
|
||||
func parseAttrV2(fg, bg tcell.Color, attr tcell.AttrMask) string {
|
||||
if attr == tcell.AttrInvalid {
|
||||
panic("invalid attribute")
|
||||
}
|
||||
|
||||
var params []string
|
||||
if attr&tcell.AttrBold == tcell.AttrBold {
|
||||
params = append(params, "1")
|
||||
attr ^= tcell.AttrBold
|
||||
}
|
||||
if attr&tcell.AttrBlink == tcell.AttrBlink {
|
||||
params = append(params, "5")
|
||||
attr ^= tcell.AttrBlink
|
||||
}
|
||||
if attr&tcell.AttrReverse == tcell.AttrReverse {
|
||||
params = append(params, "7")
|
||||
attr ^= tcell.AttrReverse
|
||||
}
|
||||
if attr&tcell.AttrUnderline == tcell.AttrUnderline {
|
||||
params = append(params, "4")
|
||||
attr ^= tcell.AttrUnderline
|
||||
}
|
||||
if attr&tcell.AttrDim == tcell.AttrDim {
|
||||
params = append(params, "2")
|
||||
attr ^= tcell.AttrDim
|
||||
}
|
||||
if attr&tcell.AttrItalic == tcell.AttrItalic {
|
||||
params = append(params, "3")
|
||||
attr ^= tcell.AttrItalic
|
||||
}
|
||||
if attr&tcell.AttrStrikeThrough == tcell.AttrStrikeThrough {
|
||||
params = append(params, "9")
|
||||
attr ^= tcell.AttrStrikeThrough
|
||||
}
|
||||
|
||||
switch {
|
||||
case fg == 0: // Ignore.
|
||||
case fg == tcell.ColorDefault:
|
||||
params = append(params, "39")
|
||||
case fg > tcell.Color255:
|
||||
r, g, b := fg.RGB()
|
||||
params = append(params, "38", "2", fmt.Sprint(r), fmt.Sprint(g), fmt.Sprint(b))
|
||||
default:
|
||||
params = append(params, "38", "5", fmt.Sprint(fg-tcell.ColorValid))
|
||||
}
|
||||
|
||||
switch {
|
||||
case bg == 0: // Ignore.
|
||||
case bg == tcell.ColorDefault:
|
||||
params = append(params, "49")
|
||||
case bg > tcell.Color255:
|
||||
r, g, b := bg.RGB()
|
||||
params = append(params, "48", "2", fmt.Sprint(r), fmt.Sprint(g), fmt.Sprint(b))
|
||||
default:
|
||||
params = append(params, "48", "5", fmt.Sprint(bg-tcell.ColorValid))
|
||||
}
|
||||
|
||||
return fmt.Sprintf("\x1b[%sm", strings.Join(params, ";"))
|
||||
}
|
||||
|
||||
func toAnsi3bit(color tcell.Color) int {
|
||||
colors := []tcell.Color{
|
||||
tcell.ColorBlack, tcell.ColorRed, tcell.ColorGreen, tcell.ColorYellow, tcell.ColorBlue, tcell.ColorDarkMagenta, tcell.ColorDarkCyan, tcell.ColorWhite,
|
||||
}
|
||||
for i, c := range colors {
|
||||
if c == color {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
Reference in New Issue
Block a user