Basic non-compliant autocompletion via LSP

This commit is contained in:
Zachary Yedidia
2020-08-10 18:19:13 -04:00
parent f6ba76424a
commit 053134af1c
5 changed files with 193 additions and 58 deletions

View File

@@ -678,7 +678,7 @@ func (h *BufPane) Autocomplete() bool {
b.CycleAutocomplete(true)
return true
}
return b.Autocomplete(buffer.BufferComplete)
return b.Autocomplete(buffer.LSPComplete)
}
// CycleAutocompleteBack cycles back in the autocomplete suggestion list

View File

@@ -7,6 +7,7 @@ import (
"sort"
"strings"
lspt "github.com/sourcegraph/go-lsp"
"github.com/zyedidia/micro/v2/internal/util"
)
@@ -203,3 +204,34 @@ func BufferComplete(b *Buffer) ([]string, []string) {
return completions, suggestions
}
func LSPComplete(b *Buffer) ([]string, []string) {
c := b.GetActiveCursor()
_, argstart := GetWord(b)
if argstart == -1 {
return []string{}, []string{}
}
pos := lspt.Position{
Line: c.Y,
Character: c.X,
}
items, err := b.server.Completion(b.AbsPath, pos)
if err != nil {
return []string{}, []string{}
}
suggestions := 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)
}
return completions, suggestions
}

View File

@@ -138,7 +138,9 @@ func (b *SharedBuffer) insert(pos Loc, value []byte) {
inslines := bytes.Count(value, []byte{'\n'})
b.MarkModified(pos.Y, pos.Y+inslines)
b.lspDidChange(pos, pos.MoveLA(util.CharacterCount(value), b.LineArray), string(value))
p := pos
p.X += util.CharacterCount(value)
b.lspDidChange(pos, p, string(value))
}
func (b *SharedBuffer) remove(start, end Loc) []byte {
b.isModified = true
@@ -405,7 +407,11 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
if ok && l.Installed() {
b.server, _ = lsp.StartServer(l)
b.server.Initialize(gopath.Dir(b.AbsPath))
b.server.DidOpen(b.AbsPath, ft, string(b.Bytes()), b.version)
bytes := b.Bytes()
if len(bytes) == 0 {
bytes = []byte{'\n'}
}
b.server.DidOpen(b.AbsPath, ft, string(bytes), b.version)
}
}
}

View File

@@ -1,9 +1,17 @@
package lsp
import (
"encoding/json"
"github.com/sourcegraph/go-lsp"
)
type RPCCompletion struct {
RPCVersion string `json:"jsonrpc"`
ID int `json:"id"`
Result lsp.CompletionList `json:"result"`
}
func (s *Server) DidOpen(filename, language, text string, version int) {
doc := lsp.TextDocumentItem{
URI: lsp.DocumentURI("file://" + filename),
@@ -16,7 +24,7 @@ func (s *Server) DidOpen(filename, language, text string, version int) {
TextDocument: doc,
}
go s.SendMessage("textDocument/didOpen", params)
s.SendMessage("textDocument/didOpen", params)
}
func (s *Server) DidSave(filename string) {
@@ -27,7 +35,7 @@ func (s *Server) DidSave(filename string) {
params := lsp.DidSaveTextDocumentParams{
TextDocument: doc,
}
go s.SendMessage("textDocument/didSave", params)
s.SendMessage("textDocument/didSave", params)
}
func (s *Server) DidChange(filename string, version int, changes []lsp.TextDocumentContentChangeEvent) {
@@ -42,7 +50,7 @@ func (s *Server) DidChange(filename string, version int, changes []lsp.TextDocum
TextDocument: doc,
ContentChanges: changes,
}
go s.SendMessage("textDocument/didChange", params)
s.SendMessage("textDocument/didChange", params)
}
func (s *Server) DidClose(filename string) {
@@ -53,7 +61,7 @@ func (s *Server) DidClose(filename string) {
params := lsp.DidCloseTextDocumentParams{
TextDocument: doc,
}
go s.SendMessage("textDocument/didClose", params)
s.SendMessage("textDocument/didClose", params)
}
func (s *Server) DocumentFormat() {
@@ -64,8 +72,34 @@ func (s *Server) DocumentRangeFormat() {
}
func (s *Server) Completion() {
func (s *Server) Completion(filename string, pos lsp.Position) ([]lsp.CompletionItem, error) {
cc := lsp.CompletionContext{
TriggerKind: lsp.CTKInvoked,
}
docpos := lsp.TextDocumentPositionParams{
TextDocument: lsp.TextDocumentIdentifier{
URI: lsp.DocumentURI("file://" + filename),
},
Position: pos,
}
params := lsp.CompletionParams{
TextDocumentPositionParams: docpos,
Context: cc,
}
resp, err := s.SendMessageGetResponse("textDocument/completion", params)
if err != nil {
return nil, err
}
var r RPCCompletion
err = json.Unmarshal(resp, &r)
if err != nil {
return nil, err
}
return r.Result.Items, nil
}
func (s *Server) CompletionResolve() {

View File

@@ -30,6 +30,9 @@ type Server struct {
capabilities lsp.ServerCapabilities
root string
lock sync.Mutex
active bool
requestID int
responses map[int]chan ([]byte)
}
type RPCMessage struct {
@@ -45,6 +48,12 @@ type RPCInit struct {
Result lsp.InitializeResult `json:"result"`
}
type RPCResult struct {
RPCVersion string `json:"jsonrpc"`
ID int `json:"id,omitempty"`
Method string `json:"method,omitempty"`
}
func StartServer(l Language) (*Server, error) {
c := exec.Command(l.Command, l.Args...)
@@ -70,6 +79,7 @@ func StartServer(l Language) (*Server, error) {
s.stdin = stdin
s.stdout = bufio.NewReader(stdout)
s.language = &l
s.responses = make(map[int]chan []byte)
// activeServers[l.Command] = s
@@ -133,67 +143,83 @@ func (s *Server) Initialize(directory string) {
},
}
go func() {
resp, err := s.SendMessage("initialize", params)
if err != nil {
return
}
// todo parse capabilities
log.Println("Received", string(resp))
var r RPCInit
err = json.Unmarshal(resp, &r)
if err != nil {
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.lock.Unlock()
}()
}
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)
err := s.SendMessage("initialize", params)
if err != nil {
return nil, err
return
}
msg = append(msg, '\r', '\n')
resp, err := s.receiveMessage()
if err != nil {
return
}
header := []byte("Content-Length: " + strconv.Itoa(len(msg)) + "\r\n\r\n")
msg = append(header, msg...)
// todo parse capabilities
log.Println("Received", string(resp))
log.Println("Sending", string(msg))
var r RPCInit
err = json.Unmarshal(resp, &r)
if err != nil {
return
}
err = s.SendMessage("initialized", struct{}{})
if err != nil {
return
}
slock.Lock()
activeServers[s.language.Command+"-"+directory] = s
slock.Unlock()
s.lock.Lock()
s.stdin.Write(msg)
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)
continue
}
log.Println("Received", string(resp))
var r RPCResult
err = json.Unmarshal(resp, &r)
if err != nil {
log.Println(err)
continue
}
switch r.Method {
case "window/logMessage":
// TODO
case "textDocument/publishDiagnostics":
// TODO
case "":
// Response
if _, ok := s.responses[r.ID]; ok {
s.responses[r.ID] <- resp
}
}
}
}
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
}
@@ -213,15 +239,52 @@ func (s *Server) SendMessage(method string, params interface{}) ([]byte, error)
return []byte{}, nil
}
log.Println("CONTENT-LENGTH:", n)
bytes := make([]byte, n)
_, err = s.stdout.Read(bytes)
_, err := io.ReadFull(s.stdout, bytes)
if err != nil {
log.Println("ERROR:", err)
}
return bytes, err
}
func (s *Server) SendMessageGetResponse(method string, params interface{}) ([]byte, error) {
id := s.requestID
r := make(chan []byte)
s.responses[id] = r
err := s.SendMessage(method, params)
if err != nil {
return nil, err
}
log.Println("Received", string(bytes))
s.lock.Unlock()
bytes := <-r
delete(s.responses, id)
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++
msg, err := json.Marshal(m)
if err != nil {
return err
}
// 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
}