Skip to content

Commit

Permalink
feat: remove documents in lsp server, follow single truth principle b…
Browse files Browse the repository at this point in the history
…y relying on daemon server
  • Loading branch information
nedpals committed Apr 1, 2024
1 parent 88bd254 commit 68c5cd8
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 58 deletions.
8 changes: 8 additions & 0 deletions server/daemon/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,14 @@ func (c *Client) DeleteDocument(filepath string) error {
}, nil)
}

func (c *Client) RetrieveDocument(filepath string) (string, error) {
var content types.DocumentPayload
err := c.Call(types.RetrieveDocumentMethod, types.DocumentIdentifier{
Filepath: filepath,
}, &content)
return content.Content, err
}

func (c *Client) GetDataDirPath() (string, error) {
var path string
err := c.Call(types.GetDataDirMethod, nil, &path)
Expand Down
105 changes: 95 additions & 10 deletions server/daemon/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ type resultError struct {
type Server struct {
ServerLog *log.Logger
engine *errgoengine.ErrgoEngine
// fileUseCounter is use to keep track how many clients are using a file
fileUseCounter map[string][]int
// TODO: add storage for context data
connectedClients connectedClients
logger *logger.Logger
Expand Down Expand Up @@ -203,11 +205,21 @@ func (d *Server) Handle(ctx context.Context, c *jsonrpc2.Conn, r *jsonrpc2.Reque
return
}

if err := d.FS().WriteFile(payloadStr.Filepath, []byte(payloadStr.Content)); err != nil {
c.ReplyWithError(ctx, r.ID, &jsonrpc2.Error{
Message: err.Error(),
})
return
if _, ok := d.fileUseCounter[payloadStr.Filepath]; !ok {
if err := d.FS().WriteFile(payloadStr.Filepath, []byte(payloadStr.Content)); err != nil {
c.ReplyWithError(ctx, r.ID, &jsonrpc2.Error{
Message: err.Error(),
})
return
}

d.fileUseCounter[payloadStr.Filepath] = []int{}
}

// check if the current connected client is present in specific file of fileUseCounter
procId, _ := d.getProcessId(r)
if idx := d.GetFileUseIdx(payloadStr.Filepath, procId); idx == -1 {
d.fileUseCounter[payloadStr.Filepath] = append(d.fileUseCounter[payloadStr.Filepath], procId)
}

d.ServerLog.Printf("resolved document: %s (len: %d)\n", payloadStr.Filepath, len(payloadStr.Content))
Expand Down Expand Up @@ -277,18 +289,76 @@ func (d *Server) Handle(ctx context.Context, c *jsonrpc2.Conn, r *jsonrpc2.Reque
file.Close()
}

// TODO: use dependency tree
if err := d.FS().Remove(payload.Filepath); err != nil {
// decide if the file will be removed
procId, _ := d.getProcessId(r)
if idx := d.GetFileUseIdx(payload.Filepath, procId); idx != -1 {
// remove the process id from the file use counter
d.fileUseCounter[payload.Filepath] = append(
d.fileUseCounter[payload.Filepath][:idx],
d.fileUseCounter[payload.Filepath][idx+1:]...)

if idx > 0 {
d.ServerLog.Printf("file %q is still in use. removing the client from the file users instead", payload.Filepath)
}
}

if fileConsumers, ok := d.fileUseCounter[payload.Filepath]; !ok || len(fileConsumers) == 0 {
if ok {
delete(d.fileUseCounter, payload.Filepath)
}

if err := d.FS().Remove(payload.Filepath); err != nil {
c.ReplyWithError(ctx, r.ID, &jsonrpc2.Error{
Message: err.Error(),
})
return
}

d.ServerLog.Printf("removed document: %s\n", payload.Filepath)
}

c.Reply(ctx, r.ID, "ok")

// doc := d.engine
case types.RetrieveDocumentMethod:
var payload types.DocumentIdentifier
if err := json.Unmarshal(*r.Params, &payload); err != nil {
c.ReplyWithError(ctx, r.ID, &jsonrpc2.Error{
Message: "Unable to decode params of method " + r.Method,
})
return
}

if len(payload.Filepath) == 0 {
c.ReplyWithError(ctx, r.ID, &jsonrpc2.Error{
Message: "Filepath is empty",
})
return
}

file, err := d.FS().Open(payload.Filepath)
if err != nil {
c.ReplyWithError(ctx, r.ID, &jsonrpc2.Error{
Message: err.Error(),
})

return
} else {
file.Close()
}

d.ServerLog.Printf("removed document: %s\n", payload.Filepath)
c.Reply(ctx, r.ID, "ok")
fileContents, err := d.FS().ReadFile(payload.Filepath)
if err != nil {
c.ReplyWithError(ctx, r.ID, &jsonrpc2.Error{
Message: err.Error(),
})
return
}

// doc := d.engine
c.Reply(ctx, r.ID, types.DocumentPayload{
DocumentIdentifier: payload,
Content: string(fileContents),
})
case types.RetrieveParticipantIdMethod:
c.Reply(ctx, r.ID, d.logger.ParticipantId())
case types.GenerateParticipantIdMethod:
Expand Down Expand Up @@ -483,6 +553,20 @@ func (s *Server) Start(addr string) error {
)
}

func (s *Server) GetFileUseIdx(file string, procId int) int {
if _, ok := s.fileUseCounter[file]; !ok {
return -1
}

for i, id := range s.fileUseCounter[file] {
if id == procId {
return i
}
}

return -1
}

func NewServer() *Server {
server := &Server{
ServerLog: log.New(os.Stdout, "server> ", 0),
Expand All @@ -498,6 +582,7 @@ func NewServer() *Server {
OutputGen: &errgoengine.OutputGenerator{},
},
connectedClients: connectedClients{},
fileUseCounter: map[string][]int{},
errors: []resultError{},
logger: logger.NewMemoryLoggerPanic(),
}
Expand Down
47 changes: 47 additions & 0 deletions server/daemon/server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,10 @@ func TestDeleteDocument(t *testing.T) {
t.Fatal(err)
}

if srv.GetFileUseIdx("hello.py", clientId) == -1 {
t.Fatalf("expected client to be added as user of the file")
}

// wait for the server to process the document
time.Sleep(100 * time.Millisecond)

Expand All @@ -242,6 +246,11 @@ func TestDeleteDocument(t *testing.T) {
t.Fatal(err)
}

// check if the client is removed as user of the file
if srv.GetFileUseIdx("hello.py", clientId) != -1 {
t.Fatalf("expected client to be removed as user of the file")
}

// wait for the server to process the document
time.Sleep(100 * time.Millisecond)

Expand Down Expand Up @@ -342,6 +351,10 @@ func TestUpdateDocument(t *testing.T) {
t.Fatal(err)
}

if srv.GetFileUseIdx("hello.py", clientId) == -1 {
t.Fatalf("expected client to be added as user of the file")
}

// wait for the server to process the document
time.Sleep(100 * time.Millisecond)

Expand Down Expand Up @@ -419,6 +432,40 @@ func TestUpdateDocument_Nonexisting(t *testing.T) {
}
}

func TestRetrieveDocument(t *testing.T) {
clientId := 1
conn, srv, client := Setup()
defer conn.Close()

client.SetId(clientId)
defer client.Close()

if err := client.Connect(); err != nil {
t.Fatal(err)
}

// add first document
expected := "print(a)"
err := client.ResolveDocument("hello.py", expected)
if err != nil {
t.Fatal(err)
}

if srv.GetFileUseIdx("hello.py", clientId) == -1 {
t.Fatalf("expected client to be added as user of the file")
}

// retrieve the document
resp, err := client.RetrieveDocument("hello.py")
if err != nil {
t.Fatal(err)
}

if resp != expected {
t.Fatalf("expected non-empty content, got %s", resp)
}
}

func TestCollect(t *testing.T) {
clientId := 1
conn, _, client := Setup()
Expand Down
7 changes: 4 additions & 3 deletions server/daemon/types/methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@ var (

// document methods
var (
ResolveDocumentMethod = documentsNamespace.methodName("resolve")
UpdateDocumentMethod = documentsNamespace.methodName("update")
DeleteDocumentMethod = documentsNamespace.methodName("delete")
ResolveDocumentMethod = documentsNamespace.methodName("resolve")
UpdateDocumentMethod = documentsNamespace.methodName("update")
DeleteDocumentMethod = documentsNamespace.methodName("delete")
RetrieveDocumentMethod = documentsNamespace.methodName("retrieve")
)

// client methods
Expand Down
27 changes: 6 additions & 21 deletions server/lsp_server/lsp_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import (
"github.com/nedpals/bugbuddy/server/release"
"github.com/nedpals/bugbuddy/server/rpc"
"github.com/nedpals/bugbuddy/server/runner"
"github.com/nedpals/bugbuddy/server/types"
"github.com/sourcegraph/jsonrpc2"
lsp "go.lsp.dev/protocol"
"go.lsp.dev/uri"
Expand All @@ -42,7 +41,6 @@ type LspServer struct {
unpublishedDiagnostics map[uri.URI][]daemonTypes.ErrorReport
publishChan chan int
doneChan chan int
documents map[uri.URI]*types.Rope
}

func mustDecodePayload[T any](ctx context.Context, c *jsonrpc2.Conn, r *jsonrpc2.Request) *T {
Expand Down Expand Up @@ -157,10 +155,9 @@ func (s *LspServer) Handle(ctx context.Context, c *jsonrpc2.Conn, r *jsonrpc2.Re
return
}

s.documents[payload.TextDocument.URI] = types.NewRope(payload.TextDocument.Text)
s.daemonClient.ResolveDocument(
payload.TextDocument.URI.Filename(),
s.documents[payload.TextDocument.URI].ToString(),
payload.TextDocument.Text,
)

s.publishChan <- len(s.unpublishedDiagnostics)
Expand All @@ -170,31 +167,20 @@ func (s *LspServer) Handle(ctx context.Context, c *jsonrpc2.Conn, r *jsonrpc2.Re
return
}

text := s.documents[payload.TextDocument.URI]

// edit the existing text (if any), and send the newly edited version to the daemon
for _, change := range payload.ContentChanges {
startOffset := text.OffsetFromPosition(change.Range.Start)

if len(change.Text) == 0 {
endOffset := text.OffsetFromPosition(change.Range.End)
text.Delete(startOffset, endOffset-startOffset)
} else {
text.Insert(startOffset, change.Text)
}
// NOTE: change the code if TextDocumentKind is set to Incremental
s.daemonClient.UpdateDocument(
payload.TextDocument.URI.Filename(),
change.Text,
)
}

s.daemonClient.UpdateDocument(
payload.TextDocument.URI.Filename(), // TODO:
text.ToString(),
)
case lsp.MethodTextDocumentDidClose:
payload := mustDecodePayload[lsp.DidCloseTextDocumentParams](ctx, c, r)
if payload == nil {
return
}

delete(s.documents, payload.TextDocument.URI)
s.daemonClient.DeleteDocument(
payload.TextDocument.URI.Filename(),
)
Expand Down Expand Up @@ -378,7 +364,6 @@ func Start() error {
unpublishedDiagnostics: map[uri.URI][]daemonTypes.ErrorReport{},
publishChan: make(chan int),
doneChan: doneChan,
documents: map[uri.URI]*types.Rope{},
version: release.Version(),
}

Expand Down
Loading

0 comments on commit 68c5cd8

Please sign in to comment.