@@ -29,12 +29,14 @@ var (
2929 // mentionPattern matches all mentions in the form of "@user"
3030 mentionPattern = regexp .MustCompile (`(?:\s|^|\(|\[)(@[0-9a-zA-Z-_]+|@[0-9a-zA-Z-_][0-9a-zA-Z-_.]+[0-9a-zA-Z-_])(?:\s|[:,;.?!]\s|[:,;.?!]?$|\)|\])` )
3131 // issueNumericPattern matches string that references to a numeric issue, e.g. #1287
32- issueNumericPattern = regexp .MustCompile (`(?:\s|^|\(|\[)([#!][0-9]+)(?:\s|$|\)|\]|:|\.( \s|$) )` )
32+ issueNumericPattern = regexp .MustCompile (`(?:\s|^|\(|\[)([#!][0-9]+)(?:\s|$|\)|\]|[:;,.?!] \s|[:;,.?!]$ )` )
3333 // issueAlphanumericPattern matches string that references to an alphanumeric issue, e.g. ABC-1234
3434 issueAlphanumericPattern = regexp .MustCompile (`(?:\s|^|\(|\[)([A-Z]{1,10}-[1-9][0-9]*)(?:\s|$|\)|\]|:|\.(\s|$))` )
3535 // crossReferenceIssueNumericPattern matches string that references a numeric issue in a different repository
3636 // e.g. gogits/gogs#12345
37- crossReferenceIssueNumericPattern = regexp .MustCompile (`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+/[0-9a-zA-Z-_\.]+[#!][0-9]+)(?:\s|$|\)|\]|\.(\s|$))` )
37+ crossReferenceIssueNumericPattern = regexp .MustCompile (`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+/[0-9a-zA-Z-_\.]+[#!][0-9]+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)` )
38+ // spaceTrimmedPattern let's us find the trailing space
39+ spaceTrimmedPattern = regexp .MustCompile (`(?:.*[0-9a-zA-Z-_])\s` )
3840
3941 issueCloseKeywordsPat , issueReopenKeywordsPat * regexp.Regexp
4042 issueKeywordsOnce sync.Once
@@ -172,10 +174,24 @@ func FindAllMentionsMarkdown(content string) []string {
172174// FindAllMentionsBytes matches mention patterns in given content
173175// and returns a list of locations for the unvalidated user names, including the @ prefix.
174176func FindAllMentionsBytes (content []byte ) []RefSpan {
175- mentions := mentionPattern .FindAllSubmatchIndex (content , - 1 )
176- ret := make ([]RefSpan , len (mentions ))
177- for i , val := range mentions {
178- ret [i ] = RefSpan {Start : val [2 ], End : val [3 ]}
177+ // Sadly we can't use FindAllSubmatchIndex because our pattern checks for starting and
178+ // trailing spaces (\s@mention,\s), so if we get two consecutive references, the space
179+ // from the second reference will be "eaten" by the first one:
180+ // ...\s@mention1\s@mention2\s... --> ...`\s@mention1\s`, (not) `@mention2,\s...`
181+ ret := make ([]RefSpan , 0 , 5 )
182+ pos := 0
183+ for {
184+ match := mentionPattern .FindSubmatchIndex (content [pos :])
185+ if match == nil {
186+ break
187+ }
188+ ret = append (ret , RefSpan {Start : match [2 ] + pos , End : match [3 ] + pos })
189+ notrail := spaceTrimmedPattern .FindSubmatchIndex (content [match [2 ]+ pos : match [3 ]+ pos ])
190+ if notrail == nil {
191+ pos = match [3 ] + pos
192+ } else {
193+ pos = match [3 ] + pos + notrail [1 ] - notrail [3 ]
194+ }
179195 }
180196 return ret
181197}
@@ -252,19 +268,44 @@ func FindRenderizableReferenceAlphanumeric(content string) (bool, *RenderizableR
252268func findAllIssueReferencesBytes (content []byte , links []string ) []* rawReference {
253269
254270 ret := make ([]* rawReference , 0 , 10 )
255-
256- matches := issueNumericPattern .FindAllSubmatchIndex (content , - 1 )
257- for _ , match := range matches {
258- if ref := getCrossReference (content , match [2 ], match [3 ], false , false ); ref != nil {
271+ pos := 0
272+
273+ // Sadly we can't use FindAllSubmatchIndex because our pattern checks for starting and
274+ // trailing spaces (\s#ref,\s), so if we get two consecutive references, the space
275+ // from the second reference will be "eaten" by the first one:
276+ // ...\s#ref1\s#ref2\s... --> ...`\s#ref1\s`, (not) `#ref2,\s...`
277+ for {
278+ match := issueNumericPattern .FindSubmatchIndex (content [pos :])
279+ if match == nil {
280+ break
281+ }
282+ if ref := getCrossReference (content , match [2 ]+ pos , match [3 ]+ pos , false , false ); ref != nil {
259283 ret = append (ret , ref )
260284 }
285+ notrail := spaceTrimmedPattern .FindSubmatchIndex (content [match [2 ]+ pos : match [3 ]+ pos ])
286+ if notrail == nil {
287+ pos = match [3 ] + pos
288+ } else {
289+ pos = match [3 ] + pos + notrail [1 ] - notrail [3 ]
290+ }
261291 }
262292
263- matches = crossReferenceIssueNumericPattern .FindAllSubmatchIndex (content , - 1 )
264- for _ , match := range matches {
265- if ref := getCrossReference (content , match [2 ], match [3 ], false , false ); ref != nil {
293+ pos = 0
294+
295+ for {
296+ match := crossReferenceIssueNumericPattern .FindSubmatchIndex (content [pos :])
297+ if match == nil {
298+ break
299+ }
300+ if ref := getCrossReference (content , match [2 ]+ pos , match [3 ]+ pos , false , false ); ref != nil {
266301 ret = append (ret , ref )
267302 }
303+ notrail := spaceTrimmedPattern .FindSubmatchIndex (content [match [2 ]+ pos : match [3 ]+ pos ])
304+ if notrail == nil {
305+ pos = match [3 ] + pos
306+ } else {
307+ pos = match [3 ] + pos + notrail [1 ] - notrail [3 ]
308+ }
268309 }
269310
270311 localhost := getGiteaHostName ()
0 commit comments