appview/pages: support split diffs for interdiff #324

merged
opened by oppi.li targeting master from push-qkpqsrknozxs
Changed files
+289 -136
appview
patchutil
types
+1
appview/pages/pages.go
···
Round int
Interdiff *patchutil.InterdiffResult
OrderedReactionKinds []db.ReactionKind
+
DiffOpts types.DiffOpts
}
// this name is a mouthful
-123
appview/pages/templates/repo/fragments/diff.html
···
{{ end }}
{{ end }}
{{ end }}
-
-
{{ define "unifiedDiffLines" }}
-
{{ $name := .Name.New }}
-
<pre class="overflow-x-auto"><div class="overflow-x-auto"><div class="min-w-full inline-block">{{- range .TextFragments -}}<div class="bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400 select-none text-center">&middot;&middot;&middot;</div>
-
{{- $oldStart := .OldPosition -}}
-
{{- $newStart := .NewPosition -}}
-
{{- $lineNrStyle := "min-w-[3.5rem] flex-shrink-0 select-none text-right bg-white dark:bg-gray-800 target:bg-yellow-200 target:dark:bg-yellow-600" -}}
-
{{- $linkStyle := "text-gray-400 dark:text-gray-500 hover:underline" -}}
-
{{- $lineNrSepStyle1 := "" -}}
-
{{- $lineNrSepStyle2 := "pr-2 border-r border-gray-200 dark:border-gray-700" -}}
-
{{- $containerStyle := "flex min-w-full items-center target:border target:rounded-sm target:border-yellow-200 target:dark:border-yellow-700 scroll-mt-20" -}}
-
{{- $addStyle := "bg-green-100 dark:bg-green-800/30 text-green-700 dark:text-green-400 " -}}
-
{{- $delStyle := "bg-red-100 dark:bg-red-800/30 text-red-700 dark:text-red-400 " -}}
-
{{- $ctxStyle := "bg-white dark:bg-gray-800 text-gray-500 dark:text-gray-400" -}}
-
{{- $opStyle := "w-5 flex-shrink-0 select-none text-center" -}}
-
{{- range .Lines -}}
-
{{- if eq .Op.String "+" -}}
-
<div class="{{ $addStyle }} {{ $containerStyle }}" id="{{$name}}-N{{$newStart}}">
-
<div class="{{$lineNrStyle}} {{$lineNrSepStyle1}}"><span aria-hidden="true" class="invisible">{{$newStart}}</span></div>
-
<div class="{{$lineNrStyle}} {{$lineNrSepStyle2}}"><a class="{{$linkStyle}}" href="#{{$name}}-N{{$newStart}}">{{ $newStart }}</a></div>
-
<div class="{{ $opStyle }}">{{ .Op.String }}</div>
-
<div class="px-2">{{ .Line }}</div>
-
</div>
-
{{- $newStart = add64 $newStart 1 -}}
-
{{- end -}}
-
{{- if eq .Op.String "-" -}}
-
<div class="{{ $delStyle }} {{ $containerStyle }}" id="{{$name}}-O{{$oldStart}}">
-
<div class="{{$lineNrStyle}} {{$lineNrSepStyle1}}"><a class="{{$linkStyle}}" href="#{{$name}}-O{{$oldStart}}">{{ $oldStart }}</a></div>
-
<div class="{{$lineNrStyle}} {{$lineNrSepStyle2}}"><span aria-hidden="true" class="invisible">{{$oldStart}}</span></div>
-
<div class="{{ $opStyle }}">{{ .Op.String }}</div>
-
<div class="px-2">{{ .Line }}</div>
-
</div>
-
{{- $oldStart = add64 $oldStart 1 -}}
-
{{- end -}}
-
{{- if eq .Op.String " " -}}
-
<div class="{{ $ctxStyle }} {{ $containerStyle }}" id="{{$name}}-O{{$oldStart}}-N{{$newStart}}">
-
<div class="{{$lineNrStyle}} {{$lineNrSepStyle1}}"><a class="{{$linkStyle}}" href="#{{$name}}-O{{$oldStart}}-N{{$newStart}}">{{ $oldStart }}</a></div>
-
<div class="{{$lineNrStyle}} {{$lineNrSepStyle2}}"><a class="{{$linkStyle}}" href="#{{$name}}-O{{$oldStart}}-N{{$newStart}}">{{ $newStart }}</a></div>
-
<div class="{{ $opStyle }}">{{ .Op.String }}</div>
-
<div class="px-2">{{ .Line }}</div>
-
</div>
-
{{- $newStart = add64 $newStart 1 -}}
-
{{- $oldStart = add64 $oldStart 1 -}}
-
{{- end -}}
-
{{- end -}}
-
{{- end -}}</div></div></pre>
-
{{ end }}
-
-
{{ define "splitDiffLines" }}
-
{{ $name := .Name.New }}
-
{{- $lineNrStyle := "min-w-[3.5rem] flex-shrink-0 select-none text-right bg-white dark:bg-gray-800" -}}
-
{{- $linkStyle := "text-gray-400 dark:text-gray-500 hover:underline" -}}
-
{{- $lineNrSepStyle := "pr-2 border-r border-gray-200 dark:border-gray-700" -}}
-
{{- $containerStyle := "flex min-w-full items-center target:border target:rounded-sm target:border-yellow-200 target:dark:border-yellow-700 scroll-mt-20" -}}
-
{{- $emptyStyle := "bg-gray-200/30 dark:bg-gray-700/30" -}}
-
{{- $addStyle := "bg-green-100 dark:bg-green-800/30 text-green-700 dark:text-green-400" -}}
-
{{- $delStyle := "bg-red-100 dark:bg-red-800/30 text-red-700 dark:text-red-400 " -}}
-
{{- $ctxStyle := "bg-white dark:bg-gray-800 text-gray-500 dark:text-gray-400" -}}
-
{{- $opStyle := "w-5 flex-shrink-0 select-none text-center" -}}
-
<div class="grid grid-cols-2 divide-x divide-gray-200 dark:divide-gray-700">
-
<pre class="overflow-x-auto col-span-1"><div class="overflow-x-auto"><div class="min-w-full inline-block">{{- range .TextFragments -}}<div class="bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400 select-none text-center">&middot;&middot;&middot;</div>
-
{{- range .LeftLines -}}
-
{{- if .IsEmpty -}}
-
<div class="{{ $emptyStyle }} {{ $containerStyle }}">
-
<div class="{{$lineNrStyle}} {{$lineNrSepStyle}}"><span aria-hidden="true" class="invisible">{{.LineNumber}}</span></div>
-
<div class="{{ $opStyle }}"><span aria-hidden="true" class="invisible">{{ .Op.String }}</span></div>
-
<div class="px-2 invisible" aria-hidden="true">{{ .Content }}</div>
-
</div>
-
{{- else if eq .Op.String "-" -}}
-
<div class="{{ $delStyle }} {{ $containerStyle }}" id="{{$name}}-O{{.LineNumber}}">
-
<div class="{{ $lineNrStyle }} {{ $lineNrSepStyle }}"><a class="{{$linkStyle}}" href="#{{$name}}-O{{.LineNumber}}">{{ .LineNumber }}</a></div>
-
<div class="{{ $opStyle }}">{{ .Op.String }}</div>
-
<div class="px-2">{{ .Content }}</div>
-
</div>
-
{{- else if eq .Op.String " " -}}
-
<div class="{{ $ctxStyle }} {{ $containerStyle }}" id="{{$name}}-O{{.LineNumber}}">
-
<div class="{{ $lineNrStyle }} {{ $lineNrSepStyle }}"><a class="{{$linkStyle}}" href="#{{$name}}-O{{.LineNumber}}">{{ .LineNumber }}</a></div>
-
<div class="{{ $opStyle }}">{{ .Op.String }}</div>
-
<div class="px-2">{{ .Content }}</div>
-
</div>
-
{{- end -}}
-
{{- end -}}
-
{{- end -}}</div></div></pre>
-
-
<pre class="overflow-x-auto col-span-1"><div class="overflow-x-auto"><div class="min-w-full inline-block">{{- range .TextFragments -}}<div class="bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400 select-none text-center">&middot;&middot;&middot;</div>
-
{{- range .RightLines -}}
-
{{- if .IsEmpty -}}
-
<div class="{{ $emptyStyle }} {{ $containerStyle }}">
-
<div class="{{$lineNrStyle}} {{$lineNrSepStyle}}"><span aria-hidden="true" class="invisible">{{.LineNumber}}</span></div>
-
<div class="{{ $opStyle }}"><span aria-hidden="true" class="invisible">{{ .Op.String }}</span></div>
-
<div class="px-2 invisible" aria-hidden="true">{{ .Content }}</div>
-
</div>
-
{{- else if eq .Op.String "+" -}}
-
<div class="{{ $addStyle }} {{ $containerStyle }}" id="{{$name}}-N{{.LineNumber}}">
-
<div class="{{$lineNrStyle}} {{$lineNrSepStyle}}"><a class="{{$linkStyle}}" href="#{{$name}}-N{{.LineNumber}}">{{ .LineNumber }}</a></div>
-
<div class="{{ $opStyle }}">{{ .Op.String }}</div>
-
<div class="px-2" >{{ .Content }}</div>
-
</div>
-
{{- else if eq .Op.String " " -}}
-
<div class="{{ $ctxStyle }} {{ $containerStyle }}" id="{{$name}}-N{{.LineNumber}}">
-
<div class="{{$lineNrStyle}} {{$lineNrSepStyle}}"><a class="{{$linkStyle}}" href="#{{$name}}-N{{.LineNumber}}">{{ .LineNumber }}</a></div>
-
<div class="{{ $opStyle }}">{{ .Op.String }}</div>
-
<div class="px-2">{{ .Content }}</div>
-
</div>
-
{{- end -}}
-
{{- end -}}
-
{{- end -}}</div></div></pre>
-
</div>
-
{{ end }}
-
-
{{ define "statPill" }}
-
<div class="flex items-center font-mono text-sm">
-
{{ if and .Insertions .Deletions }}
-
<span class="rounded-l p-1 select-none bg-green-100 text-green-700 dark:bg-green-800/50 dark:text-green-400">+{{ .Insertions }}</span>
-
<span class="rounded-r p-1 select-none bg-red-100 text-red-700 dark:bg-red-800/50 dark:text-red-400">-{{ .Deletions }}</span>
-
{{ else if .Insertions }}
-
<span class="rounded p-1 select-none bg-green-100 text-green-700 dark:bg-green-800/50 dark:text-green-400">+{{ .Insertions }}</span>
-
{{ else if .Deletions }}
-
<span class="rounded p-1 select-none bg-red-100 text-red-700 dark:bg-red-800/50 dark:text-red-400">-{{ .Deletions }}</span>
-
{{ end }}
-
</div>
-
{{ end }}
-
+13
appview/pages/templates/repo/fragments/diffStatPill.html
···
+
{{ define "repo/fragments/diffStatPill" }}
+
<div class="flex items-center font-mono text-sm">
+
{{ if and .Insertions .Deletions }}
+
<span class="rounded-l p-1 select-none bg-green-100 text-green-700 dark:bg-green-800/50 dark:text-green-400">+{{ .Insertions }}</span>
+
<span class="rounded-r p-1 select-none bg-red-100 text-red-700 dark:bg-red-800/50 dark:text-red-400">-{{ .Deletions }}</span>
+
{{ else if .Insertions }}
+
<span class="rounded p-1 select-none bg-green-100 text-green-700 dark:bg-green-800/50 dark:text-green-400">+{{ .Insertions }}</span>
+
{{ else if .Deletions }}
+
<span class="rounded p-1 select-none bg-red-100 text-red-700 dark:bg-red-800/50 dark:text-red-400">-{{ .Deletions }}</span>
+
{{ end }}
+
</div>
+
{{ end }}
+
+1 -12
appview/pages/templates/repo/fragments/interdiff.html
···
{{ define "repo/fragments/interdiff" }}
{{ $repo := index . 0 }}
{{ $x := index . 1 }}
+
{{ $opts := index . 2 }}
{{ $fileTree := fileTree $x.AffectedFiles }}
{{ $diff := $x.Files }}
{{ $last := sub (len $diff) 1 }}
···
{{ end }}
{{ end }}
-
{{ define "statPill" }}
-
<div class="flex items-center font-mono text-sm">
-
{{ if and .Insertions .Deletions }}
-
<span class="rounded-l p-1 select-none bg-green-100 text-green-700 dark:bg-green-800/50 dark:text-green-400">+{{ .Insertions }}</span>
-
<span class="rounded-r p-1 select-none bg-red-100 text-red-700 dark:bg-red-800/50 dark:text-red-400">-{{ .Deletions }}</span>
-
{{ else if .Insertions }}
-
<span class="rounded p-1 select-none bg-green-100 text-green-700 dark:bg-green-800/50 dark:text-green-400">+{{ .Insertions }}</span>
-
{{ else if .Deletions }}
-
<span class="rounded p-1 select-none bg-red-100 text-red-700 dark:bg-red-800/50 dark:text-red-400">-{{ .Deletions }}</span>
-
{{ end }}
-
</div>
-
{{ end }}
+61
appview/pages/templates/repo/fragments/splitDiff.html
···
+
{{ define "repo/fragments/splitDiff" }}
+
{{ $name := .Id }}
+
{{- $lineNrStyle := "min-w-[3.5rem] flex-shrink-0 select-none text-right bg-white dark:bg-gray-800" -}}
+
{{- $linkStyle := "text-gray-400 dark:text-gray-500 hover:underline" -}}
+
{{- $lineNrSepStyle := "pr-2 border-r border-gray-200 dark:border-gray-700" -}}
+
{{- $containerStyle := "flex min-w-full items-center target:border target:rounded-sm target:border-yellow-200 target:dark:border-yellow-700 scroll-mt-20" -}}
+
{{- $emptyStyle := "bg-gray-200/30 dark:bg-gray-700/30" -}}
+
{{- $addStyle := "bg-green-100 dark:bg-green-800/30 text-green-700 dark:text-green-400" -}}
+
{{- $delStyle := "bg-red-100 dark:bg-red-800/30 text-red-700 dark:text-red-400 " -}}
+
{{- $ctxStyle := "bg-white dark:bg-gray-800 text-gray-500 dark:text-gray-400" -}}
+
{{- $opStyle := "w-5 flex-shrink-0 select-none text-center" -}}
+
<div class="grid grid-cols-2 divide-x divide-gray-200 dark:divide-gray-700">
+
<pre class="overflow-x-auto col-span-1"><div class="overflow-x-auto"><div class="min-w-full inline-block">{{- range .TextFragments -}}<div class="bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400 select-none text-center">&middot;&middot;&middot;</div>
+
{{- range .LeftLines -}}
+
{{- if .IsEmpty -}}
+
<div class="{{ $emptyStyle }} {{ $containerStyle }}">
+
<div class="{{$lineNrStyle}} {{$lineNrSepStyle}}"><span aria-hidden="true" class="invisible">{{.LineNumber}}</span></div>
+
<div class="{{ $opStyle }}"><span aria-hidden="true" class="invisible">{{ .Op.String }}</span></div>
+
<div class="px-2 invisible" aria-hidden="true">{{ .Content }}</div>
+
</div>
+
{{- else if eq .Op.String "-" -}}
+
<div class="{{ $delStyle }} {{ $containerStyle }}" id="{{$name}}-O{{.LineNumber}}">
+
<div class="{{ $lineNrStyle }} {{ $lineNrSepStyle }}"><a class="{{$linkStyle}}" href="#{{$name}}-O{{.LineNumber}}">{{ .LineNumber }}</a></div>
+
<div class="{{ $opStyle }}">{{ .Op.String }}</div>
+
<div class="px-2">{{ .Content }}</div>
+
</div>
+
{{- else if eq .Op.String " " -}}
+
<div class="{{ $ctxStyle }} {{ $containerStyle }}" id="{{$name}}-O{{.LineNumber}}">
+
<div class="{{ $lineNrStyle }} {{ $lineNrSepStyle }}"><a class="{{$linkStyle}}" href="#{{$name}}-O{{.LineNumber}}">{{ .LineNumber }}</a></div>
+
<div class="{{ $opStyle }}">{{ .Op.String }}</div>
+
<div class="px-2">{{ .Content }}</div>
+
</div>
+
{{- end -}}
+
{{- end -}}
+
{{- end -}}</div></div></pre>
+
+
<pre class="overflow-x-auto col-span-1"><div class="overflow-x-auto"><div class="min-w-full inline-block">{{- range .TextFragments -}}<div class="bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400 select-none text-center">&middot;&middot;&middot;</div>
+
{{- range .RightLines -}}
+
{{- if .IsEmpty -}}
+
<div class="{{ $emptyStyle }} {{ $containerStyle }}">
+
<div class="{{$lineNrStyle}} {{$lineNrSepStyle}}"><span aria-hidden="true" class="invisible">{{.LineNumber}}</span></div>
+
<div class="{{ $opStyle }}"><span aria-hidden="true" class="invisible">{{ .Op.String }}</span></div>
+
<div class="px-2 invisible" aria-hidden="true">{{ .Content }}</div>
+
</div>
+
{{- else if eq .Op.String "+" -}}
+
<div class="{{ $addStyle }} {{ $containerStyle }}" id="{{$name}}-N{{.LineNumber}}">
+
<div class="{{$lineNrStyle}} {{$lineNrSepStyle}}"><a class="{{$linkStyle}}" href="#{{$name}}-N{{.LineNumber}}">{{ .LineNumber }}</a></div>
+
<div class="{{ $opStyle }}">{{ .Op.String }}</div>
+
<div class="px-2" >{{ .Content }}</div>
+
</div>
+
{{- else if eq .Op.String " " -}}
+
<div class="{{ $ctxStyle }} {{ $containerStyle }}" id="{{$name}}-N{{.LineNumber}}">
+
<div class="{{$lineNrStyle}} {{$lineNrSepStyle}}"><a class="{{$linkStyle}}" href="#{{$name}}-N{{.LineNumber}}">{{ .LineNumber }}</a></div>
+
<div class="{{ $opStyle }}">{{ .Op.String }}</div>
+
<div class="px-2">{{ .Content }}</div>
+
</div>
+
{{- end -}}
+
{{- end -}}
+
{{- end -}}</div></div></pre>
+
</div>
+
{{ end }}
+47
appview/pages/templates/repo/fragments/unifiedDiff.html
···
+
{{ define "repo/fragments/unifiedDiff" }}
+
{{ $name := .Id }}
+
<pre class="overflow-x-auto"><div class="overflow-x-auto"><div class="min-w-full inline-block">{{- range .TextFragments -}}<div class="bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400 select-none text-center">&middot;&middot;&middot;</div>
+
{{- $oldStart := .OldPosition -}}
+
{{- $newStart := .NewPosition -}}
+
{{- $lineNrStyle := "min-w-[3.5rem] flex-shrink-0 select-none text-right bg-white dark:bg-gray-800 target:bg-yellow-200 target:dark:bg-yellow-600" -}}
+
{{- $linkStyle := "text-gray-400 dark:text-gray-500 hover:underline" -}}
+
{{- $lineNrSepStyle1 := "" -}}
+
{{- $lineNrSepStyle2 := "pr-2 border-r border-gray-200 dark:border-gray-700" -}}
+
{{- $containerStyle := "flex min-w-full items-center target:border target:rounded-sm target:border-yellow-200 target:dark:border-yellow-700 scroll-mt-20" -}}
+
{{- $addStyle := "bg-green-100 dark:bg-green-800/30 text-green-700 dark:text-green-400 " -}}
+
{{- $delStyle := "bg-red-100 dark:bg-red-800/30 text-red-700 dark:text-red-400 " -}}
+
{{- $ctxStyle := "bg-white dark:bg-gray-800 text-gray-500 dark:text-gray-400" -}}
+
{{- $opStyle := "w-5 flex-shrink-0 select-none text-center" -}}
+
{{- range .Lines -}}
+
{{- if eq .Op.String "+" -}}
+
<div class="{{ $addStyle }} {{ $containerStyle }}" id="{{$name}}-N{{$newStart}}">
+
<div class="{{$lineNrStyle}} {{$lineNrSepStyle1}}"><span aria-hidden="true" class="invisible">{{$newStart}}</span></div>
+
<div class="{{$lineNrStyle}} {{$lineNrSepStyle2}}"><a class="{{$linkStyle}}" href="#{{$name}}-N{{$newStart}}">{{ $newStart }}</a></div>
+
<div class="{{ $opStyle }}">{{ .Op.String }}</div>
+
<div class="px-2">{{ .Line }}</div>
+
</div>
+
{{- $newStart = add64 $newStart 1 -}}
+
{{- end -}}
+
{{- if eq .Op.String "-" -}}
+
<div class="{{ $delStyle }} {{ $containerStyle }}" id="{{$name}}-O{{$oldStart}}">
+
<div class="{{$lineNrStyle}} {{$lineNrSepStyle1}}"><a class="{{$linkStyle}}" href="#{{$name}}-O{{$oldStart}}">{{ $oldStart }}</a></div>
+
<div class="{{$lineNrStyle}} {{$lineNrSepStyle2}}"><span aria-hidden="true" class="invisible">{{$oldStart}}</span></div>
+
<div class="{{ $opStyle }}">{{ .Op.String }}</div>
+
<div class="px-2">{{ .Line }}</div>
+
</div>
+
{{- $oldStart = add64 $oldStart 1 -}}
+
{{- end -}}
+
{{- if eq .Op.String " " -}}
+
<div class="{{ $ctxStyle }} {{ $containerStyle }}" id="{{$name}}-O{{$oldStart}}-N{{$newStart}}">
+
<div class="{{$lineNrStyle}} {{$lineNrSepStyle1}}"><a class="{{$linkStyle}}" href="#{{$name}}-O{{$oldStart}}-N{{$newStart}}">{{ $oldStart }}</a></div>
+
<div class="{{$lineNrStyle}} {{$lineNrSepStyle2}}"><a class="{{$linkStyle}}" href="#{{$name}}-O{{$oldStart}}-N{{$newStart}}">{{ $newStart }}</a></div>
+
<div class="{{ $opStyle }}">{{ .Op.String }}</div>
+
<div class="px-2">{{ .Line }}</div>
+
</div>
+
{{- $newStart = add64 $newStart 1 -}}
+
{{- $oldStart = add64 $oldStart 1 -}}
+
{{- end -}}
+
{{- end -}}
+
{{- end -}}</div></div></pre>
+
{{ end }}
+
+10 -1
appview/pulls/pulls.go
···
return
}
+
var diffOpts types.DiffOpts
+
if d := r.URL.Query().Get("diff"); d == "split" {
+
diffOpts.Split = true
+
}
+
pull, ok := r.Context().Value("pull").(*db.Pull)
if !ok {
log.Println("failed to get pull")
···
interdiff := patchutil.Interdiff(previousPatch, currentPatch)
+
for _, f := range interdiff.Files {
+
log.Println("", "", f.Split())
+
}
+
s.pages.RepoPullInterdiffPage(w, pages.RepoPullInterdiffParams{
LoggedInUser: s.oauth.GetUser(r),
RepoInfo: f.RepoInfo(user),
···
Round: roundIdInt,
DidHandleMap: didHandleMap,
Interdiff: interdiff,
+
DiffOpts: diffOpts,
})
-
return
}
func (s *Pulls) RepoPullPatchRaw(w http.ResponseWriter, r *http.Request) {
+25
patchutil/interdiff.go
···
"strings"
"github.com/bluekeyes/go-gitdiff/gitdiff"
+
"tangled.sh/tangled.sh/core/types"
)
type InterdiffResult struct {
···
Status InterdiffFileStatus
}
+
func (s *InterdiffFile) Split() *types.SplitDiff {
+
fragments := make([]types.SplitFragment, len(s.TextFragments))
+
+
for i, fragment := range s.TextFragments {
+
leftLines, rightLines := types.SeparateLines(fragment)
+
+
fragments[i] = types.SplitFragment{
+
Header: fragment.Header(),
+
LeftLines: leftLines,
+
RightLines: rightLines,
+
}
+
}
+
+
return &types.SplitDiff{
+
Name: s.Id(),
+
TextFragments: fragments,
+
}
+
}
+
+
// used by html elements as a unique ID for hrefs
+
func (s *InterdiffFile) Id() string {
+
return s.Name
+
}
+
func (s *InterdiffFile) String() string {
var b strings.Builder
b.WriteString(s.Status.String())
+131
types/split.go
···
+
package types
+
+
import (
+
"github.com/bluekeyes/go-gitdiff/gitdiff"
+
)
+
+
type SplitLine struct {
+
LineNumber int `json:"line_number,omitempty"`
+
Content string `json:"content"`
+
Op gitdiff.LineOp `json:"op"`
+
IsEmpty bool `json:"is_empty"`
+
}
+
+
type SplitFragment struct {
+
Header string `json:"header"`
+
LeftLines []SplitLine `json:"left_lines"`
+
RightLines []SplitLine `json:"right_lines"`
+
}
+
+
type SplitDiff struct {
+
Name string `json:"name"`
+
TextFragments []SplitFragment `json:"fragments"`
+
}
+
+
// used by html elements as a unique ID for hrefs
+
func (d *SplitDiff) Id() string {
+
return d.Name
+
}
+
+
// separate lines into left and right, this includes additional logic to
+
// group consecutive runs of additions and deletions in order to align them
+
// properly in the final output
+
//
+
// TODO: move all diff stuff to a single package, we are spread across patchutil and types right now
+
func SeparateLines(fragment *gitdiff.TextFragment) ([]SplitLine, []SplitLine) {
+
lines := fragment.Lines
+
var leftLines, rightLines []SplitLine
+
oldLineNum := fragment.OldPosition
+
newLineNum := fragment.OldPosition
+
+
// process deletions and additions in groups for better alignment
+
i := 0
+
for i < len(lines) {
+
line := lines[i]
+
+
switch line.Op {
+
case gitdiff.OpContext:
+
leftLines = append(leftLines, SplitLine{
+
LineNumber: int(oldLineNum),
+
Content: line.Line,
+
Op: gitdiff.OpContext,
+
IsEmpty: false,
+
})
+
rightLines = append(rightLines, SplitLine{
+
LineNumber: int(newLineNum),
+
Content: line.Line,
+
Op: gitdiff.OpContext,
+
IsEmpty: false,
+
})
+
oldLineNum++
+
newLineNum++
+
i++
+
+
case gitdiff.OpDelete:
+
deletionCount := 0
+
for j := i; j < len(lines) && lines[j].Op == gitdiff.OpDelete; j++ {
+
leftLines = append(leftLines, SplitLine{
+
LineNumber: int(oldLineNum),
+
Content: lines[j].Line,
+
Op: gitdiff.OpDelete,
+
IsEmpty: false,
+
})
+
oldLineNum++
+
deletionCount++
+
}
+
i += deletionCount
+
+
additionCount := 0
+
for j := i; j < len(lines) && lines[j].Op == gitdiff.OpAdd; j++ {
+
rightLines = append(rightLines, SplitLine{
+
LineNumber: int(newLineNum),
+
Content: lines[j].Line,
+
Op: gitdiff.OpAdd,
+
IsEmpty: false,
+
})
+
newLineNum++
+
additionCount++
+
}
+
i += additionCount
+
+
// add empty lines to balance the sides
+
if deletionCount > additionCount {
+
// more deletions than additions - pad right side
+
for k := 0; k < deletionCount-additionCount; k++ {
+
rightLines = append(rightLines, SplitLine{
+
Content: "",
+
Op: gitdiff.OpContext,
+
IsEmpty: true,
+
})
+
}
+
} else if additionCount > deletionCount {
+
// more additions than deletions - pad left side
+
for k := 0; k < additionCount-deletionCount; k++ {
+
leftLines = append(leftLines, SplitLine{
+
Content: "",
+
Op: gitdiff.OpContext,
+
IsEmpty: true,
+
})
+
}
+
}
+
+
case gitdiff.OpAdd:
+
// standalone addition (not preceded by deletion)
+
leftLines = append(leftLines, SplitLine{
+
Content: "",
+
Op: gitdiff.OpContext,
+
IsEmpty: true,
+
})
+
rightLines = append(rightLines, SplitLine{
+
LineNumber: int(newLineNum),
+
Content: line.Line,
+
Op: gitdiff.OpAdd,
+
IsEmpty: false,
+
})
+
newLineNum++
+
i++
+
}
+
}
+
+
return leftLines, rightLines
+
}