Files
zyedidia.micro/internal/lsp/server.go
2020-08-12 16:03:23 -04:00

213 lines
4.5 KiB
Go

package lsp
import (
"bufio"
"encoding/json"
"fmt"
"io"
"log"
"os"
"os/exec"
"strconv"
"strings"
"github.com/sourcegraph/go-lsp"
"github.com/zyedidia/micro/v2/internal/util"
)
var ActiveServers map[string]*Server
func init() {
ActiveServers = make(map[string]*Server)
}
type Server struct {
cmd *exec.Cmd
stdin io.WriteCloser
stdout *bufio.Reader
language *Language
capabilities lsp.ServerCapabilities
}
type RPCMessage struct {
RPCVersion string `json:"jsonrpc"`
ID int `json:"id"`
Method string `json:"method"`
Params interface{} `json:"params"`
}
type RPCInit struct {
RPCVersion string `json:"jsonrpc"`
ID int `json:"id"`
Result lsp.InitializeResult `json:"result"`
}
func StartServer(l Language) (*Server, error) {
c := exec.Command(l.Command, l.Args...)
log.Println("Running", l.Command, l.Args)
stdin, err := c.StdinPipe()
if err != nil {
return nil, err
}
stdout, err := c.StdoutPipe()
if err != nil {
return nil, err
}
err = c.Start()
if err != nil {
return nil, err
}
s := new(Server)
s.cmd = c
s.stdin = stdin
s.stdout = bufio.NewReader(stdout)
s.language = &l
// ActiveServers[l.Command] = s
return s, nil
}
func (s *Server) Initialize(directory string) error {
params := lsp.InitializeParams{
ProcessID: os.Getpid(),
RootURI: lsp.DocumentURI("file://" + directory),
ClientInfo: lsp.ClientInfo{
Name: "micro",
Version: util.Version,
},
Trace: "off",
Capabilities: lsp.ClientCapabilities{
Workspace: lsp.WorkspaceClientCapabilities{
WorkspaceEdit: struct {
DocumentChanges bool `json:"documentChanges,omitempty"`
ResourceOperations []string `json:"resourceOperations,omitempty"`
}{
DocumentChanges: true,
ResourceOperations: []string{"create", "rename", "delete"},
},
ApplyEdit: true,
},
TextDocument: lsp.TextDocumentClientCapabilities{
Formatting: &struct {
DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
}{
DynamicRegistration: true,
},
Completion: struct {
CompletionItem struct {
DocumentationFormat []lsp.DocumentationFormat `json:"documentationFormat,omitempty"`
SnippetSupport bool `json:"snippetSupport,omitempty"`
} `json:"completionItem,omitempty"`
CompletionItemKind struct {
ValueSet []lsp.CompletionItemKind `json:"valueSet,omitempty"`
} `json:"completionItemKind,omitempty"`
ContextSupport bool `json:"contextSupport,omitempty"`
}{
CompletionItem: struct {
DocumentationFormat []lsp.DocumentationFormat `json:"documentationFormat,omitempty"`
SnippetSupport bool `json:"snippetSupport,omitempty"`
}{
DocumentationFormat: []lsp.DocumentationFormat{lsp.DFPlainText},
SnippetSupport: false,
},
ContextSupport: false,
},
},
Window: lsp.WindowClientCapabilities{
WorkDoneProgress: false,
},
Experimental: nil,
},
}
resp, err := s.SendMessage("initialize", params)
if err != nil {
return err
}
// todo parse capabilities
log.Println("Received", string(resp))
var r RPCInit
err = json.Unmarshal(resp, &r)
if err != nil {
return err
}
fmt.Println(r)
s.capabilities = r.Result.Capabilities
fmt.Println(s.capabilities)
_, err = s.SendMessage("initialized", struct{}{})
if err != nil {
return err
}
return nil
}
func (s *Server) SendMessage(method string, params interface{}) ([]byte, error) {
m := RPCMessage{
RPCVersion: "2.0",
ID: os.Getpid(),
Method: method,
Params: params,
}
msg, err := json.Marshal(m)
if err != nil {
return nil, err
}
msg = append(msg, '\r', '\n')
header := []byte("Content-Length: " + strconv.Itoa(len(msg)) + "\r\n\r\n")
msg = append(header, msg...)
log.Println("Sending", string(msg))
s.stdin.Write(msg)
n := -1
for {
b, err := s.stdout.ReadBytes('\n')
if err != nil {
return nil, err
}
headerline := strings.TrimSpace(string(b))
if len(headerline) == 0 {
break
}
if strings.HasPrefix(headerline, "Content-Length:") {
split := strings.Split(headerline, ":")
if len(split) <= 1 {
break
}
n, err = strconv.Atoi(strings.TrimSpace(split[1]))
if err != nil {
return nil, err
}
}
}
if n <= 0 {
return []byte{}, nil
}
bytes := make([]byte, n)
_, err = s.stdout.Read(bytes)
if err != nil {
return nil, err
}
return bytes, nil
}