Highlight difftags/v1.2.0-rc1
| @@ -13,10 +13,13 @@ import ( | |||
| "os" | |||
| "os/exec" | |||
| "strings" | |||
| "html/template" | |||
| "html" | |||
| "github.com/Unknwon/com" | |||
| "golang.org/x/net/html/charset" | |||
| "golang.org/x/text/transform" | |||
| "github.com/sergi/go-diff/diffmatchpatch" | |||
| "github.com/gogits/git-module" | |||
| @@ -25,16 +28,19 @@ import ( | |||
| "github.com/gogits/gogs/modules/process" | |||
| ) | |||
| // Diff line types. | |||
| type DiffLineType uint8 | |||
| const ( | |||
| DIFF_LINE_PLAIN = iota + 1 | |||
| DIFF_LINE_PLAIN DiffLineType = iota + 1 | |||
| DIFF_LINE_ADD | |||
| DIFF_LINE_DEL | |||
| DIFF_LINE_SECTION | |||
| ) | |||
| type DiffFileType uint8 | |||
| const ( | |||
| DIFF_FILE_ADD = iota + 1 | |||
| DIFF_FILE_ADD DiffFileType = iota + 1 | |||
| DIFF_FILE_CHANGE | |||
| DIFF_FILE_DEL | |||
| DIFF_FILE_RENAME | |||
| @@ -43,12 +49,13 @@ const ( | |||
| type DiffLine struct { | |||
| LeftIdx int | |||
| RightIdx int | |||
| Type int | |||
| Type DiffLineType | |||
| Content string | |||
| ParsedContent template.HTML | |||
| } | |||
| func (d DiffLine) GetType() int { | |||
| return d.Type | |||
| func (d *DiffLine) GetType() int { | |||
| return int(d.Type) | |||
| } | |||
| type DiffSection struct { | |||
| @@ -56,12 +63,89 @@ type DiffSection struct { | |||
| Lines []*DiffLine | |||
| } | |||
| func diffToHtml(diffRecord []diffmatchpatch.Diff, lineType DiffLineType) template.HTML { | |||
| result := "" | |||
| for _, s := range diffRecord { | |||
| if s.Type == diffmatchpatch.DiffInsert && lineType == DIFF_LINE_ADD { | |||
| result = result + "<span class=\"added-code\">"+html.EscapeString(s.Text)+"</span>" | |||
| } else if s.Type == diffmatchpatch.DiffDelete && lineType == DIFF_LINE_DEL { | |||
| result = result + "<span class=\"removed-code\">"+html.EscapeString(s.Text)+"</span>" | |||
| } else if s.Type == diffmatchpatch.DiffEqual { | |||
| result = result + html.EscapeString(s.Text) | |||
| } | |||
| } | |||
| return template.HTML(result) | |||
| } | |||
| // get an specific line by type (add or del) and file line number | |||
| func (diffSection *DiffSection) GetLine(lineType DiffLineType, idx int) *DiffLine { | |||
| difference := 0 | |||
| for _, diffLine := range diffSection.Lines { | |||
| if diffLine.Type == DIFF_LINE_PLAIN { | |||
| // get the difference of line numbers between ADD and DEL versions | |||
| difference = diffLine.RightIdx - diffLine.LeftIdx | |||
| continue | |||
| } | |||
| if lineType == DIFF_LINE_DEL { | |||
| if diffLine.RightIdx == 0 && diffLine.LeftIdx == idx - difference { | |||
| return diffLine | |||
| } | |||
| } else if lineType == DIFF_LINE_ADD { | |||
| if diffLine.LeftIdx == 0 && diffLine.RightIdx == idx + difference { | |||
| return diffLine | |||
| } | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| // computes diff of each diff line and set the HTML on diffLine.ParsedContent | |||
| func (diffSection *DiffSection) ComputeLinesDiff() { | |||
| for _, diffLine := range diffSection.Lines { | |||
| var compareDiffLine *DiffLine | |||
| var diff1, diff2 string | |||
| // default content: as is | |||
| diffLine.ParsedContent = template.HTML(html.EscapeString(diffLine.Content[1:])) | |||
| // just compute diff for adds and removes | |||
| if diffLine.Type != DIFF_LINE_ADD && diffLine.Type != DIFF_LINE_DEL { | |||
| continue | |||
| } | |||
| // try to find equivalent diff line. ignore, otherwise | |||
| if diffLine.Type == DIFF_LINE_ADD { | |||
| compareDiffLine = diffSection.GetLine(DIFF_LINE_DEL, diffLine.RightIdx) | |||
| if compareDiffLine == nil { | |||
| continue | |||
| } | |||
| diff1 = compareDiffLine.Content | |||
| diff2 = diffLine.Content | |||
| } else { | |||
| compareDiffLine = diffSection.GetLine(DIFF_LINE_ADD, diffLine.LeftIdx) | |||
| if compareDiffLine == nil { | |||
| continue | |||
| } | |||
| diff1 = diffLine.Content | |||
| diff2 = compareDiffLine.Content | |||
| } | |||
| dmp := diffmatchpatch.New() | |||
| diffRecord := dmp.DiffMain(diff1[1:], diff2[1:], true) | |||
| diffRecord = dmp.DiffCleanupSemantic(diffRecord) | |||
| diffLine.ParsedContent = diffToHtml(diffRecord, diffLine.Type) | |||
| } | |||
| } | |||
| type DiffFile struct { | |||
| Name string | |||
| OldName string | |||
| Index int | |||
| Addition, Deletion int | |||
| Type int | |||
| Type DiffFileType | |||
| IsCreated bool | |||
| IsDeleted bool | |||
| IsBin bool | |||
| @@ -69,6 +153,10 @@ type DiffFile struct { | |||
| Sections []*DiffSection | |||
| } | |||
| func (diffFile *DiffFile) GetType() int { | |||
| return int(diffFile.Type) | |||
| } | |||
| type Diff struct { | |||
| TotalAddition, TotalDeletion int | |||
| Files []*DiffFile | |||
| @@ -0,0 +1,70 @@ | |||
| package models | |||
| import ( | |||
| dmp "github.com/sergi/go-diff/diffmatchpatch" | |||
| "html/template" | |||
| "testing" | |||
| ) | |||
| func assertEqual(t *testing.T, s1 string, s2 template.HTML) { | |||
| if s1 != string(s2) { | |||
| t.Errorf("%s should be equal %s", s2, s1) | |||
| } | |||
| } | |||
| func assertLineEqual(t *testing.T, d1 *DiffLine, d2 *DiffLine) { | |||
| if d1 != d2 { | |||
| t.Errorf("%v should be equal %v", d1, d2) | |||
| } | |||
| } | |||
| func TestDiffToHtml(t *testing.T) { | |||
| assertEqual(t, "foo <span class=\"added-code\">bar</span> biz", diffToHtml([]dmp.Diff{ | |||
| dmp.Diff{dmp.DiffEqual, "foo "}, | |||
| dmp.Diff{dmp.DiffInsert, "bar"}, | |||
| dmp.Diff{dmp.DiffDelete, " baz"}, | |||
| dmp.Diff{dmp.DiffEqual, " biz"}, | |||
| }, DIFF_LINE_ADD)) | |||
| assertEqual(t, "foo <span class=\"removed-code\">bar</span> biz", diffToHtml([]dmp.Diff{ | |||
| dmp.Diff{dmp.DiffEqual, "foo "}, | |||
| dmp.Diff{dmp.DiffDelete, "bar"}, | |||
| dmp.Diff{dmp.DiffInsert, " baz"}, | |||
| dmp.Diff{dmp.DiffEqual, " biz"}, | |||
| }, DIFF_LINE_DEL)) | |||
| } | |||
| // test if GetLine is return the correct lines | |||
| func TestGetLine(t *testing.T) { | |||
| ds := DiffSection{Lines: []*DiffLine{ | |||
| &DiffLine{LeftIdx: 28, RightIdx: 28, Type: DIFF_LINE_PLAIN}, | |||
| &DiffLine{LeftIdx: 29, RightIdx: 29, Type: DIFF_LINE_PLAIN}, | |||
| &DiffLine{LeftIdx: 30, RightIdx: 30, Type: DIFF_LINE_PLAIN}, | |||
| &DiffLine{LeftIdx: 31, RightIdx: 0, Type: DIFF_LINE_DEL}, | |||
| &DiffLine{LeftIdx: 0, RightIdx: 31, Type: DIFF_LINE_ADD}, | |||
| &DiffLine{LeftIdx: 0, RightIdx: 32, Type: DIFF_LINE_ADD}, | |||
| &DiffLine{LeftIdx: 32, RightIdx: 33, Type: DIFF_LINE_PLAIN}, | |||
| &DiffLine{LeftIdx: 33, RightIdx: 0, Type: DIFF_LINE_DEL}, | |||
| &DiffLine{LeftIdx: 34, RightIdx: 0, Type: DIFF_LINE_DEL}, | |||
| &DiffLine{LeftIdx: 35, RightIdx: 0, Type: DIFF_LINE_DEL}, | |||
| &DiffLine{LeftIdx: 36, RightIdx: 0, Type: DIFF_LINE_DEL}, | |||
| &DiffLine{LeftIdx: 0, RightIdx: 34, Type: DIFF_LINE_ADD}, | |||
| &DiffLine{LeftIdx: 0, RightIdx: 35, Type: DIFF_LINE_ADD}, | |||
| &DiffLine{LeftIdx: 0, RightIdx: 36, Type: DIFF_LINE_ADD}, | |||
| &DiffLine{LeftIdx: 0, RightIdx: 37, Type: DIFF_LINE_ADD}, | |||
| &DiffLine{LeftIdx: 37, RightIdx: 38, Type: DIFF_LINE_PLAIN}, | |||
| &DiffLine{LeftIdx: 38, RightIdx: 39, Type: DIFF_LINE_PLAIN}, | |||
| }} | |||
| assertLineEqual(t, ds.GetLine(DIFF_LINE_ADD, 31), ds.Lines[4]) | |||
| assertLineEqual(t, ds.GetLine(DIFF_LINE_DEL, 31), ds.Lines[3]) | |||
| assertLineEqual(t, ds.GetLine(DIFF_LINE_ADD, 33), ds.Lines[11]) | |||
| assertLineEqual(t, ds.GetLine(DIFF_LINE_ADD, 34), ds.Lines[12]) | |||
| assertLineEqual(t, ds.GetLine(DIFF_LINE_ADD, 35), ds.Lines[13]) | |||
| assertLineEqual(t, ds.GetLine(DIFF_LINE_ADD, 36), ds.Lines[14]) | |||
| assertLineEqual(t, ds.GetLine(DIFF_LINE_DEL, 34), ds.Lines[7]) | |||
| assertLineEqual(t, ds.GetLine(DIFF_LINE_DEL, 35), ds.Lines[8]) | |||
| assertLineEqual(t, ds.GetLine(DIFF_LINE_DEL, 36), ds.Lines[9]) | |||
| assertLineEqual(t, ds.GetLine(DIFF_LINE_DEL, 37), ds.Lines[10]) | |||
| } | |||
| @@ -2764,6 +2764,12 @@ footer .container .links > *:first-child { | |||
| #delete-repo-modal .ui.message { | |||
| width: 100%!important; | |||
| } | |||
| .removed-code { | |||
| background-color: #ff9999; | |||
| } | |||
| .added-code { | |||
| background-color: #99ff99; | |||
| } | |||
| .organization { | |||
| padding-top: 15px; | |||
| padding-bottom: 80px; | |||
| @@ -1217,3 +1217,11 @@ | |||
| width: 100%!important; | |||
| } | |||
| } | |||
| .removed-code { | |||
| background-color: #ff9999; | |||
| } | |||
| .added-code { | |||
| background-color: #99ff99; | |||
| } | |||
| @@ -168,6 +168,12 @@ func Diff(ctx *middleware.Context) { | |||
| } | |||
| } | |||
| for _, diffFile := range diff.Files { | |||
| for _, diffSection := range diffFile.Sections { | |||
| diffSection.ComputeLinesDiff() | |||
| } | |||
| } | |||
| ctx.Data["IsSplitStyle"] = ctx.Query("style") == "split" | |||
| ctx.Data["Username"] = userName | |||
| ctx.Data["Reponame"] = repoName | |||
| @@ -26,7 +26,7 @@ | |||
| {{end}} | |||
| </div> | |||
| <!-- todo finish all file status, now modify, add, delete and rename --> | |||
| <span class="status {{DiffTypeToStr .Type}} poping up" data-content="{{DiffTypeToStr .Type}}" data-variation="inverted tiny" data-position="right center"> </span> | |||
| <span class="status {{DiffTypeToStr .GetType}} poping up" data-content="{{DiffTypeToStr .GetType}}" data-variation="inverted tiny" data-position="right center"> </span> | |||
| <a class="file" href="#diff-{{.Index}}">{{.Name}}</a> | |||
| </li> | |||
| {{end}} | |||
| @@ -71,18 +71,18 @@ | |||
| {{if $.IsSplitStyle}} | |||
| {{range $j, $section := .Sections}} | |||
| {{range $k, $line := .Lines}} | |||
| <tr class="{{DiffLineTypeToStr .Type}}-code nl-{{$k}} ol-{{$k}}"> | |||
| <tr class="{{DiffLineTypeToStr .GetType}}-code nl-{{$k}} ol-{{$k}}"> | |||
| <td class="lines-num lines-num-old"> | |||
| <span rel="{{if $line.LeftIdx}}diff-{{Sha1 $file.Name}}L{{$line.LeftIdx}}{{end}}">{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}</span> | |||
| </td> | |||
| <td class="lines-code halfwidth"> | |||
| <pre class="wrap">{{if $line.LeftIdx}}{{$line.Content}}{{end}}</pre> | |||
| <pre class="wrap">{{if $line.LeftIdx}}{{$line.ParsedContent}}{{end}}</pre> | |||
| </td> | |||
| <td class="lines-num lines-num-new"> | |||
| <span rel="{{if $line.RightIdx}}diff-{{Sha1 $file.Name}}R{{$line.RightIdx}}{{end}}">{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}</span> | |||
| </td> | |||
| <td class="lines-code halfwidth"> | |||
| <pre class="wrap">{{if $line.RightIdx}}{{$line.Content}}{{end}}</pre> | |||
| <pre class="wrap">{{if $line.RightIdx}}{{$line.ParsedContent}}{{end}}</pre> | |||
| </td> | |||
| </tr> | |||
| {{end}} | |||
| @@ -90,8 +90,8 @@ | |||
| {{else}} | |||
| {{range $j, $section := .Sections}} | |||
| {{range $k, $line := .Lines}} | |||
| <tr class="{{DiffLineTypeToStr .Type}}-code nl-{{$k}} ol-{{$k}}"> | |||
| {{if eq .Type 4}} | |||
| <tr class="{{DiffLineTypeToStr .GetType}}-code nl-{{$k}} ol-{{$k}}"> | |||
| {{if eq .GetType 4}} | |||
| <td colspan="2" class="lines-num"> | |||
| {{/* {{if gt $j 0}}<span class="fold octicon octicon-fold"></span>{{end}} */}} | |||
| </td> | |||
| @@ -104,7 +104,7 @@ | |||
| </td> | |||
| {{end}} | |||
| <td class="lines-code"> | |||
| <pre>{{$line.Content}}</pre> | |||
| <pre>{{$line.ParsedContent}}</pre> | |||
| </td> | |||
| </tr> | |||
| {{end}} | |||