From c21c2cd3b975aac4d1da339ea5a4b09db15e28db Mon Sep 17 00:00:00 2001 From: Tadeas Uhlir Date: Tue, 2 Apr 2024 19:04:59 +0200 Subject: [PATCH 01/10] fix(lsp): ignore diagnostic check within code blocks --- internal/adapter/lsp/document.go | 88 ++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/internal/adapter/lsp/document.go b/internal/adapter/lsp/document.go index 2b8b55c0..fda26d3c 100644 --- a/internal/adapter/lsp/document.go +++ b/internal/adapter/lsp/document.go @@ -163,6 +163,9 @@ func (d *document) LookForward(pos protocol.Position, length int) string { var wikiLinkRegex = regexp.MustCompile(`\[?\[\[(.+?)(?: *\| *(.+?))?\]\]`) var markdownLinkRegex = regexp.MustCompile(`\[([^\]]+?[^\\])\]\((.+?[^\\])\)`) var fileURIregex = regexp.MustCompile(`file:///`) +var fencedStartRegex = regexp.MustCompile(`^(` + "```" + `|~~~).*`) +var fencedEndRegex = regexp.MustCompile(`^(` + "```" + `|~~~)\s*`) +var indentedRegex = regexp.MustCompile(`^(\s{4}|\t).+`) // LinkFromRoot returns a Link to this document from the root of the given // notebook. @@ -194,11 +197,52 @@ func (d *document) DocumentLinkAt(pos protocol.Position) (*documentLink, error) return nil, nil } +func linkWithinInlineCode(strBuffer string, linkStart, linkEnd int, insideInline bool) bool { + if backtickId := strings.Index(strBuffer, "`"); backtickId > 0 && backtickId < linkEnd { + return linkWithinInlineCode(strBuffer[backtickId+1:], + linkStart-backtickId-1, linkEnd-backtickId-1, !insideInline) + } else { + return insideInline + } + + // backtickStart += cursorPos + // if insideInline { + // if backtickStart >= end { + // return true + // } + // return linkWithinInlineCode(line[backtickStart+1:], start-backtickStart-1, end-backtickStart-1, false) + // } else if backtickStart < start { + // return linkWithinInlineCode(line[backtickStart+1:], start-backtickStart-1, end-backtickStart-1, !insideInline) + // } + + // if backtickStart < start { + // backtickEnd := strings.Index(line[backtickStart+1:], "`") + // if backtickEnd >= 0 { + // backtickEnd += backtickStart + 1 + // if backtickEnd >= end { + // return true + // } else { + // return linkWithinInlineCode(line[backtickEnd+1:], start-backtickEnd-1, end-backtickEnd-1, false) + // } + // } else { + // return true + // } + // } + // return false + //} else { + // return insideInline + //} +} + // DocumentLinks returns all the internal and external links found in the // document. func (d *document) DocumentLinks() ([]documentLink, error) { links := []documentLink{} + insideInline := false + insideFenced := false + insideIndented := false + currentCodeBlockStart := -1 lines := d.GetLines() for lineIndex, line := range lines { @@ -229,10 +273,47 @@ func (d *document) DocumentLinks() ([]documentLink, error) { }) } + if insideFenced { + if fencedEndRegex.FindStringIndex(line) != nil && + lines[currentCodeBlockStart][:3] == line[:3] { + // Fenced code block ending with this line + insideFenced = false + currentCodeBlockStart = -1 + } + // Within fenced code block, skip link checks + continue + } else if insideIndented { + if indentedRegex.FindStringIndex(line) == nil && len(line) > 0 { + // No longer indented, will process links + insideIndented = false + currentCodeBlockStart = -1 + } else { + // Still indented, skip link checks + continue + } + } else { + // Check whether we are in fenced / indented code check + if fencedStartRegex.FindStringIndex(line) != nil { + insideFenced = true + currentCodeBlockStart = lineIndex + continue + } else if indentedRegex.FindStringIndex(line) != nil && + (lineIndex > 0 && len(lines[lineIndex-1]) == 0 || lineIndex == 0) { + // Indented code block starts on this line + insideIndented = true + currentCodeBlockStart = lineIndex + continue + } + } + // extract link paths from [title](path) patterns // note: match[0:1] is the entire match, match[2:3] is the contents of // brackets, match[4:5] is contents of parentheses for _, match := range markdownLinkRegex.FindAllStringSubmatchIndex(line, -1) { + // Ignore when inside backticks: `[title](file)` + if linkWithinInlineCode(line, match[0], match[1], insideInline) { + continue + } // Ignore embedded images ![title](file.png) if match[0] > 0 && line[match[0]-1] == '!' { @@ -259,10 +340,17 @@ func (d *document) DocumentLinks() ([]documentLink, error) { } for _, match := range wikiLinkRegex.FindAllStringSubmatchIndex(line, -1) { + // Ignore when inside backticks: `[[filename]]` + if linkWithinInlineCode(line, match[0], match[1], insideInline) { + continue + } href := line[match[2]:match[3]] hasTitle := match[4] != -1 appendLink(href, match[0], match[1], hasTitle, true) } + if strings.Count(line, "`") % 2 == 1 { + insideInline = !insideInline + } } return links, nil From 2bf9b5ddd073a533243563029f5b9befc1396ccc Mon Sep 17 00:00:00 2001 From: Tadeas Uhlir Date: Tue, 2 Apr 2024 19:13:02 +0200 Subject: [PATCH 02/10] chore(lsp): cleanup commented out code, add new comment --- internal/adapter/lsp/document.go | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/internal/adapter/lsp/document.go b/internal/adapter/lsp/document.go index fda26d3c..4530155a 100644 --- a/internal/adapter/lsp/document.go +++ b/internal/adapter/lsp/document.go @@ -197,6 +197,7 @@ func (d *document) DocumentLinkAt(pos protocol.Position) (*documentLink, error) return nil, nil } +// Recursive function to check whether a link is within inline code block. func linkWithinInlineCode(strBuffer string, linkStart, linkEnd int, insideInline bool) bool { if backtickId := strings.Index(strBuffer, "`"); backtickId > 0 && backtickId < linkEnd { return linkWithinInlineCode(strBuffer[backtickId+1:], @@ -204,34 +205,6 @@ func linkWithinInlineCode(strBuffer string, linkStart, linkEnd int, insideInline } else { return insideInline } - - // backtickStart += cursorPos - // if insideInline { - // if backtickStart >= end { - // return true - // } - // return linkWithinInlineCode(line[backtickStart+1:], start-backtickStart-1, end-backtickStart-1, false) - // } else if backtickStart < start { - // return linkWithinInlineCode(line[backtickStart+1:], start-backtickStart-1, end-backtickStart-1, !insideInline) - // } - - // if backtickStart < start { - // backtickEnd := strings.Index(line[backtickStart+1:], "`") - // if backtickEnd >= 0 { - // backtickEnd += backtickStart + 1 - // if backtickEnd >= end { - // return true - // } else { - // return linkWithinInlineCode(line[backtickEnd+1:], start-backtickEnd-1, end-backtickEnd-1, false) - // } - // } else { - // return true - // } - // } - // return false - //} else { - // return insideInline - //} } // DocumentLinks returns all the internal and external links found in the From d3aab27d8712dcd20a60853b7c49da6636cf499c Mon Sep 17 00:00:00 2001 From: Tadeas Uhlir Date: Thu, 4 Apr 2024 10:53:04 +0200 Subject: [PATCH 03/10] fix(lsp): fix behavior when inline code is a first string on the line --- internal/adapter/lsp/document.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/adapter/lsp/document.go b/internal/adapter/lsp/document.go index 4530155a..c636e679 100644 --- a/internal/adapter/lsp/document.go +++ b/internal/adapter/lsp/document.go @@ -197,9 +197,9 @@ func (d *document) DocumentLinkAt(pos protocol.Position) (*documentLink, error) return nil, nil } -// Recursive function to check whether a link is within inline code block. +// Recursive function to check whether a link is within inline code. func linkWithinInlineCode(strBuffer string, linkStart, linkEnd int, insideInline bool) bool { - if backtickId := strings.Index(strBuffer, "`"); backtickId > 0 && backtickId < linkEnd { + if backtickId := strings.Index(strBuffer, "`"); backtickId >= 0 && backtickId < linkEnd { return linkWithinInlineCode(strBuffer[backtickId+1:], linkStart-backtickId-1, linkEnd-backtickId-1, !insideInline) } else { From 37ea59af163bd2b43c508457c671f7c5083d275c Mon Sep 17 00:00:00 2001 From: tjex Date: Fri, 5 Apr 2024 14:25:33 +1100 Subject: [PATCH 04/10] clarify comments and move if block before appendLink() --- internal/adapter/lsp/document.go | 71 ++++++++++++++++---------------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/internal/adapter/lsp/document.go b/internal/adapter/lsp/document.go index c636e679..784c4f81 100644 --- a/internal/adapter/lsp/document.go +++ b/internal/adapter/lsp/document.go @@ -201,7 +201,7 @@ func (d *document) DocumentLinkAt(pos protocol.Position) (*documentLink, error) func linkWithinInlineCode(strBuffer string, linkStart, linkEnd int, insideInline bool) bool { if backtickId := strings.Index(strBuffer, "`"); backtickId >= 0 && backtickId < linkEnd { return linkWithinInlineCode(strBuffer[backtickId+1:], - linkStart-backtickId-1, linkEnd-backtickId-1, !insideInline) + linkStart-backtickId-1, linkEnd-backtickId-1, !insideInline) } else { return insideInline } @@ -219,6 +219,38 @@ func (d *document) DocumentLinks() ([]documentLink, error) { lines := d.GetLines() for lineIndex, line := range lines { + // Checks to ignore lines within code fences and indented code blocks + if insideFenced { + if fencedEndRegex.FindStringIndex(line) != nil && + lines[currentCodeBlockStart][:3] == line[:3] { + // Fenced code block ends with this line + insideFenced = false + currentCodeBlockStart = -1 + } + continue + } else if insideIndented { + if indentedRegex.FindStringIndex(line) == nil && len(line) > 0 { + // Indeted code block ends with this line + insideIndented = false + currentCodeBlockStart = -1 + } else { + continue + } + } else { + // Check whether the current line is the start of a code fence or + // indented code block + if fencedStartRegex.FindStringIndex(line) != nil { + insideFenced = true + currentCodeBlockStart = lineIndex + continue + } else if indentedRegex.FindStringIndex(line) != nil && + (lineIndex > 0 && len(lines[lineIndex-1]) == 0 || lineIndex == 0) { + insideIndented = true + currentCodeBlockStart = lineIndex + continue + } + } + appendLink := func(href string, start, end int, hasTitle bool, isWikiLink bool) { if href == "" { return @@ -246,39 +278,6 @@ func (d *document) DocumentLinks() ([]documentLink, error) { }) } - if insideFenced { - if fencedEndRegex.FindStringIndex(line) != nil && - lines[currentCodeBlockStart][:3] == line[:3] { - // Fenced code block ending with this line - insideFenced = false - currentCodeBlockStart = -1 - } - // Within fenced code block, skip link checks - continue - } else if insideIndented { - if indentedRegex.FindStringIndex(line) == nil && len(line) > 0 { - // No longer indented, will process links - insideIndented = false - currentCodeBlockStart = -1 - } else { - // Still indented, skip link checks - continue - } - } else { - // Check whether we are in fenced / indented code check - if fencedStartRegex.FindStringIndex(line) != nil { - insideFenced = true - currentCodeBlockStart = lineIndex - continue - } else if indentedRegex.FindStringIndex(line) != nil && - (lineIndex > 0 && len(lines[lineIndex-1]) == 0 || lineIndex == 0) { - // Indented code block starts on this line - insideIndented = true - currentCodeBlockStart = lineIndex - continue - } - } - // extract link paths from [title](path) patterns // note: match[0:1] is the entire match, match[2:3] is the contents of // brackets, match[4:5] is contents of parentheses @@ -321,7 +320,9 @@ func (d *document) DocumentLinks() ([]documentLink, error) { hasTitle := match[4] != -1 appendLink(href, match[0], match[1], hasTitle, true) } - if strings.Count(line, "`") % 2 == 1 { + // if there are an odd number of back ticks, the state of insideInline + // for the following link will be true + if strings.Count(line, "`")%2 == 1 { insideInline = !insideInline } } From d363631d5a6b6cda6d54d4ccc99a5b133ad80c45 Mon Sep 17 00:00:00 2001 From: tjex Date: Fri, 5 Apr 2024 14:30:27 +1100 Subject: [PATCH 05/10] hard code insideInline boolean instead of inverting it --- internal/adapter/lsp/document.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/adapter/lsp/document.go b/internal/adapter/lsp/document.go index 784c4f81..e0df263c 100644 --- a/internal/adapter/lsp/document.go +++ b/internal/adapter/lsp/document.go @@ -323,7 +323,7 @@ func (d *document) DocumentLinks() ([]documentLink, error) { // if there are an odd number of back ticks, the state of insideInline // for the following link will be true if strings.Count(line, "`")%2 == 1 { - insideInline = !insideInline + insideInline = true } } From 4df45d26db7f152215f0c2bcd0dedefd9bc176ea Mon Sep 17 00:00:00 2001 From: tjex Date: Fri, 5 Apr 2024 17:58:27 +1100 Subject: [PATCH 06/10] Revert "hard code insideInline boolean instead of inverting it" This reverts commit d363631d5a6b6cda6d54d4ccc99a5b133ad80c45. --- internal/adapter/lsp/document.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/adapter/lsp/document.go b/internal/adapter/lsp/document.go index e0df263c..784c4f81 100644 --- a/internal/adapter/lsp/document.go +++ b/internal/adapter/lsp/document.go @@ -323,7 +323,7 @@ func (d *document) DocumentLinks() ([]documentLink, error) { // if there are an odd number of back ticks, the state of insideInline // for the following link will be true if strings.Count(line, "`")%2 == 1 { - insideInline = true + insideInline = !insideInline } } From 9161a5be4ae003c0f8344f834ba92801e21d94b1 Mon Sep 17 00:00:00 2001 From: tjex Date: Fri, 5 Apr 2024 18:00:24 +1100 Subject: [PATCH 07/10] remove my comment, code is clear enough --- internal/adapter/lsp/document.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/internal/adapter/lsp/document.go b/internal/adapter/lsp/document.go index 784c4f81..6cf6638d 100644 --- a/internal/adapter/lsp/document.go +++ b/internal/adapter/lsp/document.go @@ -320,9 +320,7 @@ func (d *document) DocumentLinks() ([]documentLink, error) { hasTitle := match[4] != -1 appendLink(href, match[0], match[1], hasTitle, true) } - // if there are an odd number of back ticks, the state of insideInline - // for the following link will be true - if strings.Count(line, "`")%2 == 1 { + if strings.Count(line, "`")%2 == 1 { insideInline = !insideInline } } From b68df7f87ea9c759bd8175f239755e0535c22dad Mon Sep 17 00:00:00 2001 From: tjex Date: Sat, 6 Apr 2024 09:12:59 +1100 Subject: [PATCH 08/10] wip: refactor if statement into function --- internal/adapter/lsp/document.go | 79 ++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 35 deletions(-) diff --git a/internal/adapter/lsp/document.go b/internal/adapter/lsp/document.go index 6cf6638d..ef227d51 100644 --- a/internal/adapter/lsp/document.go +++ b/internal/adapter/lsp/document.go @@ -167,6 +167,11 @@ var fencedStartRegex = regexp.MustCompile(`^(` + "```" + `|~~~).*`) var fencedEndRegex = regexp.MustCompile(`^(` + "```" + `|~~~)\s*`) var indentedRegex = regexp.MustCompile(`^(\s{4}|\t).+`) +var insideInline = false +var insideFenced = false +var insideIndented = false +var currentCodeBlockStart = -1 + // LinkFromRoot returns a Link to this document from the root of the given // notebook. func (d *document) LinkFromRoot(nb *core.Notebook) (*documentLink, error) { @@ -207,49 +212,53 @@ func linkWithinInlineCode(strBuffer string, linkStart, linkEnd int, insideInline } } +func ignoreLinesInCodeBlocks(lines []string, lineIndex int, line string) bool { + // Checks to ignore lines within code fences and indented code blocks + if insideFenced { + if fencedEndRegex.FindStringIndex(line) != nil && + lines[currentCodeBlockStart][:3] == line[:3] { + // Fenced code block ends with this line + insideFenced = false + currentCodeBlockStart = -1 + } + return true + } else if insideIndented { + if indentedRegex.FindStringIndex(line) == nil && len(line) > 0 { + // Indeted code block ends with this line + insideIndented = false + currentCodeBlockStart = -1 + } else { + return true + } + } else { + // Check whether the current line is the start of a code fence or + // indented code block + if fencedStartRegex.FindStringIndex(line) != nil { + insideFenced = true + currentCodeBlockStart = lineIndex + return true + } else if indentedRegex.FindStringIndex(line) != nil && + (lineIndex > 0 && len(lines[lineIndex-1]) == 0 || lineIndex == 0) { + insideIndented = true + currentCodeBlockStart = lineIndex + return true + } + } + return false + +} + // DocumentLinks returns all the internal and external links found in the // document. func (d *document) DocumentLinks() ([]documentLink, error) { links := []documentLink{} - insideInline := false - insideFenced := false - insideIndented := false - currentCodeBlockStart := -1 lines := d.GetLines() for lineIndex, line := range lines { - // Checks to ignore lines within code fences and indented code blocks - if insideFenced { - if fencedEndRegex.FindStringIndex(line) != nil && - lines[currentCodeBlockStart][:3] == line[:3] { - // Fenced code block ends with this line - insideFenced = false - currentCodeBlockStart = -1 - } - continue - } else if insideIndented { - if indentedRegex.FindStringIndex(line) == nil && len(line) > 0 { - // Indeted code block ends with this line - insideIndented = false - currentCodeBlockStart = -1 - } else { - continue - } - } else { - // Check whether the current line is the start of a code fence or - // indented code block - if fencedStartRegex.FindStringIndex(line) != nil { - insideFenced = true - currentCodeBlockStart = lineIndex - continue - } else if indentedRegex.FindStringIndex(line) != nil && - (lineIndex > 0 && len(lines[lineIndex-1]) == 0 || lineIndex == 0) { - insideIndented = true - currentCodeBlockStart = lineIndex - continue - } - } + if ignoreLinesInCodeBlocks(lines, lineIndex, line) { + continue + } appendLink := func(href string, start, end int, hasTitle bool, isWikiLink bool) { if href == "" { From ddc77c4bdf84664cba8b941b920898a1afbc542b Mon Sep 17 00:00:00 2001 From: tjex Date: Sat, 6 Apr 2024 14:29:09 +1100 Subject: [PATCH 09/10] refactor if checks into local function --- internal/adapter/lsp/document.go | 34 ++++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/internal/adapter/lsp/document.go b/internal/adapter/lsp/document.go index ef227d51..566f8fdd 100644 --- a/internal/adapter/lsp/document.go +++ b/internal/adapter/lsp/document.go @@ -160,18 +160,6 @@ func (d *document) LookForward(pos protocol.Position, length int) string { return string(utf16.Decode(utf16Bytes[charIdx:(charIdx + length)])) } -var wikiLinkRegex = regexp.MustCompile(`\[?\[\[(.+?)(?: *\| *(.+?))?\]\]`) -var markdownLinkRegex = regexp.MustCompile(`\[([^\]]+?[^\\])\]\((.+?[^\\])\)`) -var fileURIregex = regexp.MustCompile(`file:///`) -var fencedStartRegex = regexp.MustCompile(`^(` + "```" + `|~~~).*`) -var fencedEndRegex = regexp.MustCompile(`^(` + "```" + `|~~~)\s*`) -var indentedRegex = regexp.MustCompile(`^(\s{4}|\t).+`) - -var insideInline = false -var insideFenced = false -var insideIndented = false -var currentCodeBlockStart = -1 - // LinkFromRoot returns a Link to this document from the root of the given // notebook. func (d *document) LinkFromRoot(nb *core.Notebook) (*documentLink, error) { @@ -212,8 +200,20 @@ func linkWithinInlineCode(strBuffer string, linkStart, linkEnd int, insideInline } } -func ignoreLinesInCodeBlocks(lines []string, lineIndex int, line string) bool { - // Checks to ignore lines within code fences and indented code blocks +var wikiLinkRegex = regexp.MustCompile(`\[?\[\[(.+?)(?: *\| *(.+?))?\]\]`) +var markdownLinkRegex = regexp.MustCompile(`\[([^\]]+?[^\\])\]\((.+?[^\\])\)`) +var fileURIregex = regexp.MustCompile(`file:///`) +var fencedStartRegex = regexp.MustCompile(`^(` + "```" + `|~~~).*`) +var fencedEndRegex = regexp.MustCompile(`^(` + "```" + `|~~~)\s*`) +var indentedRegex = regexp.MustCompile(`^(\s{4}|\t).+`) + +var insideInline = false +var insideFenced = false +var insideIndented = false +var currentCodeBlockStart = -1 + +func isLineWithinCodeBlock(lines []string, lineIndex int, line string) bool { + // if line is already within code fences or indented code block if insideFenced { if fencedEndRegex.FindStringIndex(line) != nil && lines[currentCodeBlockStart][:3] == line[:3] { @@ -256,9 +256,9 @@ func (d *document) DocumentLinks() ([]documentLink, error) { lines := d.GetLines() for lineIndex, line := range lines { - if ignoreLinesInCodeBlocks(lines, lineIndex, line) { - continue - } + if isLineWithinCodeBlock(lines, lineIndex, line) { + continue + } appendLink := func(href string, start, end int, hasTitle bool, isWikiLink bool) { if href == "" { From ea2ca9e9cb318d20b07a7755ec1335660ae3d69a Mon Sep 17 00:00:00 2001 From: tjex Date: Sat, 6 Apr 2024 14:35:00 +1100 Subject: [PATCH 10/10] update code comment --- internal/adapter/lsp/document.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/adapter/lsp/document.go b/internal/adapter/lsp/document.go index 566f8fdd..f540b283 100644 --- a/internal/adapter/lsp/document.go +++ b/internal/adapter/lsp/document.go @@ -212,6 +212,8 @@ var insideFenced = false var insideIndented = false var currentCodeBlockStart = -1 +// check whether the current line in document is within a fenced or indented +// code block func isLineWithinCodeBlock(lines []string, lineIndex int, line string) bool { // if line is already within code fences or indented code block if insideFenced {