2020-08-15 03:18:40 +02:00
package patch
2018-12-02 10:57:01 +02:00
import (
2019-10-30 11:22:30 +02:00
"regexp"
2018-12-02 10:57:01 +02:00
"strings"
2019-10-30 11:22:30 +02:00
"github.com/fatih/color"
"github.com/jesseduffield/lazygit/pkg/theme"
"github.com/jesseduffield/lazygit/pkg/utils"
2018-12-02 10:57:01 +02:00
"github.com/sirupsen/logrus"
)
2021-03-31 14:55:06 +02:00
type PatchLineKind int
2019-10-30 11:22:30 +02:00
const (
2021-03-31 14:55:06 +02:00
PATCH_HEADER PatchLineKind = iota
2019-11-04 10:47:25 +02:00
COMMIT_SHA
COMMIT_DESCRIPTION
2019-10-30 11:22:30 +02:00
HUNK_HEADER
ADDITION
DELETION
CONTEXT
NEWLINE_MESSAGE
)
// the job of this file is to parse a diff, find out where the hunks begin and end, which lines are stageable, and how to find the next hunk from the current position or the next stageable line from the current position.
type PatchLine struct {
2021-03-31 14:55:06 +02:00
Kind PatchLineKind
2019-10-30 11:22:30 +02:00
Content string // something like '+ hello' (note the first character is not removed)
}
2018-12-02 10:57:01 +02:00
type PatchParser struct {
2019-10-30 11:22:30 +02:00
Log * logrus . Entry
PatchLines [ ] * PatchLine
PatchHunks [ ] * PatchHunk
HunkStarts [ ] int
StageableLines [ ] int // rename to mention we're talking about indexes
2018-12-02 10:57:01 +02:00
}
// NewPatchParser builds a new branch list builder
2021-04-18 08:30:34 +02:00
func NewPatchParser ( log * logrus . Entry , patch string ) * PatchParser {
hunkStarts , stageableLines , patchLines := parsePatch ( patch )
2019-10-30 11:22:30 +02:00
patchHunks := GetHunksFromDiff ( patch )
2018-12-02 10:57:01 +02:00
return & PatchParser {
2019-10-30 11:22:30 +02:00
Log : log ,
HunkStarts : hunkStarts , // deprecated
StageableLines : stageableLines ,
PatchLines : patchLines ,
PatchHunks : patchHunks ,
2021-04-18 08:30:34 +02:00
}
2018-12-02 10:57:01 +02:00
}
2019-10-30 11:22:30 +02:00
// GetHunkContainingLine takes a line index and an offset and finds the hunk
// which contains the line index, then returns the hunk considering the offset.
// e.g. if the offset is 1 it will return the next hunk.
func ( p * PatchParser ) GetHunkContainingLine ( lineIndex int , offset int ) * PatchHunk {
if len ( p . PatchHunks ) == 0 {
return nil
}
for index , hunk := range p . PatchHunks {
2020-08-15 02:58:29 +02:00
if lineIndex >= hunk . FirstLineIdx && lineIndex <= hunk . LastLineIdx ( ) {
2019-10-30 11:22:30 +02:00
resultIndex := index + offset
if resultIndex < 0 {
resultIndex = 0
} else if resultIndex > len ( p . PatchHunks ) - 1 {
resultIndex = len ( p . PatchHunks ) - 1
}
return p . PatchHunks [ resultIndex ]
}
}
// if your cursor is past the last hunk, select the last hunk
2020-08-15 02:58:29 +02:00
if lineIndex > p . PatchHunks [ len ( p . PatchHunks ) - 1 ] . LastLineIdx ( ) {
2019-10-30 11:22:30 +02:00
return p . PatchHunks [ len ( p . PatchHunks ) - 1 ]
}
// otherwise select the first
return p . PatchHunks [ 0 ]
}
2019-11-04 10:47:25 +02:00
// selected means you've got it highlighted with your cursor
// included means the line has been included in the patch (only applicable when
// building a patch)
func ( l * PatchLine ) render ( selected bool , included bool ) string {
2019-10-30 11:22:30 +02:00
content := l . Content
if len ( content ) == 0 {
content = " " // using the space so that we can still highlight if necessary
}
// for hunk headers we need to start off cyan and then use white for the message
if l . Kind == HUNK_HEADER {
re := regexp . MustCompile ( "(@@.*?@@)(.*)" )
match := re . FindStringSubmatch ( content )
2019-11-04 10:47:25 +02:00
return coloredString ( color . FgCyan , match [ 1 ] , selected , included ) + coloredString ( theme . DefaultTextColor , match [ 2 ] , selected , false )
2019-10-30 11:22:30 +02:00
}
var colorAttr color . Attribute
switch l . Kind {
case PATCH_HEADER :
colorAttr = color . Bold
case ADDITION :
colorAttr = color . FgGreen
case DELETION :
colorAttr = color . FgRed
2019-11-04 10:47:25 +02:00
case COMMIT_SHA :
colorAttr = color . FgYellow
2019-10-30 11:22:30 +02:00
default :
colorAttr = theme . DefaultTextColor
}
2019-11-04 10:47:25 +02:00
return coloredString ( colorAttr , content , selected , included )
2019-10-30 11:22:30 +02:00
}
2019-11-04 10:47:25 +02:00
func coloredString ( colorAttr color . Attribute , str string , selected bool , included bool ) string {
2019-10-30 11:22:30 +02:00
var cl * color . Color
2019-11-04 10:47:25 +02:00
attributes := [ ] color . Attribute { colorAttr }
2019-10-30 11:22:30 +02:00
if selected {
2020-05-15 12:43:16 +02:00
attributes = append ( attributes , theme . SelectedRangeBgColor )
2019-11-04 10:47:25 +02:00
}
cl = color . New ( attributes ... )
var clIncluded * color . Color
if included {
clIncluded = color . New ( append ( attributes , color . BgGreen ) ... )
2019-10-30 11:22:30 +02:00
} else {
2019-11-04 10:47:25 +02:00
clIncluded = color . New ( attributes ... )
}
if len ( str ) < 2 {
return utils . ColoredStringDirect ( str , clIncluded )
2019-10-30 11:22:30 +02:00
}
2019-11-04 10:47:25 +02:00
return utils . ColoredStringDirect ( str [ : 1 ] , clIncluded ) + utils . ColoredStringDirect ( str [ 1 : ] , cl )
2019-10-30 11:22:30 +02:00
}
2021-04-18 08:30:34 +02:00
func parsePatch ( patch string ) ( [ ] int , [ ] int , [ ] * PatchLine ) {
2018-12-02 10:57:01 +02:00
lines := strings . Split ( patch , "\n" )
hunkStarts := [ ] int { }
stageableLines := [ ] int { }
2019-10-30 11:22:30 +02:00
pastFirstHunkHeader := false
2019-11-04 10:47:25 +02:00
pastCommitDescription := true
2019-10-30 11:22:30 +02:00
patchLines := make ( [ ] * PatchLine , len ( lines ) )
2021-03-31 14:55:06 +02:00
var lineKind PatchLineKind
2019-10-30 11:22:30 +02:00
var firstChar string
2018-12-05 10:33:46 +02:00
for index , line := range lines {
2019-10-30 11:22:30 +02:00
firstChar = " "
if len ( line ) > 0 {
firstChar = line [ : 1 ]
}
2019-11-04 10:47:25 +02:00
if index == 0 && strings . HasPrefix ( line , "commit" ) {
lineKind = COMMIT_SHA
pastCommitDescription = false
} else if ! pastCommitDescription {
if strings . HasPrefix ( line , "diff" ) || strings . HasPrefix ( line , "---" ) {
pastCommitDescription = true
lineKind = PATCH_HEADER
} else {
lineKind = COMMIT_DESCRIPTION
}
} else if firstChar == "@" {
2019-10-30 11:22:30 +02:00
pastFirstHunkHeader = true
2018-12-02 10:57:01 +02:00
hunkStarts = append ( hunkStarts , index )
2019-10-30 11:22:30 +02:00
lineKind = HUNK_HEADER
} else if pastFirstHunkHeader {
switch firstChar {
case "-" :
lineKind = DELETION
stageableLines = append ( stageableLines , index )
case "+" :
lineKind = ADDITION
stageableLines = append ( stageableLines , index )
case "\\" :
lineKind = NEWLINE_MESSAGE
case " " :
lineKind = CONTEXT
}
2019-11-04 10:47:25 +02:00
} else {
lineKind = PATCH_HEADER
2018-12-02 10:57:01 +02:00
}
2019-10-30 11:22:30 +02:00
patchLines [ index ] = & PatchLine { Kind : lineKind , Content : line }
}
2021-04-18 08:30:34 +02:00
return hunkStarts , stageableLines , patchLines
2019-10-30 11:22:30 +02:00
}
// Render returns the coloured string of the diff with any selected lines highlighted
2019-11-04 10:47:25 +02:00
func ( p * PatchParser ) Render ( firstLineIndex int , lastLineIndex int , incLineIndices [ ] int ) string {
2019-10-30 11:22:30 +02:00
renderedLines := make ( [ ] string , len ( p . PatchLines ) )
for index , patchLine := range p . PatchLines {
selected := index >= firstLineIndex && index <= lastLineIndex
2019-11-04 10:47:25 +02:00
included := utils . IncludesInt ( incLineIndices , index )
renderedLines [ index ] = patchLine . render ( selected , included )
2019-10-30 11:22:30 +02:00
}
2019-11-05 09:21:09 +02:00
result := strings . Join ( renderedLines , "\n" )
if strings . TrimSpace ( utils . Decolorise ( result ) ) == "" {
return ""
}
return result
2019-10-30 11:22:30 +02:00
}
// GetNextStageableLineIndex takes a line index and returns the line index of the next stageable line
// note this will actually include the current index if it is stageable
func ( p * PatchParser ) GetNextStageableLineIndex ( currentIndex int ) int {
for _ , lineIndex := range p . StageableLines {
if lineIndex >= currentIndex {
return lineIndex
2018-12-02 10:57:01 +02:00
}
}
2019-10-30 11:22:30 +02:00
return p . StageableLines [ len ( p . StageableLines ) - 1 ]
2018-12-02 10:57:01 +02:00
}