-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: parse out word for errors and clear them #1163
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,15 @@ | ||
package lsp | ||
|
||
import ( | ||
"bufio" | ||
"context" | ||
"errors" | ||
"fmt" | ||
"os" | ||
"strings" | ||
"unicode" | ||
|
||
"github.com/puzpuzpuz/xsync/v3" | ||
_ "github.com/tliron/commonlog/simple" | ||
"github.com/tliron/glsp" | ||
protocol "github.com/tliron/glsp/protocol_3_16" | ||
|
@@ -22,10 +26,10 @@ const lsName = "ftl-language-server" | |
// Server is a language server. | ||
type Server struct { | ||
server *glspServer.Server | ||
glspLogger *GLSPLogger | ||
glspContext *glsp.Context | ||
handler protocol.Handler | ||
logger log.Logger | ||
diagnostics *xsync.MapOf[protocol.DocumentUri, []protocol.Diagnostic] | ||
} | ||
|
||
// NewServer creates a new language server. | ||
|
@@ -38,9 +42,9 @@ func NewServer(ctx context.Context) *Server { | |
} | ||
s := glspServer.NewServer(&handler, lsName, false) | ||
server := &Server{ | ||
server: s, | ||
glspLogger: NewGLSPLogger(s.Log), | ||
logger: *log.FromContext(ctx), | ||
server: s, | ||
logger: *log.FromContext(ctx).Scope("lsp"), | ||
diagnostics: xsync.NewMapOf[protocol.DocumentUri, []protocol.Diagnostic](), | ||
} | ||
handler.Initialize = server.initialize() | ||
return server | ||
|
@@ -56,6 +60,19 @@ func (s *Server) Run() error { | |
|
||
type errSet map[string]schema.Error | ||
|
||
// BuildStarted clears diagnostics for the given directory. New errors will arrive later if they still exist. | ||
func (s *Server) BuildStarted(dir string) { | ||
dirURI := "file://" + dir | ||
|
||
s.diagnostics.Range(func(uri protocol.DocumentUri, diagnostics []protocol.Diagnostic) bool { | ||
if strings.HasPrefix(uri, dirURI) { | ||
s.diagnostics.Delete(uri) | ||
s.publishDiagnostics(uri, []protocol.Diagnostic{}) | ||
} | ||
return true | ||
}) | ||
} | ||
|
||
// Post sends diagnostics to the client. err must be joined schema.Errors. | ||
func (s *Server) post(err error) { | ||
errByFilename := make(map[string]errSet) | ||
|
@@ -83,26 +100,41 @@ func publishErrors(errByFilename map[string]errSet, s *Server) { | |
pp := e.Pos | ||
sourceName := "ftl" | ||
severity := protocol.DiagnosticSeverityError | ||
|
||
length, err := getLineOrWordLength(filename, pp.Line, pp.Column, false) | ||
if err != nil { | ||
s.logger.Errorf(err, "Failed to get line or word length") | ||
continue | ||
} | ||
endColumn := pp.Column + length | ||
|
||
diagnostics = append(diagnostics, protocol.Diagnostic{ | ||
Range: protocol.Range{ | ||
Start: protocol.Position{Line: uint32(pp.Line - 1), Character: uint32(pp.Column - 1)}, | ||
End: protocol.Position{Line: uint32(pp.Line - 1), Character: uint32(pp.Column + 10 - 1)}, | ||
End: protocol.Position{Line: uint32(pp.Line - 1), Character: uint32(endColumn - 1)}, | ||
}, | ||
Severity: &severity, | ||
Source: &sourceName, | ||
Message: e.Msg, | ||
}) | ||
} | ||
|
||
if s.glspContext == nil { | ||
return | ||
} | ||
uri := "file://" + filename | ||
s.diagnostics.Store(uri, diagnostics) | ||
s.publishDiagnostics(uri, diagnostics) | ||
} | ||
} | ||
|
||
go s.glspContext.Notify(protocol.ServerTextDocumentPublishDiagnostics, protocol.PublishDiagnosticsParams{ | ||
URI: "file://" + filename, | ||
Diagnostics: diagnostics, | ||
}) | ||
func (s *Server) publishDiagnostics(uri protocol.DocumentUri, diagnostics []protocol.Diagnostic) { | ||
s.logger.Debugf("Publishing diagnostics for %s\n", uri) | ||
if s.glspContext == nil { | ||
return | ||
} | ||
|
||
go s.glspContext.Notify(protocol.ServerTextDocumentPublishDiagnostics, protocol.PublishDiagnosticsParams{ | ||
URI: uri, | ||
Diagnostics: diagnostics, | ||
}) | ||
} | ||
|
||
func (s *Server) initialize() protocol.InitializeFunc { | ||
|
@@ -123,14 +155,6 @@ func (s *Server) initialize() protocol.InitializeFunc { | |
}, nil | ||
} | ||
} | ||
func (s *Server) clearDiagnosticsOfDocument(uri protocol.DocumentUri) { | ||
go func() { | ||
go s.glspContext.Notify(protocol.ServerTextDocumentPublishDiagnostics, protocol.PublishDiagnosticsParams{ | ||
URI: uri, | ||
Diagnostics: []protocol.Diagnostic{}, | ||
}) | ||
}() | ||
} | ||
|
||
func initialized(context *glsp.Context, params *protocol.InitializedParams) error { | ||
return nil | ||
|
@@ -149,3 +173,36 @@ func setTrace(context *glsp.Context, params *protocol.SetTraceParams) error { | |
protocol.SetTraceValue(params.Value) | ||
return nil | ||
} | ||
|
||
// getLineOrWordLength returns the length of the line or the length of the word starting at the given column. | ||
// If wholeLine is true, it returns the length of the entire line. | ||
// If wholeLine is false, it returns the length of the word starting at the column. | ||
func getLineOrWordLength(filePath string, lineNum, column int, wholeLine bool) (int, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While this is probably okay as a stop-gap, we should really be extending the error type to include range information so we can include it when the errors are created, as this is incredibly inefficient. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I started down this path and got pretty hung up on parser stuff (I think that was the source of the issue). It was a bit of a time suck so I just put this in for now to keep moving forward. I suspect it had something to do with how our There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Your assumption is correct: the Position type is a mirror of participles so you'll need to either add the end location to the error rather than the position, or include it in our position type and write a function to do the mapping. |
||
file, err := os.Open(filePath) | ||
if err != nil { | ||
return 0, err | ||
} | ||
defer file.Close() | ||
|
||
scanner := bufio.NewScanner(file) | ||
currentLine := 1 | ||
for scanner.Scan() { | ||
if currentLine == lineNum { | ||
lineText := scanner.Text() | ||
if wholeLine { | ||
return len(lineText), nil | ||
} | ||
start := column - 1 | ||
end := start | ||
for end < len(lineText) && !unicode.IsSpace(rune(lineText[end])) { | ||
end++ | ||
} | ||
return end - start, nil | ||
} | ||
currentLine++ | ||
} | ||
if err := scanner.Err(); err != nil { | ||
return 0, err | ||
} | ||
return 0, os.ErrNotExist | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aha!