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 }