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 5eab8ad
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 67 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
74 changes: 30 additions & 44 deletions internal/lsp/acmelsp/acmelsp.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,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 +53,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 Down Expand Up @@ -160,36 +158,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,21 +239,39 @@ 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) {
// 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)
}
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)
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)
}
}
}
Expand Down
140 changes: 140 additions & 0 deletions internal/lsp/acmelsp/files.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package acmelsp

import (
"fmt"
"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"
)

type FileManager struct {
ss *client.ServerSet
wins map[string]struct{} // set of open files
mu sync.Mutex
}

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 5eab8ad

Please sign in to comment.