Skip to content

Commit

Permalink
Tell LSP server about new, del, get, and put events in acme/log
Browse files Browse the repository at this point in the history
Before we were only handling put for formatting.
Now we handle all events related to file synchronization.
We also send all open files to LSP server on startup.

Helps #6
  • Loading branch information
fhs committed Jun 8, 2019
1 parent 2bfb2f6 commit ecb3548
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 85 deletions.
11 changes: 8 additions & 3 deletions cmd/Lone/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,24 +96,29 @@ func main() {
if flag.NArg() < 1 {
usage()
}

fm, err := acmelsp.NewFileManager(serverSet)
if err != nil {
log.Fatalf("failed to create file manager: %v\n", err)
}
switch flag.Arg(0) {
case "win":
if flag.NArg() < 2 {
usage()
}
acmelsp.Watch(serverSet, flag.Arg(1))
acmelsp.Watch(serverSet, fm, flag.Arg(1))
return

case "monitor":
acmelsp.FormatOnPut(serverSet)
acmelsp.ManageFiles(serverSet, fm)
return

case "servers":
serverSet.PrintTo(os.Stdout)
return
}

cmd, err := acmelsp.CurrentWindowCmd(serverSet)
cmd, err := acmelsp.CurrentWindowCmd(serverSet, fm)
if err != nil {
log.Fatalf("CurrentWindowCmd failed: %v\n", err)
}
Expand Down
23 changes: 14 additions & 9 deletions cmd/acme-lsp/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,16 @@ func main() {
if len(ss.Data) == 0 {
log.Fatalf("No servers specified. Specify either -server or -dial flag. Run with -help for usage help.\n")
}
go acmelsp.FormatOnPut(ss)
readPlumb(ss)

fm, err := acmelsp.NewFileManager(ss)
if err != nil {
log.Fatalf("failed to create file manager: %v\n", err)
}
go acmelsp.ManageFiles(ss, fm)
readPlumb(ss, fm)
}

func readPlumb(ss *client.ServerSet) {
func readPlumb(ss *client.ServerSet, fm *acmelsp.FileManager) {
for {
var fid *p9client.Fid

Expand Down Expand Up @@ -105,7 +110,7 @@ func readPlumb(ss *client.ServerSet) {
for a := m.Attr; a != nil; a = a.Next {
attr[a.Name] = a.Value
}
err = run(ss, string(m.Data), attr)
err = run(ss, fm, string(m.Data), attr)
if err != nil {
log.Printf("%v failed: %v\n", string(m.Data), err)
}
Expand All @@ -115,7 +120,7 @@ func readPlumb(ss *client.ServerSet) {
}
}

func run(ss *client.ServerSet, data string, attr map[string]string) error {
func run(ss *client.ServerSet, fm *acmelsp.FileManager, data string, attr map[string]string) error {
args := strings.Fields(data)
switch args[0] {
case "workspaces":
Expand All @@ -134,7 +139,7 @@ func run(ss *client.ServerSet, data string, attr map[string]string) error {
if err != nil {
return errors.Wrap(err, "failed to parse $winid")
}
cmd, err := acmelsp.WindowCmd(ss, winid)
cmd, err := acmelsp.WindowCmd(ss, fm, winid)
if err != nil {
return err
}
Expand Down Expand Up @@ -162,13 +167,13 @@ func run(ss *client.ServerSet, data string, attr map[string]string) error {
case "symbols":
return cmd.Symbols()
case "watch-completion":
go acmelsp.Watch(ss, "comp")
go acmelsp.Watch(ss, fm, "comp")
return nil
case "watch-signature":
go acmelsp.Watch(ss, "sig")
go acmelsp.Watch(ss, fm, "sig")
return nil
case "watch-hover":
go acmelsp.Watch(ss, "hov")
go acmelsp.Watch(ss, fm, "hov")
return nil
}
return fmt.Errorf("unknown command %v", args[0])
Expand Down
70 changes: 8 additions & 62 deletions internal/lsp/acmelsp/acmelsp.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"strconv"
"strings"
Expand All @@ -28,15 +27,15 @@ type Cmd struct {
filename string
}

func CurrentWindowCmd(ss *client.ServerSet) (*Cmd, error) {
func CurrentWindowCmd(ss *client.ServerSet, fm *FileManager) (*Cmd, error) {
id, err := strconv.Atoi(os.Getenv("winid"))
if err != nil {
return nil, errors.Wrapf(err, "failed to parse $winid")
}
return WindowCmd(ss, id)
return WindowCmd(ss, fm, id)
}

func WindowCmd(ss *client.ServerSet, winid int) (*Cmd, error) {
func WindowCmd(ss *client.ServerSet, fm *FileManager, winid int) (*Cmd, error) {
w, err := acmeutil.OpenWin(winid)
if err != nil {
return nil, errors.Wrapf(err, "failed to to open window %v", winid)
Expand All @@ -53,12 +52,10 @@ func WindowCmd(ss *client.ServerSet, winid int) (*Cmd, error) {
return nil, fmt.Errorf("no language server for filename %q", fname)
}

b, err := w.ReadAll("body")
if err != nil {
return nil, errors.Wrap(err, "failed to read source body")
}
if err = srv.Conn.DidOpen(fname, b); err != nil {
return nil, errors.Wrap(err, "DidOpen failed")
// In case the window has unsaved changes (it's dirty),
// send changes to LSP server.
if err = fm.didChange(winid, fname); err != nil {
return nil, errors.Wrap(err, "DidChange failed")
}

return &Cmd{
Expand All @@ -69,9 +66,8 @@ func WindowCmd(ss *client.ServerSet, winid int) (*Cmd, error) {
}, nil
}

func (c *Cmd) Close() error {
func (c *Cmd) Close() {
c.win.CloseFiles()
return c.conn.DidClose(c.filename)
}

func (c *Cmd) Completion() error {
Expand Down Expand Up @@ -160,36 +156,6 @@ func plumbLocation(loc *protocol.Location) *plumb.Message {
}
}

func formatWin(serverSet *client.ServerSet, id int, fname string) error {
s, found, err := serverSet.StartForFile(fname)
if err != nil {
return err
}
if !found {
return nil // unknown language server
}

w, err := acmeutil.OpenWin(id)
if err != nil {
return err
}
defer w.CloseFiles()

b, err := w.ReadAll("body")
if err != nil {
log.Fatalf("failed to read source body: %v\n", err)
}
if err := s.Conn.DidOpen(fname, b); err != nil {
log.Fatalf("DidOpen failed: %v\n", err)
}
defer func() {
if err := s.Conn.DidClose(fname); err != nil {
log.Printf("DidClose failed: %v\n", err)
}
}()
return FormatFile(s.Conn, text.ToURI(fname), w)
}

// FormatFile organizes import paths and then formats the file f.
func FormatFile(c *client.Conn, uri protocol.DocumentURI, f text.File) error {
if c.Capabilities.CodeActionProvider {
Expand Down Expand Up @@ -271,26 +237,6 @@ func editWorkspace(we *protocol.WorkspaceEdit) error {
return nil
}

// FormatOnPut watches for Put executed on an acme window and formats it using LSP.
func FormatOnPut(serverSet *client.ServerSet) {
alog, err := acme.Log()
if err != nil {
panic(err)
}
defer alog.Close()
for {
ev, err := alog.Read()
if err != nil {
panic(err)
}
if ev.Op == "put" {
if err = formatWin(serverSet, ev.ID, ev.Name); err != nil {
log.Printf("formating window %v failed: %v\n", ev.ID, err)
}
}
}
}

// ParseFlags adds some standard flags, parses all flags, and returns the server set and debug.
func ParseFlags(serverSet *client.ServerSet) (*client.ServerSet, bool) {
serverSet, debug, err := ParseFlagSet(flag.CommandLine, os.Args[1:], serverSet)
Expand Down
182 changes: 182 additions & 0 deletions internal/lsp/acmelsp/files.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package acmelsp

import (
"fmt"
"log"
"sync"

"9fans.net/go/acme"
"github.com/fhs/acme-lsp/internal/acmeutil"
"github.com/fhs/acme-lsp/internal/lsp/client"
"github.com/fhs/acme-lsp/internal/lsp/text"
"github.com/pkg/errors"
)

// ManageFiles watches for files opened, closed, saved, or refreshed in acme
// and tells LSP server about it. It also formats files when it's saved.
func ManageFiles(serverSet *client.ServerSet, fm *FileManager) {
alog, err := acme.Log()
if err != nil {
panic(err)
}
defer alog.Close()

for {
ev, err := alog.Read()
if err != nil {
panic(err)
}
switch ev.Op {
case "new":
if err := fm.didOpen(ev.ID, ev.Name); err != nil {
log.Printf("didOpen failed in file manager: %v", err)
}
case "del":
if err := fm.didClose(ev.Name); err != nil {
log.Printf("didClose failed in file manager: %v", err)
}
case "get":
if err := fm.didChange(ev.ID, ev.Name); err != nil {
log.Printf("didChange failed in file manager: %v", err)
}
case "put":
if err := fm.didSave(ev.ID, ev.Name); err != nil {
log.Printf("didSave failed in file manager: %v", err)
}
if err := fm.format(ev.ID, ev.Name); err != nil {
log.Printf("Format failed in file manager: %v", err)
}
}
}
}

// FileManager keeps track of open files in acme.
// It is used to synchronize text with LSP server.
type FileManager struct {
ss *client.ServerSet
wins map[string]struct{} // set of open files
mu sync.Mutex
}

// NewFileManager creates a new file manager, initialized with files currently open in acme.
func NewFileManager(ss *client.ServerSet) (*FileManager, error) {
fm := &FileManager{
ss: ss,
wins: make(map[string]struct{}),
}

wins, err := acme.Windows()
if err != nil {
return nil, errors.Wrapf(err, "failed to read list of acme index")
}
for _, info := range wins {
err := fm.didOpen(info.ID, info.Name)
if err != nil {
return nil, err
}
}
return fm, nil
}

func (fm *FileManager) withClient(winid int, name string, f func(*client.Conn, *acmeutil.Win) error) error {
s, found, err := fm.ss.StartForFile(name)
if err != nil {
return err
}
if !found {
return nil // Unknown language server.
}

var win *acmeutil.Win
if winid >= 0 {
w, err := acmeutil.OpenWin(winid)
if err != nil {
return err
}
defer w.CloseFiles()
win = w
}
return f(s.Conn, win)
}

func (fm *FileManager) didOpen(winid int, name string) error {
return fm.withClient(winid, name, func(c *client.Conn, w *acmeutil.Win) error {
fm.mu.Lock()
defer fm.mu.Unlock()

if _, ok := fm.wins[name]; ok {
return fmt.Errorf("file already open in file manager: %v", name)
}
fm.wins[name] = struct{}{}

b, err := w.ReadAll("body")
if err != nil {
return err
}
return c.DidOpen(name, b)
})
}

func (fm *FileManager) didClose(name string) error {
fm.mu.Lock()
defer fm.mu.Unlock()

if _, ok := fm.wins[name]; !ok {
return nil // Unknown language server.
}
delete(fm.wins, name)

return fm.withClient(-1, name, func(c *client.Conn, _ *acmeutil.Win) error {
return c.DidClose(name)
})
}

func (fm *FileManager) didChange(winid int, name string) error {
fm.mu.Lock()
defer fm.mu.Unlock()

if _, ok := fm.wins[name]; !ok {
return nil // Unknown language server.
}
return fm.withClient(winid, name, func(c *client.Conn, w *acmeutil.Win) error {
b, err := w.ReadAll("body")
if err != nil {
return err
}
return c.DidChange(name, b)
})
}

func (fm *FileManager) didSave(winid int, name string) error {
fm.mu.Lock()
defer fm.mu.Unlock()

if _, ok := fm.wins[name]; !ok {
return nil // Unknown language server.
}
return fm.withClient(winid, name, func(c *client.Conn, w *acmeutil.Win) error {
b, err := w.ReadAll("body")
if err != nil {
return err
}

// TODO(fhs): Maybe DidChange is not needed with includeText option to DidSave?
err = c.DidChange(name, b)
if err != nil {
return err
}
return c.DidSave(name)
})
}

func (fm *FileManager) format(winid int, name string) error {
fm.mu.Lock()
defer fm.mu.Unlock()

if _, ok := fm.wins[name]; !ok {
return nil // Unknown language server.
}
return fm.withClient(winid, name, func(c *client.Conn, w *acmeutil.Win) error {
return FormatFile(c, text.ToURI(name), w)
})
}
Loading

0 comments on commit ecb3548

Please sign in to comment.