From c1621086a26d1290104691f27c86e03eea547bdd Mon Sep 17 00:00:00 2001 From: Zachary Yedidia Date: Wed, 12 Aug 2020 16:03:14 -0400 Subject: [PATCH] Autoformatting --- internal/action/actions.go | 22 +++++++++++++++++ internal/action/bufpane.go | 1 + internal/buffer/buffer.go | 14 ++++++++++- internal/buffer/loc.go | 8 +++++++ internal/lsp/requests.go | 46 ++++++++++++++++++++++++++++++++---- internal/lsp/server.go | 11 +++++++-- internal/lsp/servers_toml.go | 4 ++-- 7 files changed, 96 insertions(+), 10 deletions(-) diff --git a/internal/action/actions.go b/internal/action/actions.go index d8e8968f..04db5b44 100644 --- a/internal/action/actions.go +++ b/internal/action/actions.go @@ -15,6 +15,7 @@ import ( "github.com/zyedidia/micro/v2/internal/shell" "github.com/zyedidia/micro/v2/internal/util" "github.com/zyedidia/tcell" + "go.lsp.dev/protocol" ) // ScrollUp is not an action @@ -1815,6 +1816,9 @@ func (h *BufPane) RemoveAllMultiCursors() bool { return true } +// SemanticInfo returns information about the identifier the cursor is on and +// displays the information in the infobar +// The information is fetched using the LSP server (must be enabled) func (h *BufPane) SemanticInfo() bool { info, err := h.Buf.Server.Hover(h.Buf.AbsPath, lsp.Position(h.Cursor.X, h.Cursor.Y)) @@ -1829,6 +1833,24 @@ func (h *BufPane) SemanticInfo() bool { return true } +// AutoFormat automatically formats the document using LSP +func (h *BufPane) AutoFormat() bool { + edits, err := h.Buf.Server.DocumentFormat(h.Buf.AbsPath, protocol.FormattingOptions{ + InsertSpaces: h.Buf.Settings["tabstospaces"].(bool), + TabSize: h.Buf.Settings["tabsize"].(float64), + }) + if err != nil { + InfoBar.Error(err) + return false + } + + for _, e := range edits { + h.Buf.ApplyEdit(e) + } + + return true +} + // None is an action that does nothing func (h *BufPane) None() bool { return true diff --git a/internal/action/bufpane.go b/internal/action/bufpane.go index 6781973a..0b78144c 100644 --- a/internal/action/bufpane.go +++ b/internal/action/bufpane.go @@ -689,6 +689,7 @@ var BufKeyActions = map[string]BufKeyAction{ "Deselect": (*BufPane).Deselect, "ClearInfo": (*BufPane).ClearInfo, "SemanticInfo": (*BufPane).SemanticInfo, + "AutoFormat": (*BufPane).AutoFormat, "None": (*BufPane).None, // This was changed to InsertNewline but I don't want to break backwards compatibility diff --git a/internal/buffer/buffer.go b/internal/buffer/buffer.go index fd682bee..e4d27859 100644 --- a/internal/buffer/buffer.go +++ b/internal/buffer/buffer.go @@ -419,7 +419,8 @@ func (b *Buffer) lspInit() { var err error b.Server, err = lsp.StartServer(l) if err == nil { - b.Server.Initialize(gopath.Dir(b.AbsPath)) + d, _ := os.Getwd() + b.Server.Initialize(d) } } if b.HasLSP() { @@ -505,6 +506,17 @@ func (b *Buffer) Remove(start, end Loc) { } } +// ApplyEdit performs a LSP text edit on the buffer +func (b *Buffer) ApplyEdit(e lspt.TextEdit) { + if len(e.NewText) == 0 { + // deletion + b.Remove(toLoc(e.Range.Start), toLoc(e.Range.End)) + } else { + // insertion + b.Insert(toLoc(e.Range.Start), e.NewText) + } +} + // FileType returns the buffer's filetype func (b *Buffer) FileType() string { return b.Settings["filetype"].(string) diff --git a/internal/buffer/loc.go b/internal/buffer/loc.go index 44f59c78..232133cb 100644 --- a/internal/buffer/loc.go +++ b/internal/buffer/loc.go @@ -2,6 +2,7 @@ package buffer import ( "github.com/zyedidia/micro/v2/internal/util" + "go.lsp.dev/protocol" ) // Loc stores a location @@ -146,3 +147,10 @@ func clamp(pos Loc, la *LineArray) Loc { } return pos } + +func toLoc(r protocol.Position) Loc { + return Loc{ + X: int(r.Character), + Y: int(r.Line), + } +} diff --git a/internal/lsp/requests.go b/internal/lsp/requests.go index faf29a79..d8b52507 100644 --- a/internal/lsp/requests.go +++ b/internal/lsp/requests.go @@ -13,12 +13,24 @@ type RPCCompletion struct { Result lsp.CompletionList `json:"result"` } +type RPCCompletionAlternate struct { + RPCVersion string `json:"jsonrpc"` + ID int `json:"id"` + Result []lsp.CompletionItem `json:"result"` +} + type RPCHover struct { RPCVersion string `json:"jsonrpc"` ID int `json:"id"` Result lsp.Hover `json:"result"` } +type RPCFormat struct { + RPCVersion string `json:"jsonrpc"` + ID int `json:"id"` + Result []lsp.TextEdit `json:"result"` +} + type hoverAlternate struct { // Contents is the hover's content Contents []interface{} `json:"contents"` @@ -41,8 +53,28 @@ func Position(x, y int) lsp.Position { } } -func (s *Server) DocumentFormat() { +func (s *Server) DocumentFormat(filename string, options lsp.FormattingOptions) ([]lsp.TextEdit, error) { + doc := lsp.TextDocumentIdentifier{ + URI: uri.File(filename), + } + params := lsp.DocumentFormattingParams{ + Options: options, + TextDocument: doc, + } + + resp, err := s.sendRequest(lsp.MethodTextDocumentFormatting, params) + if err != nil { + return nil, err + } + + var r RPCFormat + err = json.Unmarshal(resp, &r) + if err != nil { + return nil, err + } + + return r.Result, nil } func (s *Server) DocumentRangeFormat() { @@ -65,18 +97,22 @@ func (s *Server) Completion(filename string, pos lsp.Position) ([]lsp.Completion TextDocumentPositionParams: docpos, Context: &cc, } - resp, err := s.sendRequest("textDocument/completion", params) + resp, err := s.sendRequest(lsp.MethodTextDocumentCompletion, params) if err != nil { return nil, err } var r RPCCompletion err = json.Unmarshal(resp, &r) + if err == nil { + return r.Result.Items, nil + } + var ra RPCCompletionAlternate + err = json.Unmarshal(resp, &ra) if err != nil { return nil, err } - - return r.Result.Items, nil + return ra.Result, nil } func (s *Server) CompletionResolve() { @@ -91,7 +127,7 @@ func (s *Server) Hover(filename string, pos lsp.Position) (string, error) { Position: pos, } - resp, err := s.sendRequest("textDocument/hover", params) + resp, err := s.sendRequest(lsp.MethodTextDocumentHover, params) if err != nil { return "", err } diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 9202d453..da22dd68 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -3,6 +3,7 @@ package lsp import ( "bufio" "encoding/json" + "errors" "io" "log" "os" @@ -10,6 +11,7 @@ import ( "strconv" "strings" "sync" + "time" lsp "go.lsp.dev/protocol" "go.lsp.dev/uri" @@ -263,10 +265,15 @@ func (s *Server) sendRequest(method string, params interface{}) ([]byte, error) return nil, err } - bytes := <-r + var bytes []byte + select { + case bytes = <-r: + case <-time.After(5 * time.Second): + err = errors.New("Request timed out") + } delete(s.responses, id) - return bytes, nil + return bytes, err } func (s *Server) sendMessage(m interface{}) error { diff --git a/internal/lsp/servers_toml.go b/internal/lsp/servers_toml.go index 5d4e7676..7fde89bf 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 = "ccls" +command = "clangd" args = [] [language.cpp] -command = "ccls" +command = "clangd" args = [] [language.haskell]