|
@@ -7,6 +7,9 @@ import (
|
|
|
lsp "github.com/sourcegraph/go-lsp"
|
|
|
"github.com/sourcegraph/jsonrpc2"
|
|
|
"src.elv.sh/pkg/diag"
|
|
|
+ "src.elv.sh/pkg/edit"
|
|
|
+ "src.elv.sh/pkg/edit/complete"
|
|
|
+ "src.elv.sh/pkg/eval"
|
|
|
"src.elv.sh/pkg/parse"
|
|
|
)
|
|
|
|
|
@@ -17,30 +20,21 @@ var (
|
|
|
Code: jsonrpc2.CodeInvalidParams, Message: "invalid params"}
|
|
|
)
|
|
|
|
|
|
-func routingHandler(methods map[string]method) jsonrpc2.Handler {
|
|
|
- return jsonrpc2.HandlerWithError(func(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) (interface{}, error) {
|
|
|
- fn, ok := methods[req.Method]
|
|
|
- if !ok {
|
|
|
- return nil, errMethodNotFound
|
|
|
- }
|
|
|
- return fn(ctx, conn, *req.Params)
|
|
|
- })
|
|
|
-}
|
|
|
-
|
|
|
-type method func(context.Context, jsonrpc2.JSONRPC2, json.RawMessage) (interface{}, error)
|
|
|
-
|
|
|
-func noop(_ context.Context, _ jsonrpc2.JSONRPC2, _ json.RawMessage) (interface{}, error) {
|
|
|
- return nil, nil
|
|
|
+type server struct {
|
|
|
+ evaler complete.PureEvaler
|
|
|
+ content map[lsp.DocumentURI]string
|
|
|
}
|
|
|
|
|
|
-type server struct {
|
|
|
+func newServer() *server {
|
|
|
+ return &server{edit.PureEvaler(eval.NewEvaler()), make(map[lsp.DocumentURI]string)}
|
|
|
}
|
|
|
|
|
|
-func (s *server) handler() jsonrpc2.Handler {
|
|
|
+func handler(s *server) jsonrpc2.Handler {
|
|
|
return routingHandler(map[string]method{
|
|
|
- "initialize": s.initialize,
|
|
|
- "textDocument/didOpen": s.didOpen,
|
|
|
- "textDocument/didChange": s.didChange,
|
|
|
+ "initialize": s.initialize,
|
|
|
+ "textDocument/didOpen": s.didOpen,
|
|
|
+ "textDocument/didChange": s.didChange,
|
|
|
+ "textDocument/completion": s.completion,
|
|
|
|
|
|
"textDocument/didClose": noop,
|
|
|
// Required by spec.
|
|
@@ -51,6 +45,24 @@ func (s *server) handler() jsonrpc2.Handler {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+type method func(context.Context, jsonrpc2.JSONRPC2, json.RawMessage) (interface{}, error)
|
|
|
+
|
|
|
+func noop(_ context.Context, _ jsonrpc2.JSONRPC2, _ json.RawMessage) (interface{}, error) {
|
|
|
+ return nil, nil
|
|
|
+}
|
|
|
+
|
|
|
+func routingHandler(methods map[string]method) jsonrpc2.Handler {
|
|
|
+ return jsonrpc2.HandlerWithError(func(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) (interface{}, error) {
|
|
|
+ fn, ok := methods[req.Method]
|
|
|
+ if !ok {
|
|
|
+ return nil, errMethodNotFound
|
|
|
+ }
|
|
|
+ return fn(ctx, conn, *req.Params)
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// Handler implementations. These are all called synchronously.
|
|
|
+
|
|
|
func (s *server) initialize(_ context.Context, _ jsonrpc2.JSONRPC2, _ json.RawMessage) (interface{}, error) {
|
|
|
return &lsp.InitializeResult{
|
|
|
Capabilities: lsp.ServerCapabilities{
|
|
@@ -71,7 +83,8 @@ func (s *server) didOpen(ctx context.Context, conn jsonrpc2.JSONRPC2, rawParams
|
|
|
}
|
|
|
|
|
|
uri, content := params.TextDocument.URI, params.TextDocument.Text
|
|
|
- go update(ctx, conn, uri, content)
|
|
|
+ s.content[uri] = content
|
|
|
+ go publishDiagnostics(ctx, conn, uri, content)
|
|
|
return nil, nil
|
|
|
}
|
|
|
|
|
@@ -81,18 +94,63 @@ func (s *server) didChange(ctx context.Context, conn jsonrpc2.JSONRPC2, rawParam
|
|
|
return nil, errInvalidParams
|
|
|
}
|
|
|
|
|
|
+ // ContentChanges includes full text since the server is only advertised to
|
|
|
+ // support that; see the initialize method.
|
|
|
uri, content := params.TextDocument.URI, params.ContentChanges[0].Text
|
|
|
- go update(ctx, conn, uri, content)
|
|
|
+ s.content[uri] = content
|
|
|
+ go publishDiagnostics(ctx, conn, uri, content)
|
|
|
return nil, nil
|
|
|
}
|
|
|
|
|
|
-func update(ctx context.Context, conn jsonrpc2.JSONRPC2, uri lsp.DocumentURI, content string) {
|
|
|
+func (s *server) completion(ctx context.Context, conn jsonrpc2.JSONRPC2, rawParams json.RawMessage) (interface{}, error) {
|
|
|
+ var params lsp.CompletionParams
|
|
|
+ if json.Unmarshal(rawParams, ¶ms) != nil {
|
|
|
+ return nil, errInvalidParams
|
|
|
+ }
|
|
|
+
|
|
|
+ content := s.content[params.TextDocument.URI]
|
|
|
+ result, err := complete.Complete(
|
|
|
+ complete.CodeBuffer{
|
|
|
+ Content: content,
|
|
|
+ Dot: lspPositionToIdx(content, params.Position)},
|
|
|
+ complete.Config{PureEvaler: s.evaler},
|
|
|
+ )
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return []lsp.CompletionItem{}, nil
|
|
|
+ }
|
|
|
+
|
|
|
+ lspItems := make([]lsp.CompletionItem, len(result.Items))
|
|
|
+ lspRange := lspRangeFromRange(content, result.Replace)
|
|
|
+ var kind lsp.CompletionItemKind
|
|
|
+ switch result.Name {
|
|
|
+ case "command":
|
|
|
+ kind = lsp.CIKFunction
|
|
|
+ case "variable":
|
|
|
+ kind = lsp.CIKVariable
|
|
|
+ default:
|
|
|
+ // TODO: Support more values of kind
|
|
|
+ }
|
|
|
+ for i, item := range result.Items {
|
|
|
+ lspItems[i] = lsp.CompletionItem{
|
|
|
+ Label: item.ToInsert,
|
|
|
+ Kind: kind,
|
|
|
+ TextEdit: &lsp.TextEdit{
|
|
|
+ Range: lspRange,
|
|
|
+ NewText: item.ToInsert,
|
|
|
+ },
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return lspItems, nil
|
|
|
+}
|
|
|
+
|
|
|
+func publishDiagnostics(ctx context.Context, conn jsonrpc2.JSONRPC2, uri lsp.DocumentURI, content string) {
|
|
|
conn.Notify(ctx, "textDocument/publishDiagnostics",
|
|
|
lsp.PublishDiagnosticsParams{URI: uri, Diagnostics: diagnostics(uri, content)})
|
|
|
}
|
|
|
|
|
|
-func diagnostics(fileURI lsp.DocumentURI, content string) []lsp.Diagnostic {
|
|
|
- _, err := parse.Parse(parse.Source{Name: string(fileURI), Code: content}, parse.Config{})
|
|
|
+func diagnostics(uri lsp.DocumentURI, content string) []lsp.Diagnostic {
|
|
|
+ _, err := parse.Parse(parse.Source{Name: string(uri), Code: content}, parse.Config{})
|
|
|
if err == nil {
|
|
|
return []lsp.Diagnostic{}
|
|
|
}
|
|
@@ -101,7 +159,7 @@ func diagnostics(fileURI lsp.DocumentURI, content string) []lsp.Diagnostic {
|
|
|
diags := make([]lsp.Diagnostic, len(entries))
|
|
|
for i, err := range entries {
|
|
|
diags[i] = lsp.Diagnostic{
|
|
|
- Range: rangeToLSP(content, err),
|
|
|
+ Range: lspRangeFromRange(content, err),
|
|
|
Severity: lsp.Error,
|
|
|
Source: "parse",
|
|
|
Message: err.Message,
|
|
@@ -110,41 +168,60 @@ func diagnostics(fileURI lsp.DocumentURI, content string) []lsp.Diagnostic {
|
|
|
return diags
|
|
|
}
|
|
|
|
|
|
-func rangeToLSP(s string, r diag.Ranger) lsp.Range {
|
|
|
+func lspRangeFromRange(s string, r diag.Ranger) lsp.Range {
|
|
|
rg := r.Range()
|
|
|
return lsp.Range{
|
|
|
- Start: position(s, rg.From),
|
|
|
- End: position(s, rg.To),
|
|
|
+ Start: lspPositionFromIdx(s, rg.From),
|
|
|
+ End: lspPositionFromIdx(s, rg.To),
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-func position(s string, idx int) lsp.Position {
|
|
|
+func lspPositionToIdx(s string, pos lsp.Position) int {
|
|
|
+ var idx int
|
|
|
+ walkString(s, func(i int, p lsp.Position) bool {
|
|
|
+ idx = i
|
|
|
+ return p.Line < pos.Line || (p.Line == pos.Line && p.Character < pos.Character)
|
|
|
+ })
|
|
|
+ return idx
|
|
|
+}
|
|
|
+
|
|
|
+func lspPositionFromIdx(s string, idx int) lsp.Position {
|
|
|
var pos lsp.Position
|
|
|
+ walkString(s, func(i int, p lsp.Position) bool {
|
|
|
+ pos = p
|
|
|
+ return i < idx
|
|
|
+ })
|
|
|
+ return pos
|
|
|
+}
|
|
|
+
|
|
|
+// Generates (index, lspPosition) pairs in s, stopping if f returns false.
|
|
|
+func walkString(s string, f func(i int, p lsp.Position) bool) {
|
|
|
+ var p lsp.Position
|
|
|
lastCR := false
|
|
|
|
|
|
for i, r := range s {
|
|
|
- if i == idx {
|
|
|
- return pos
|
|
|
+ if !f(i, p) {
|
|
|
+ return
|
|
|
}
|
|
|
switch {
|
|
|
case r == '\r':
|
|
|
- pos.Line++
|
|
|
- pos.Character = 0
|
|
|
+ p.Line++
|
|
|
+ p.Character = 0
|
|
|
case r == '\n':
|
|
|
if lastCR {
|
|
|
// Ignore \n if it's part of a \r\n sequence
|
|
|
} else {
|
|
|
- pos.Line++
|
|
|
- pos.Character = 0
|
|
|
+ p.Line++
|
|
|
+ p.Character = 0
|
|
|
}
|
|
|
case r <= 0xFFFF:
|
|
|
// Encoded in UTF-16 with one unit
|
|
|
- pos.Character++
|
|
|
+ p.Character++
|
|
|
default:
|
|
|
// Encoded in UTF-16 with two units
|
|
|
- pos.Character += 2
|
|
|
+ p.Character += 2
|
|
|
}
|
|
|
lastCR = r == '\r'
|
|
|
}
|
|
|
- return pos
|
|
|
+ f(len(s), p)
|
|
|
}
|