Skip to content

Commit 6408b54

Browse files
authored
feat(server): implement file synchronization methods and publish diagnostic notifications (#47)
* feat(server): implement file synchronization methods for document notifications * feat(server): enhance LSP notification handlers with detailed comments * chore: update documentation for file synchronization methods * chore: remove unused imports and clean up code structure * feat(server): implement incremental text document updates and enhance diagnostics handling * test(server): update test case URI for DidChangeTextDocumentParams * fix(server): add mutex for thread-safe diagnostic generation * feat(project): add ModifyFiles method to handle batch file changes * refactor: remove deprecated ModTime field and associated ModifyFiles method * feat(project): implement singleflight to prevent duplicate builder calls in cache methods * refactor(project): remove singleflight usage from cache methods * chore(deps): update gogen to v1.16.8 in go.mod and go.sum * fix(deps): resolve merge conflict in go.sum and update gogen to v1.16.8 * chore(deps): update gop package from v1.3.5 to v1.3.6 * refactor(server): replace PositionOffset method with positionOffset function
1 parent a221c34 commit 6408b54

File tree

5 files changed

+1388
-4
lines changed

5 files changed

+1388
-4
lines changed

gop/proj.go

+4
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@ type fileKey struct {
6565
type File = *FileImpl
6666
type FileImpl struct {
6767
Content []byte
68+
// Deprecated: ModTime is no longer supported due to lsp text sync specification. Use Version instead.
6869
ModTime time.Time
70+
Version int
6971
}
7072

7173
// Project represents a project.
@@ -276,6 +278,7 @@ func (p *Project) FileCache(kind, path string) (any, error) {
276278
if !ok {
277279
return nil, fs.ErrNotExist
278280
}
281+
279282
data, err := builder(p, path, file)
280283
p.fileCaches.Store(key, encodeDataOrErr(data, err))
281284
return data, err
@@ -290,6 +293,7 @@ func (p *Project) Cache(kind string) (any, error) {
290293
if !ok {
291294
return nil, ErrUnknownKind
292295
}
296+
293297
data, err := builder(p)
294298
p.caches.Store(kind, encodeDataOrErr(data, err))
295299
return data, err

internal/server/server.go

+44-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"slices"
88
"strings"
99

10+
gopast "github.com/goplus/gop/ast"
11+
goptoken "github.com/goplus/gop/token"
1012
"github.com/goplus/goxlsw/gop"
1113
"github.com/goplus/goxlsw/internal/analysis"
1214
"github.com/goplus/goxlsw/internal/vfs"
@@ -242,25 +244,26 @@ func (s *Server) handleNotification(n *jsonrpc2.Notification) error {
242244
if err := UnmarshalJSON(n.Params(), &params); err != nil {
243245
return fmt.Errorf("failed to parse didOpen params: %w", err)
244246
}
245-
return errors.New("TODO")
247+
248+
return s.didOpen(&params)
246249
case "textDocument/didChange":
247250
var params DidChangeTextDocumentParams
248251
if err := UnmarshalJSON(n.Params(), &params); err != nil {
249252
return fmt.Errorf("failed to parse didChange params: %w", err)
250253
}
251-
return errors.New("TODO")
254+
return s.didChange(&params)
252255
case "textDocument/didSave":
253256
var params DidSaveTextDocumentParams
254257
if err := UnmarshalJSON(n.Params(), &params); err != nil {
255258
return fmt.Errorf("failed to parse didSave params: %w", err)
256259
}
257-
return errors.New("TODO")
260+
return s.didSave(&params)
258261
case "textDocument/didClose":
259262
var params DidCloseTextDocumentParams
260263
if err := UnmarshalJSON(n.Params(), &params); err != nil {
261264
return fmt.Errorf("failed to parse didClose params: %w", err)
262265
}
263-
return errors.New("TODO")
266+
return s.didClose(&params)
264267
}
265268
return nil
266269
}
@@ -333,3 +336,40 @@ func (s *Server) fromDocumentURI(documentURI DocumentURI) (string, error) {
333336
func (s *Server) toDocumentURI(path string) DocumentURI {
334337
return DocumentURI(string(s.workspaceRootURI) + path)
335338
}
339+
340+
// fromPosition converts a token.Position to an LSP Position.
341+
func (s *Server) fromPosition(astFile *gopast.File, position goptoken.Position) Position {
342+
tokenFile := s.getProj().Fset.File(astFile.Pos())
343+
344+
line := position.Line
345+
lineStart := int(tokenFile.LineStart(line))
346+
relLineStart := lineStart - tokenFile.Base()
347+
lineContent := astFile.Code[relLineStart : relLineStart+position.Column-1]
348+
utf16Offset := utf8OffsetToUTF16(string(lineContent), position.Column-1)
349+
350+
return Position{
351+
Line: uint32(position.Line - 1),
352+
Character: uint32(utf16Offset),
353+
}
354+
}
355+
356+
// rangeForASTFilePosition returns a [Range] for the given position in an AST file.
357+
func (s *Server) rangeForASTFilePosition(astFile *gopast.File, position goptoken.Position) Range {
358+
p := s.fromPosition(astFile, position)
359+
return Range{Start: p, End: p}
360+
}
361+
362+
// rangeForPos returns the [Range] for the given position.
363+
func (s *Server) rangeForPos(pos goptoken.Pos) Range {
364+
return s.rangeForASTFilePosition(s.posASTFile(pos), s.getProj().Fset.Position(pos))
365+
}
366+
367+
// posASTFile returns the AST file for the given position.
368+
func (s *Server) posASTFile(pos goptoken.Pos) *gopast.File {
369+
return getASTPkg(s.getProj()).Files[s.posFilename(pos)]
370+
}
371+
372+
// posFilename returns the filename for the given position.
373+
func (s *Server) posFilename(pos goptoken.Pos) string {
374+
return s.getProj().Fset.Position(pos).Filename
375+
}

0 commit comments

Comments
 (0)