Skip to content

Commit

Permalink
cmd/govim: refactor functionality of govim into separate files (#192)
Browse files Browse the repository at this point in the history
Just makes working with it easier.
  • Loading branch information
myitcv committed Apr 30, 2019
1 parent af25fed commit 45c91bd
Show file tree
Hide file tree
Showing 9 changed files with 672 additions and 600 deletions.
50 changes: 50 additions & 0 deletions cmd/govim/balloonexpr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package main

import (
"context"
"encoding/json"
"fmt"
"strings"

"github.com/myitcv/govim/cmd/govim/internal/lsp/protocol"
"github.com/myitcv/govim/cmd/govim/types"
)

func (v *vimstate) balloonExpr(args ...json.RawMessage) (interface{}, error) {
var vpos struct {
BufNum int `json:"bufnum"`
Line int `json:"line"`
Col int `json:"col"`
}
expr := v.ChannelExpr(`{"bufnum": v:beval_bufnr, "line": v:beval_lnum, "col": v:beval_col}`)
if err := json.Unmarshal(expr, &vpos); err != nil {
return nil, fmt.Errorf("failed to unmarshal current mouse position info: %v", err)
}
b, ok := v.buffers[vpos.BufNum]
if !ok {
return nil, fmt.Errorf("unable to resolve buffer %v", vpos.BufNum)
}
pos, err := types.PointFromVim(b, vpos.Line, vpos.Col)
if err != nil {
return nil, fmt.Errorf("failed to determine mouse position: %v", err)
}
go func() {
params := &protocol.TextDocumentPositionParams{
TextDocument: b.ToTextDocumentIdentifier(),
Position: pos.ToPosition(),
}
hovRes, err := v.server.Hover(context.Background(), params)
if err != nil {
v.ChannelCall("balloon_show", fmt.Sprintf("failed to get hover details: %v", err))
} else {
msg := strings.TrimSpace(hovRes.Contents.Value)
var args interface{} = msg
if !v.isGui {
args = strings.Split(msg, "\n")
}
v.ChannelCall("balloon_show", args)
}

}()
return "", nil
}
83 changes: 83 additions & 0 deletions cmd/govim/buffer_events.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package main

import (
"context"
"encoding/json"
"fmt"

"github.com/myitcv/govim/cmd/govim/internal/lsp/protocol"
"github.com/myitcv/govim/cmd/govim/types"
)

func (v *vimstate) bufReadPost(args ...json.RawMessage) error {
b := v.currentBufferInfo(args[0])
if cb, ok := v.buffers[b.Num]; ok {
// reload of buffer, e.v. e!
b.Version = cb.Version + 1
} else if wf, ok := v.watchedFiles[b.Name]; ok {
// We are now picking up from a file that was previously watched. If we subsequently
// close this buffer then we will handle that event and delete the entry in v.buffers
// at which point the file watching will take back over again.
delete(v.watchedFiles, b.Name)
b.Version = wf.Version + 1
} else {
b.Version = 0
}
return v.handleBufferEvent(b)
}

func (v *vimstate) bufTextChanged(args ...json.RawMessage) error {
b := v.currentBufferInfo(args[0])
cb, ok := v.buffers[b.Num]
if !ok {
return fmt.Errorf("have not seen buffer %v (%v) - this should be impossible", b.Num, b.Name)
}
b.Version = cb.Version + 1
return v.handleBufferEvent(b)
}

func (v *vimstate) handleBufferEvent(b *types.Buffer) error {
v.buffers[b.Num] = b

if b.Version == 0 {
params := &protocol.DidOpenTextDocumentParams{
TextDocument: protocol.TextDocumentItem{
URI: string(b.URI()),
Version: float64(b.Version),
Text: string(b.Contents),
},
}
err := v.server.DidOpen(context.Background(), params)
return err
}

params := &protocol.DidChangeTextDocumentParams{
TextDocument: protocol.VersionedTextDocumentIdentifier{
TextDocumentIdentifier: b.ToTextDocumentIdentifier(),
Version: float64(b.Version),
},
ContentChanges: []protocol.TextDocumentContentChangeEvent{
{
Text: string(b.Contents),
},
},
}
err := v.server.DidChange(context.Background(), params)
return err
}

func (v *vimstate) deleteCurrentBuffer(args ...json.RawMessage) error {
currBufNr := v.ParseInt(args[0])
cb, ok := v.buffers[currBufNr]
if !ok {
return fmt.Errorf("tried to remove buffer %v; but we have no record of it", currBufNr)
}
delete(v.buffers, cb.Num)
params := &protocol.DidCloseTextDocumentParams{
TextDocument: cb.ToTextDocumentIdentifier(),
}
if err := v.server.DidClose(context.Background(), params); err != nil {
return fmt.Errorf("failed to call gopls.DidClose on %v: %v", cb.Name, err)
}
return nil
}
53 changes: 53 additions & 0 deletions cmd/govim/complete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package main

import (
"context"
"encoding/json"
"fmt"

"github.com/myitcv/govim/cmd/govim/internal/lsp/protocol"
)

func (v *vimstate) complete(args ...json.RawMessage) (interface{}, error) {
// Params are: findstart int, base string
findstart := v.ParseInt(args[0]) == 1

if findstart {
b, pos, err := v.cursorPos()
if err != nil {
return nil, fmt.Errorf("failed to get current position: %v", err)
}
params := &protocol.CompletionParams{
TextDocumentPositionParams: protocol.TextDocumentPositionParams{
TextDocument: protocol.TextDocumentIdentifier{
URI: string(b.URI()),
},
Position: pos.ToPosition(),
},
}
res, err := v.server.Completion(context.Background(), params)
if err != nil {
return nil, fmt.Errorf("called to gopls.Completion failed: %v", err)
}

v.lastCompleteResults = res
return pos.Col(), nil
} else {
var matches []completionResult
for _, i := range v.lastCompleteResults.Items {
matches = append(matches, completionResult{
Abbr: i.Label,
Word: i.TextEdit.NewText,
Info: i.Detail,
})
}

return matches, nil
}
}

type completionResult struct {
Abbr string `json:"abbr"`
Word string `json:"word"`
Info string `json:"info"`
}
123 changes: 123 additions & 0 deletions cmd/govim/format.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package main

import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"strconv"
"strings"
"time"

"github.com/myitcv/govim/cmd/govim/config"
"github.com/myitcv/govim/cmd/govim/internal/lsp/protocol"
"github.com/myitcv/govim/cmd/govim/types"
)

func (v *vimstate) formatCurrentBuffer(args ...json.RawMessage) (err error) {
tool := v.ParseString(v.ChannelExpr(config.GlobalFormatOnSave))
// we are an autocmd endpoint so we need to be told the current
// buffer number via <abuf>
currBufNr := v.ParseInt(args[0])
b, ok := v.buffers[currBufNr]
if !ok {
return fmt.Errorf("failed to resolve buffer %v", currBufNr)
}

var edits []protocol.TextEdit

switch config.FormatOnSave(tool) {
case config.FormatOnSaveNone:
return nil
case config.FormatOnSaveGoFmt:
params := &protocol.DocumentFormattingParams{
TextDocument: b.ToTextDocumentIdentifier(),
}
edits, err = v.server.Formatting(context.Background(), params)
if err != nil {
return fmt.Errorf("failed to call gopls.Formatting: %v", err)
}
case config.FormatOnSaveGoImports:
params := &protocol.CodeActionParams{
TextDocument: b.ToTextDocumentIdentifier(),
}
actions, err := v.server.CodeAction(context.Background(), params)
if err != nil {
return fmt.Errorf("failed to call gopls.CodeAction: %v", err)
}
switch len(actions) {
case 0:
return nil
case 1:
edits = (*actions[0].Edit.Changes)[string(b.URI())]
default:
return fmt.Errorf("don't know how to handle %v actions", len(actions))
}
default:
return fmt.Errorf("unknown format tool specified for %v: %v", config.GlobalFormatOnSave, tool)
}

// see :help wundo. The use of wundo! is significant. It first deletes
// the temp file we created, but only recreates it if there is something
// to write. This is inherently racey... because theorectically the file
// might in the meantime have been created by another instance of
// govim.... We reduce that risk using the time above
tf, err := ioutil.TempFile("", strconv.FormatInt(time.Now().UnixNano(), 10))
if err != nil {
return fmt.Errorf("failed to create temp undo file")
}

v.ChannelExf("wundo! %v", tf.Name())
defer func() {
if _, err := os.Stat(tf.Name()); err != nil {
return
}
v.ChannelExf("silent! rundo %v", tf.Name())
err = os.Remove(tf.Name())
}()

preEventIgnore := v.ParseString(v.ChannelExpr("&eventignore"))
v.ChannelEx("set eventignore=all")
defer v.ChannelExf("set eventignore=%v", preEventIgnore)
v.ToggleOnViewportChange()
defer v.ToggleOnViewportChange()
for ie := len(edits) - 1; ie >= 0; ie-- {
e := edits[ie]
start, err := types.PointFromPosition(b, e.Range.Start)
if err != nil {
return fmt.Errorf("failed to derive start point from position: %v", err)
}
end, err := types.PointFromPosition(b, e.Range.End)
if err != nil {
return fmt.Errorf("failed to derive end point from position: %v", err)
}

if start.Col() != 1 || end.Col() != 1 {
// Whether this is a delete or not, we will implement support for this later
return fmt.Errorf("saw an edit where start col != end col (range start: %v, range end: %v start: %v, end: %v). We can't currently handle this", e.Range.Start, e.Range.End, start, end)
}

if start.Line() != end.Line() {
if e.NewText != "" {
return fmt.Errorf("saw an edit where start line != end line with replacement text %q; We can't currently handle this", e.NewText)
}
// This is a delete of line
if res := v.ParseInt(v.ChannelCall("deletebufline", b.Num, start.Line(), end.Line()-1)); res != 0 {
return fmt.Errorf("deletebufline(%v, %v, %v) failed", b.Num, start.Line(), end.Line()-1)
}
} else {
// do we have anything to do?
if e.NewText == "" {
continue
}
// we are within the same line so strip the newline
if e.NewText[len(e.NewText)-1] == '\n' {
e.NewText = e.NewText[:len(e.NewText)-1]
}
repl := strings.Split(e.NewText, "\n")
v.ChannelCall("append", start.Line()-1, repl)
}
}
return nil
}
Loading

0 comments on commit 45c91bd

Please sign in to comment.