1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-07-15 11:41:50 +03:00

some refactoring in anticipation of the graph feature

This commit is contained in:
Jesse Duffield
2021-11-01 09:35:54 +11:00
parent 7a464ae5b7
commit 2fc1498517
40 changed files with 523 additions and 411 deletions

View File

@ -6,7 +6,6 @@ package gocui
import (
standardErrors "errors"
"fmt"
"log"
"runtime"
"strings"
@ -76,6 +75,8 @@ type GuiMutexes struct {
tickingMutex sync.Mutex
ViewsMutex sync.Mutex
drawMutex sync.Mutex
}
type PlayMode int
@ -166,14 +167,14 @@ type Gui struct {
}
// NewGui returns a new Gui object with a given output mode.
func NewGui(mode OutputMode, supportOverlaps bool, playMode PlayMode, headless bool) (*Gui, error) {
func NewGui(mode OutputMode, supportOverlaps bool, playMode PlayMode, headless bool, runeReplacements map[rune]string) (*Gui, error) {
g := &Gui{}
var err error
if headless {
err = g.tcellInitSimulation()
} else {
err = g.tcellInit()
err = g.tcellInit(runeReplacements)
}
if err != nil {
return nil, err
@ -664,79 +665,6 @@ func (g *Gui) onResize() {
// g.screen.Sync()
}
// flush updates the gui, re-drawing frames and buffers.
func (g *Gui) flush() error {
// pretty sure we don't need this, but keeping it here in case we get weird visual artifacts
// g.clear(g.FgColor, g.BgColor)
maxX, maxY := Screen.Size()
// if GUI's size has changed, we need to redraw all views
if maxX != g.maxX || maxY != g.maxY {
for _, v := range g.views {
v.clearViewLines()
}
}
g.maxX, g.maxY = maxX, maxY
for _, m := range g.managers {
if err := m.Layout(g); err != nil {
return err
}
}
for _, v := range g.views {
if !v.Visible || v.y1 < v.y0 {
continue
}
if v.Frame {
var fgColor, bgColor, frameColor Attribute
if g.Highlight && v == g.currentView {
fgColor = g.SelFgColor
bgColor = g.SelBgColor
frameColor = g.SelFrameColor
} else {
bgColor = g.BgColor
if v.TitleColor != ColorDefault {
fgColor = v.TitleColor
} else {
fgColor = g.FgColor
}
if v.FrameColor != ColorDefault {
frameColor = v.FrameColor
} else {
frameColor = g.FrameColor
}
}
if err := g.drawFrameEdges(v, frameColor, bgColor); err != nil {
return err
}
if err := g.drawFrameCorners(v, frameColor, bgColor); err != nil {
return err
}
if v.Title != "" || len(v.Tabs) > 0 {
if err := g.drawTitle(v, fgColor, bgColor); err != nil {
return err
}
}
if v.Subtitle != "" {
if err := g.drawSubtitle(v, fgColor, bgColor); err != nil {
return err
}
}
if v.ContainsList && g.ShowListFooter {
if err := g.drawListFooter(v, fgColor, bgColor); err != nil {
return err
}
}
}
if err := g.draw(v); err != nil {
return err
}
}
Screen.Show()
return nil
}
func (g *Gui) clear(fg, bg Attribute) (int, int) {
st := getTcellStyle(oldStyle{fg: fg, bg: bg, outputMode: g.outputMode})
w, h := Screen.Size()
@ -983,7 +911,7 @@ func (g *Gui) drawListFooter(v *View, fgColor, bgColor Attribute) error {
return nil
}
message := fmt.Sprintf("%d of %d", v.cy+v.oy+1, len(v.lines))
message := v.Footer
if v.y1 < 0 || v.y1 >= g.maxY {
return nil
@ -1006,12 +934,102 @@ func (g *Gui) drawListFooter(v *View, fgColor, bgColor Attribute) error {
return nil
}
// flush updates the gui, re-drawing frames and buffers.
func (g *Gui) flush() error {
g.Mutexes.drawMutex.Lock()
defer g.Mutexes.drawMutex.Unlock()
// pretty sure we don't need this, but keeping it here in case we get weird visual artifacts
// g.clear(g.FgColor, g.BgColor)
maxX, maxY := Screen.Size()
// if GUI's size has changed, we need to redraw all views
if maxX != g.maxX || maxY != g.maxY {
for _, v := range g.views {
v.clearViewLines()
}
}
g.maxX, g.maxY = maxX, maxY
for _, m := range g.managers {
if err := m.Layout(g); err != nil {
return err
}
}
for _, v := range g.views {
if err := g.draw(v); err != nil {
return err
}
}
Screen.Show()
return nil
}
func (g *Gui) Draw(v *View) error {
g.Mutexes.drawMutex.Lock()
defer g.Mutexes.drawMutex.Unlock()
if err := g.draw(v); err != nil {
return err
}
Screen.Show()
return nil
}
// draw manages the cursor and calls the draw function of a view.
func (g *Gui) draw(v *View) error {
if g.suspended {
return nil
}
if !v.Visible || v.y1 < v.y0 {
return nil
}
if v.Frame {
var fgColor, bgColor, frameColor Attribute
if g.Highlight && v == g.currentView {
fgColor = g.SelFgColor
bgColor = g.SelBgColor
frameColor = g.SelFrameColor
} else {
bgColor = g.BgColor
if v.TitleColor != ColorDefault {
fgColor = v.TitleColor
} else {
fgColor = g.FgColor
}
if v.FrameColor != ColorDefault {
frameColor = v.FrameColor
} else {
frameColor = g.FrameColor
}
}
if err := g.drawFrameEdges(v, frameColor, bgColor); err != nil {
return err
}
if err := g.drawFrameCorners(v, frameColor, bgColor); err != nil {
return err
}
if v.Title != "" || len(v.Tabs) > 0 {
if err := g.drawTitle(v, fgColor, bgColor); err != nil {
return err
}
}
if v.Subtitle != "" {
if err := g.drawSubtitle(v, fgColor, bgColor); err != nil {
return err
}
}
if v.Footer != "" && g.ShowListFooter {
if err := g.drawListFooter(v, fgColor, bgColor); err != nil {
return err
}
}
}
if g.Cursor {
if curview := g.currentView; curview != nil {
vMaxX, vMaxY := curview.Size()

View File

@ -26,6 +26,10 @@ var runeReplacements = map[rune]string{
'┐': "+",
'└': "+",
'┘': "+",
'╭': "+",
'╮': "+",
'╰': "+",
'╯': "+",
'─': "-",
// using a hyphen here actually looks weird.
@ -33,6 +37,9 @@ var runeReplacements = map[rune]string{
'╶': " ",
'╴': " ",
'┴': "+",
'┬': "+",
'╷': "|",
'├': "+",
'│': "|",
'▼': "v",
@ -42,7 +49,7 @@ var runeReplacements = map[rune]string{
}
// tcellInit initializes tcell screen for use.
func (g *Gui) tcellInit() error {
func (g *Gui) tcellInit(runeReplacements map[rune]string) error {
runewidth.DefaultCondition.EastAsianWidth = false
tcell.SetEncodingFallback(tcell.EncodingFallbackASCII)
@ -51,7 +58,7 @@ func (g *Gui) tcellInit() error {
} else if e = s.Init(); e != nil {
return e
} else {
registerRuneFallbacks(s)
registerRuneFallbacks(s, runeReplacements)
g.screen = s
Screen = s
@ -59,10 +66,14 @@ func (g *Gui) tcellInit() error {
}
}
func registerRuneFallbacks(s tcell.Screen) {
func registerRuneFallbacks(s tcell.Screen, additional map[rune]string) {
for before, after := range runeReplacements {
s.RegisterRuneFallback(before, after)
}
for before, after := range additional {
s.RegisterRuneFallback(before, after)
}
}
// tcellInitSimulation initializes tcell screen for use.

View File

@ -153,14 +153,14 @@ type View struct {
searcher *searcher
// when ContainsList is true, we show the current index and total count in the view
ContainsList bool
// KeybindOnEdit should be set to true when you want to execute keybindings even when the view is editable
// (this is usually not the case)
KeybindOnEdit bool
TextArea *TextArea
// something like '1 of 20' for a list view
Footer string
}
// call this in the event of a view resize, or if you want to render new content
@ -372,11 +372,21 @@ func (v *View) Height() int {
// if a view has a frame, that leaves less space for its writeable area
func (v *View) InnerWidth() int {
return v.Width() - v.frameOffset()
innerWidth := v.Width() - v.frameOffset()
if innerWidth < 0 {
return 0
}
return innerWidth
}
func (v *View) InnerHeight() int {
return v.Height() - v.frameOffset()
innerHeight := v.Height() - v.frameOffset()
if innerHeight < 0 {
return 0
}
return innerHeight
}
func (v *View) frameOffset() int {
@ -563,8 +573,6 @@ func (v *View) Write(p []byte) (n int, err error) {
v.writeMutex.Lock()
defer v.writeMutex.Unlock()
v.tainted = true
v.makeWriteable(v.wx, v.wy)
v.writeRunes(bytes.Runes(p))
return len(p), nil
@ -574,20 +582,16 @@ func (v *View) WriteRunes(p []rune) {
v.writeMutex.Lock()
defer v.writeMutex.Unlock()
v.writeRunes(p)
}
// writeRunes copies slice of runes into internal lines buffer.
func (v *View) writeRunes(p []rune) {
v.tainted = true
// Fill with empty cells, if writing outside current view buffer
v.makeWriteable(v.wx, v.wy)
v.writeRunes(p)
}
func (v *View) WriteString(s string) {
v.WriteRunes([]rune(s))
}
// writeRunes copies slice of runes into internal lines buffer.
// caller must make sure that writing position is accessable.
func (v *View) writeRunes(p []rune) {
for _, r := range p {
switch r {
case '\n':
@ -613,6 +617,16 @@ func (v *View) writeRunes(p []rune) {
}
}
// exported functions use the mutex. Non-exported functions are for internal use
// and a calling function should use a mutex
func (v *View) WriteString(s string) {
v.WriteRunes([]rune(s))
}
func (v *View) writeString(s string) {
v.writeRunes([]rune(s))
}
// parseInput parses char by char the input written to the View. It returns nil
// while processing ESC sequences. Otherwise, it returns a cell slice that
// contains the processed data.
@ -696,16 +710,28 @@ func (v *View) Read(p []byte) (n int, err error) {
return offset, io.EOF
}
// only use this if the calling function has a lock on writeMutex
func (v *View) clear() {
v.rewind()
v.lines = nil
v.clearViewLines()
}
// Clear empties the view's internal buffer.
// And resets reading and writing offsets.
func (v *View) Clear() {
v.writeMutex.Lock()
defer v.writeMutex.Unlock()
v.rewind()
v.lines = nil
v.clearViewLines()
v.clearRunes()
v.clear()
}
func (v *View) SetContent(str string) {
v.writeMutex.Lock()
defer v.writeMutex.Unlock()
v.clear()
v.writeString(str)
}
// Rewind sets read and write pos to (0, 0).
@ -802,6 +828,9 @@ func (v *View) IsTainted() bool {
// draw re-draws the view's contents.
func (v *View) draw() error {
v.writeMutex.Lock()
defer v.writeMutex.Unlock()
v.clearRunes()
if !v.Visible {