@@ -10,6 +10,7 @@ import (
1010 "path"
1111 "path/filepath"
1212 "regexp"
13+ "slices"
1314 "strings"
1415 "sync"
1516
5455 shortLinkPattern = regexp .MustCompile (`\[\[(.*?)\]\](\w*)` )
5556
5657 // anyHashPattern splits url containing SHA into parts
57- anyHashPattern = regexp .MustCompile (`https?://(?:\S+/){4,5}([0-9a-f]{40,64})(/[-+~_%.a-zA-Z0-9/ ]+)?(# [-+~_%.a-zA-Z0-9 ]+)?` )
58+ anyHashPattern = regexp .MustCompile (`https?://(?:\S+/){4,5}([0-9a-f]{40,64})(/[-+~%./\w ]+)?(\? [-+~%.\w&=]+)?(#[-+~%.\w ]+)?` )
5859
5960 // comparePattern matches "http://domain/org/repo/compare/COMMIT1...COMMIT2#hash"
6061 comparePattern = regexp .MustCompile (`https?://(?:\S+/){4,5}([0-9a-f]{7,64})(\.\.\.?)([0-9a-f]{7,64})?(#[-+~_%.a-zA-Z0-9]+)?` )
@@ -591,7 +592,8 @@ func replaceContentList(node *html.Node, i, j int, newNodes []*html.Node) {
591592
592593func mentionProcessor (ctx * RenderContext , node * html.Node ) {
593594 start := 0
594- for node != nil {
595+ nodeStop := node .NextSibling
596+ for node != nodeStop {
595597 found , loc := references .FindFirstMentionBytes (util .UnsafeStringToBytes (node .Data [start :]))
596598 if ! found {
597599 node = node .NextSibling
@@ -962,57 +964,68 @@ func commitCrossReferencePatternProcessor(ctx *RenderContext, node *html.Node) {
962964 }
963965}
964966
965- // fullHashPatternProcessor renders SHA containing URLs
966- func fullHashPatternProcessor (ctx * RenderContext , node * html.Node ) {
967- if ctx .Metas == nil {
968- return
967+ type anyHashPatternResult struct {
968+ PosStart int
969+ PosEnd int
970+ FullURL string
971+ CommitID string
972+ SubPath string
973+ QueryHash string
974+ }
975+
976+ func anyHashPatternExtract (s string ) (ret anyHashPatternResult , ok bool ) {
977+ m := anyHashPattern .FindStringSubmatchIndex (s )
978+ if m == nil {
979+ return ret , false
969980 }
970981
971- next := node .NextSibling
972- for node != nil && node != next {
973- m := anyHashPattern .FindStringSubmatchIndex (node .Data )
974- if m == nil {
975- return
982+ ret .PosStart , ret .PosEnd = m [0 ], m [1 ]
983+ ret .FullURL = s [ret .PosStart :ret .PosEnd ]
984+ if strings .HasSuffix (ret .FullURL , "." ) {
985+ // if url ends in '.', it's very likely that it is not part of the actual url but used to finish a sentence.
986+ ret .PosEnd --
987+ ret .FullURL = ret .FullURL [:len (ret .FullURL )- 1 ]
988+ for i := 0 ; i < len (m ); i ++ {
989+ m [i ] = min (m [i ], ret .PosEnd )
976990 }
991+ }
977992
978- urlFull := node .Data [m [0 ]:m [1 ]]
979- text := base .ShortSha (node .Data [m [2 ]:m [3 ]])
993+ ret .CommitID = s [m [2 ]:m [3 ]]
994+ if m [5 ] > 0 {
995+ ret .SubPath = s [m [4 ]:m [5 ]]
996+ }
980997
981- // 3rd capture group matches a optional path
982- subpath := ""
983- if m [5 ] > 0 {
984- subpath = node .Data [m [4 ]:m [5 ]]
985- }
998+ lastStart , lastEnd := m [len (m )- 2 ], m [len (m )- 1 ]
999+ if lastEnd > 0 {
1000+ ret .QueryHash = s [lastStart :lastEnd ][1 :]
1001+ }
1002+ return ret , true
1003+ }
9861004
987- // 4th capture group matches a optional url hash
988- hash := ""
989- if m [7 ] > 0 {
990- hash = node .Data [m [6 ]:m [7 ]][1 :]
1005+ // fullHashPatternProcessor renders SHA containing URLs
1006+ func fullHashPatternProcessor (ctx * RenderContext , node * html.Node ) {
1007+ if ctx .Metas == nil {
1008+ return
1009+ }
1010+ nodeStop := node .NextSibling
1011+ for node != nodeStop {
1012+ if node .Type != html .TextNode {
1013+ node = node .NextSibling
1014+ continue
9911015 }
992-
993- start := m [0 ]
994- end := m [1 ]
995-
996- // If url ends in '.', it's very likely that it is not part of the
997- // actual url but used to finish a sentence.
998- if strings .HasSuffix (urlFull , "." ) {
999- end --
1000- urlFull = urlFull [:len (urlFull )- 1 ]
1001- if hash != "" {
1002- hash = hash [:len (hash )- 1 ]
1003- } else if subpath != "" {
1004- subpath = subpath [:len (subpath )- 1 ]
1005- }
1016+ ret , ok := anyHashPatternExtract (node .Data )
1017+ if ! ok {
1018+ node = node .NextSibling
1019+ continue
10061020 }
1007-
1008- if subpath != "" {
1009- text += subpath
1021+ text := base . ShortSha ( ret . CommitID )
1022+ if ret . SubPath != "" {
1023+ text += ret . SubPath
10101024 }
1011-
1012- if hash != "" {
1013- text += " (" + hash + ")"
1025+ if ret .QueryHash != "" {
1026+ text += " (" + ret .QueryHash + ")"
10141027 }
1015- replaceContent (node , start , end , createCodeLink (urlFull , text , "commit" ))
1028+ replaceContent (node , ret . PosStart , ret . PosEnd , createCodeLink (ret . FullURL , text , "commit" ))
10161029 node = node .NextSibling .NextSibling
10171030 }
10181031}
@@ -1021,19 +1034,16 @@ func comparePatternProcessor(ctx *RenderContext, node *html.Node) {
10211034 if ctx .Metas == nil {
10221035 return
10231036 }
1024-
1025- next := node .NextSibling
1026- for node != nil && node != next {
1027- m := comparePattern .FindStringSubmatchIndex (node .Data )
1028- if m == nil {
1029- return
1037+ nodeStop := node .NextSibling
1038+ for node != nodeStop {
1039+ if node .Type != html .TextNode {
1040+ node = node .NextSibling
1041+ continue
10301042 }
1031-
1032- // Ensure that every group (m[0]...m[7]) has a match
1033- for i := 0 ; i < 8 ; i ++ {
1034- if m [i ] == - 1 {
1035- return
1036- }
1043+ m := comparePattern .FindStringSubmatchIndex (node .Data )
1044+ if m == nil || slices .Contains (m [:8 ], - 1 ) { // ensure that every group (m[0]...m[7]) has a match
1045+ node = node .NextSibling
1046+ continue
10371047 }
10381048
10391049 urlFull := node .Data [m [0 ]:m [1 ]]
0 commit comments