mirror of https://github.com/lapce/lapce.git
325 lines
6.4 KiB
Go
325 lines
6.4 KiB
Go
package plugin
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net"
|
|
"runtime/debug"
|
|
|
|
"github.com/crane-editor/crane/log"
|
|
|
|
"github.com/crane-editor/crane/lsp"
|
|
"github.com/crane-editor/crane/plugin"
|
|
"github.com/sourcegraph/jsonrpc2"
|
|
)
|
|
|
|
// Server is the rpc server for lsp plugin
|
|
type Server struct {
|
|
lis net.Listener
|
|
plugin *Plugin
|
|
}
|
|
|
|
type handler struct {
|
|
plugin *Plugin
|
|
}
|
|
|
|
func newServer(plugin *Plugin, addr string) (*Server, error) {
|
|
log.Infoln("now listen")
|
|
lis, err := net.Listen("tcp", addr)
|
|
if err != nil {
|
|
log.Infoln("now listen", err)
|
|
return nil, err
|
|
}
|
|
return &Server{
|
|
lis: lis,
|
|
plugin: plugin,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Server) run() {
|
|
for {
|
|
conn, err := s.lis.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
go s.serve(conn)
|
|
}
|
|
}
|
|
|
|
func (s *Server) close() {
|
|
if s.lis != nil {
|
|
s.lis.Close()
|
|
}
|
|
}
|
|
|
|
func (s *Server) serve(conn net.Conn) {
|
|
log.Infoln("now serve", conn.RemoteAddr().String())
|
|
s.plugin.conns[conn.RemoteAddr().String()] = jsonrpc2.NewConn(context.Background(), NewConnStream(conn), &handler{plugin: s.plugin})
|
|
}
|
|
|
|
func (h *handler) Handle(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
log.Infoln("handle error", r, string(debug.Stack()))
|
|
}
|
|
}()
|
|
|
|
paramsData, err := req.Params.MarshalJSON()
|
|
if err != nil {
|
|
log.Infoln(err)
|
|
return
|
|
}
|
|
log.Infoln("now handle", req.ID, req.Method, string(paramsData))
|
|
var meta map[string]string
|
|
metaData, err := req.Meta.MarshalJSON()
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = json.Unmarshal(metaData, &meta)
|
|
if err != nil {
|
|
return
|
|
}
|
|
viewID, ok := meta["view_id"]
|
|
if !ok {
|
|
return
|
|
}
|
|
log.Infoln("view id", viewID)
|
|
switch req.Method {
|
|
case "definition":
|
|
var params *lsp.TextDocumentPositionParams
|
|
err = json.Unmarshal(paramsData, ¶ms)
|
|
if err != nil {
|
|
return
|
|
}
|
|
view, ok := h.plugin.Views[viewID]
|
|
if !ok {
|
|
return
|
|
}
|
|
lspClient, ok := h.plugin.lsp[view.Syntax]
|
|
if !ok {
|
|
return
|
|
}
|
|
locations, err := lspClient.Definition(params)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if len(locations) == 0 {
|
|
return
|
|
}
|
|
for _, conn := range h.plugin.conns {
|
|
conn.Notify(context.Background(), "definition", locations[0])
|
|
}
|
|
case "hover":
|
|
var params *lsp.TextDocumentPositionParams
|
|
err = json.Unmarshal(paramsData, ¶ms)
|
|
if err != nil {
|
|
return
|
|
}
|
|
view, ok := h.plugin.Views[viewID]
|
|
if !ok {
|
|
return
|
|
}
|
|
lspClient, ok := h.plugin.lsp[view.Syntax]
|
|
if !ok {
|
|
return
|
|
}
|
|
lspClient.Hover(params)
|
|
case "completion":
|
|
var params *lsp.TextDocumentPositionParams
|
|
err = json.Unmarshal(paramsData, ¶ms)
|
|
if err != nil {
|
|
return
|
|
}
|
|
view, ok := h.plugin.Views[viewID]
|
|
if !ok {
|
|
return
|
|
}
|
|
lspClient, ok := h.plugin.lsp[view.Syntax]
|
|
if !ok {
|
|
return
|
|
}
|
|
resp, err := lspClient.Completion(params)
|
|
if err != nil {
|
|
return
|
|
}
|
|
log.Infoln("get resp", resp)
|
|
conn.Reply(ctx, req.ID, resp)
|
|
log.Infoln("resp replied")
|
|
case "completion_reset":
|
|
h.plugin.completionItems = []*lsp.CompletionItem{}
|
|
case "completion_select":
|
|
h.plugin.Mutex.Lock()
|
|
defer h.plugin.Mutex.Unlock()
|
|
|
|
var item *lsp.CompletionItem
|
|
err = json.Unmarshal(paramsData, &item)
|
|
if err != nil {
|
|
return
|
|
}
|
|
view, ok := h.plugin.Views[viewID]
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
els := []*plugin.El{}
|
|
el := &plugin.El{
|
|
Copy: []int{0, h.plugin.getCompletionStart(view)},
|
|
}
|
|
els = append(els, el)
|
|
el = &plugin.El{
|
|
Insert: item.Label,
|
|
}
|
|
els = append(els, el)
|
|
el = &plugin.El{
|
|
Copy: []int{view.Cache.GetOffset(), len(view.Cache.GetContent())},
|
|
}
|
|
els = append(els, el)
|
|
delta := &plugin.Delta{
|
|
BaseLen: len(view.Cache.GetContent()),
|
|
Els: els,
|
|
}
|
|
edit := &plugin.Edit{
|
|
Priority: plugin.EditPriorityHigh,
|
|
AfterCursor: false,
|
|
Author: "lsp",
|
|
Delta: delta,
|
|
Rev: view.Rev,
|
|
}
|
|
h.plugin.Edit(view, edit)
|
|
case "format":
|
|
reply := ""
|
|
defer conn.Reply(ctx, req.ID, reply)
|
|
|
|
view, ok := h.plugin.Views[viewID]
|
|
if !ok {
|
|
return
|
|
}
|
|
lspClient, ok := h.plugin.lsp[view.Syntax]
|
|
if !ok {
|
|
return
|
|
}
|
|
h.plugin.Mutex.Lock()
|
|
defer h.plugin.Mutex.Unlock()
|
|
|
|
log.Infoln("now format", view.Path)
|
|
result, err := lspClient.Format(view.Path)
|
|
if err != nil {
|
|
log.Infoln(err)
|
|
return
|
|
}
|
|
resultBytes, _ := json.Marshal(result)
|
|
log.Infoln(string(resultBytes))
|
|
|
|
els := []*plugin.El{}
|
|
for _, edit := range result {
|
|
elsItems := lspEditToXi(view, edit)
|
|
if len(els) > 0 {
|
|
lastEl := els[len(els)-1]
|
|
lastEl.Copy[1] = elsItems[0].Copy[1]
|
|
elsItems = elsItems[1:]
|
|
}
|
|
els = append(els, elsItems...)
|
|
}
|
|
if len(els) == 0 {
|
|
return
|
|
}
|
|
delta := &plugin.Delta{
|
|
BaseLen: len(view.Cache.GetContent()),
|
|
Els: els,
|
|
}
|
|
xiEdit := &plugin.Edit{
|
|
Priority: plugin.EditPriorityHigh,
|
|
AfterCursor: false,
|
|
Author: "lsp",
|
|
Delta: delta,
|
|
Rev: view.Rev,
|
|
}
|
|
h.plugin.Edit(view, xiEdit)
|
|
}
|
|
}
|
|
|
|
func lspEditToXi(view *plugin.View, edit *lsp.TextEdit) []*plugin.El {
|
|
start := edit.Range.Start
|
|
end := edit.Range.End
|
|
content := view.Cache.GetContent()
|
|
|
|
if start.Line == 0 && start.Character == 0 && view.Cache.PosToOffset(end.Line, end.Character) == len(content) {
|
|
els := []*plugin.El{}
|
|
oldRaw := content
|
|
newRaw := []byte(edit.NewText)
|
|
oldLen := len(oldRaw)
|
|
newLen := len(newRaw)
|
|
i := 0
|
|
for i = range oldRaw {
|
|
if i >= newLen {
|
|
break
|
|
}
|
|
if oldRaw[i] != newRaw[i] {
|
|
break
|
|
}
|
|
}
|
|
|
|
j := oldLen - 1
|
|
newJ := newLen - (oldLen - j)
|
|
for j = oldLen - 1; j > i; {
|
|
if newJ <= 0 {
|
|
break
|
|
}
|
|
if oldRaw[j] != newRaw[newJ] {
|
|
break
|
|
}
|
|
j--
|
|
newJ = newLen - (oldLen - j)
|
|
}
|
|
if newJ < i {
|
|
i = newJ
|
|
}
|
|
|
|
el := &plugin.El{
|
|
Copy: []int{0, i},
|
|
}
|
|
els = append(els, el)
|
|
|
|
log.Info(i, j, newJ, oldLen, newLen)
|
|
if newJ+1 > i {
|
|
newText := string(newRaw[i : newJ+1])
|
|
el = &plugin.El{
|
|
Insert: newText,
|
|
}
|
|
els = append(els, el)
|
|
}
|
|
|
|
log.Infoln(i, j, newJ)
|
|
el = &plugin.El{
|
|
Copy: []int{j + 1, len(content)},
|
|
}
|
|
els = append(els, el)
|
|
log.Infoln(els)
|
|
return els
|
|
}
|
|
|
|
els := []*plugin.El{}
|
|
el := &plugin.El{
|
|
Copy: []int{0, view.Cache.PosToOffset(start.Line, start.Character)},
|
|
}
|
|
els = append(els, el)
|
|
if edit.NewText != "" {
|
|
el = &plugin.El{
|
|
Insert: edit.NewText,
|
|
}
|
|
els = append(els, el)
|
|
}
|
|
|
|
offset := view.Cache.PosToOffset(end.Line, end.Character)
|
|
if offset < len(content) {
|
|
el = &plugin.El{
|
|
Copy: []int{view.Cache.PosToOffset(end.Line, end.Character), len(content)},
|
|
}
|
|
els = append(els, el)
|
|
} else if len(els) == 1 {
|
|
els[0].Copy[1]--
|
|
}
|
|
|
|
return els
|
|
}
|