Skip to content

Commit

Permalink
Add diff gutter
Browse files Browse the repository at this point in the history
  • Loading branch information
p-e-w committed Feb 8, 2020
1 parent c4bfa82 commit de33eac
Show file tree
Hide file tree
Showing 32 changed files with 402 additions and 48 deletions.
15 changes: 15 additions & 0 deletions internal/action/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -1195,6 +1195,21 @@ func (h *BufPane) HalfPageDown() bool {
return true
}

// ToggleDiffGutter turns the diff gutter off and on
func (h *BufPane) ToggleDiffGutter() bool {
if !h.Buf.Settings["diffgutter"].(bool) {
h.Buf.Settings["diffgutter"] = true
h.Buf.UpdateDiff(func(synchronous bool) {
screen.DrawChan <- true
})
InfoBar.Message("Enabled diff gutter")
} else {
h.Buf.Settings["diffgutter"] = false
InfoBar.Message("Disabled diff gutter")
}
return true
}

// ToggleRuler turns line numbers off and on
func (h *BufPane) ToggleRuler() bool {
if !h.Buf.Settings["ruler"].(bool) {
Expand Down
1 change: 1 addition & 0 deletions internal/action/bufpane.go
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,7 @@ var BufKeyActions = map[string]BufKeyAction{
"EndOfLine": (*BufPane).EndOfLine,
"ToggleHelp": (*BufPane).ToggleHelp,
"ToggleKeyMenu": (*BufPane).ToggleKeyMenu,
"ToggleDiffGutter": (*BufPane).ToggleDiffGutter,
"ToggleRuler": (*BufPane).ToggleRuler,
"ClearStatus": (*BufPane).ClearStatus,
"ShellMode": (*BufPane).ShellMode,
Expand Down
1 change: 1 addition & 0 deletions internal/action/infopane.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ var InfoNones = []string{
"HalfPageDown",
"ToggleHelp",
"ToggleKeyMenu",
"ToggleDiffGutter",
"ToggleRuler",
"JumpLine",
"ClearStatus",
Expand Down
111 changes: 111 additions & 0 deletions internal/buffer/buffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"golang.org/x/text/encoding/htmlindex"
"golang.org/x/text/encoding/unicode"
"golang.org/x/text/transform"
dmp "github.com/sergi/go-diff/diffmatchpatch"
)

const backupTime = 8000
Expand Down Expand Up @@ -97,6 +98,15 @@ func (b *SharedBuffer) remove(start, end Loc) []byte {
return b.LineArray.remove(start, end)
}

const (
DSUnchanged = 0
DSAdded = 1
DSModified = 2
DSDeletedAbove = 3
)

type DiffStatus byte

// Buffer stores the main information about a currently open file including
// the actual text (in a LineArray), the undo/redo stack (in an EventHandler)
// all the cursors, the syntax highlighting info, the settings for the buffer
Expand Down Expand Up @@ -138,6 +148,12 @@ type Buffer struct {

Messages []*Message

updateDiffTimer *time.Timer
diffBase []byte
diffBaseLineCount int
diffLock sync.RWMutex
diff map[int]DiffStatus

// counts the number of edits
// resets every backupTime edits
lastbackup time.Time
Expand Down Expand Up @@ -930,6 +946,101 @@ func (b *Buffer) Write(bytes []byte) (n int, err error) {
return len(bytes), nil
}

func (b *Buffer) updateDiffSync() {
b.diffLock.Lock()
defer b.diffLock.Unlock()

b.diff = make(map[int]DiffStatus)

if b.diffBase == nil {
return
}

differ := dmp.New()
baseRunes, bufferRunes, _ := differ.DiffLinesToRunes(string(b.diffBase), string(b.Bytes()))
diffs := differ.DiffMainRunes(baseRunes, bufferRunes, false)
lineN := 0

for _, diff := range diffs {
lineCount := len([]rune(diff.Text))

switch diff.Type {
case dmp.DiffEqual:
lineN += lineCount
case dmp.DiffInsert:
var status DiffStatus
if b.diff[lineN] == DSDeletedAbove {
status = DSModified
} else {
status = DSAdded
}
for i := 0; i < lineCount; i++ {
b.diff[lineN] = status
lineN++
}
case dmp.DiffDelete:
b.diff[lineN] = DSDeletedAbove
}
}
}

// UpdateDiff computes the diff between the diff base and the buffer content.
// The update may be performed synchronously or asynchronously.
// UpdateDiff calls the supplied callback when the update is complete.
// The argument passed to the callback is set to true if and only if
// the update was performed synchronously.
// If an asynchronous update is already pending when UpdateDiff is called,
// UpdateDiff does not schedule another update, in which case the callback
// is not called.
func (b *Buffer) UpdateDiff(callback func(bool)) {
if b.updateDiffTimer != nil {
return
}

lineCount := b.LinesNum()
if b.diffBaseLineCount > lineCount {
lineCount = b.diffBaseLineCount
}

if lineCount < 1000 {
b.updateDiffSync()
callback(true)
} else if lineCount < 30000 {
b.updateDiffTimer = time.AfterFunc(500*time.Millisecond, func() {
b.updateDiffTimer = nil
b.updateDiffSync()
callback(false)
})
} else {
// Don't compute diffs for very large files
b.diffLock.Lock()
b.diff = make(map[int]DiffStatus)
b.diffLock.Unlock()
callback(true)
}
}

// SetDiffBase sets the text that is used as the base for diffing the buffer content
func (b *Buffer) SetDiffBase(diffBase []byte) {
b.diffBase = diffBase
if diffBase == nil {
b.diffBaseLineCount = 0
} else {
b.diffBaseLineCount = strings.Count(string(diffBase), "\n")
}
b.UpdateDiff(func(synchronous bool) {
screen.DrawChan <- true
})
}

// DiffStatus returns the diff status for a line in the buffer
func (b *Buffer) DiffStatus(lineN int) DiffStatus {
b.diffLock.RLock()
defer b.diffLock.RUnlock()
// Note that the zero value for DiffStatus is equal to DSUnchanged
return b.diff[lineN]
}

// WriteLog writes a string to the log buffer
func WriteLog(s string) {
LogBuf.EventHandler.Insert(LogBuf.End(), s)
Expand Down
127 changes: 99 additions & 28 deletions internal/config/runtime.go

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions internal/config/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ var defaultCommonSettings = map[string]interface{}{
"basename": false,
"colorcolumn": float64(0),
"cursorline": true,
"diffgutter": true,
"encoding": "utf-8",
"eofnewline": false,
"fastdirty": true,
Expand Down
91 changes: 78 additions & 13 deletions internal/display/bufwindow.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,9 @@ func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
if hasMessage {
vloc.X += 2
}
if b.Settings["diffgutter"].(bool) {
vloc.X++
}
if b.Settings["ruler"].(bool) {
vloc.X += maxLineNumLength + 1
}
Expand Down Expand Up @@ -273,6 +276,9 @@ func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
break
}
vloc.X = 0
if b.Settings["diffgutter"].(bool) {
vloc.X++
}
// This will draw an empty line number because the current line is wrapped
if b.Settings["ruler"].(bool) {
vloc.X += maxLineNumLength + 1
Expand Down Expand Up @@ -311,6 +317,34 @@ func (w *BufWindow) drawGutter(vloc *buffer.Loc, bloc *buffer.Loc) {
vloc.X++
}

func (w *BufWindow) drawDiffGutter(backgroundStyle tcell.Style, softwrapped bool, vloc *buffer.Loc, bloc *buffer.Loc) {
symbol := ' '
styleName := ""

switch w.Buf.DiffStatus(bloc.Y) {
case buffer.DSAdded:
symbol = '\u258C' // Left half block
styleName = "diff-added"
case buffer.DSModified:
symbol = '\u258C' // Left half block
styleName = "diff-modified"
case buffer.DSDeletedAbove:
if !softwrapped {
symbol = '\u2594' // Upper one eighth block
styleName = "diff-deleted"
}
}

style := backgroundStyle
if s, ok := config.Colorscheme[styleName]; ok {
foreground, _, _ := s.Decompose()
style = style.Foreground(foreground)
}

screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, symbol, nil, style)
vloc.X++
}

func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, maxLineNumLength int, vloc *buffer.Loc, bloc *buffer.Loc) {
lineNum := strconv.Itoa(bloc.Y + 1)

Expand Down Expand Up @@ -373,15 +407,33 @@ func (w *BufWindow) displayBuffer() {
bufWidth--
}

if b.Settings["syntax"].(bool) && b.SyntaxDef != nil {
for _, r := range b.Modifications {
final := -1
for i := r.X; i <= r.Y; i++ {
final = util.Max(b.Highlighter.ReHighlightStates(b, i), final)
if len(b.Modifications) > 0 {
if b.Settings["syntax"].(bool) && b.SyntaxDef != nil {
for _, r := range b.Modifications {
final := -1
for i := r.X; i <= r.Y; i++ {
final = util.Max(b.Highlighter.ReHighlightStates(b, i), final)
}
b.Highlighter.HighlightMatches(b, r.X, final+1)
}
b.Highlighter.HighlightMatches(b, r.X, final+1)
}

b.ClearModifications()

if b.Settings["diffgutter"].(bool) {
b.UpdateDiff(func(synchronous bool) {
// If the diff was updated asynchronously, the outer call to
// displayBuffer might already be completed and we need to
// schedule a redraw in order to display the new diff.
// Note that this cannot lead to an infinite recursion
// because the modifications were cleared above so there won't
// be another call to UpdateDiff when displayBuffer is called
// during the redraw.
if !synchronous {
screen.DrawChan <- true
}
})
}
}

var matchingBraces []buffer.Loc
Expand Down Expand Up @@ -444,18 +496,28 @@ func (w *BufWindow) displayBuffer() {
for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ {
vloc.X = 0

currentLine := false
for _, c := range cursors {
if bloc.Y == c.Y && w.active {
currentLine = true
break
}
}

s := lineNumStyle
if currentLine {
s = curNumStyle
}

if hasMessage {
w.drawGutter(&vloc, &bloc)
}

if b.Settings["diffgutter"].(bool) {
w.drawDiffGutter(s, false, &vloc, &bloc)
}

if b.Settings["ruler"].(bool) {
s := lineNumStyle
for _, c := range cursors {
if bloc.Y == c.Y && w.active {
s = curNumStyle
break
}
}
w.drawLineNum(s, false, maxLineNumLength, &vloc, &bloc)
}

Expand Down Expand Up @@ -579,6 +641,9 @@ func (w *BufWindow) displayBuffer() {
break
}
vloc.X = 0
if b.Settings["diffgutter"].(bool) {
w.drawDiffGutter(lineNumStyle, true, &vloc, &bloc)
}
// This will draw an empty line number because the current line is wrapped
if b.Settings["ruler"].(bool) {
w.drawLineNum(lineNumStyle, true, maxLineNumLength, &vloc, &bloc)
Expand Down
3 changes: 3 additions & 0 deletions runtime/colorschemes/atom-dark.micro
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ color-link tabbar "#1D1F21,#C5C8C6"
color-link indent-char "#505050,#1D1F21"
color-link line-number "#656866,#232526"
color-link current-line-number "#656866,#1D1F21"
color-link diff-added "#00AF00"
color-link diff-modified "#FFAF00"
color-link diff-deleted "#D70000"
color-link gutter-error "#FF4444,#1D1F21"
color-link gutter-warning "#EEEE77,#1D1F21"
color-link cursor-line "#2D2F31"
Expand Down
3 changes: 3 additions & 0 deletions runtime/colorschemes/bubblegum.micro
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ color-link underlined "underline 241,231"
color-link todo "246,231"
color-link statusline "241,254"
color-link tabbar "241,254"
color-link diff-added "34"
color-link diff-modified "214"
color-link diff-deleted "160"
color-link gutter-error "197,231"
color-link gutter-warning "134,231"
color-link line-number "246,254"
Expand Down
3 changes: 3 additions & 0 deletions runtime/colorschemes/cmc-16.micro
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ color-link statusline "white,blue"
color-link tabbar "white,blue"
color-link current-line-number "red"
color-link current-line-number.scroller "red"
color-link diff-added "green"
color-link diff-modified "yellow"
color-link diff-deleted "red"
color-link gutter-error ",red"
color-link gutter-warning "red"
color-link color-column "cyan"
Expand Down
5 changes: 4 additions & 1 deletion runtime/colorschemes/cmc-tc.micro
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@ color-link statusline "#aaaaaa,#8a496b"
color-link tabbar "#aaaaaa,#8a496b"
color-link current-line-number "bold #e34234,#424549"
color-link current-line-number.scroller "red"
color-link diff-added "#00AF00"
color-link diff-modified "#FFAF00"
color-link diff-deleted "#D70000"
color-link gutter-error ",#e34234"
color-link gutter-warning "#e34234"
color-link color-column "#f26522"
color-link constant.bool "bold #55ffff"
color-link constant.bool.true "bold #85ff85"
color-link constant.bool.false "bold #ff8585"
color-link constant.bool.false "bold #ff8585"
3 changes: 3 additions & 0 deletions runtime/colorschemes/darcula.micro
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ color-link tabbar "#242424,#CCCCCC"
color-link indent-char "#4F4F4F,#242424"
color-link line-number "#666666,#2C2C2C"
color-link current-line-number "#666666,#242424"
color-link diff-added "#00AF00"
color-link diff-modified "#FFAF00"
color-link diff-deleted "#D70000"
color-link gutter-error "#CB4B16,#242424"
color-link gutter-warning "#E6DB74,#242424"
color-link cursor-line "#2C2C2C"
Expand Down
3 changes: 3 additions & 0 deletions runtime/colorschemes/default.micro
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ color-link tabbar "#282828,#F8F8F2"
color-link indent-char "#505050,#282828"
color-link line-number "#AAAAAA,#323232"
color-link current-line-number "#AAAAAA,#282828"
color-link diff-added "#00AF00"
color-link diff-modified "#FFAF00"
color-link diff-deleted "#D70000"
color-link gutter-error "#CB4B16,#282828"
color-link gutter-warning "#E6DB74,#282828"
color-link cursor-line "#323232"
Expand Down
3 changes: 3 additions & 0 deletions runtime/colorschemes/geany.micro
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,8 @@ color-link current-line-number ""
color-link statusline "black,white"
color-link tabbar "black,white"
color-link color-column "bold geren"
color-link diff-added "green"
color-link diff-modified "yellow"
color-link diff-deleted "red"
color-link gutter-error ",red"
color-link gutter-warning "red"
Loading

0 comments on commit de33eac

Please sign in to comment.