diff --git a/internal/buffer/autocomplete.go b/internal/buffer/autocomplete.go index d660c0b2..f32dd337 100644 --- a/internal/buffer/autocomplete.go +++ b/internal/buffer/autocomplete.go @@ -223,14 +223,17 @@ func LSPComplete(b *Buffer) ([]string, []string) { } suggestions := make([]string, len(items)) + completions := make([]string, len(items)) for i, item := range items { suggestions[i] = item.Label - } - - completions := make([]string, len(suggestions)) - for i := range suggestions { - completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart) + if len(item.TextEdit.NewText) > 0 { + completions[i] = util.SliceEndStr(item.TextEdit.NewText, c.X-argstart) + } else if len(item.InsertText) > 0 { + completions[i] = util.SliceEndStr(item.InsertText, c.X-argstart) + } else { + completions[i] = util.SliceEndStr(item.Label, c.X-argstart) + } } return completions, suggestions diff --git a/internal/lsp/notifications.go b/internal/lsp/notifications.go new file mode 100644 index 00000000..f4005642 --- /dev/null +++ b/internal/lsp/notifications.go @@ -0,0 +1,55 @@ +package lsp + +import "github.com/sourcegraph/go-lsp" + +func (s *Server) DidOpen(filename, language, text string, version int) { + doc := lsp.TextDocumentItem{ + URI: lsp.DocumentURI("file://" + filename), + LanguageID: language, + Version: version, + Text: text, + } + + params := lsp.DidOpenTextDocumentParams{ + TextDocument: doc, + } + + s.sendNotification("textDocument/didOpen", params) +} + +func (s *Server) DidSave(filename string) { + doc := lsp.TextDocumentIdentifier{ + URI: lsp.DocumentURI("file://" + filename), + } + + params := lsp.DidSaveTextDocumentParams{ + TextDocument: doc, + } + s.sendNotification("textDocument/didSave", params) +} + +func (s *Server) DidChange(filename string, version int, changes []lsp.TextDocumentContentChangeEvent) { + doc := lsp.VersionedTextDocumentIdentifier{ + TextDocumentIdentifier: lsp.TextDocumentIdentifier{ + URI: lsp.DocumentURI("file://" + filename), + }, + Version: version, + } + + params := lsp.DidChangeTextDocumentParams{ + TextDocument: doc, + ContentChanges: changes, + } + s.sendNotification("textDocument/didChange", params) +} + +func (s *Server) DidClose(filename string) { + doc := lsp.TextDocumentIdentifier{ + URI: lsp.DocumentURI("file://" + filename), + } + + params := lsp.DidCloseTextDocumentParams{ + TextDocument: doc, + } + s.sendNotification("textDocument/didClose", params) +} diff --git a/internal/lsp/requests.go b/internal/lsp/requests.go index 3f756b61..80137a7b 100644 --- a/internal/lsp/requests.go +++ b/internal/lsp/requests.go @@ -12,58 +12,6 @@ type RPCCompletion struct { Result lsp.CompletionList `json:"result"` } -func (s *Server) DidOpen(filename, language, text string, version int) { - doc := lsp.TextDocumentItem{ - URI: lsp.DocumentURI("file://" + filename), - LanguageID: language, - Version: version, - Text: text, - } - - params := lsp.DidOpenTextDocumentParams{ - TextDocument: doc, - } - - s.SendMessage("textDocument/didOpen", params) -} - -func (s *Server) DidSave(filename string) { - doc := lsp.TextDocumentIdentifier{ - URI: lsp.DocumentURI("file://" + filename), - } - - params := lsp.DidSaveTextDocumentParams{ - TextDocument: doc, - } - s.SendMessage("textDocument/didSave", params) -} - -func (s *Server) DidChange(filename string, version int, changes []lsp.TextDocumentContentChangeEvent) { - doc := lsp.VersionedTextDocumentIdentifier{ - TextDocumentIdentifier: lsp.TextDocumentIdentifier{ - URI: lsp.DocumentURI("file://" + filename), - }, - Version: version, - } - - params := lsp.DidChangeTextDocumentParams{ - TextDocument: doc, - ContentChanges: changes, - } - s.SendMessage("textDocument/didChange", params) -} - -func (s *Server) DidClose(filename string) { - doc := lsp.TextDocumentIdentifier{ - URI: lsp.DocumentURI("file://" + filename), - } - - params := lsp.DidCloseTextDocumentParams{ - TextDocument: doc, - } - s.SendMessage("textDocument/didClose", params) -} - func (s *Server) DocumentFormat() { } @@ -88,7 +36,7 @@ func (s *Server) Completion(filename string, pos lsp.Position) ([]lsp.Completion TextDocumentPositionParams: docpos, Context: cc, } - resp, err := s.SendMessageGetResponse("textDocument/completion", params) + resp, err := s.sendRequest("textDocument/completion", params) if err != nil { return nil, err } diff --git a/internal/lsp/server.go b/internal/lsp/server.go index f3719023..1f7ce92b 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -35,13 +35,19 @@ type Server struct { responses map[int]chan ([]byte) } -type RPCMessage struct { +type RPCRequest struct { RPCVersion string `json:"jsonrpc"` ID int `json:"id"` Method string `json:"method"` Params interface{} `json:"params"` } +type RPCNotification struct { + RPCVersion string `json:"jsonrpc"` + Method string `json:"method"` + Params interface{} `json:"params"` +} + type RPCInit struct { RPCVersion string `json:"jsonrpc"` ID int `json:"id"` @@ -57,20 +63,23 @@ type RPCResult struct { func StartServer(l Language) (*Server, error) { c := exec.Command(l.Command, l.Args...) - log.Println("Running", l.Command, l.Args) + c.Stderr = log.Writer() stdin, err := c.StdinPipe() if err != nil { + log.Println("[micro-lsp]", err) return nil, err } stdout, err := c.StdoutPipe() if err != nil { + log.Println("[micro-lsp]", err) return nil, err } err = c.Start() if err != nil { + log.Println("[micro-lsp]", err) return nil, err } @@ -143,56 +152,46 @@ func (s *Server) Initialize(directory string) { }, } - err := s.SendMessage("initialize", params) - if err != nil { - return - } + activeServers[s.language.Command+"-"+directory] = s + s.active = true - resp, err := s.receiveMessage() + go s.receive() + + resp, err := s.sendRequest("initialize", params) if err != nil { + log.Println("[micro-lsp]", err) return } // todo parse capabilities - log.Println("Received", string(resp)) + log.Println("[micro-lsp] <<<", string(resp)) var r RPCInit - err = json.Unmarshal(resp, &r) + json.Unmarshal(resp, &r) + + err = s.sendNotification("initialized", struct{}{}) if err != nil { + log.Println("[micro-lsp]", err) return } - err = s.SendMessage("initialized", struct{}{}) - if err != nil { - return - } - - slock.Lock() - activeServers[s.language.Command+"-"+directory] = s - slock.Unlock() - - s.lock.Lock() s.capabilities = r.Result.Capabilities s.root = directory - s.active = true - s.lock.Unlock() - - go s.receive() } func (s *Server) receive() { for s.active { resp, err := s.receiveMessage() if err != nil { - log.Println(err) + log.Println("[micro-lsp]", err) continue } - log.Println("Received", string(resp)) + log.Println("[micro-lsp] <<<", string(resp)) var r RPCResult err = json.Unmarshal(resp, &r) if err != nil { - log.Println(err) + log.Println("[micro-lsp]", err) continue } @@ -213,13 +212,11 @@ func (s *Server) receive() { func (s *Server) receiveMessage() ([]byte, error) { n := -1 for { - log.Println("waiting for header") b, err := s.stdout.ReadBytes('\n') if err != nil { return nil, err } headerline := strings.TrimSpace(string(b)) - log.Println("Read header", headerline) if len(headerline) == 0 { break } @@ -239,21 +236,38 @@ func (s *Server) receiveMessage() ([]byte, error) { return []byte{}, nil } - log.Println("CONTENT-LENGTH:", n) - bytes := make([]byte, n) _, err := io.ReadFull(s.stdout, bytes) if err != nil { - log.Println("ERROR:", err) + log.Println("[micro-lsp]", err) } return bytes, err } -func (s *Server) SendMessageGetResponse(method string, params interface{}) ([]byte, error) { +func (s *Server) sendNotification(method string, params interface{}) error { + m := RPCNotification{ + RPCVersion: "2.0", + Method: method, + Params: params, + } + + return s.sendMessage(m) +} + +func (s *Server) sendRequest(method string, params interface{}) ([]byte, error) { id := s.requestID + s.requestID++ r := make(chan []byte) s.responses[id] = r - err := s.SendMessage(method, params) + + m := RPCRequest{ + RPCVersion: "2.0", + ID: id, + Method: method, + Params: params, + } + + err := s.sendMessage(m) if err != nil { return nil, err } @@ -264,27 +278,19 @@ func (s *Server) SendMessageGetResponse(method string, params interface{}) ([]by return bytes, nil } -func (s *Server) SendMessage(method string, params interface{}) error { - m := RPCMessage{ - RPCVersion: "2.0", - ID: s.requestID, - Method: method, - Params: params, - } - s.requestID++ - +func (s *Server) sendMessage(m interface{}) error { msg, err := json.Marshal(m) if err != nil { return err } + log.Println("[micro-lsp] >>>", string(msg)) + // encode header and proper line endings 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) - return nil + _, err = s.stdin.Write(msg) + return err } diff --git a/internal/lsp/servers_toml.go b/internal/lsp/servers_toml.go index 6123d290..63a64157 100644 --- a/internal/lsp/servers_toml.go +++ b/internal/lsp/servers_toml.go @@ -32,11 +32,11 @@ command = "pyls" install = [["pip", "install", "python-language-server"]] [language.c] -command = "clangd" -args = ["--log=verbose"] +command = "ccls" +args = [] [language.cpp] -command = "clangd" +command = "ccls" args = [] [language.haskell]