mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-24 01:37:15 +09:00
Basic non-compliant autocompletion via LSP
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user