You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
zk/internal/adapter/lsp/document.go

173 lines
4.2 KiB
Go

package lsp
import (
"net/url"
"regexp"
"strings"
protocol "github.com/tliron/glsp/protocol_3_16"
"github.com/tliron/kutil/logging"
)
// document represents an opened file.
type document struct {
Path string
Content string
Log logging.Logger
lines []string
}
// ApplyChanges updates the content of the document from LSP textDocument/didChange events.
func (d *document) ApplyChanges(changes []interface{}) {
for _, change := range changes {
switch c := change.(type) {
case protocol.TextDocumentContentChangeEvent:
startIndex, endIndex := c.Range.IndexesIn(d.Content)
d.Content = d.Content[:startIndex] + c.Text + d.Content[endIndex:]
case protocol.TextDocumentContentChangeEventWhole:
d.Content = c.Text
}
}
d.lines = nil
}
var nonEmptyString = regexp.MustCompile(`\S+`)
// WordAt returns the word found at the given location.
// Credit https://github.com/aca/neuron-language-server/blob/450a7cff71c14e291ee85ff8a0614fa9d4dd5145/utils.go#L13
func (d *document) WordAt(pos protocol.Position) string {
line, ok := d.GetLine(int(pos.Line))
if !ok {
return ""
}
charIdx := int(pos.Character)
wordIdxs := nonEmptyString.FindAllStringIndex(line, -1)
for _, wordIdx := range wordIdxs {
if wordIdx[0] <= charIdx && charIdx <= wordIdx[1] {
return line[wordIdx[0]:wordIdx[1]]
}
}
return ""
}
// GetLine returns the line at the given index.
func (d *document) GetLine(index int) (string, bool) {
lines := d.GetLines()
if index < 0 || index > len(lines) {
return "", false
}
return lines[index], true
}
// GetLines returns all the lines in the document.
func (d *document) GetLines() []string {
if d.lines == nil {
// We keep \r on purpose, to avoid messing up position conversions.
d.lines = strings.Split(d.Content, "\n")
}
return d.lines
}
// LookBehind returns the n characters before the given position, on the same line.
func (d *document) LookBehind(pos protocol.Position, length int) string {
line, ok := d.GetLine(int(pos.Line))
if !ok {
return ""
}
charIdx := int(pos.Character)
if length > charIdx {
return line[0:charIdx]
}
return line[(charIdx - length):charIdx]
}
// LookForward returns the n characters after the given position, on the same line.
func (d *document) LookForward(pos protocol.Position, length int) string {
line, ok := d.GetLine(int(pos.Line))
if !ok {
return ""
}
lineLength := len(line)
charIdx := int(pos.Character)
if lineLength <= charIdx+length {
return line[charIdx:]
}
return line[charIdx:(charIdx + length)]
}
var wikiLinkRegex = regexp.MustCompile(`\[?\[\[(.+?)(?:\|(.+?))?\]\]`)
var markdownLinkRegex = regexp.MustCompile(`\[([^\]]+?[^\\])\]\((.+?[^\\])\)`)
// DocumentLinkAt returns the internal or external link found in the document
// at the given position.
func (d *document) DocumentLinkAt(pos protocol.Position) (*documentLink, error) {
links, err := d.DocumentLinks()
if err != nil {
return nil, err
}
for _, link := range links {
if positionInRange(d.Content, link.Range, pos) {
return &link, nil
}
}
return nil, nil
}
// DocumentLinks returns all the internal and external links found in the
// document.
func (d *document) DocumentLinks() ([]documentLink, error) {
links := []documentLink{}
lines := d.GetLines()
for lineIndex, line := range lines {
appendLink := func(href string, start, end int) {
if href == "" {
return
}
links = append(links, documentLink{
Href: href,
Range: protocol.Range{
Start: protocol.Position{
Line: protocol.UInteger(lineIndex),
Character: protocol.UInteger(start),
},
End: protocol.Position{
Line: protocol.UInteger(lineIndex),
Character: protocol.UInteger(end),
},
},
})
}
for _, match := range markdownLinkRegex.FindAllStringSubmatchIndex(line, -1) {
href := line[match[4]:match[5]]
// Valid Markdown links are percent-encoded.
if decodedHref, err := url.PathUnescape(href); err == nil {
href = decodedHref
}
appendLink(href, match[0], match[1])
}
for _, match := range wikiLinkRegex.FindAllStringSubmatchIndex(line, -1) {
href := line[match[2]:match[3]]
appendLink(href, match[0], match[1])
}
}
return links, nil
}
type documentLink struct {
Href string
Range protocol.Range
}