Skip to content

Commit

Permalink
Added initial LSP support.
Browse files Browse the repository at this point in the history
This pull-request, once complete, will had LSP support and
will close #108.

Currently we show help for standard library functions, when
the user hovers over a token.  We also provide completion for
all our standard-library functions.
  • Loading branch information
skx committed Nov 23, 2022
1 parent caf59f7 commit 35228f2
Show file tree
Hide file tree
Showing 4 changed files with 259 additions and 2 deletions.
25 changes: 25 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,28 @@
module github.com/skx/yal

go 1.18

require (
github.com/tliron/glsp v0.1.1
github.com/tliron/kutil v0.1.63
go.lsp.dev/uri v0.3.0
)

require (
github.com/gorilla/websocket v1.5.0 // indirect
github.com/iancoleman/strcase v0.2.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/muesli/termenv v0.12.0 // indirect
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/sasha-s/go-deadlock v0.3.1 // indirect
github.com/sourcegraph/jsonrpc2 v0.1.0 // indirect
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect
)
49 changes: 49 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0=
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/muesli/termenv v0.12.0 h1:KuQRUE3PgxRFWhq4gHvZtPSLCGDqM5q/cYr1pZ39ytc=
github.com/muesli/termenv v0.12.0/go.mod h1:WCCv32tusQ/EEZ5S8oUIIrC/nIuBcxCVqlN4Xfkv+7A=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0=
github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM=
github.com/sourcegraph/jsonrpc2 v0.1.0 h1:ohJHjZ+PcaLxDUjqk2NC3tIGsVa5bXThe1ZheSXOjuk=
github.com/sourcegraph/jsonrpc2 v0.1.0/go.mod h1:ZafdZgk/axhT1cvZAPOhw+95nz2I/Ra5qMlU4gTRwIo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tliron/glsp v0.1.1 h1:GNNgUX9p1Q9MoPQooJoZ0+WaLL03EkhcKZUYJAtiNqs=
github.com/tliron/glsp v0.1.1/go.mod h1:RVyVKeY3U+Nlc3DRklUiaegNsQyjzNTEool6YWh1v7g=
github.com/tliron/kutil v0.1.63 h1:/xOqEShxPymwhcVcPFAks8zj43HU+NljbmzYjNXIO+Y=
github.com/tliron/kutil v0.1.63/go.mod h1:Mo1pAtg/9yG3ClnUv32Hrl+t0BFFCg49RpCjHG3sY7c=
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
go.lsp.dev/uri v0.3.0 h1:KcZJmh6nFIBeJzTugn5JTU6OOyG0lDOo3R9KwTxTYbo=
go.lsp.dev/uri v0.3.0/go.mod h1:P5sbO1IQR+qySTWOCnhnK7phBx+W3zbLqSMDJNTw88I=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
178 changes: 178 additions & 0 deletions lsp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
// This file provides our LSP support.
//
// When yal is invoked with the "-lsp" flag we call lspStart(),
// which provides simple completion and hover support.
//

package main

import (
"fmt"
"os"
"sort"

"github.com/skx/yal/primitive"
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
"github.com/tliron/glsp/server"
"github.com/tliron/kutil/logging"
"go.lsp.dev/uri"

// Must include a backend implementation. See kutil's logging/ for other options.
_ "github.com/tliron/kutil/logging/simple"
)

// lsName contains the name of our LSP handler
const lsName = "yal"

// handler contains the pointer to our handler
var handler protocol.Handler

// initialize is called to setup a new buffer.
func initialize(context *glsp.Context, params *protocol.InitializeParams) (any, error) {
capabilities := handler.CreateServerCapabilities()

return protocol.InitializeResult{
Capabilities: capabilities,
ServerInfo: &protocol.InitializeResultServerInfo{
Name: "yal",
Version: &version,
},
}, nil
}

func lspStart() {
logging.Configure(1, nil)

handler = protocol.Handler{
// generic
Initialize: initialize,

// Yal Specific
TextDocumentCompletion: textDocumentCompletion,
TextDocumentHover: textDocumentHover,
}

server := server.NewServer(&handler, lsName, false)

server.RunStdio()
}

// textDocumentCompletion should return appropriate completions.
//
// However we just always return a list of all known functions, the
// client can sort it out.
func textDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (interface{}, error) {

// Build up a list of all things known in the environment
keys := []string{}

// Save the known "things", because we want show them in sorted-order.
items := ENV.Items()
for k := range items {
keys = append(keys, k)
}

// sort the known-things (i.e. environment keys)
sort.Strings(keys)

// Create the return value
out := make([]protocol.CompletionItem, len(keys))

// The kind of completion we have
kind := protocol.CompletionItemKindFunction

// Now we have a list of sorted things.
for i, key := range keys {

out[i] = protocol.CompletionItem{
Label: key,
Kind: &kind,
Detail: &key,
}
}
return out, nil
}

// textDocumentHover is called when the client hovers over a token
//
// We need to find out what text is being hovered over, and return
// something "useful" to the client.
func textDocumentHover(context *glsp.Context, params *protocol.HoverParams) (*protocol.Hover, error) {

// Get the file
_uri, err := uri.Parse(params.TextDocument.URI)
if err != nil {
return nil, err
}

// open the file
var content []byte

// read the content
content, err = os.ReadFile(_uri.Filename())
if err != nil {
return nil, err
}

// We'll build up the current line, being hovered on here
var curLine uint32
line := ""

// count the newlines to get the current line.
for _, chr := range content {
if chr == '\n' {
curLine++
continue
}
if curLine == params.Position.Line {
line += string(chr)
}
}

// current line is empty? Then abort
if line == "" {
return nil, nil
}

// Right now we have the line we want the token
//
// Assume we have a line like "(this is (cake))"
// and position points to the "c" we want to have the
// whole token
//
token := ""

for i, chr := range line {
if chr == rune(' ') || chr == rune('(') || chr == rune(')') || chr == rune('\t') {
if uint32(i) > params.Position.Character {
break
}
token = ""
continue
}
token += string(chr)
}

// Find the details of the function, if we can
info, ok := ENV.Get(token)
if !ok {
return nil, nil
}

// Is it a procedure?
prc, ok2 := info.(*primitive.Procedure)
if !ok2 {
return nil, nil
}

// The text we'll show.
help := fmt.Sprintf("**%s**\n%s", token, prc.Help)

return &protocol.Hover{
Contents: protocol.MarkupContent{
Kind: protocol.MarkupKindMarkdown,
Value: help,
},
}, nil
}
9 changes: 7 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
// All the logic is contained within the `main` function, and it merely
// reads the contents of the user-supplied filename, prepends the standard
// library to that content, and executes it.
//
// Notably we don't contain a REPL-mode at the moment.
package main

import (
Expand Down Expand Up @@ -189,6 +187,7 @@ func main() {
// Parse our command-line flags
exp := flag.String("e", "", "A string to evaluate.")
hlp := flag.Bool("h", false, "Show help information and exit.")
lsp := flag.Bool("lsp", false, "Launch the LSP mode")
ver := flag.Bool("v", false, "Show our version and exit.")
flag.Parse()

Expand All @@ -213,6 +212,12 @@ func main() {
//
create()

// LSP?
if *lsp {
lspStart()
return
}

// showing the help?
if *hlp {
help(flag.Args())
Expand Down

0 comments on commit 35228f2

Please sign in to comment.