fzf/src/tui/tcell.go

800 lines
18 KiB
Go
Raw Normal View History

2022-03-29 12:20:33 +00:00
//go:build tcell || windows
2016-10-24 03:45:45 +00:00
package tui
import (
"os"
2016-10-24 03:45:45 +00:00
"time"
"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v2/encoding"
"github.com/junegunn/fzf/src/util"
"github.com/rivo/uniseg"
2016-10-24 03:45:45 +00:00
)
func HasFullscreenRenderer() bool {
return true
}
var DefaultBorderShape BorderShape = BorderSharp
func asTcellColor(color Color) tcell.Color {
2022-09-26 05:09:38 +00:00
if color == colDefault {
return tcell.ColorDefault
}
value := uint64(tcell.ColorValid) + uint64(color)
if color.is24() {
value = value | uint64(tcell.ColorIsRGB)
}
return tcell.Color(value)
}
2017-01-07 16:30:31 +00:00
func (p ColorPair) style() tcell.Style {
style := tcell.StyleDefault
return style.Foreground(asTcellColor(p.Fg())).Background(asTcellColor(p.Bg()))
2017-01-07 16:30:31 +00:00
}
2016-10-24 03:45:45 +00:00
type Attr int32
2017-01-07 16:30:31 +00:00
type TcellWindow struct {
color bool
preview bool
top int
left int
width int
height int
normal ColorPair
lastX int
lastY int
moveCursor bool
borderStyle BorderStyle
2016-10-24 03:45:45 +00:00
}
2017-01-07 16:30:31 +00:00
func (w *TcellWindow) Top() int {
return w.top
2016-10-24 03:45:45 +00:00
}
2017-01-07 16:30:31 +00:00
func (w *TcellWindow) Left() int {
return w.left
2016-10-24 03:45:45 +00:00
}
2017-01-07 16:30:31 +00:00
func (w *TcellWindow) Width() int {
return w.width
}
func (w *TcellWindow) Height() int {
return w.height
}
func (w *TcellWindow) Refresh() {
if w.moveCursor {
_screen.ShowCursor(w.left+w.lastX, w.top+w.lastY)
w.moveCursor = false
}
w.lastX = 0
w.lastY = 0
}
2016-10-24 03:45:45 +00:00
2017-01-07 16:30:31 +00:00
func (w *TcellWindow) FinishFill() {
// NO-OP
2016-10-24 03:45:45 +00:00
}
const (
Bold Attr = Attr(tcell.AttrBold)
Dim = Attr(tcell.AttrDim)
Blink = Attr(tcell.AttrBlink)
Reverse = Attr(tcell.AttrReverse)
Underline = Attr(tcell.AttrUnderline)
StrikeThrough = Attr(tcell.AttrStrikeThrough)
Italic = Attr(tcell.AttrItalic)
2016-10-24 03:45:45 +00:00
)
const (
AttrUndefined = Attr(0)
AttrRegular = Attr(1 << 7)
AttrClear = Attr(1 << 8)
2016-10-24 03:45:45 +00:00
)
func (r *FullscreenRenderer) PassThrough(str string) {
// No-op
2023-11-12 13:08:08 +00:00
// https://github.com/gdamore/tcell/pull/650#issuecomment-1806442846
Experimental support for Kitty image protocol in preview window Close #3228 * Works inside and outside of tmux * There is a problem where fzf unnecessarily displays the scroll offset indicator at the topbright of the screen when the image just fits the preview window. This is because `kitty icat` generates an extra line after the image area. # A 5-row images; an extra row at the end confuses fzf ["\e_Ga ... \e[9C􎻮̅̅ࠪ􎻮̅̍ࠪ􎻮̅̎ࠪ􎻮̅̐ࠪ􎻮̅̒ࠪ􎻮̅̽ࠪ􎻮̅̾ࠪ􎻮̅̿ࠪ􎻮̅͆ࠪ􎻮̅͊ࠪ􎻮̅͋ࠪ\n", "\r\e[9C􎻮̍̅ࠪ􎻮̍̍ࠪ􎻮̍̎ࠪ􎻮̍̐ࠪ􎻮̍̒ࠪ􎻮̍̽ࠪ􎻮̍̾ࠪ􎻮̍̿ࠪ􎻮̍͆ࠪ􎻮̍͊ࠪ􎻮̍͋ࠪ\n", "\r\e[9C􎻮̎̅ࠪ􎻮̎̍ࠪ􎻮̎̎ࠪ􎻮̎̐ࠪ􎻮̎̒ࠪ􎻮̎̽ࠪ􎻮̎̾ࠪ􎻮̎̿ࠪ􎻮̎͆ࠪ􎻮̎͊ࠪ􎻮̎͋ࠪ\n", "\r\e[9C􎻮̐̅ࠪ􎻮̐̍ࠪ􎻮̐̎ࠪ􎻮̐̐ࠪ􎻮̐̒ࠪ􎻮̐̽ࠪ􎻮̐̾ࠪ􎻮̐̿ࠪ􎻮̐͆ࠪ􎻮̐͊ࠪ􎻮̐͋ࠪ\n", "\r\e[9C􎻮̒̅ࠪ􎻮̒̍ࠪ􎻮̒̎ࠪ􎻮̒̐ࠪ􎻮̒̒ࠪ􎻮̒̽ࠪ􎻮̒̾ࠪ􎻮̒̿ࠪ􎻮̒͆ࠪ􎻮̒͊ࠪ􎻮̒͋ࠪ\n", "\r\e[39m\e8"] * Example: fzf --preview=' if file --mime-type {} | grep -qF 'image/'; then # --transfer-mode=memory is the fastest option but if you want fzf to be able # to redraw the image on terminal resize or on 'change-preview-window', # you need to use --transfer-mode=stream. kitty icat --clear --transfer-mode=memory --stdin=no --place=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}@0x0 {} else bat --color=always {} fi '
2023-10-07 09:20:27 +00:00
}
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
2017-01-07 16:30:31 +00:00
func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
2016-10-24 03:45:45 +00:00
if _screen.Colors() >= 256 {
return Dark256
}
return Default16
}
var (
_colorToAttribute = []tcell.Color{
tcell.ColorBlack,
tcell.ColorRed,
tcell.ColorGreen,
tcell.ColorYellow,
tcell.ColorBlue,
tcell.ColorDarkMagenta,
tcell.ColorLightCyan,
tcell.ColorWhite,
}
)
func (c Color) Style() tcell.Color {
if c <= colDefault {
return tcell.ColorDefault
} else if c >= colBlack && c <= colWhite {
return _colorToAttribute[int(c)]
} else {
return tcell.Color(c)
}
}
func (a Attr) Merge(b Attr) Attr {
return a | b
}
// handle the following as private members of FullscreenRenderer instance
// they are declared here to prevent introducing tcell library in non-windows builds
2016-10-24 03:45:45 +00:00
var (
_screen tcell.Screen
_prevMouseButton tcell.ButtonMask
2024-02-01 07:25:53 +00:00
_initialResize bool = true
2016-10-24 03:45:45 +00:00
)
2017-01-07 16:30:31 +00:00
func (r *FullscreenRenderer) initScreen() {
2016-10-24 03:45:45 +00:00
s, e := tcell.NewScreen()
if e != nil {
errorExit(e.Error())
2016-10-24 03:45:45 +00:00
}
if e = s.Init(); e != nil {
errorExit(e.Error())
}
2017-01-07 16:30:31 +00:00
if r.mouse {
s.EnableMouse()
} else {
s.DisableMouse()
2016-10-24 03:45:45 +00:00
}
_screen = s
}
2017-01-07 16:30:31 +00:00
func (r *FullscreenRenderer) Init() {
if os.Getenv("TERM") == "cygwin" {
os.Setenv("TERM", "")
}
encoding.Register()
2017-01-07 16:30:31 +00:00
r.initScreen()
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
}
2016-10-24 03:45:45 +00:00
func (r *FullscreenRenderer) Top() int {
return 0
}
2017-01-07 16:30:31 +00:00
func (r *FullscreenRenderer) MaxX() int {
2016-10-24 03:45:45 +00:00
ncols, _ := _screen.Size()
return int(ncols)
}
2017-01-07 16:30:31 +00:00
func (r *FullscreenRenderer) MaxY() int {
2016-10-24 03:45:45 +00:00
_, nlines := _screen.Size()
return int(nlines)
}
2017-01-07 16:30:31 +00:00
func (w *TcellWindow) X() int {
return w.lastX
}
func (w *TcellWindow) Y() int {
return w.lastY
}
2017-01-07 16:30:31 +00:00
func (r *FullscreenRenderer) Clear() {
_screen.Sync()
2016-10-24 03:45:45 +00:00
_screen.Clear()
}
func (r *FullscreenRenderer) NeedScrollbarRedraw() bool {
return true
}
2024-01-24 06:59:54 +00:00
func (r *FullscreenRenderer) ShouldEmitResizeEvent() bool {
return true
}
2017-01-07 16:30:31 +00:00
func (r *FullscreenRenderer) Refresh() {
2016-10-24 03:45:45 +00:00
// noop
}
// TODO: Pixel width and height not implemented
func (r *FullscreenRenderer) Size() TermSize {
cols, lines := _screen.Size()
return TermSize{lines, cols, 0, 0}
2023-10-22 16:01:47 +00:00
}
2017-01-07 16:30:31 +00:00
func (r *FullscreenRenderer) GetChar() Event {
2016-10-24 03:45:45 +00:00
ev := _screen.PollEvent()
switch ev := ev.(type) {
case *tcell.EventResize:
2024-02-01 07:25:53 +00:00
// Ignore the first resize event
// https://github.com/gdamore/tcell/blob/v2.7.0/TUTORIAL.md?plain=1#L18
if _initialResize {
_initialResize = false
return Event{Invalid, 0, nil}
}
return Event{Resize, 0, nil}
2016-10-24 03:45:45 +00:00
// process mouse events:
case *tcell.EventMouse:
// mouse down events have zeroed buttons, so we can't use them
// mouse up event consists of two events, 1. (main) event with modifier and other metadata, 2. event with zeroed buttons
// so mouse click is three consecutive events, but the first and last are indistinguishable from movement events (with released buttons)
// dragging has same structure, it only repeats the middle (main) event appropriately
2016-10-24 03:45:45 +00:00
x, y := ev.Position()
mod := ev.Modifiers() != 0
// since we dont have mouse down events (unlike LightRenderer), we need to track state in prevButton
prevButton, button := _prevMouseButton, ev.Buttons()
_prevMouseButton = button
drag := prevButton == button
switch {
case button&tcell.WheelDown != 0:
return Event{Mouse, 0, &MouseEvent{y, x, -1, false, false, false, mod}}
case button&tcell.WheelUp != 0:
return Event{Mouse, 0, &MouseEvent{y, x, +1, false, false, false, mod}}
case button&tcell.Button1 != 0:
double := false
if !drag {
// all potential double click events put their coordinates in the clicks array
// double click event has two conditions, temporal and spatial, the first is checked here
now := time.Now()
if now.Sub(r.prevDownTime) < doubleClickDuration {
r.clicks = append(r.clicks, [2]int{x, y})
} else {
r.clicks = [][2]int{{x, y}}
}
r.prevDownTime = now
// detect double clicks (also check for spatial condition)
n := len(r.clicks)
double = n > 1 && r.clicks[n-2][0] == r.clicks[n-1][0] && r.clicks[n-2][1] == r.clicks[n-1][1]
if double {
// make sure two consecutive double clicks require four clicks
r.clicks = [][2]int{}
}
}
// fire single or double click event
return Event{Mouse, 0, &MouseEvent{y, x, 0, true, !double, double, mod}}
case button&tcell.Button2 != 0:
return Event{Mouse, 0, &MouseEvent{y, x, 0, false, true, false, mod}}
default:
2016-10-24 03:45:45 +00:00
// double and single taps on Windows don't quite work due to
// the console acting on the events and not allowing us
// to consume them.
left := button&tcell.Button1 != 0
down := left || button&tcell.Button3 != 0
2016-10-24 03:45:45 +00:00
double := false
return Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, mod}}
2016-10-24 03:45:45 +00:00
}
// process keyboard:
case *tcell.EventKey:
mods := ev.Modifiers()
none := mods == tcell.ModNone
alt := (mods & tcell.ModAlt) > 0
ctrl := (mods & tcell.ModCtrl) > 0
shift := (mods & tcell.ModShift) > 0
ctrlAlt := ctrl && alt
altShift := alt && shift
keyfn := func(r rune) Event {
if alt {
return CtrlAltKey(r)
}
return EventType(CtrlA.Int() - 'a' + int(r)).AsEvent()
}
2016-10-24 03:45:45 +00:00
switch ev.Key() {
// section 1: Ctrl+(Alt)+[a-z]
2016-10-24 03:45:45 +00:00
case tcell.KeyCtrlA:
return keyfn('a')
2016-10-24 03:45:45 +00:00
case tcell.KeyCtrlB:
return keyfn('b')
2016-10-24 03:45:45 +00:00
case tcell.KeyCtrlC:
return keyfn('c')
2016-10-24 03:45:45 +00:00
case tcell.KeyCtrlD:
return keyfn('d')
2016-10-24 03:45:45 +00:00
case tcell.KeyCtrlE:
return keyfn('e')
2016-10-24 03:45:45 +00:00
case tcell.KeyCtrlF:
return keyfn('f')
2016-10-24 03:45:45 +00:00
case tcell.KeyCtrlG:
return keyfn('g')
case tcell.KeyCtrlH:
switch ev.Rune() {
case 0:
if ctrl {
return Event{BSpace, 0, nil}
}
case rune(tcell.KeyCtrlH):
switch {
case ctrl:
return keyfn('h')
case alt:
return Event{AltBS, 0, nil}
case none, shift:
return Event{BSpace, 0, nil}
}
}
case tcell.KeyCtrlI:
return keyfn('i')
2016-10-24 03:45:45 +00:00
case tcell.KeyCtrlJ:
return keyfn('j')
2016-10-24 03:45:45 +00:00
case tcell.KeyCtrlK:
return keyfn('k')
2016-10-24 03:45:45 +00:00
case tcell.KeyCtrlL:
return keyfn('l')
2016-10-24 03:45:45 +00:00
case tcell.KeyCtrlM:
return keyfn('m')
2016-10-24 03:45:45 +00:00
case tcell.KeyCtrlN:
return keyfn('n')
2016-10-24 03:45:45 +00:00
case tcell.KeyCtrlO:
return keyfn('o')
2016-10-24 03:45:45 +00:00
case tcell.KeyCtrlP:
return keyfn('p')
2016-10-24 03:45:45 +00:00
case tcell.KeyCtrlQ:
return keyfn('q')
2016-10-24 03:45:45 +00:00
case tcell.KeyCtrlR:
return keyfn('r')
2016-10-24 03:45:45 +00:00
case tcell.KeyCtrlS:
return keyfn('s')
2016-10-24 03:45:45 +00:00
case tcell.KeyCtrlT:
return keyfn('t')
2016-10-24 03:45:45 +00:00
case tcell.KeyCtrlU:
return keyfn('u')
2016-10-24 03:45:45 +00:00
case tcell.KeyCtrlV:
return keyfn('v')
2016-10-24 03:45:45 +00:00
case tcell.KeyCtrlW:
return keyfn('w')
2016-10-24 03:45:45 +00:00
case tcell.KeyCtrlX:
return keyfn('x')
2016-10-24 03:45:45 +00:00
case tcell.KeyCtrlY:
return keyfn('y')
2016-10-24 03:45:45 +00:00
case tcell.KeyCtrlZ:
return keyfn('z')
// section 2: Ctrl+[ \]_]
case tcell.KeyCtrlSpace:
return Event{CtrlSpace, 0, nil}
2019-11-14 13:39:25 +00:00
case tcell.KeyCtrlBackslash:
return Event{CtrlBackSlash, 0, nil}
case tcell.KeyCtrlRightSq:
return Event{CtrlRightBracket, 0, nil}
case tcell.KeyCtrlCarat:
return Event{CtrlCaret, 0, nil}
2019-11-14 13:39:25 +00:00
case tcell.KeyCtrlUnderscore:
return Event{CtrlSlash, 0, nil}
// section 3: (Alt)+Backspace2
2017-04-28 13:58:08 +00:00
case tcell.KeyBackspace2:
if alt {
return Event{AltBS, 0, nil}
}
2016-10-24 03:45:45 +00:00
return Event{BSpace, 0, nil}
// section 4: (Alt+Shift)+Key(Up|Down|Left|Right)
2016-10-24 03:45:45 +00:00
case tcell.KeyUp:
if altShift {
return Event{AltSUp, 0, nil}
}
if shift {
return Event{SUp, 0, nil}
}
if alt {
return Event{AltUp, 0, nil}
}
2016-10-24 03:45:45 +00:00
return Event{Up, 0, nil}
case tcell.KeyDown:
if altShift {
return Event{AltSDown, 0, nil}
}
if shift {
return Event{SDown, 0, nil}
}
if alt {
return Event{AltDown, 0, nil}
}
2016-10-24 03:45:45 +00:00
return Event{Down, 0, nil}
case tcell.KeyLeft:
if altShift {
return Event{AltSLeft, 0, nil}
}
if shift {
return Event{SLeft, 0, nil}
}
if alt {
return Event{AltLeft, 0, nil}
}
2016-10-24 03:45:45 +00:00
return Event{Left, 0, nil}
case tcell.KeyRight:
if altShift {
return Event{AltSRight, 0, nil}
}
if shift {
return Event{SRight, 0, nil}
}
if alt {
return Event{AltRight, 0, nil}
}
2016-10-24 03:45:45 +00:00
return Event{Right, 0, nil}
// section 5: (Insert|Home|Delete|End|PgUp|PgDn|BackTab|F1-F12)
case tcell.KeyInsert:
return Event{Insert, 0, nil}
2016-10-24 03:45:45 +00:00
case tcell.KeyHome:
return Event{Home, 0, nil}
case tcell.KeyDelete:
if ctrl {
return Event{CtrlDelete, 0, nil}
}
if shift {
return Event{SDelete, 0, nil}
}
2016-10-24 03:45:45 +00:00
return Event{Del, 0, nil}
case tcell.KeyEnd:
return Event{End, 0, nil}
case tcell.KeyPgUp:
2016-10-24 03:45:45 +00:00
return Event{PgUp, 0, nil}
case tcell.KeyPgDn:
return Event{PgDn, 0, nil}
case tcell.KeyBacktab:
return Event{BTab, 0, nil}
2016-10-24 03:45:45 +00:00
case tcell.KeyF1:
return Event{F1, 0, nil}
case tcell.KeyF2:
return Event{F2, 0, nil}
case tcell.KeyF3:
return Event{F3, 0, nil}
case tcell.KeyF4:
return Event{F4, 0, nil}
case tcell.KeyF5:
return Event{F5, 0, nil}
case tcell.KeyF6:
return Event{F6, 0, nil}
case tcell.KeyF7:
return Event{F7, 0, nil}
case tcell.KeyF8:
return Event{F8, 0, nil}
case tcell.KeyF9:
return Event{F9, 0, nil}
case tcell.KeyF10:
return Event{F10, 0, nil}
case tcell.KeyF11:
return Event{F11, 0, nil}
2016-10-24 03:45:45 +00:00
case tcell.KeyF12:
return Event{F12, 0, nil}
2016-10-24 03:45:45 +00:00
// section 6: (Ctrl+Alt)+'rune'
2016-10-24 03:45:45 +00:00
case tcell.KeyRune:
r := ev.Rune()
switch {
// translate native key events to ascii control characters
case r == ' ' && ctrl:
return Event{CtrlSpace, 0, nil}
// handle AltGr characters
case ctrlAlt:
return Event{Rune, r, nil} // dropping modifiers
// simple characters (possibly with modifier)
case alt:
return AltKey(r)
default:
return Event{Rune, r, nil}
}
2016-10-24 03:45:45 +00:00
// section 7: Esc
2016-10-24 03:45:45 +00:00
case tcell.KeyEsc:
return Event{ESC, 0, nil}
}
}
// section 8: Invalid
2016-10-24 03:45:45 +00:00
return Event{Invalid, 0, nil}
}
func (r *FullscreenRenderer) Pause(clear bool) {
if clear {
_screen.Fini()
}
}
func (r *FullscreenRenderer) Resume(clear bool, sigcont bool) {
if clear {
r.initScreen()
}
2016-10-24 03:45:45 +00:00
}
2017-01-07 16:30:31 +00:00
func (r *FullscreenRenderer) Close() {
2016-10-24 03:45:45 +00:00
_screen.Fini()
}
2017-01-07 16:30:31 +00:00
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {
2016-10-24 03:45:45 +00:00
// TODO
for _, w := range windows {
2017-01-07 16:30:31 +00:00
w.Refresh()
2016-10-24 03:45:45 +00:00
}
_screen.Show()
}
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window {
normal := ColNormal
if preview {
normal = ColPreview
}
w := &TcellWindow{
color: r.theme.Colored,
preview: preview,
top: top,
left: left,
width: width,
height: height,
normal: normal,
borderStyle: borderStyle}
w.Erase()
return w
2016-10-24 03:45:45 +00:00
}
2017-01-07 16:30:31 +00:00
func (w *TcellWindow) Close() {
2016-10-24 03:45:45 +00:00
// TODO
}
func fill(x, y, w, h int, n ColorPair, r rune) {
2016-10-24 03:45:45 +00:00
for ly := 0; ly <= h; ly++ {
for lx := 0; lx <= w; lx++ {
_screen.SetContent(x+lx, y+ly, r, nil, n.style())
2016-10-24 03:45:45 +00:00
}
}
}
2017-01-07 16:30:31 +00:00
func (w *TcellWindow) Erase() {
2023-01-04 01:09:39 +00:00
fill(w.left-1, w.top, w.width+1, w.height-1, w.normal, ' ')
w.drawBorder(false)
2016-10-24 03:45:45 +00:00
}
func (w *TcellWindow) EraseMaybe() bool {
w.Erase()
return true
}
2017-01-07 16:30:31 +00:00
func (w *TcellWindow) Enclose(y int, x int) bool {
return x >= w.left && x < (w.left+w.width) &&
y >= w.top && y < (w.top+w.height)
2016-10-24 03:45:45 +00:00
}
2017-01-07 16:30:31 +00:00
func (w *TcellWindow) Move(y int, x int) {
w.lastX = x
w.lastY = y
w.moveCursor = true
2016-10-24 03:45:45 +00:00
}
2017-01-07 16:30:31 +00:00
func (w *TcellWindow) MoveAndClear(y int, x int) {
2016-10-24 03:45:45 +00:00
w.Move(y, x)
2017-01-07 16:30:31 +00:00
for i := w.lastX; i < w.width; i++ {
_screen.SetContent(i+w.left, w.lastY+w.top, rune(' '), nil, w.normal.style())
2016-10-24 03:45:45 +00:00
}
2017-01-07 16:30:31 +00:00
w.lastX = x
2016-10-24 03:45:45 +00:00
}
2017-01-07 16:30:31 +00:00
func (w *TcellWindow) Print(text string) {
w.printString(text, w.normal)
2016-10-24 03:45:45 +00:00
}
func (w *TcellWindow) printString(text string, pair ColorPair) {
2016-10-24 03:45:45 +00:00
lx := 0
a := pair.Attr()
2016-10-24 03:45:45 +00:00
style := pair.style()
if a&AttrClear == 0 {
style = style.
2016-11-08 16:45:06 +00:00
Reverse(a&Attr(tcell.AttrReverse) != 0).
Underline(a&Attr(tcell.AttrUnderline) != 0).
StrikeThrough(a&Attr(tcell.AttrStrikeThrough) != 0).
Italic(a&Attr(tcell.AttrItalic) != 0).
Blink(a&Attr(tcell.AttrBlink) != 0).
Dim(a&Attr(tcell.AttrDim) != 0)
2016-11-08 16:45:06 +00:00
}
2016-10-24 03:45:45 +00:00
gr := uniseg.NewGraphemes(text)
for gr.Next() {
st := style
rs := gr.Runes()
2016-10-24 03:45:45 +00:00
if len(rs) == 1 {
r := rs[0]
if r == '\r' {
st = style.Dim(true)
rs[0] = '␍'
} else if r == '\n' {
st = style.Dim(true)
rs[0] = '␊'
} else if r < rune(' ') { // ignore control characters
2016-10-24 03:45:45 +00:00
continue
}
}
var xPos = w.left + w.lastX + lx
var yPos = w.top + w.lastY
if xPos < (w.left+w.width) && yPos < (w.top+w.height) {
_screen.SetContent(xPos, yPos, rs[0], rs[1:], st)
}
lx += util.StringWidth(string(rs))
2016-10-24 03:45:45 +00:00
}
2017-01-07 16:30:31 +00:00
w.lastX += lx
2016-10-24 03:45:45 +00:00
}
func (w *TcellWindow) CPrint(pair ColorPair, text string) {
w.printString(text, pair)
2016-10-24 03:45:45 +00:00
}
2020-10-25 12:43:53 +00:00
func (w *TcellWindow) fillString(text string, pair ColorPair) FillReturn {
2016-10-24 03:45:45 +00:00
lx := 0
2020-10-25 12:43:53 +00:00
a := pair.Attr()
2016-10-24 03:45:45 +00:00
2016-11-08 16:45:06 +00:00
var style tcell.Style
2017-01-07 16:30:31 +00:00
if w.color {
2016-11-08 16:45:06 +00:00
style = pair.style()
} else {
style = w.normal.style()
2016-11-08 16:45:06 +00:00
}
style = style.
2016-10-24 03:45:45 +00:00
Blink(a&Attr(tcell.AttrBlink) != 0).
Bold(a&Attr(tcell.AttrBold) != 0).
Dim(a&Attr(tcell.AttrDim) != 0).
Reverse(a&Attr(tcell.AttrReverse) != 0).
Underline(a&Attr(tcell.AttrUnderline) != 0).
StrikeThrough(a&Attr(tcell.AttrStrikeThrough) != 0).
Italic(a&Attr(tcell.AttrItalic) != 0)
2016-10-24 03:45:45 +00:00
gr := uniseg.NewGraphemes(text)
Loop:
for gr.Next() {
st := style
rs := gr.Runes()
if len(rs) == 1 {
r := rs[0]
switch r {
case '\r':
st = style.Dim(true)
rs[0] = '␍'
case '\n':
w.lastY++
w.lastX = 0
lx = 0
continue Loop
}
}
2016-10-24 03:45:45 +00:00
// word wrap:
xPos := w.left + w.lastX + lx
if xPos >= (w.left + w.width) {
w.lastY++
w.lastX = 0
lx = 0
xPos = w.left
}
2016-10-24 03:45:45 +00:00
yPos := w.top + w.lastY
if yPos >= (w.top + w.height) {
return FillSuspend
2016-10-24 03:45:45 +00:00
}
_screen.SetContent(xPos, yPos, rs[0], rs[1:], st)
lx += util.StringWidth(string(rs))
2016-10-24 03:45:45 +00:00
}
2017-01-07 16:30:31 +00:00
w.lastX += lx
2020-10-25 12:43:53 +00:00
if w.lastX == w.width {
w.lastY++
w.lastX = 0
return FillNextLine
}
2016-10-24 03:45:45 +00:00
return FillContinue
2016-10-24 03:45:45 +00:00
}
func (w *TcellWindow) Fill(str string) FillReturn {
2020-10-25 12:43:53 +00:00
return w.fillString(str, w.normal)
2016-10-24 03:45:45 +00:00
}
func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
if fg == colDefault {
fg = w.normal.Fg()
}
if bg == colDefault {
bg = w.normal.Bg()
}
2020-10-25 12:43:53 +00:00
return w.fillString(str, NewColorPair(fg, bg, a))
2016-10-24 03:45:45 +00:00
}
2023-10-22 16:01:47 +00:00
func (w *TcellWindow) DrawBorder() {
w.drawBorder(false)
}
func (w *TcellWindow) DrawHBorder() {
w.drawBorder(true)
}
func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
shape := w.borderStyle.shape
if shape == BorderNone {
return
}
2017-01-07 16:30:31 +00:00
left := w.left
right := left + w.width
top := w.top
bot := top + w.height
2016-10-24 03:45:45 +00:00
2016-11-08 16:45:06 +00:00
var style tcell.Style
2017-01-07 16:30:31 +00:00
if w.color {
if w.preview {
style = ColPreviewBorder.style()
} else {
style = ColBorder.style()
}
2016-11-08 16:45:06 +00:00
} else {
style = w.normal.style()
2016-11-08 16:45:06 +00:00
}
2016-10-24 03:45:45 +00:00
hw := runeWidth(w.borderStyle.top)
switch shape {
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderHorizontal, BorderTop:
2023-01-16 10:34:28 +00:00
max := right - 2*hw
if shape == BorderHorizontal || shape == BorderTop {
max = right - hw
}
// tcell has an issue displaying two overlapping wide runes
// e.g. SetContent( HH )
// SetContent( TR )
// ==================
// ( HH ) => TR is ignored
for x := left; x <= max; x += hw {
_screen.SetContent(x, top, w.borderStyle.top, nil, style)
}
2016-10-24 03:45:45 +00:00
}
switch shape {
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderHorizontal, BorderBottom:
2023-01-16 10:34:28 +00:00
max := right - 2*hw
if shape == BorderHorizontal || shape == BorderBottom {
max = right - hw
}
for x := left; x <= max; x += hw {
_screen.SetContent(x, bot-1, w.borderStyle.bottom, nil, style)
}
}
if !onlyHorizontal {
switch shape {
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderVertical, BorderLeft:
for y := top; y < bot; y++ {
_screen.SetContent(left, y, w.borderStyle.left, nil, style)
}
}
switch shape {
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderVertical, BorderRight:
vw := runeWidth(w.borderStyle.right)
for y := top; y < bot; y++ {
_screen.SetContent(right-vw, y, w.borderStyle.right, nil, style)
}
}
}
switch shape {
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble:
_screen.SetContent(left, top, w.borderStyle.topLeft, nil, style)
_screen.SetContent(right-runeWidth(w.borderStyle.topRight), top, w.borderStyle.topRight, nil, style)
_screen.SetContent(left, bot-1, w.borderStyle.bottomLeft, nil, style)
_screen.SetContent(right-runeWidth(w.borderStyle.bottomRight), bot-1, w.borderStyle.bottomRight, nil, style)
}
2016-10-24 03:45:45 +00:00
}