mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-29 14:22:42 +09:00
Basic communication with lsp server
This commit is contained in:
212
internal/lsp/server.go
Normal file
212
internal/lsp/server.go
Normal file
@@ -0,0 +1,212 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user