1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-07-31 14:24:25 +03:00

refactor to only have one context per view

This commit is contained in:
Jesse Duffield
2022-06-13 11:01:26 +10:00
parent 6dfef08efc
commit 524bf83a4a
372 changed files with 28866 additions and 6902 deletions

View File

@ -1,8 +1,13 @@
<a href="https://stand-with-ukraine.pp.ua">
<img src="https://upload.wikimedia.org/wikipedia/commons/d/d2/Flag_of_Ukraine.png" height="20px" width="100%"/>
</a>
# ![Tcell](logos/tcell.png)
<img src="logos/tcell.png" style="float: right"/>
Please see [here](UKRAINE.md) for an important message for the people of Russia.
# Tcell
_Tcell_ is a _Go_ package that provides a cell based view for text terminals, like _XTerm_.
It was inspired by _termbox_, but includes many additional improvements.

View File

@ -1,6 +1,7 @@
//go:build windows
// +build windows
// Copyright 2021 The TCell Authors
// Copyright 2022 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
@ -114,22 +115,23 @@ var (
// characters (Unicode) are in use. The documentation refers to them
// without this suffix, as the resolution is made via preprocessor.
var (
procReadConsoleInput = k32.NewProc("ReadConsoleInputW")
procWaitForMultipleObjects = k32.NewProc("WaitForMultipleObjects")
procCreateEvent = k32.NewProc("CreateEventW")
procSetEvent = k32.NewProc("SetEvent")
procGetConsoleCursorInfo = k32.NewProc("GetConsoleCursorInfo")
procSetConsoleCursorInfo = k32.NewProc("SetConsoleCursorInfo")
procSetConsoleCursorPosition = k32.NewProc("SetConsoleCursorPosition")
procSetConsoleMode = k32.NewProc("SetConsoleMode")
procGetConsoleMode = k32.NewProc("GetConsoleMode")
procGetConsoleScreenBufferInfo = k32.NewProc("GetConsoleScreenBufferInfo")
procFillConsoleOutputAttribute = k32.NewProc("FillConsoleOutputAttribute")
procFillConsoleOutputCharacter = k32.NewProc("FillConsoleOutputCharacterW")
procSetConsoleWindowInfo = k32.NewProc("SetConsoleWindowInfo")
procSetConsoleScreenBufferSize = k32.NewProc("SetConsoleScreenBufferSize")
procSetConsoleTextAttribute = k32.NewProc("SetConsoleTextAttribute")
procMessageBeep = u32.NewProc("MessageBeep")
procReadConsoleInput = k32.NewProc("ReadConsoleInputW")
procWaitForMultipleObjects = k32.NewProc("WaitForMultipleObjects")
procCreateEvent = k32.NewProc("CreateEventW")
procSetEvent = k32.NewProc("SetEvent")
procGetConsoleCursorInfo = k32.NewProc("GetConsoleCursorInfo")
procSetConsoleCursorInfo = k32.NewProc("SetConsoleCursorInfo")
procSetConsoleCursorPosition = k32.NewProc("SetConsoleCursorPosition")
procSetConsoleMode = k32.NewProc("SetConsoleMode")
procGetConsoleMode = k32.NewProc("GetConsoleMode")
procGetConsoleScreenBufferInfo = k32.NewProc("GetConsoleScreenBufferInfo")
procFillConsoleOutputAttribute = k32.NewProc("FillConsoleOutputAttribute")
procFillConsoleOutputCharacter = k32.NewProc("FillConsoleOutputCharacterW")
procSetConsoleWindowInfo = k32.NewProc("SetConsoleWindowInfo")
procSetConsoleScreenBufferSize = k32.NewProc("SetConsoleScreenBufferSize")
procSetConsoleTextAttribute = k32.NewProc("SetConsoleTextAttribute")
procGetLargestConsoleWindowSize = k32.NewProc("GetLargestConsoleWindowSize")
procMessageBeep = u32.NewProc("MessageBeep")
)
const (
@ -189,7 +191,7 @@ func (s *cScreen) Init() error {
s.in = in
out, e := syscall.Open("CONOUT$", syscall.O_RDWR, 0)
if e != nil {
syscall.Close(s.in)
_ = syscall.Close(s.in)
return e
}
s.out = out
@ -224,15 +226,15 @@ func (s *cScreen) Init() error {
s.resize()
s.fini = false
s.setInMode(modeResizeEn | modeExtndFlg)
s.setInMode(modeResizeEn | modeExtendFlg)
// 24-bit color is opt-in for now, because we can't figure out
// to make it work consistently.
if s.truecolor {
s.setOutMode(modeVtOutput | modeNoAutoNL | modeCookedOut)
var omode uint32
s.getOutMode(&omode)
if omode&modeVtOutput == modeVtOutput {
var om uint32
s.getOutMode(&om)
if om&modeVtOutput == modeVtOutput {
s.vten = true
} else {
s.truecolor = false
@ -268,9 +270,9 @@ func (s *cScreen) DisableMouse() {
func (s *cScreen) enableMouse(on bool) {
if on {
s.setInMode(modeResizeEn | modeMouseEn | modeExtndFlg)
s.setInMode(modeResizeEn | modeMouseEn | modeExtendFlg)
} else {
s.setInMode(modeResizeEn | modeExtndFlg)
s.setInMode(modeResizeEn | modeExtendFlg)
}
}
@ -292,7 +294,7 @@ func (s *cScreen) disengage() {
}
s.running = false
stopQ := s.stopQ
procSetEvent.Call(uintptr(s.cancelflag))
_, _, _ = procSetEvent.Call(uintptr(s.cancelflag))
close(stopQ)
s.Unlock()
@ -307,7 +309,7 @@ func (s *cScreen) disengage() {
s.clearScreen(StyleDefault, false)
s.setCursorPos(0, 0, false)
s.setCursorInfo(&s.ocursor)
procSetConsoleTextAttribute.Call(
_, _, _ = procSetConsoleTextAttribute.Call(
uintptr(s.out),
uintptr(s.mapStyle(StyleDefault)))
}
@ -421,7 +423,7 @@ type rect struct {
func (s *cScreen) emitVtString(vs string) {
esc := utf16.Encode([]rune(vs))
syscall.WriteConsole(s.out, &esc[0], uint32(len(esc)), nil, nil)
_ = syscall.WriteConsole(s.out, &esc[0], uint32(len(esc)), nil, nil)
}
func (s *cScreen) showCursor() {
@ -487,8 +489,8 @@ const (
keyEvent uint16 = 1
mouseEvent uint16 = 2
resizeEvent uint16 = 4
menuEvent uint16 = 8 // don't use
focusEvent uint16 = 16 // don't use
// menuEvent uint16 = 8 // don't use
// focusEvent uint16 = 16 // don't use
)
type mouseRecord struct {
@ -500,10 +502,10 @@ type mouseRecord struct {
}
const (
mouseDoubleClick uint32 = 0x2
mouseHWheeled uint32 = 0x8
mouseVWheeled uint32 = 0x4
mouseMoved uint32 = 0x1
mouseHWheeled uint32 = 0x8
mouseVWheeled uint32 = 0x4
// mouseDoubleClick uint32 = 0x2
// mouseMoved uint32 = 0x1
)
type resizeRecord struct {
@ -590,6 +592,8 @@ var vkKeys = map[uint16]Key{
vkInsert: KeyInsert,
vkDelete: KeyDelete,
vkHelp: KeyHelp,
vkEscape: KeyEscape,
vkSpace: ' ',
vkF1: KeyF1,
vkF2: KeyF2,
vkF3: KeyF3,
@ -806,11 +810,11 @@ func (s *cScreen) scanInput(stopQ chan struct{}) {
}
}
// Windows console can display 8 characters, in either low or high intensity
func (s *cScreen) Colors() int {
if s.vten {
return 1 << 24
}
// Windows console can display 8 colors, in either low or high intensity
return 16
}
@ -868,10 +872,10 @@ func (s *cScreen) mapStyle(style Style) uint16 {
// views.
if a&AttrReverse != 0 {
attr = ba
attr |= (fa << 4)
attr |= fa << 4
} else {
attr = fa
attr |= (ba << 4)
attr |= ba << 4
}
if a&AttrBold != 0 {
attr |= 0x8
@ -895,19 +899,19 @@ func (s *cScreen) SetCell(x, y int, style Style, ch ...rune) {
}
}
func (s *cScreen) SetContent(x, y int, mainc rune, combc []rune, style Style) {
func (s *cScreen) SetContent(x, y int, primary rune, combining []rune, style Style) {
s.Lock()
if !s.fini {
s.cells.SetContent(x, y, mainc, combc, style)
s.cells.SetContent(x, y, primary, combining, style)
}
s.Unlock()
}
func (s *cScreen) GetContent(x, y int) (rune, []rune, Style, int) {
s.Lock()
mainc, combc, style, width := s.cells.GetContent(x, y)
primary, combining, style, width := s.cells.GetContent(x, y)
s.Unlock()
return mainc, combc, style, width
return primary, combining, style, width
}
func (s *cScreen) sendVtStyle(style Style) {
@ -931,15 +935,15 @@ func (s *cScreen) sendVtStyle(style Style) {
}
if fg.IsRGB() {
r, g, b := fg.RGB()
fmt.Fprintf(esc, vtSetFgRGB, r, g, b)
_, _ = fmt.Fprintf(esc, vtSetFgRGB, r, g, b)
} else if fg.Valid() {
fmt.Fprintf(esc, vtSetFg, fg&0xff)
_, _ = fmt.Fprintf(esc, vtSetFg, fg&0xff)
}
if bg.IsRGB() {
r, g, b := bg.RGB()
fmt.Fprintf(esc, vtSetBgRGB, r, g, b)
_, _ = fmt.Fprintf(esc, vtSetBgRGB, r, g, b)
} else if bg.Valid() {
fmt.Fprintf(esc, vtSetBg, bg&0xff)
_, _ = fmt.Fprintf(esc, vtSetBg, bg&0xff)
}
s.emitVtString(esc.String())
}
@ -954,16 +958,16 @@ func (s *cScreen) writeString(x, y int, style Style, ch []uint16) {
if s.vten {
s.sendVtStyle(style)
} else {
procSetConsoleTextAttribute.Call(
_, _, _ = procSetConsoleTextAttribute.Call(
uintptr(s.out),
uintptr(s.mapStyle(style)))
}
syscall.WriteConsole(s.out, &ch[0], uint32(len(ch)), nil, nil)
_ = syscall.WriteConsole(s.out, &ch[0], uint32(len(ch)), nil, nil)
}
func (s *cScreen) draw() {
// allocate a scratch line bit enough for no combining chars.
// if you have combining characters, you may pay for extra allocs.
// if you have combining characters, you may pay for extra allocations.
if s.clear {
s.clearScreen(s.style, s.vten)
s.clear = false
@ -1053,19 +1057,19 @@ type consoleInfo struct {
}
func (s *cScreen) getConsoleInfo(info *consoleInfo) {
procGetConsoleScreenBufferInfo.Call(
_, _, _ = procGetConsoleScreenBufferInfo.Call(
uintptr(s.out),
uintptr(unsafe.Pointer(info)))
}
func (s *cScreen) getCursorInfo(info *cursorInfo) {
procGetConsoleCursorInfo.Call(
_, _, _ = procGetConsoleCursorInfo.Call(
uintptr(s.out),
uintptr(unsafe.Pointer(info)))
}
func (s *cScreen) setCursorInfo(info *cursorInfo) {
procSetConsoleCursorInfo.Call(
_, _, _ = procSetConsoleCursorInfo.Call(
uintptr(s.out),
uintptr(unsafe.Pointer(info)))
@ -1076,14 +1080,14 @@ func (s *cScreen) setCursorPos(x, y int, vtEnable bool) {
// Note that the string is Y first. Origin is 1,1.
s.emitVtString(fmt.Sprintf(vtCursorPos, y+1, x+1))
} else {
procSetConsoleCursorPosition.Call(
_, _, _ = procSetConsoleCursorPosition.Call(
uintptr(s.out),
coord{int16(x), int16(y)}.uintptr())
}
}
func (s *cScreen) setBufferSize(x, y int) {
procSetConsoleScreenBufferSize.Call(
_, _, _ = procSetConsoleScreenBufferSize.Call(
uintptr(s.out),
coord{int16(x), int16(y)}.uintptr())
}
@ -1096,6 +1100,37 @@ func (s *cScreen) Size() (int, int) {
return w, h
}
func (s *cScreen) SetSize(w, h int) {
xy, _, _ := procGetLargestConsoleWindowSize.Call(uintptr(s.out))
// xy is little endian packed
y := int(xy >> 16)
x := int(xy & 0xffff)
if x == 0 || y == 0 {
return
}
// This is a hacky workaround for Windows Terminal.
// Essentially Windows Terminal (Windows 11) does not support application
// initiated resizing. To detect this, we look for an extremely large size
// for the maximum width. If it is > 500, then this is almost certainly
// Windows Terminal, and won't support this. (Note that the legacy console
// does support application resizing.)
if x >= 500 {
return
}
s.setBufferSize(x, y)
r := rect{0, 0, int16(w - 1), int16(h - 1)}
_, _, _ = procSetConsoleWindowInfo.Call(
uintptr(s.out),
uintptr(1),
uintptr(unsafe.Pointer(&r)))
s.resize()
}
func (s *cScreen) resize() {
info := consoleInfo{}
s.getConsoleInfo(&info)
@ -1114,11 +1149,11 @@ func (s *cScreen) resize() {
s.setBufferSize(w, h)
r := rect{0, 0, int16(w - 1), int16(h - 1)}
procSetConsoleWindowInfo.Call(
_, _, _ = procSetConsoleWindowInfo.Call(
uintptr(s.out),
uintptr(1),
uintptr(unsafe.Pointer(&r)))
s.PostEvent(NewEventResize(w, h))
_ = s.PostEvent(NewEventResize(w, h))
}
func (s *cScreen) Clear() {
@ -1151,13 +1186,13 @@ func (s *cScreen) clearScreen(style Style, vtEnable bool) {
scratch := uint32(0)
count := uint32(x * y)
procFillConsoleOutputAttribute.Call(
_, _, _ = procFillConsoleOutputAttribute.Call(
uintptr(s.out),
uintptr(attr),
uintptr(count),
pos.uintptr(),
uintptr(unsafe.Pointer(&scratch)))
procFillConsoleOutputCharacter.Call(
_, _, _ = procFillConsoleOutputCharacter.Call(
uintptr(s.out),
uintptr(' '),
uintptr(count),
@ -1168,47 +1203,39 @@ func (s *cScreen) clearScreen(style Style, vtEnable bool) {
const (
// Input modes
modeExtndFlg uint32 = 0x0080
modeMouseEn = 0x0010
modeResizeEn = 0x0008
modeCooked = 0x0001
modeVtInput = 0x0200
modeExtendFlg uint32 = 0x0080
modeMouseEn = 0x0010
modeResizeEn = 0x0008
// modeCooked = 0x0001
// modeVtInput = 0x0200
// Output modes
modeCookedOut uint32 = 0x0001
modeWrapEOL = 0x0002
modeVtOutput = 0x0004
modeNoAutoNL = 0x0008
// modeWrapEOL = 0x0002
)
func (s *cScreen) setInMode(mode uint32) error {
rv, _, err := procSetConsoleMode.Call(
func (s *cScreen) setInMode(mode uint32) {
_, _, _ = procSetConsoleMode.Call(
uintptr(s.in),
uintptr(mode))
if rv == 0 {
return err
}
return nil
}
func (s *cScreen) setOutMode(mode uint32) error {
rv, _, err := procSetConsoleMode.Call(
func (s *cScreen) setOutMode(mode uint32) {
_, _, _ = procSetConsoleMode.Call(
uintptr(s.out),
uintptr(mode))
if rv == 0 {
return err
}
return nil
}
func (s *cScreen) getInMode(v *uint32) {
procGetConsoleMode.Call(
_, _, _ = procGetConsoleMode.Call(
uintptr(s.in),
uintptr(unsafe.Pointer(v)))
}
func (s *cScreen) getOutMode(v *uint32) {
procGetConsoleMode.Call(
_, _, _ = procGetConsoleMode.Call(
uintptr(s.out),
uintptr(unsafe.Pointer(v)))
}
@ -1221,15 +1248,15 @@ func (s *cScreen) SetStyle(style Style) {
// No fallback rune support, since we have Unicode. Yay!
func (s *cScreen) RegisterRuneFallback(r rune, subst string) {
func (s *cScreen) RegisterRuneFallback(_ rune, _ string) {
}
func (s *cScreen) UnregisterRuneFallback(r rune) {
func (s *cScreen) UnregisterRuneFallback(_ rune) {
}
func (s *cScreen) CanDisplay(r rune, checkFallbacks bool) bool {
func (s *cScreen) CanDisplay(_ rune, _ bool) bool {
// We presume we can display anything -- we're Unicode.
// (Sadly this not precisely true. Combinings are especially
// (Sadly this not precisely true. Combining characters are especially
// poorly supported under Windows.)
return true
}

View File

@ -27,7 +27,7 @@ type EventPaste struct {
t time.Time
}
// When returns the time when this EventMouse was created.
// When returns the time when this EventPaste was created.
func (ev *EventPaste) When() time.Time {
return ev.t
}

View File

@ -1,4 +1,4 @@
// Copyright 2021 The TCell Authors
// Copyright 2022 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
@ -43,7 +43,7 @@ type Screen interface {
// be displayed if Show() or Sync() is called. The width is the width
// in screen cells; most often this will be 1, but some East Asian
// characters require two cells.
GetContent(x, y int) (mainc rune, combc []rune, style Style, width int)
GetContent(x, y int) (primary rune, combining []rune, style Style, width int)
// SetContent sets the contents of the given cell location. If
// the coordinates are out of range, then the operation is ignored.
@ -52,13 +52,13 @@ type Screen interface {
// that follows is a possible list of combining characters to append,
// and will usually be nil (no combining characters.)
//
// The results are not displayd until Show() or Sync() is called.
// The results are not displayed until Show() or Sync() is called.
//
// Note that wide (East Asian full width) runes occupy two cells,
// and attempts to place character at next cell to the right will have
// undefined effects. Wide runes that are printed in the
// last column will be replaced with a single width space on output.
SetContent(x int, y int, mainc rune, combc []rune, style Style)
SetContent(x int, y int, primary rune, combining []rune, style Style)
// SetStyle sets the default style to use when clearing the screen
// or when StyleDefault is specified. If it is also StyleDefault,
@ -70,7 +70,7 @@ type Screen interface {
// dimensions of the screen, the cursor will be hidden.
ShowCursor(x int, y int)
// HideCursor is used to hide the cursor. Its an alias for
// HideCursor is used to hide the cursor. It's an alias for
// ShowCursor(-1, -1).sim
HideCursor()
@ -139,7 +139,7 @@ type Screen interface {
DisablePaste()
// HasMouse returns true if the terminal (apparently) supports a
// mouse. Note that the a return value of true doesn't guarantee that
// mouse. Note that the return value of true doesn't guarantee that
// a mouse/pointing device is present; a false return definitely
// indicates no mouse support is available.
HasMouse() bool
@ -161,8 +161,8 @@ type Screen interface {
// internal model. This may be both expensive and visually jarring,
// so it should only be used when believed to actually be necessary.
//
// Typically this is called as a result of a user-requested redraw
// (e.g. to clear up on screen corruption caused by some other program),
// Typically, this is called as a result of a user-requested redraw
// (e.g. to clear up on-screen corruption caused by some other program),
// or during a resize event.
Sync()
@ -178,13 +178,13 @@ type Screen interface {
// o as a fallback for ø. This should be done cautiously for
// characters that might be displayed ordinarily in language
// specific text -- characters that could change the meaning of
// of written text would be dangerous. The intention here is to
// written text would be dangerous. The intention here is to
// facilitate fallback characters in pseudo-graphical applications.
//
// If the terminal has fallbacks already in place via an alternate
// character set, those are used in preference. Also, standard
// fallbacks for graphical characters in the ACSC terminfo string
// are registered implicitly.
// fallbacks for graphical characters in the alternate character set
// terminfo string are registered implicitly.
//
// The display string should be the same width as original rune.
// This makes it possible to register two character replacements
@ -203,7 +203,7 @@ type Screen interface {
UnregisterRuneFallback(r rune)
// CanDisplay returns true if the given rune can be displayed on
// this screen. Note that this is a best guess effort -- whether
// this screen. Note that this is a best-guess effort -- whether
// your fonts support the character or not may be questionable.
// Mostly this is for folks who work outside of Unicode.
//
@ -213,7 +213,7 @@ type Screen interface {
// one that is visually indistinguishable from the one requested.
CanDisplay(r rune, checkFallbacks bool) bool
// Resize does nothing, since its generally not possible to
// Resize does nothing, since it's generally not possible to
// ask a screen to resize, but it allows the Screen to implement
// the View interface.
Resize(int, int, int, int)
@ -239,6 +239,15 @@ type Screen interface {
// Beep attempts to sound an OS-dependent audible alert and returns an error
// when unsuccessful.
Beep() error
// SetSize attempts to resize the window. It also invalidates the cells and
// calls the resize function. Note that if the window size is changed, it will
// not be restored upon application exit.
//
// Many terminals cannot support this. Perversely, the "modern" Windows Terminal
// does not support application-initiated resizing, whereas the legacy terminal does.
// Also, some emulators can support this but may have it disabled by default.
SetSize(int, int)
}
// NewScreen returns a default Screen suitable for the user's terminal
@ -255,7 +264,7 @@ func NewScreen() (Screen, error) {
}
// MouseFlags are options to modify the handling of mouse events.
// Actual events can be or'd together.
// Actual events can be ORed together.
type MouseFlags int
const (
@ -265,7 +274,7 @@ const (
)
// CursorStyle represents a given cursor style, which can include the shape and
// whether the cursor blinks or is solid. Support for changing these is not universal.
// whether the cursor blinks or is solid. Support for changing this is not universal.
type CursorStyle int
const (
@ -276,4 +285,4 @@ const (
CursorStyleSteadyUnderline
CursorStyleBlinkingBar
CursorStyleSteadyBar
)
)

View File

@ -1,4 +1,4 @@
// Copyright 2021 The TCell Authors
// Copyright 2022 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
@ -49,13 +49,6 @@ type SimulationScreen interface {
// InjectMouse injects a mouse event.
InjectMouse(x, y int, buttons ButtonMask, mod ModMask)
// SetSize resizes the underlying physical screen. It also causes
// a resize event to be injected during the next Show() or Sync().
// A new physical contents array will be allocated (with data from
// the old copied), so any prior value obtained with GetContents
// won't be used anymore
SetSize(width, height int)
// GetContents returns screen contents as an array of
// cells, along with the physical width & height. Note that the
// physical contents will be used until the next time SetSize()

View File

@ -1,4 +1,4 @@
// Copyright 2020 The TCell Authors
// Copyright 2022 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
@ -26,6 +26,7 @@ type Style struct {
fg Color
bg Color
attrs AttrMask
url string
}
// StyleDefault represents a default style, based upon the context.
@ -42,6 +43,7 @@ func (s Style) Foreground(c Color) Style {
fg: c,
bg: s.bg,
attrs: s.attrs,
url: s.url,
}
}
@ -52,11 +54,12 @@ func (s Style) Background(c Color) Style {
fg: s.fg,
bg: c,
attrs: s.attrs,
url: s.url,
}
}
// Decompose breaks a style up, returning the foreground, background,
// and other attributes.
// and other attributes. The URL if set is not included.
func (s Style) Decompose() (fg Color, bg Color, attr AttrMask) {
return s.fg, s.bg, s.attrs
}
@ -67,12 +70,14 @@ func (s Style) setAttrs(attrs AttrMask, on bool) Style {
fg: s.fg,
bg: s.bg,
attrs: s.attrs | attrs,
url: s.url,
}
}
return Style{
fg: s.fg,
bg: s.bg,
attrs: s.attrs &^ attrs,
url: s.url,
}
}
@ -133,5 +138,18 @@ func (s Style) Attributes(attrs AttrMask) Style {
fg: s.fg,
bg: s.bg,
attrs: attrs,
url: s.url,
}
}
// Url returns a style with the Url set. If the provided Url is not empty,
// and the terminal supports it, text will typically be marked up as a clickable
// link to that Url. If the Url is empty, then this mode is turned off.
func (s Style) Url(url string) Style {
return Style{
fg: s.fg,
bg: s.bg,
attrs: s.attrs,
url: url,
}
}

View File

@ -1,4 +1,4 @@
// Copyright 2021 The TCell Authors
// Copyright 2022 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
@ -227,6 +227,9 @@ type Terminfo struct {
CursorSteadyUnderline string
CursorBlinkingBar string
CursorSteadyBar string
EnterUrl string
ExitUrl string
SetWindowSize string
}
const (
@ -234,93 +237,75 @@ const (
ModifiersXTerm = 1
)
type stackElem struct {
s string
i int
isStr bool
isInt bool
type stack []interface{}
func (st stack) Push(v interface{}) stack {
return append(st, v)
}
type stack []stackElem
func (st stack) Push(v string) stack {
e := stackElem{
s: v,
isStr: true,
}
return append(st, e)
}
func (st stack) Pop() (string, stack) {
v := ""
func (st stack) Pop() (interface{}, stack) {
if len(st) > 0 {
e := st[len(st)-1]
st = st[:len(st)-1]
if e.isStr {
v = e.s
} else {
v = strconv.Itoa(e.i)
}
return e, st[:len(st)-1]
}
return v, st
return 0, st
}
func (st stack) PopString() (string, stack) {
if len(st) > 0 {
e := st[len(st)-1]
var s string
switch v := e.(type) {
case int:
s = strconv.Itoa(v)
case bool:
s = strconv.FormatBool(v)
case string:
s = v
}
return s, st[:len(st)-1]
}
return "", st
}
func (st stack) PopInt() (int, stack) {
if len(st) > 0 {
e := st[len(st)-1]
st = st[:len(st)-1]
if e.isInt {
return e.i, st
} else if e.isStr {
// If the string that was pushed was the representation
// of a number e.g. '123', then return the number. If the
// conversion doesn't work, assume the string pushed was
// intended to return, as an int, the ascii representation
// of the (one and only) character.
i, err := strconv.Atoi(e.s)
if err == nil {
return i, st
} else if len(e.s) >= 1 {
return int(e.s[0]), st
var i int
switch v := e.(type) {
case int:
i = v
case bool:
if v {
i = 1
} else {
i = 0
}
case string:
i, _ = strconv.Atoi(v)
}
return i, st[:len(st)-1]
}
return 0, st
}
func (st stack) PopBool() (bool, stack) {
var b bool
if len(st) > 0 {
e := st[len(st)-1]
st = st[:len(st)-1]
if e.isStr {
if e.s == "1" {
return true, st
}
return false, st
} else if e.i == 1 {
return true, st
} else {
return false, st
switch v := e.(type) {
case int:
b = v != 0
case bool:
b = v
case string:
b = v != "" && v != "false"
}
return b, st[:len(st)-1]
}
return false, st
}
func (st stack) PushInt(i int) stack {
e := stackElem{
i: i,
isInt: true,
}
return append(st, e)
}
func (st stack) PushBool(i bool) stack {
if i {
return st.PushInt(1)
}
return st.PushInt(0)
}
// static vars
var svars [26]string
@ -372,13 +357,13 @@ var pb = &paramsBuffer{}
// TParm takes a terminfo parameterized string, such as setaf or cup, and
// evaluates the string, and returns the result with the parameter
// applied.
func (t *Terminfo) TParm(s string, p ...int) string {
func (t *Terminfo) TParm(s string, p ...interface{}) string {
var stk stack
var a, b string
var ai, bi int
var ab bool
var dvars [26]string
var params [9]int
var params [9]interface{}
pb.Start(s)
@ -413,14 +398,18 @@ func (t *Terminfo) TParm(s string, p ...int) string {
pb.PutCh(ch)
case 'i': // increment both parameters (ANSI cup support)
params[0]++
params[1]++
if i, ok := params[0].(int); ok {
params[0] = i + 1
}
if i, ok := params[1].(int); ok {
params[1] = i + 1
}
case 'c', 's':
// NB: these, and 'd' below are special cased for
// efficiency. They could be handled by the richer
// format support below, less efficiently.
a, stk = stk.Pop()
a, stk = stk.PopString()
pb.PutString(a)
case 'd':
@ -431,7 +420,7 @@ func (t *Terminfo) TParm(s string, p ...int) string {
// This is pretty suboptimal, but this is rarely used.
// None of the mainstream terminals use any of this,
// and it would surprise me if this code is ever
// executed outside of test cases.
// executed outside test cases.
f := "%"
if ch == ':' {
ch, _ = pb.NextCh()
@ -450,7 +439,7 @@ func (t *Terminfo) TParm(s string, p ...int) string {
ai, stk = stk.PopInt()
pb.PutString(fmt.Sprintf(f, ai))
case 'c', 's':
a, stk = stk.Pop()
a, stk = stk.PopString()
pb.PutString(fmt.Sprintf(f, a))
}
@ -458,17 +447,17 @@ func (t *Terminfo) TParm(s string, p ...int) string {
ch, _ = pb.NextCh()
ai = int(ch - '1')
if ai >= 0 && ai < len(params) {
stk = stk.PushInt(params[ai])
stk = stk.Push(params[ai])
} else {
stk = stk.PushInt(0)
stk = stk.Push(0)
}
case 'P': // pop & store variable
ch, _ = pb.NextCh()
if ch >= 'A' && ch <= 'Z' {
svars[int(ch-'A')], stk = stk.Pop()
svars[int(ch-'A')], stk = stk.PopString()
} else if ch >= 'a' && ch <= 'z' {
dvars[int(ch-'a')], stk = stk.Pop()
dvars[int(ch-'a')], stk = stk.PopString()
}
case 'g': // recall & push variable
@ -481,7 +470,7 @@ func (t *Terminfo) TParm(s string, p ...int) string {
case '\'': // push(char)
ch, _ = pb.NextCh()
pb.NextCh() // must be ' but we don't check
_, _ = pb.NextCh() // must be ' but we don't check
stk = stk.Push(string(ch))
case '{': // push(int)
@ -493,82 +482,82 @@ func (t *Terminfo) TParm(s string, p ...int) string {
ch, _ = pb.NextCh()
}
// ch must be '}' but no verification
stk = stk.PushInt(ai)
stk = stk.Push(ai)
case 'l': // push(strlen(pop))
a, stk = stk.Pop()
stk = stk.PushInt(len(a))
a, stk = stk.PopString()
stk = stk.Push(len(a))
case '+':
bi, stk = stk.PopInt()
ai, stk = stk.PopInt()
stk = stk.PushInt(ai + bi)
stk = stk.Push(ai + bi)
case '-':
bi, stk = stk.PopInt()
ai, stk = stk.PopInt()
stk = stk.PushInt(ai - bi)
stk = stk.Push(ai - bi)
case '*':
bi, stk = stk.PopInt()
ai, stk = stk.PopInt()
stk = stk.PushInt(ai * bi)
stk = stk.Push(ai * bi)
case '/':
bi, stk = stk.PopInt()
ai, stk = stk.PopInt()
if bi != 0 {
stk = stk.PushInt(ai / bi)
stk = stk.Push(ai / bi)
} else {
stk = stk.PushInt(0)
stk = stk.Push(0)
}
case 'm': // push(pop mod pop)
bi, stk = stk.PopInt()
ai, stk = stk.PopInt()
if bi != 0 {
stk = stk.PushInt(ai % bi)
stk = stk.Push(ai % bi)
} else {
stk = stk.PushInt(0)
stk = stk.Push(0)
}
case '&': // AND
bi, stk = stk.PopInt()
ai, stk = stk.PopInt()
stk = stk.PushInt(ai & bi)
stk = stk.Push(ai & bi)
case '|': // OR
bi, stk = stk.PopInt()
ai, stk = stk.PopInt()
stk = stk.PushInt(ai | bi)
stk = stk.Push(ai | bi)
case '^': // XOR
bi, stk = stk.PopInt()
ai, stk = stk.PopInt()
stk = stk.PushInt(ai ^ bi)
stk = stk.Push(ai ^ bi)
case '~': // bit complement
ai, stk = stk.PopInt()
stk = stk.PushInt(ai ^ -1)
stk = stk.Push(ai ^ -1)
case '!': // logical NOT
ai, stk = stk.PopInt()
stk = stk.PushBool(ai != 0)
stk = stk.Push(ai != 0)
case '=': // numeric compare or string compare
b, stk = stk.Pop()
a, stk = stk.Pop()
stk = stk.PushBool(a == b)
b, stk = stk.PopString()
a, stk = stk.PopString()
stk = stk.Push(a == b)
case '>': // greater than, numeric
bi, stk = stk.PopInt()
ai, stk = stk.PopInt()
stk = stk.PushBool(ai > bi)
stk = stk.Push(ai > bi)
case '<': // less than, numeric
bi, stk = stk.PopInt()
ai, stk = stk.PopInt()
stk = stk.PushBool(ai < bi)
stk = stk.Push(ai < bi)
case '?': // start conditional
@ -650,15 +639,15 @@ func (t *Terminfo) TPuts(w io.Writer, s string) {
beg := strings.Index(s, "$<")
if beg < 0 {
// Most strings don't need padding, which is good news!
io.WriteString(w, s)
_, _ = io.WriteString(w, s)
return
}
io.WriteString(w, s[:beg])
_, _ = io.WriteString(w, s[:beg])
s = s[beg+2:]
end := strings.Index(s, ">")
if end < 0 {
// unterminated.. just emit bytes unadulterated
io.WriteString(w, "$<"+s)
_, _ = io.WriteString(w, "$<"+s)
return
}
val := s[:end]
@ -729,7 +718,6 @@ func (t *Terminfo) TColor(fi, bi int) string {
var (
dblock sync.Mutex
terminfos = make(map[string]*Terminfo)
aliases = make(map[string]string)
)
// AddTerminfo can be called to register a new Terminfo entry.

View File

@ -148,6 +148,9 @@ type tScreen struct {
finiOnce sync.Once
enablePaste string
disablePaste string
enterUrl string
exitUrl string
setWinSize string
cursorStyles map[CursorStyle]string
cursorStyle CursorStyle
saved *term.State
@ -334,6 +337,26 @@ func (t *tScreen) prepareBracketedPaste() {
}
}
func (t *tScreen) prepareExtendedOSC() {
// More stuff for limits in terminfo. This time we are applying
// the most common OSC (operating system commands). Generally
// terminals that don't understand these will ignore them.
// Again, we condition this based on mouse capabilities.
if t.ti.EnterUrl != "" {
t.enterUrl = t.ti.EnterUrl
t.exitUrl = t.ti.ExitUrl
} else if t.ti.Mouse != "" {
t.enterUrl = "\x1b]8;;%p1%s\x1b\\"
t.exitUrl = "\x1b]8;;\x1b\\"
}
if t.ti.SetWindowSize != "" {
t.setWinSize = t.ti.SetWindowSize
} else if t.ti.Mouse != "" {
t.setWinSize = "\x1b[8;%p1%p2%d;%dt"
}
}
func (t *tScreen) prepareCursorStyles() {
// Another workaround for lack of reporting in terminfo.
// We assume if the terminal has a mouse entry, that it
@ -502,6 +525,7 @@ func (t *tScreen) prepareKeys() {
t.prepareXtermModifiers()
t.prepareBracketedPaste()
t.prepareCursorStyles()
t.prepareExtendedOSC()
outer:
// Add key mappings for control keys.
@ -623,11 +647,27 @@ func (t *tScreen) encodeRune(r rune, buf []byte) []byte {
return buf
}
func (t *tScreen) sendFgBg(fg Color, bg Color) {
func (t *tScreen) sendFgBg(fg Color, bg Color, attr AttrMask) AttrMask {
ti := t.ti
if ti.Colors == 0 {
return
// foreground vs background, we calculate luminance
// and possibly do a reverse video
if !fg.Valid() {
return attr
}
v, ok := t.colors[fg]
if !ok {
v = FindColor(fg, []Color{ColorBlack, ColorWhite})
t.colors[fg] = v
}
switch v {
case ColorWhite:
return attr
case ColorBlack:
return attr ^ AttrReverse
}
}
if fg == ColorReset || bg == ColorReset {
t.TPuts(ti.ResetFgBg)
}
@ -638,7 +678,7 @@ func (t *tScreen) sendFgBg(fg Color, bg Color) {
t.TPuts(ti.TParm(ti.SetFgBgRGB,
int(r1), int(g1), int(b1),
int(r2), int(g2), int(b2)))
return
return attr
}
if fg.IsRGB() && ti.SetFgRGB != "" {
@ -685,6 +725,7 @@ func (t *tScreen) sendFgBg(fg Color, bg Color) {
t.TPuts(ti.TParm(ti.SetBg, int(bg&0xff)))
}
}
return attr
}
func (t *tScreen) drawCell(x, y int) int {
@ -727,7 +768,7 @@ func (t *tScreen) drawCell(x, y int) int {
t.TPuts(ti.AttrOff)
t.sendFgBg(fg, bg)
attrs = t.sendFgBg(fg, bg, attrs)
if attrs&AttrBold != 0 {
t.TPuts(ti.Bold)
}
@ -749,8 +790,19 @@ func (t *tScreen) drawCell(x, y int) int {
if attrs&AttrStrikeThrough != 0 {
t.TPuts(ti.StrikeThrough)
}
// URL string can be long, so don't send it unless we really need to
if t.enterUrl != "" && t.curstyle != style {
if style.url != "" {
t.TPuts(ti.TParm(t.enterUrl, style.url))
} else {
t.TPuts(t.exitUrl)
}
}
t.curstyle = style
}
// now emit runes - taking care to not overrun width with a
// wide character, and to ensure that we emit exactly one regular
// character followed up by any residual combing characters
@ -859,8 +911,9 @@ func (t *tScreen) Show() {
func (t *tScreen) clearScreen() {
t.TPuts(t.ti.AttrOff)
t.TPuts(t.exitUrl)
fg, bg, _ := t.style.Decompose()
t.sendFgBg(fg, bg)
_ = t.sendFgBg(fg, bg, AttrNone)
t.TPuts(t.ti.Clear)
t.clear = false
}
@ -1716,6 +1769,14 @@ func (t *tScreen) HasKey(k Key) bool {
return t.keyexist[k]
}
func (t *tScreen) SetSize(w, h int) {
if t.setWinSize != "" {
t.TPuts(t.ti.TParm(t.setWinSize, w, h))
}
t.cells.Invalidate()
t.resize()
}
func (t *tScreen) Resize(int, int, int, int) {}
func (t *tScreen) Suspend() error {