Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proof of concept LSP server #21

Merged
merged 10 commits into from
Apr 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions cmd/container.go → adapter/container.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package adapter

import (
"io"
Expand All @@ -22,6 +22,7 @@ import (
)

type Container struct {
Version string
Config zk.Config
Date date.Provider
Logger util.Logger
Expand All @@ -32,7 +33,7 @@ type Container struct {
zkErr error
}

func NewContainer() (*Container, error) {
func NewContainer(version string) (*Container, error) {
wrap := errors.Wrapper("initialization")

config := zk.NewDefaultConfig()
Expand All @@ -52,7 +53,8 @@ func NewContainer() (*Container, error) {
date := date.NewFrozenNow()

return &Container{
Config: config,
Version: version,
Config: config,
// zk is short-lived, so we freeze the current date to use the same
// date for any template rendering during the execution.
Date: &date,
Expand Down
152 changes: 152 additions & 0 deletions adapter/lsp/document.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package lsp

import (
"regexp"
"strings"

protocol "github.com/tliron/glsp/protocol_3_16"
"github.com/tliron/kutil/logging"
)

// document represents an opened file.
type document struct {
URI protocol.DocumentUri
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]
}

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]]
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
}
Loading